Commits

Tino de Bruijn  committed 4832e41

Made the byte processing a bit more efficient. The iterate call itself now grabs all the bytes the command handler needs

  • Participants
  • Parent commits 0615562

Comments (0)

Files changed (2)

File pyfirmata/pyfirmata.py

         This method should be called in a main loop, or in an
         :class:`Iterator` instance to keep this boards pin values up to date
         """
-        self._process_data(self.sp.read())
-        
-    def _process_data(self, byte):
-        """
-        Does the actual processing of the data from the microcontroller and
-        delegates the command processing to :method:`_process_command`
-        """
-        # TODO Make this method greedy: read all the bytes from a command at once
+        byte = self.sp.read()
         if not byte:
             return
         data = ord(byte)
-        if self._parsing_sysex:
-            if data == END_SYSEX:
-                self._parsing_sysex = False
-                if self._stored_data[0] in self._command_handlers:
-                    self._process_command(self._stored_data[0], self._stored_data[1:])
-                self._stored_data = []
-            else:
-                self._stored_data.append(data)
-        elif not self._command:
-            if data == START_SYSEX:
-                self._parsing_sysex = True
+        received_data = []
+        handler = None
+        if data < START_SYSEX:
+            # These commands can have 'channel data' like a pin nummber appended.
+            try:
+                handler = self._command_handlers[data & 0xF0]
+            except KeyError:
                 return
-            elif data < 0xF0:
-                # Commands can have 'channel data' like a pin nummber appended. 
-                # This is for commands smaller than 0xF0
-                command = data & 0xF0
-                self._stored_data.append(data & 0x0F)
-            else:
-                command = data
-            if command not in self._command_handlers:
-                # We received a byte not denoting a command with handler 
-                # while we are not processing any commands data. Nothing we
-                # can do about it so discard everything and we'll see what 
-                # comes next.
-                self._stored_data = []
+            received_data.append(data & 0x0F)
+            while len(received_data) < handler.bytes_needed:
+                received_data.append(ord(self.sp.read()))
+        elif data == START_SYSEX:
+            data = ord(self.sp.read())
+            handler = self._command_handlers.get(data)
+            if not handler:
                 return
-            self._command = command
+            data = ord(self.sp.read())
+            while data != END_SYSEX:
+                received_data.append(data)
+                data = ord(self.sp.read())
         else:
-            # This is a data command either belonging to a sysex message, or
-            # to a multibyte command. Append it to the data and see if we can
-            # process the command. If _process_command returns False, it
-            # needs more data.
-            self._stored_data.append(data)
-            if self._process_command(self._command, self._stored_data):
-                self._command = None
-                self._stored_data = []
-    
-    def _process_command(self, command, data):
-        """
-        Tries to get a handler for this command from the self.cmds helper.
-        Will return True if command is handled. This means either the handler
-        handled the data correctly, or it raised a ValueError for not getting
-        in the correct data. It will return False if there wasn't enough data
-        for the handler
-        """
-        # TODO document that a handler should
-        handler = self._command_handlers[command]
-        if len(data) < handler.bytes_needed:
-            return False
+            try:
+                handler = self._command_handlers[data]
+            except KeyError:
+                return
+            while len(received_data) < handler.bytes_needed:
+                received_data.append(ord(self.sp.read()))
+        # Handle the data
         try:
-            handler(*data)
-        except (ValueError, TypeError):
-            # TypeError occurs when we pass to many arguments.
-            # ValueError may be thrown when the received data is not correct.
-            return True
-        return True
+            handler(*received_data)
+        except ValueError:
+            pass
             
     def get_firmata_version(self):
         """
                 self.analog[pin_nr].value = value
         except IndexError:
             raise ValueError
-        return True
 
     def _handle_digital_message(self, port_nr, lsb, msb):
         """
             self.digital_ports[port_nr]._update(mask)
         except IndexError:
             raise ValueError
-        return True
 
     def _handle_report_version(self, major, minor):
         self.firmata_version = (major, minor)
-        return True
         
     def _handle_report_firmware(self, *data):
         major = data[0]
         minor = data[1]
         self.firmata_version = (major, minor)
         self.firmware = ''.join([chr(x) for x in data[2:]]) # TODO this is more complicated, values is send as 7 bit bytes
-        return True
 
 class Port(object):
     """ An 8-bit port on the board """
 import unittest
 import serial
 import time
-from optparse import OptionParser
-
 import pyfirmata
 from pyfirmata import mockup
 from pyfirmata.boards import BOARDS
 
 # type                command  channel    first byte            second byte 
 # ---------------------------------------------------------------------------
-# sysex start           0xF0   
 # set pin mode(I/O)     0xF4              pin # (0-127)         pin state(0=in)
-# sysex end             0xF7   
-# protocol version      0xF9              major version         minor version
 # system reset          0xFF
-#
-# SysEx-based commands (0x00-0x7F) are used for an extended command set.
-
 
 class BoardBaseTest(unittest.TestCase):
     def setUp(self):
         pyfirmata.pyfirmata.serial.Serial = mockup.MockupSerial
         self.board = pyfirmata.Board('')
         self.board._stored_data = [] # FIXME How can it be that a fresh instance sometimes still contains data?
-        
-    def iterate(self, count):
-        for i in range(count):
-            self.board.iterate()
             
 class TestBoardMessages(BoardBaseTest):
     # TODO Test layout of Board Mega
         self.assertEqual(self.board.analog[3].read(), None)
         # This sould set it correctly. 1023 (127, 7 in to 7 bit bytes) is the
         # max value an analog pin will send and it should result in a value 1
-        self.assertTrue(self.board._handle_analog_message(3, 127, 7))
+        self.board._handle_analog_message(3, 127, 7)
         self.assertEqual(self.board.analog[3].read(), 1.0)
         
     def test_handle_digital_message(self):
         mask = 0
         mask |= 1 << 5 # set the bit for pin 5 to to 1
         self.assertEqual(self.board.digital[5].read(), None)
-        self.assertTrue(self.board._handle_digital_message(0, mask % 128, mask >> 7))
+        self.board._handle_digital_message(0, mask % 128, mask >> 7)
         self.assertEqual(self.board.digital[5].read(), True)
         
     def test_handle_report_version(self):
         self.assertEqual(self.board.firmata_version, None)
-        self.assertTrue(self.board._handle_report_version(2, 1))
+        self.board._handle_report_version(2, 1)
         self.assertEqual(self.board.firmata_version, (2, 1))
         
     def test_handle_report_firmware(self):
         self.assertEqual(self.board.firmware, None)
         data = [2, 1] + [ord(x) for x in 'Firmware_name']
-        self.assertTrue(self.board._handle_report_firmware(*data))
+        self.board._handle_report_firmware(*data)
         self.assertEqual(self.board.firmware, 'Firmware_name')
         self.assertEqual(self.board.firmata_version, (2, 1))
         
         self.assertEqual(self.board.analog[4].reporting, False)
         # Should do nothing as the pin isn't set to report
         self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
-        self.iterate(3)
+        self.board.iterate()
         self.assertEqual(self.board.analog[4].read(), None)
         self.board.analog[4].enable_reporting()
         self.board.sp.clear()
         # This should set analog port 4 to 1
         self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
-        self.iterate(3)
+        self.board.iterate()
         self.assertEqual(self.board.analog[4].read(), 1.0)
         self.board._stored_data = []
     
         mask |= 1 << (9 - 8) # set the bit for pin 9 to to 1
         self.assertEqual(self.board.digital[9].read(), None)
         self.board.sp.write([chr(pyfirmata.DIGITAL_MESSAGE + 1), chr(mask % 128), chr(mask >> 7)])
-        self.iterate(3)
+        self.board.iterate()
         self.assertEqual(self.board.digital[9].read(), True)
         
     # version report format
     def test_incoming_report_version(self):
         self.assertEqual(self.board.firmata_version, None)
         self.board.sp.write([chr(pyfirmata.REPORT_VERSION), chr(2), chr(1)])
-        self.iterate(3)
+        self.board.iterate()
         self.assertEqual(self.board.firmata_version, (2, 1))
     
     # Receive Firmware Name and Version (after query)
                chr(1)] + [i for i in 'Firmware_name'] + \
               [chr(pyfirmata.END_SYSEX)]
         self.board.sp.write(msg)
-        self.iterate(25)
+        self.board.iterate()
         self.assertEqual(self.board.firmware, 'Firmware_name')
         self.assertEqual(self.board.firmata_version, (2, 1))
         
         
     def test_too_much_data(self):
         """ 
-        ``_process_command`` should return True when too much data is given
-        for a handler.
+        When we send random bytes, before or after a command, they should be
+        ignored to prevent cascading errors when missing a byte.
         """
-        res = self.board._process_command(pyfirmata.ANALOG_MESSAGE, [1, 1, 1, 1, 1])
-        self.assertTrue(res)
+        self.board.analog[4].enable_reporting()
+        self.board.sp.clear()
+        # Crap
+        self.board.sp.write([chr(i) for i in range(10)])
+        # This should set analog port 4 to 1
+        self.board.sp.write([chr(pyfirmata.ANALOG_MESSAGE + 4), chr(127), chr(7)])
+        # Crap
+        self.board.sp.write([chr(10-i) for i in range(10)])
+        while len(self.board.sp):
+            self.board.iterate()
+        self.assertEqual(self.board.analog[4].read(), 1.0)
+        
         
 class TestBoardLayout(BoardBaseTest):
 
 mockup_suite = unittest.TestLoader().loadTestsFromTestCase(TestMockupBoardLayout)
 
 if __name__ == '__main__':
+    from optparse import OptionParser
     parser = OptionParser()
     parser.add_option("-m", "--mockup", dest="mockup", action="store_true",
         help="Also run the mockup tests")
     options, args = parser.parse_args()
     if not options.mockup:
-        print "Running normal suite. Also consider running the live (-l, --live) \
-                and mockup (-m, --mockup) suites"
+        print "Running normal suite. Also consider running the mockup (-m, --mockup) suite"
         unittest.TextTestRunner(verbosity=3).run(default)
     if options.mockup:
         print "Running the mockup test suite"