$search
00001 #! /usr/bin/env python -m 00002 # -*- coding: utf-8 -*- 00003 # _____ 00004 # / _ \ 00005 # / _/ \ \ 00006 # / / \_/ \ 00007 # / \_/ _ \ ___ _ ___ ___ ____ ____ ___ _____ _ _ 00008 # \ / \_/ \ / / _\| | | __| / _ \ | ┌┐ \ | ┌┐ \ / _ \ |_ _|| | | | 00009 # \ \_/ \_/ / | | | | | └─┐| |_| || └┘ / | └┘_/| |_| | | | | └─┘ | 00010 # \ \_/ / | |_ | |_ | ┌─┘| _ || |\ \ | | | _ | | | | ┌─┐ | 00011 # \_____/ \___/|___||___||_| |_||_| \_\|_| |_| |_| |_| |_| |_| 00012 # ROBOTICS™ 00013 # 00014 # File: messages.py 00015 # Desc: Horizon Protocol Message Definition 00016 # 00017 # Copyright © 2010 Clearpath Robotics, Inc. 00018 # All Rights Reserved 00019 # 00020 # Redistribution and use in source and binary forms, with or without 00021 # modification, are permitted provided that the following conditions are met: 00022 # * Redistributions of source code must retain the above copyright 00023 # notice, this list of conditions and the following disclaimer. 00024 # * Redistributions in binary form must reproduce the above copyright 00025 # notice, this list of conditions and the following disclaimer in the 00026 # documentation and/or other materials provided with the distribution. 00027 # * Neither the name of Clearpath Robotics, Inc. nor the 00028 # names of its contributors may be used to endorse or promote products 00029 # derived from this software without specific prior written permission. 00030 # 00031 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00032 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00033 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00034 # ARE DISCLAIMED. IN NO EVENT SHALL CLEARPATH ROBOTICS, INC. BE LIABLE FOR ANY 00035 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 00036 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00037 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 00038 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00039 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00040 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00041 # 00042 # Please send comments, questions, or patches to code@clearpathrobotics.com 00043 # 00044 00045 00046 from .. import utils # Clearpath Utilities 00047 from . import codes # Horizon Message Codes 00048 from . import check 00049 00050 # Required Python Modules 00051 import logging # Logging Utilities 00052 00053 00054 # Module Support 00055 __version__ = "1.0" 00056 __revision__ = "$Revision: 916 $" 00057 00058 00059 ## Message Log 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 ## Horizon Message 00069 # 00070 # Represents a message to be transmitted to / received from the hardware. 00071 # 00072 class Message(check.Fields, check.namedtuple('Message_', 'code payload no_ack timestamp')): 00073 """Horizon Protocol Message""" 00074 00075 # Horizon Message Constants 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 # If a message's stated type is a command or a request, but it's being received, 00118 # then its payload is actually of type acknowledgment. 00119 payload_cls = codes.codes[codes.names[code]].data_payload 00120 if code < 0x8000: 00121 payload_cls = codes.payloads.Ack 00122 00123 # Payload 00124 payload = payload_cls(raw = raw[12:-2]) # payload_cls.received(raw) 00125 return cls(code=code, payload=payload, no_ack=no_ack, timestamp=timestamp) 00126 00127 00128 def __init__(self, **kwargs): 00129 # Assignment to fields occurs in namedtuple's __new__ 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 # later: data() 00163 00164 length = len(data) + 2 # +2 for checksum 00165 data = [Message.SOH] + utils.from_byte(length) + utils.from_byte(0xFF&(~(0xFF&length))) + data 00166 00167 # Checksum 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.")