00001
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
00038 return
00039
00040
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
00054 return
00055
00056
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
00107 return
00108
00109
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
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
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
00184 header = '\x01\x01\xFF'
00185
00186
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
00211 header = '\x01\x01\xFF'
00212
00213
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
00238
00239 header = '\x01\x00\xAA'
00240
00241
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
00261
00262 header = '\x01\x00\x01'
00263
00264
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
00280
00281 header = '\x02\x00\x01'
00282
00283
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
00300 header = '\x03\x01\xFF'
00301
00302
00303
00304
00305
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
00347
00348 header = '\x02\x06\x00'
00349
00350
00351
00352
00353
00354
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
00374
00375 header = '\x01\x02\xAA'
00376
00377
00378
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
00408 xbee.send('at', frame_id='A', command='MY')
00409
00410
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
00425 xbee.send(
00426 'at',
00427 frame_id='A',
00428 command='MY',
00429 parameter='\x00\x00'
00430 )
00431
00432
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
00454 self.xbee.at(frame_id='A', command='MY')
00455
00456
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
00467 self.xbee.at(frame_id='A', command='MY', parameter='\x00\x00')
00468
00469
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
00528
00529
00530 header = '\x01\x02\xAA'
00531
00532
00533
00534 sample = '\x00\xAA\x00\xFF'
00535 data = header + sample
00536
00537
00538
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()