Skip to content

Commit

Permalink
Use send_and_listen where applicable, send back 64 bytes as Ack for c…
Browse files Browse the repository at this point in the history
…ommands with params, handle retries for multi-frame commands, slurp up interrupted command responses
  • Loading branch information
ps2 committed Apr 4, 2016
1 parent b58f0cd commit e1059d2
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ dist/
.idea/
*.iml
logs/
build
86 changes: 54 additions & 32 deletions mmeowlink/handlers/stick.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from .. packets.rf import Packet
from .. exceptions import InvalidPacketReceived, CommsException


import logging
import time

Expand All @@ -17,13 +16,11 @@ class Sender (object):
RETRY_BACKOFF = 1

sent_params = False
expected = 64
def __init__ (self, link):
self.link = link
self.frames = [ ]

def send (self, payload):
self.link.write(payload)
self.ack_for_more_data = False
self.received_ack = False

def send_params (self):
command = self.command
Expand All @@ -35,26 +32,37 @@ def send_params (self):
pkt = Packet.fromCommand(command, payload=payload, serial=command.serial)
pkt = pkt.update(payload)
buf = pkt.assemble( )
self.link.write(buf)
self.sent_params = True
try:
buf = self.link.write_and_read(buf)
resp = Packet.fromBuffer(buf)
self.respond(resp)
except AttributeError:
self.link.write(buf)

def ack (self):
def ack (self, listen=False):
null = bytearray([0x00])
pkt = Packet.fromCommand(self.command, payload=null, serial=self.command.serial)
pkt = pkt._replace(payload=null, op=0x06)
buf = pkt.assemble( )
self.link.write(buf)
if listen:
buf = self.link.write_and_read(buf, timeout=0.1)
return Packet.fromBuffer(buf)
else:
self.link.write(buf)

def unframe (self, resp):
if self.expected > 64:
if self.command.bytesPerRecord * self.command.maxRecords > 64:
self.ack_for_more_data = True
num, payload = resp.payload[0], resp.payload[1:]
self.frames.append((num, resp.payload))
self.ack( )
else:
self.ack_for_more_data = False
payload = resp.payload[1:]

self.command.respond(payload)


def done (self):
needs_params = self.command.params and len(self.command.params) > 0 or False
if needs_params and not self.sent_params:
Expand All @@ -63,30 +71,20 @@ def done (self):

def respond (self, resp):
if resp.valid and resp.serial == self.command.serial:
if resp.op == 0x06:
# if not self.command.done( ) or (self.command.params and not self.sent_params):
if not self.done( ):
return self.command
else:
return self.command
if resp.op == self.command.code:
# self.command.respond(resp.payload)
if resp.op == 0x06 and self.sent_params:
self.command.respond(bytearray(64))
elif resp.op == self.command.code:
self.unframe(resp)
if self.done( ):
return self.command
else:
pass

def wait_for_ack (self, timeout=.500):
link = self.link

while not self.done( ):
buf = link.read( timeout=timeout )

resp = Packet.fromBuffer(buf)
if self.responds_to(resp):
if resp.op == 0x06:
return resp
return

def responds_to (self, resp):
return resp.valid and resp.serial == self.command.serial
Expand All @@ -101,23 +99,38 @@ def wait_response (self):
def prelude (self):
link = self.link
command = self.command

self.expected = command.bytesPerRecord * command.maxRecords
log.debug("*** Sending prelude for command %d" % command.code)

payload = bytearray([0])
self.pkt = Packet.fromCommand(command, payload=payload, serial=command.serial)
self.pkt = self.pkt.update(payload)
buf = self.pkt.assemble( )
self.send(buf)
try:
buf = self.link.write_and_read(buf)
resp = Packet.fromBuffer(buf)
if self.responds_to(resp):
if resp.op == 0x06:
self.received_ack = True
else:
self.respond(resp)
except AttributeError:
self.link.write(buf)

def upload (self):
params = self.command.params
log.debug("len(params) == %d" % len(params))

should_send = len(params) > 0
if should_send:
self.wait_for_ack( )
if not self.received_ack:
self.wait_for_ack( )
self.send_params( )

def restart_command( self ):
# This is a bit of a hack; would be nice if decocare explicitly supported a command reset
self.command.data = bytearray()
self.command.responded = False

def __call__ (self, command):
self.command = command

Expand All @@ -127,15 +140,24 @@ def __call__ (self, command):
self.upload()

while not self.done( ):
resp = self.wait_response( )
if self.ack_for_more_data:
try:
resp = self.ack(listen=True)
except AttributeError:
self.ack(listen=False)
resp = self.wait_response( )
else:
resp = self.wait_response( )
if resp:
self.respond(resp)

return command
except InvalidPacketReceived as e:
log.error("Invalid Packet Received - '%s' - retrying: %s of %s" % (e, retry_count, self.STANDARD_RETRY_COUNT))
log.error("Invalid Packet Received - '%s' - retrying: %s of %s" % (e, retry_count+1, self.STANDARD_RETRY_COUNT))
self.restart_command()
except CommsException as e:
log.error("Timed out or other comms error - %s - retrying: %s of %s" % (e, retry_count, self.STANDARD_RETRY_COUNT))
log.error("Timed out or other comms error - %s - retrying: %s of %s" % (e, retry_count+1, self.STANDARD_RETRY_COUNT))
self.restart_command()
time.sleep(self.RETRY_BACKOFF * retry_count)

class Repeater (Sender):
Expand All @@ -145,7 +167,7 @@ def __call__ (self, command, repetitions=None, ack_wait_seconds=None, retry_coun

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

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

Expand Down
3 changes: 2 additions & 1 deletion mmeowlink/mmtune.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ def __init__(self, link, pumpserial, locale='US'):
self.scan_range = self.FREQ_RANGES[self.locale]

def run(self):

############################################################################
# Commented these out as they may be causing issues with certain pumps:
############################################################################
# self.link.update_register(SubgRfspyLink.REG_MDMCFG4, 0xd9)
# self.link.update_register(SubgRfspyLink.REG_MDMCFG4, 0xa9)
#
# # Sometimes getting lower ber with 0x07 here (default is 0x03)
# self.link.update_register(SubgRfspyLink.REG_AGCCTRL2, 0x07)
Expand Down
18 changes: 18 additions & 0 deletions mmeowlink/vendors/serial_rf_spy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
import serial
import time
from .. exceptions import CommsException
import logging

io = logging.getLogger( )
log = io.getChild(__name__)

class SerialRfSpy:
CMD_GET_STATE = 1
Expand All @@ -42,6 +46,10 @@ class SerialRfSpy:
CMD_UPDATE_REGISTER = 6
CMD_RESET = 7

RFSPY_ERROR_TIMEOUT = 0xaa
RFSPY_ERROR_COMMAND_INTERRUPTED = 0xbb
RFSPY_ERROR_ZERO_DATA = 0xcc

def __init__(self, ser):
self.default_write_timeout = 1
self.ser = ser
Expand All @@ -55,23 +63,33 @@ def send_command(self, command, param="", timeout=1):
self.ser.write_timeout = timeout

self.ser.write(chr(command))
log.debug("command %d" % command)
if len(param) > 0:
log.debug("params: %s" % str(param).encode('hex'))
self.ser.write(param)

self.ser.write_timeout = self.default_write_timeout

def get_response(self, timeout=0):
log.debug("get_response: timeout = %s" % str(timeout))
start = time.time()
while 1:
bytesToRead = self.ser.inWaiting()
if bytesToRead > 0:
self.buf.extend(self.ser.read(bytesToRead))
log.debug("buf = %s" % str(self.buf).encode('hex'))
eop = self.buf.find(b'\x00',0)
if eop >= 0:
r = self.buf[:eop]
del self.buf[:(eop+1)]
if len(r) == 0:
return bytearray()
if len(r) <= 2 and r[0] == self.RFSPY_ERROR_COMMAND_INTERRUPTED:
log.debug("response = command interrupted, getting the next response")
continue
return r
if (timeout > 0) and (start + timeout < time.time()):
log.debug("gave up waiting for response from subg_rfspy")
return bytearray()
time.sleep(0.005)

Expand Down
77 changes: 58 additions & 19 deletions mmeowlink/vendors/subg_rfspy_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,39 +75,33 @@ def check_setup(self):
self.serial_rf_spy.send_command(self.serial_rf_spy.CMD_GET_VERSION, timeout=1)
version = self.serial_rf_spy.get_response(timeout=1).split(' ')[1]

log.debug( 'serial_rf_spy Firmare version: %s' % version)
log.debug( 'serial_rf_spy Firmware version: %s' % version)

self.uint16_timeout_width = version in self.UINT16_TIMEOUT_VERSIONS

if version not in self.SUPPORTED_VERSIONS:
raise SubgRfspyVersionNotSupported("Your subg_rfspy version (%s) is not in the supported version list: %s" % (version, "".join(self.SUPPORTED_VERSIONS)))

def write( self, string, repetitions=1, repetition_delay=0, timeout=None ):
def write_and_read( self, string, repetitions=1, repetition_delay=0, timeout=None ):
rf_spy = self.serial_rf_spy

remaining_messages = repetitions
while remaining_messages > 0:
if remaining_messages < self.MAX_REPETITION_BATCHSIZE:
transmissions = remaining_messages
else:
transmissions = self.MAX_REPETITION_BATCHSIZE
remaining_messages = remaining_messages - transmissions
if timeout == None:
timeout = 0.5

timeout_ms = int(timeout * 1000)

crc = CRC8.compute(string)

message = chr(self.channel) + chr(transmissions - 1) + chr(repetition_delay) + FourBySix.encode(string)
log.debug("write_and_read: %s" % str(string).encode('hex'))

rf_spy.do_command(rf_spy.CMD_SEND_PACKET, message, timeout=timeout)
if repetitions > self.MAX_REPETITION_BATCHSIZE:
raise CommsException("repetition count of %d is greater than max repitition count of %d" % (repetitions, self.MAX_REPETITION_BATCHSIZE))

def get_packet( self, timeout=None ):
rf_spy = self.serial_rf_spy
crc = CRC8.compute(string)

if timeout is None:
timeout = self.timeout
listen_channel = self.channel

timeout_ms = int(timeout * 1000)
cmd_body = chr(self.channel) + chr(repetitions - 1) + chr(repetition_delay) + chr(listen_channel)

cmd_body = chr(self.channel)
if self.uint16_timeout_width:
timeout_ms_high = int(timeout_ms / 256)
timeout_ms_low = int(timeout_ms - (timeout_ms_high * 256))
Expand All @@ -116,7 +110,32 @@ def get_packet( self, timeout=None ):
cmd_body += chr(timeout_ms >> 24) + chr((timeout_ms >> 16) & 0xff) + \
chr((timeout_ms >> 8) & 0xff) + chr(timeout_ms & 0xff)

resp = rf_spy.do_command(SerialRfSpy.CMD_GET_PACKET, cmd_body, timeout=timeout + 1)
retry_count = 0
cmd_body += chr(retry_count)

cmd_body += FourBySix.encode(string)

resp = rf_spy.do_command(rf_spy.CMD_SEND_AND_LISTEN, cmd_body, timeout=(timeout_ms/1000.0 + 1))
return self.handle_response(resp)['data']

def write( self, string, repetitions=1, repetition_delay=0, timeout=None ):
rf_spy = self.serial_rf_spy

remaining_messages = repetitions
while remaining_messages > 0:
if remaining_messages < self.MAX_REPETITION_BATCHSIZE:
transmissions = remaining_messages
else:
transmissions = self.MAX_REPETITION_BATCHSIZE
remaining_messages = remaining_messages - transmissions

crc = CRC8.compute(string)

message = chr(self.channel) + chr(transmissions - 1) + chr(repetition_delay) + FourBySix.encode(string)

rf_spy.do_command(rf_spy.CMD_SEND_PACKET, message, timeout=timeout)

def handle_response( self, resp ):
if not resp:
raise CommsException("Did not get a response, or response is too short: %s" % len(resp))

Expand All @@ -137,5 +156,25 @@ def get_packet( self, timeout=None ):

return {'rssi':rssi, 'sequence':sequence, 'data':decoded}

def get_packet( self, timeout=None ):
rf_spy = self.serial_rf_spy

if timeout is None:
timeout = self.timeout

timeout_ms = int(timeout * 1000)

cmd_body = chr(self.channel)
if self.uint16_timeout_width:
timeout_ms_high = int(timeout_ms / 256)
timeout_ms_low = int(timeout_ms - (timeout_ms_high * 256))
cmd_body += chr(timeout_ms_high) + chr(timeout_ms_low)
else:
cmd_body += chr(timeout_ms >> 24) + chr((timeout_ms >> 16) & 0xff) + \
chr((timeout_ms >> 8) & 0xff) + chr(timeout_ms & 0xff)

resp = rf_spy.do_command(SerialRfSpy.CMD_GET_PACKET, cmd_body, timeout=timeout + 1)
return self.handle_response(resp)

def read( self, timeout=None ):
return self.get_packet(timeout)['data']

0 comments on commit e1059d2

Please sign in to comment.