messages.py
Go to the documentation of this file.
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.")


clearpath_base
Author(s): Mike Purvis
autogenerated on Sat Dec 28 2013 16:50:47