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.
Files changed (36) hide show
  1. kallistoapi/__init__.py +1 -0
  2. kallistoapi/bluetooth_gatt.py +199 -0
  3. kallistoapi/config_pb2.py +79 -0
  4. kallistoapi/kallisto_battery_service.py +168 -0
  5. kallistoapi/kallisto_helper.py +57 -0
  6. kallistoapi/kallisto_manager.py +673 -0
  7. kallistoapi/kallisto_sensry_serive_microstrain.py +184 -0
  8. kallistoapi/kallisto_time.py +82 -0
  9. kallistoapi/mcumgr.py +204 -0
  10. kallistoapi/modules/__init__.py +0 -0
  11. kallistoapi/modules/base.py +104 -0
  12. kallistoapi/modules/device_info.py +43 -0
  13. kallistoapi/modules/list.py +57 -0
  14. kallistoapi/modules/sensor.py +383 -0
  15. kallistoapi/modules/sensor_accelerometer.py +98 -0
  16. kallistoapi/modules/sensor_barometer.py +71 -0
  17. kallistoapi/modules/sensor_bvoc.py +71 -0
  18. kallistoapi/modules/sensor_eco2.py +71 -0
  19. kallistoapi/modules/sensor_env_vector.py +68 -0
  20. kallistoapi/modules/sensor_fuel_gauge.py +40 -0
  21. kallistoapi/modules/sensor_gyrometer.py +99 -0
  22. kallistoapi/modules/sensor_humidity.py +71 -0
  23. kallistoapi/modules/sensor_iaq.py +71 -0
  24. kallistoapi/modules/sensor_light.py +71 -0
  25. kallistoapi/modules/sensor_magnetometer.py +70 -0
  26. kallistoapi/modules/sensor_pressure.py +71 -0
  27. kallistoapi/modules/sensor_pt100.py +71 -0
  28. kallistoapi/modules/sensor_temperature.py +71 -0
  29. kallistoapi/modules/sensor_tvoc.py +71 -0
  30. kallistoapi/modules/sensor_tx_power.py +86 -0
  31. kallistoapi/modules/sensor_vibration.py +117 -0
  32. kallistoapi-2.0.1.dist-info/METADATA +140 -0
  33. kallistoapi-2.0.1.dist-info/RECORD +36 -0
  34. kallistoapi-2.0.1.dist-info/WHEEL +5 -0
  35. kallistoapi-2.0.1.dist-info/licenses/LICENSE +17 -0
  36. kallistoapi-2.0.1.dist-info/top_level.txt +1 -0
@@ -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