$search
00001 #!/usr/bin/env python 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 # Setting of field values has already happened in the 00017 # tuple setup, in __new__. We just validate them here, 00018 # and raise any exceptions necessary. For received data, 00019 # any ValueErrors thrown will be captured by the Receiver 00020 # thread and assigned to Payload.error. For outbound messages 00021 # where the data is user-originated, Errors thrown will 00022 # bubble back to the user. 00023 # logger.debug("Payload: %s" % repr(self)) 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 #if isinstance(value, Fields): 00035 # Special case for the Payload members of Message. 00036 # value = str(value) 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('') # Default units 00063 if fieldmeta[1] == '-': fieldmeta[1] = '' 00064 if len(fieldmeta) == 2: fieldmeta.append("%.2f") # Default format 00065 if len(fieldmeta) == 3: 00066 # Default title based on field name. 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 # return cls(left_speed = utils.to_short(data[0:2], 100), 00125 # right_speed = utils.to_short(data[2:4], 100), 00126 # left_accel = utils.to_short(data[4:6], 100), 00127 # right_accel = utils.to_short(data[6:8], 100)) 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 # payload = DifferentialSpeed(left_speed = 0.8, right_speed = 400, left_accel = 0.2, right_accel = 0.2) 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