Skip to content

Commit 58eb3b7

Browse files
committed
Add project files
1 parent 30a5b57 commit 58eb3b7

4 files changed

Lines changed: 397 additions & 0 deletions

File tree

holman/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .holman import TapTimerManager, TapTimerManagerListener, TapTimer, TapTimerListener

holman/holman.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
"""
2+
Module for managing Holman Bluetooth tap timers.
3+
"""
4+
import gatt
5+
6+
7+
class TapTimerManager(gatt.DeviceManager):
8+
"""
9+
Entry point for managing and discovering Holman ``TapTimer``s.
10+
"""
11+
12+
def __init__(self, adapter_name='hci0'):
13+
"""
14+
Instantiates a ``TapTimerManager``
15+
16+
:param adapter_name: name of Bluetooth adapter used by this
17+
tap timer manager
18+
"""
19+
super().__init__(adapter_name)
20+
self.listener = None
21+
self.discovered_tap_timers = {}
22+
23+
def tap_timers(self):
24+
"""
25+
Returns all known Holman tap timers.
26+
"""
27+
return self.devices()
28+
29+
def start_discovery(self, service_uuids=None):
30+
"""
31+
Starts a Bluetooth discovery for Holman tap timers.
32+
33+
Assign a `TapTimerManagerListener` to the `listener` attribute
34+
to collect discovered Holmans.
35+
"""
36+
super().start_discovery(service_uuids=TapTimer.SERVICE_UUIDS)
37+
38+
def make_device(self, mac_address):
39+
device = gatt.Device(
40+
mac_address=mac_address, manager=self, managed=False)
41+
if device.alias() != 'Tap Timer':
42+
return None
43+
return TapTimer(mac_address=mac_address, manager=self)
44+
45+
def device_discovered(self, device):
46+
super().device_discovered(device)
47+
if device.mac_address in self.discovered_tap_timers:
48+
return
49+
self.discovered_tap_timers[device.mac_address] = device
50+
if self.listener is not None:
51+
self.listener.tap_timer_discovered(device)
52+
53+
54+
class TapTimerManagerListener(object):
55+
"""
56+
Base class for receiving discovery events from ``TapTimerManager``.
57+
58+
Assign an instance of your subclass to the ``listener`` attribute
59+
of your ``TapTimerManager`` to receive discovery events.
60+
"""
61+
62+
def tap_timer_discovered(self, tap_timer):
63+
"""
64+
This method gets called once for each Holman tap timer
65+
discovered nearby.
66+
67+
:param tap_timer: the Holman tap timer that was discovered
68+
"""
69+
pass
70+
71+
72+
class TapTimer(gatt.Device):
73+
"""
74+
This class represents a Holman tap timer.
75+
76+
Obtain instances of this class by using the discovery mechanism of
77+
``TapTimerManager`` or by manually creating an instance.
78+
79+
Assign an instance of ``TapTimerListener`` to the ``listener``
80+
attribute to receive all Holman related events such as connection,
81+
disconnection and user input.
82+
83+
:param adapter_name: name of Bluetooth adapter that can connect to
84+
this tap timer
85+
:param mac_address: MAC address of this Holman tap timer
86+
:param listener: instance of ``TapTimerListener`` that will be
87+
notified with all events
88+
"""
89+
90+
HOLMAN_SERVICE_UUID = '0a75f000-f9ad-467a-e564-3c19163ad543'
91+
STATE_CHARACTERISTIC_UUID = '0000f004-0000-1000-8000-00805f9b34fb'
92+
MANUAL_CHARACTERISTIC_UUID = '0000f006-0000-1000-8000-00805f9b34fb'
93+
94+
SERVICE_UUIDS = [
95+
HOLMAN_SERVICE_UUID]
96+
97+
def __init__(self, mac_address, manager):
98+
"""
99+
Create an instance with given Bluetooth adapter name and MAC
100+
address.
101+
102+
:param mac_address: MAC address of Holman tap timer with
103+
format: ``AA:BB:CC:DD:EE:FF``
104+
:param manager: reference to the `TapTimerManager` that manages
105+
this tap timer
106+
"""
107+
super().__init__(mac_address=mac_address, manager=manager)
108+
109+
self.listener = None
110+
self._battery_level = None
111+
self._manual_characteristic = None
112+
self._state_characteristic = None
113+
self._state = bytes([0])
114+
115+
def connect(self):
116+
"""
117+
Tries to connect to this Holman tap timer and blocks until it
118+
has connected or failed to connect.
119+
120+
Notifies ``listener`` as soon has the connection has succeeded
121+
or failed.
122+
"""
123+
if self.listener:
124+
self.listener.started_connecting()
125+
super().connect()
126+
127+
def connect_failed(self, error):
128+
super().connect_failed(error)
129+
if self.listener:
130+
self.listener.connect_failed(error)
131+
132+
def disconnect(self):
133+
"""
134+
Disconnects this Holman tap timer if connected.
135+
136+
Notifies ``listener`` as soon as Holman was disconnected.
137+
"""
138+
if self.listener:
139+
self.listener.started_disconnecting()
140+
self._refresh_state()
141+
super().disconnect()
142+
143+
def disconnect_succeeded(self):
144+
super().disconnect_succeeded()
145+
if self.listener:
146+
self.listener.disconnect_succeeded()
147+
148+
def services_resolved(self):
149+
super().services_resolved()
150+
151+
holman_service = next((
152+
service for service in self.services
153+
if service.uuid == self.HOLMAN_SERVICE_UUID), None)
154+
if holman_service is None:
155+
if self.listener:
156+
# TODO: Use proper exception subclass
157+
self.listener.connect_failed(
158+
Exception("Holman GATT service missing"))
159+
return
160+
161+
self._manual_characteristic = next((
162+
char for char in holman_service.characteristics
163+
if char.uuid == self.MANUAL_CHARACTERISTIC_UUID), None)
164+
if self._manual_characteristic is None:
165+
# TODO: Use proper exception subclass
166+
self.listener.connect_failed(
167+
Exception(
168+
"Holman GATT characteristic %s missing",
169+
self.MANUAL_CHARACTERISTIC_UUID))
170+
return
171+
172+
self._state_characteristic = next((
173+
char for char in holman_service.characteristics
174+
if char.uuid == self.STATE_CHARACTERISTIC_UUID), None)
175+
if self._state_characteristic is None:
176+
# TODO: Use proper exception subclass
177+
self.listener.connect_failed(
178+
Exception(
179+
"Holman GATT characteristic %s missing",
180+
self.STATE_CHARACTERISTIC_UUID))
181+
return
182+
self._refresh_state()
183+
184+
# TODO: Only fire connected event when we read the firmware
185+
# version or battery value as in other SDKs
186+
if self.listener:
187+
self.listener.connect_succeeded()
188+
189+
def battery_level(self):
190+
# TODO: determine which byte in state is used for battery level
191+
return self._battery_level
192+
193+
def _refresh_state(self):
194+
self._state = self._state_characteristic.read_value()
195+
196+
@property
197+
def is_on(self):
198+
"""Return true if tap is running."""
199+
self._refresh_state()
200+
return self._state[-1] == 1
201+
202+
@property
203+
def name(self):
204+
"""The name of the tap timer."""
205+
return str(self.alias())
206+
207+
def start(self, runtime=1):
208+
"""
209+
Turn on the tap for ```runtime``` minutes.
210+
211+
:param runtime: the number of minutes to run the tap
212+
"""
213+
runtime = 255 if runtime > 255 else runtime
214+
if self._manual_characteristic:
215+
value = bytes([0x01, 0x00, 0x00, runtime])
216+
self._manual_characteristic.write_value(value)
217+
218+
def stop(self):
219+
"""Turn off the tap."""
220+
if self._manual_characteristic:
221+
value = bytes([0x00, 0x00, 0x00, 0x00])
222+
self._manual_characteristic.write_value(value)
223+
224+
def characteristic_write_value_succeeded(self, characteristic):
225+
self._refresh_state()
226+
227+
def characteristic_write_value_failed(self, characteristic, error):
228+
self._refresh_state()
229+
230+
231+
class TapTimerListener:
232+
"""
233+
Base class of listeners for a ``HolmanTapTimer`` with empty handler
234+
implementations.
235+
"""
236+
def received_gesture_event(self, event):
237+
pass
238+
239+
def started_connecting(self):
240+
pass
241+
242+
def connect_succeeded(self):
243+
pass
244+
245+
def connect_failed(self, error):
246+
pass
247+
248+
def started_disconnecting(self):
249+
pass
250+
251+
def disconnect_succeeded(self):
252+
pass

holmanctl.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from argparse import ArgumentParser
5+
import holman
6+
7+
tap_timer_manager = None
8+
9+
10+
class TapTimerPrintListener(holman.TapTimerListener):
11+
"""
12+
An implementation of ``TapTimerListener`` that prints each event.
13+
"""
14+
def __init__(self, tap_timer):
15+
self.tap_timer = tap_timer
16+
17+
def started_connecting(self):
18+
self.print("connecting...")
19+
20+
def connect_succeeded(self):
21+
self.print("connected")
22+
23+
def connect_failed(self, error):
24+
self.print("connect failed: " + str(error))
25+
26+
def started_disconnecting(self):
27+
self.print("disconnecting...")
28+
29+
def disconnect_succeeded(self):
30+
self.print("disconnected")
31+
32+
def print(self, string):
33+
print("Holman tap timer " + self.tap_timer.mac_address + " " + string)
34+
35+
36+
class TapTimerTestListener(TapTimerPrintListener):
37+
def __init__(self, tap_timer, auto_reconnect=False):
38+
super().__init__(tap_timer)
39+
self.auto_reconnect = auto_reconnect
40+
41+
def connect_failed(self, error):
42+
super().connect_failed(error)
43+
tap_timer_manager.stop()
44+
sys.exit(0)
45+
46+
def disconnect_succeeded(self):
47+
super().disconnect_succeeded()
48+
49+
if self.auto_reconnect:
50+
# Reconnect as soon as Holman was disconnected
51+
print("Disconnected, reconnecting...")
52+
self.tap_timer.connect()
53+
else:
54+
tap_timer_manager.stop()
55+
sys.exit(0)
56+
57+
58+
class TapTimerManagerPrintListener(holman.TapTimerManagerListener):
59+
def tap_timer_discovered(self, tap_timer):
60+
print("Discovered Holman tap timer", tap_timer.mac_address)
61+
62+
63+
def main():
64+
arg_parser = ArgumentParser(description="Holman Tap Timer Demo")
65+
arg_parser.add_argument(
66+
'--adapter',
67+
default='hci0',
68+
help="Name of Bluetooth adapter, defaults to 'hci0'")
69+
arg_commands_group = arg_parser.add_mutually_exclusive_group(required=True)
70+
arg_commands_group.add_argument(
71+
'--discover',
72+
action='store_true',
73+
help="Lists all nearby Holman tap timers")
74+
arg_commands_group.add_argument(
75+
'--known',
76+
action='store_true',
77+
help="Lists all known Holman tap timers")
78+
arg_commands_group.add_argument(
79+
'--connect',
80+
metavar='address',
81+
type=str,
82+
help="Connect to a Holman tap timer with a given MAC address")
83+
arg_commands_group.add_argument(
84+
'--auto',
85+
metavar='address',
86+
type=str,
87+
help="Connect and automatically reconnect to a Holman tap timer with a given MAC address")
88+
arg_commands_group.add_argument(
89+
'--disconnect',
90+
metavar='address',
91+
type=str,
92+
help="Disconnect a Holman tap timer with a given MAC address")
93+
args = arg_parser.parse_args()
94+
95+
global tap_timer_manager
96+
tap_timer_manager = holman.TapTimerManager(adapter_name=args.adapter)
97+
98+
if args.discover:
99+
tap_timer_manager.listener = TapTimerManagerPrintListener()
100+
tap_timer_manager.start_discovery()
101+
if args.known:
102+
for tap_timer in tap_timer_manager.tap_timers():
103+
print("[%s] %s" % (tap_timer.mac_address, tap_timer.alias()))
104+
return
105+
elif args.connect:
106+
tap_timer = holman.TapTimer(mac_address=args.connect, manager=tap_timer_manager)
107+
tap_timer.listener = TapTimerTestListener(tap_timer=tap_timer)
108+
tap_timer.connect()
109+
elif args.auto:
110+
tap_timer = holman.TapTimer(mac_address=args.auto, manager=tap_timer_manager)
111+
tap_timer.listener = TapTimerTestListener(tap_timer=tap_timer, auto_reconnect=True)
112+
tap_timer.connect()
113+
elif args.disconnect:
114+
tap_timer = holman.TapTimer(mac_address=args.disconnect, manager=tap_timer_manager)
115+
tap_timer.disconnect()
116+
return
117+
118+
print("Terminate with Ctrl+C")
119+
try:
120+
tap_timer_manager.run()
121+
except KeyboardInterrupt:
122+
pass
123+
124+
if __name__ == '__main__':
125+
main()

0 commit comments

Comments
 (0)