kallistoapi 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kallistoapi/__init__.py +1 -0
- kallistoapi/bluetooth_gatt.py +199 -0
- kallistoapi/config_pb2.py +79 -0
- kallistoapi/kallisto_battery_service.py +168 -0
- kallistoapi/kallisto_helper.py +57 -0
- kallistoapi/kallisto_manager.py +673 -0
- kallistoapi/kallisto_sensry_serive_microstrain.py +184 -0
- kallistoapi/kallisto_time.py +82 -0
- kallistoapi/mcumgr.py +204 -0
- kallistoapi/modules/__init__.py +0 -0
- kallistoapi/modules/base.py +104 -0
- kallistoapi/modules/device_info.py +43 -0
- kallistoapi/modules/list.py +57 -0
- kallistoapi/modules/sensor.py +383 -0
- kallistoapi/modules/sensor_accelerometer.py +98 -0
- kallistoapi/modules/sensor_barometer.py +71 -0
- kallistoapi/modules/sensor_bvoc.py +71 -0
- kallistoapi/modules/sensor_eco2.py +71 -0
- kallistoapi/modules/sensor_env_vector.py +68 -0
- kallistoapi/modules/sensor_fuel_gauge.py +40 -0
- kallistoapi/modules/sensor_gyrometer.py +99 -0
- kallistoapi/modules/sensor_humidity.py +71 -0
- kallistoapi/modules/sensor_iaq.py +71 -0
- kallistoapi/modules/sensor_light.py +71 -0
- kallistoapi/modules/sensor_magnetometer.py +70 -0
- kallistoapi/modules/sensor_pressure.py +71 -0
- kallistoapi/modules/sensor_pt100.py +71 -0
- kallistoapi/modules/sensor_temperature.py +71 -0
- kallistoapi/modules/sensor_tvoc.py +71 -0
- kallistoapi/modules/sensor_tx_power.py +86 -0
- kallistoapi/modules/sensor_vibration.py +117 -0
- kallistoapi-2.0.1.dist-info/METADATA +140 -0
- kallistoapi-2.0.1.dist-info/RECORD +36 -0
- kallistoapi-2.0.1.dist-info/WHEEL +5 -0
- kallistoapi-2.0.1.dist-info/licenses/LICENSE +17 -0
- kallistoapi-2.0.1.dist-info/top_level.txt +1 -0
kallistoapi/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#kallistointegrationtest/support$ protoc --python_out=. kallistoapi/config.proto
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
from bleak import BleakClient, BleakScanner
|
|
4
|
+
import asyncio
|
|
5
|
+
# from pydbus import SystemBus
|
|
6
|
+
|
|
7
|
+
class BluetoothGatt:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._client = None
|
|
10
|
+
self._mac = "none"
|
|
11
|
+
|
|
12
|
+
# bleak runs in async, we need to have a way to run the loop in parallel to pythons
|
|
13
|
+
# sync calls. so we let the loop run in a thread
|
|
14
|
+
self._bleak_loop_thread = None
|
|
15
|
+
self._bleak_client_loop = None
|
|
16
|
+
self._bleak_loop_thread_started = threading.Event()
|
|
17
|
+
self._start_bleak_loop_thread()
|
|
18
|
+
|
|
19
|
+
self.context = None
|
|
20
|
+
self.notify_dict = {}
|
|
21
|
+
self.service_handlers = {}
|
|
22
|
+
|
|
23
|
+
self.disconnect_handler = None
|
|
24
|
+
|
|
25
|
+
def connect(self, mac_address: str, disconnect_callback=None):
|
|
26
|
+
# if self.is_device_connected(mac_address):
|
|
27
|
+
# print(f"Device {mac_address} is already connected")
|
|
28
|
+
# return False
|
|
29
|
+
|
|
30
|
+
if self.is_connected():
|
|
31
|
+
if mac_address == self._mac:
|
|
32
|
+
return True
|
|
33
|
+
print(f"disconnecting from {self._mac}")
|
|
34
|
+
self.disconnect()
|
|
35
|
+
|
|
36
|
+
self._mac = mac_address
|
|
37
|
+
|
|
38
|
+
self.disconnect_handler = disconnect_callback
|
|
39
|
+
if not self._await_bleak_loop(self._connect(self._mac), 60):
|
|
40
|
+
self.disconnect_handler = None
|
|
41
|
+
print("connection timed out")
|
|
42
|
+
return False
|
|
43
|
+
self._discover_services()
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def disconnect(self):
|
|
47
|
+
return self._await_bleak_loop(self._disconnect())
|
|
48
|
+
|
|
49
|
+
# def is_device_connected(self, mac_address):
|
|
50
|
+
# dbus_path = "/org/bluez/hci0/dev_" + mac_address.replace(":", "_")
|
|
51
|
+
# bus = SystemBus()
|
|
52
|
+
# mngr = bus.get("org.bluez", "/")
|
|
53
|
+
# objects = mngr.GetManagedObjects()
|
|
54
|
+
# device = objects.get(dbus_path, {}).get("org.bluez.Device1")
|
|
55
|
+
# return device.get("Connected", False) if device else False
|
|
56
|
+
#
|
|
57
|
+
def scan_for_devices(self):
|
|
58
|
+
return self._await_bleak_loop(self._scan_for_devices())
|
|
59
|
+
|
|
60
|
+
def detect_services(self):
|
|
61
|
+
return self._await_bleak_loop(self._detect_services())
|
|
62
|
+
|
|
63
|
+
def read_gatt_characteristics(self, characteristic_uuid):
|
|
64
|
+
return self._await_bleak_loop(self._read_gatt_characteristics(characteristic_uuid))
|
|
65
|
+
|
|
66
|
+
def get_service(self, characteristic_uuid):
|
|
67
|
+
return self._client.services.get_service(characteristic_uuid)
|
|
68
|
+
|
|
69
|
+
def start_gatt_notify(self, characteristic_uuid, notify_cb):
|
|
70
|
+
return self._await_bleak_loop(self._start_gatt_notify(characteristic_uuid, notify_cb))
|
|
71
|
+
|
|
72
|
+
def stop_gatt_notify(self, characteristic_uuid):
|
|
73
|
+
return self._await_bleak_loop(self._stop_gatt_notify(characteristic_uuid))
|
|
74
|
+
|
|
75
|
+
def write_gatt_characteristics(self, characteristic_uuid, data):
|
|
76
|
+
return self._await_bleak_loop(self._write_gatt_characteristics(characteristic_uuid, data))
|
|
77
|
+
|
|
78
|
+
def get_mac_address(self):
|
|
79
|
+
return self._mac
|
|
80
|
+
|
|
81
|
+
def is_connected(self):
|
|
82
|
+
if self._client is None:
|
|
83
|
+
return False
|
|
84
|
+
return self._client.is_connected
|
|
85
|
+
|
|
86
|
+
def get_uuid_from_handle(self, handle):
|
|
87
|
+
if handle in self.service_handlers:
|
|
88
|
+
return self.service_handlers[handle]
|
|
89
|
+
else:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
###############################
|
|
93
|
+
# internal functions
|
|
94
|
+
def _discover_services(self):
|
|
95
|
+
for service in self._client.services:
|
|
96
|
+
for characteristic in service.characteristics:
|
|
97
|
+
self.service_handlers[characteristic.handle] = characteristic.uuid
|
|
98
|
+
|
|
99
|
+
###############################
|
|
100
|
+
# async functions
|
|
101
|
+
|
|
102
|
+
def _start_bleak_loop_thread(self):
|
|
103
|
+
self._bleak_thread = threading.Thread(target=self._run_bleak_loop_thread)
|
|
104
|
+
self._bleak_thread.daemon = True
|
|
105
|
+
self._bleak_thread.start()
|
|
106
|
+
# finally wait for the thread to have properly started
|
|
107
|
+
self._bleak_loop_thread_started.wait()
|
|
108
|
+
|
|
109
|
+
def _run_bleak_loop_thread(self):
|
|
110
|
+
self._bleak_client_loop = asyncio.new_event_loop()
|
|
111
|
+
self._bleak_loop_thread_started.set()
|
|
112
|
+
self._bleak_client_loop.run_forever()
|
|
113
|
+
|
|
114
|
+
def _await_bleak_loop(self, coro, timeout=10):
|
|
115
|
+
future = asyncio.run_coroutine_threadsafe(coro, self._bleak_client_loop)
|
|
116
|
+
try:
|
|
117
|
+
return future.result(timeout)
|
|
118
|
+
except asyncio.TimeoutError:
|
|
119
|
+
print("Timeout waiting for bluetooth operation")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
async def _connect(self, mac_address):
|
|
123
|
+
self._client = BleakClient(mac_address, disconnected_callback=self._disconnect_handler)
|
|
124
|
+
if self._client is None:
|
|
125
|
+
print(f"Failed to create client for {mac_address}")
|
|
126
|
+
return False
|
|
127
|
+
try:
|
|
128
|
+
await self._client.connect()
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"Failed to connect to {mac_address}: {e}")
|
|
131
|
+
return False
|
|
132
|
+
if self._client.is_connected:
|
|
133
|
+
print(f"Connected to {mac_address}")
|
|
134
|
+
self._mac = mac_address
|
|
135
|
+
return True
|
|
136
|
+
else:
|
|
137
|
+
self._mac = "none"
|
|
138
|
+
print(f"Failed to connect to {mac_address}")
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
async def _disconnect(self):
|
|
142
|
+
if self._client:
|
|
143
|
+
await self._client.disconnect()
|
|
144
|
+
print(f"Disconnected successfully")
|
|
145
|
+
else:
|
|
146
|
+
print("No device to disconnect")
|
|
147
|
+
|
|
148
|
+
def _disconnect_handler(self, client):
|
|
149
|
+
print(f"Device disconnected: {self._mac}")
|
|
150
|
+
if self.disconnect_handler is not None:
|
|
151
|
+
self.disconnect_handler()
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
async def _scan_for_devices():
|
|
156
|
+
devices = await BleakScanner.discover(timeout=3.0)
|
|
157
|
+
# for device in devices:
|
|
158
|
+
# print(f"Device found: {device.name} ({device.address})")
|
|
159
|
+
return devices
|
|
160
|
+
|
|
161
|
+
async def _detect_services(self):
|
|
162
|
+
if not self.is_connected():
|
|
163
|
+
print("Not connected")
|
|
164
|
+
return {}
|
|
165
|
+
|
|
166
|
+
# This implicitly fetches services
|
|
167
|
+
services = self._client.services
|
|
168
|
+
|
|
169
|
+
s = {}
|
|
170
|
+
for service in services:
|
|
171
|
+
s[service.uuid] = {}
|
|
172
|
+
for char in service.characteristics:
|
|
173
|
+
s[service.uuid][char.uuid] = {
|
|
174
|
+
"uuid": char.uuid,
|
|
175
|
+
"description": char.description,
|
|
176
|
+
"properties": char.properties,
|
|
177
|
+
}
|
|
178
|
+
return s
|
|
179
|
+
|
|
180
|
+
# characteristics
|
|
181
|
+
async def _read_gatt_characteristics(self, characteristic_uuid):
|
|
182
|
+
return await self._client.read_gatt_char(characteristic_uuid)
|
|
183
|
+
|
|
184
|
+
async def _write_gatt_characteristics(self, characteristic_uuid, data):
|
|
185
|
+
return await self._client.write_gatt_char(characteristic_uuid, data)
|
|
186
|
+
|
|
187
|
+
async def _get_mtu(self):
|
|
188
|
+
return await self._client._acquire_mtu()
|
|
189
|
+
|
|
190
|
+
# notification
|
|
191
|
+
def _notify_callback(self, sender, data):
|
|
192
|
+
print(f"{sender}: {data}")
|
|
193
|
+
self.context.notify_dict.update({"uuid": data})
|
|
194
|
+
|
|
195
|
+
async def _start_gatt_notify(self, characteristic_uuid, notify_cb):
|
|
196
|
+
return await self._client.start_notify(characteristic_uuid, notify_cb)
|
|
197
|
+
|
|
198
|
+
async def _stop_gatt_notify(self, characteristic_uuid):
|
|
199
|
+
return await self._client.stop_notify(characteristic_uuid)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: kallistoapi/config.proto
|
|
4
|
+
"""Generated protocol buffer code."""
|
|
5
|
+
from google.protobuf.internal import builder as _builder
|
|
6
|
+
from google.protobuf import descriptor as _descriptor
|
|
7
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
8
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
9
|
+
# @@protoc_insertion_point(imports)
|
|
10
|
+
|
|
11
|
+
_sym_db = _symbol_database.Default()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18kallistoapi/config.proto\x12\rsensry.api.v1\"m\n GlobalConfigEnabledFeaturesClass\"I\n\x04Type\x12\x0f\n\x0b\x62\x65\x61\x63on_mode\x10\x00\x12\x13\n\x0fradio_time_sync\x10\x01\x12\x1b\n\x17vibration_approximation\x10\x02\"G\n\x0bUIModeClass\"8\n\x04Type\x12\x08\n\x04none\x10\x00\x12\x0c\n\x08periodic\x10\x01\x12\x0b\n\x07sw_sync\x10\x02\x12\x0b\n\x07hw_sync\x10\x03\"K\n\x19TransformationMethodClass\".\n\x04Type\x12\x08\n\x04mean\x10\x00\x12\n\n\x06median\x10\x01\x12\x07\n\x03max\x10\x02\x12\x07\n\x03min\x10\x03\"g\n\rTimesyncClass\"!\n\rTimestampType\x12\x07\n\x03tai\x10\x00\x12\x07\n\x03utc\x10\x01\"3\n\x11InterpolationMode\x12\x08\n\x04none\x10\x00\x12\t\n\x05liner\x10\x01\x12\t\n\x05\x63ubic\x10\x02\"g\n\x0e\x45xtensionClass\"U\n\tBoardType\x12\x08\n\x04none\x10\x00\x12\n\n\x06hf_gas\x10\x01\x12\x0f\n\x0bmicrostrain\x10\x02\x12\x0f\n\x0btemperature\x10\x03\x12\x10\n\x0cthermocouple\x10\x04\"\x9a\x03\n\x06\x43onfig\x12\x31\n\tmeta_info\x18\xff\x01 \x01(\x0b\x32\x1d.sensry.api.v1.ConfigMetaInfo\x12\x38\n\x13global_config_label\x18\x02 \x01(\x0b\x32\x1b.sensry.api.v1.GlobalConfig\x12\x32\n\rsensor_config\x18\x03 \x01(\x0b\x32\x1b.sensry.api.v1.SensorConfig\x12\x42\n\x15transformation_config\x18\x04 \x01(\x0b\x32#.sensry.api.v1.TransformationConfig\x12\x38\n\x10\x65xtension_config\x18\x05 \x01(\x0b\x32\x1e.sensry.api.v1.ExtensionConfig\x12\x36\n\x0ftimesync_config\x18\x06 \x01(\x0b\x32\x1d.sensry.api.v1.TimesyncConfig\x12\x39\n\x11ui_manager_config\x18\x07 \x01(\x0b\x32\x1e.sensry.api.v1.UIManagerConfig\"=\n\x0e\x43onfigMetaInfo\x12\x16\n\x0e\x63onfig_version\x18\x01 \x02(\r\x12\x13\n\nconfig_crc\x18\xff\x01 \x02(\r\"@\n\x0fUIManagerConfig\x12-\n\x04mode\x18\x01 \x02(\x0e\x32\x1f.sensry.api.v1.UIModeClass.Type\"\x86\x01\n\x0cGlobalConfig\x12\x13\n\x0b\x64\x65vice_name\x18\x01 \x01(\t\x12\x11\n\tlog_level\x18\x02 \x01(\x05\x12N\n\x10\x65nabled_features\x18\x03 \x03(\x0e\x32\x34.sensry.api.v1.GlobalConfigEnabledFeaturesClass.Type\"\xbd\x01\n\x0eTimesyncConfig\x12\x1b\n\x13prolongation_offset\x18\x01 \x02(\x05\x12\x42\n\x0etimestamp_type\x18\x02 \x02(\x0e\x32*.sensry.api.v1.TimesyncClass.TimestampType\x12J\n\x12interpolation_mode\x18\x03 \x02(\x0e\x32..sensry.api.v1.TimesyncClass.InterpolationMode\"\xcc\x02\n\x0cSensorConfig\x12/\n\x08\x64\x65\x66\x61ults\x18\x01 \x01(\x0b\x32\x1d.sensry.api.v1.SensorDefaults\x12\x31\n\tvibration\x18\x02 \x01(\x0b\x32\x1e.sensry.api.v1.VibrationConfig\x12\x35\n\x0btemperature\x18\x03 \x01(\x0b\x32 .sensry.api.v1.TemperatureConfig\x12\x36\n\x0bmicrostrain\x18\x04 \x01(\x0b\x32!.sensry.api.v1.ADCExtentionConfig\x12\x30\n\x05pt100\x18\x05 \x01(\x0b\x32!.sensry.api.v1.ADCExtentionConfig\x12\x37\n\x0cthermocouple\x18\x06 \x01(\x0b\x32!.sensry.api.v1.ADCExtentionConfig\"5\n\x0eSensorDefaults\x12\x15\n\rsampling_rate\x18\x01 \x02(\x05\x12\x0c\n\x04unit\x18\x02 \x02(\t\"$\n\x0fVibrationConfig\x12\x11\n\tthreshold\x18\x01 \x02(\x02\"R\n\x11TemperatureConfig\x12\x15\n\rsampling_rate\x18\x01 \x01(\x05\x12\x0c\n\x04unit\x18\x02 \x01(\t\x12\x18\n\x10\x63\x61libration_mode\x18\x03 \x01(\r\"{\n\x14TransformationConfig\x12\r\n\x05scale\x18\x01 \x02(\x02\x12\x0e\n\x06offset\x18\x02 \x01(\x02\x12\x0f\n\x07\x66ilters\x18\x03 \x03(\t\x12\x33\n\x06\x63ustom\x18\x04 \x01(\x0b\x32#.sensry.api.v1.TransformationCustom\"j\n\x14TransformationCustom\x12\x13\n\x0bwindow_size\x18\x01 \x02(\x05\x12=\n\x06method\x18\x02 \x02(\x0e\x32-.sensry.api.v1.TransformationMethodClass.Type\"X\n\x0f\x45xtensionConfig\x12\x45\n\x14\x65xtension_board_type\x18\x01 \x02(\x0e\x32\'.sensry.api.v1.ExtensionClass.BoardType\"\xf3\x01\n\x0f\x41\x64\x63\x44riverConfig\x12\x1a\n\x12samples_per_second\x18\x01 \x01(\r\x12\r\n\x05v_ref\x18\x02 \x01(\r\x12\x18\n\x10\x63ontiniouse_mode\x18\x03 \x01(\r\x12\x10\n\x08\x61\x64\x63_gain\x18\x04 \x01(\r\x12\x10\n\x08meas_cur\x18\x05 \x01(\r\x12\x17\n\x0fmeas_cur_output\x18\x06 \x01(\r\x12\x14\n\x0c\x64ifferential\x18\x07 \x01(\r\x12\x10\n\x08idac1_ch\x18\x08 \x01(\r\x12\x10\n\x08idac2_ch\x18\t \x01(\r\x12\x11\n\tinput_pos\x18\n \x01(\r\x12\x11\n\tinput_neg\x18\x0b \x01(\r\"H\n\x11\x41\x44\x43SensorWireMode\"3\n\x04Type\x12\x0c\n\x08two_wire\x10\x00\x12\x0e\n\nthree_wire\x10\x01\x12\r\n\tfour_wire\x10\x02\"\x9a\x02\n\x13\x41\x44\x43SensorItemConfig\x12\x15\n\rover_sampling\x18\x02 \x01(\r\x12 \n\x18oversampling_interval_us\x18\x03 \x01(\r\x12\x12\n\nresolution\x18\x04 \x01(\r\x12\r\n\x05r_ref\x18\x05 \x01(\x02\x12\x0e\n\x06offset\x18\x06 \x01(\x02\x12\x18\n\x10\x61\x64\x63_quantization\x18\x07 \x01(\x02\x12\x0f\n\x07lp_coof\x18\x08 \x01(\x02\x12\x38\n\twire_mode\x18\t \x01(\x0e\x32%.sensry.api.v1.ADCSensorWireMode.Type\x12\x32\n\nadc_config\x18\n \x03(\x0b\x32\x1e.sensry.api.v1.AdcDriverConfig\"s\n\x12\x41\x44\x43\x45xtentionConfig\x12\x16\n\x0e\x65nable_sensors\x18\x01 \x01(\r\x12\x11\n\tperiod_us\x18\x02 \x01(\r\x12\x32\n\x06\x63onfig\x18\x03 \x03(\x0b\x32\".sensry.api.v1.ADCSensorItemConfig')
|
|
17
|
+
|
|
18
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
|
19
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kallistoapi.config_pb2', globals())
|
|
20
|
+
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
21
|
+
|
|
22
|
+
DESCRIPTOR._options = None
|
|
23
|
+
_GLOBALCONFIGENABLEDFEATURESCLASS._serialized_start=43
|
|
24
|
+
_GLOBALCONFIGENABLEDFEATURESCLASS._serialized_end=152
|
|
25
|
+
_GLOBALCONFIGENABLEDFEATURESCLASS_TYPE._serialized_start=79
|
|
26
|
+
_GLOBALCONFIGENABLEDFEATURESCLASS_TYPE._serialized_end=152
|
|
27
|
+
_UIMODECLASS._serialized_start=154
|
|
28
|
+
_UIMODECLASS._serialized_end=225
|
|
29
|
+
_UIMODECLASS_TYPE._serialized_start=169
|
|
30
|
+
_UIMODECLASS_TYPE._serialized_end=225
|
|
31
|
+
_TRANSFORMATIONMETHODCLASS._serialized_start=227
|
|
32
|
+
_TRANSFORMATIONMETHODCLASS._serialized_end=302
|
|
33
|
+
_TRANSFORMATIONMETHODCLASS_TYPE._serialized_start=256
|
|
34
|
+
_TRANSFORMATIONMETHODCLASS_TYPE._serialized_end=302
|
|
35
|
+
_TIMESYNCCLASS._serialized_start=304
|
|
36
|
+
_TIMESYNCCLASS._serialized_end=407
|
|
37
|
+
_TIMESYNCCLASS_TIMESTAMPTYPE._serialized_start=321
|
|
38
|
+
_TIMESYNCCLASS_TIMESTAMPTYPE._serialized_end=354
|
|
39
|
+
_TIMESYNCCLASS_INTERPOLATIONMODE._serialized_start=356
|
|
40
|
+
_TIMESYNCCLASS_INTERPOLATIONMODE._serialized_end=407
|
|
41
|
+
_EXTENSIONCLASS._serialized_start=409
|
|
42
|
+
_EXTENSIONCLASS._serialized_end=512
|
|
43
|
+
_EXTENSIONCLASS_BOARDTYPE._serialized_start=427
|
|
44
|
+
_EXTENSIONCLASS_BOARDTYPE._serialized_end=512
|
|
45
|
+
_CONFIG._serialized_start=515
|
|
46
|
+
_CONFIG._serialized_end=925
|
|
47
|
+
_CONFIGMETAINFO._serialized_start=927
|
|
48
|
+
_CONFIGMETAINFO._serialized_end=988
|
|
49
|
+
_UIMANAGERCONFIG._serialized_start=990
|
|
50
|
+
_UIMANAGERCONFIG._serialized_end=1054
|
|
51
|
+
_GLOBALCONFIG._serialized_start=1057
|
|
52
|
+
_GLOBALCONFIG._serialized_end=1191
|
|
53
|
+
_TIMESYNCCONFIG._serialized_start=1194
|
|
54
|
+
_TIMESYNCCONFIG._serialized_end=1383
|
|
55
|
+
_SENSORCONFIG._serialized_start=1386
|
|
56
|
+
_SENSORCONFIG._serialized_end=1718
|
|
57
|
+
_SENSORDEFAULTS._serialized_start=1720
|
|
58
|
+
_SENSORDEFAULTS._serialized_end=1773
|
|
59
|
+
_VIBRATIONCONFIG._serialized_start=1775
|
|
60
|
+
_VIBRATIONCONFIG._serialized_end=1811
|
|
61
|
+
_TEMPERATURECONFIG._serialized_start=1813
|
|
62
|
+
_TEMPERATURECONFIG._serialized_end=1895
|
|
63
|
+
_TRANSFORMATIONCONFIG._serialized_start=1897
|
|
64
|
+
_TRANSFORMATIONCONFIG._serialized_end=2020
|
|
65
|
+
_TRANSFORMATIONCUSTOM._serialized_start=2022
|
|
66
|
+
_TRANSFORMATIONCUSTOM._serialized_end=2128
|
|
67
|
+
_EXTENSIONCONFIG._serialized_start=2130
|
|
68
|
+
_EXTENSIONCONFIG._serialized_end=2218
|
|
69
|
+
_ADCDRIVERCONFIG._serialized_start=2221
|
|
70
|
+
_ADCDRIVERCONFIG._serialized_end=2464
|
|
71
|
+
_ADCSENSORWIREMODE._serialized_start=2466
|
|
72
|
+
_ADCSENSORWIREMODE._serialized_end=2538
|
|
73
|
+
_ADCSENSORWIREMODE_TYPE._serialized_start=2487
|
|
74
|
+
_ADCSENSORWIREMODE_TYPE._serialized_end=2538
|
|
75
|
+
_ADCSENSORITEMCONFIG._serialized_start=2541
|
|
76
|
+
_ADCSENSORITEMCONFIG._serialized_end=2823
|
|
77
|
+
_ADCEXTENTIONCONFIG._serialized_start=2825
|
|
78
|
+
_ADCEXTENTIONCONFIG._serialized_end=2940
|
|
79
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
class BatteryConfig:
|
|
9
|
+
def __init__(self, command, mode=None, value=None):
|
|
10
|
+
"""
|
|
11
|
+
:param command: 0x01 = shutdown, 0x02 = reboot
|
|
12
|
+
:param mode: 0x01 = offset (value is int), 0x02 = absolute time (value is datetime)
|
|
13
|
+
:param value: int (for offset), or datetime (for absolute time)
|
|
14
|
+
"""
|
|
15
|
+
self.command = command
|
|
16
|
+
self.mode = mode
|
|
17
|
+
|
|
18
|
+
if self.mode == 0x02 and isinstance(value, datetime):
|
|
19
|
+
# Convert to UTC before encoding
|
|
20
|
+
utc_dt = value.astimezone(timezone.utc)
|
|
21
|
+
self.value = self._datetime_to_cts_bytes(utc_dt)
|
|
22
|
+
else:
|
|
23
|
+
self.value = value
|
|
24
|
+
|
|
25
|
+
def _datetime_to_cts_bytes(self, dt: datetime) -> bytes:
|
|
26
|
+
year = dt.year
|
|
27
|
+
month = dt.month
|
|
28
|
+
day = dt.day
|
|
29
|
+
hour = dt.hour
|
|
30
|
+
minute = dt.minute
|
|
31
|
+
second = dt.second
|
|
32
|
+
day_of_week = dt.isoweekday() # Monday = 1, Sunday = 7
|
|
33
|
+
fractions256 = 0
|
|
34
|
+
adjust_reason = 0
|
|
35
|
+
|
|
36
|
+
return struct.pack('<HBBBBBB2B',
|
|
37
|
+
year,
|
|
38
|
+
month,
|
|
39
|
+
day,
|
|
40
|
+
hour,
|
|
41
|
+
minute,
|
|
42
|
+
second,
|
|
43
|
+
day_of_week,
|
|
44
|
+
fractions256,
|
|
45
|
+
adjust_reason)
|
|
46
|
+
|
|
47
|
+
def to_bytes(self):
|
|
48
|
+
result = bytearray()
|
|
49
|
+
result.append(self.command)
|
|
50
|
+
|
|
51
|
+
if self.mode is not None:
|
|
52
|
+
result.append(self.mode)
|
|
53
|
+
|
|
54
|
+
if self.mode == 0x01 and isinstance(self.value, int):
|
|
55
|
+
result += struct.pack('<I', self.value)
|
|
56
|
+
elif self.mode == 0x02 and isinstance(self.value, (bytes, bytearray)) and len(self.value) == 10:
|
|
57
|
+
result += self.value
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError("Invalid value format for the given mode")
|
|
60
|
+
|
|
61
|
+
return bytes(result)
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def from_bytes(data):
|
|
65
|
+
if len(data) < 1:
|
|
66
|
+
raise ValueError("Data too short")
|
|
67
|
+
|
|
68
|
+
command = data[0]
|
|
69
|
+
|
|
70
|
+
if len(data) == 1:
|
|
71
|
+
return BatteryConfig(command)
|
|
72
|
+
|
|
73
|
+
mode = data[1]
|
|
74
|
+
|
|
75
|
+
if mode == 0x01 and len(data) >= 6:
|
|
76
|
+
value = struct.unpack('<I', data[2:6])[0]
|
|
77
|
+
elif mode == 0x02 and len(data) >= 12:
|
|
78
|
+
value = data[2:12] # leave as raw bytes
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError("Invalid or incomplete data for the specified mode")
|
|
81
|
+
|
|
82
|
+
return BatteryConfig(command, mode, value)
|
|
83
|
+
|
|
84
|
+
def to_dict(self):
|
|
85
|
+
data = {
|
|
86
|
+
'command': self.command,
|
|
87
|
+
'mode': self.mode
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if self.mode == 0x01:
|
|
91
|
+
data['value'] = self.value
|
|
92
|
+
elif self.mode == 0x02:
|
|
93
|
+
data['value'] = list(self.value) if self.value else None
|
|
94
|
+
|
|
95
|
+
return data
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def from_dict(data):
|
|
99
|
+
value = data.get('value')
|
|
100
|
+
if isinstance(value, list): # CTS bytes in list form
|
|
101
|
+
value = bytes(value)
|
|
102
|
+
return BatteryConfig(data['command'], data.get('mode'), value)
|
|
103
|
+
|
|
104
|
+
def save_to_json(self, filepath):
|
|
105
|
+
with open(filepath, 'w') as f:
|
|
106
|
+
json.dump(self.to_dict(), f, indent=4)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def load_from_json(filepath):
|
|
110
|
+
with open(filepath, 'r') as f:
|
|
111
|
+
data = json.load(f)
|
|
112
|
+
return BatteryConfig.from_dict(data)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class BatteryHandler:
|
|
117
|
+
def serialize(self, config):
|
|
118
|
+
"""Serializes a MicrostrainConfig object to bytes."""
|
|
119
|
+
return config.to_bytes()
|
|
120
|
+
|
|
121
|
+
def deserialize(self, data):
|
|
122
|
+
"""Deserializes bytes to a MicrostrainConfig object."""
|
|
123
|
+
return BatteryConfig.from_bytes(data)
|
|
124
|
+
|
|
125
|
+
def set_battery_config(self, json_filepath):
|
|
126
|
+
"""
|
|
127
|
+
Loads configuration from a JSON file, serializes it to bytes,
|
|
128
|
+
and returns the serialized configuration.
|
|
129
|
+
"""
|
|
130
|
+
config = BatteryConfig.load_from_json(json_filepath)
|
|
131
|
+
serialized_config = config.to_bytes()
|
|
132
|
+
|
|
133
|
+
print(f"Microstrain config with serialized value {serialized_config.hex()}")
|
|
134
|
+
return serialized_config
|
|
135
|
+
|
|
136
|
+
def get_battery_config(self, data):
|
|
137
|
+
"""
|
|
138
|
+
Deserializes binary data to a MicrostrainConfig object
|
|
139
|
+
and converts it to a dictionary.
|
|
140
|
+
"""
|
|
141
|
+
config = BatteryConfig.from_bytes(data)
|
|
142
|
+
return config.to_dict()
|
|
143
|
+
|
|
144
|
+
def unpack_battery_samples(self, buffers: list[bytes]):
|
|
145
|
+
return buffers
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
from datetime import datetime, timedelta
|
|
149
|
+
# Schedule shutdown untill tomorrow 08:30
|
|
150
|
+
future_time = datetime.now().replace(hour=8, minute=30, second=0, microsecond=0) + timedelta(days=1)
|
|
151
|
+
|
|
152
|
+
config = BatteryConfig(command=0x01, mode=0x02, value=future_time)
|
|
153
|
+
payload = config.to_bytes()
|
|
154
|
+
|
|
155
|
+
print(payload.hex()) # Output binary payload in hex format
|
|
156
|
+
|
|
157
|
+
handler = BatteryHandler()
|
|
158
|
+
# Serialize and Deserialize to Bytes
|
|
159
|
+
serialized = handler.serialize(config)
|
|
160
|
+
print("Serialized:", serialized)
|
|
161
|
+
|
|
162
|
+
deserialized = handler.deserialize(serialized)
|
|
163
|
+
print("Deserialized:", deserialized.__dict__)
|
|
164
|
+
|
|
165
|
+
# Save to JSON and Load from JSON
|
|
166
|
+
config.save_to_json("battery_config.json")
|
|
167
|
+
loaded_config = BatteryConfig.load_from_json("battery_config.json")
|
|
168
|
+
print("Loaded from JSON:", loaded_config.__dict__)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
def get_conf_uuid(name):
|
|
3
|
+
uuid_map = {
|
|
4
|
+
"log_global_conf": "00002021-702b-69b5-b243-d6094a2b0e24",
|
|
5
|
+
"log_start_cond": "00002022-702b-69b5-b243-d6094a2b0e24",
|
|
6
|
+
"log_mem_ctrl": "00002024-702b-69b5-b243-d6094a2b0e24",
|
|
7
|
+
"log_mem_stat": "00002025-702b-69b5-b243-d6094a2b0e24",
|
|
8
|
+
"log_temp_dump": "00002101-702b-69b5-b243-d6094a2b0e24",
|
|
9
|
+
"log_temp_conf": "00002104-702b-69b5-b243-d6094a2b0e24",
|
|
10
|
+
"log_vibration_dump": "00002111-702b-69b5-b243-d6094a2b0e24",
|
|
11
|
+
"log_vibration_conf": "00002114-702b-69b5-b243-d6094a2b0e24",
|
|
12
|
+
"vibration_conf": "00001022-702b-69b5-b243-d6094a2b0e24"
|
|
13
|
+
}
|
|
14
|
+
if name not in uuid_map:
|
|
15
|
+
raise Exception("unknown requested UUID")
|
|
16
|
+
|
|
17
|
+
return uuid_map[name]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_compare_value(name):
|
|
21
|
+
uuid_map = {
|
|
22
|
+
"log_global_disable": bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
|
23
|
+
"log_global_enable": bytearray([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
|
24
|
+
"log_vibration_disable": bytearray([0x00]),
|
|
25
|
+
"log_vibration_enable": bytearray([0x01]),
|
|
26
|
+
"log_vibration_dump": bytearray([0x02]),
|
|
27
|
+
"log_manual_trigger": bytearray([0x00]),
|
|
28
|
+
"log_timestamp_trigger": bytearray([0x01, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, ]),
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
if name not in uuid_map:
|
|
32
|
+
raise Exception("unknown requested UUID")
|
|
33
|
+
|
|
34
|
+
return uuid_map[name]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def log_dumping_callback(sender, data):
|
|
38
|
+
print(f"{sender}: {data}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def log_vibration_callback(context, sender, data):
|
|
42
|
+
print(f"{sender}: {data}")
|
|
43
|
+
context.vibration_dump = data
|
|
44
|
+
|
|
45
|
+
def kallisto_connect(context, mac_address):
|
|
46
|
+
if mac_address == "default":
|
|
47
|
+
mac_address = context.default_mac
|
|
48
|
+
|
|
49
|
+
if context.ble_manager.connect(mac_address):
|
|
50
|
+
return True
|
|
51
|
+
context.ble_manager.scan_for_devices()
|
|
52
|
+
|
|
53
|
+
if context.ble_manager.connect(mac_address):
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
print(f"failed again to connect device {mac_address}")
|
|
57
|
+
return False
|