00001
00002
00003
00004 from collections import namedtuple
00005
00006 from . import check
00007 from . import messages
00008
00009 from .. import utils
00010
00011 FieldMeta = namedtuple('FieldMeta', 'units format title')
00012
00013
00014 class Payload(check.Fields):
00015 def __init__(self, **kwargs):
00016
00017
00018
00019
00020
00021
00022
00023
00024 self.validate()
00025
00026 def __str__(self):
00027 """Prints a basic human-readable output, using formats and units for
00028 fields based on the self.meta information from the Payload.fields
00029 initializer."""
00030 lines = []
00031 for field in self._fields:
00032 meta = self.meta[field]
00033 value = getattr(self, field)
00034
00035
00036
00037
00038 one = lambda value: "%s %s" % (meta.format, meta.units) % value
00039 if isinstance(value, list):
00040 if len(value) > 0:
00041 valuestr = ', '.join(map(one, value))
00042 else:
00043 valuestr = '<none>'
00044 else:
00045 valuestr = one(value)
00046
00047 line = "%s: %s" % (meta.title, valuestr)
00048 lines.append(line)
00049
00050 return "\n".join(lines)
00051
00052 @staticmethod
00053 def fields(*fieldstrs):
00054 """Returns pointer to a SubPayload class which inherits from Payload, but also
00055 has a namedtuple mixin with the specified fields, and also a special
00056 meta attribute which provides hints to Payload about the fields and units
00057 and so-on. Usage:
00058 m = Payload.fields('field units format title')."""
00059 fieldmetas = {}
00060 fieldnames = []
00061 for fieldmeta in map(lambda s: s.split(' ', 4), fieldstrs):
00062 if len(fieldmeta) == 1: fieldmeta.append('')
00063 if fieldmeta[1] == '-': fieldmeta[1] = ''
00064 if len(fieldmeta) == 2: fieldmeta.append("%.2f")
00065 if len(fieldmeta) == 3:
00066
00067 fieldmeta.append(fieldmeta[0].replace('_', ' ').title())
00068 fieldnames.append(fieldmeta[0])
00069 fieldmetas[fieldmeta[0]] = FieldMeta(*fieldmeta[1:])
00070
00071 tuplename = "p%d_" % abs(hash(tuple(fieldnames)))
00072
00073 class SubPayload(Payload, namedtuple(tuplename, fieldnames)):
00074 meta = fieldmetas
00075 return SubPayload
00076
00077 @classmethod
00078 def parse(cls, data):
00079 '''Payloads which implement this may be received on the wire, and
00080 this method will parse the list of bytes and return a Payload object.'''
00081 raise NotImplementedError("Payload does not have a parse method.")
00082
00083 def data(self):
00084 '''Payloads which may be sent in outbound messages will implement this
00085 method, which produces a list of bytes. For most messages, the data()
00086 output will be identical to what is expected for parse(). However, not
00087 all the messages are symmetrical!'''
00088 raise NotImplementedError("Payload does not have a data output method.")
00089
00090 def validate(self):
00091 '''All payloads must implement validation, using Fields.check()'''
00092 raise NotImplementedError("Payloade does not have a validate method.")
00093
00094
00095 class DifferentialSpeed(Payload.fields('left_speed m/s', 'right_speed m/s',
00096 'left_accel m/s^2', 'right_accel m/s^2')):
00097
00098 @classmethod
00099 def parse(cls, data):
00100 return cls(left_speed = utils.to_short(data[0:2], 100),
00101 right_speed = utils.to_short(data[2:4], 100),
00102 left_accel = utils.to_short(data[4:6], 100),
00103 right_accel = utils.to_short(data[6:8], 100))
00104
00105 def data(self):
00106 data = []
00107 data += utils.from_short(self.left_speed, 100)
00108 data += utils.from_short(self.right_speed, 100)
00109 data += utils.from_short(self.left_accel, 100)
00110 data += utils.from_short(self.right_accel, 100)
00111 return data;
00112
00113 def validate(self):
00114 self.check('left_speed right_speed').range(-320, 320)
00115 self.check('left_accel right_accel').range(0, 320)
00116
00117
00118 class SystemStatus(Payload.fields('uptime sec %d', 'voltages V',
00119 'currents A', 'temperatures C')):
00120
00121 @classmethod
00122 def parse(cls, data):
00123 pass
00124
00125
00126
00127
00128
00129 def validate(self):
00130 self.check('uptime').range(0, 4000000)
00131 self.check('voltages currents temperatures').each().range(0, 320)
00132
00133
00134
00135 payload = DifferentialSpeed.parse([0x6E, 0x00, 0x88, 0xFF, 0xC8, 0x00, 0xC8, 0x00])
00136 print(payload)
00137 print(repr(payload))
00138
00139 payload = DifferentialSpeed(left_speed = 0.8, right_speed = 0.8, left_accel = 0.2, right_accel = 0.2)
00140 print(payload.data())
00141 print(payload.hex())
00142 print(repr(payload))
00143
00144
00145 payload = SystemStatus(uptime=100, voltages=[1,200,3], currents=[100,101,120], temperatures=[])
00146 print(payload)
00147 print(repr(payload))
00148
00149 msg = messages.Message(code=12, payload=payload, no_ack=0, timestamp=1000000)
00150 print(msg)
00151 print(repr(msg))
00152