Пример #1
0
    def __init__(self, connection=None):
        if not connection:
            connection = get_connection_auto()

        self.connection = connection
        self.info = {}
        self.devices = {}

        # shorthand fields
        self.button = Button(self)
        self.led = None
        self.amperage = None
        self.voltage = None
        self.motor_A = None
        self.motor_B = None
        self.motor_AB = None
        self.color_distance_sensor = None
        self.tilt_sensor = None
        self.motor_external = None
        self.port_C = None
        self.port_D = None

        self.connection.set_notify_handler(self._notify)

        self._wait_for_devices()
        self._report_status()
    def test_button(self):
        hub = HubMock()
        time.sleep(0.1)
        button = Button(hub)
        hub.peripherals[0x00] = button

        vals = []

        def callback(pressed):
            vals.append(pressed)

        hub.connection.notification_delayed("060001020600", 0.0)
        button.subscribe(callback)
        time.sleep(0.1)

        hub.connection.notification_delayed("060001020600", 0.1)
        hub.connection.notification_delayed("060001020601", 0.2)
        hub.connection.notification_delayed("060001020600", 0.3)
        time.sleep(0.4)

        button.unsubscribe(callback)
        time.sleep(0.1)
        hub.connection.wait_notifications_handled()

        self.assertEqual([0, 1, 0], vals)
        self.assertEqual(b"0500010202", hub.writes[1][1])
        self.assertEqual(b"0500010203", hub.writes[2][1])
Пример #3
0
class MoveHub(object):
    """
    :type connection: pylgbst.comms.Connection
    :type devices: dict[int,Peripheral]
    :type led: LED
    :type tilt_sensor: TiltSensor
    :type button: Button
    :type amperage: Amperage
    :type voltage: Voltage
    :type color_distance_sensor: pylgbst.peripherals.ColorDistanceSensor
    :type port_C: Peripheral
    :type port_D: Peripheral
    :type motor_A: EncodedMotor
    :type motor_B: EncodedMotor
    :type motor_AB: EncodedMotor
    :type motor_external: EncodedMotor
    """

    DEV_STATUS_DETACHED = 0x00
    DEV_STATUS_DEVICE = 0x01
    DEV_STATUS_GROUP = 0x02

    def __init__(self, connection=None):
        if not connection:
            connection = get_connection_auto()

        self.connection = connection
        self.info = {}
        self.devices = {}

        # shorthand fields
        self.button = Button(self)
        self.led = None
        self.amperage = None
        self.voltage = None
        self.motor_A = None
        self.motor_B = None
        self.motor_AB = None
        self.color_distance_sensor = None
        self.tilt_sensor = None
        self.motor_external = None
        self.port_C = None
        self.port_D = None

        self.connection.set_notify_handler(self._notify)

        self._wait_for_devices()
        self._report_status()

    def send(self, msg_type, payload):
        cmd = pack("<B", PACKET_VER) + pack("<B", msg_type) + payload
        self.connection.write(MOVE_HUB_HARDWARE_HANDLE,
                              pack("<B",
                                   len(cmd) + 1) + cmd)

    def _wait_for_devices(self):
        self.connection.enable_notifications()

        builtin_devices = ()
        for num in range(0, 60):
            builtin_devices = (self.led, self.motor_A, self.motor_B,
                               self.motor_AB, self.tilt_sensor, self.button,
                               self.amperage, self.voltage)
            if None not in builtin_devices:
                log.debug("All devices are present")
                return
            log.debug("Waiting for builtin devices to appear: %s",
                      builtin_devices)
            time.sleep(0.05)
        log.warning("Got only these devices: %s", builtin_devices)
        raise RuntimeError("Failed to obtain all builtin devices")

    def _notify(self, handle, data):
        orig = data

        if handle != MOVE_HUB_HARDWARE_HANDLE:
            log.warning("Unsupported notification handle: 0x%s", handle)
            return

        log.debug("Notification on %s: %s", handle, str2hex(orig))

        msg_type = usbyte(data, 2)

        if msg_type == MSG_PORT_INFO:
            self._handle_port_info(data)
        elif msg_type == MSG_PORT_STATUS:
            self._handle_port_status(data)
        elif msg_type == MSG_SENSOR_DATA:
            self._handle_sensor_data(data)
        elif msg_type == MSG_SENSOR_SUBSCRIBE_ACK:
            port = usbyte(data, 3)
            log.debug("Sensor subscribe ack on port %s", PORTS[port])
            self.devices[port].finished()
        elif msg_type == MSG_PORT_CMD_ERROR:
            log.warning("Command error: %s", str2hex(data[3:]))
            port = usbyte(data, 3)
            self.devices[port].finished()
        elif msg_type == MSG_DEVICE_SHUTDOWN:
            log.warning("Device reported shutdown: %s", str2hex(data))
            raise KeyboardInterrupt("Device shutdown")
        elif msg_type == MSG_DEVICE_INFO:
            self._handle_device_info(data)
        else:
            log.warning("Unhandled msg type 0x%x: %s", msg_type, str2hex(orig))

    def _handle_device_info(self, data):
        kind = usbyte(data, 3)
        if kind == 2:
            self.button.handle_port_data(data)

        if usbyte(data, 4) == 0x06:
            self.info[kind] = data[5:]
        else:
            log.warning("Unhandled device info: %s", str2hex(data))

    def _handle_sensor_data(self, data):
        port = usbyte(data, 3)
        if port not in self.devices:
            log.warning("Notification on port with no device: %s", PORTS[port])
            return

        device = self.devices[port]
        device.queue_port_data(data)

    def _handle_port_status(self, data):
        port = usbyte(data, 3)
        status = usbyte(data, 4)

        if status == STATUS_STARTED:
            self.devices[port].started()
        elif status == STATUS_FINISHED:
            self.devices[port].finished()
        elif status == STATUS_CONFLICT:
            log.warning("Command conflict on port %s", PORTS[port])
            self.devices[port].finished()
        elif status == STATUS_INPROGRESS:
            log.warning("Another command is in progress on port %s",
                        PORTS[port])
            self.devices[port].finished()
        elif status == STATUS_INTERRUPTED:
            log.warning("Command interrupted on port %s", PORTS[port])
            self.devices[port].finished()
        else:
            log.warning("Unhandled status value: 0x%x on port %s", status,
                        PORTS[port])

    def _handle_port_info(self, data):
        port = usbyte(data, 3)
        status = usbyte(data, 4)

        if status == self.DEV_STATUS_DETACHED:
            log.info("Detached %s", self.devices[port])
            self.devices[port] = None
        elif status == self.DEV_STATUS_DEVICE or status == self.DEV_STATUS_GROUP:
            dev_type = usbyte(data, 5)
            self._attach_device(dev_type, port)
        else:
            raise ValueError("Unhandled device status: %s", status)

        self._update_field(port)
        if self.devices[port] is None:
            del self.devices[port]

    def _attach_device(self, dev_type, port):
        if port in PORTS and dev_type in DEVICE_TYPES:
            log.info("Attached %s on port %s", DEVICE_TYPES[dev_type],
                     PORTS[port])
        else:
            log.warning("Attached device 0x%x on port 0x%x", dev_type, port)

        if dev_type == DEV_MOTOR:
            self.devices[port] = EncodedMotor(self, port)
        elif dev_type == DEV_IMOTOR:
            self.motor_external = EncodedMotor(self, port)
            self.devices[port] = self.motor_external
        elif dev_type == DEV_DCS:
            self.color_distance_sensor = ColorDistanceSensor(self, port)
            self.devices[port] = self.color_distance_sensor
        elif dev_type == DEV_LED:
            self.devices[port] = LED(self, port)
        elif dev_type == DEV_TILT_SENSOR:
            self.devices[port] = TiltSensor(self, port)
        elif dev_type == DEV_AMPERAGE:
            self.devices[port] = Amperage(self, port)
        elif dev_type == DEV_VOLTAGE:
            self.devices[port] = Voltage(self, port)
        else:
            log.warning("Unhandled peripheral type 0x%x on port 0x%x",
                        dev_type, port)
            self.devices[port] = Peripheral(self, port)

    def _update_field(self, port):
        if port == PORT_A:
            self.motor_A = self.devices[port]
        elif port == PORT_B:
            self.motor_B = self.devices[port]
        elif port == PORT_AB:
            self.motor_AB = self.devices[port]
        elif port == PORT_C:
            self.port_C = self.devices[port]
        elif port == PORT_D:
            self.port_D = self.devices[port]
        elif port == PORT_LED:
            self.led = self.devices[port]
        elif port == PORT_TILT_SENSOR:
            self.tilt_sensor = self.devices[port]
        elif port == PORT_AMPERAGE:
            self.amperage = self.devices[port]
        elif port == PORT_VOLTAGE:
            self.voltage = self.devices[port]
        else:
            log.warning("Unhandled port: %s", PORTS[port])

    def shutdown(self):
        self.send(MSG_DEVICE_SHUTDOWN, b'')

    def _report_status(self):
        # TODO: add firmware version
        log.info("%s by %s", self.info_get(INFO_DEVICE_NAME),
                 self.info_get(INFO_MANUFACTURER))

        self.__voltage = 0

        def on_voltage(value):
            self.__voltage = value

        self.voltage.subscribe(on_voltage, granularity=0)
        while not self.__voltage:
            time.sleep(0.05)
        self.voltage.unsubscribe(on_voltage)
        log.info("Voltage: %d%%", self.__voltage * 100)

    def info_get(self, info_type):
        self.info[info_type] = None
        self.send(MSG_DEVICE_INFO,
                  pack("<B", info_type) + pack("<B", INFO_ACTION_GET))
        while self.info[info_type] is None:  # FIXME: will hang forever on error
            time.sleep(0.05)

        return self.info[info_type]

    def __del__(self):
        if self.connection and self.connection.is_alive():
            self.connection.disconnect()