$search
00001 #! /usr/bin/python 00002 """ 00003 test_ieee.py 00004 00005 By Paul Malmsten, 2010 00006 pmalmsten@gmail.com 00007 00008 Tests the XBee (IEEE 802.15.4) implementation class for XBee API compliance 00009 """ 00010 import unittest 00011 from xbee.tests.Fake import FakeDevice, FakeReadDevice 00012 from xbee.ieee import XBee 00013 00014 class InitXBee(unittest.TestCase): 00015 """ 00016 Base initalization class 00017 """ 00018 def setUp(self): 00019 """ 00020 Initialize XBee object 00021 """ 00022 self.xbee = XBee(None) 00023 00024 class TestBuildCommand(InitXBee): 00025 """ 00026 _build_command should properly build a command packet 00027 """ 00028 00029 def test_build_at_data_mismatch(self): 00030 """ 00031 if not enough or incorrect data is provided, an exception should 00032 be raised. 00033 """ 00034 try: 00035 self.xbee._build_command("at") 00036 except KeyError: 00037 # Test passes 00038 return 00039 00040 # No exception? Fail. 00041 self.fail( 00042 "An exception was not raised with improper data supplied" 00043 ) 00044 00045 def test_build_at_data_len_mismatch(self): 00046 """ 00047 if data of incorrect length is provided, an exception should be 00048 raised 00049 """ 00050 try: 00051 self.xbee._build_command("at", frame_id="AB", command="MY") 00052 except ValueError: 00053 # Test passes 00054 return 00055 00056 # No exception? Fail. 00057 self.fail( 00058 "An exception was not raised with improper data length" 00059 ) 00060 00061 def test_build_at(self): 00062 """ 00063 _build_command should build a valid at command packet which has 00064 no parameter data to be saved 00065 """ 00066 00067 at_command = "MY" 00068 frame = chr(43) 00069 data = self.xbee._build_command( 00070 "at", 00071 frame_id=frame, 00072 command=at_command 00073 ) 00074 00075 expected_data = '\x08+MY' 00076 self.assertEqual(data, expected_data) 00077 00078 def test_build_at_with_default(self): 00079 """ 00080 _build_command should build a valid at command packet which has 00081 no parameter data to be saved and no frame specified (the 00082 default value of \x00 should be used) 00083 """ 00084 00085 at_command = "MY" 00086 data = self.xbee._build_command("at", command=at_command) 00087 00088 expected_data = '\x08\x00MY' 00089 self.assertEqual(data, expected_data) 00090 00091 class TestSplitResponse(InitXBee): 00092 """ 00093 _split_response should properly split a response packet 00094 """ 00095 00096 def test_unrecognized_response(self): 00097 """ 00098 if a response begins with an unrecognized id byte, 00099 _split_response should raise an exception 00100 """ 00101 data = '\x23\x00\x00\x00' 00102 00103 try: 00104 self.xbee._split_response(data) 00105 except KeyError: 00106 # Passes 00107 return 00108 00109 # Test Fails 00110 self.fail() 00111 00112 def test_bad_data_long(self): 00113 """ 00114 if a response doesn't match the specification's layout, 00115 _split_response should raise an exception 00116 """ 00117 # Over length 00118 data = '\x8a\x00\x00\x00' 00119 self.assertRaises(ValueError, self.xbee._split_response, data) 00120 00121 def test_bad_data_short(self): 00122 """ 00123 if a response doesn't match the specification's layout, 00124 _split_response should raise an exception 00125 """ 00126 # Under length 00127 data = '\x8a' 00128 self.assertRaises(ValueError, self.xbee._split_response, data) 00129 00130 def test_split_status_response(self): 00131 """ 00132 _split_response should properly split a status response packet 00133 """ 00134 data = '\x8a\x01' 00135 00136 info = self.xbee._split_response(data) 00137 expected_info = {'id':'status', 00138 'status':'\x01'} 00139 00140 self.assertEqual(info, expected_info) 00141 00142 def test_split_short_at_response(self): 00143 """ 00144 _split_response should properly split an at_response packet which 00145 has no parameter data 00146 """ 00147 00148 data = '\x88DMY\x01' 00149 info = self.xbee._split_response(data) 00150 expected_info = {'id':'at_response', 00151 'frame_id':'D', 00152 'command':'MY', 00153 'status':'\x01'} 00154 self.assertEqual(info, expected_info) 00155 00156 def test_split_at_resp_with_param(self): 00157 """ 00158 _split_response should properly split an at_response packet which 00159 has parameter data 00160 """ 00161 00162 data = '\x88DMY\x01ABCDEF' 00163 info = self.xbee._split_response(data) 00164 expected_info = {'id':'at_response', 00165 'frame_id':'D', 00166 'command':'MY', 00167 'status':'\x01', 00168 'parameter':'ABCDEF'} 00169 self.assertEqual(info, expected_info) 00170 00171 00172 class TestParseIOData(InitXBee): 00173 """ 00174 XBee class should properly parse IO data received from an XBee 00175 device 00176 """ 00177 00178 def test_parse_single_dio(self): 00179 """ 00180 _parse_samples should properly parse a packet containing a single 00181 sample of only digital io data 00182 """ 00183 # One sample, ADC disabled and DIO8 enabled, DIO 0-7 enabled 00184 header = '\x01\x01\xFF' 00185 00186 # First 7 bits ignored, DIO8 high, DIO 0-7 high 00187 sample = '\x01\xFF' 00188 data = header + sample 00189 00190 expected_results = [{'dio-0':True, 00191 'dio-1':True, 00192 'dio-2':True, 00193 'dio-3':True, 00194 'dio-4':True, 00195 'dio-5':True, 00196 'dio-6':True, 00197 'dio-7':True, 00198 'dio-8':True}] 00199 00200 results = self.xbee._parse_samples(data) 00201 00202 self.assertEqual(results, expected_results) 00203 00204 def test_parse_single_dio_again(self): 00205 """ 00206 _parse_samples should properly parse a packet containing a single 00207 sample of only digital io data, which alternates between on and 00208 off 00209 """ 00210 # One sample, ADC disabled and DIO8 enabled, DIO 0-7 enabled 00211 header = '\x01\x01\xFF' 00212 00213 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00214 sample = '\x00\xAA' 00215 data = header + sample 00216 00217 expected_results = [{'dio-0':False, 00218 'dio-1':True, 00219 'dio-2':False, 00220 'dio-3':True, 00221 'dio-4':False, 00222 'dio-5':True, 00223 'dio-6':False, 00224 'dio-7':True, 00225 'dio-8':False}] 00226 00227 results = self.xbee._parse_samples(data) 00228 00229 self.assertEqual(results, expected_results) 00230 00231 def test_parse_single_dio_subset(self): 00232 """ 00233 _parse_samples should properly parse a packet containing a single 00234 sample of only digital io data for only a subset of the 00235 available pins 00236 """ 00237 # One sample, ADC disabled 00238 # DIO 1,3,5,7 enabled 00239 header = '\x01\x00\xAA' 00240 00241 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00242 sample = '\x00\xAA' 00243 data = header + sample 00244 00245 expected_results = [{'dio-1':True, 00246 'dio-3':True, 00247 'dio-5':True, 00248 'dio-7':True}] 00249 00250 results = self.xbee._parse_samples(data) 00251 00252 self.assertEqual(results, expected_results) 00253 00254 def test_parse_single_dio_subset_again(self): 00255 """ 00256 _parse_samples should properly parse a packet containing a single 00257 sample of only digital io data for only a subset of the 00258 available pins 00259 """ 00260 # One sample, ADC disabled 00261 # DIO 0 enabled 00262 header = '\x01\x00\x01' 00263 00264 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00265 sample = '\x00\xAA' 00266 data = header + sample 00267 00268 expected_results = [{'dio-0':False}] 00269 00270 results = self.xbee._parse_samples(data) 00271 00272 self.assertEqual(results, expected_results) 00273 00274 def test_parse_multiple_dio_subset(self): 00275 """ 00276 _parse_samples should properly parse a packet containing two 00277 samples of only digital io data for one dio line 00278 """ 00279 # Two samples, ADC disabled 00280 # DIO 0 enabled 00281 header = '\x02\x00\x01' 00282 00283 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00284 sample = '\x00\xAA' + '\x00\x01' 00285 data = header + sample 00286 00287 expected_results = [{'dio-0':False}, 00288 {'dio-0':True}] 00289 00290 results = self.xbee._parse_samples(data) 00291 00292 self.assertEqual(results, expected_results) 00293 00294 def test_parse_multiple_dio(self): 00295 """ 00296 _parse_samples should properly parse a packet containing three 00297 samples of only digital io data 00298 """ 00299 # Three samples, ADC disabled and DIO8 enabled, DIO 0-7 enabled 00300 header = '\x03\x01\xFF' 00301 00302 # First 7 bits ignored 00303 # First sample: all bits on 00304 # Second sample: alternating bits on 00305 # Third sample: all bits off 00306 sample = '\x01\xFF' + '\x00\xAA' + '\x00\x00' 00307 data = header + sample 00308 00309 expected_results = [{'dio-0':True, 00310 'dio-1':True, 00311 'dio-2':True, 00312 'dio-3':True, 00313 'dio-4':True, 00314 'dio-5':True, 00315 'dio-6':True, 00316 'dio-7':True, 00317 'dio-8':True}, 00318 {'dio-0':False, 00319 'dio-1':True, 00320 'dio-2':False, 00321 'dio-3':True, 00322 'dio-4':False, 00323 'dio-5':True, 00324 'dio-6':False, 00325 'dio-7':True, 00326 'dio-8':False}, 00327 {'dio-0':False, 00328 'dio-1':False, 00329 'dio-2':False, 00330 'dio-3':False, 00331 'dio-4':False, 00332 'dio-5':False, 00333 'dio-6':False, 00334 'dio-7':False, 00335 'dio-8':False}] 00336 00337 results = self.xbee._parse_samples(data) 00338 00339 self.assertEqual(results, expected_results) 00340 00341 def test_parse_multiple_adc_subset(self): 00342 """ 00343 _parse_samples should parse a data packet containing multiple 00344 samples of adc data from multiple pins in the proper order 00345 """ 00346 # One sample, ADC 0,1 enabled 00347 # DIO disabled 00348 header = '\x02\x06\x00' 00349 00350 # No dio data 00351 # ADC0 value of 0 00352 # ADC1 value of 255 00353 # ADC0 value of 5 00354 # ADC1 value of 7 00355 sample = '\x00\x00' + '\x00\xFF' + '\x00\x05' + '\x00\x07' 00356 data = header + sample 00357 00358 expected_results = [{'adc-0':0, 00359 'adc-1':255}, 00360 {'adc-0':5, 00361 'adc-1':7}] 00362 00363 results = self.xbee._parse_samples(data) 00364 00365 self.assertEqual(results, expected_results) 00366 00367 def test_parse_single_dio_adc_subset(self): 00368 """ 00369 _parse_samples should properly parse a packet containing a single 00370 sample of digital and analog io data for only a subset of the 00371 available pins 00372 """ 00373 # One sample, ADC 0 enabled 00374 # DIO 1,3,5,7 enabled 00375 header = '\x01\x02\xAA' 00376 00377 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00378 # ADC0 value of 255 00379 sample = '\x00\xAA\x00\xFF' 00380 data = header + sample 00381 00382 expected_results = [{'dio-1':True, 00383 'dio-3':True, 00384 'dio-5':True, 00385 'dio-7':True, 00386 'adc-0':255}] 00387 00388 results = self.xbee._parse_samples(data) 00389 00390 self.assertEqual(results, expected_results) 00391 00392 class TestWriteToDevice(unittest.TestCase): 00393 """ 00394 XBee class should properly write binary data in a valid API 00395 frame to a given serial device, including a valid command packet. 00396 """ 00397 00398 def test_send_at_command(self): 00399 """ 00400 calling send should write a full API frame containing the 00401 API AT command packet to the serial device. 00402 """ 00403 00404 serial_port = FakeDevice() 00405 xbee = XBee(serial_port) 00406 00407 # Send an AT command 00408 xbee.send('at', frame_id='A', command='MY') 00409 00410 # Expect a full packet to be written to the device 00411 expected_data = '\x7E\x00\x04\x08AMY\x10' 00412 self.assertEqual(serial_port.data, expected_data) 00413 00414 00415 def test_send_at_command_with_param(self): 00416 """ 00417 calling send should write a full API frame containing the 00418 API AT command packet to the serial device. 00419 """ 00420 00421 serial_port = FakeDevice() 00422 xbee = XBee(serial_port) 00423 00424 # Send an AT command 00425 xbee.send( 00426 'at', 00427 frame_id='A', 00428 command='MY', 00429 parameter='\x00\x00' 00430 ) 00431 00432 # Expect a full packet to be written to the device 00433 expected_data = '\x7E\x00\x06\x08AMY\x00\x00\x10' 00434 self.assertEqual(serial_port.data, expected_data) 00435 00436 class TestSendShorthand(unittest.TestCase): 00437 """ 00438 Tests shorthand for sending commands to an XBee provided by 00439 XBee.__getattr__ 00440 """ 00441 00442 def setUp(self): 00443 """ 00444 Prepare a fake device to read from 00445 """ 00446 self.ser = FakeDevice() 00447 self.xbee = XBee(self.ser) 00448 00449 def test_send_at_command(self): 00450 """ 00451 Send an AT command with a shorthand call 00452 """ 00453 # Send an AT command 00454 self.xbee.at(frame_id='A', command='MY') 00455 00456 # Expect a full packet to be written to the device 00457 expected_data = '\x7E\x00\x04\x08AMY\x10' 00458 self.assertEqual(self.ser.data, expected_data) 00459 00460 def test_send_at_command_with_param(self): 00461 """ 00462 calling send should write a full API frame containing the 00463 API AT command packet to the serial device. 00464 """ 00465 00466 # Send an AT command 00467 self.xbee.at(frame_id='A', command='MY', parameter='\x00\x00') 00468 00469 # Expect a full packet to be written to the device 00470 expected_data = '\x7E\x00\x06\x08AMY\x00\x00\x10' 00471 self.assertEqual(self.ser.data, expected_data) 00472 00473 def test_shorthand_disabled(self): 00474 """ 00475 When shorthand is disabled, any attempt at calling a 00476 non-existant attribute should raise AttributeError 00477 """ 00478 self.xbee = XBee(self.ser, shorthand=False) 00479 00480 try: 00481 self.xbee.at 00482 except AttributeError: 00483 pass 00484 else: 00485 self.fail("Specified shorthand command should not exist") 00486 00487 class TestReadFromDevice(unittest.TestCase): 00488 """ 00489 XBee class should properly read and parse binary data from a serial 00490 port device. 00491 """ 00492 def test_read_at(self): 00493 """ 00494 read and parse a parameterless AT command 00495 """ 00496 device = FakeReadDevice('\x7E\x00\x05\x88DMY\x01\x8c') 00497 xbee = XBee(device) 00498 00499 info = xbee.wait_read_frame() 00500 expected_info = {'id':'at_response', 00501 'frame_id':'D', 00502 'command':'MY', 00503 'status':'\x01'} 00504 self.assertEqual(info, expected_info) 00505 00506 def test_read_at_params(self): 00507 """ 00508 read and parse an AT command with a parameter 00509 """ 00510 device = FakeReadDevice( 00511 '\x7E\x00\x08\x88DMY\x01\x00\x00\x00\x8c' 00512 ) 00513 xbee = XBee(device) 00514 00515 info = xbee.wait_read_frame() 00516 expected_info = {'id':'at_response', 00517 'frame_id':'D', 00518 'command':'MY', 00519 'status':'\x01', 00520 'parameter':'\x00\x00\x00'} 00521 self.assertEqual(info, expected_info) 00522 00523 def test_read_io_data(self): 00524 """ 00525 XBee class should properly read and parse incoming IO data 00526 """ 00527 ## Build IO data 00528 # One sample, ADC 0 enabled 00529 # DIO 1,3,5,7 enabled 00530 header = '\x01\x02\xAA' 00531 00532 # First 7 bits ignored, DIO8 low, DIO 0-7 alternating 00533 # ADC0 value of 255 00534 sample = '\x00\xAA\x00\xFF' 00535 data = header + sample 00536 00537 ## Wrap data in frame 00538 # RX frame data 00539 rx_io_resp = '\x83\x00\x01\x28\x00' 00540 00541 device = FakeReadDevice( 00542 '\x7E\x00\x0C'+ rx_io_resp + data + '\xfd' 00543 ) 00544 xbee = XBee(device) 00545 00546 info = xbee.wait_read_frame() 00547 expected_info = {'id':'rx_io_data', 00548 'source_addr':'\x00\x01', 00549 'rssi':'\x28', 00550 'options':'\x00', 00551 'samples': [{'dio-1':True, 00552 'dio-3':True, 00553 'dio-5':True, 00554 'dio-7':True, 00555 'adc-0':255}] 00556 } 00557 self.assertEqual(info, expected_info) 00558 00559 00560 if __name__ == '__main__': 00561 unittest.main()