-
Notifications
You must be signed in to change notification settings - Fork 74
/
igrill.py
executable file
·157 lines (121 loc) · 5.74 KB
/
igrill.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import bluepy.btle as btle
import random
from crypto import encrypt, decrypt
class UUIDS:
FIRMWARE_VERSION = btle.UUID("64ac0001-4a4b-4b58-9f37-94d3c52ffdf7")
BATTERY_LEVEL = btle.UUID(0x2a19)
APP_CHALLENGE = btle.UUID("64ac0002-4a4b-4b58-9f37-94d3c52ffdf7")
DEVICE_CHALLENGE = btle.UUID("64ac0003-4a4b-4b58-9f37-94d3c52ffdf7")
DEVICE_RESPONSE = btle.UUID("64ac0004-4a4b-4b58-9f37-94d3c52ffdf7")
CONFIG = btle.UUID("06ef0002-2e06-4b79-9e33-fce2c42805ec")
PROBE1_TEMPERATURE = btle.UUID("06ef0002-2e06-4b79-9e33-fce2c42805ec")
PROBE1_THRESHOLD = btle.UUID("06ef0003-2e06-4b79-9e33-fce2c42805ec")
PROBE2_TEMPERATURE = btle.UUID("06ef0004-2e06-4b79-9e33-fce2c42805ec")
PROBE2_THRESHOLD = btle.UUID("06ef0005-2e06-4b79-9e33-fce2c42805ec")
PROBE3_TEMPERATURE = btle.UUID("06ef0006-2e06-4b79-9e33-fce2c42805ec")
PROBE3_THRESHOLD = btle.UUID("06ef0007-2e06-4b79-9e33-fce2c42805ec")
PROBE4_TEMPERATURE = btle.UUID("06ef0008-2e06-4b79-9e33-fce2c42805ec")
PROBE4_THRESHOLD = btle.UUID("06ef0009-2e06-4b79-9e33-fce2c42805ec")
class IGrillHandler(object):
def __init__(self, device_settings):
self.device_settings = device_settings
self.devices = []
def handleDiscovery(self, scanEntry, isNewDev, isNewData):
name = scanEntry.getValueText(8)
if name != "iGrill_mini" or not isNewDev:
return
#see if we have this device already
for d in self.devices:
if d.addr == scanEntry.addr:
return
#add dev
dev = IGrillMiniPeripheral(scanEntry.addr)
self.devices.append(dev)
def persist_stats(self, persistence):
for dev in list(self.devices):
settings = self.device_settings.get(dev.deviceAddr)
print "dev: ", dev, ", settings: ", settings
if settings == None:
settings = {
"device": "iGrill " + dev.deviceAddr,
"type": "kitchen",
"addr": dev.addr,
}
#read data from device
try:
temp = dev.read_temperature()
battery = dev.read_battery()
except Exception as ex:
print "Error reading from ", dev.deviceAddr, ": ", ex, "removing device"
self.devices.remove(dev)
continue
#persist data
try:
fields = {"temperature": temp}
persistence.save("sensordata", fields, settings)
persistence.save_battery_level(battery, **settings)
except Exception as ex:
print "Error persisting stats,", ex, ", retrying next time"
class IDevicePeripheral(btle.Peripheral):
encryption_key = None
def __init__(self, address):
"""
Connects to the device given by address performing necessary authentication
"""
btle.Peripheral.__init__(self, address)
# iDevice devices require bonding. I don't think this will give us bonding
# if no bonding exists, so please use bluetoothctl to create a bond first
self.setSecurityLevel("medium")
# enumerate all characteristics so we can look up handles from uuids
self.characteristics = self.getCharacteristics()
# authenticate with iDevices custom challenge/response protocol
if not self.authenticate():
raise RuntimeError("Unable to authenticate with device")
def characteristic(self, uuid):
"""
Returns the characteristic for a given uuid.
"""
for c in self.characteristics:
if c.uuid == uuid:
return c
def authenticate(self):
"""
Performs iDevices challenge/response handshake. Returns if handshake succeeded
"""
print "Authenticating..."
# encryption key used by igrill mini
key = "".join([chr((256 + x) % 256) for x in self.encryption_key])
# send app challenge
challenge = str(bytearray([(random.randint(0, 255)) for i in range(8)] + [0] * 8))
self.characteristic(UUIDS.APP_CHALLENGE).write(challenge, True)
# read device challenge
encrypted_device_challenge = self.characteristic(UUIDS.DEVICE_CHALLENGE).read()
print "encrypted device challenge:", str(encrypted_device_challenge).encode("hex")
device_challenge = decrypt(key, encrypted_device_challenge)
print "decrypted device challenge:", str(device_challenge).encode("hex")
# verify device challenge
if device_challenge[:8] != challenge[:8]:
print "Invalid device challenge"
return False
# send device response
device_response = chr(0) * 8 + device_challenge[8:]
print "device response: ", str(device_response).encode("hex")
encrypted_device_response = encrypt(key, device_response)
self.characteristic(UUIDS.DEVICE_RESPONSE).write(encrypted_device_response, True)
print("Authenticated")
return True
class IGrillMiniPeripheral(IDevicePeripheral):
"""
Specialization of iDevice peripheral for the iGrill Mini (sets the correct encryption key
"""
# encryption key for the iGrill Mini
encryption_key = [-19, 94, 48, -114, -117, -52, -111, 19, 48, 108, -44, 104, 84, 21, 62, -35]
def __init__(self, address):
IDevicePeripheral.__init__(self, address)
# find characteristics for battery and temperature
self.battery_char = self.characteristic(UUIDS.BATTERY_LEVEL)
self.temp_char = self.characteristic(UUIDS.PROBE1_TEMPERATURE)
def read_temperature(self):
return float(ord(self.temp_char.read()[0]))
def read_battery(self):
return float(ord(self.battery_char.read()[0]))