$search
00001 """ 00002 frame.py 00003 00004 By Paul Malmsten, 2010 00005 pmalmsten@gmail.com 00006 00007 Represents an API frame for communicating with an XBee 00008 """ 00009 import struct 00010 00011 class APIFrame: 00012 """ 00013 Represents a frame of data to be sent to or which was received 00014 from an XBee device 00015 """ 00016 00017 START_BYTE = '\x7E' 00018 ESCAPE_BYTE = '\x7D' 00019 XON_BYTE = '\x11' 00020 XOFF_BYTE = '\x13' 00021 ESCAPE_BYTES = (START_BYTE, ESCAPE_BYTE, XON_BYTE, XOFF_BYTE) 00022 00023 def __init__(self, data='', escaped=False): 00024 self.data = data 00025 self.raw_data = '' 00026 self.escaped = escaped 00027 self._unescape_next_byte = False 00028 00029 def checksum(self): 00030 """ 00031 checksum: None -> single checksum byte 00032 00033 checksum adds all bytes of the binary, unescaped data in the 00034 frame, saves the last byte of the result, and subtracts it from 00035 0xFF. The final result is the checksum 00036 """ 00037 total = 0 00038 00039 # Add together all bytes 00040 for byte in self.data: 00041 total += ord(byte) 00042 00043 # Only keep the last byte 00044 total = total & 0xFF 00045 00046 # Subtract from 0xFF 00047 return chr(0xFF - total) 00048 00049 def verify(self, chksum): 00050 """ 00051 verify: 1 byte -> boolean 00052 00053 verify checksums the frame, adds the expected checksum, and 00054 determines whether the result is correct. The result should 00055 be 0xFF. 00056 """ 00057 total = 0 00058 00059 # Add together all bytes 00060 for byte in self.data: 00061 total += ord(byte) 00062 00063 # Add checksum too 00064 total += ord(chksum) 00065 00066 # Only keep low bits 00067 total &= 0xFF 00068 00069 # Check result 00070 return total == 0xFF 00071 00072 def len_bytes(self): 00073 """ 00074 len_data: None -> (MSB, LSB) 16-bit integer length, two bytes 00075 00076 len_bytes counts the number of bytes to be sent and encodes the 00077 data length in two bytes, big-endian (most significant first). 00078 """ 00079 count = len(self.data) 00080 return struct.pack("> h", count) 00081 00082 def output(self): 00083 """ 00084 output: None -> valid API frame (binary data) 00085 00086 output will produce a valid API frame for transmission to an 00087 XBee module. 00088 """ 00089 # start is one byte long, length is two bytes 00090 # data is n bytes long (indicated by length) 00091 # chksum is one byte long 00092 data = self.len_bytes() + self.data + self.checksum() 00093 00094 # Only run the escaoe process if it hasn't been already 00095 if self.escaped and len(self.raw_data) < 1: 00096 self.raw_data = APIFrame.escape(data) 00097 00098 if self.escaped: 00099 data = self.raw_data 00100 00101 # Never escape start byte 00102 return APIFrame.START_BYTE + data 00103 00104 @staticmethod 00105 def escape(data): 00106 """ 00107 escape: byte string -> byte string 00108 00109 When a 'special' byte is encountered in the given data string, 00110 it is preceded by an escape byte and XORed with 0x20. 00111 """ 00112 00113 escaped_data = "" 00114 for byte in data: 00115 if byte in APIFrame.ESCAPE_BYTES: 00116 escaped_data += APIFrame.ESCAPE_BYTE 00117 escaped_data += chr(0x20 ^ ord(byte)) 00118 else: 00119 escaped_data += byte 00120 00121 return escaped_data 00122 00123 def fill(self, byte): 00124 """ 00125 fill: byte -> None 00126 00127 Adds the given raw byte to this APIFrame. If this APIFrame is marked 00128 as escaped and this byte is an escape byte, the next byte in a call 00129 to fill() will be unescaped. 00130 """ 00131 00132 if self._unescape_next_byte: 00133 byte = chr(ord(byte) ^ 0x20) 00134 self._unescape_next_byte = False 00135 elif self.escaped and byte == APIFrame.ESCAPE_BYTE: 00136 self._unescape_next_byte = True 00137 return 00138 00139 self.raw_data += byte 00140 00141 def remaining_bytes(self): 00142 remaining = 3 00143 00144 if len(self.raw_data) >= 3: 00145 # First two bytes are the length of the data 00146 raw_len = self.raw_data[1:3] 00147 data_len = struct.unpack("> h", raw_len)[0] 00148 00149 remaining += data_len 00150 00151 # Don't forget the checksum 00152 remaining += 1 00153 00154 return remaining - len(self.raw_data) 00155 00156 def parse(self): 00157 """ 00158 parse: None -> None 00159 00160 Given a valid API frame, parse extracts the data contained 00161 inside it and verifies it against its checksum 00162 """ 00163 if len(self.raw_data) < 3: 00164 ValueError("parse() may only be called on a frame containing at least 3 bytes of raw data (see fill())") 00165 00166 # First two bytes are the length of the data 00167 raw_len = self.raw_data[1:3] 00168 00169 # Unpack it 00170 data_len = struct.unpack("> h", raw_len)[0] 00171 00172 # Read the data 00173 data = self.raw_data[3:3 + data_len] 00174 chksum = self.raw_data[-1] 00175 00176 # Checksum check 00177 self.data = data 00178 if not self.verify(chksum): 00179 raise ValueError("Invalid checksum")