mirror of
https://github.com/undera/pylgbst.git
synced 2020-11-18 19:37:26 -08:00
Started experiments, notifications work
This commit is contained in:
commit
6be73c45af
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.pyc
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Ideas
|
||||||
|
|
||||||
|
Make a server that will listen to wire proto to speed up debug
|
||||||
|
|
||||||
|
|
||||||
|
# Links
|
||||||
|
|
||||||
|
https://github.com/JorgePe/BOOSTreveng
|
||||||
|
|
||||||
|
https://github.com/hobbyquaker/node-movehub
|
||||||
|
|
||||||
|
https://github.com/JorgePe/pyb00st
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
https://github.com/RealTimeWeb/blockpy
|
||||||
|
|
||||||
|
https://ru.wikipedia.org/wiki/App_Inventor
|
||||||
|
|
||||||
|
https://en.wikipedia.org/wiki/Blockly
|
||||||
|
|
0
pylegoboost/__init__.py
Normal file
0
pylegoboost/__init__.py
Normal file
137
pylegoboost/constants.py
Normal file
137
pylegoboost/constants.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
MOVE_HUB_HARDWARE_HANDLE = 0x0E
|
||||||
|
MOVE_HUB_HARDWARE_UUID = '00001624-1212-efde-1623-785feabcd123'
|
||||||
|
|
||||||
|
DEVICE_NAME = 0x07
|
||||||
|
ENABLE_NOTIFICATIONS_HANDLE = 0x000f
|
||||||
|
ENABLE_NOTIFICATIONS_VALUE = b'\x01\x00'
|
||||||
|
|
||||||
|
# Ports
|
||||||
|
PORT_A = 0x37
|
||||||
|
PORT_B = 0x38
|
||||||
|
PORT_C = 0x01
|
||||||
|
PORT_D = 0x02
|
||||||
|
PORT_AB = 0x39
|
||||||
|
|
||||||
|
# Commands for setting RGB LED color
|
||||||
|
SET_LED_OFF = b'\x08\x00\x81\x32\x11\x51\x00\x00'
|
||||||
|
SET_LED_PINK = b'\x08\x00\x81\x32\x11\x51\x00\x01'
|
||||||
|
SET_LED_PURPLE = b'\x08\x00\x81\x32\x11\x51\x00\x02'
|
||||||
|
SET_LED_BLUE = b'\x08\x00\x81\x32\x11\x51\x00\x03'
|
||||||
|
SET_LED_LIGHTBLUE = b'\x08\x00\x81\x32\x11\x51\x00\x04'
|
||||||
|
SET_LED_CYAN = b'\x08\x00\x81\x32\x11\x51\x00\x05'
|
||||||
|
SET_LED_GREEN = b'\x08\x00\x81\x32\x11\x51\x00\x06'
|
||||||
|
SET_LED_YELLOW = b'\x08\x00\x81\x32\x11\x51\x00\x07'
|
||||||
|
SET_LED_ORANGE = b'\x08\x00\x81\x32\x11\x51\x00\x08'
|
||||||
|
SET_LED_RED = b'\x08\x00\x81\x32\x11\x51\x00\x09'
|
||||||
|
SET_LED_WHITE = b'\x08\x00\x81\x32\x11\x51\x00\x0A'
|
||||||
|
|
||||||
|
SET_LED_COLOR = [SET_LED_OFF,
|
||||||
|
SET_LED_PINK,
|
||||||
|
SET_LED_PURPLE,
|
||||||
|
SET_LED_BLUE,
|
||||||
|
SET_LED_LIGHTBLUE,
|
||||||
|
SET_LED_CYAN,
|
||||||
|
SET_LED_GREEN,
|
||||||
|
SET_LED_YELLOW,
|
||||||
|
SET_LED_ORANGE,
|
||||||
|
SET_LED_RED,
|
||||||
|
SET_LED_WHITE]
|
||||||
|
|
||||||
|
# Colors:
|
||||||
|
LED_COLORS = ['OFF', 'PINK', 'PURPLE', 'BLUE', 'LIGHTBLUE', 'CYAN', 'GREEN', 'YELLOW', 'ORANGE', 'RED', 'WHITE']
|
||||||
|
|
||||||
|
# Motors:
|
||||||
|
|
||||||
|
MOTOR_A = bytes([0x37])
|
||||||
|
MOTOR_B = bytes([0x38])
|
||||||
|
MOTOR_AB = bytes([0x39])
|
||||||
|
MOTOR_C = bytes([0x01])
|
||||||
|
MOTOR_D = bytes([0x02])
|
||||||
|
|
||||||
|
# a group of all single motors
|
||||||
|
MOTORS = [MOTOR_A, MOTOR_B, MOTOR_AB, MOTOR_C, MOTOR_D]
|
||||||
|
|
||||||
|
# a group of 1 is silly but there might be other pairs in the future
|
||||||
|
MOTOR_PAIRS = [MOTOR_AB]
|
||||||
|
|
||||||
|
# Commands for Interactive Motors (Timed):
|
||||||
|
|
||||||
|
# Motor A, B, C, D: 12-byte commands
|
||||||
|
# Motor AB: 13-byte commands
|
||||||
|
|
||||||
|
MOTOR_TIMED_INI = b'\x0c\x01\x81'
|
||||||
|
MOTOR_TIMED_MID = b'\x11\x09'
|
||||||
|
MOTOR_TIMED_END = b'\x64\x7f\x03'
|
||||||
|
|
||||||
|
MOTORS_TIMED_INI = b'\x0d\x01\x81'
|
||||||
|
MOTORS_TIMED_MID = b'\x11\x0A'
|
||||||
|
MOTORS_TIMED_END = b'\x64\x7f\x03'
|
||||||
|
|
||||||
|
# Commands for Interactive Motors (Angle):
|
||||||
|
|
||||||
|
# Motor A, B, C, D: 14-byte commands
|
||||||
|
# Motor AB: 15-byte commands
|
||||||
|
|
||||||
|
MOTOR_ANGLE_INI = b'\x0e\x01\x81'
|
||||||
|
MOTOR_ANGLE_MID = b'\x11\x0b'
|
||||||
|
MOTOR_ANGLE_END = b'\x64\x7f\x03'
|
||||||
|
|
||||||
|
MOTORS_ANGLE_INI = b'\x0f\x01\x81'
|
||||||
|
MOTORS_ANGLE_MID = b'\x11\x0c'
|
||||||
|
MOTORS_ANGLE_END = b'\x64\x7f\x03'
|
||||||
|
|
||||||
|
# Commands for WeDo Motors (just Duty Cycle):
|
||||||
|
MOTOR_WEDO_INI = b'\x08\x00\x81'
|
||||||
|
MOTOR_WEDO_MID = b'\x11\x51\x00'
|
||||||
|
|
||||||
|
# Commands for Color Sensor
|
||||||
|
LISTEN_COLOR_SENSOR_ON_C = b'\x0a\x00\x41\x01\x08\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_COLOR_SENSOR_ON_D = b'\x0a\x00\x41\x02\x08\x01\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
# Sensor Colors:
|
||||||
|
COLOR_SENSOR_COLORS = ['BLACK', '', '', 'BLUE', '', 'GREEN', '', 'YELLOW', '', 'RED', 'WHITE']
|
||||||
|
|
||||||
|
# Commands for Distance Sensor
|
||||||
|
LISTEN_DIST_SENSOR_ON_C = b'\x0a\x00\x41\x01\x08\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_DIST_SENSOR_ON_D = b'\x0a\x00\x41\x02\x08\x01\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
# Commands for Reading Encoders
|
||||||
|
|
||||||
|
LISTEN_ENCODER_ON_A = b'\x0a\x00\x41\x37\x02\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_ENCODER_ON_B = b'\x0a\x00\x41\x38\x02\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_ENCODER_ON_C = b'\x0a\x00\x41\x01\x02\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_ENCODER_ON_D = b'\x0a\x00\x41\x02\x02\x01\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
#
|
||||||
|
ENCODER_MID = 2147483648
|
||||||
|
ENCODER_MAX = 4294967296
|
||||||
|
|
||||||
|
# Commands for Reading Button
|
||||||
|
LISTEN_BUTTON = b'\x05\x00\x01\x02\x02'
|
||||||
|
|
||||||
|
BUTTON_PRESSED = '\x01'
|
||||||
|
BUTTON_RELEASED = '\x00'
|
||||||
|
|
||||||
|
# Commands for Tilt Sensor
|
||||||
|
LISTEN_TILT_BASIC = b'\x0a\x00\x41\x3a\x02\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_TILT_FULL = b'\x0a\x00\x41\x3a\x00\x01\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
TILT_HORIZ = 0x00
|
||||||
|
TILT_UP = 0x01
|
||||||
|
TILT_DOWN = 0x02
|
||||||
|
TILT_RIGHT = 0x03
|
||||||
|
TILT_LEFT = 0x04
|
||||||
|
TILT_INVERT = 0x05
|
||||||
|
|
||||||
|
TILT_BASIC_VALUES = [TILT_HORIZ, TILT_UP, TILT_DOWN, TILT_RIGHT, TILT_LEFT, TILT_INVERT]
|
||||||
|
TILT_BASIC_TEXT = ['TILT_HORIZ', 'TILT_UP', 'TILT_DOWN', 'TILT_RIGHT', 'TILT_LEFT', 'TILT_INVERT']
|
||||||
|
|
||||||
|
# Commands for WeDo Tilt Sensor
|
||||||
|
# There ARE more modes, use just this one for now
|
||||||
|
LISTEN_WEDO_TILT_ON_C = b'\x0a\x00\x41\x01\x00\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_WEDO_TILT_ON_D = b'\x0a\x00\x41\x02\x00\x01\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
# Commands for WeDo Distance Sensor
|
||||||
|
# There MIGHT be more modes, use just this one for now
|
||||||
|
LISTEN_WEDO_DISTANCE_ON_C = b'\x0a\x00\x41\x01\x00\x01\x00\x00\x00\x01'
|
||||||
|
LISTEN_WEDO_DISTANCE_ON_D = b'\x0a\x00\x41\x02\x00\x01\x00\x00\x00\x01'
|
139
pylegoboost/transport.py
Normal file
139
pylegoboost/transport.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import traceback
|
||||||
|
from abc import abstractmethod
|
||||||
|
from gattlib import DiscoveryService, GATTRequester
|
||||||
|
|
||||||
|
from pylegoboost.constants import DEVICE_NAME
|
||||||
|
|
||||||
|
log = logging.getLogger('transport')
|
||||||
|
|
||||||
|
LEGO_MOVE_HUB = "LEGO Move Hub"
|
||||||
|
|
||||||
|
|
||||||
|
def strtohex(sval):
|
||||||
|
return " ".join("{:02x}".format(ord(c)) for c in sval)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyMethodOverriding
|
||||||
|
class Requester(GATTRequester):
|
||||||
|
def on_notification(self, handle, data):
|
||||||
|
log.debug("Notification on handle %s: %s", handle, strtohex(data))
|
||||||
|
|
||||||
|
def on_indication(self, handle, data):
|
||||||
|
log.debug("Indication on handle %s: %s", handle, strtohex(data))
|
||||||
|
|
||||||
|
|
||||||
|
class Transport(object):
|
||||||
|
@abstractmethod
|
||||||
|
def read(self, handle):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def write(self, handle, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BLETransport(Transport):
|
||||||
|
"""
|
||||||
|
:type requester: Requester
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Transport, self).__init__()
|
||||||
|
self.requester = None
|
||||||
|
|
||||||
|
def connect(self, bt_iface_name='hci0'):
|
||||||
|
service = DiscoveryService(bt_iface_name)
|
||||||
|
|
||||||
|
while not self.requester:
|
||||||
|
log.info("Discovering devices using %s...", bt_iface_name)
|
||||||
|
devices = service.discover(5)
|
||||||
|
log.debug("Devices: %s", devices)
|
||||||
|
|
||||||
|
for address, name in devices.items():
|
||||||
|
if name == LEGO_MOVE_HUB:
|
||||||
|
logging.info("Found %s at %s", name, address)
|
||||||
|
self._get_requester(address, bt_iface_name)
|
||||||
|
break
|
||||||
|
|
||||||
|
log.info("Device declares itself as: %s", self.read(DEVICE_NAME))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _get_requester(self, address, bt_iface_name):
|
||||||
|
self.requester = Requester(address, True, bt_iface_name)
|
||||||
|
|
||||||
|
def read(self, handle):
|
||||||
|
log.debug("Reading from: %s", handle)
|
||||||
|
data = self.requester.read_by_handle(handle)
|
||||||
|
log.debug("Result: %s", data)
|
||||||
|
if isinstance(data, list):
|
||||||
|
data = data[0]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def write(self, handle, data):
|
||||||
|
log.debug("Writing to %s: %s", handle, data)
|
||||||
|
return self.requester.write_by_handle(handle, data)
|
||||||
|
|
||||||
|
|
||||||
|
class DebugServer(object):
|
||||||
|
def __init__(self, ble_trans):
|
||||||
|
self.sock = socket.socket()
|
||||||
|
self.ble = ble_trans
|
||||||
|
|
||||||
|
def start(self, port=9090):
|
||||||
|
self.sock.bind(('', port))
|
||||||
|
self.sock.listen(1)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
conn, addr = self.sock.accept()
|
||||||
|
try:
|
||||||
|
self._handle_conn(conn)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.sock.close()
|
||||||
|
|
||||||
|
def _handle_conn(self, conn):
|
||||||
|
"""
|
||||||
|
:type conn: socket._socketobject
|
||||||
|
"""
|
||||||
|
buf = ""
|
||||||
|
while True:
|
||||||
|
data = conn.recv(1024)
|
||||||
|
log.debug("Recv: %s", data)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
|
||||||
|
buf += data
|
||||||
|
|
||||||
|
if "\n" in buf:
|
||||||
|
line = buf[:buf.index("\n")]
|
||||||
|
buf = buf[buf.index("\n") + 1:]
|
||||||
|
|
||||||
|
if line:
|
||||||
|
log.debug("Cmd line: %s", line)
|
||||||
|
try:
|
||||||
|
self._handle_cmd(json.loads(line))
|
||||||
|
except BaseException:
|
||||||
|
log.error("Failed to handle cmd: %s", traceback.format_exc())
|
||||||
|
|
||||||
|
# conn.send(data.upper())
|
||||||
|
|
||||||
|
def _handle_cmd(self, line):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DebugServerTransport(Transport):
|
||||||
|
def __init__(self):
|
||||||
|
self.sock = socket.socket()
|
||||||
|
self.sock.connect(('localhost', 9090))
|
||||||
|
|
||||||
|
# sock.send('hello, world!')
|
||||||
|
|
||||||
|
# data = sock.recv(1024)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.sock.close()
|
21
test.py
Normal file
21
test.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from pylegoboost.constants import *
|
||||||
|
from pylegoboost.transport import BLETransport
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralTest(unittest.TestCase):
|
||||||
|
def test_1(self):
|
||||||
|
transport = BLETransport()
|
||||||
|
transport.connect()
|
||||||
|
transport.write(ENABLE_NOTIFICATIONS_HANDLE, ENABLE_NOTIFICATIONS_VALUE)
|
||||||
|
transport.write(MOVE_HUB_HARDWARE_HANDLE, LISTEN_DIST_SENSOR_ON_C)
|
||||||
|
sleep(60)
|
||||||
|
|
||||||
|
# from pylegoboost import DebugServer
|
||||||
|
# srv = DebugServer(None)
|
||||||
|
# srv.start()
|
Loading…
x
Reference in New Issue
Block a user