/tests.py

https://bitbucket.org/tino/pyfirmata/ · Python · 351 lines · 224 code · 41 blank · 86 comment · 15 complexity · 81cab3dea6f30c687d2850804e0231c6 MD5 · raw file

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