-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathprotocol.py
133 lines (105 loc) · 4.38 KB
/
protocol.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
import struct
import enum
import objc
import CoreBluetooth
from collections import namedtuple
"""
Define structured datatypes for some MT protocol messages.
"""
class GLMSettings(namedtuple('GLMSettings', 'spiritLevelEnabled, '
'dispRotationEnabled, speakerEnabled, '
'laserPointerEnabled, backlightMode, '
'angleUnit, measurementUnit')):
@staticmethod
def fromBytes(b):
return GLMSettings(*struct.unpack('????BBBxxxx', b))
def toBytes(self):
return struct.pack('????BBBxxxx', *tuple(self))
class GLMDeviceInfo(namedtuple('GLMDeviceInfo', 'serialNumber, swRevision, '
'swVersionMain, swVersionSub, swVersionBug, '
'hwPCBVersion, hwPCBVariant, hwPCBBug, '
'unknown')):
@staticmethod
def fromBytes(b):
return GLMDeviceInfo(*struct.unpack('xxxxihBBBBBB12sx', b))
class GLMSyncContainer(namedtuple('GLMSyncContainer', 'measurementType, '
'calcIndicator, distReference, '
'angleReference, distanceUnit, '
'stateOfCharge, temperature, distance, '
'result, angle, timestamp, laserOn, '
'usabilityErrors, measurementListIndex, '
'compassHeading, ndofSensorStatus')):
@staticmethod
def fromBytes(b):
return GLMSyncContainer(
measurementType=b[0] & 0x1f,
calcIndicator=b[0] >> 5,
distReference=b[1] & 7,
angleReference=(b[1] >> 3) & 7,
distanceUnit=(b[1] >> 6) & 1,
stateOfCharge=b[2],
temperature=b[3],
distance=struct.unpack_from('fff', b, 4),
result=struct.unpack_from('f', b, 16)[0],
angle=struct.unpack_from('f', b, 20)[0],
timestamp=struct.unpack_from('i', b, 24)[0],
laserOn=b[28] & 1,
usabilityErrors=b[28] >> 1,
measurementListIndex=b[29],
compassHeading=struct.unpack_from('h', b, 30)[0],
ndofSensorStatus=b[32],
)
class GLMPayloadSize(namedtuple('GLMPayloadSize', 'RXPayloadSize, '
'TXPayloadSize')):
@staticmethod
def fromBytes(b):
return GLMPayloadSize(*struct.unpack('xxxxHH', b))
class GLMProtocolVersion(namedtuple('GLMProtocolVersion', 'Main, Sub, Bug, '
'ProjMain, ProjSub, ProjBug')):
@staticmethod
def fromBytes(b):
return GLMProtocolVersion(*b)
class GLMRealTimeClock(namedtuple('GLMRealTimeClock', 'clockSeconds')):
@staticmethod
def fromBytes(b):
return GLMRealTimeClock(*struct.unpack('I', b))
class GLMUploadResult(namedtuple('GLMUploadResult', 'uploadErrorCode, '
'blockNumber')):
@staticmethod
def fromBytes(b):
return GLMUploadResult(uploadErrorCode=b[0] & 0xf,
blockNumber=b[0] >> 4)
GLM_SERVICE_UUID = CoreBluetooth.CBUUID.alloc() \
.initWithString_("00005301-0000-0041-5253-534F46540000")
TX_CHARACTERISTIC_UUID = CoreBluetooth.CBUUID.alloc() \
.initWithString_("00004301-0000-0041-5253-534F46540000")
RX_CHARACTERISTIC_UUID = CoreBluetooth.CBUUID.alloc() \
.initWithString_("00004302-0000-0041-5253-534F46540000")
class DistReference(enum.IntEnum):
Front, Center, Back, Tripod = range(4)
class DistanceUnit(enum.IntEnum):
Metric, Imperial = range(2)
class CRCError(Exception):
pass
class StatusError(Exception):
def __init__(self, number):
string = [
'Success', 'CommunicationTimeout', 'ModeInvalid', 'ChecksumError',
'UnknownCommand', 'InvalidAccessLevel', 'InvalidDatabytes',
'Reserved'
][number & 7]
if number & 8:
string += ' | HardwareError'
if number & 16:
string += ' | DeviceNotReady'
if number & 32:
string += ' | HandRaised'
super().__init__(string)
def crc8(data, iv=0xaa, poly=0xa6):
value = iv
for b in data:
for i in range(8):
x, value = (value >> 7) ^ (b >> (7-i)) & 1, (value << 1) & 0xff
if x:
value ^= poly
return value