]> git.earman.xyz Git - emacsinit.git/commitdiff
Go to split config, add qmk functions
authorrearman <rearman@r717.net>
Sat, 7 Jan 2023 19:51:51 +0000 (14:51 -0500)
committerrearman <rearman@r717.net>
Sat, 7 Jan 2023 19:51:51 +0000 (14:51 -0500)
Splitting init.el into multiple *.el files in .emacs.d.  Adding defuns and
eshell alias for some qmk-related tasks - editing keymaps, and calling qmk msys.

.gitignore
bindings.el [new file with mode: 0644]
defuns.el [new file with mode: 0644]
eshell/alias
hooks-puts.el [new file with mode: 0644]
init.el
org-setup.el [new file with mode: 0644]
package-config.el [new file with mode: 0644]
setq-defaults.el [new file with mode: 0644]
work-eqs.el [new file with mode: 0644]

index dc85ccf6a02f36c4e3ee20deb7e262ae4e0867d0..09386f9bcf920c43234b877d5f0e043ce13f0328 100644 (file)
@@ -1,5 +1,3 @@
 *
 !/eshell/alias
-!/config
-!early-init.el
-!init.el
+!/*.el
diff --git a/bindings.el b/bindings.el
new file mode 100644 (file)
index 0000000..5b8b11c
--- /dev/null
@@ -0,0 +1,43 @@
+;; -*- lexical-binding: t; -*-
+;; BINDINGS
+;; USER SPACE
+(global-set-key (kbd "C-c a") 'org-agenda)
+(global-set-key (kbd "C-c b") 'buffer-menu-other-window)
+(global-set-key (kbd "C-c c") 'org-capture)
+(global-set-key (kbd "C-c ei") 'edit-init)
+(global-set-key (kbd "C-c r") 'rotate-windows)
+(global-set-key (kbd "C-c s") 'eshell)
+(global-set-key (kbd "C-c t") 'toggle-window-split)
+(global-set-key (kbd "C-c w") 'edit-work-eqs)
+(global-set-key (kbd "<f8>") 'recentf-open-files)
+;; OVERRIDES
+(global-set-key [remap move-beginning-of-line] 'smart-beginning-of-line)
+(global-set-key (kbd "M-j") 'backward-join-line)
+(global-set-key (kbd "M-S-j") 'join-line)
+(global-set-key (kbd "C-S-k") 'kill-whole-line)
+(global-set-key (kbd "C-o") 'open-line-below)
+(global-set-key (kbd "M-o") 'open-line-above)
+(global-set-key (kbd "M-s .") 'isearch-forward-thing-at-point)
+(global-set-key (kbd "M-s M-.") 'isearch-forward-symbol-at-point)
+(global-set-key (kbd "C-u") 'backward-kill-line)
+(global-set-key (kbd "C-w") 'kill-bword-or-region)
+(global-set-key (kbd "C-z") 'zap-up-to-char)
+(global-set-key (kbd "C-M-z") 'eval-region)
+(global-set-key (kbd "C-'") 'universal-argument)
+(global-set-key (kbd "C-.") 'next-window-any-frame)
+(global-set-key (kbd "C-,") 'previous-window-any-frame)
+(global-set-key (kbd "C-x C-b") 'buffer-menu)
+(global-set-key (kbd "C-x .") 'next-buffer)
+(global-set-key (kbd "C-x ,") 'previous-buffer)
+(global-set-key (kbd "C-%") 'replace-regexp)
+(global-set-key (kbd "M-%") 'slot/replace-string-many)
+(global-set-key (kbd "C-M-%") 'slot/query-replace-many)
+;; MODE-SPECIFIC
+(add-hook 'eshell-mode-hook (lambda ()
+                             (define-key eshell-mode-map (kbd ")") 'eshell-send-on-close-paren)))
+(add-hook 'org-mode-hook (lambda ()
+                          (define-key org-mode-map (kbd "C-'") nil)
+                          (define-key org-mode-map (kbd "C-,") nil)))
+(add-hook 'org-agenda-mode-hook (lambda ()
+                                 (define-key org-agenda-mode-map (kbd "C-'") nil)
+                                 (define-key org-agenda-mode-map (kbd "C-,") nil)))
diff --git a/defuns.el b/defuns.el
new file mode 100644 (file)
index 0000000..32647b7
--- /dev/null
+++ b/defuns.el
@@ -0,0 +1,216 @@
+;; -*- lexical-binding: t; -*-
+;; DEFUNS
+(defun open-line-below ()
+  "Creates a new empty line below the current line."
+  (interactive)
+  (end-of-line)
+  (newline)
+  (indent-for-tab-command))
+
+(defun open-line-above ()
+  "Creates a new empty line above the current line.
+Can't go prev line first, edge case of beginning of buffer."
+  (interactive)
+  (beginning-of-line)
+  (newline)
+  (previous-line)
+  (indent-for-tab-command))
+
+(defun kill-bword-or-region ()
+  "Kill region if active, otherwise kill back one word."
+  (interactive)
+  (if (region-active-p)
+      (call-interactively 'kill-region)
+    (call-interactively 'backward-kill-word)))
+
+(defun backward-kill-line ()
+  "Kill back to beginning of line from point."
+  (interactive)
+  (kill-line 0))
+
+(defun backward-join-line ()
+  "A wrapper for join-line to make it go in the right direction."
+  (interactive)
+  (join-line 0))
+
+(defun edit-init ()
+  "Bring up init.el for editing."
+  (interactive)
+  (find-file user-init-file))
+
+(defun edit-work-eqs ()
+  "Bring up work-eqs.el for editing."
+  (interactive)
+  (find-file work-eqns))
+
+(defun toggle-window-split ()
+  "If two windows are open, toggle their split layout between vert. and horiz.
+Stolen from http://whattheemacsd.com"
+  (interactive)
+  (if (= (count-windows) 2)
+      (let* ((this-win-buffer (window-buffer))
+            (next-win-buffer (window-buffer (next-window)))
+            (this-win-edges (window-edges (selected-window)))
+            (next-win-edges (window-edges (next-window)))
+            (this-win-2nd (not (and (<= (car this-win-edges)
+                                        (car next-win-edges))
+                                    (<= (cadr this-win-edges)
+                                        (cadr next-win-edges)))))
+            (splitter
+             (if (= (car this-win-edges)
+                    (car (window-edges (next-window))))
+                 'split-window-horizontally
+               'split-window-vertically)))
+       (delete-other-windows)
+       (let ((first-win (selected-window)))
+         (funcall splitter)
+         (if this-win-2nd (other-window 1))
+         (set-window-buffer (selected-window) this-win-buffer)
+         (set-window-buffer (next-window) next-win-buffer)
+         (select-window first-win)
+         (if this-win-2nd (other-window 1))))))
+
+(defun rotate-windows ()
+  "Rotate your windows.
+Stolen from http://whattheemacsd.com"
+  (interactive)
+  (cond ((not (> (count-windows)1))
+        (message "You can't rotate a single window!"))
+       (t
+        (setq i 1)
+        (setq numWindows (count-windows))
+        (while  (< i numWindows)
+          (let* (
+                 (w1 (elt (window-list) i))
+                 (w2 (elt (window-list) (+ (% i numWindows) 1)))
+
+                 (b1 (window-buffer w1))
+                 (b2 (window-buffer w2))
+
+                 (s1 (window-start w1))
+                 (s2 (window-start w2))
+                 )
+            (set-window-buffer w1  b2)
+            (set-window-buffer w2 b1)
+            (set-window-start w1 s2)
+            (set-window-start w2 s1)
+            (setq i (1+ i)))))))
+
+(defun smart-beginning-of-line ()
+  "Move point to first non-whitespace character or beginning-of-line.
+If point was already at that position, move point to beginning of line.
+Stolen from BrettWitty's dotemacs github repo."
+  (interactive "^")
+  (let ((oldpos (point)))
+    (back-to-indentation)
+    (and (= oldpos (point))
+        (beginning-of-line))))
+
+(defun unfuck-aveva-license ()
+  "Remove the offending xml files when aveva can't find the license."
+  (interactive)
+  (let ((xml1 "c:/ProgramData/AVEVA/Licensing/License API2/Data/LocalAcquireInfo.xml")
+       (xml2 "c:/ProgramData/AVEVA/Licensing/License API2/Data/LocalBackEndAcquireInfo.xml"))
+    (when (file-exists-p xml1) (delete-file xml1))
+    (when (file-exists-p xml2) (delete-file xml2))))
+
+(defun eshell-send-on-close-paren ()
+  "Makes eshell act somewhat like genera.
+Makes a closing paren execute the sexp.  Currently in test, look out for errors."
+  (interactive)
+  (insert-char ?\))
+  (let ((p (point)))
+    ;;(eshell-bol)
+    ;;(syntax-ppss-flush-cache (point))
+    ;;(goto-char p)
+    (cond ((= 0 (car (syntax-ppss)))
+          (eshell-send-input))
+         ((< (car (syntax-ppss)) 0)
+          (message "Check flush-cache"))
+         ((t nil)))))
+
+(defun slot/get-queries (title &optional pairs)
+  "Get multiple `query-replace' pairs from the user.
+PAIRS is a list of replacement pairs of the form (FROM . TO).
+Stolen from https://tony-zorman.com/posts/query-replace/2022-08-06-query-replace-many.html"
+  (-let* (((from to delim arg)
+          (query-replace-read-args
+           (s-join " "
+                   (-non-nil
+                    (list title
+                          (cond ((eq current-prefix-arg '-) "backward")
+                                (current-prefix-arg         "word"))
+                          (when (use-region-p) "in region"))))
+           nil))                       ; no regexp-flag
+         (from-to (cons (regexp-quote from)
+                        (s-replace "\\" "\\\\" to))))
+    ;; HACK: Since the default suggestion of replace.el will be
+    ;; the last one we've entered, an empty string will give us
+    ;; exactly that.  Instead of trying to fight against this,
+    ;; use it in order to signal an exit.
+    (if (-contains? pairs from-to)
+       (list pairs delim arg)
+      (slot/get-queries (push from-to pairs)))))
+
+(defun slot/replace-string-many
+    (pairs &optional delimited start end backward region-noncontiguous-p)
+  "Like `replace-string', but query for several replacements.
+Query for replacement pairs until the users enters an empty
+string (but see `slot/get-queries').
+
+Refer to `query-replace' and `perform-replace' for what the other
+arguments actually mean.
+Stolen from https://tony-zorman.com/posts/query-replace/2022-08-06-query-replace-many.html
+Edited and renamed to remove the Query."
+  (interactive
+   (let ((common (slot/get-queries "Replace strings")))
+     (list (nth 0 common) (nth 1 common)
+          (if (use-region-p) (region-beginning))
+          (if (use-region-p) (region-end))
+          (nth 2 common)
+          (if (use-region-p) (region-noncontiguous-p)))))
+  (perform-replace
+   (concat "\\(?:" (mapconcat #'car pairs "\\|") "\\)") ; build query
+   (cons (lambda (pairs _count)
+          (cl-loop for (from . to) in pairs
+                   when (string-match from (match-string 0))
+                   return to))
+        pairs)
+   nil :regexp
+   delimited nil nil start end backward region-noncontiguous-p))
+
+(defun slot/query-replace-many
+    (pairs &optional delimited start end backward region-noncontiguous-p)
+  "Like `query-replace', but query for several replacements.
+Query for replacement pairs until the users enters an empty
+string (but see `slot/get-queries').
+
+Refer to `query-replace' and `perform-replace' for what the other
+arguments actually mean.
+Stolen from https://tony-zorman.com/posts/query-replace/2022-08-06-query-replace-many.html"
+  (interactive
+   (let ((common (slot/get-queries "Query Replace Strings")))
+     (list (nth 0 common) (nth 1 common)
+          (if (use-region-p) (region-beginning))
+          (if (use-region-p) (region-end))
+          (nth 2 common)
+          (if (use-region-p) (region-noncontiguous-p)))))
+  (perform-replace
+   (concat "\\(?:" (mapconcat #'car pairs "\\|") "\\)") ; build query
+   (cons (lambda (pairs _count)
+          (cl-loop for (from . to) in pairs
+                   when (string-match from (match-string 0))
+                   return to))
+        pairs)
+   :query :regexp
+   delimited nil nil start end backward region-noncontiguous-p))
+
+(defun edit-keymap-planck ()
+  "Bring up planck keymap for editing."
+  (interactive)
+  (find-file "~/qmk_firmware/keyboards/planck/keymaps/ehrman/keymap.c"))
+
+(defun edit-keymap-tokyo60 ()
+  "Bring up tokyo60 keymap for editing."
+  (interactive)
+  (find-file "~/qmk_firmware/keyboards/tokyokeyboard/tokyo60/keymaps/ehrman/keymap.c"))
index 78ab70ee4fe95c0fc65b163dedd24d8052876f02..29107727ec59faef1d0663608d758241d98a1196 100644 (file)
@@ -1 +1,2 @@
 alias weather clear -l && curl https://wttr.in
+alias qmk c:/QMK_MSYS/conemu/ConEmu64.exe
diff --git a/hooks-puts.el b/hooks-puts.el
new file mode 100644 (file)
index 0000000..2a57b02
--- /dev/null
@@ -0,0 +1,10 @@
+;; -*- lexical-binding: t; -*-
+;; HOOKS
+(add-hook 'before-save-hook 'whitespace-cleanup)
+(add-hook 'dired-mode-hook 'dired-hide-details-mode t)
+
+;; PUTS AND PUSHES
+(put 'upcase-region 'disabled nil)
+(put 'downcase-region 'disabled nil)
+(put 'narrow-to-region 'disabled nil)
+(put 'dired-find-alternate-file 'disabled nil)
diff --git a/init.el b/init.el
index 980f8ebf809d0f568bf845151e119cc2bf87dbf5..b051f2a4962c061fe2cddb320200258605babd48 100644 (file)
--- a/init.el
+++ b/init.el
@@ -4,13 +4,13 @@
     (progn (setq delete-by-moving-to-trash t))
   nil)
 
-(load (expand-file-name "config/work-eqs.el" user-emacs-directory))
-(load (expand-file-name "config/package-config.el" user-emacs-directory))
-(load (expand-file-name "config/setq-defaults.el" user-emacs-directory))
-(load (expand-file-name "config/defuns.el" user-emacs-directory))
-(load (expand-file-name "config/org-setup.el" user-emacs-directory))
-(load (expand-file-name "config/hooks-puts.el" user-emacs-directory))
-(load (expand-file-name "config/bindings.el" user-emacs-directory))
+(load (expand-file-name "work-eqs.el" user-emacs-directory))
+(load (expand-file-name "package-config.el" user-emacs-directory))
+(load (expand-file-name "setq-defaults.el" user-emacs-directory))
+(load (expand-file-name "defuns.el" user-emacs-directory))
+(load (expand-file-name "org-setup.el" user-emacs-directory))
+(load (expand-file-name "hooks-puts.el" user-emacs-directory))
+(load (expand-file-name "bindings.el" user-emacs-directory))
 
 (custom-set-variables
  ;; custom-set-variables was added by Custom.
diff --git a/org-setup.el b/org-setup.el
new file mode 100644 (file)
index 0000000..d40d529
--- /dev/null
@@ -0,0 +1,54 @@
+;; -*- lexical-binding: t; -*-
+;; ORG SETTINGS AND DEFUNS
+(defun erfassen-zettel ()
+  "Add a new zettel to the kasten.
+Creates a new file <datestamp>-name.org in ~/org/kasten."
+  (interactive)
+  (let ((name (read-string "Zettel-Name: ")))
+  (expand-file-name (format "%s-%s.org" (format-time-string "%Y-%m-%d-%H%M") name) "~/org/kasten/")))
+
+(defun org-auto-archive ()
+  "Automatically archive completed tasks in an org file.
+Intended for use as an after-save-hook."
+  (interactive)
+  (org-map-entries
+   (lambda ()
+     (org-archive-subtree)
+     (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
+   "TODO=\"DONE\"|TODO=\"CANCELLED\""
+   'file)
+  (save-buffer))
+
+(setq org-M-RET-may-split-line nil
+      org-startup-folded t
+      org-reverse-note-order t
+      org-use-fast-todo-selection 'expert
+      org-agenda-start-on-weekday nil
+      org-catch-invisible-edits 'show
+      org-directory "~/org"
+      org-agenda-files '("~/org/"
+                        "~/org/kasten/")
+      org-refile-targets '((nil :maxlevel . 9)
+                          (org-agenda-files :maxlevel . 9))
+      org-default-notes-file (concat org-directory "/notes.org")
+      safe-local-variable-values '((after-save-hook org-auto-archive))
+      org-todo-keywords '((sequence "TODO(t@)"
+                                   "WAITING(w@)"
+                                   "IN-PROGRESS(i@)"
+                                   "DELEGATED(l@)"
+                                   "APPT(a@)" "|"
+                                   "DONE(d@)"
+                                   "CANCELLED(c@)"))
+      org-capture-templates '(("n" "Note" entry (file+olp org-default-notes-file "Notes") "* %u %?")
+                             ("t" "TODO" entry (file+olp org-default-notes-file "Tasks") "* TODO %? \n %u")
+                             ("s" "Service" entry (file+olp org-default-notes-file "Service") "* TODO %? \n %u")
+                             ("z" "Zettel" entry (file erfassen-zettel) "* %? ::"))
+      org-agenda-custom-commands '(("n" "Agenda and all TODOs"
+                                   ((agenda "") (alltodo "")))
+                                  ("u" "Unscheduled TODOs"
+                                   alltodo "" ((org-agenda-skip-function
+                                                (lambda nil
+                                                  (org-agenda-skip-entry-if 'scheduled
+                                                                            'deadline
+                                                                            'regexp "\n]+>")))
+                                               (org-agenda-overriding-header "Unscheduled TODO entries: ")))))
diff --git a/package-config.el b/package-config.el
new file mode 100644 (file)
index 0000000..4beb122
--- /dev/null
@@ -0,0 +1,58 @@
+;; -*- lexical-binding: t; -*-
+
+;; PACKAGES
+;; NEW STRAIGHT.EL CONFIG
+(defvar bootstrap-version)
+(let ((bootstrap-file
+       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
+      (bootstrap-version 6))
+  (unless (file-exists-p bootstrap-file)
+    (with-current-buffer
+       (url-retrieve-synchronously
+        "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
+        'silent 'inhibit-cookies)
+      (goto-char (point-max))
+      (eval-print-last-sexp)))
+  (load bootstrap-file nil 'nomessage))
+
+(straight-use-package 'use-package)
+
+(use-package straight
+  :custom (straight-use-package-by-default t))
+
+(use-package company
+  :custom
+  (company-idle-delay 0)
+  (company-minimum-prefix-length 3)
+  (company-selection-wrap-around t)
+  (company-dabbrev-other-buffers t)
+  (company-dabbrev-code-other-buffers t)
+  (company-dabbrev-downcase nil)
+  (company-dabbrev-ignore-case t)
+  :config
+  (global-company-mode t))
+
+(use-package expand-region
+  :bind (("C-=" . er/expand-region)))
+
+(use-package magit
+  :init
+  (message "Loading Magit...")
+  :config
+  (message "Loaded Magit!")
+  :bind (("C-x g" . magit-status)))
+
+(use-package gnuplot-mode)
+
+(use-package openwith
+  :config
+  (openwith-mode t)
+  (setq openwith-associations '(("\\.pdf\\'" "sumatrapdf" (file))
+                               ("\\.xls\\'" "excel" (file))
+                               ("\\.xlsx\\'" "excel" (file))
+                               ("\\.doc\\'" "word" (file))
+                               ("\\.docx\\'" "word" (file))
+                               ("\\.adpro\\'" "PoductivitySuite" (file)))))
+(use-package s)
+
+(use-package dash)
diff --git a/setq-defaults.el b/setq-defaults.el
new file mode 100644 (file)
index 0000000..0395b9c
--- /dev/null
@@ -0,0 +1,47 @@
+;; -*- lexical-binding: t; -*-
+;; SETQ AND DEFAULTS
+(setq-default indicate-empty-lines t
+             fill-column 80
+             read-file-name-completion-ignore-case t
+             read-buffer-completion-ignore-case t
+             cursor-type 'bar
+             cursor-in-non-selected-windows nil)
+
+(setq default-directory "~/"
+      inhibit-startup-message t
+      initial-scratch-message nil
+      server-client-instructions nil
+      require-final-newline t
+      use-short-answers t
+      mode-line-compact t
+      make-backup-files nil
+      auto-save-default nil
+      confirm-nonexistent-file-or-buffer nil
+      show-paren-delay 0
+      show-paren-style 'expression
+      blink-matching-paren 'jump
+      global-hl-line-sticky-flag t
+      echo-keystrokes 0.01
+      save-interprogram-paste-before-kill t)
+
+;; OPTIONS
+(recentf-mode t)
+(fringe-mode 1)
+(menu-bar-mode -1)
+(tool-bar-mode -1)
+(scroll-bar-mode -1)
+(show-paren-mode t)
+(auto-fill-mode t)
+(midnight-mode t)
+(mouse-avoidance-mode 'animate)
+(column-number-mode t)
+(delete-selection-mode t)
+(global-hl-line-mode t)
+(global-prettify-symbols-mode t)
+(global-display-line-numbers-mode t)
+(set-face-background 'show-paren-match nil)
+(set-face-underline 'show-paren-match t)
+(add-to-list 'default-frame-alist '(background-color . "#cae0a6"))
+
+(unless (display-graphic-p)
+  (xterm-mouse-mode 1))
diff --git a/work-eqs.el b/work-eqs.el
new file mode 100644 (file)
index 0000000..d217c77
--- /dev/null
@@ -0,0 +1,145 @@
+;; WORK (NON-EDITING) RELATED DEFUNS
+(defun square (x)
+  "Calculate the square of a value."
+  (expt x 2))
+
+(defun cube (x)
+  "Calculate the cube of a value."
+  (expt x 3))
+
+(defun inv (x)
+  "Calculate the inverse of a value."
+  (/ 1 x))
+
+(defun scaleval (pmax pmin emax emin &optional ai)
+  "Calculate the slope and offset given PLC Max/Min and Eng. Max/Min.
+With optional argument 'ai', also calculate a final scaled value from an input."
+  (let ((div (/ (- pmax pmin) (- (float emax) (float emin)))))
+    (let ((ofst (- emin (/ pmin div))))
+      (if (eq nil ai)
+         (list div ofst)
+       (let ((final (+ ofst (/ ai div))))
+         (list div ofst final))))))
+
+(defun cfm-circ (fpm radius)
+  "Calculate the CFM of a circular duct given fpm and radius (in)."
+  (list (* pi fpm (square (/ radius 12)))))
+
+(defun cfm-rect (fpm width height)
+  "Calculate the CFM of a rectangular duct given fpm, width (in) and height (in)."
+  (list (* fpm (/ width 12) (/ height 12))))
+
+(defun pid-tune (Ku Tu &optional p-type)
+  "Use the Ziegler-Nichols method to tune a P, PI or PID loop.
+Takes Ku, Tu and optional p-type (P, PI, or PID [default]) as arguments,
+ and returns appropriate kp, ki, and kd."
+  (cond ((or (equal 'p p-type)
+            (equal 'P p-type))
+        (list (* 0.5 Ku)))
+       ((or (equal 'pi p-type)
+            (equal 'PI p-type))
+        (list (* 0.45 Ku) (/ (* 0.54 Ku) Tu)))
+       (t (list (* 0.6 Ku) (/ (* 1.2 Ku) Tu) (/ (* 3.0 Ku Tu) 40)))))
+
+(defun gn-water-per-lb (temp humidity)
+  "Calculate the grains of water per lb of air, given temperature (F) and humidity (%).
+Returns a list of Sat. Water Press., Hum. Ratio, and Gns water/lb air."
+  (let ((sat-water-press (+ .0182795
+                           (* temp .001029904)
+                           (* (square temp) 0.00002579408)
+                           (* (cube temp) (* 2.400493 (expt 10 -7)))
+                           (* (expt temp 4) (* 8.100939 (expt 10 -10)))
+                           (* (expt temp 5) (* 3.256805 (expt 10 -11)))
+                           (* (expt temp 6) (* -1.001922 (expt 10 -13)))
+                           (* (expt temp 7) (* 2.44161 (expt 10 -16))))))
+    (let ((hum-press (* (/ humidity 100.0) sat-water-press)))
+      (let ((hum-ratio (/ (* hum-press 0.62198) (- 14.7 hum-press))))
+       (let ((gns-water-lb-air (* hum-ratio 7000)))
+         (list sat-water-press hum-ratio gns-water-lb-air))))))
+
+(defun mm-to-in (mm)
+  "Convert milimeters to inches."
+  (/ mm 25.4))
+
+(defun in-to-mm (in)
+  "Convert milimeters to inches."
+  (* in 25.4))
+
+(defun k-to-c (tempk)
+  "Convert degrees Kelvin to degrees Celsius."
+  (- tempk 273.15))
+
+(defun c-to-k (tempc)
+  "Convert degrees Celsius to degrees Kelvin."
+  (+ tempc 273.15))
+
+(defun c-to-f (tempc)
+  "Convert degrees Celsius to degrees Fahrenheit."
+  (+ (* tempc 1.8) 32.0))
+
+(defun f-to-c (tempf)
+  "Convert degrees Fahrenheit to degrees Celsius."
+  (/ (- tempf 32.0) 1.8))
+
+(defun k-to-f (tempk)
+  "Convert degrees Kelvin to degrees Fahrenheit.
+Applies `c-to-f' to `k-to-c'."
+  (c-to-f (k-to-c tempk)))
+
+(defun f-to-k (tempf)
+  "Convert degrees Fahrenheit to degrees Kelvin.
+Applies `c-to-k' to `f-to-c'."
+  (c-to-k (f-to-c tempf)))
+
+(defun max-counts (resolution)
+  "Calculate the max count for a PLC analog, given card's bit-resolution.
+Formula is 1 - 2^(resolution)"
+  (- (expt 2 resolution) 1))
+
+(defun volts-to-counts (vin vmax resolution)
+  "Convert a voltage signal to a PLC count.
+Calculates a ratio of vin/vmax, then scales by `max-counts'."
+  (* (/ (float vin) (float vmax)) (float (max-counts resolution))))
+
+(defun counts-to-volts (cin vmax resolution)
+  "Convert a PLC count to a voltage.
+Calculates a ratio of cin/`max-counts', then multiplies by vmax."
+  (* (/ cin (float (max-counts resolution))) vmax))
+
+(defun eff-imp (r1 &optional r2)
+  "Calculate the effective input impedance, given two resistances.
+For use in `amps-to-volts' and related.  The handling of only one resistance
+given is done here, instead of doing it in every function that uses this."
+  (if (eq nil r2)
+      r1
+    (inv (+ (inv (float r1)) (inv (float r2))))))
+
+(defun amps-to-volts (amps r1 &optional r2)
+  "Calculate the voltage, given amperage and impedance.
+Multiplies the amperage by the effective impedance calculated with `eff-imp'."
+  (* amps (eff-imp r1 r2)))
+
+(defun volts-to-amps (volts r1 &optional r2)
+  "Calculate the amperage, given voltage and impedance.
+Divides the voltage by the effective impedance calculated with `eff-imp'."
+  (/ volts (eff-imp r1 r2)))
+
+(defun amps-to-counts (amps vmax resolution r1 &optional r2)
+  "Convert an amp signal to PLC Counts.
+Converts the amperage to a voltage using `amps-to-volts' (with r1 and optional r2),
+then applies `volts-to-counts' to the resulting voltage, vmax, and resolution."
+  (volts-to-counts (amps-to-volts amps r1 r2) vmax resolution))
+
+(defun count-range (type upper lower vmax resolution &optional r1 r2)
+  "Given an upper and lower signal, return the list of upper and lower PLC counts.
+Type takes either a v or an i, corresponding to a voltage or current signal.
+With a current signal, use r1 and maybe r2 to calculate `amps-to-counts'.
+Otherwise ignore r1/r2 and calculate `volts-to-counts'."
+  (cond ((or (equal 'v type)
+            (equal 'V type))
+        (list (volts-to-counts upper vmax resolution)
+              (volts-to-counts lower vmax resolution)))
+       ((or (equal 'i type)
+            (equal 'I type))
+        (list (amps-to-counts upper vmax resolution r1 r2)
+              (amps-to-counts lower vmax resolution r1 r2)))))