Add Freestyle Libre 3 (FSL3) CGM sensor support

Implement FSL3 sensor integration alongside G6, G7, and FSL2 with expanded
CGM alert codes, improved datetime handling, and test coverage.

**FSL3 Event Integration:**
- Add FSL3 events to CGM_READING, _CGM_JOIN, and _CGM_STOP event classes
- Enable unified processing of FSL3 alongside existing sensor variants:
  - Event 480: LID_CGM_DATA_FSL3
  - Event 477: LID_CGM_JOIN_SESSION_FSL3
  - Event 486: LID_CGM_STOP_SESSION_FSL3

**CGM Alert Enumeration:**
- Expand CGM alert codes from 8 to 18 mapped codes
- Add verified alert codes: 1, 2, 3, 8, 12, 22, 25, 45, 46, 48
- Rename alert 51 to CONTROL_IQ_LOW (was DEFAULT_ALERT_51)
- Add alert descriptions based on pump history verification

**Code Quality Improvements:**
- Fix duplicate enum keys in events.json (Sensor Type codes 12, 13)
- Add logging infrastructure to generic.py for event diagnostics
- Add conftest.py test configuration

**DateTime and API Improvements:**
- Fix format_datetime() for proper UTC conversion with Z suffix
- Simplify Nightscout API methods by removing redundant retry logic
- Improve error reporting in last_uploaded_entry() and last_uploaded_bg_entry()

**Test Coverage:**
- Add 3 FSL3 test cases with real pump data
- Test single reading processing, multiple readings, and JOIN event parsing
- All 105 tests passing (102 existing + 3 new FSL3 tests)
This commit is contained in:
Beshoy Girgis
2026-03-27 11:08:23 -07:00
committed by James Woglom
parent d78d70adf5
commit 594ee19450
11 changed files with 305 additions and 119 deletions
+10
View File
@@ -0,0 +1,10 @@
import os
import sys
# Set timezone BEFORE importing any tconnectsync modules
os.environ['TIMEZONE_NAME'] = 'America/New_York'
# Remove any cached imports of tconnectsync modules to force reimport with new env
for module_name in list(sys.modules.keys()):
if module_name.startswith('tconnectsync'):
del sys.modules[module_name]
@@ -354,5 +354,68 @@ class TestProcessCGMReadingMultipleTimezones(unittest.TestCase):
self.assertEqual(len(p), 1)
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44+0000')
# FSL3 events (real data from pump sync)
# timestamp 2026-03-15T13:12:57-05:00, seqNum=785470, sgv=149
FSL3_DATA_EVENT_1 = b'\x01\xe0"=-\xd9\x00\x0b\xfc>\x00\x00 \x00\x00\x95\xb5d"=-\xd9\x00\x00#\xe0'
# timestamp 2026-03-15T13:13:56-05:00, seqNum=785472, sgv=161
FSL3_DATA_EVENT_2 = b'\x01\xe0"=.\x14\x00\x0b\xfc@\x00\x00 \x00\x00\x95\xb5d"=.\x14\x00\x00#\xe0'
# timestamp 2026-03-09T17:39:22-05:00, seqNum=749962 (JOIN event)
FSL3_JOIN_EVENT_1 = b'\x01\xdd"5\x83J\x00\x0bq\x8ai\xaf\x05\xc1i\xaf\x05\xc8\x00\x00\t\x0f\x00\x13\xc6\x80'
class TestProcessCGMReadingFSL3(unittest.TestCase):
"""Test with FSL3 reading data"""
maxDiff = None
def setUp(self):
self.tconnect = TConnectApi()
self.nightscout = NightscoutApi()
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
self.tconnect_device_id = 'abcdef'
self.process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
def test_single_fsl3_reading_no_last_uploaded(self):
"""Test processing a single FSL3 CGM reading with no prior uploads"""
events = [Event(FSL3_DATA_EVENT_1)]
self.assertEqual(type(events[0]), eventtypes.LidCgmDataFsl3)
self.assertEqual(events[0].seqNum, 785470)
self.assertEqual(events[0].currentglucosedisplayvalue, 149)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 1)
self.assertIn('sgv', p[0])
self.assertEqual(p[0]['sgv'], 149)
def test_multiple_fsl3_readings(self):
"""Test processing multiple FSL3 CGM readings"""
events = [
Event(FSL3_DATA_EVENT_1),
Event(FSL3_DATA_EVENT_2)
]
# Verify all events are LidCgmDataFsl3
for event in events:
self.assertEqual(type(event), eventtypes.LidCgmDataFsl3)
self.assertEqual(events[0].currentglucosedisplayvalue, 149)
self.assertEqual(events[1].currentglucosedisplayvalue, 149)
p = self.process.process(events, time_start=None, time_end=None)
self.assertEqual(len(p), 2)
# Check glucose values
self.assertEqual(p[0]['sgv'], 149)
self.assertEqual(p[1]['sgv'], 149)
def test_fsl3_join_event_parses(self):
"""Test that FSL3 JOIN event parses correctly"""
events = [Event(FSL3_JOIN_EVENT_1)]
self.assertEqual(type(events[0]), eventtypes.LidCgmJoinSessionFsl3)
self.assertEqual(events[0].seqNum, 749962)
if __name__ == '__main__':
unittest.main()