00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046 from .. import utils
00047 from . import codes
00048 from . import check
00049
00050
00051 import logging
00052
00053
00054
00055 __version__ = "1.0"
00056 __revision__ = "$Revision: 916 $"
00057
00058
00059
00060 logger = logging.getLogger('clearpath.horizon.messages')
00061 """Horizon Messages Module Log"""
00062 logger.setLevel(logging.DEBUG)
00063 logger.addHandler(utils.NullLoggingHandler())
00064 logger.propagate = False
00065 logger.debug("Loading clearpath.horizon.messages ...")
00066
00067
00068
00069
00070
00071
00072 class Message(check.Fields, check.namedtuple('Message_', 'code payload no_ack timestamp')):
00073 """Horizon Protocol Message"""
00074
00075
00076 SOH = 0xAA
00077 STX = 0x55
00078
00079 @classmethod
00080 def command(cls, name, args={}, timestamp=0, no_ack=False):
00081 code = codes.codes[name]
00082 return cls(code=code.set(), payload=code.data_payload(**args),
00083 no_ack=no_ack, timestamp=timestamp)
00084
00085 @classmethod
00086 def request(cls, name, args={}, timestamp=0):
00087 code = codes.codes[name]
00088 return cls(code=code.request(), payload=code.request_payload(**args),
00089 no_ack=False, timestamp=timestamp)
00090
00091 @classmethod
00092 def parse(cls, raw):
00093 checksum = utils.to_unsigned_short(raw[-2:])
00094 if utils.ccitt_checksum(raw[:-2]) != checksum:
00095 raise utils.ChecksumError("Bad Checksum")
00096
00097 if raw[0] != Message.SOH:
00098 raise ValueError("Invalid SOH")
00099
00100 if raw[1] != 0xFF & (~raw[2]):
00101 raise ValueError("Invalid length complement")
00102
00103 timestamp = utils.to_unsigned_int(raw[4:8])
00104
00105 if utils.to_byte(raw[8:9]) == 1:
00106 no_ack = True
00107 elif utils.to_byte(raw[8:9]) == 0:
00108 no_ack = False
00109 else:
00110 raise ValueError("Invalid flags")
00111
00112 code = utils.to_unsigned_short(raw[9:11])
00113
00114 if raw[11] != Message.STX:
00115 raise ValueError("Invalid STX")
00116
00117
00118
00119 payload_cls = codes.codes[codes.names[code]].data_payload
00120 if code < 0x8000:
00121 payload_cls = codes.payloads.Ack
00122
00123
00124 payload = payload_cls(raw = raw[12:-2])
00125 return cls(code=code, payload=payload, no_ack=no_ack, timestamp=timestamp)
00126
00127
00128 def __init__(self, **kwargs):
00129
00130 self.validate()
00131
00132
00133 def __str__(self):
00134 lines = []
00135 lines.append("Code: 0x%X" % self.code)
00136 lines.append("No Ack: %s" % bool(self.no_ack))
00137 lines.append("Timestamp: %d" % self.timestamp)
00138 lines.append("Payload:\n %s" % (str(self.payload).replace("\n", "\n ")))
00139 return "\n".join(lines)
00140
00141
00142 def copy(self, timestamp = None):
00143 """Return a copy of this message, with a new timestamp. This is for
00144 resending messages."""
00145 if timestamp == None:
00146 timestamp = self.timestamp
00147 return self._replace(timestamp=timestamp)
00148
00149
00150 def validate(self):
00151 self.check('timestamp').range(0, 4294967295)
00152 self.check('code').range(0, 65535)
00153
00154
00155 def data(self):
00156 data = []
00157 data += utils.from_byte(codes.VERSION_BYTE)
00158 data += utils.from_unsigned_int(self.timestamp)
00159 data += utils.from_byte(1 if self.no_ack else 0)
00160 data += utils.from_unsigned_short(self.code)
00161 data += [Message.STX]
00162 data += self.payload.data
00163
00164 length = len(data) + 2
00165 data = [Message.SOH] + utils.from_byte(length) + utils.from_byte(0xFF&(~(0xFF&length))) + data
00166
00167
00168 checksum = utils.ccitt_checksum(data)
00169 data += utils.from_unsigned_short(checksum)
00170 return data
00171
00172
00173 logger.debug("... clearpath.horizon.messages loaded.")