Skip to content

Commit 8180d88

Browse files
committed
Initial commit
0 parents  commit 8180d88

File tree

7 files changed

+353
-0
lines changed

7 files changed

+353
-0
lines changed

.gitignore

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
.hypothesis/
48+
49+
# Translations
50+
*.mo
51+
*.pot
52+
53+
# Django stuff:
54+
*.log
55+
local_settings.py
56+
57+
# Flask stuff:
58+
instance/
59+
.webassets-cache
60+
61+
# Scrapy stuff:
62+
.scrapy
63+
64+
# Sphinx documentation
65+
docs/_build/
66+
67+
# PyBuilder
68+
target/
69+
70+
# Jupyter Notebook
71+
.ipynb_checkpoints
72+
73+
# pyenv
74+
.python-version
75+
76+
# celery beat schedule file
77+
celerybeat-schedule
78+
79+
# SageMath parsed files
80+
*.sage.py
81+
82+
# dotenv
83+
.env
84+
85+
# virtualenv
86+
.venv
87+
venv/
88+
ENV/
89+
90+
# Spyder project settings
91+
.spyderproject
92+
.spyproject
93+
94+
# Rope project settings
95+
.ropeproject
96+
97+
# mkdocs documentation
98+
/site
99+
100+
# mypy
101+
.mypy_cache/
102+
.idea

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Peter
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

discover.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
#
4+
5+
from bluepy import btle
6+
7+
class ScanDelegate(btle.DefaultDelegate):
8+
def __init__(self):
9+
btle.DefaultDelegate.__init__(self)
10+
11+
def handleDiscovery(self, dev, isNewDev, isNewData):
12+
if not dev.addr == 'f1:05:a5:9c:2e:9b':
13+
return
14+
if isNewDev:
15+
print "Discovered device", dev.addr
16+
print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
17+
print dev.getScanData()
18+
for (adtype, desc, value) in dev.getScanData():
19+
print " %s = %s" % (desc, value)
20+
elif isNewData:
21+
print "Received new data from", dev.addr
22+
print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
23+
for (adtype, desc, value) in dev.getScanData():
24+
print " %s = %s" % (desc, value)
25+
# else:
26+
# print "Discovered device", dev.addr
27+
28+
29+
30+
scanner = btle.Scanner(0).withDelegate(ScanDelegate())
31+
scanner.start()
32+
33+
while True:
34+
scanner.process(30)
35+
36+
scanner.stop()
37+
38+
# for dev in devices:
39+
# print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
40+
# for (adtype, desc, value) in dev.getScanData():
41+
# print " %s = %s" % (desc, value)

read_characteristics.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
#
4+
5+
from bluepy import btle
6+
from tabulate import tabulate
7+
8+
9+
# connect (enter your own devices MAC address of course)
10+
zei = btle.Peripheral('f1:05:a5:9c:2e:9b', 'random', iface=1)
11+
12+
13+
try:
14+
data=[]
15+
for i, c in enumerate(zei.getCharacteristics()):
16+
data.append((i, c.uuid.getCommonName(), c.propertiesToString(), c.getHandle()))
17+
18+
print tabulate(tabular_data=data,
19+
headers=["", "Characteristics", "Properties", "Handle"])
20+
21+
finally:
22+
zei.disconnect()
23+
24+

readme.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Some experimental code to communicate with Zei° time tracking device
2+
3+
## General
4+
5+
Basically Zei° is a Bluetooth LE device.
6+
7+
It seams to support the following characteristics (check read_characteristics.py)
8+
9+
| Idx | Characteristics | Properties | Handle | Notes
10+
| --- | ------------------------------------------ | ------------- | -------- | ------
11+
| | Standard profiles | | |
12+
| 0 | Device Name | READ WRITE | 3 |
13+
| 1 | Appearance | READ | 5 |
14+
| 2 | Peripheral Preferred Connection Parameters | READ | 7 |
15+
| 3 | Manufacturer Name String | READ | 11 |
16+
| 4 | Model Number String | READ | 13 |
17+
| 5 | Serial Number String | READ | 15 |
18+
| 6 | Hardware Revision String | READ | 17 |
19+
| 7 | Firmware Revision String | READ | 19 |
20+
| 8 | Software Revision String | READ | 21 |
21+
| 9 | System ID | READ | 23 |
22+
| 10 | PnP ID | READ | 25 |
23+
| 11 | Tx Power Level | READ | 28 |
24+
| 12 | Battery Level | NOTIFY READ | 31 |
25+
| | | | |
26+
| | Zei° specific | | |
27+
| 13 | c7e70001-c847-11e6-8175-8c89a55d403c | NOTIFY WRITE | 35 | unknown notification
28+
| 14 | c7e70012-c847-11e6-8175-8c89a55d403c | READ INDICATE | 39 | this is the currently active side
29+
| 15 | c7e70011-c847-11e6-8175-8c89a55d403c | READ | 42 | new value on every change (timestamp??)
30+
| 16 | c7e70021-c847-11e6-8175-8c89a55d403c | INDICATE | 45 | indicates switching off the device
31+
| 17 | c7e70022-c847-11e6-8175-8c89a55d403c | WRITE | 48 | unknown (probably for firmware update?)
32+
| 18 | c7e70041-c847-11e6-8175-8c89a55d403c | WRITE | 51 |
33+
| 19 | c7e70042-c847-11e6-8175-8c89a55d403c | WRITE | 53 |
34+
35+
36+
## Current state
37+
38+
+ `test.py` has a small example that basically already reads everything interesting from the device.
39+
+ `read_characteristics.py` dumps all the BTLE characteristics
40+
+ `discover.py` has a very basic discovery example (note: you need to have root permissions for scanning)
41+
+ `scan.py` has a very basic scanning example
42+
43+
+ Basic communication with the device is straight forward and seams to work, however there are still open questions.
44+
+ If you press the OFF switch the device sends notification about shutdown to HNDL45.
45+
- The software should stop tracking in this case.
46+
- Is Zei° still bonded on the next connection attempt?
47+
+ If the device is placed on the base (Side 0 or 9) it's going to sleep and the connections is lost.
48+
- No notification on HNDL45 -> do not stop tracking.
49+
- How to resume if it wakes up again? I think we need to catch the connection lost and start scanning for advertisements until it wakes up again.
50+
- Zei° seams to wake up again if placed on one of the other sides again
51+
- I think the same also happens after a while without moving Zei°
52+
+ What data comes from HNDL42? How is the format? How to decode?
53+
+ What are all the other undocumented characteristics for?
54+
55+
56+
Example traffic
57+
```bash
58+
>$ python test.py
59+
60+
{'sec': ['low'], 'state': ['conn'], 'rsp': ['stat'], 'dst': ['f1:05:a5:9c:2e:9b'], 'mtu': [0]}
61+
Current side up is 8, '\xe0\xff\xff\xff\xc0\xfc\xff\xff\x80\x02\x00\x00'
62+
Battery level: '\x1c'
63+
Current side up is 5, '\xb0\xfc\xff\xff\x10\x00\x00\x00p\x02\x00\x00'
64+
Current side up is 0, '`\xfe\xff\xff@\x00\x00\x00\xb0\x03\x00\x00'
65+
Notification from hndl: 45 - '\x01' <--(after pressing switch off)
66+
```
67+

scan.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
#
4+
5+
from bluepy import btle
6+
7+
sc = btle.Scanner(1)
8+
for s in sc.scan():
9+
print "%s %s %s %s" % (s.getValueText(9), s.iface, s.addr, s.addrType)

test.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# /usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Characteristics Properties Handle
5+
# -- ------------------------------------------ ------------- --------
6+
# 0 Device Name READ WRITE 3
7+
# 1 Appearance READ 5
8+
# 2 Peripheral Preferred Connection Parameters READ 7
9+
# 3 Manufacturer Name String READ 11
10+
# 4 Model Number String READ 13
11+
# 5 Serial Number String READ 15
12+
# 6 Hardware Revision String READ 17
13+
# 7 Firmware Revision String READ 19
14+
# 8 Software Revision String READ 21
15+
# 9 System ID READ 23
16+
# 10 PnP ID READ 25
17+
# 11 Tx Power Level READ 28
18+
# 12 Battery Level NOTIFY READ 31
19+
#
20+
# 13 c7e70001-c847-11e6-8175-8c89a55d403c NOTIFY WRITE 35 # unknown notification
21+
# 14 c7e70012-c847-11e6-8175-8c89a55d403c READ INDICATE 39 # this is the currently active side
22+
# 15 c7e70011-c847-11e6-8175-8c89a55d403c READ 42 # new value on every change (timestamp??)
23+
# 16 c7e70021-c847-11e6-8175-8c89a55d403c INDICATE 45 # indicates switching off the device
24+
# 17 c7e70022-c847-11e6-8175-8c89a55d403c WRITE 48
25+
# 18 c7e70041-c847-11e6-8175-8c89a55d403c WRITE 51
26+
# 19 c7e70042-c847-11e6-8175-8c89a55d403c WRITE 53
27+
28+
# device goes into sleep after a short period of time if placed with side 0 or 9 up
29+
# device disconnects without indication on HNDL 45
30+
# wakes up by itself if placed on another side after this
31+
# advertices wake up in scanner
32+
33+
# sometimes it disconnects after a while in general
34+
35+
36+
# algorithm should be:
37+
# scan for device
38+
# connect device if found
39+
# register notification
40+
# wait for notification
41+
# on BTLEException.DISCONNECTED
42+
# start discovery
43+
# on discovery handle
44+
# reconnect
45+
# on shutdown of device signal
46+
# reset internal states to beginning (reset scanner, clear internal data)
47+
48+
49+
from bluepy import btle
50+
51+
52+
class MyDelegate(btle.DefaultDelegate):
53+
def __init__(self):
54+
btle.DefaultDelegate.__init__(self)
55+
# ... initialise here
56+
57+
def handleNotification(self, cHandle, data):
58+
# ... perhaps check cHandle
59+
# ... process 'data'
60+
if cHandle == 39:
61+
print "Current side up is %i, %r" % (int(data.encode('hex'), 16), zei.readCharacteristic(42))
62+
elif cHandle == 31:
63+
print "Battery level: %i" % int(data.encode('hex'), 16)
64+
else:
65+
print "Notification from hndl: %s - %r" % (cHandle, data)
66+
67+
68+
# connect
69+
zei = btle.Peripheral('f1:05:a5:9c:2e:9b', 'random', iface=0)
70+
zei._mgmtCmd("pair")
71+
72+
try:
73+
print zei.status()
74+
75+
# wait for notifications
76+
zei.withDelegate(MyDelegate())
77+
78+
zei.writeCharacteristic(32, b'\x01\x00') # activate notification about battery level (HNDL31)
79+
zei.writeCharacteristic(40, b'\x02\x00') # activate indication of turn (HNDL39)
80+
81+
zei.writeCharacteristic(36, b'\x01\x00') # unknown notification
82+
zei.writeCharacteristic(46, b'\x02\x00') # indicate switch off/shutdown
83+
84+
while True:
85+
if zei.waitForNotifications(1.0):
86+
continue
87+
88+
finally:
89+
zei.disconnect()

0 commit comments

Comments
 (0)