PageRenderTime 145ms CodeModel.GetById 60ms app.highlight 57ms RepoModel.GetById 24ms app.codeStats 0ms

/tests.py

https://bitbucket.org/tino/pyfirmata/
Python | 351 lines | 268 code | 21 blank | 62 comment | 6 complexity | 81cab3dea6f30c687d2850804e0231c6 MD5 | raw file
  1import unittest
  2import doctest
  3import serial
  4from itertools import chain
  5import pyfirmata
  6from pyfirmata import mockup
  7from pyfirmata.boards import BOARDS
  8from pyfirmata.util import str_to_two_byte_iter, to_two_bytes
  9
 10# Messages todo left:
 11
 12# type                command  channel    first byte            second byte
 13# ---------------------------------------------------------------------------
 14# set pin mode(I/O)     0xF4              pin # (0-127)         pin state(0=in)
 15# system reset          0xFF
 16
 17class BoardBaseTest(unittest.TestCase):
 18    def setUp(self):
 19        # Test with the MockupSerial so no real connection is needed
 20        pyfirmata.pyfirmata.serial.Serial = mockup.MockupSerial
 21        self.board = pyfirmata.Board('', BOARDS['arduino'])
 22        self.board._stored_data = [] # FIXME How can it be that a fresh instance sometimes still contains data?
 23
 24
 25class TestBoardMessages(BoardBaseTest):
 26    # TODO Test layout of Board Mega
 27    def assert_serial(self, *list_of_chrs):
 28        res = self.board.sp.read()
 29        serial_msg = res
 30        while res:
 31            res = self.board.sp.read()
 32            serial_msg += res
 33        self.assertEqual(''.join(list(list_of_chrs)), serial_msg)
 34
 35    # First test the handlers
 36    def test_handle_analog_message(self):
 37        self.board.analog[3].reporting = True
 38        self.assertEqual(self.board.analog[3].read(), None)
 39        # This sould set it correctly. 1023 (127, 7 in to 7 bit bytes) is the
 40        # max value an analog pin will send and it should result in a value 1
 41        self.board._handle_analog_message(3, 127, 7)
 42        self.assertEqual(self.board.analog[3].read(), 1.0)
 43
 44    def test_handle_digital_message(self):
 45        # A digital message sets the value for a whole port. We will set pin
 46        # 5 (That is on port 0) to 1 to test if this is working.
 47        self.board.digital_ports[0].reporting = True
 48        self.board.digital[5]._mode = 0 # Set it to input
 49        # Create the mask
 50        mask = 0
 51        mask |= 1 << 5 # set the bit for pin 5 to to 1
 52        self.assertEqual(self.board.digital[5].read(), None)
 53        self.board._handle_digital_message(0, mask % 128, mask >> 7)
 54        self.assertEqual(self.board.digital[5].read(), True)
 55
 56    def test_handle_report_version(self):
 57        self.assertEqual(self.board.firmata_version, None)
 58        self.board._handle_report_version(2, 1)
 59        self.assertEqual(self.board.firmata_version, (2, 1))
 60
 61    def test_handle_report_firmware(self):
 62        self.assertEqual(self.board.firmware, None)
 63        data = [2, 1] + str_to_two_byte_iter('Firmware_name')
 64        self.board._handle_report_firmware(*data)
 65        self.assertEqual(self.board.firmware, 'Firmware_name')
 66        self.assertEqual(self.board.firmware_version, (2, 1))
 67
 68    # type                command  channel    first byte            second byte
 69    # ---------------------------------------------------------------------------
 70    # analog I/O message    0xE0   pin #      LSB(bits 0-6)         MSB(bits 7-13)
 71    def test_incoming_analog_message(self):
 72        self.assertEqual(self.board.analog[4].read(), None)
 73        self.assertEqual(self.board.analog[4].reporting, False)
 74        # Should do nothing as the pin isn't set to report
 75        self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
 76        self.board.iterate()
 77        self.assertEqual(self.board.analog[4].read(), None)
 78        self.board.analog[4].enable_reporting()
 79        self.board.sp.clear()
 80        # This should set analog port 4 to 1
 81        self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
 82        self.board.iterate()
 83        self.assertEqual(self.board.analog[4].read(), 1.0)
 84        self.board._stored_data = []
 85
 86    # type                command  channel    first byte            second byte
 87    # ---------------------------------------------------------------------------
 88    # digital I/O message   0x90   port       LSB(bits 0-6)         MSB(bits 7-13)
 89    def test_incoming_digital_message(self):
 90        # A digital message sets the value for a whole port. We will set pin
 91        # 9 (on port 1) to 1 to test if this is working.
 92        self.board.digital[9].mode = pyfirmata.INPUT
 93        self.board.sp.clear() # clear mode sent over the wire.
 94        # Create the mask
 95        mask = 0
 96        mask |= 1 << (9 - 8) # set the bit for pin 9 to to 1
 97        self.assertEqual(self.board.digital[9].read(), None)
 98        self.board.sp.write([chr(pyfirmata.DIGITAL_MESSAGE + 1), chr(mask % 128), chr(mask >> 7)])
 99        self.board.iterate()
100        self.assertEqual(self.board.digital[9].read(), True)
101
102    # version report format
103    # -------------------------------------------------
104    # 0  version report header (0xF9) (MIDI Undefined)
105    # 1  major version (0-127)
106    # 2  minor version (0-127)
107    def test_incoming_report_version(self):
108        self.assertEqual(self.board.firmata_version, None)
109        self.board.sp.write([chr(pyfirmata.REPORT_VERSION), chr(2), chr(1)])
110        self.board.iterate()
111        self.assertEqual(self.board.firmata_version, (2, 1))
112
113    # Receive Firmware Name and Version (after query)
114    # 0  START_SYSEX (0xF0)
115    # 1  queryFirmware (0x79)
116    # 2  major version (0-127)
117    # 3  minor version (0-127)
118    # 4  first 7-bits of firmware name
119    # 5  second 7-bits of firmware name
120    # x  ...for as many bytes as it needs)
121    # 6  END_SYSEX (0xF7)
122    def test_incoming_report_firmware(self):
123        self.assertEqual(self.board.firmware, None)
124        self.assertEqual(self.board.firmware_version, None)
125        msg = [chr(pyfirmata.START_SYSEX),
126               chr(pyfirmata.REPORT_FIRMWARE),
127               chr(2),
128               chr(1)] + str_to_two_byte_iter('Firmware_name') + \
129              [chr(pyfirmata.END_SYSEX)]
130        self.board.sp.write(msg)
131        self.board.iterate()
132        self.assertEqual(self.board.firmware, 'Firmware_name')
133        self.assertEqual(self.board.firmware_version, (2, 1))
134
135    # type                command  channel    first byte            second byte
136    # ---------------------------------------------------------------------------
137    # report analog pin     0xC0   pin #      disable/enable(0/1)   - n/a -
138    def test_report_analog(self):
139        self.board.analog[1].enable_reporting()
140        self.assert_serial(chr(0xC0 + 1), chr(1))
141        self.assertTrue(self.board.analog[1].reporting)
142        self.board.analog[1].disable_reporting()
143        self.assert_serial(chr(0xC0 + 1), chr(0))
144        self.assertFalse(self.board.analog[1].reporting)
145
146    # type                command  channel    first byte            second byte
147    # ---------------------------------------------------------------------------
148    # report digital port   0xD0   port       disable/enable(0/1)   - n/a -
149    def test_report_digital(self):
150        # This should enable reporting of whole port 1
151        self.board.digital[8]._mode = pyfirmata.INPUT # Outputs can't report
152        self.board.digital[8].enable_reporting()
153        self.assert_serial(chr(0xD0 + 1), chr(1))
154        self.assertTrue(self.board.digital_ports[1].reporting)
155        self.board.digital[8].disable_reporting()
156        self.assert_serial(chr(0xD0 + 1), chr(0))
157
158    # Generic Sysex Message
159    # 0     START_SYSEX (0xF0)
160    # 1     sysex command (0x00-0x7F)
161    # x     between 0 and MAX_DATA_BYTES 7-bit bytes of arbitrary data
162    # last  END_SYSEX (0xF7)
163    def test_send_sysex_message(self):
164        # 0x79 is queryFirmware, but that doesn't matter for now
165        self.board.send_sysex(0x79, [1, 2, 3])
166        sysex = (chr(0xF0), chr(0x79), chr(1), chr(2), chr(3), chr(0xF7))
167        self.assert_serial(*sysex)
168
169    def test_send_sysex_to_big_data(self):
170        self.assertRaises(ValueError, self.board.send_sysex, 0x79, [256, 1])
171
172    def test_receive_sysex_message(self):
173        sysex = (chr(0xF0), chr(0x79), chr(2), chr(1), 'a', '\x00', 'b',
174            '\x00', 'c', '\x00', chr(0xF7))
175        self.board.sp.write(sysex)
176        while len(self.board.sp):
177            self.board.iterate()
178        self.assertEqual(self.board.firmware_version, (2, 1))
179        self.assertEqual(self.board.firmware, 'abc')
180
181    def test_too_much_data(self):
182        """
183        When we send random bytes, before or after a command, they should be
184        ignored to prevent cascading errors when missing a byte.
185        """
186        self.board.analog[4].enable_reporting()
187        self.board.sp.clear()
188        # Crap
189        self.board.sp.write([chr(i) for i in range(10)])
190        # This should set analog port 4 to 1
191        self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
192        # Crap
193        self.board.sp.write([chr(10-i) for i in range(10)])
194        while len(self.board.sp):
195            self.board.iterate()
196        self.assertEqual(self.board.analog[4].read(), 1.0)
197
198    # Servo config
199    # --------------------
200    # 0  START_SYSEX (0xF0)
201    # 1  SERVO_CONFIG (0x70)
202    # 2  pin number (0-127)
203    # 3  minPulse LSB (0-6)
204    # 4  minPulse MSB (7-13)
205    # 5  maxPulse LSB (0-6)
206    # 6  maxPulse MSB (7-13)
207    # 7  END_SYSEX (0xF7)
208    #
209    # then sets angle
210    # 8  analog I/O message (0xE0)
211    # 9  angle LSB
212    # 10 angle MSB
213    def test_servo_config(self):
214        self.board.servo_config(2)
215        data = chain([chr(0xF0), chr(0x70), chr(2)], to_two_bytes(544),
216            to_two_bytes(2400), chr(0xF7), chr(0xE0 + 2), chr(0), chr(0))
217        self.assert_serial(*data)
218
219    def test_servo_config_min_max_pulse(self):
220        self.board.servo_config(2, 600, 2000)
221        data = chain([chr(0xF0), chr(0x70), chr(2)], to_two_bytes(600),
222            to_two_bytes(2000), chr(0xF7), chr(0xE0 + 2), chr(0), chr(0))
223        self.assert_serial(*data)
224
225    def test_servo_config_min_max_pulse_angle(self):
226        self.board.servo_config(2, 600, 2000, angle=90)
227        data = chain([chr(0xF0), chr(0x70), chr(2)], to_two_bytes(600),
228            to_two_bytes(2000), chr(0xF7))
229        angle_set = [chr(0xE0 + 2), chr(90 % 128),
230            chr(90 >> 7)] # Angle set happens through analog message
231        data = list(data) + angle_set
232        self.assert_serial(*data)
233
234    def test_servo_config_invalid_pin(self):
235        self.assertRaises(IOError, self.board.servo_config, 1)
236
237    def test_set_mode_servo(self):
238        p = self.board.digital[2]
239        p.mode = pyfirmata.SERVO
240        data = chain([chr(0xF0), chr(0x70), chr(2)], to_two_bytes(544),
241            to_two_bytes(2400), chr(0xF7), chr(0xE0 + 2), chr(0), chr(0))
242        self.assert_serial(*data)
243
244
245class TestBoardLayout(BoardBaseTest):
246
247    def test_layout_arduino(self):
248        self.assertEqual(len(BOARDS['arduino']['digital']), len(self.board.digital))
249        self.assertEqual(len(BOARDS['arduino']['analog']), len(self.board.analog))
250        
251    def test_layout_arduino_mega(self):
252        pyfirmata.pyfirmata.serial.Serial = mockup.MockupSerial
253        mega = pyfirmata.Board('', BOARDS['arduino_mega'])
254        self.assertEqual(len(BOARDS['arduino_mega']['digital']), len(mega.digital))
255        self.assertEqual(len(BOARDS['arduino_mega']['analog']), len(mega.analog))        
256
257    def test_pwm_layout(self):
258        pins = []
259        for pin in self.board.digital:
260            if pin.PWM_CAPABLE:
261                pins.append(self.board.get_pin('d:%d:p' % pin.pin_number))
262        for pin in pins:
263            self.assertEqual(pin.mode, pyfirmata.PWM)
264            self.assertTrue(pin.pin_number in BOARDS['arduino']['pwm'])
265        self.assertTrue(len(pins) == len(BOARDS['arduino']['pwm']))
266
267    def test_get_pin_digital(self):
268        pin = self.board.get_pin('d:13:o')
269        self.assertEqual(pin.pin_number, 13)
270        self.assertEqual(pin.mode, pyfirmata.OUTPUT)
271        self.assertEqual(pin.port.port_number, 1)
272        self.assertEqual(pin.port.reporting, False)
273
274    def test_get_pin_analog(self):
275        pin = self.board.get_pin('a:5:i')
276        self.assertEqual(pin.pin_number, 5)
277        self.assertEqual(pin.reporting, True)
278        self.assertEqual(pin.value, None)
279
280    def tearDown(self):
281        self.board.exit()
282        pyfirmata.serial.Serial = serial.Serial
283
284
285class TestMockupBoardLayout(TestBoardLayout, TestBoardMessages):
286    """
287    TestMockupBoardLayout is subclassed from TestBoardLayout and
288    TestBoardMessages as it should pass the same tests, but with the
289    MockupBoard.
290    """
291    def setUp(self):
292        self.board = mockup.MockupBoard('test', BOARDS['arduino'])
293
294
295class RegressionTests(BoardBaseTest):
296
297    def test_correct_digital_input_first_pin_issue_9(self):
298        """
299        The first pin on the port would always be low, even if the mask said
300        it to be high.
301        """
302        pin = self.board.get_pin('d:8:i')
303        mask = 0
304        mask |= 1 << 0 # set pin 0 high
305        self.board._handle_digital_message(pin.port.port_number,
306            mask % 128, mask >> 7)
307        self.assertEqual(pin.value, True)
308
309    def test_handle_digital_inputs(self):
310        """
311        Test if digital inputs are correctly updated.
312        """
313        for i in range(8, 16): # pins of port 1
314            if not bool(i%2) and i != 14: # all even pins
315                self.board.digital[i].mode = pyfirmata.INPUT
316                self.assertEqual(self.board.digital[i].value, None)
317        mask = 0
318        # Set the mask high for the first 4 pins
319        for i in range(4):
320            mask |= 1 << i
321        self.board._handle_digital_message(1, mask % 128, mask >> 7)
322        self.assertEqual(self.board.digital[8].value, True)
323        self.assertEqual(self.board.digital[9].value, None)
324        self.assertEqual(self.board.digital[10].value, True)
325        self.assertEqual(self.board.digital[11].value, None)
326        self.assertEqual(self.board.digital[12].value, False)
327        self.assertEqual(self.board.digital[13].value, None)
328
329
330board_messages = unittest.TestLoader().loadTestsFromTestCase(TestBoardMessages)
331board_layout = unittest.TestLoader().loadTestsFromTestCase(TestBoardLayout)
332regression = unittest.TestLoader().loadTestsFromTestCase(RegressionTests)
333default = unittest.TestSuite([board_messages, board_layout, regression])
334mockup_suite = unittest.TestLoader().loadTestsFromTestCase(TestMockupBoardLayout)
335
336if __name__ == '__main__':
337    from optparse import OptionParser
338    parser = OptionParser()
339    parser.add_option("-m", "--mockup", dest="mockup", action="store_true",
340        help="Also run the mockup tests")
341    options, args = parser.parse_args()
342    if not options.mockup:
343        print "Running normal suite. Also consider running the mockup (-m, --mockup) suite"
344        unittest.TextTestRunner(verbosity=3).run(default)
345        from pyfirmata import util
346        print "Running doctests for pyfirmata.util. (No output = No errors)"
347        doctest.testmod(util)
348        print "Done running doctests"
349    if options.mockup:
350        print "Running the mockup test suite"
351        unittest.TextTestRunner(verbosity=2).run(mockup_suite)