Sunday, 15 February 2015

Eyepiece - absolute position sensing using optical detectors (part 2)

A month ago, I discussed the idea of using an old USB mouse as a motion sensor.

One of the problems that I have been wrestling with is that reading the data stream output by the mouse will hang the process if there is no data (i.e. no changes), until data is presented to the reading program.

Finally, in desperation, I tried splitting the process of reading the mouse data from the process of actually using it. Success in using a system that I'm still not terribly confident with to perform a fairly esoteric job feels good.


The operating system (Raspian Linux) treats the data flow between the OS and devices as file objects, which means that you can simply open the device interface file and read from or write to the device as though it were a binary file.

By opening a text file in another location and writing the appropriate data to it allows the data stream from the mouse to be buffered so that it can be read asynchronously, on demand and the end of file detected - the virtual device file doesn't allow this luxury as there is no EOF marker.

In order to get this to work, I wrote a simple device driver program in python, and set it to work as a background process, a second program can pick up the pre-processed data from the mouse whenever it is required.

The simple proof of concept flushes the file occasionally when the mouse is idling. The mouse driver opens the communication file whenever it is ready to write data and closes it immediately after writing. The reader program simply echoes the mouse data on-screen and occasionally flushes the file in order to ensure that it doesn't become an unwieldy size.

Because of the tendency of SD cards to have a limited operational lifespan, it is important that the data files be on a remote or an external hard drive.

So, the programs:

The Mouse Driver - the original, simple file that needs a bit of work, but essentially works as-is.

#!/usr/bin/python
# Filename: udrv.Mouse.py
# Mouse comms driver program (proof of concept)

# Core logic courtesy of PeterO on the Raspberry Pi forum.
#  http://www.raspberrypi.org/forums/viewtopic.php?f=63&t=80987

import struct
import binhex
import sys

# You'll need to find the name of your particular mouse to put in here...
mFile = open("/dev/input/by-id/usb-05e3_USB_Mouse-event-mouse","rb")

# And this is the communications file
mLog = open("/$.m55.eyepiece.remote/udrv.mouse.comm", "w")
mLog.close()

while True:
    mChunk = mFile.read(16)
    (mType,mCode,mValue) =  struct.unpack_from('hhi', mChunk, offset=8)

    if mType == 1 or mType ==2:
                # These represent button-click and movement messages

        mRes = str(mType) + ", "+str(mCode) + ", " + str(mValue)
       
mRes = mRes + chr(13) + chr(10)
        mLog = open("/$.m55.eyepiece.remote/udrv.mouse.comm", "a")
        mLog.write(mRes)
        mLog.close()
   
This program is launched using a single-line BASH script:

#/bin/bash
# Filename:  script.mousestart.sh
# Simple script to start the mouse driver program
python /$.m55.eyepiece.remote/udrv.mouse.py
quit
And finally, the reader program:

#!/usr/bin/python
# Filename: udrv.MouseComms.py
# Mouse comms reader program (proof of concept)

# Core logic courtesy of PeterO on the Raspberry Pi forum.
#  http://www.raspberrypi.org/forums/viewtopic.php?f=63&t=80987

import struct
import binhex
import sys
import subprocess
import time

mLog = open("/$.m55.eyepiece.remote/udrv.mouse.comm", "r")
mActive = False
mCount = 0
mTime = time.clock()

while True:
    mBuff = mLog.readline()
    if mBuff != "":
        mActive = True
        mTime = time.clock()
        print mTime, mBuff
    else:
        mCount = time.clock() - mTime
        if mActive and mCount > 0.1: # Flush the buffer.
            print "Flushing buffer"
            mLog.close()
            mLog = open("/$.m55.eyepiece.remote/udrv.mouse.comm", "w")
            mLog.close()
            mLog = open("/$.m55.eyepiece.remote/udrv.mouse.comm", "r")
            mActive = False
There is no attempt at locking the file by the during a write operation, so some care is required in operation. This is not a substitute for a GUI mouse driver, after all.

In theory, this system could be used to make precise measurements of the relative motion of mouse and surface, though the need for careful tracking and conversion of mickeys (mouse pixels) to millimetres is fraught with difficulty. Theoretically, this could take the place of a binary scale for sensing the position of the specimen stage for macro focusing.

Note that /$.m55.eyepiece.remote/ is the path to a network shared drive, and is not located on the Raspberry Pi's SD card system drive.