1
0
mirror of https://github.com/undera/pylgbst.git synced 2020-11-18 19:37:26 -08:00

Notification reading channel established

This commit is contained in:
Andrey Pohilko 2017-09-13 17:43:33 +03:00
parent e22fea19c0
commit 5b81e86632
4 changed files with 115 additions and 48 deletions

View File

@ -1,21 +1,20 @@
Best way to start is to look at [demo.py](demo.py) file, or maybe run it.
# Features
- auto-detect and connect for Bluetooth device
- permanent Bluetooth connection server for faster debugging
- angled and timed movement for motors
- LED color change
# Ideas # Ideas
Make a server that will listen to wire proto to speed up debug
# Links # Links
https://github.com/JorgePe/BOOSTreveng - https://github.com/JorgePe/BOOSTreveng - source of protocol knowledge
- https://github.com/RealTimeWeb/blockpy
https://github.com/hobbyquaker/node-movehub - https://ru.wikipedia.org/wiki/App_Inventor
- https://en.wikipedia.org/wiki/Blockly
https://github.com/JorgePe/pyb00st
https://github.com/RealTimeWeb/blockpy
https://ru.wikipedia.org/wiki/App_Inventor
https://en.wikipedia.org/wiki/Blockly

27
demo.py
View File

@ -8,8 +8,7 @@ from pylegoboost.comms import DebugServerConnection, BLEConnection
log = logging.getLogger("demo") log = logging.getLogger("demo")
def demo_all(conn): def demo_all(movehub):
movehub = MoveHub(conn)
demo_led_colors(movehub) demo_led_colors(movehub)
demo_motors_timed(movehub) demo_motors_timed(movehub)
demo_motors_angled(movehub) demo_motors_angled(movehub)
@ -60,6 +59,20 @@ def demo_port_c_motor(movehub):
sleep(1) sleep(1)
def vernie_head(movehub):
portd = EncodedMotor(movehub, PORT_D)
while True:
angle = 20
portd.angled(angle, 0.2)
sleep(2)
portd.angled(angle, -0.2)
sleep(2)
portd.angled(angle, -0.2)
sleep(2)
portd.angled(angle, 0.2)
sleep(2)
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -69,4 +82,12 @@ if __name__ == '__main__':
logging.warning("Failed to use debug server: %s", traceback.format_exc()) logging.warning("Failed to use debug server: %s", traceback.format_exc())
connection = BLEConnection().connect() connection = BLEConnection().connect()
demo_all(connection) hub = MoveHub(connection)
sleep(1)
#hub.get_name()
for x in range(1, 60):
sleep(1)
# demo_led_colors(hub)
# demo_all(movehub)

View File

@ -14,6 +14,8 @@ class MoveHub(object):
""" """
def __init__(self, connection): def __init__(self, connection):
self.notified = False
self.connection = connection self.connection = connection
self.led = LED(self) self.led = LED(self)
@ -21,15 +23,30 @@ class MoveHub(object):
self.motor_B = EncodedMotor(self, PORT_B) self.motor_B = EncodedMotor(self, PORT_B)
self.motor_AB = EncodedMotor(self, PORT_AB) self.motor_AB = EncodedMotor(self, PORT_AB)
self.port_c = None
self.port_d = None
# self.button # self.button
# self.tilt_sensor # self.tilt_sensor
# transport.write(ENABLE_NOTIFICATIONS_HANDLE, ENABLE_NOTIFICATIONS_VALUE) # enables notifications reading
self.connection.set_notify_handler(self._notify)
self.connection.write(ENABLE_NOTIFICATIONS_HANDLE, ENABLE_NOTIFICATIONS_VALUE)
# while not self.notified:
# log.debug("Waiting to be notified")
# time.sleep(1)
# self.port_C = None
# self.port_D = None
# transport.write(MOVE_HUB_HARDWARE_HANDLE, b'\x0a\x00\x41\x01\x08\x01\x00\x00\x00\x01') # transport.write(MOVE_HUB_HARDWARE_HANDLE, b'\x0a\x00\x41\x01\x08\x01\x00\x00\x00\x01')
def _notify(self, handle, data):
# TODO
log.debug("Notification on %s: %s", handle, data.encode("hex"))
def get_name(self):
# note: reading this too fast makes it hang
self.connection.read(DEVICE_NAME)
class Peripheral(object): class Peripheral(object):
""" """

View File

@ -4,9 +4,11 @@ This package holds communication aspects
import json import json
import logging import logging
import socket import socket
import time
import traceback import traceback
from abc import abstractmethod from abc import abstractmethod
from gattlib import DiscoveryService, GATTRequester from gattlib import DiscoveryService, GATTRequester
from threading import Thread
from pylegoboost.constants import DEVICE_NAME, LEGO_MOVE_HUB from pylegoboost.constants import DEVICE_NAME, LEGO_MOVE_HUB
@ -25,6 +27,7 @@ class Requester(GATTRequester):
self.notification_sink = None self.notification_sink = None
def on_notification(self, handle, data): def on_notification(self, handle, data):
# log.debug("requester notified, sink: %s", self.notification_sink)
if self.notification_sink: if self.notification_sink:
self.notification_sink(handle, data) self.notification_sink(handle, data)
@ -42,7 +45,7 @@ class Connection(object):
pass pass
@abstractmethod @abstractmethod
def notify(self, handle, data): def set_notify_handler(self, handler):
pass pass
@ -51,10 +54,6 @@ class ConnectionMock(Connection):
For unit testing purposes For unit testing purposes
""" """
def notify(self, handle, data):
# TODO
pass
def write(self, handle, data): def write(self, handle, data):
log.debug("Writing to %s: %s", handle, data.encode("hex")) log.debug("Writing to %s: %s", handle, data.encode("hex"))
@ -66,13 +65,13 @@ class ConnectionMock(Connection):
class BLEConnection(Connection): class BLEConnection(Connection):
""" """
Main transport class, uses real Bluetooth LE connection. Main transport class, uses real Bluetooth LE connection.
Loops with timeout of 5 seconds to find device named "Lego MOVE Hub" Loops with timeout of 1 seconds to find device named "Lego MOVE Hub"
:type requester: Requester :type requester: Requester
""" """
def __init__(self): def __init__(self):
super(Connection, self).__init__() super(BLEConnection, self).__init__()
self.requester = None self.requester = None
def connect(self, bt_iface_name='hci0'): def connect(self, bt_iface_name='hci0'):
@ -80,7 +79,7 @@ class BLEConnection(Connection):
while not self.requester: while not self.requester:
log.info("Discovering devices using %s...", bt_iface_name) log.info("Discovering devices using %s...", bt_iface_name)
devices = service.discover(5) devices = service.discover(1)
log.debug("Devices: %s", devices) log.debug("Devices: %s", devices)
for address, name in devices.items(): for address, name in devices.items():
@ -94,7 +93,13 @@ class BLEConnection(Connection):
def _get_requester(self, address, bt_iface_name): def _get_requester(self, address, bt_iface_name):
self.requester = Requester(address, True, bt_iface_name) self.requester = Requester(address, True, bt_iface_name)
self.requester.notification_sink = self.notify
def set_notify_handler(self, handler):
if self.requester:
log.debug("Setting notification handler: %s", handler)
self.requester.notification_sink = handler
else:
raise RuntimeError("No requester available")
def read(self, handle): def read(self, handle):
log.debug("Reading from: %s", handle) log.debug("Reading from: %s", handle)
@ -108,10 +113,6 @@ class BLEConnection(Connection):
log.debug("Writing to %s: %s", handle, data.encode("hex")) log.debug("Writing to %s: %s", handle, data.encode("hex"))
return self.requester.write_by_handle(handle, data) return self.requester.write_by_handle(handle, data)
def notify(self, handle, data):
# TODO
log.debug("Notification on %s: %s", handle, data.encode("hex"))
class DebugServer(object): class DebugServer(object):
""" """
@ -133,14 +134,26 @@ class DebugServer(object):
while True: while True:
log.info("Accepting connections at %s", port) log.info("Accepting connections at %s", port)
conn, addr = self.sock.accept() conn, addr = self.sock.accept()
self.ble.requester.notification_sink = lambda x, y: self._notify(conn, x, y)
try: try:
self._handle_conn(conn) self._handle_conn(conn)
except BaseException:
log.error("Problem handling incoming connection: %s", traceback.format_exc())
finally: finally:
self.ble.requester.notification_sink = None
conn.close() conn.close()
def __del__(self): def __del__(self):
self.sock.close() self.sock.close()
def _notify(self, conn, handle, data):
payload = {"type": "notification", "handle": handle, "data": data.encode('hex')}
log.debug("Send notification: %s", payload)
try:
conn.send(json.dumps(payload) + "\n")
except BaseException:
log.error("Problem sending notification: %s", traceback.format_exc())
def _handle_conn(self, conn): def _handle_conn(self, conn):
""" """
:type conn: socket._socketobject :type conn: socket._socketobject
@ -165,13 +178,14 @@ class DebugServer(object):
except BaseException: except BaseException:
log.error("Failed to handle cmd: %s", traceback.format_exc()) log.error("Failed to handle cmd: %s", traceback.format_exc())
# conn.send(data.upper())
def _handle_cmd(self, cmd): def _handle_cmd(self, cmd):
if cmd['type'] == 'write': if cmd['type'] == 'write':
self.ble.write(cmd['handle'], cmd['data'].decode('hex')) self.ble.write(cmd['handle'], cmd['data'].decode('hex'))
elif cmd['type'] == 'read': elif cmd['type'] == 'read':
self.sock.send(self.ble.read(cmd['handle']).encode('hex') + "\n") data = self.ble.read(cmd['handle'])
payload = {"type": "response", "data": data.encode('hex')}
log.debug("Send response: %s", payload)
self.sock.send(json.dumps(payload) + "\n")
else: else:
raise ValueError("Unhandled cmd: %s", cmd) raise ValueError("Unhandled cmd: %s", cmd)
@ -181,18 +195,21 @@ class DebugServerConnection(Connection):
Connection type to be used with DebugServer, replaces BLEConnection Connection type to be used with DebugServer, replaces BLEConnection
""" """
def __init__(self): def __init__(self, port=9090):
super(DebugServerConnection, self).__init__()
self.notify_handler = None
self.buf = "" self.buf = ""
self.sock = socket.socket() self.sock = socket.socket()
self.sock.connect(('localhost', 9090)) self.sock.connect(('localhost', port))
self.incoming = []
self.reader = Thread(target=self._recv)
self.reader.setDaemon(True)
self.reader.start()
def __del__(self): def __del__(self):
self.sock.close() self.sock.close()
def notify(self, handle, data):
# TODO
pass
def write(self, handle, data): def write(self, handle, data):
payload = { payload = {
"type": "write", "type": "write",
@ -207,7 +224,13 @@ class DebugServerConnection(Connection):
"handle": handle "handle": handle
} }
self._send(payload) self._send(payload)
return self._recv()
while True:
for item in self.incoming:
if item['type'] == 'response':
self.incoming.remove(item)
return item['data'].decode('hex')
time.sleep(0.1)
def _send(self, payload): def _send(self, payload):
log.debug("Sending to debug server: %s", payload) log.debug("Sending to debug server: %s", payload)
@ -222,10 +245,17 @@ class DebugServerConnection(Connection):
self.buf += data self.buf += data
if "\n" in self.buf: while "\n" in self.buf:
line = self.buf[:self.buf.index("\n")] line = self.buf[:self.buf.index("\n")]
self.buf = self.buf[self.buf.index("\n") + 1:] self.buf = self.buf[self.buf.index("\n") + 1:]
if line: if line:
return line.decode("hex") item = json.loads(line)
break if item['type'] == 'notification' and self.notify_handler:
raise RuntimeError("No data read") self.notify_handler(item['handle'], item['data'].decode('hex'))
elif item['type'] == 'response':
self.incoming.append(item)
else:
log.warning("Dropped inbound: %s", item)
def set_notify_handler(self, handler):
self.notify_handler = handler