A cursor goto hydra for emacs

| categories: hydra, emacs | tags:

In the spirit of upping my navigation game, here we examine navigation by search like methods. You probably know about C-s with will search forward for a word, and C-r which will search backwards. This will get you to the start of a word pretty easily. It won't get you into a word though, you have to navigate to that, and it isn't too handy to get to a line, or window, or headline in an org-file. Each of these is an emacs command, which as with navigation I don't always remember. Today, we build a hydra to make this easy too.

We will use features from avy , and helm , and some standard emacs features. avy is pretty neat. It provides an interface to jump to characters, words and subwords by pressing keys. To jump to a character that is visible on the screen, you invoke avy-goto-char and press the character you want to jump to. avy will overlay a sequence of keys you then type to jump to that character. It might be more convenient to jump to a pair of characters, which you do by invoking avy-goto-char-2. Similarly, there are commands to jump to the beginning of a word, and a subword, both with variations that allow you to specify the beginning letter of the word, or to get overlays on every word.

I spend most of my days in org-files, so I frequently want to jump to an org headline in the current buffer, or some headline in an org-file in my agenda. Helm provides a nice set of functions for this in helm-org-headlines and helm-org-agenda-files-headings. We can also use helm-multi-swoop-org to use the swoop search function in all open org-buffers with helm selection. Within a buffer, you might also use the search forward and backward capabilities, or the more advanced helm-occur or swiper-helm features. Finally, I may want my cursor to go to another recent file, or open buffer.

The hydra we will develop here puts all of these commands a few keystrokes away, with a hint system to remind you what is possible. In addition to these "goto" commands, I add a character to switch to the navigation hydra we developed in the last post so I can switch to navigation if I change my mind. I also put two commands to store the current position before the goto command, and to return to that stored position conveniently. I bind this hydra to super-g, because the super key isn't used much on my Mac, and g reminds of "goto". So, here is my hydra code:

(defhydra goto (:color blue :hint nil)
  "
Goto:
^Char^              ^Word^                ^org^                    ^search^
^^^^^^^^---------------------------------------------------------------------------
_c_: 2 chars        _w_: word by char     _h_: headline in buffer  _o_: helm-occur
_C_: char           _W_: some word        _a_: heading in agenda   _p_: helm-swiper
_L_: char in line   _s_: subword by char  _q_: swoop org buffers   _f_: search forward
^  ^                _S_: some subword     ^ ^                      _b_: search backward
-----------------------------------------------------------------------------------
_B_: helm-buffers       _l_: avy-goto-line
_m_: helm-mini          _i_: ace-window
_R_: helm-recentf

_n_: Navigate           _._: mark position _/_: jump to mark
"
  ("c" avy-goto-char-2)
  ("C" avy-goto-char)
  ("L" avy-goto-char-in-line)
  ("w" avy-goto-word-1)
  ;; jump to beginning of some word
  ("W" avy-goto-word-0)
  ;; jump to subword starting with a char
  ("s" avy-goto-subword-1)
  ;; jump to some subword
  ("S" avy-goto-subword-0)

  ("l" avy-goto-line)
  ("i" ace-window)

  ("h" helm-org-headlines)
  ("a" helm-org-agenda-files-headings)
  ("q" helm-multi-swoop-org)

  ("o" helm-occur)
  ("p" swiper-helm)

  ("f" isearch-forward)
  ("b" isearch-backward)

  ("." org-mark-ring-push :color red)
  ("/" org-mark-ring-goto :color blue)
  ("B" helm-buffers-list)
  ("m" helm-mini)
  ("R" helm-recentf)
  ("n" hydra-navigate/body))

(global-set-key (kbd "s-g") 'goto/body)

As with the last navigation hydra, this is a pretty massive set of options and takes up some decent screen space at the bottom om my emacs. They are mostly here to remind me that there are better navigation options, and with practice I suspect muscle memory will provide fast navigation tools with more precision and fewer keystrokes than simple navigation.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Upping my Emacs navigation game

| categories: hydra, emacs | tags:

I have been trying to up my navigation game in Emacs, by which I mean I want to get my cursor where I want it with a minimal number of keystrokes, and preferrably no mouse actions. There are lots of little and big navigations I do a lot:

  1. forward/backward by a character
  2. forward/backward by a word/subword
  3. forward/backward by a sentence
  4. forward/backward by a line
  5. to the beginning and end of a line
  6. to the beginning and end of a sentence
  7. to the beginning and end of a paragraph
  8. to the beginning and end of a page
  9. to the beginning and end of a buffer
  10. scrolling up/down
  11. into another window
  12. back and forth to buffers

Occasionally, I want to save a location so I can easily get back to it. Not all of these are strictly speaking navigation in the usual sense, but they are things I do often enough. There are Emacs commands for all these, and keyboard shortcuts for many of them, but I don't use them often, and as a result I don't remember them either.

Here I develop a hydra that will provide these features. Hydra is a super amazing, menu prompting system that provides hints to remind you of what can be done, and to access it from a character. It is possible to pass numeric and universal arguments to the commands by typing -, a number, or C-u before pressing the character.

I want some commands to be repeatable, which we get with a "red" hydra, and some commands to exit on running, which we get with a "blue" head. So, here is an over-the-top hydra for navigation.

(defhydra hydra-navigate (:color red
                          :hint nil)
  "
_f_: forward-char       _w_: forward-word       _n_: next-line
_b_: backward-char      _W_: backward-word      _p_: previous-line
^ ^                     _o_: subword-right      _,_: beginning-of-line
^ ^                     _O_: subword-left       _._: end-of-line

_s_: forward sentence   _a_: forward paragraph  _g_: forward page
_S_: backward sentence  _A_: backward paragraph _G_: backward page

_h_: helm mini _B_: buffer list _i_: window
_<left>_: previous buffer   _<right>_: next buffer
_<up>_: scroll-up           _<down>_: scroll-down

_[_: backward-sexp _]_: forward-sexp
_<_ beginning of buffer _>_ end of buffer _m_: set mark _/_: jump to mark
"
  ("f" forward-char)
  ("b" backward-char)
  ("w" forward-word)
  ("W" backward-word)
  ("n" next-line)
  ("p" previous-line)
  ("o" subword-right)
  ("O" subword-left)
  ("s" forward-sentence)
  ("S" backward-sentence)
  ("a" forward-paragraph)
  ("A" backward-paragraph)
  ("g" forward-page)
  ("G" backward-page)
  ("<right>" next-buffer)
  ("<left>" previous-buffer)
  ("h" helm-mini :color blue)
  ("i" ace-window :color blue)
  ("m" org-mark-ring-push)
  ("/" org-mark-ring-goto :color blue)
  ("B" helm-buffers-list)
  ("<up>" scroll-up)
  ("<down>" scroll-down)
  ("<" beginning-of-buffer)
  (">" end-of-buffer)
  ("." end-of-line)
  ("[" backward-sexp)
  ("]" forward-sexp)
  ("," beginning-of-line)
  ("q" nil "quit" :color blue))

(global-set-key (kbd "s-n") 'hydra-navigate/body)
hydra-navigate/body

I basically like it. The menu is a little on the large side, but it makes for easy modal navigation in a buffer, to other windows, and other buffers. On the whole for moderate cursor movements, this results in basically equal keystrokes. For example, to move 3 characters forward, we have C-f C-f C-f or C-u 3 C-f, or s-n 3 f. The advantage (I think) is a single interface to all these navigation commands with hints on what to do.

There is still another level of navigation, which is related to navigation by searching. That is a whole different level of navigation I will work on another day!

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Checking for email attachments before you send email

| categories: email, emacs | tags:

Does this comic (http://www.phdcomics.com/comics/archive.php?comicid=1817 ) apply to you? Do you miss the Gmail feature that will alert you that it seems like you mention an attachment but there isn't one attached before it lets you send it? Let's make Emacs help us here. We will scan our emails for the word "attach", and if we see it, scan the email for evidence of an attachment. Then create a hook function that will prompt us if it appears we mention an attachment, but don't have one.

An attachment looks like this in my messages:

<#part type="image/png" filename="~/Desktop/wordcloud.png" disposition=attachment>
<#/part>

So, probably finding "<#part" in the buffer means I probably have an attachment. We will use the message-send-hook to run this function. Here is the code. Some brief testing from me seems to work fine! It is pretty simple, but probably good enough to save me from sending messages with no attachment, and not too intrusive for when no attachment is actually needed, e.g. in replies. Let me know if you have ideas for improvements.

(defun email-says-attach-p ()
  "Return t if email suggests there could be an attachment."
  (save-excursion
    (goto-char (point-min))
    (re-search-forward "attach" nil t)))

(defun email-has-attachment-p ()
  "Return t if the currently open email has an attachment"
  (save-excursion
    (goto-char (point-min))
    (re-search-forward "<#part" nil t)))

(defun email-pre-send-check-attachment ()
  (when (and (email-says-attach-p)
             (not (email-has-attachment-p)))
    (unless
        (y-or-n-p "Your email suggests you need an attachment, but no attachment was found. Send anyway?")
      (error "It seems an attachment is needed, but none was found. Aborting send."))))

(add-hook 'message-send-hook 'email-pre-send-check-attachment)

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

A highlight annotation mode for Emacs using font-lock

| categories: annotation, emacs | tags:

Table of Contents

One of my students asked about highlighting text in emacs for note-taking. I can see some advantages for doing it while teaching, for example, and for students studying, so here we we work it out.

You will definitely want to see the video on this one, the highlights do not show up in the published html. https://www.youtube.com/watch?v=Cvz2tiT12-I

For temporary use, highlighting is pretty easy, you just set a property on a region, e.g. the background color. For example, we can do this:

; this seems to be necessary to get the tooltips to work.
(setq font-lock-extra-managed-props (delq 'help-echo font-lock-extra-managed-props))

(defun highlight-region (beg end)
 (interactive "r")
 (set-text-properties
  beg end
  '(font-lock-face (:background "Light Salmon")
                   highlighted t
                   help-echo "highlighted")))

(global-set-key (kbd "s-h") 'highlight-region)
highlight-region

This sets the background color, and another property "highlighted" that we will use later. The trouble is this is transient. When I close the file, the highlights are lost. We can save them to a file though, and reload them later. As long as we are diligent about that we should be able to provide persistent highlights.

First we need a function to get all the highlights, their start and end, their color, and if there is a help-echo which provides a tooltip. We will see why later. Here we loop through the buffer collecting highlights, and return a list of them.

(defun highlight-get-highlights ()
  "Scan buffer for list of highlighted regions.
These are defined only by the highlighted property. That means
adjacent highlighted regions will be merged into one region with
the color of the first one."
  (save-excursion
    (goto-char (point-min))
    (let ((highlights '())
          (p)
          (beg)
          (end)
          (note)
          (color))
      ;; corner case of first point being highlighted
      (when (get-text-property (point) 'highlighted)
        (setq beg (point)
              end (next-single-property-change (point) 'highlighted)
              color (background-color-at-point)
              help-echo (get-text-property (point) 'help-echo))
        (add-to-list 'highlights (list beg end color help-echo) t)
        (goto-char end))

      ;; Now the rest of the buffer
      (while (setq p (next-single-property-change (point) 'highlighted))
        (setq beg (goto-char p))
        (setq color (background-color-at-point))
        (setq note (get-text-property (point) 'help-echo))
        (setq end (next-single-property-change (point) 'highlighted))
        (when (and beg end)
          (goto-char end)
          (add-to-list 'highlights (list beg
                                         end
                                         color
                                         note)
                       t)
          (goto-char end)))
      highlights)))

(highlight-get-highlights)
438 454 Light Salmon highlighted
1014 1031 Light Salmon highlighted

Next, we generate a filename, and a function to save the highlights to disk. We make it a hook function that runs every time we save the buffer.

(defun highlight-save-filename ()
  "Return name of file to save overlays in."
  (when (buffer-file-name)
    (concat "." (file-name-nondirectory (buffer-file-name)) ".highlights")))

(defun highlight-save ()
  "Loop through buffer and save regions with property highlighted.
Save beginning, end of each region, color and help-echo on the
first character of the region. Delete highlight file if it is empty."
  (interactive)
  (let ((fname (highlight-save-filename))
        (highlights (highlight-get-highlights)))
    (if (and fname highlights)
          (with-temp-file fname
            (print highlights (current-buffer)))
        ;; get rid of file if there are not highlights
        (when (and fname (file-exists-p fname))
          (delete-file fname)))))

(add-hook 'after-save-hook 'highlight-save)
highlight-save helm-swoop–clear-cache
cat .highlights.org.highlights
((438 454 "Light Salmon" "highlighted") (1014 1031 "Light Salmon" "highlighted"))

Here, we can read the contents and apply the highlights. We set this up on a hook for org-mode, so it will apply them on when we open org-files. You could make this more general if you plan to highlight in code files, for example.

(defun highlight-load ()
  "Load and apply highlights."
  (interactive)
  (setq font-lock-extra-managed-props (delq 'help-echo font-lock-extra-managed-props))
  (let ((fname (highlight-save-filename)))
    (when (and fname (file-exists-p fname))
      (mapcar
       (lambda (entry)
         (let ((beg (nth 0 entry))
               (end (nth 1 entry))
               (color (nth 2 entry))
               (help-echo (nth 3 entry)))
           (set-text-properties
            beg end
            `(font-lock-face (:background ,color)
                             help-echo ,help-echo
                             highlighted t))))
       (with-temp-buffer (insert-file-contents fname)
                         (read (current-buffer)))))))


(add-hook 'org-mode-hook 'highlight-load)

Now, let's outdo ourselves in ridiculosity. We will add a helm-colors selector to give you unprecedented highlighting capability in multicolor magnificence. This function will highlight selected text, or update the color of an existing highlight.

(defun highlight (beg end &optional color)
  "Highlight region from BEG to END with COLOR.
COLOR is selected from `helm-colors' when run interactively."
  (interactive "r")
  (unless (or (get-text-property (point) 'highlighted)
              (region-active-p))
    (error "No region selected or not on a highlight."))
  (unless color
    (setq color (s-trim (helm-colors))))
  (if (get-text-property (point) 'highlighted)
      ;; update color
      (let ((beg (previous-single-property-change (point) 'highlighted))
            (end (next-single-property-change (point) 'highlighted)))
        (set-text-properties
         beg end
         `(font-lock-face (:background ,color)
                          highlighted t)))
  (set-text-properties
   beg end
   `(font-lock-face (:background ,color)
                    highlighted t))))

;; For convenience
(global-set-key (kbd "s-h") 'highlight)
highlight

Now, we can conveniently highlight text in whatever color we want. How about list your highlights? After we have highlighted a lot, it might be nice to see a list of these we can click on to find our highlights more quickly.

(defun highlight-list ()
  "Make a list of highlighted text in another buffer. "
  (interactive)
  (let ((cb (current-buffer))
        (fname (buffer-file-name))
        (hls (mapcar
              (lambda (entry)
                (list (nth 0 entry)
                      (buffer-substring (nth 0 entry) (nth 1 entry))))
              (highlight-get-highlights))))
    (if hls
        (progn
          (split-window-right)
          (switch-to-buffer-other-window "*highlights*") (org-mode)
          (setq buffer-read-only nil)
          (erase-buffer)
          (insert "Click on text to jump to the position.\n\n")

          (dolist (s hls)
            (let ((map (make-sparse-keymap)))
              (define-key map [mouse-1]
                `(lambda ()
                   (interactive)
                   (find-file ,fname)
                   (goto-char ,(nth 0 s))))
              (insert (propertize
                       (concat (nth 1 s) "\n")
                       'local-map map))))
          (setq buffer-read-only t))
      (message "No highlights found."))))
highlight-list

You probably would like to just select some text with your mouse, and have it highlighted. That requires us to advise the mouse-set-region function.

(defun highlight-green ()
  "Highlight region in green."
  (interactive)
  (highlight (region-beginning) (region-end) "Darkolivegreen1"))

;; create the advice for use later
(defadvice mouse-set-region (after my-highlight () disable)
  "Highlight"
  (highlight-green))

(defun highlight-mouse-on ()
  "Turn on mouse highlighting"
  (interactive)
  (ad-enable-advice 'mouse-set-region 'after 'my-highlight)
  (ad-activate 'mouse-set-region))

(defun highlight-mouse-off ()
  (interactive)
  (ad-disable-advice 'mouse-set-region 'after 'my-highlight)
  (ad-deactivate 'mouse-set-region))
highlight-mouse-off
(defun highlight-picasso-blues ()
 (interactive)
 (save-excursion
   (let ((colors '("PowderBlue"
                   "Lightskyblue1"
                   "Lightskyblue2"
                   "Lightskyblue3"
                   "Lightskyblue4"))
         (beg (region-beginning))
         (end (region-end)))
     (goto-char beg)
     (while (< (point) (- end 1))
       (highlight (point) (+ 1 (point))
                  (nth (mod (- (point) (region-beginning)) (length colors)) colors))
       (forward-char)))))

(defun highlight-rainbow ()
 (interactive)
 (save-excursion
   (let ((colors '("Red1"
                   "Orange1"
                   "Yellow1"
                   "Darkolivegreen1"
                   "Skyblue1"
                   "Blue1"
                   "DarkViolet"))
         (beg (region-beginning))
         (end (region-end)))
     (goto-char beg)
     (while (< (point) (- end 1))
       (highlight (point) (+ 1 (point))
                  (nth (mod (- (point) (region-beginning)) (length colors)) colors))
       (forward-char)))))

=These look cool, but they don't get properly saved. The code that finds the highlights finds the region, but only saves the first color. That means that adjacent highlights of different color will also not be saved correctly.

How about a highlight with your own tooltip? In theory we can set the help-echo property to some text. In practice I have found this tricky because font-lock occasionally erases help-echo properties on re-fontifying. We remove help-echo from a list of properties that are affected by this, but another library may add it back, and there might be some unintended consequences of that. Here we design a function to highlight with a user-defined tooltip.

(defun highlight-note (beg end color &optional note)
  "Highlight selected text and add NOTE to it as a tooltip."
  (interactive
   (list
    (region-beginning)
    (region-end)
    (s-trim (helm-colors))))
  (unless note (setq note (read-input "Note: ")))
  (unless (region-active-p)
    (error "No region selected."))
  (set-text-properties
   beg end
   `(help-echo ,note font-lock-face (:background ,color)
               highlighted t)))


(defun highlight-note-edit (new-note)
  "Set tooltip of highlight at point to NEW-NOTE."
  (interactive (list (read-input "Note: " (get-text-property (point) 'help-echo))))
  (let* ((region (button-lock-find-extent (point) 'highlighted))
         (beg (car region))
         (end (cdr region)))
    (put-text-property beg end 'help-echo new-note)))

=highlight-note-edit ==highlight-note-edit ==highlight-note-edit ==highlight-note-edit =C Want to get rid of the highlights? We may want to delete one or all. We make a function for each.

(defun highlight-clear ()
  "Clear highlight at point."
  (interactive)
  (when (get-text-property (point) 'highlighted)
    (set-text-properties
     (next-single-property-change (point) 'highlighted)
     (previous-single-property-change (point) 'highlighted)
     nil)))


(defun highlight-clear-all ()
  "Clear all highlights.
They are really deleted when you save the buffer."
  (interactive)
  (mapcar
   (lambda (entry)
     (let ((beg (nth 0 entry))
           (end (nth 1 entry)))
       (set-text-properties
        beg end nil)))
   (highlight-get-highlights))
  (when (get-buffer "*highlights*")
    (kill-buffer "*highlights*")))
highlight-clear-all

Let's define a few convenience functions for common colors, a hydra to quickly select them and bind it to a key for convenience. While we are at it, we add a menu to Org too.

(defun highlight-yellow ()
  "Highlight region in yellow."
  (interactive)
  (highlight (region-beginning) (region-end) "Yellow"))

(defun highlight-blue ()
  "Highlight region in blue."
  (interactive)
  (highlight (region-beginning) (region-end) "LightBlue"))

(defun highlight-pink ()
  "Highlight region in pink."
  (interactive)
  (highlight (region-beginning) (region-end) "Pink"))

(defun highlight-green ()
  "Highlight region in green."
  (interactive)
  (highlight (region-beginning) (region-end) "Darkolivegreen1"))


(defhydra highlighter (:color blue) "highlighter"
  ("b" highlight-blue "blue")
  ("g" highlight-green "Green")
  ("p" highlight-pink "Pink")
  ;; define as many special colors as you like.
  ("s" (highlight (region-beginning) (region-end) "Lightsalmon1") "Salmon")
  ("y" highlight-yellow "yellow")
  ("c" highlight "Choose color")
  ("n" (highlight-note (region-beginning) (region-end) "Thistle") "Note")
  ("N" highlight-note "Note (c)")
  ("m" highlight-mouse-on "Mouse")
  ("M" highlight-mouse-off "Mouse off")
  ("e" highlight-note-edit "Edit note")
  ("l" highlight-list "List highlights")
  ("r" highlight-load "Reload")
  ("S" highlight-save "Save")
  ("d" highlight-clear "Delete")
  ("D" highlight-clear-all "Delete All"))

(easy-menu-change
 '("Org") "highlighter"
 '(["Highlight" highlight]
   ["Highlight (B)" highlight-blue]
   ["Highlight (G)" highlight-green]
   ["Highlight (P)" highlight-pink]
   ["Highlight (Y)" highlight-yellow]
   ["Highlight note" highlight-note]
   ["List highlights" highlight-list]
   ["Delete highlight" highlight-clear]
   ["Delete highlights" highlight-clear-all])
 "Show/Hide")


(global-set-key (kbd "s-h") 'highlighter/body)
highlighter/body

1 Known limitations

The tooltips seem especially fragile, and if there is code that undoes the removal of help-echo from font-lock-extra-managed-props, it seems possible they would easily get lost. I wouldn't use them a lot without a lot of testing. You have to rely on the hook functions defined to keep the highlights synchronized between the buffer and the external highlight file. If you were to rename a file externally, e.g. in the OS, or with a shell command, then the highlights will be lost unless you also rename the external file.

Highlights are not robust enough to survive refiling an org-mode section from one file to another. Personally I don't see these as too big a problem; I don't put a lot of value of highlights, but I can see it being pretty annoying to lose them!

Still, if you want to give this a try, you can use the code here: highlights.el . You should bind the functions to whatever keys you want. Also, it is setup to only work for org-mode. I am not sure what the best hook to use for any file might be. Maybe find-file-hook.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Running scientific instruments in Emacs and recording the results

| categories: notebook, orgmode, emacs | tags:

Today we look at running a scientific instrument via http requests from Emacs and org-mode. We will use a Gamry Ref600 potentiostat because Gamry has very nicely provide a COM interface we can access via Python. This will be only a proof of concept to see what it is like. We will not consider any issues of security, etc…, only what is it like to do it.

The setup will look like this: we will run a flask web app that uses python to control the instrument via http requests. Why? Because I want to run the instrument from my Mac ;) and so far there are only Windows drivers for the instrument. So, we run the flask app on the Windows machine, and I run it from here on my Mac by sending requests. Flask takes care of converting requests to action using Python. You can see the Flask app here.

Let's see what is online:

curl jkitchin-win.cheme.cmu.edu:5000/pstats
(u'REF600-13089',)

We have one potentiostat online with serial number 13089. I have a dummy cell connected to it which has a little resistor on it. So we can run a cyclic voltammogram and it should be a straight line. We have to know a bit about what is returned. We will get a json file back, and it will have the data in it. The data will be a list of lists. The data we want is in columns 1 and 3 (python indexing). Obviously you need some prior knowledge of what data comes back to use this. That would come from reading some documentation.

import requests
import numpy as np
import matplotlib.pyplot as plt

resp = requests.get('http://jkitchin-win.cheme.cmu.edu:5000/cv?endv=0.25&startv=-0.25')

dj = resp.json()
data = np.array(dj['data'])

plt.plot(data[:, 1], data[:, 3])
plt.xlabel('Voltage (V)')
plt.ylabel('Current (A)')
plt.tight_layout()
plt.savefig('cv-1.png')

Well, there you have it. Possibly the first Gamry Ref600 to ever have been driven from a Mac ;) Let me be more explicit about that; I could also run this from Linux, an iPad, etc… You could do this in a browser, or in an IPython notebook, or in Matlab, among many other possibilities. You could write a script in perl, shell, ruby, emacs-lisp, or any other language that supports http requests.

I am not sure why the graph is not perfectly linear, maybe there is some capacitive charging that starts out. The resistance based on the current at 0.2V is about 2000 ohms, which is in good agreement with what is listed on the board the dummy cell is on.

1 Summary thoughts

There are a host of interesting issues one eventually has to consider here including security, but also error management and debugging. I hacked something like an http api here by running flask on the windows machine running the instrument. That is a layer of abstraction on an abstraction to start with. I think later instruments are likely to run these webservers themselves on small dedicated computers, e.g. via a Raspberry pi or Arduino chipset. It is not obvious how sophisticated you can make this with respect to triggering different instruments, etc…

In running this, my "notebook" was blocked while the experiment ran. It is possible to run things asynchronously, and sometimes that would make sense. In the example here, we have provided a very limited set of functions to "run" the potentiostat. It was only a proof of concept to get a sense for what it is like. In practice a fuller set of functions would be implemented. Another point to consider is how the data comes back from the potentiostat. We used json here because it is convenient, but we could just as well send files, and other sorts of data too.

This lays out the possibility to walk up to an instrument with an electronic notebook, setup and run the experiment, capture the results in the notebook and take it back to the office for analysis. Pretty cool.

2 Flask app

So, here is my flask app. We setup a few routes using get requests to do things like get a list of the potentiostats online, and to run a cyclic voltamogram. As a side note, after this post is over, I am turning off the app, so you won't be able to repeat the codes ;) This is not a beautiful, secure or error tolerant code. It works enough for a proof of concept of simple experiments.

from flask import Flask, request, jsonify
import time

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/pstats')
def get_pstats():
    import win32com.client as client
    devices = client.Dispatch('GamryCOM.GamryDeviceList')
    result = str(devices.EnumSections())
    return result

@app.route('/close_pstat')
def close():
    import win32com.client as client
    devicelist = client.Dispatch('GamryCOM.GamryDeviceList')

    x = devicelist.EnumSections()[0]
    pstat = client.Dispatch('GamryCOM.GamryPstat')
    pstat.Init(x)

    pstat.Close()


def run_ramp(Sinit,  # start value
             Sfinal, # end value
             ScanRate=1,
             SampleRate=0.01,
             CtrlMode=1,  # GamryCOM.PstatMode
             fname=None):
    '''We assume the first device is the one you want.
    '''
    import win32com.client as client
    import numpy as np
    devicelist = client.Dispatch('GamryCOM.GamryDeviceList')

    x = devicelist.EnumSections()[0]

    pstat = client.Dispatch('GamryCOM.GamryPstat')
    pstat.Init(x)

    pstat.Open()

    dtaqcpiv=client.Dispatch('GamryCOM.GamryDtaqCpiv')
    dtaqcpiv.Init(pstat)

    sigramp=client.Dispatch('GamryCOM.GamrySignalRamp')
    sigramp.Init(pstat, Sinit, Sfinal, ScanRate, SampleRate, CtrlMode)

    pstat.SetSignal(sigramp)
    pstat.SetCell(1) # 1 == GamryCOM.CellOn

    try:
        dtaqcpiv.Run(True)
    except Exception as e:
        pstat.Close()
        raise

    # NOTE:  The comtypes example in this same directory illustrates the use of com
    # notification events.  The comtypes package is recommended as an alternative
    # to win32com.
    time.sleep(2) # just wait sufficiently long for the acquisition to complete.

    acquired_points = []
    count = 1
    while count > 0:
        count, points = dtaqcpiv.Cook(10)
        # The columns exposed by GamryDtaq.Cook vary by dtaq and are
        # documented in the Toolkit Reference Manual.
        acquired_points.extend(zip(*points))

    acquired_points = np.array(acquired_points)
    if fname is not None:
        np.savetxt(fname, acquired_points)

    pstat.Close()
    return jsonify({'data': acquired_points.tolist()})

@app.route('/cv')
def run_cv():
    result = str(request.values)
    startv = float(request.values.get('startv', -0.1))
    endv = float(request.values.get('endv', 0.1))
    scanrate = float(request.values.get('scanrate', 1.0))
    samplerate = float(request.values.get('samplerate', 0.01))

    data = run_ramp(startv, endv, scanrate, samplerate)
    return data


if __name__ == '__main__':
    app.run(host='jkitchin-win.cheme.cmu.edu', port=5000, debug=True)

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter
« Previous Page -- Next Page »