Repository: ManiacalLabs/BiblioPixel
Pull request #148 (2017-04-05 13:12:27)

Simpixel release

rec: Whattabout this guy??

Patch:
diff --git a/bibliopixel/data_maker.py b/bibliopixel/data_maker.py
index 0734dbc..43976e0 100644
--- a/bibliopixel/data_maker.py
+++ b/bibliopixel/data_maker.py
@@ -1,4 +1,4 @@
-import ctypes, enum
+import ctypes
 from multiprocessing.sharedctypes import RawArray
 
 # Note https://stackoverflow.com/questions/37705974/
diff --git a/bibliopixel/drivers/serial/__init__.py b/bibliopixel/drivers/serial/__init__.py
new file mode 100644
index 0000000..5b70301
--- /dev/null
+++ b/bibliopixel/drivers/serial/__init__.py
@@ -0,0 +1,4 @@
+from . codes import LEDTYPE, SPIChipsets
+from . devices import Devices, serial
+from . driver import DriverSerial, BiblioSerialError
+from . manifest import MANIFEST
diff --git a/bibliopixel/drivers/serial/codes.py b/bibliopixel/drivers/serial/codes.py
new file mode 100644
index 0000000..ef0ff27
--- /dev/null
+++ b/bibliopixel/drivers/serial/codes.py
@@ -0,0 +1,50 @@
+from enum import IntEnum
+
+
+class CMDTYPE(IntEnum):
+    SETUP_DATA = 1  # config data (LED type, SPI speed, num LEDs)
+    PIXEL_DATA = 2  # raw pixel data will be sent as [R1,G1,B1,R2,G2,B2,...]
+    BRIGHTNESS = 3  # data will be single 0-255 brightness value, length must be 0x00,0x01
+    GETID = 4
+    SETID = 5
+    GETVER = 6
+    SYNC = 7
+
+
+class LEDTYPE(IntEnum):
+    GENERIC = 0  # Use if the serial device only supports one chipset
+    LPD8806 = 1
+    WS2801 = 2
+    # These are all the same
+    WS2811 = 3
+    WS2812 = 3
+    WS2812B = 3
+    NEOPIXEL = 3
+    APA104 = 3
+    # 400khz variant of above
+    WS2811_400 = 4
+
+    TM1809 = 5
+    TM1804 = 5
+    TM1803 = 6
+    UCS1903 = 7
+    SM16716 = 8
+    APA102 = 9
+    LPD1886 = 10
+    P9813 = 11
+
+
+SPIChipsets = [
+    LEDTYPE.LPD8806,
+    LEDTYPE.WS2801,
+    LEDTYPE.SM16716,
+    LEDTYPE.APA102,
+    LEDTYPE.P9813
+]
+
+# Chipsets here require extra pixels padded at the end
+# Key must be an LEDTYPE
+# value a lambda function to calc the value based on numLEDs
+BufferChipsets = {
+    LEDTYPE.APA102: lambda num: (int(num / 64.0) + 1)
+}
diff --git a/bibliopixel/drivers/serial/devices.py b/bibliopixel/drivers/serial/devices.py
new file mode 100644
index 0000000..1916cb7
--- /dev/null
+++ b/bibliopixel/drivers/serial/devices.py
@@ -0,0 +1,103 @@
+from distutils.version import LooseVersion
+from . codes import CMDTYPE, LEDTYPE, SPIChipsets, BufferChipsets
+from ... return_codes import RETURN_CODES, print_error
+from ... import log, util
+
+try:
+    import serial, serial.tools
+
+except ImportError as e:
+    error = "Please install pyserial 2.7+! pip install pyserial"
+    log.error(error)
+    raise ImportError(error)
+
+if LooseVersion(serial.VERSION) < LooseVersion('2.7'):
+    error = "pyserial v{} found, please upgrade to v2.7+! pip install pyserial --upgrade".format(
+        serial.VERSION)
+    log.error(error)
+    raise ImportError(error)
+
+
+class Devices(object):
+    """Manage a list of serial devices."""
+
+    def __init__(self, hardware_id, baudrate):
+        self.hardware_id = hardware_id
+        self.baudrate = baudrate
+
+    def find_serial_devices(self):
+        self.devices = {}
+        hardware_id = "(?i)" + self.hardware_id  # forces case insensitive
+
+        for ports in serial.tools.list_ports.grep(hardware_id):
+            port = ports[0]
+            id = self.get_device_id(port, self.baudrate)
+            ver = self._get_device_version(port, self.baudrate)
+            if len(ports) > 1:
+                log.debug('Multi-port device %s:%s:%s with %s ports found',
+                          self.hardware_id, id, ver, len(ports))
+            if id < 0:
+                log.debug('Serial device %s:%s:%s with id %s < 0',
+                          self.hardware_id, id, ver)
+            else:
+                self.devices[id] = port, ver
+
+        return self.devices
+
+    def get_device(self, id=None):
+        if id is None:
+            if not self.devices:
+                raise ValueError('No default device for %s' % self.hardware_id)
+            id, (device, version) = sorted(self.devices.items())[0]
+
+        elif id in self.devices:
+            device, version = self.devices[id]
+
+        else:
+            error = 'Unable to find device with ID %s' % self.deviceID
+            log.error(error)
+            raise ValueError(error)
+
+        log.info("Using COM Port: %s, Device ID: %s, Device Ver: %s",
+                 device, id, version)
+        return id, device, version
+
+    def error(self):
+        error = "There was an unknown error communicating with the device."
+        log.error(error)
+        raise IOError(error)
+
+    def set_device_id(self, dev, id, baudrate=921600):
+        if id < 0 or id > 255:
+            raise ValueError("ID must be an unsigned byte!")
+
+        com = serial.Serial(dev, baudrate=baudrate, timeout=5)
+
+        packet = util.generate_header(CMDTYPE.SETID, 1)
+        packet.append(id)
+        com.write(packet)
+
+        resp = com.read(1)
+        if len(resp) == 0:
+            self.error()
+        elif ord(resp) != RETURN_CODES.SUCCESS:
+            print_error(ord(resp))
+
+    def get_device_id(self, dev, baudrate=921600):
+        packet = util.generate_header(CMDTYPE.GETID, 0)
+        com = serial.Serial(dev, baudrate=baudrate, timeout=5)
+        com.write(packet)
+        resp = ord(com.read(1))
+        return resp
+
+    def _get_device_version(self, dev, baudrate=921600):
+        packet = util.generate_header(CMDTYPE.GETVER, 0)
+        com = serial.Serial(dev, baudrate=baudrate, timeout=0.5)
+        com.write(packet)
+        ver = 0
+        resp = com.read(1)
+        if len(resp) > 0:
+            resp = ord(resp)
+            if resp == RETURN_CODES.SUCCESS:
+                ver = ord(com.read(1))
+        return ver
diff --git a/bibliopixel/drivers/serial/driver.py b/bibliopixel/drivers/serial/driver.py
new file mode 100644
index 0000000..2ca2b0e
--- /dev/null
+++ b/bibliopixel/drivers/serial/driver.py
@@ -0,0 +1,142 @@
+import os, sys, time, traceback
+
+from . codes import CMDTYPE, LEDTYPE, SPIChipsets, BufferChipsets
+from . devices import Devices, serial
+from .. driver_base import DriverBase, ChannelOrder
+from ... import log, util
+from ... return_codes import RETURN_CODES, print_error, BiblioSerialError
+
+
+class DriverSerial(DriverBase):
+    """Main driver for Serial based LED strips"""
+
+    def __init__(self, type, num, dev="",
+                 c_order=ChannelOrder.RGB, SPISpeed=2,
+                 gamma=None, restart_timeout=3,
+                 deviceID=None, hardwareID="1D50:60AB",
+                 baudrate=921600):
+        super().__init__(num, c_order=c_order, gamma=gamma)
+        self.devices = Devices(hardwareID, baudrate)
+
+        if SPISpeed < 1 or SPISpeed > 24 or not (type in SPIChipsets):
+            SPISpeed = 1
+
+        self._SPISpeed = SPISpeed
+        self._com = None
+        self._type = type
+        self._bufPad = 0
+        self.dev = dev
+        self.device_version = 0
+        self.deviceID = deviceID
+        self._sync_packet = util.generate_header(CMDTYPE.SYNC, 0)
+
+        if self.deviceID is not None and (self.deviceID < 0 or self.deviceID > 255):
+            raise ValueError("deviceID must be between 0 and 255")
+
+        resp = self._connect()
+        if resp == RETURN_CODES.REBOOT:  # reboot needed
+            log.info(
+                "Reconfigure and reboot needed, waiting for controller to restart...")
+            self._com.close()
+            time.sleep(restart_timeout)
+            resp = self._connect()
+            if resp != RETURN_CODES.SUCCESS:
+                print_error(resp)
+            else:
+                log.info("Reconfigure success!")
+        elif resp != RETURN_CODES.SUCCESS:
+            print_error(resp)
+
+        if type in SPIChipsets:
+            log.info("Using SPI Speed: %sMHz", self._SPISpeed)
+
+    def cleanup(self):
+        if self._com:
+            log.info("Closing connection to: %s", self.dev)
+            self._com.close()
+
+    def _connect(self):
+        try:
+            self.devices.find_serial_devices()
+            idv = self.devices.get_device(self.device_id)
+            self.device_id, self.dev, self.device_version = idv
+            try:
+                self._com = serial.Serial(
+                    self.dev, baudrate=self.devices.baudrate, timeout=5)
+            except serial.SerialException as e:
+                ports = self.devices.devices.values()
+                error = "Invalid port specified. No COM ports available."
+                if ports:
+                    error = ("Invalid port specified. Try using one of: \n" +
+                             "\n".join(ports))
+                log.info(error)
+                raise BiblioSerialError(error)
+
+            packet = util.generate_header(CMDTYPE.SETUP_DATA, 4)
+            packet.append(self._type)  # set strip type
+            byteCount = self.bufByteCount()
+            if self._type in BufferChipsets:
+                if self._type == LEDTYPE.APA102 and self.device_version >= 2:
+                    pass
+                else:
+                    self._bufPad = BufferChipsets[self._type](self.numLEDs) * 3
+                    byteCount += self._bufPad
+
+            packet.append(byteCount & 0xFF)  # set 1st byte of byteCount
+            packet.append(byteCount >> 8)  # set 2nd byte of byteCount
+            packet.append(self._SPISpeed)
+            self._com.write(packet)
+
+            resp = self._com.read(1)
+            if len(resp) == 0:
+                self.devices.error()
+
+            return ord(resp)
+
+        except serial.SerialException as e:
+            error = ("Unable to connect to the device. Please check that "
+                     "it is connected and the correct port is selected.")
+            log.error(traceback.format_exc())
+            log.error(error)
+            raise e
+
+    def set_brightness(self, brightness):
+        super().set_brightness(brightness)
+        packet = util.generate_header(CMDTYPE.BRIGHTNESS, 1)
+        packet.append(self._brightness)
+        self._com.write(packet)
+        resp = ord(self._com.read(1))
+        if resp == RETURN_CODES.SUCCESS:
+            return True
+        print_error(resp)
+
+    def _send_packet(self):
+        self._com.write(self._packet)
+
+        resp = self._com.read(1)
+        if len(resp) == 0:
+            self.devices.error()
+        if ord(resp) != RETURN_CODES.SUCCESS:
+            print_error(ord(resp))
+
+        self._com.flushInput()
+
+    def _compute_packet(self):
+        count = self.bufByteCount() + self._bufPad
+        self._packet = util.generate_header(CMDTYPE.PIXEL_DATA, count)
+
+        self._render()
+
+        self._packet.extend(self._buf)
+        self._packet.extend([0] * self._bufPad)
+
+    def _send_sync(self):
+        self._com.write(self._sync_packet)
+
+
+class DriverTeensySmartMatrix(DriverSerial):
+    def __init__(self, width, height, dev="", deviceID=None,
+                 hardwareID="16C0:0483"):
+        super().__init__(type=LEDTYPE.GENERIC, num=width * height,
+                         deviceID=deviceID, hardwareID=hardwareID)
+        self.sync = self._send_sync
diff --git a/bibliopixel/drivers/serial/manifest.py b/bibliopixel/drivers/serial/manifest.py
new file mode 100644
index 0000000..4f34770
--- /dev/null
+++ b/bibliopixel/drivers/serial/manifest.py
@@ -0,0 +1,156 @@
+from ... import gamma
+from . driver import DriverSerial, DriverTeensySmartMatrix
+
+MANIFEST = [
+    {
+        "id": "serial",
+        "class": DriverSerial,
+        "type": "driver",
+        "display": "Serial (AllPixel)",
+        "desc": "Interface with USB Serial devices that support the AllPixel protocol.",
+        "params": [{
+                "id": "type",
+                "label": "LED Type",
+                "type": "combo",
+                "options": {
+                    0: "GENERIC",
+                    1: "LPD8806",
+                    2: "WS2801",
+                    3: "WS281x/NEOPIXEL",
+                    4: "WS2811_400",
+                    5: "TM1804",
+                    6: "TM1803",
+                    7: "UCS1903",
+                    8: "SM16716",
+                    9: "APA102",
+                    10: "LPD1886",
+                    11: "P98131"
+                },
+            "default": 0
+        }, {
+            "id": "num",
+            "label": "# Pixels",
+            "type": "int",
+            "default": 0,
+            "min": 0,
+            "help": "Total pixels in display."
+        }, {
+            "id": "dev",
+            "label": "Device Path",
+            "type": "str",
+            "default": "",
+        }, {
+            "id": "c_order",
+            "label": "Channel Order",
+            "type": "combo",
+            "options": {
+                    0: "RGB",
+                    1: "RBG",
+                    2: "GRB",
+                    3: "GBR",
+                    4: "BRG",
+                    5: "BGR"
+            },
+            "options_map": [
+                [0, 1, 2],
+                [0, 2, 1],
+                [1, 0, 2],
+                [1, 2, 0],
+                [2, 0, 1],
+                [2, 1, 0]
+            ],
+            "default": 0
+        }, {
+            "id": "SPISpeed",
+            "label": "SPI Speed (MHz)",
+            "type": "int",
+            "default": 2,
+            "min": 1,
+            "max": 24,
+            "group": "Advanced"
+        }, {
+            "id": "gamma",
+            "label": "Gamma",
+            "type": "combo",
+            "default": None,
+            "options": {
+                    0: "LPD8806",
+                    1: "APA102",
+                    2: "WS2801",
+                    3: "SM16716",
+                    5: "WS281x"
+            },
+            "options_map": [
+                gamma.LPD8806,
+                gamma.APA102,
+                gamma.WS2801,
+                gamma.SM16716,
+                gamma.WS2812B
+            ]
+        }, {
+            "id": "restart_timeout",
+            "label": "Restart Timeout",
+            "type": "int",
+            "default": 3,
+            "min": 1,
+            "group": "Advanced"
+        }, {
+            "id": "deviceID",
+            "label": "Device ID",
+            "type": "int",
+            "default": None,
+            "min": 0,
+            "max": 255,
+            "msg": "AllPixel ID",
+            "group": "Advanced"
+        }, {
+            "id": "hardwareID",
+            "label": "Hardware ID",
+            "type": "str",
+            "default": "1D50:60AB",
+            "group": "Advanced"
+        }, ]
+    },
+    {
+        "id": "teensysmartmatrix",
+        "class": DriverTeensySmartMatrix,
+        "type": "driver",
+        "display": "Teensy SmartMatrix",
+        "desc": "Interface with Teensy SmartMatrix Controller.",
+        "params": [{
+            "id": "width",
+            "label": "Width",
+            "type": "int",
+            "default": 32,
+            "min": 16,
+            "help": "Width of display. Firmware hardcoded."
+        }, {
+            "id": "height",
+            "label": "Height",
+            "type": "int",
+            "default": 32,
+            "min": 16,
+            "help": "Width of display. Firmware hardcoded."
+        }, {
+            "id": "dev",
+            "label": "Device Path",
+            "type": "str",
+            "default": "",
+        }, {
+            "id": "deviceID",
+            "label": "Device ID",
+            "type": "int",
+            "default": None,
+            "min": 0,
+            "max": 255,
+            "msg": "Teensy ID",
+            "group": "Advanced"
+        }, {
+            "id": "hardwareID",
+            "label": "Hardware ID",
+            "type": "str",
+            "default": "16C0:0483",
+            "group": "Advanced"
+        }, ]
+    }
+]
diff --git a/bibliopixel/drivers/serial_driver.py b/bibliopixel/drivers/serial_driver.py
deleted file mode 100644
index b5a1ba9..0000000
--- a/bibliopixel/drivers/serial_driver.py
+++ /dev/null
@@ -1,473 +0,0 @@
-from . driver_base import DriverBase, ChannelOrder
-
-import sys
-import time
-import os
-import traceback
-
-from .. import gamma, log, util
-from .. return_codes import RETURN_CODES, print_error, BiblioSerialError
-
-try:
-    import serial
-    import serial.tools.list_ports
-except ImportError as e:
-    error = "Please install pyserial 2.7+! pip install pyserial"
-    log.error(error)
-    raise ImportError(error)
-
-from distutils.version import LooseVersion
-
-if LooseVersion(serial.VERSION) < LooseVersion('2.7'):
-    error = "pyserial v{} found, please upgrade to v2.7+! pip install pyserial --upgrade".format(
-        serial.VERSION)
-    log.error(error)
-    raise ImportError(error)
-
-
-class CMDTYPE:
-    SETUP_DATA = 1  # config data (LED type, SPI speed, num LEDs)
-    PIXEL_DATA = 2  # raw pixel data will be sent as [R1,G1,B1,R2,G2,B2,...]
-    BRIGHTNESS = 3  # data will be single 0-255 brightness value, length must be 0x00,0x01
-    GETID = 4
-    SETID = 5
-    GETVER = 6
-    SYNC = 7
-
-
-class LEDTYPE:
-    GENERIC = 0  # Use if the serial device only supports one chipset
-    LPD8806 = 1
-    WS2801 = 2
-    # These are all the same
-    WS2811 = 3
-    WS2812 = 3
-    WS2812B = 3
-    NEOPIXEL = 3
-    APA104 = 3
-    # 400khz variant of above
-    WS2811_400 = 4
-
-    TM1809 = 5
-    TM1804 = 5
-    TM1803 = 6
-    UCS1903 = 7
-    SM16716 = 8
-    APA102 = 9
-    LPD1886 = 10
-    P9813 = 11
-
-
-SPIChipsets = [
-    LEDTYPE.LPD8806,
-    LEDTYPE.WS2801,
-    LEDTYPE.SM16716,
-    LEDTYPE.APA102,
-    LEDTYPE.P9813
-]
-
-# Chipsets here require extra pixels padded at the end
-# Key must be an LEDTYPE
-# value a lambda function to calc the value based on numLEDs
-BufferChipsets = {
-    LEDTYPE.APA102: lambda num: (int(num / 64.0) + 1)
-}
-
-
-class DriverSerial(DriverBase):
-    """Main driver for Serial based LED strips"""
-    foundDevices = []
-    deviceIDS = {}
-    deviceVers = []
-
-    def __init__(self, type, num, dev="",
-                 c_order=ChannelOrder.RGB, SPISpeed=2,
-                 gamma=None, restart_timeout=3,
-                 deviceID=None, hardwareID="1D50:60AB",
-                 baudrate=921600):
-        super().__init__(num, c_order=c_order, gamma=gamma)
-
-        if SPISpeed < 1 or SPISpeed > 24 or not (type in SPIChipsets):
-            SPISpeed = 1
-
-        self._hardwareID = hardwareID
-        self._SPISpeed = SPISpeed
-        self._com = None
-        self._type = type
-        self._bufPad = 0
-        self.dev = dev
-        self.devVer = 0
-        self.deviceID = deviceID
-        self._sync_packet = util.generate_header(CMDTYPE.SYNC, 0)
-
-        if self.deviceID is not None and (self.deviceID < 0 or self.deviceID > 255):
-            raise ValueError("deviceID must be between 0 and 255")
-
-        resp = self._connect(baudrate)
-        if resp == RETURN_CODES.REBOOT:  # reboot needed
-            log.info(
-                "Reconfigure and reboot needed, waiting for controller to restart...")
-            self._com.close()
-            time.sleep(restart_timeout)
-            resp = self._connect(baudrate)
-            if resp != RETURN_CODES.SUCCESS:
-                print_error(resp)
-            else:
-                log.info("Reconfigure success!")
-        elif resp != RETURN_CODES.SUCCESS:
-            print_error(resp)
-
-        if type in SPIChipsets:
-            log.info("Using SPI Speed: %sMHz", self._SPISpeed)
-
-    def cleanup(self):
-        if self._com:
-            log.info("Closing connection to: %s", self.dev)
-            self._com.close()
-
-    @staticmethod
-    def findSerialDevices(hardwareID="1D50:60AB", baudrate=921600):
-        hardwareID = "(?i)" + hardwareID  # forces case insensitive
-        if len(DriverSerial.foundDevices) == 0:
-            DriverSerial.foundDevices = []
-            DriverSerial.deviceIDS = {}
-            for port in serial.tools.list_ports.grep(hardwareID):
-                id = DriverSerial.getDeviceID(port[0], baudrate)
-                ver = DriverSerial.getDeviceVer(port[0], baudrate)
-                if id >= 0:
-                    DriverSerial.deviceIDS[id] = port[0]
-                    DriverSerial.foundDevices.append(port[0])
-                    DriverSerial.deviceVers.append(ver)
-
-        return DriverSerial.foundDevices
-
-    @staticmethod
-    def _comError():
-        error = "There was an unknown error communicating with the device."
-        log.error(error)
-        raise IOError(error)
-
-    def _connect(self, baudrate):
-        try:
-            if(self.dev == "" or self.dev is None):
-                DriverSerial.findSerialDevices(self._hardwareID, baudrate)
-
-                if self.deviceID is not None:
-                    if self.deviceID in DriverSerial.deviceIDS:
-                        self.dev = DriverSerial.deviceIDS[self.deviceID]
-                        self.devVer = 0
-                        try:
-                            i = DriverSerial.foundDevices.index(self.dev)
-                            self.devVer = DriverSerial.deviceVers[i]
-                        except:
-                            pass
-                        log.info("Using COM Port: %s, Device ID: %s, Device Ver: %s",
-                                 self.dev, self.deviceID, self.devVer)
-
-                    if self.dev == "" or self.dev is None:
-                        error = "Unable to find device with ID: {}".format(
-                            self.deviceID)
-                        log.error(error)
-                        raise ValueError(error)
-                elif len(DriverSerial.foundDevices) > 0:
-                    self.dev = DriverSerial.foundDevices[0]
-                    self.devVer = 0
-                    try:
-                        i = DriverSerial.foundDevices.index(self.dev)
-                        self.devVer = DriverSerial.deviceVers[i]
-                    except:
-                        pass
-                    devID = -1
-                    for id in DriverSerial.deviceIDS:
-                        if DriverSerial.deviceIDS[id] == self.dev:
-                            devID = id
-
-                    log.info("Using COM Port: %s, Device ID: %s, Device Ver: %s",
-                             self.dev, devID, self.devVer)
-
-            try:
-                self._com = serial.Serial(self.dev, baudrate=baudrate, timeout=5)
-            except serial.SerialException as e:
-                ports = DriverSerial.findSerialDevices(self._hardwareID, baudrate)
-                error = "Invalid port specified. No COM ports available."
-                if len(ports) > 0:
-                    error = "Invalid port specified. Try using one of: \n" + \
-                        "\n".join(ports)
-                log.info(error)
-                raise BiblioSerialError(error)
-
-            packet = util.generate_header(CMDTYPE.SETUP_DATA, 4)
-            packet.append(self._type)  # set strip type
-            byteCount = self.bufByteCount()
-            if self._type in BufferChipsets:
-                if self._type == LEDTYPE.APA102 and self.devVer >= 2:
-                    pass
-                else:
-                    self._bufPad = BufferChipsets[self._type](self.numLEDs) * 3
-                    byteCount += self._bufPad
-
-            packet.append(byteCount & 0xFF)  # set 1st byte of byteCount
-            packet.append(byteCount >> 8)  # set 2nd byte of byteCount
-            packet.append(self._SPISpeed)
-            self._com.write(packet)
-
-            resp = self._com.read(1)
-            if len(resp) == 0:
-                DriverSerial._comError()
-
-            return ord(resp)
-
-        except serial.SerialException as e:
-            error = ("Unable to connect to the device. Please check that "
-                     "it is connected and the correct port is selected.")
-            log.error(traceback.format_exc())
-            log.error(error)
-            raise e
-
-    @staticmethod
-    def setDeviceID(dev, id, baudrate=921600):
-        if id < 0 or id > 255:
-            raise ValueError("ID must be an unsigned byte!")
-
-        try:
-            com = serial.Serial(dev, baudrate=baudrate, timeout=5)
-
-            packet = util.generate_header(CMDTYPE.SETID, 1)
-            packet.append(id)
-            com.write(packet)
-
-            resp = com.read(1)
-            if len(resp) == 0:
-                DriverSerial._comError()
-            else:
-                if ord(resp) != RETURN_CODES.SUCCESS:
-                    print_error(ord(resp))
-
-        except serial.SerialException:
-            log.error("Problem connecting to serial device.")
-            raise IOError("Problem connecting to serial device.")
-
-    @staticmethod
-    def getDeviceID(dev, baudrate=921600):
-        packet = util.generate_header(CMDTYPE.GETID, 0)
-        try:
-            com = serial.Serial(dev, baudrate=baudrate, timeout=5)
-            com.write(packet)
-            resp = ord(com.read(1))
-            return resp
-        except serial.SerialException:
-            log.error("Problem connecting to serial device.")
-            return -1
-
-    @staticmethod
-    def getDeviceVer(dev, baudrate=921600):
-        packet = util.generate_header(CMDTYPE.GETVER, 0)
-        try:
-            com = serial.Serial(dev, baudrate=baudrate, timeout=0.5)
-            com.write(packet)
-            ver = 0
-            resp = com.read(1)
-            if len(resp) > 0:
-                resp = ord(resp)
-                if resp == RETURN_CODES.SUCCESS:
-                    ver = ord(com.read(1))
-            return ver
-        except serial.SerialException:
-            log.error("Problem connecting to serial device.")
-            return 0
-
-    def set_brightness(self, brightness):
-        super().set_brightness(brightness)
-        packet = util.generate_header(CMDTYPE.BRIGHTNESS, 1)
-        packet.append(self._brightness)
-        self._com.write(packet)
-        resp = ord(self._com.read(1))
-        if resp == RETURN_CODES.SUCCESS:
-            return True
-        print_error(resp)
-
-    def _send_packet(self):
-        self._com.write(self._packet)
-
-        resp = self._com.read(1)
-        if len(resp) == 0:
-            DriverSerial._comError()
-        if ord(resp) != RETURN_CODES.SUCCESS:
-            print_error(ord(resp))
-
-        self._com.flushInput()
-
-    def _compute_packet(self):
-        count = self.bufByteCount() + self._bufPad
-        self._packet = util.generate_header(CMDTYPE.PIXEL_DATA, count)
-
-        self._render()
-
-        self._packet.extend(self._buf)
-        self._packet.extend([0] * self._bufPad)
-
-    def _send_sync(self):
-        self._com.write(self._sync_packet)
-
-
-class DriverTeensySmartMatrix(DriverSerial):
-    def __init__(self, width, height, dev="", deviceID=None,
-                 hardwareID="16C0:0483"):
-        super().__init__(type=LEDTYPE.GENERIC, num=width * height,
-                         deviceID=deviceID, hardwareID=hardwareID)
-        self.sync = self._send_sync
-
-
-MANIFEST = [
-    {
-        "id": "serial",
-        "class": DriverSerial,
-        "type": "driver",
-        "display": "Serial (AllPixel)",
-        "desc": "Interface with USB Serial devices that support the AllPixel protocol.",
-        "params": [{
-                "id": "type",
-                "label": "LED Type",
-                "type": "combo",
-                "options": {
-                    0: "GENERIC",
-                    1: "LPD8806",
-                    2: "WS2801",
-                    3: "WS281x/NEOPIXEL",
-                    4: "WS2811_400",
-                    5: "TM1804",
-                    6: "TM1803",
-                    7: "UCS1903",
-                    8: "SM16716",
-                    9: "APA102",
-                    10: "LPD1886",
-                    11: "P98131"
-                },
-            "default": 0
-        }, {
-            "id": "num",
-            "label": "# Pixels",
-            "type": "int",
-            "default": 0,
-            "min": 0,
-            "help": "Total pixels in display."
-        }, {
-            "id": "dev",
-            "label": "Device Path",
-            "type": "str",
-            "default": "",
-        }, {
-            "id": "c_order",
-            "label": "Channel Order",
-            "type": "combo",
-            "options": {
-                    0: "RGB",
-                    1: "RBG",
-                    2: "GRB",
-                    3: "GBR",
-                    4: "BRG",
-                    5: "BGR"
-            },
-            "options_map": [
-                [0, 1, 2],
-                [0, 2, 1],
-                [1, 0, 2],
-                [1, 2, 0],
-                [2, 0, 1],
-                [2, 1, 0]
-            ],
-            "default": 0
-        }, {
-            "id": "SPISpeed",
-            "label": "SPI Speed (MHz)",
-            "type": "int",
-            "default": 2,
-            "min": 1,
-            "max": 24,
-            "group": "Advanced"
-        }, {
-            "id": "gamma",
-            "label": "Gamma",
-            "type": "combo",
-            "default": None,
-            "options": {
-                    0: "LPD8806",
-                    1: "APA102",
-                    2: "WS2801",
-                    3: "SM16716",
-                    5: "WS281x"
-            },
-            "options_map": [
-                gamma.LPD8806,
-                gamma.APA102,
-                gamma.WS2801,
-                gamma.SM16716,
-                gamma.WS2812B
-            ]
-        }, {
-            "id": "restart_timeout",
-            "label": "Restart Timeout",
-            "type": "int",
-            "default": 3,
-            "min": 1,
-            "group": "Advanced"
-        }, {
-            "id": "deviceID",
-            "label": "Device ID",
-            "type": "int",
-            "default": None,
-            "min": 0,
-            "max": 255,
-            "msg": "AllPixel ID",
-            "group": "Advanced"
-        }, {
-            "id": "hardwareID",
-            "label": "Hardware ID",
-            "type": "str",
-            "default": "1D50:60AB",
-            "group": "Advanced"
-        }, ]
-    },
-    {
-        "id": "teensysmartmatrix",
-        "class": DriverTeensySmartMatrix,
-        "type": "driver",
-        "display": "Teensy SmartMatrix",
-        "desc": "Interface with Teensy SmartMatrix Controller.",
-        "params": [{
-            "id": "width",
-            "label": "Width",
-            "type": "int",
-            "default": 32,
-            "min": 16,
-            "help": "Width of display. Firmware hardcoded."
-        }, {
-            "id": "height",
-            "label": "Height",
-            "type": "int",
-            "default": 32,
-            "min": 16,
-            "help": "Width of display. Firmware hardcoded."
-        }, {
-            "id": "dev",
-            "label": "Device Path",
-            "type": "str",
-            "default": "",
-        }, {
-            "id": "deviceID",
-            "label": "Device ID",
-            "type": "int",
-            "default": None,
-            "min": 0,
-            "max": 255,
-            "msg": "Teensy ID",
-            "group": "Advanced"
-        }, {
-            "id": "hardwareID",
-            "label": "Hardware ID",
-            "type": "str",
-            "default": "16C0:0483",
-            "group": "Advanced"
-        }, ]
-    }
-]
diff --git a/bibliopixel/main/all_pixel.py b/bibliopixel/main/all_pixel.py
new file mode 100644
index 0000000..5bea5a8
--- /dev/null
+++ b/bibliopixel/main/all_pixel.py
@@ -0,0 +1,107 @@
+import signal, sys
+from ..drivers.serial import (
+    LEDTYPE, DriverSerial, SPIChipsets, BiblioSerialError, Devices)
+
+HELP = """Configure the AllPixel lighting module."""
+
+types = [
+    [LEDTYPE.GENERIC, "GENERIC"],
+    [LEDTYPE.LPD8806, "LPD8806"],
+    [LEDTYPE.WS2801, "WS2801 "],
+    [LEDTYPE.NEOPIXEL, "WS2812/NEOPIXEL"],
+    [LEDTYPE.APA104, "APA104"],
+    [LEDTYPE.WS2811_400, "WS2811_400"],
+    [LEDTYPE.TM1809, "TM1809/TM1804"],
+    [LEDTYPE.TM1803, "TM1803 "],
+    [LEDTYPE.UCS1903, "UCS1903"],
+    [LEDTYPE.SM16716, "SM16716"],
+    [LEDTYPE.APA102, "APA102/DOTSTAR"],
+    [LEDTYPE.LPD1886, "LPD1886"],
+    [LEDTYPE.P9813, "P9813"]
+]
+
+
+def O(msg):
+    print("\n" + msg)
+
+
+def I(msg):
+    return input("\n" + msg)
+
+
+def get_int(msg, invalid=-1):
+    v = I(msg)
+    try:
+        return int(v)
+    except:
+        return invalid
+
+
+def showSelectList(msg, values):
+    print("\n" + msg)
+    shift = len(str(len(values)))
+    count = 0
+    for v in values:
+        print("{}: {}".format(str(count).rjust(shift), v))
+        count += 1
+    return get_int("Choice: ")
+
+
+def run(args, settings):
+    try:
+        print("Press Ctrl+C anytime to quit.")
+
+        O("Scanning for devices...")
+        devs = sorted(Devices().find_serial_devices().items())
+        d = ""
+        if not devs:
+            I("No devices found! Please connect one and press any key...")
+            raise ValueError()
+        if len(devs) == 1:
+            d = devs[0][1]
+        else:
+            d = showSelectList("Select device:", devs)
+            if d < 0 or d >= len(devs):
+                O("Invalid choice!")
+                raise ValueError()
+            d = devs[d][1]
+
+        t = showSelectList("Choose LED Type", [v[1] for v in types])
+        if t < 0 or t >= len(types):
+            O("Invalid choice!")
+            raise ValueError()
+        t = types[t]
+
+        num = get_int("Number of LEDs: ", invalid=0)
+        if num <= 0:
+            O("Invalid choice!")
+            raise ValueError()
+
+        spi = 2
+        useSPI = t[0] in SPIChipsets
+        if useSPI:
+            spi = get_int("SPI Speed (1-24): ", invalid=0)
+            if spi <= 0:
+                O("Invalid choice!")
+                raise ValueError()
+
+        details = "Device: {}\nLED Type: {}\nNum LEDs: {}".format(d, t[1], num)
+        if useSPI:
+            details += "\nSPI Speed: {}".format(spi)
+
+        O(details + "\n")
+
+        try:
+            DriverSerial(t[0], num, dev=d, SPISpeed=spi, restart_timeout=6)
+            O("Configure complete!")
+        except BiblioSerialError as e:
+            O("Error configuring device!")
+
+    except KeyboardInterrupt:
+        sys.exit(128 + signal.SIGINT)
+    except ValueError as e:
+        pass
+
+
+def set_parser(parser):
+    parser.set_defaults(run=run)
diff --git a/bibliopixel/main/demo.py b/bibliopixel/main/demo.py
index 115f2ba..f4aae49 100644
--- a/bibliopixel/main/demo.py
+++ b/bibliopixel/main/demo.py
@@ -10,7 +10,7 @@ Run a demo.  For the list of possible demos, type
 
 """
 
-SIMPIXEL_URL = 'http://beta.simpixel.io'
+DEFAULT_SIMPIXEL_URL = 'http://simpixel.io'
 IMPORT_ERROR_TEXT = """
 Please install the BiblioPixelAnimations library from here:
 
@@ -61,8 +61,8 @@ def run(args, settings):
             e.msg += IMPORT_ERROR_TEXT
         raise
 
-    if not args.no_simpixel:
-        webbrowser.open(SIMPIXEL_URL, new=0, autoraise=True)
+    if not args.simpixel.startswith('no'):
+        webbrowser.open(args.simpixel, new=0, autoraise=True)
 
     anim.run()
 
@@ -70,6 +70,6 @@ def run(args, settings):
 def set_parser(parser):
     parser.set_defaults(run=run)
     parser.add_argument('name', nargs='?', default='bloom')
-    parser.add_argument('-width', default=32, type=int)
-    parser.add_argument('-height', default=32, type=int)
-    parser.add_argument('-no_simpixel', action='store_true')
+    parser.add_argument('--width', default=32, type=int)
+    parser.add_argument('--height', default=32, type=int)
+    parser.add_argument('--simpixel', default=DEFAULT_SIMPIXEL_URL)
diff --git a/bibliopixel/main/devices.py b/bibliopixel/main/devices.py
new file mode 100644
index 0000000..5953ba1
--- /dev/null
+++ b/bibliopixel/main/devices.py
@@ -0,0 +1,48 @@
+from ..drivers.serial import DriverSerial, Devices, serial
+
+CONNECT_MESSAGE = """
+Connect just one Serial device (AllPixel) and press enter..."""
+
+HELP = """Find serial devices."""
+
+
+def run(args, settings):
+    run = True
+    print("Press Ctrl+C any time to exit.")
+    try:
+        while run:
+            try:
+                input(CONNECT_MESSAGE)
+                devices = Devices()
+                ports = devices.find_serial_devices()
+                if not ports:
+                    print("No serial devices found. Please connect one.")
+                    continue
+
+                port = sorted(ports.items())[0][1]
+                id = devices.get_device_id(port)
+                print("Device ID of {}: {}".format(port, id))
+                newID = input("Input new ID (enter to skip): ")
+                if newID != '':
+                    try:
+                        newID = int(newID)
+                        if newID < 0 or newID > 255:
+                            raise ValueError()
+
+                        devices.set_device_id(port, newID)
+                        id = devices.get_device_id(port)
+                        print("Device ID set to: %s" % id)
+                    except ValueError:
+                        print("Please enter a number between 0 and 255.")
+            except serial.SerialException as e:
+                print("Problem connecting to serial device. %s" % e)
+
+            except Exception as e:
+                print('Programmer error with exception %s' % e)
+
+    except KeyboardInterrupt:
+        pass
+
+
+def set_parser(parser):
+    parser.set_defaults(run=run)
diff --git a/bibliopixel/main/main.py b/bibliopixel/main/main.py
index 0351c61..9def704 100644
--- a/bibliopixel/main/main.py
+++ b/bibliopixel/main/main.py
@@ -5,7 +5,7 @@ from .. project.settings_file import SettingsFile
 
 __all__ = ['main']
 
-COMMANDS = ('demo', 'run', 'set', 'show',)  # 'network'
+COMMANDS = ('all_pixel', 'devices', 'demo', 'run', 'set', 'show')
 MODULES = {c: import_symbol('.' + c, 'bibliopixel.main') for c in COMMANDS}
 DOTFILE_DEFAULT = '~/.bibliopixel'
 LOG_LEVELS = ('debug', 'info', 'warning', 'error', 'critical')
diff --git a/bibliopixel/serial_gamepad.py b/bibliopixel/serial_gamepad.py
index fa65ab5..54782b8 100644
--- a/bibliopixel/serial_gamepad.py
+++ b/bibliopixel/serial_gamepad.py
@@ -31,7 +31,7 @@ class SerialPadError(Exception):
 
 
 class SerialGamePad(BaseGamePad):
-    foundDevices = []
+    devices = []
 
     def __init__(self, btn_map=["A", "B", "SELECT", "START", "UP", "DOWN", "LEFT", "RIGHT", "X", "Y"], dev="", hardwareID="1B4F:9206"):
         super().__init__()
@@ -54,14 +54,14 @@ class SerialGamePad(BaseGamePad):
         self.cleanup()
 
     @staticmethod
-    def findSerialDevices(hardwareID="1B4F:9206"):
+    def find_serial_devices(hardwareID="1B4F:9206"):
         hardwareID = "(?i)" + hardwareID  # forces case insensitive
-        if len(SerialGamePad.foundDevices) == 0:
-            SerialGamePad.foundDevices = []
+        if len(SerialGamePad.devices) == 0:
+            SerialGamePad.devices = []
             for port in serial.tools.list_ports.grep(hardwareID):
-                SerialGamePad.foundDevices.append(port[0])
+                SerialGamePad.devices.append(port[0])
 
-        return SerialGamePad.foundDevices
+        return SerialGamePad.devices
 
     @staticmethod
     def _printError(error):
@@ -85,16 +85,16 @@ class SerialGamePad(BaseGamePad):
     def _connect(self):
         try:
             if(self.dev == ""):
-                SerialGamePad.findSerialDevices(self._hardwareID)
+                SerialGamePad.find_serial_devices(self._hardwareID)
 
-                if len(SerialGamePad.foundDevices) > 0:
-                    self.dev = SerialGamePad.foundDevices[0]
+                if len(SerialGamePad.devices) > 0:
+                    self.dev = SerialGamePad.devices[0]
                     log.info("Using COM Port: %s", self.dev)
 
             try:
                 self._com = serial.Serial(self.dev, timeout=5)
             except serial.SerialException as e:
-                ports = SerialGamePad.findSerialDevices(self._hardwareID)
+                ports = SerialGamePad.find_serial_devices(self._hardwareID)
                 error = "Invalid port specified. No COM ports available."
                 if len(ports) > 0:
                     error = "Invalid port specified. Try using one of: \n" + \
diff --git a/util/AllPixelConfigure.py b/util/AllPixelConfigure.py
deleted file mode 100644
index 6fad826..0000000
--- a/util/AllPixelConfigure.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from __future__ import print_function
-import sys
-from bibliopixel.drivers.serial_driver import LEDTYPE, DriverSerial, SPIChipsets, BiblioSerialError
-import signal
-
-types = [
-    [LEDTYPE.GENERIC, "GENERIC"],
-    [LEDTYPE.LPD8806, "LPD8806"],
-    [LEDTYPE.WS2801, "WS2801 "],
-    [LEDTYPE.NEOPIXEL, "WS2812/NEOPIXEL"],
-    [LEDTYPE.APA104, "APA104"],
-    [LEDTYPE.WS2811_400, "WS2811_400"],
-    [LEDTYPE.TM1809, "TM1809/TM1804"],
-    [LEDTYPE.TM1803, "TM1803 "],
-    [LEDTYPE.UCS1903, "UCS1903"],
-    [LEDTYPE.SM16716, "SM16716"],
-    [LEDTYPE.APA102, "APA102/DOTSTAR"],
-    [LEDTYPE.LPD1886, "LPD1886"],
-    [LEDTYPE.P9813, "P9813"]
-]
-
-
-def O(msg):
-    print("\n" + msg)
-
-
-def I(msg):
-    return input("\n" + msg)
-
-
-def get_int(msg, invalid=-1):
-    v = I(msg)
-    try:
-        return int(v)
-    except:
-        return invalid
-
-
-def showSelectList(msg, values):
-    print("\n" + msg)
-    shift = len(str(len(values)))
-    count = 0
-    for v in values:
-        print("{}: {}".format(str(count).rjust(shift), v))
-        count += 1
-    return get_int("Choice: ")
-
-
-try:
-    print("Press Ctrl+C anytime to quit.")
-
-    O("Scanning for devices...")
-
-    DriverSerial.foundDevices = []
-    devs = DriverSerial.findSerialDevices()
-
-    d = ""
-    if len(devs) == 0:
-        I("No devices found! Please connect one and press any key...")
-        raise ValueError()
-    elif len(devs) > 1:
-        d = showSelectList("Select device:", devs)
-        if d < 0 or d >= len(devs):
-            O("Invalid choice!")
-            raise ValueError()
-        d = devs[d]
-    else:
-        d = devs[0]
-
-    t = showSelectList("Choose LED Type", [v[1] for v in types])
-    if t < 0 or t >= len(types):
-        O("Invalid choice!")
-        raise ValueError()
-    t = types[t]
-
-    num = get_int("Number of LEDs: ", invalid=0)
-    if num <= 0:
-        O("Invalid choice!")
-        raise ValueError()
-
-    spi = 2
-    useSPI = t[0] in SPIChipsets
-    if useSPI:
-        spi = get_int("SPI Speed (1-24): ", invalid=0)
-        if spi <= 0:
-            O("Invalid choice!")
-            raise ValueError()
-
-    details = "Device: {}\nLED Type: {}\nNum LEDs: {}".format(d, t[1], num)
-    if useSPI:
-        details += "\nSPI Speed: {}".format(spi)
-
-    O(details + "\n")
-
-    driver = None
-    try:
-        driver = DriverSerial(
-            t[0], num, dev=d, SPISpeed=spi, restart_timeout=6)
-        O("Configure complete!")
-    except BiblioSerialError as e:
-        O("Error configuring device!")
-
-
-except KeyboardInterrupt:
-    sys.exit(128 + signal.SIGINT)
-except ValueError as e:
-    pass
diff --git a/util/DeviceIDManager.py b/util/DeviceIDManager.py
deleted file mode 100644
index 217ceb7..0000000
--- a/util/DeviceIDManager.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import print_function
-import sys
-from bibliopixel.drivers.serial_driver import DriverSerial
-
-run = True
-print("Press Ctrl+C any time to exit.")
-try:
-    while run:
-        ignored = input("\nConnect just one Serial device (AllPixel) and press enter...")
-        ports = DriverSerial.findSerialDevices()
-        if len(ports):
-            try:
-                id = DriverSerial.getDeviceID(ports[0])
-                print("Device ID of {}: {}".format(ports[0], id))
-                newID = input("Input new ID (enter to skip): ")
-                if newID != '':
-                    try:
-                        newID = int(newID)
-                        if newID < 0 or newID > 255:
-                            raise ValueError()
-
-                        try:
-                            DriverSerial.setDeviceID(ports[0], newID)
-                            print("Device ID set to: {}".format(DriverSerial.getDeviceID(ports[0])))
-                        except:
-                            pass
-                    except ValueError:
-                        print("Please enter a valid number between 0 and 255.")
-            except Exception as e:
-                print(e)
-        else:
-            print("No serial devices found. Please connect one.")
-
-except KeyboardInterrupt as err:
-    pass
-else:
-    pass


Test patch:
diff --git a/test/all_test.py b/test/all_test.py
index 3711144..76ea8d1 100644
--- a/test/all_test.py
+++ b/test/all_test.py
@@ -31,7 +31,7 @@ from bibliopixel.drivers import (
     network,
     network_receiver,
     network_udp,
-    serial_driver,
+    serial,
     spi_driver_base,
     # timedata_visualizer,
 )