From b78ccab2cd3d023d974d6b4cca78c0fff7bdbb79 Mon Sep 17 00:00:00 2001 From: lasers Date: Mon, 15 Jan 2024 19:09:13 -0600 Subject: [PATCH] add new module: dexcom --- py3status/modules/dexcom.py | 150 ++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 py3status/modules/dexcom.py diff --git a/py3status/modules/dexcom.py b/py3status/modules/dexcom.py new file mode 100644 index 0000000000..5ca2ff60f6 --- /dev/null +++ b/py3status/modules/dexcom.py @@ -0,0 +1,150 @@ +r""" + +Display glucose readings from your Dexcom CGM system. + +Dexcom CGM systems provide glucose readings up to every five minutes, all with +zero fingersticks required for calibration or mealtime dosing. Designed to help +diabetes patients keep track of their blood glucose levels with ease. + +Configuration parameters: + format: display format for this module + *(default "Dexcom [\?color=mg_dl {mg_dl} mg/dL {trend_arrow}] [\?color=darkgrey {datetime}]")* + format_datetime: specify strftime characters to format (default {"datetime": "%-I:%M %p"}) + ous: specify whether if the Dexcom Share user is outside of the US (default False) + password: specify password for the Dexcom Share user (default None) + user: specify username for the Dexcom Share user, not follower (default None) + thresholds: specify color thresholds to use + *(default { + "mg_dl": [(55, "bad"), (70, "degraded"), (80, "good"), (130, "degraded"), (180, "bad")], + "mmol_l": [(3.1, "bad"), (3.9, "degraded"), (4.4, "good"), (7.2, "degraded"), (10.0, "bad")], + })* + +Format placeholders: + {mg_dl} blood glucose value in mg/dL, eg 80 + {mmol_l} blood glucose value in mmol/L, eg 4.4 + {trend} blood glucose trend information, eg 4 + {trend_direction} blood glucose trend direction, eg Flat + {trend_description} blood glucose trend information description, eg steady + {trend_arrow} blood glucose trend as unicode arrow, eg → + {datetime} glucose reading recorded time as datetime + +format_datetime placeholders: + key: epoch_placeholder, eg {datetime} + value: % strftime characters to be translated, eg '%b %d' ----> 'Jan 1' + +Color thresholds: + xxx: print a color based on the value of `xxx` placeholder + +Requires: + pydexcom: A simple Python API to interact with Dexcom Share service + +Note: + IF GLUCOSE ALERTS AND CGM READINGS DO NOT MATCH SYMPTOMS OR EXPECTATIONS, + USE A BLOOD GLUCOSE METER TO MAKE DIABETES TREATMENT DECISIONS. + +Examples: +``` +# compact +dexcom { + format = "\?color=darkgrey [\?color=mg_dl {mg_dl} {trend_arrow}] {datetime}" + format_datetime = {"datetime": "%-I:%M"} +} +``` + +@author lasers + +SAMPLE OUTPUT +[ + {"full_text": "Dexcom "}, + {"full_text": "80 mg/dL →", "color": "#00FF00"}, + {"full_text": "7:15 PM", "color": "#A9A9A9"} +] +""" + +from pydexcom import Dexcom +from datetime import datetime as dt, timedelta as td + + +class Py3status: + """ """ + + # available configuration parameters + format = r"Dexcom [\?color=mg_dl {mg_dl} mg/dL {trend_arrow}] [\?color=darkgrey {datetime}]" + format_datetime = {"datetime": "%-I:%M %p"} + ous = False + password = None + thresholds = { + "mg_dl": [ + (55, "bad"), + (70, "degraded"), + (80, "good"), + (130, "degraded"), + (180, "bad"), + ], + "mmol_l": [ + (3.1, "bad"), + (3.9, "degraded"), + (4.4, "good"), + (7.2, "degraded"), + (10.0, "bad"), + ], + } + user = None + + def post_config_hook(self): + for x in ["user", "password"]: + if not getattr(self, x): + raise Exception(f"missing {x}") + + # fmt: off + self.placeholders = [ + "datetime", "mg_dl", "mmol_l", "trend", "trend_arrow", "trend_description", "trend_direction", + ] + # fmt: on + + self.init = {"datetimes": []} + for word in ["datetime"]: + if self.py3.format_contains(self.format, word) and word in self.format_datetime: + self.init["datetimes"].append(word) + + self.thresholds_init = self.py3.get_color_names_list(self.format) + self.dexcom_class = Dexcom(self.user, self.password, ous=self.ous) + + def _get_dexcom_data(self): + glucose_reading = self.dexcom_class.get_current_glucose_reading() + data = {x: getattr(glucose_reading, x) for x in self.placeholders} + data["datetime"] = data["datetime"].isoformat() + return data + + def _get_cached_until(self, data): + timestamp = dt.fromisoformat(data["datetime"]) + return ((timestamp + td(minutes=5, seconds=5)) - dt.today()).total_seconds() + + def dexcom(self): + dexcom_data = self._get_dexcom_data() + cached_until = self._get_cached_until(dexcom_data) + + for word in self.init["datetimes"]: + if word in dexcom_data: + tmp_str = dt.strftime( + dt.fromisoformat(dexcom_data[word]), self.format_datetime[word] + ) + dexcom_data[word] = self.py3.safe_format(tmp_str) + + for x in self.thresholds_init: + if x in dexcom_data: + self.py3.threshold_get_color(dexcom_data[x], x) + + return { + "cached_until": self.py3.time_in(cached_until), + "full_text": self.py3.safe_format(self.format, dexcom_data), + } + + +if __name__ == "__main__": + """ + Run module in test mode. + """ + from py3status.module_test import module_test + + module_test(Py3status)