Pure js implementation of comlink2 serial protocol
This is a javascript port of decoding-carelink which is a serial protocol that allows using the Carelink USB stick to talk to remote insulin pumps.
To use in another project:
$ npm install git+ssh://git@github.com:bewest/comlink2-uart.git
Until it's on npm
.
See windows notes:
Make sure brew
and pkg-config
is installed:
# install brew
# First install 'brew' (package manager) by typing into your terminal:
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
# Next type
brew install libusb pkg-config
Just works.
Or clone the repo:
$ git clone git@github.com:bewest/comlink2-uart.git
$ npm install
Should work with net
and serialport
transports.
The transport given to uart
must be a bidirectional stream.
Here's an example using serialport
.
If youclone this repo, you can run: replace 208850 with your pump's serial number.
$ REMOTE_SERIAL=208850 node index.js
Or for prettier output:
$ REMOTE_SERIAL=208850 node index.js 2>&1 | ./node_modules/.bin/bunyan | less -R +F
Should show you a debug log of trying to power on for ten minutes, then reading the serial number.
Or you can write your own scripts like this:
var uart = require('comlink2-uart');
// scan all serial ports
var Serial = require('serialport');
function scan (open) {
Serial.list(function (err, list) {
var spec = list.pop( );
console.log("OPENING", spec);
var serial = new Serial.SerialPort(spec.comName, {bufferSize: 64});
serial.open(open.bind(serial));
});
}
scan(function ( ) {
var pump = uart(this).session;
console.log("PUMP", pump);
pump.open( )
.serial('208850') // set the serial number
.status(console.log.bind(console, "STATUS", 1))
// fetch model number
.ReadPumpModel(console.log.bind(console, "MODEL NUMBER", 1))
.status(console.log.bind(console, "STATUS", 2))
// fetch model number
.ReadPumpModel(console.log.bind(console, "MODEL NUMBER", 2))
.end( )
;
});
The module, comlink2-uart
exports a function, create
, aka uart
in example
code above.
var create = require('comlink2-uart');
It returns an object like this:
{ session: <chainsaw>, uart: <chainsaw>}
.
See node-chainsaw, which builds up a list of actions, then calls them in order.
It exposes an api of supported actions as a fluent
interface, which
allows us to provide a sane interface for serially queuing a bunch of
asynchronous actions.
We expose one set of commands for the usb stick
, aka our uart
.
The stick acts like a modem, the user must use the uart
to issue
opcodes to check for modem status, send commands on the radio, and
correctly handle incoming radio packets.
This first api, the uart
allows users to ignore the specifics of how
their underlying transport works. As long as the transport
is a
bidirectional stream, the stick
module should allow users to queue
up opcodes to manipulate radio state using the serial protocol over
the given transport.
We expose another chainsaw
api called session
which allows users
to logically queue up remote commands to be exchanged with remote
equipment through the radio. Each of these commands actually uses the
uart
to issue dozens of commands in order to complete a single
command in a session
. This second api allows users to ignore the
complexity of how the modem operates, in order to meaningfully
exchange messages with remote equipment.
It must have a write
, and pipe
method, support data
, end
,
close
, and error
events as an EventEmitter
, etc. It must be a
duplex stream
supporting pipe
.
var uart = require('comlink2-uart');
// * choose one of these
// USB support!
// var transport = require('comlink2-uart/usb')( )
// var transport = new SerialPort
// var transport = net.createConnection( ) // untested
// var transport = myStream( ); // any duplex stream
var link = create(transport);
// session
var pump = link.session;
var stick = link.uart;
The session
imports all known commands and creates a fluent api
(meaning we can chain method calls to queue up remote actions).
Most users will want to write something like this:
// we can treat session like a remote pump:
var pump = link.session;
pump.open( )
.serial('123456') // serial number of pump
.power_on_ten_minutes(console.log.bind(console, "SET POWER ON"))
.ReadPumpModel( ) // fetch remote pump
.end( ) // clean up session
;
In order to exchange well defined messages as designed/supported by the manufacturer.
Initializes the usb stick, confirms stick product information, protocol compatibility, signal status, basic sanity check on usb stick.
Sets the serial number of the pump used by subsequent commands to address the intended pump.
Users must call this method to send messages to a pump.
End and cleanup transport.
Send instance of command.
Send instance of command with serial number set.
Read the remote pump's model number.
Callback is called with this
applied to session's scope, with the
model
number (as ascii string) as the first parameter, and the
command itself as the second.
Send "power control on" command. Should initialize RF session for ten minutes or so.
pump.open( )
.power_on_ten_minutes(console.log.bind(console, "SET POWER ON"))
;
Most users won't want to use this. This facilitates easily exchanging opcodes/messages with the modem itself.
// This is an example of implementing a custom "clear radio
// buffer" routine (untested)
var stick = link.uart;
stick.open( )
.stats( )
.status( )
.tap(function ( ) {
var i = 0;
// poll status, eg 5 times
this.loop(function (stop) {
if (++i >= 5) { console.log("NO DATA FOUND"); stop( ); }
else {
this.status(function (err, stats) {
// if the radio size is bigger than the header, try
// downloading it
if (stats.size >= 14) {
this.read_packet(stats.size)
.tap(function (err, data) {
console.log("FOUND", data);
})
;
stop( );
}
});
}
});
})
.stats( )
.close( )
;
Query both rf
and usb
interface statistics. How many
packets/messages have been exchanged?
Query status of radio/buffer. Is the radio transmitting, receiving, ready for download, in error, requesting delay?
Initialize radio, confirm compatibility with stick, identify usb product information, prepare for sending/receiving bytes.
Download bytes from radio buffer.
Transmit remote command
.
Poll radio and download all packets for remote command
.
Poll radio status for command
.
Stop, inspect and perform some arbitrary logic.
Stop, inspect and perform some arbitrary logic forever until end is called.
Stop sending messages, clean up event listeners.
Create a duplex
stream representing the usb transport.
Must have usb
installed
var createUSB = require('comlink2-uart/lib/usb');
// sniff for device, create duplex stream
var transport = createUSB( );
var usb = require('comlink2-uart/lib/usb');
The module is a function which scans and creates the duplex stream.
Specifically, it returns an instance of UsbSimpleDuplex
using the
device found after scanning the bus.
Our usb helpers:
Scan usb bus for Carelink device. Returns usb device.
Base class for our duplex
stream.
$ node examples/usb_stick.js # just check usb diagnostics
# eg: node examples/usb_pump.js 208850
$ node examples/usb_pump.js 208850
# or use environment variable SERIAL
$ SERIAL=208850 node examples/usb_pump.js
Make sure SERIAL is set to your pump's serial number.
Here's an example using the usb transport with the pump api:
var create = require('comlink2-uart');
var usb = require('comlink2-uart/lib/usb');
if (!module.parent) {
var serial = process.argv.slice(2,3).pop( ) || process.env['SERIAL'];
if (!serial) {
console.log('usage: usb_pump.js SERIAL');
process.exit(1);
}
console.log('howdy');
var stream = usb( );
stream.on('error', function ( ) {
console.log("BAD ERROR", arguments);
stream.close( );
stream.end( );
});
stream.open( );
var session = create(stream)
var pump = session.session;
pump.open(console.log.bind(console, "OPENED"))
.serial(serial)
.power_on_ten_minutes(console.log.bind(console, 'POWER ON'))
.ReadPumpModel(console.log.bind(console, 'POWER ON for MODEL'))
.end( )
;
}
Probably many, file an issue.
comlink2-uart
Copyright (C) 2014 Ben West <bewest+insulaudit@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.