Skip to content

Commit

Permalink
Merge pull request #30 from oskarpearson/dev
Browse files Browse the repository at this point in the history
Merge from Dev
  • Loading branch information
oskarpearson committed May 3, 2016
2 parents 244e63b + dc2b9fd commit 528bffc
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 60 deletions.
7 changes: 7 additions & 0 deletions bin/mmeowlink-bolus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK

from mmeowlink.cli.bolus_app import BolusApp

if __name__ == '__main__':
BolusApp().run(None)
7 changes: 7 additions & 0 deletions bin/mmeowlink-rf-dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK

from mmeowlink.cli.rf_dump_app import RfDumpApp

if __name__ == '__main__':
RfDumpApp().run(None)
5 changes: 2 additions & 3 deletions bin/mmeowlink-send.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK

from mmeowlink.cli import messages
from mmeowlink.cli.send_msg_app import SendMsgApp

if __name__ == '__main__':
app = messages.SendMsgApp( )
app.run(None)
SendMsgApp().run(None)
27 changes: 3 additions & 24 deletions bin/mmtune.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK

import sys
import json
from mmeowlink.mmtune import MMTune
from mmeowlink.vendors.subg_rfspy_link import SubgRfspyLink
from mmeowlink.cli.mmtune_app import MMTuneApp

if __name__ == '__main__':
locale = 'US'

if len(sys.argv) < 3:
print "Usage: mmtune.py /dev/ttyMFD1 pumpserial [radio_locale]"
print "Radio locale defaults to 'US'. Set to 'WW' for other countries"
sys.exit(-1)

link = SubgRfspyLink(sys.argv[1])
serial = sys.argv[2]

if len(sys.argv) >= 4:
locale = sys.argv[3]

if locale not in ['US', 'WW']:
print "Radio locale not supported - must be either 'WW' or 'US'"
sys.exit(1)

tuner = MMTune(link, serial, locale)
output = tuner.run()
print json.dumps(output, sort_keys=True,indent=4, separators=(',', ': '))
MMTuneApp().run(None)
21 changes: 12 additions & 9 deletions mmeowlink/cli/messages.py → mmeowlink/cli/base_mmeowlink_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
module to send arbitrary pump messages.
"""

# PYTHON_ARGCOMPLETE_OK
from decocare.helpers import messages
from decocare.helpers import messages as decocare_messages
from mmeowlink.handlers.stick import Pump
from mmeowlink.link_builder import LinkBuilder
import argcomplete

class SendMsgApp (messages.SendMsgApp):
class BaseMMeowlinkApp(decocare_messages.SendMsgApp):
"""
mmeowlink adapter to decocare's SendMsgApp
Base class used by other apps here
"""
def customize_parser (self, parser):
def configure_radio_params(self, parser):
parser.add_argument('--radio_type', dest='radio_type', default='subg_rfspy', choices=['mmcommander', 'subg_rfspy'])
parser.add_argument('--mmcommander', dest='radio_type', action='store_const', const='mmcommander')
parser.add_argument('--subg_rfspy', dest='radio_type', action='store_const', const='subg_rfspy')
parser = super(SendMsgApp, self).customize_parser(parser)

return parser

def prelude (self, args):
Expand All @@ -29,15 +28,19 @@ def prelude (self, args):
# get link
# drain rx buffer
self.pump = Pump(self.link, args.serial)

# Early return if we don't want to send any radio comms. Useful from both
# the command line and for MMTuneApp
if args.no_rf_prelude:
return

if not args.autoinit:
if args.init:
self.pump.power_control(minutes=args.session_life)
else:
self.autoinit(args)
self.sniff_model( )

def postlude (self, args):
# self.link.close( )
self.sniff_model()

def postlude(self, args):
return
87 changes: 87 additions & 0 deletions mmeowlink/cli/bolus_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK

from decocare import commands
from decocare import lib
from base_mmeowlink_app import BaseMMeowlinkApp

from mmeowlink.link_builder import LinkBuilder
from mmeowlink.handlers.stick import Pump


class BolusApp (BaseMMeowlinkApp):
""" %(prog)s - Send bolus command to a pump.
XXX: Be careful please!
Units might be wrong. Keep disconnected from pump until you trust it by
observing the right amount first.
"""
def customize_parser (self, parser):
parser.add_argument('units',
type=float,
help="Amount of insulin to bolus."
)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--515',
dest='strokes_per_unit',
action='store_const',
const=10
)
group.add_argument('--554',
dest='strokes_per_unit',
action='store_const',
const=40
)
group.add_argument('--strokes',
dest='strokes_per_unit',
type=int
)

parser.add_argument('--radio_type', dest='radio_type', default='subg_rfspy', choices=['mmcommander', 'subg_rfspy'])
parser.add_argument('--mmcommander', dest='radio_type', action='store_const', const='mmcommander')
parser.add_argument('--subg_rfspy', dest='radio_type', action='store_const', const='subg_rfspy')
# parser = super(BolusApp, self).customize_parser(parser)

return parser

def prelude(self, args):
port = args.port
builder = LinkBuilder()
if port == 'scan':
port = builder.scan()
self.link = link = LinkBuilder().build(args.radio_type, port)
link.open()
self.pump = Pump(self.link, args.serial)
self.model = None
if args.no_rf_prelude:
return
if not args.autoinit:
if args.init:
self.pump.power_control(minutes=args.session_life)
else:
self.autoinit(args)
self.sniff_model()

def postlude(self, args):
# self.link.close( )
return

def main (self, args):
print args
self.bolus(args);

def bolus (self, args):
query = commands.Bolus
kwds = dict(params=fmt_params(args))

resp = self.exec_request(self.pump, query, args=kwds,
dryrun=args.dryrun, render_hexdump=False)
return resp

def fmt_params (args):
strokes = int(float(args.units) * args.strokes_per_unit)
if (args.strokes_per_unit > 10):
return [lib.HighByte(strokes), lib.LowByte(strokes)]
return [strokes]
28 changes: 28 additions & 0 deletions mmeowlink/cli/mmtune_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json

from mmeowlink.mmtune import MMTune
from base_mmeowlink_app import BaseMMeowlinkApp

class MMTuneApp(BaseMMeowlinkApp):
"""
Tune radio automatically
"""
def customize_parser(self, parser):
parser = super(self.__class__, self).configure_radio_params(parser)

parser.add_argument('--radio_locale', choices=['US', 'WW'], default='US', help="US=916mhz, WW=868mhz. Only supported on subg_rfspy radios")

return parser

def prelude(self, args):
# When running mmtune, we don't want the code to try and send
# prelude packets or auto-init the pump, since they duplicate what
# we are about to do
args.no_rf_prelude = True

super(MMTuneApp, self).prelude(args)

def main(self, args):
tuner = MMTune(self.link, args.serial, args.radio_locale)
output = tuner.run()
print json.dumps(output, sort_keys=True,indent=4, separators=(',', ': '))
42 changes: 42 additions & 0 deletions mmeowlink/cli/rf_dump_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from datetime import datetime

from .. hex_handling import hexify
from .. exceptions import CommsException

from mmeowlink.vendors.mmcommander_link import MMCommanderLink
from mmeowlink.vendors.subg_rfspy_link import SubgRfspyLink

from base_mmeowlink_app import BaseMMeowlinkApp

class RfDumpApp(BaseMMeowlinkApp):
"""
Dump Radio Transmissions
"""

# Override the parser since we don't want the standard commands
def customize_parser(self, parser):
parser = super(self.__class__, self).configure_radio_params(parser)

return parser

def prelude(self, args):
# When running mmtune, we don't want the code to try and send
# prelude packets or auto-init the pump, since they duplicate what
# we are about to do
args.no_rf_prelude = True

super(RfDumpApp, self).prelude(args)

def main(self, args):
while True:
try:
if type(self.link) == SubgRfspyLink:
resp = self.link.get_packet(timeout=1)
ts = datetime.now()
print "%s (%d db) - %s" % (ts, resp['rssi'], hexify(resp['data']).upper())
elif type(self.link) == MMCommanderLink:
resp = self.link.read(timeout=1)
ts = datetime.now()
print "%s (N/A db) - %s" % (ts, hexify(resp).upper())
except CommsException as e:
pass
12 changes: 12 additions & 0 deletions mmeowlink/cli/send_msg_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from base_mmeowlink_app import BaseMMeowlinkApp

class SendMsgApp(BaseMMeowlinkApp):
"""
mmeowlink adapter to decocare's SendMsgApp. All actual implementation details
are handled in MMeowlinkApp and messages in decocare.helpers
"""
def customize_parser(self, parser):
parser = super(self.__class__, self).configure_radio_params(parser)
parser = super(self.__class__, self).customize_parser(parser)

return parser
31 changes: 24 additions & 7 deletions mmeowlink/handlers/stick.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def done (self):
def respond (self, resp):
if resp.valid and resp.serial == self.command.serial:
if resp.op == 0x06 and self.sent_params:
self.command.respond(bytearray(64))
self.command.respond(bytearray(64))
elif resp.op == self.command.code:
self.unframe(resp)

Expand Down Expand Up @@ -162,26 +162,38 @@ def __call__ (self, command):

class Repeater (Sender):

def __call__ (self, command, repetitions=None, ack_wait_seconds=None, retry_count=None):
def __call__ (self, command, repetitions=None, ack_wait_seconds=None):
self.command = command

start = time.time()
pkt = Packet.fromCommand(self.command, serial=self.command.serial)
buf = pkt.assemble( )
log.debug('Sending repeated message %s' % (str(buf).encode('hex')))

self.link.write(buf, repetitions=repetitions)

# The radio takes a while to send all the packets, so wait for a bit before
# trying to talk to the radio, otherwise we can interrupt it.
#
# This multiplication factor is based on
# testing, which shows that it takes 8.04 seconds to send 500 packets
# (8.04/500 =~ 0.016 packets per second).
# We don't want to miss the reply, so take off a bit:
time.sleep((repetitions * 0.016) - 2.2)

# Sometimes the first packet received will be mangled by the simultaneous
# transmission of a CGMS and the pump. We thus retry on invalid packets
# being received. Note how ever that we do *not* retry on timeouts, since
# our wait period is typically very long here, which would lead to long
# waits with no activity. It's better to fail and retry externally
for retry_count in range(retry_count):
while (time.time() <= start + ack_wait_seconds):
try:
self.wait_for_ack(timeout=ack_wait_seconds)
self.wait_for_ack()
return True
except InvalidPacketReceived:
log.error("Invalid Packet Received - retrying: %s of %s" % (retry_count, self.STANDARD_RETRY_COUNT))
except CommsException, InvalidPacketReceived:
log.error("Response not received - retrying at %s" % time.time)

return False

class Pump (session.Pump):
STANDARD_RETRY_COUNT = 3
Expand All @@ -197,7 +209,12 @@ def power_control (self, minutes=None):
self.command = commands.PowerControl(**dict(minutes=minutes, serial=self.serial))
repeater = Repeater(self.link)

repeater(self.command, repetitions=500, ack_wait_seconds=15, retry_count=2)
status = repeater(self.command, repetitions=500, ack_wait_seconds=20)

if status:
return True
else:
raise CommsException("No acknowledgement from pump on wakeup. Is it out of range or is the battery too low?")

def execute (self, command):
command.serial = self.serial
Expand Down
2 changes: 2 additions & 0 deletions mmeowlink/hex_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def hexify(data):
return ' '.join( [ '%02x' % x for x in list( data ) ] )
20 changes: 13 additions & 7 deletions mmeowlink/mmtune.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ class MMTune:
'WW': { 'start': 867.5, 'end': 868.5, 'default': 868.328 }
}

def __init__(self, link, pumpserial, locale='US'):
def __init__(self, link, pumpserial, radio_locale='US'):
self.link = link

# MMTune can only be used with the SubgRfspy firmware, as MMCommander
# cannot change frequencies
assert type(link) == SubgRfspyLink

self.pumpserial = pumpserial
self.locale = locale
self.scan_range = self.FREQ_RANGES[self.locale]
self.radio_locale = radio_locale

self.scan_range = self.FREQ_RANGES[self.radio_locale]

def run(self):

Expand Down Expand Up @@ -82,13 +88,13 @@ def scan_over_freq(self, start_freq, end_freq, steps):
cur_freq += step_size
return results

def send_packet(self, data, tx_count=1, msec_repeat_delay=0):
def send_packet(self, data, repetitions=1, repetition_delay=0, timeout=1):
buf = bytearray()
buf.extend(data.decode('hex'))
buf.extend([CRC8.compute(buf)])
self.link.write(buf, tx_count, msec_repeat_delay)
self.link.write(buf, repetitions=repetitions, repetition_delay=repetition_delay, timeout=timeout)

def get_packet(self, timeout=0):
def get_packet(self, timeout):
return self.link.get_packet(timeout)

def wakeup(self):
Expand All @@ -112,7 +118,7 @@ def wakeup(self):
self.link.set_base_freq(self.scan_range['default'])

# Send 200 wake-up packets
self.send_packet("a7" + self.pumpserial + "5d00", 200)
self.send_packet("a7" + self.pumpserial + "5d00", repetitions=200, timeout=4.5)
try:
wake_ack = self.get_packet(9) # wait 9 s for response
except (CommsException, InvalidPacketReceived):
Expand Down
Loading

0 comments on commit 528bffc

Please sign in to comment.