mirror of
https://github.com/undera/pylgbst.git
synced 2020-11-18 19:37:26 -08:00
Refactoring
This commit is contained in:
parent
a9a0386634
commit
06cfd9c419
19
demo.py
19
demo.py
@ -1,7 +1,10 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import traceback
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from pylgbst import *
|
from pylgbst import *
|
||||||
|
from pylgbst.comms import DebugServerConnection
|
||||||
|
from pylgbst.movehub import MoveHub
|
||||||
|
|
||||||
log = logging.getLogger("demo")
|
log = logging.getLogger("demo")
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ def demo_tilt_sensor_simple(movehub):
|
|||||||
|
|
||||||
def callback(param1):
|
def callback(param1):
|
||||||
demo_tilt_sensor_simple.cnt += 1
|
demo_tilt_sensor_simple.cnt += 1
|
||||||
log.info("Tilt #%s of %s: %s", demo_tilt_sensor_simple.cnt, limit, TILT_STATES[param1])
|
log.info("Tilt #%s of %s: %s", demo_tilt_sensor_simple.cnt, limit, TiltSensor.TILT_STATES[param1])
|
||||||
|
|
||||||
movehub.tilt_sensor.subscribe(callback)
|
movehub.tilt_sensor.subscribe(callback)
|
||||||
while demo_tilt_sensor_simple.cnt < limit:
|
while demo_tilt_sensor_simple.cnt < limit:
|
||||||
@ -91,7 +94,7 @@ def demo_tilt_sensor_precise(movehub):
|
|||||||
demo_tilt_sensor_simple.cnt += 1
|
demo_tilt_sensor_simple.cnt += 1
|
||||||
log.info("Tilt #%s of %s: roll:%s pitch:%s yaw:%s", demo_tilt_sensor_simple.cnt, limit, pitch, roll, yaw)
|
log.info("Tilt #%s of %s: roll:%s pitch:%s yaw:%s", demo_tilt_sensor_simple.cnt, limit, pitch, roll, yaw)
|
||||||
|
|
||||||
movehub.tilt_sensor.subscribe(callback, mode=TILT_MODE_FULL)
|
movehub.tilt_sensor.subscribe(callback, mode=TiltSensor.MODE_FULL)
|
||||||
while demo_tilt_sensor_simple.cnt < limit:
|
while demo_tilt_sensor_simple.cnt < limit:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
@ -169,14 +172,4 @@ if __name__ == '__main__':
|
|||||||
connection = BLEConnection().connect()
|
connection = BLEConnection().connect()
|
||||||
|
|
||||||
hub = MoveHub(connection)
|
hub = MoveHub(connection)
|
||||||
|
demo_all(hub)
|
||||||
mtr = hub.motor_A
|
|
||||||
|
|
||||||
# mtr.timed(5, async=True)
|
|
||||||
# time.sleep(1)
|
|
||||||
# mtr.stop()
|
|
||||||
|
|
||||||
mtr.test()
|
|
||||||
time.sleep(3)
|
|
||||||
mtr.stop()
|
|
||||||
time.sleep(1)
|
|
||||||
|
@ -1,170 +1,2 @@
|
|||||||
from pylgbst.comms import *
|
from pylgbst.movehub import *
|
||||||
from pylgbst.constants import *
|
|
||||||
from pylgbst.peripherals import *
|
from pylgbst.peripherals import *
|
||||||
|
|
||||||
log = logging.getLogger('movehub')
|
|
||||||
|
|
||||||
|
|
||||||
class MoveHub(object):
|
|
||||||
"""
|
|
||||||
:type connection: pylgbst.comms.Connection
|
|
||||||
:type devices: dict[int,Peripheral]
|
|
||||||
:type led: LED
|
|
||||||
:type tilt_sensor: TiltSensor
|
|
||||||
:type button: Button
|
|
||||||
:type battery: Battery
|
|
||||||
:type color_distance_sensor: pylgbst.peripherals.ColorDistanceSensor
|
|
||||||
:type motor_external: EncodedMotor
|
|
||||||
:type port_C: Peripheral
|
|
||||||
:type port_D: Peripheral
|
|
||||||
:type motor_A: EncodedMotor
|
|
||||||
:type motor_B: EncodedMotor
|
|
||||||
:type motor_AB: EncodedMotor
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, connection=None):
|
|
||||||
if not connection:
|
|
||||||
connection = BLEConnection()
|
|
||||||
|
|
||||||
self.connection = connection
|
|
||||||
self.devices = {}
|
|
||||||
|
|
||||||
# shorthand fields
|
|
||||||
self.button = Button(self)
|
|
||||||
self.led = None
|
|
||||||
self.battery = None
|
|
||||||
self.motor_A = None
|
|
||||||
self.motor_B = None
|
|
||||||
self.motor_AB = None
|
|
||||||
self.tilt_sensor = None
|
|
||||||
self.color_distance_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()
|
|
||||||
|
|
||||||
def _wait_for_devices(self):
|
|
||||||
self.connection.write(ENABLE_NOTIFICATIONS_HANDLE, ENABLE_NOTIFICATIONS_VALUE)
|
|
||||||
|
|
||||||
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)
|
|
||||||
if None not in builtin_devices:
|
|
||||||
return
|
|
||||||
log.debug("Waiting for builtin devices to appear: %s", builtin_devices)
|
|
||||||
time.sleep(1)
|
|
||||||
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
|
|
||||||
|
|
||||||
data = data[3:]
|
|
||||||
log.debug("Notification on %s: %s", handle, str2hex(orig))
|
|
||||||
|
|
||||||
msg_type = get_byte(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 = get_byte(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:]))
|
|
||||||
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):
|
|
||||||
if get_byte(data, 3) == 2:
|
|
||||||
self.button.handle_port_data(data)
|
|
||||||
else:
|
|
||||||
log.warning("Unhandled device info: %s", str2hex(data))
|
|
||||||
|
|
||||||
def _handle_sensor_data(self, data):
|
|
||||||
port = get_byte(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 = get_byte(data, 3)
|
|
||||||
status = get_byte(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])
|
|
||||||
else:
|
|
||||||
log.warning("Unhandled status value: 0x%x", status)
|
|
||||||
|
|
||||||
def _handle_port_info(self, data):
|
|
||||||
port = get_byte(data, 3)
|
|
||||||
dev_type = get_byte(data, 5)
|
|
||||||
|
|
||||||
if port in PORTS and dev_type in DEVICE_TYPES:
|
|
||||||
log.debug("Device %s at port %s", DEVICE_TYPES[dev_type], PORTS[port])
|
|
||||||
else:
|
|
||||||
log.warning("Device 0x%x at 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_BATTERY:
|
|
||||||
self.devices[port] = Battery(self, port)
|
|
||||||
else:
|
|
||||||
log.debug("Unhandled peripheral type 0x%x on port 0x%x", dev_type, port)
|
|
||||||
self.devices[port] = Peripheral(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_BATTERY:
|
|
||||||
self.battery = self.devices[port]
|
|
||||||
else:
|
|
||||||
log.debug("Unhandled port: %s", PORTS[port])
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
cmd = pack("<B", PACKET_VER) + pack("<B", MSG_DEVICE_SHUTDOWN)
|
|
||||||
self.connection.write(MOVE_HUB_HARDWARE_HANDLE, pack("<B", len(cmd) + 1) + cmd)
|
|
||||||
|
@ -9,12 +9,12 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from gattlib import DiscoveryService, GATTRequester
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from gattlib import DiscoveryService, GATTRequester
|
||||||
from six.moves import queue
|
from six.moves import queue
|
||||||
|
|
||||||
from pylgbst.constants import LEGO_MOVE_HUB, MSG_DEVICE_SHUTDOWN
|
from pylgbst.constants import MSG_DEVICE_SHUTDOWN
|
||||||
|
|
||||||
log = logging.getLogger('comms')
|
log = logging.getLogger('comms')
|
||||||
|
|
||||||
@ -30,6 +30,8 @@ else:
|
|||||||
def get_byte(seq, index):
|
def get_byte(seq, index):
|
||||||
return seq[index]
|
return seq[index]
|
||||||
|
|
||||||
|
LEGO_MOVE_HUB = "LEGO Move Hub"
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyMethodOverriding
|
# noinspection PyMethodOverriding
|
||||||
class Requester(GATTRequester):
|
class Requester(GATTRequester):
|
||||||
@ -174,7 +176,7 @@ class DebugServer(object):
|
|||||||
self._check_shutdown(data)
|
self._check_shutdown(data)
|
||||||
|
|
||||||
def _check_shutdown(self, data):
|
def _check_shutdown(self, data):
|
||||||
if get_byte(data, 5) == MSG_DEVICE_SHUTDOWN:
|
if data[5] == MSG_DEVICE_SHUTDOWN:
|
||||||
log.warning("Device shutdown")
|
log.warning("Device shutdown")
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
# GENERAL
|
# GENERAL
|
||||||
LEGO_MOVE_HUB = "LEGO Move Hub"
|
|
||||||
DEVICE_NAME = 0x07
|
|
||||||
MOVE_HUB_HARDWARE_HANDLE = 0x0E
|
MOVE_HUB_HARDWARE_HANDLE = 0x0E
|
||||||
MOVE_HUB_HARDWARE_UUID = '00001624-1212-efde-1623-785feabcd123'
|
MOVE_HUB_HARDWARE_UUID = '00001624-1212-efde-1623-785feabcd123'
|
||||||
|
|
||||||
ENABLE_NOTIFICATIONS_HANDLE = 0x000f
|
|
||||||
ENABLE_NOTIFICATIONS_VALUE = b'\x01\x00'
|
|
||||||
|
|
||||||
PACKET_VER = 0x01
|
PACKET_VER = 0x01
|
||||||
|
|
||||||
# PORTS
|
# PORTS
|
||||||
@ -70,46 +65,6 @@ STATUS_STARTED = 0x01
|
|||||||
STATUS_CONFLICT = 0x05
|
STATUS_CONFLICT = 0x05
|
||||||
STATUS_FINISHED = 0x0a
|
STATUS_FINISHED = 0x0a
|
||||||
|
|
||||||
# TILT SENSOR
|
|
||||||
TILT_MODE_2AXIS_FULL = 0x00
|
|
||||||
TILT_MODE_2AXIS_SIMPLE = 0x01
|
|
||||||
TILT_MODE_BASIC = 0x02
|
|
||||||
TILT_MODE_BUMP = 0x03
|
|
||||||
TILT_MODE_FULL = 0x04
|
|
||||||
|
|
||||||
TILT_HORIZONTAL = 0x00
|
|
||||||
TILT_UP = 0x01
|
|
||||||
TILT_DOWN = 0x02
|
|
||||||
TILT_RIGHT = 0x03
|
|
||||||
TILT_LEFT = 0x04
|
|
||||||
TILT_FRONT = 0x05
|
|
||||||
TILT_SOME1 = 0x07
|
|
||||||
TILT_SOME2 = 0x09
|
|
||||||
|
|
||||||
TILT_STATES = {
|
|
||||||
TILT_HORIZONTAL: "HORIZONTAL",
|
|
||||||
TILT_UP: "UP",
|
|
||||||
TILT_DOWN: "DOWN",
|
|
||||||
TILT_RIGHT: "RIGHT",
|
|
||||||
TILT_LEFT: "LEFT",
|
|
||||||
TILT_FRONT: "FRONT",
|
|
||||||
TILT_SOME1: "LEFT1",
|
|
||||||
TILT_SOME2: "SOME2",
|
|
||||||
}
|
|
||||||
|
|
||||||
# COLOR & DISTANCE SENSOR
|
|
||||||
CDS_MODE_COLOR_ONLY = 0x00
|
|
||||||
CDS_MODE_DISTANCE_INCHES = 0x01
|
|
||||||
CDS_MODE_COUNT_2INCH = 0x02
|
|
||||||
CDS_MODE_DISTANCE_HOW_CLOSE = 0x03
|
|
||||||
CDS_MODE_DISTANCE_SUBINCH_HOW_CLOSE = 0x04
|
|
||||||
CDS_MODE_OFF1 = 0x05
|
|
||||||
CDS_MODE_STREAM_3_VALUES = 0x06
|
|
||||||
CDS_MODE_OFF2 = 0x07
|
|
||||||
CDS_MODE_COLOR_DISTANCE_FLOAT = 0x08
|
|
||||||
CDS_MODE_LUMINOSITY = 0x09
|
|
||||||
CDS_MODE_SOME_20BYTES = 0x0a
|
|
||||||
|
|
||||||
# COLORS
|
# COLORS
|
||||||
COLOR_BLACK = 0x00
|
COLOR_BLACK = 0x00
|
||||||
COLOR_PINK = 0x01
|
COLOR_PINK = 0x01
|
||||||
@ -137,8 +92,3 @@ COLORS = {
|
|||||||
COLOR_WHITE: "WHITE",
|
COLOR_WHITE: "WHITE",
|
||||||
COLOR_NONE: "NONE"
|
COLOR_NONE: "NONE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# MOTORS
|
|
||||||
MOTOR_MODE_SOMETHING1 = 0x00
|
|
||||||
MOTOR_MODE_SPEED = 0x01
|
|
||||||
MOTOR_MODE_ANGLE = 0x02
|
|
||||||
|
177
pylgbst/movehub.py
Normal file
177
pylgbst/movehub.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from struct import pack
|
||||||
|
|
||||||
|
from pylgbst.comms import BLEConnection, str2hex, get_byte
|
||||||
|
from pylgbst.constants import *
|
||||||
|
from pylgbst.peripherals import Button, EncodedMotor, ColorDistanceSensor, LED, TiltSensor, Battery, Peripheral
|
||||||
|
|
||||||
|
log = logging.getLogger('movehub')
|
||||||
|
|
||||||
|
ENABLE_NOTIFICATIONS_HANDLE = 0x000f
|
||||||
|
ENABLE_NOTIFICATIONS_VALUE = b'\x01\x00'
|
||||||
|
|
||||||
|
|
||||||
|
class MoveHub(object):
|
||||||
|
"""
|
||||||
|
:type connection: pylgbst.comms.Connection
|
||||||
|
:type devices: dict[int,Peripheral]
|
||||||
|
:type led: LED
|
||||||
|
:type tilt_sensor: TiltSensor
|
||||||
|
:type button: Button
|
||||||
|
:type battery: Battery
|
||||||
|
:type color_distance_sensor: pylgbst.peripherals.ColorDistanceSensor
|
||||||
|
:type motor_external: EncodedMotor
|
||||||
|
:type port_C: Peripheral
|
||||||
|
:type port_D: Peripheral
|
||||||
|
:type motor_A: EncodedMotor
|
||||||
|
:type motor_B: EncodedMotor
|
||||||
|
:type motor_AB: EncodedMotor
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
if not connection:
|
||||||
|
connection = BLEConnection()
|
||||||
|
|
||||||
|
self.connection = connection
|
||||||
|
self.devices = {}
|
||||||
|
|
||||||
|
# shorthand fields
|
||||||
|
self.button = Button(self)
|
||||||
|
self.led = None
|
||||||
|
self.battery = None
|
||||||
|
self.motor_A = None
|
||||||
|
self.motor_B = None
|
||||||
|
self.motor_AB = None
|
||||||
|
self.tilt_sensor = None
|
||||||
|
self.color_distance_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()
|
||||||
|
|
||||||
|
def _wait_for_devices(self):
|
||||||
|
self.connection.write(ENABLE_NOTIFICATIONS_HANDLE, ENABLE_NOTIFICATIONS_VALUE)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if None not in builtin_devices:
|
||||||
|
return
|
||||||
|
log.debug("Waiting for builtin devices to appear: %s", builtin_devices)
|
||||||
|
time.sleep(1)
|
||||||
|
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
|
||||||
|
|
||||||
|
data = data[3:]
|
||||||
|
log.debug("Notification on %s: %s", handle, str2hex(orig))
|
||||||
|
|
||||||
|
msg_type = get_byte(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 = get_byte(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:]))
|
||||||
|
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):
|
||||||
|
if get_byte(data, 3) == 2:
|
||||||
|
self.button.handle_port_data(data)
|
||||||
|
else:
|
||||||
|
log.warning("Unhandled device info: %s", str2hex(data))
|
||||||
|
|
||||||
|
def _handle_sensor_data(self, data):
|
||||||
|
port = get_byte(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 = get_byte(data, 3)
|
||||||
|
status = get_byte(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])
|
||||||
|
else:
|
||||||
|
log.warning("Unhandled status value: 0x%x", status)
|
||||||
|
|
||||||
|
def _handle_port_info(self, data):
|
||||||
|
port = get_byte(data, 3)
|
||||||
|
dev_type = get_byte(data, 5)
|
||||||
|
|
||||||
|
if port in PORTS and dev_type in DEVICE_TYPES:
|
||||||
|
log.debug("Device %s at port %s", DEVICE_TYPES[dev_type], PORTS[port])
|
||||||
|
else:
|
||||||
|
log.warning("Device 0x%x at 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_BATTERY:
|
||||||
|
self.devices[port] = Battery(self, port)
|
||||||
|
else:
|
||||||
|
log.debug("Unhandled peripheral type 0x%x on port 0x%x", dev_type, port)
|
||||||
|
self.devices[port] = Peripheral(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_BATTERY:
|
||||||
|
self.battery = self.devices[port]
|
||||||
|
else:
|
||||||
|
log.debug("Unhandled port: %s", PORTS[port])
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
cmd = pack("<B", PACKET_VER) + pack("<B", MSG_DEVICE_SHUTDOWN)
|
||||||
|
self.connection.write(MOVE_HUB_HARDWARE_HANDLE, pack("<B", len(cmd) + 1) + cmd)
|
@ -3,9 +3,10 @@ import time
|
|||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
from six.moves import queue
|
from six.moves import queue
|
||||||
|
|
||||||
from pylgbst import get_byte, str2hex
|
from pylgbst.comms import str2hex
|
||||||
from pylgbst.constants import *
|
from pylgbst.constants import *
|
||||||
|
|
||||||
log = logging.getLogger('peripherals')
|
log = logging.getLogger('peripherals')
|
||||||
@ -19,7 +20,7 @@ class Peripheral(object):
|
|||||||
|
|
||||||
def __init__(self, parent, port):
|
def __init__(self, parent, port):
|
||||||
"""
|
"""
|
||||||
:type parent: pylgbst.MoveHub
|
:type parent: pylgbst.movehub.MoveHub
|
||||||
:type port: int
|
:type port: int
|
||||||
"""
|
"""
|
||||||
super(Peripheral, self).__init__()
|
super(Peripheral, self).__init__()
|
||||||
@ -28,6 +29,7 @@ class Peripheral(object):
|
|||||||
self._working = False
|
self._working = False
|
||||||
self._subscribers = set()
|
self._subscribers = set()
|
||||||
self._port_subscription_mode = None
|
self._port_subscription_mode = None
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
self._incoming_port_data = queue.Queue()
|
self._incoming_port_data = queue.Queue()
|
||||||
thr = Thread(target=self._queue_reader)
|
thr = Thread(target=self._queue_reader)
|
||||||
thr.setDaemon(True)
|
thr.setDaemon(True)
|
||||||
@ -130,7 +132,7 @@ class LED(Peripheral):
|
|||||||
log.debug("LED has changed color to %s", COLORS[self._last_color_set])
|
log.debug("LED has changed color to %s", COLORS[self._last_color_set])
|
||||||
self._notify_subscribers(self._last_color_set)
|
self._notify_subscribers(self._last_color_set)
|
||||||
|
|
||||||
def subscribe(self, callback, mode=None, granularity=None):
|
def subscribe(self, callback, mode=None, granularity=None, async=False):
|
||||||
self._subscribers.add(callback)
|
self._subscribers.add(callback)
|
||||||
|
|
||||||
def unsubscribe(self, callback=None):
|
def unsubscribe(self, callback=None):
|
||||||
@ -140,16 +142,22 @@ class LED(Peripheral):
|
|||||||
|
|
||||||
class EncodedMotor(Peripheral):
|
class EncodedMotor(Peripheral):
|
||||||
TRAILER = b'\x64\x7f\x03' # NOTE: \x64 is 100, might mean something
|
TRAILER = b'\x64\x7f\x03' # NOTE: \x64 is 100, might mean something
|
||||||
MOVEMENT_TYPE = b'\x11'
|
# trailer is not required for all movement types
|
||||||
|
MOVEMENT_TYPE = 0x11
|
||||||
|
|
||||||
CONSTANT_SINGLE = b'\x01'
|
CONSTANT_SINGLE = 0x01
|
||||||
CONSTANT_GROUP = b'\x02'
|
CONSTANT_GROUP = 0x02
|
||||||
SOME_SINGLE = b'\x07'
|
SOME_SINGLE = 0x07
|
||||||
SOME_GROUP = b'\x08'
|
SOME_GROUP = 0x08
|
||||||
TIMED_SINGLE = b'\x09'
|
TIMED_SINGLE = 0x09
|
||||||
TIMED_GROUP = b'\x0A'
|
TIMED_GROUP = 0x0A
|
||||||
ANGLED_SINGLE = b'\x0B'
|
ANGLED_SINGLE = 0x0B
|
||||||
ANGLED_GROUP = b'\x0C'
|
ANGLED_GROUP = 0x0C
|
||||||
|
|
||||||
|
# MOTORS
|
||||||
|
MOTOR_MODE_SOMETHING1 = 0x00
|
||||||
|
MOTOR_MODE_SPEED = 0x01
|
||||||
|
MOTOR_MODE_ANGLE = 0x02
|
||||||
|
|
||||||
def _speed_abs(self, relative):
|
def _speed_abs(self, relative):
|
||||||
if relative < -1:
|
if relative < -1:
|
||||||
@ -165,29 +173,28 @@ class EncodedMotor(Peripheral):
|
|||||||
absolute += 255
|
absolute += 255
|
||||||
return int(absolute)
|
return int(absolute)
|
||||||
|
|
||||||
def _wrap_and_write(self, command, speed_primary, speed_secondary):
|
def _wrap_and_write(self, mtype, params, speed_primary, speed_secondary):
|
||||||
# set for port
|
|
||||||
command = self.MOVEMENT_TYPE + command
|
|
||||||
|
|
||||||
command += pack("<B", self._speed_abs(speed_primary))
|
|
||||||
if self.port == PORT_AB:
|
if self.port == PORT_AB:
|
||||||
command += pack("<B", self._speed_abs(speed_secondary))
|
mtype += 1 # de-facto rule
|
||||||
|
|
||||||
command += self.TRAILER
|
params = pack("<B", self.MOVEMENT_TYPE) + pack("<B", mtype) + params
|
||||||
|
|
||||||
self._write_to_hub(MSG_SET_PORT_VAL, command)
|
params += pack("<B", self._speed_abs(speed_primary))
|
||||||
|
if self.port == PORT_AB:
|
||||||
|
params += pack("<B", self._speed_abs(speed_secondary))
|
||||||
|
|
||||||
|
params += self.TRAILER
|
||||||
|
|
||||||
|
self._write_to_hub(MSG_SET_PORT_VAL, params)
|
||||||
|
|
||||||
def timed(self, seconds, speed_primary=1, speed_secondary=None, async=False):
|
def timed(self, seconds, speed_primary=1, speed_secondary=None, async=False):
|
||||||
if speed_secondary is None:
|
if speed_secondary is None:
|
||||||
speed_secondary = speed_primary
|
speed_secondary = speed_primary
|
||||||
|
|
||||||
# movement type
|
params = pack('<H', int(seconds * 1000))
|
||||||
command = self.TIMED_GROUP if self.port == PORT_AB else self.TIMED_SINGLE
|
|
||||||
# time
|
|
||||||
command += pack('<H', int(seconds * 1000))
|
|
||||||
|
|
||||||
self.started()
|
self.started()
|
||||||
self._wrap_and_write(command, speed_primary, speed_secondary)
|
self._wrap_and_write(self.TIMED_SINGLE, params, speed_primary, speed_secondary)
|
||||||
self._wait_sync(async)
|
self._wait_sync(async)
|
||||||
|
|
||||||
def angled(self, angle, speed_primary=1, speed_secondary=None, async=False):
|
def angled(self, angle, speed_primary=1, speed_secondary=None, async=False):
|
||||||
@ -199,107 +206,98 @@ class EncodedMotor(Peripheral):
|
|||||||
speed_primary = -speed_primary
|
speed_primary = -speed_primary
|
||||||
speed_secondary = -speed_secondary
|
speed_secondary = -speed_secondary
|
||||||
|
|
||||||
# movement type
|
params = pack('<I', angle)
|
||||||
command = self.ANGLED_GROUP if self.port == PORT_AB else self.ANGLED_SINGLE
|
|
||||||
# angle
|
|
||||||
command += pack('<I', angle)
|
|
||||||
|
|
||||||
self.started()
|
self.started()
|
||||||
self._wrap_and_write(command, speed_primary, speed_secondary)
|
self._wrap_and_write(self.ANGLED_SINGLE, params, speed_primary, speed_secondary)
|
||||||
self._wait_sync(async)
|
self._wait_sync(async)
|
||||||
|
|
||||||
def constant(self, speed_primary=1, speed_secondary=None, async=False):
|
def constant(self, speed_primary=1, speed_secondary=None, async=False):
|
||||||
if speed_secondary is None:
|
if speed_secondary is None:
|
||||||
speed_secondary = speed_primary
|
speed_secondary = speed_primary
|
||||||
|
|
||||||
# movement type
|
|
||||||
command = self.CONSTANT_GROUP if self.port == PORT_AB else self.CONSTANT_SINGLE
|
|
||||||
|
|
||||||
self.started()
|
self.started()
|
||||||
self._wrap_and_write(command, speed_primary, speed_secondary)
|
self._wrap_and_write(self.CONSTANT_SINGLE, b"", speed_primary, speed_secondary)
|
||||||
self._wait_sync(async)
|
self._wait_sync(async)
|
||||||
|
|
||||||
def __some(self, speed_primary=1, speed_secondary=None, async=False):
|
def __some(self, speed_primary=1, speed_secondary=None, async=False):
|
||||||
if speed_secondary is None:
|
if speed_secondary is None:
|
||||||
speed_secondary = speed_primary
|
speed_secondary = speed_primary
|
||||||
|
|
||||||
# movement type
|
|
||||||
command = self.SOME_GROUP if self.port == PORT_AB else self.SOME_SINGLE
|
|
||||||
|
|
||||||
self.started()
|
self.started()
|
||||||
self._wrap_and_write(command, speed_primary, speed_secondary)
|
self._wrap_and_write(self.SOME_SINGLE, b"", speed_primary, speed_secondary)
|
||||||
self._wait_sync(async)
|
self._wait_sync(async)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.constant(0, async=True)
|
self.constant(0, async=True)
|
||||||
|
|
||||||
def __test(self, speed_primary=1, speed_secondary=None):
|
|
||||||
if speed_secondary is None:
|
|
||||||
speed_secondary = speed_primary
|
|
||||||
|
|
||||||
self.started()
|
|
||||||
|
|
||||||
# self._wrap_and_write(command, 0.2, 0.2)
|
|
||||||
|
|
||||||
# set for port
|
|
||||||
command = self.MOVEMENT_TYPE + b"\x07"
|
|
||||||
command += pack('<H', self._speed_abs(0.2))
|
|
||||||
command += pack('<H', 1000)
|
|
||||||
|
|
||||||
# command += pack('<B', self._speed_abs(speed_primary)) # time or angle - first param
|
|
||||||
# command += pack('<B', self._speed_abs(speed_secondary)) # time or angle - first param
|
|
||||||
|
|
||||||
speed_primary = 0.5
|
|
||||||
speed_secondary = -0.5
|
|
||||||
# command += pack("<B", self._speed_abs(speed_primary))
|
|
||||||
# if self.port == PORT_AB:
|
|
||||||
# command += pack("<B", self._speed_abs(speed_secondary))
|
|
||||||
|
|
||||||
# command += self.TRAILER
|
|
||||||
|
|
||||||
self._write_to_hub(MSG_SET_PORT_VAL, command)
|
|
||||||
|
|
||||||
def handle_port_data(self, data):
|
def handle_port_data(self, data):
|
||||||
if self._port_subscription_mode == MOTOR_MODE_ANGLE:
|
if self._port_subscription_mode == self.MOTOR_MODE_ANGLE:
|
||||||
rotation = unpack("<l", data[4:8])[0]
|
rotation = unpack("<l", data[4:8])[0]
|
||||||
self._notify_subscribers(rotation)
|
self._notify_subscribers(rotation)
|
||||||
elif self._port_subscription_mode == MOTOR_MODE_SOMETHING1:
|
elif self._port_subscription_mode == self.MOTOR_MODE_SOMETHING1:
|
||||||
# TODO: understand what it means
|
# TODO: understand what it means
|
||||||
rotation = unpack("<B", data[4])[0]
|
rotation = unpack("<B", data[4])[0]
|
||||||
self._notify_subscribers(rotation)
|
self._notify_subscribers(rotation)
|
||||||
elif self._port_subscription_mode == MOTOR_MODE_SPEED:
|
elif self._port_subscription_mode == self.MOTOR_MODE_SPEED:
|
||||||
rotation = unpack("<b", data[4])[0]
|
rotation = unpack("<b", data[4])[0]
|
||||||
self._notify_subscribers(rotation)
|
self._notify_subscribers(rotation)
|
||||||
else:
|
else:
|
||||||
log.debug("Got motor sensor data while in unexpected mode: %s", self._port_subscription_mode)
|
log.debug("Got motor sensor data while in unexpected mode: %s", self._port_subscription_mode)
|
||||||
|
|
||||||
def subscribe(self, callback, mode=MOTOR_MODE_ANGLE, granularity=1):
|
def subscribe(self, callback, mode=MOTOR_MODE_ANGLE, granularity=1, async=False):
|
||||||
super(EncodedMotor, self).subscribe(callback, mode, granularity)
|
super(EncodedMotor, self).subscribe(callback, mode, granularity)
|
||||||
|
|
||||||
|
|
||||||
class TiltSensor(Peripheral):
|
class TiltSensor(Peripheral):
|
||||||
def subscribe(self, callback, mode=TILT_MODE_BASIC, granularity=1):
|
MODE_2AXIS_FULL = 0x00
|
||||||
|
MODE_2AXIS_SIMPLE = 0x01
|
||||||
|
MODE_BASIC = 0x02
|
||||||
|
MODE_BUMP = 0x03
|
||||||
|
MODE_FULL = 0x04
|
||||||
|
|
||||||
|
HORIZONTAL = 0x00
|
||||||
|
UP = 0x01
|
||||||
|
DOWN = 0x02
|
||||||
|
RIGHT = 0x03
|
||||||
|
LEFT = 0x04
|
||||||
|
FRONT = 0x05
|
||||||
|
SOME1 = 0x07 # TODO
|
||||||
|
SOME2 = 0x09 # TODO
|
||||||
|
|
||||||
|
TILT_STATES = {
|
||||||
|
HORIZONTAL: "HORIZONTAL",
|
||||||
|
UP: "UP",
|
||||||
|
DOWN: "DOWN",
|
||||||
|
RIGHT: "RIGHT",
|
||||||
|
LEFT: "LEFT",
|
||||||
|
FRONT: "FRONT",
|
||||||
|
SOME1: "LEFT1",
|
||||||
|
SOME2: "RIGHT1",
|
||||||
|
}
|
||||||
|
|
||||||
|
def subscribe(self, callback, mode=MODE_BASIC, granularity=1, async=False):
|
||||||
super(TiltSensor, self).subscribe(callback, mode, granularity)
|
super(TiltSensor, self).subscribe(callback, mode, granularity)
|
||||||
|
|
||||||
def handle_port_data(self, data):
|
def handle_port_data(self, data):
|
||||||
if self._port_subscription_mode == TILT_MODE_BASIC:
|
if self._port_subscription_mode == self.MODE_BASIC:
|
||||||
state = get_byte(data, 4)
|
state = data[4]
|
||||||
self._notify_subscribers(state)
|
self._notify_subscribers(state)
|
||||||
elif self._port_subscription_mode == TILT_MODE_2AXIS_SIMPLE:
|
elif self._port_subscription_mode == self.MODE_2AXIS_SIMPLE:
|
||||||
# TODO: figure out right interpreting of this
|
# TODO: figure out right interpreting of this
|
||||||
state = get_byte(data, 4)
|
state = data[4]
|
||||||
self._notify_subscribers(state)
|
self._notify_subscribers(state)
|
||||||
elif self._port_subscription_mode == TILT_MODE_BUMP:
|
elif self._port_subscription_mode == self.MODE_BUMP:
|
||||||
bump_count = get_byte(data, 4)
|
bump_count = data[4]
|
||||||
self._notify_subscribers(bump_count)
|
self._notify_subscribers(bump_count)
|
||||||
elif self._port_subscription_mode == TILT_MODE_2AXIS_FULL:
|
elif self._port_subscription_mode == self.MODE_2AXIS_FULL:
|
||||||
roll = self._byte2deg(get_byte(data, 4))
|
roll = self._byte2deg(data[4])
|
||||||
pitch = self._byte2deg(get_byte(data, 5))
|
pitch = self._byte2deg(data[5])
|
||||||
self._notify_subscribers(roll, pitch)
|
self._notify_subscribers(roll, pitch)
|
||||||
elif self._port_subscription_mode == TILT_MODE_FULL:
|
elif self._port_subscription_mode == self.MODE_FULL:
|
||||||
roll = self._byte2deg(get_byte(data, 4))
|
roll = self._byte2deg(data[4])
|
||||||
pitch = self._byte2deg(get_byte(data, 5))
|
pitch = self._byte2deg(data[5])
|
||||||
yaw = self._byte2deg(get_byte(data, 6)) # did I get the order right?
|
yaw = self._byte2deg(data[6]) # did I get the order right?
|
||||||
self._notify_subscribers(roll, pitch, yaw)
|
self._notify_subscribers(roll, pitch, yaw)
|
||||||
else:
|
else:
|
||||||
log.debug("Got tilt sensor data while in unexpected mode: %s", self._port_subscription_mode)
|
log.debug("Got tilt sensor data while in unexpected mode: %s", self._port_subscription_mode)
|
||||||
@ -312,44 +310,56 @@ class TiltSensor(Peripheral):
|
|||||||
|
|
||||||
|
|
||||||
class ColorDistanceSensor(Peripheral):
|
class ColorDistanceSensor(Peripheral):
|
||||||
|
COLOR_ONLY = 0x00
|
||||||
|
DISTANCE_INCHES = 0x01
|
||||||
|
COUNT_2INCH = 0x02
|
||||||
|
DISTANCE_HOW_CLOSE = 0x03
|
||||||
|
DISTANCE_SUBINCH_HOW_CLOSE = 0x04
|
||||||
|
OFF1 = 0x05
|
||||||
|
STREAM_3_VALUES = 0x06
|
||||||
|
OFF2 = 0x07
|
||||||
|
COLOR_DISTANCE_FLOAT = 0x08
|
||||||
|
LUMINOSITY = 0x09
|
||||||
|
SOME_20BYTES = 0x0a
|
||||||
|
|
||||||
def __init__(self, parent, port):
|
def __init__(self, parent, port):
|
||||||
super(ColorDistanceSensor, self).__init__(parent, port)
|
super(ColorDistanceSensor, self).__init__(parent, port)
|
||||||
|
|
||||||
def subscribe(self, callback, mode=CDS_MODE_COLOR_DISTANCE_FLOAT, granularity=1):
|
def subscribe(self, callback, mode=COLOR_DISTANCE_FLOAT, granularity=1, async=False):
|
||||||
super(ColorDistanceSensor, self).subscribe(callback, mode, granularity)
|
super(ColorDistanceSensor, self).subscribe(callback, mode, granularity)
|
||||||
|
|
||||||
def handle_port_data(self, data):
|
def handle_port_data(self, data):
|
||||||
if self._port_subscription_mode == CDS_MODE_COLOR_DISTANCE_FLOAT:
|
if self._port_subscription_mode == self.COLOR_DISTANCE_FLOAT:
|
||||||
color = get_byte(data, 4)
|
color = data[4]
|
||||||
distance = get_byte(data, 5)
|
distance = data[5]
|
||||||
partial = get_byte(data, 7)
|
partial = data[7]
|
||||||
if partial:
|
if partial:
|
||||||
distance += 1.0 / partial
|
distance += 1.0 / partial
|
||||||
self._notify_subscribers(color, float(distance))
|
self._notify_subscribers(color, float(distance))
|
||||||
elif self._port_subscription_mode == CDS_MODE_COLOR_ONLY:
|
elif self._port_subscription_mode == self.COLOR_ONLY:
|
||||||
color = get_byte(data, 4)
|
color = data[4]
|
||||||
self._notify_subscribers(color)
|
self._notify_subscribers(color)
|
||||||
elif self._port_subscription_mode == CDS_MODE_DISTANCE_INCHES:
|
elif self._port_subscription_mode == self.DISTANCE_INCHES:
|
||||||
distance = get_byte(data, 4)
|
distance = data[4]
|
||||||
self._notify_subscribers(distance)
|
self._notify_subscribers(distance)
|
||||||
elif self._port_subscription_mode == CDS_MODE_DISTANCE_HOW_CLOSE:
|
elif self._port_subscription_mode == self.DISTANCE_HOW_CLOSE:
|
||||||
distance = get_byte(data, 4)
|
distance = data[4]
|
||||||
self._notify_subscribers(distance)
|
self._notify_subscribers(distance)
|
||||||
elif self._port_subscription_mode == CDS_MODE_DISTANCE_SUBINCH_HOW_CLOSE:
|
elif self._port_subscription_mode == self.DISTANCE_SUBINCH_HOW_CLOSE:
|
||||||
distance = get_byte(data, 4)
|
distance = data[4]
|
||||||
self._notify_subscribers(distance)
|
self._notify_subscribers(distance)
|
||||||
elif self._port_subscription_mode == CDS_MODE_OFF1 or self._port_subscription_mode == CDS_MODE_OFF2:
|
elif self._port_subscription_mode == self.OFF1 or self._port_subscription_mode == self.OFF2:
|
||||||
log.info("Turned off led on %s", self)
|
log.info("Turned off led on %s", self)
|
||||||
elif self._port_subscription_mode == CDS_MODE_COUNT_2INCH:
|
elif self._port_subscription_mode == self.COUNT_2INCH:
|
||||||
count = unpack("<L", data[4:8])[0] # is it all 4 bytes or just 2?
|
count = unpack("<L", data[4:8])[0] # is it all 4 bytes or just 2?
|
||||||
self._notify_subscribers(count)
|
self._notify_subscribers(count)
|
||||||
elif self._port_subscription_mode == CDS_MODE_STREAM_3_VALUES:
|
elif self._port_subscription_mode == self.STREAM_3_VALUES:
|
||||||
# TODO: understand better meaning of these 3 values
|
# TODO: understand better meaning of these 3 values
|
||||||
val1 = unpack("<H", data[4:6])[0]
|
val1 = unpack("<H", data[4:6])[0]
|
||||||
val2 = unpack("<H", data[6:8])[0]
|
val2 = unpack("<H", data[6:8])[0]
|
||||||
val3 = unpack("<H", data[8:10])[0]
|
val3 = unpack("<H", data[8:10])[0]
|
||||||
self._notify_subscribers(val1, val2, val3)
|
self._notify_subscribers(val1, val2, val3)
|
||||||
elif self._port_subscription_mode == CDS_MODE_LUMINOSITY:
|
elif self._port_subscription_mode == self.LUMINOSITY:
|
||||||
luminosity = unpack("<H", data[4:6])[0] / 1023.0
|
luminosity = unpack("<H", data[4:6])[0] / 1023.0
|
||||||
self._notify_subscribers(luminosity)
|
self._notify_subscribers(luminosity)
|
||||||
else: # TODO: support whatever we forgot
|
else: # TODO: support whatever we forgot
|
||||||
@ -361,7 +371,7 @@ class Battery(Peripheral):
|
|||||||
super(Battery, self).__init__(parent, port)
|
super(Battery, self).__init__(parent, port)
|
||||||
self.last_value = None
|
self.last_value = None
|
||||||
|
|
||||||
def subscribe(self, callback, mode=0, granularity=1):
|
def subscribe(self, callback, mode=0, granularity=1, async=False):
|
||||||
super(Battery, self).subscribe(callback, mode, granularity)
|
super(Battery, self).subscribe(callback, mode, granularity)
|
||||||
|
|
||||||
# we know only voltage subscription from it. is it really battery or just onboard voltage?
|
# we know only voltage subscription from it. is it really battery or just onboard voltage?
|
||||||
@ -383,7 +393,7 @@ class Button(Peripheral):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(Button, self).__init__(parent, 0)
|
super(Button, self).__init__(parent, 0)
|
||||||
|
|
||||||
def subscribe(self, callback, mode=None, granularity=1):
|
def subscribe(self, callback, mode=None, granularity=1, async=False):
|
||||||
cmd = pack("<B", PACKET_VER) + pack("<B", MSG_DEVICE_INFO) + b'\x02\x02'
|
cmd = pack("<B", PACKET_VER) + pack("<B", MSG_DEVICE_INFO) + b'\x02\x02'
|
||||||
self.parent.connection.write(MOVE_HUB_HARDWARE_HANDLE, pack("<B", len(cmd) + 1) + cmd)
|
self.parent.connection.write(MOVE_HUB_HARDWARE_HANDLE, pack("<B", len(cmd) + 1) + cmd)
|
||||||
if callback:
|
if callback:
|
||||||
|
37
tests.py
37
tests.py
@ -1,6 +1,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
from binascii import unhexlify
|
||||||
|
|
||||||
from pylgbst import *
|
from pylgbst import *
|
||||||
|
from pylgbst.comms import Connection
|
||||||
|
from pylgbst.movehub import MoveHub
|
||||||
|
|
||||||
HANDLE = MOVE_HUB_HARDWARE_HANDLE
|
HANDLE = MOVE_HUB_HARDWARE_HANDLE
|
||||||
|
|
||||||
@ -44,8 +47,14 @@ class ConnectionMock(Connection):
|
|||||||
|
|
||||||
|
|
||||||
class HubMock(MoveHub):
|
class HubMock(MoveHub):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
def __init__(self, connection=None):
|
def __init__(self, connection=None):
|
||||||
|
"""
|
||||||
|
:type connection: ConnectionMock
|
||||||
|
"""
|
||||||
super(HubMock, self).__init__(connection if connection else ConnectionMock())
|
super(HubMock, self).__init__(connection if connection else ConnectionMock())
|
||||||
|
self.notify_mock = self.connection.notifications
|
||||||
|
self.writes = self.connection.writes
|
||||||
|
|
||||||
def _wait_for_devices(self):
|
def _wait_for_devices(self):
|
||||||
pass
|
pass
|
||||||
@ -65,33 +74,33 @@ class GeneralTest(unittest.TestCase):
|
|||||||
hub = HubMock()
|
hub = HubMock()
|
||||||
led = LED(hub, PORT_LED)
|
led = LED(hub, PORT_LED)
|
||||||
led.set_color(COLOR_RED)
|
led.set_color(COLOR_RED)
|
||||||
self.assertEqual("0801813201510009", hub.connection.writes[0][1])
|
self.assertEqual("0801813201510009", hub.writes[0][1])
|
||||||
|
|
||||||
def test_tilt_sensor(self):
|
def test_tilt_sensor(self):
|
||||||
hub = HubMock()
|
hub = HubMock()
|
||||||
hub.connection.notifications.append((HANDLE, '1b0e00 0f00 04 3a 0128000000000100000001'))
|
hub.notify_mock.append((HANDLE, '1b0e00 0f00 04 3a 0128000000000100000001'))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def callback(param1, param2=None, param3=None):
|
def callback(param1, param2=None, param3=None):
|
||||||
if param2 is None:
|
if param2 is None:
|
||||||
log.debug("Tilt: %s", TILT_STATES[param1])
|
log.debug("Tilt: %s", TiltSensor.TILT_STATES[param1])
|
||||||
else:
|
else:
|
||||||
log.debug("Tilt: %s %s %s", param1, param2, param3)
|
log.debug("Tilt: %s %s %s", param1, param2, param3)
|
||||||
|
|
||||||
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
||||||
hub.tilt_sensor.subscribe(callback)
|
hub.tilt_sensor.subscribe(callback)
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e000500453a05"))
|
hub.notify_mock.append((HANDLE, "1b0e000500453a05"))
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e000a00473a010100000001"))
|
hub.notify_mock.append((HANDLE, "1b0e000a00473a010100000001"))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
||||||
hub.tilt_sensor.subscribe(callback, TILT_MODE_2AXIS_SIMPLE)
|
hub.tilt_sensor.subscribe(callback, TiltSensor.MODE_2AXIS_SIMPLE)
|
||||||
|
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e000500453a09"))
|
hub.notify_mock.append((HANDLE, "1b0e000500453a09"))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
self._inject_notification(hub, '1b0e000a00 47 3a 090100000001', 1)
|
||||||
hub.tilt_sensor.subscribe(callback, TILT_MODE_2AXIS_FULL)
|
hub.tilt_sensor.subscribe(callback, TiltSensor.MODE_2AXIS_FULL)
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e000600453a04fe"))
|
hub.notify_mock.append((HANDLE, "1b0e000600453a04fe"))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
self._wait_notifications_handled(hub)
|
self._wait_notifications_handled(hub)
|
||||||
@ -133,7 +142,7 @@ class GeneralTest(unittest.TestCase):
|
|||||||
def test_color_sensor(self):
|
def test_color_sensor(self):
|
||||||
#
|
#
|
||||||
hub = HubMock()
|
hub = HubMock()
|
||||||
hub.connection.notifications.append((HANDLE, '1b0e000f00 04010125000000001000000010'))
|
hub.notify_mock.append((HANDLE, '1b0e000f00 04010125000000001000000010'))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def callback(color, unk1, unk2=None):
|
def callback(color, unk1, unk2=None):
|
||||||
@ -143,7 +152,7 @@ class GeneralTest(unittest.TestCase):
|
|||||||
self._inject_notification(hub, '1b0e000a00 4701090100000001', 1)
|
self._inject_notification(hub, '1b0e000a00 4701090100000001', 1)
|
||||||
hub.color_distance_sensor.subscribe(callback)
|
hub.color_distance_sensor.subscribe(callback)
|
||||||
|
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e0008004501ff0aff00"))
|
hub.notify_mock.append((HANDLE, "1b0e0008004501ff0aff00"))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# TODO: assert
|
# TODO: assert
|
||||||
self._wait_notifications_handled(hub)
|
self._wait_notifications_handled(hub)
|
||||||
@ -158,8 +167,8 @@ class GeneralTest(unittest.TestCase):
|
|||||||
|
|
||||||
hub.button.subscribe(callback)
|
hub.button.subscribe(callback)
|
||||||
|
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e00060001020601"))
|
hub.notify_mock.append((HANDLE, "1b0e00060001020601"))
|
||||||
hub.connection.notifications.append((HANDLE, "1b0e00060001020600"))
|
hub.notify_mock.append((HANDLE, "1b0e00060001020600"))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# TODO: assert
|
# TODO: assert
|
||||||
self._wait_notifications_handled(hub)
|
self._wait_notifications_handled(hub)
|
||||||
@ -168,6 +177,6 @@ class GeneralTest(unittest.TestCase):
|
|||||||
def _inject_notification(self, hub, notify, pause):
|
def _inject_notification(self, hub, notify, pause):
|
||||||
def inject():
|
def inject():
|
||||||
time.sleep(pause)
|
time.sleep(pause)
|
||||||
hub.connection.notifications.append((HANDLE, notify))
|
hub.notify_mock.append((HANDLE, notify))
|
||||||
|
|
||||||
Thread(target=inject).start()
|
Thread(target=inject).start()
|
||||||
|
@ -3,6 +3,11 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from pylgbst import *
|
||||||
|
from pylgbst.comms import DebugServerConnection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import gtts
|
import gtts
|
||||||
@ -27,8 +32,6 @@ except BaseException:
|
|||||||
def say(text):
|
def say(text):
|
||||||
sys.stdout.write("%s\n", text)
|
sys.stdout.write("%s\n", text)
|
||||||
|
|
||||||
from pylgbst import *
|
|
||||||
|
|
||||||
forward = FORWARD = right = RIGHT = 1
|
forward = FORWARD = right = RIGHT = 1
|
||||||
backward = BACKWARD = left = LEFT = -1
|
backward = BACKWARD = left = LEFT = -1
|
||||||
straight = STRAIGHT = 0
|
straight = STRAIGHT = 0
|
||||||
|
@ -22,8 +22,8 @@ def on_btn(pressed):
|
|||||||
running = False
|
running = False
|
||||||
|
|
||||||
|
|
||||||
def decode_xml(messageString):
|
def decode_xml(msg):
|
||||||
parts = messageString.split("</")
|
parts = msg.split("</")
|
||||||
xxx = float(parts[1][parts[1].rfind('>') + 1:])
|
xxx = float(parts[1][parts[1].rfind('>') + 1:])
|
||||||
yyy = float(parts[2][parts[2].rfind('>') + 1:])
|
yyy = float(parts[2][parts[2].rfind('>') + 1:])
|
||||||
zzz = float(parts[3][parts[3].rfind('>') + 1:])
|
zzz = float(parts[3][parts[3].rfind('>') + 1:])
|
||||||
@ -58,7 +58,7 @@ try:
|
|||||||
message = data
|
message = data
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except BaseException:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not message:
|
if not message:
|
||||||
|
@ -9,9 +9,9 @@ criterion = min
|
|||||||
cur_luminosity = 0
|
cur_luminosity = 0
|
||||||
|
|
||||||
|
|
||||||
def on_change_lum(lum):
|
def on_change_lum(lumn):
|
||||||
global cur_luminosity
|
global cur_luminosity
|
||||||
cur_luminosity = lum
|
cur_luminosity = lumn
|
||||||
|
|
||||||
|
|
||||||
lum_values = {}
|
lum_values = {}
|
||||||
@ -23,12 +23,12 @@ def on_btn(pressed):
|
|||||||
running = False
|
running = False
|
||||||
|
|
||||||
|
|
||||||
def on_turn(angle):
|
def on_turn(angl):
|
||||||
lum_values[angle] = cur_luminosity
|
lum_values[angl] = cur_luminosity
|
||||||
|
|
||||||
|
|
||||||
robot.button.subscribe(on_btn)
|
robot.button.subscribe(on_btn)
|
||||||
robot.color_distance_sensor.subscribe(on_change_lum, CDS_MODE_LUMINOSITY, granularity=1)
|
robot.color_distance_sensor.subscribe(on_change_lum, ColorDistanceSensor.LUMINOSITY, granularity=1)
|
||||||
robot.motor_A.subscribe(on_turn, granularity=30)
|
robot.motor_A.subscribe(on_turn, granularity=30)
|
||||||
# TODO: add bump detect to go back?
|
# TODO: add bump detect to go back?
|
||||||
while running:
|
while running:
|
||||||
|
@ -5,7 +5,7 @@ robot = Vernie()
|
|||||||
robot.say('type commands')
|
robot.say('type commands')
|
||||||
|
|
||||||
|
|
||||||
def confirmation(cmd):
|
def confirmation(_):
|
||||||
robot.say("ok")
|
robot.say("ok")
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ robot = Vernie()
|
|||||||
robot.say("commands from file")
|
robot.say("commands from file")
|
||||||
|
|
||||||
|
|
||||||
def confirmation(cmd):
|
def confirmation(command):
|
||||||
robot.say(cmd[0])
|
robot.say(command[0])
|
||||||
|
|
||||||
|
|
||||||
with open("vernie.commands") as fhd:
|
with open("vernie.commands") as fhd:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user