diff --git a/main.py b/main.py index 1868a4b..ee79db1 100644 --- a/main.py +++ b/main.py @@ -56,6 +56,20 @@ class GadgetbridgeMQTTPublisher: "password": os.getenv("MQTT_PASSWORD", ""), } + def get_day_start_timestamp(self) -> int: + """Get the timestamp for the start of the current day (4am)""" + now = datetime.now() + today = now.date() + + # Day starts at 4am + day_start_time = datetime.combine(today, datetime.min.time()).replace(hour=4) + + # If current time is before 4am, we're still in "yesterday's" day + if now.hour < 4: + day_start_time -= timedelta(days=1) + + return int(day_start_time.timestamp()) + def initialize_sensors(self): """Initialize sensor definitions after device_name is available""" self.sensors = [ @@ -166,6 +180,14 @@ class GadgetbridgeMQTTPublisher: "state_class": "measurement", "query": self.query_total_sleep_duration, }, + { + "name": "Server Time", + "unique_id": "server_time", + "state_topic": f"gadgetbridge/{self.device_name}/server_time", + "icon": "mdi:clock-outline", + "device_class": "timestamp", + "query": self.query_server_time, + }, ] async def publish_home_assistant_discovery( @@ -211,36 +233,58 @@ class GadgetbridgeMQTTPublisher: ) def query_daily_steps(self, cursor) -> Any: - today = datetime.now().date() - today_start = int(datetime.combine(today, datetime.min.time()).timestamp()) - today_end = int(datetime.combine(today, datetime.max.time()).timestamp()) + day_start_ts = self.get_day_start_timestamp() + now_ts = int(datetime.now().timestamp()) + cursor.execute( "SELECT SUM(STEPS) FROM XIAOMI_ACTIVITY_SAMPLE WHERE TIMESTAMP >= ? AND TIMESTAMP <= ?", - (today_start, today_end), + (day_start_ts, now_ts), ) return cursor.fetchone()[0] or 0 def query_weekly_steps(self, cursor) -> Any: - today = datetime.now().date() + now = datetime.now() + today = now.date() + + # Week starts on Monday at 4am week_start = today - timedelta(days=today.weekday()) - week_start_ts = int( - datetime.combine(week_start, datetime.min.time()).timestamp() - ) + week_start_time = datetime.combine(week_start, datetime.min.time()).replace(hour=4) + + # If we're before 4am on Monday, the week actually started last Monday + if today.weekday() == 0 and now.hour < 4: + week_start_time -= timedelta(days=7) + + week_start_ts = int(week_start_time.timestamp()) + now_ts = int(now.timestamp()) + cursor.execute( - "SELECT SUM(STEPS) FROM XIAOMI_ACTIVITY_SAMPLE WHERE TIMESTAMP >= ?", - (week_start_ts,), + "SELECT SUM(STEPS) FROM XIAOMI_ACTIVITY_SAMPLE WHERE TIMESTAMP >= ? AND TIMESTAMP <= ?", + (week_start_ts, now_ts), ) return cursor.fetchone()[0] or 0 def query_monthly_steps(self, cursor) -> Any: - today = datetime.now().date() + now = datetime.now() + today = now.date() + + # Month starts on 1st at 4am month_start = today.replace(day=1) - month_start_ts = int( - datetime.combine(month_start, datetime.min.time()).timestamp() - ) + month_start_time = datetime.combine(month_start, datetime.min.time()).replace(hour=4) + + # If we're before 4am on the 1st, the month actually started last month's 1st + if today.day == 1 and now.hour < 4: + # Go back to previous month + if month_start.month == 1: + month_start_time = month_start_time.replace(year=month_start.year - 1, month=12) + else: + month_start_time = month_start_time.replace(month=month_start.month - 1) + + month_start_ts = int(month_start_time.timestamp()) + now_ts = int(now.timestamp()) + cursor.execute( - "SELECT SUM(STEPS) FROM XIAOMI_ACTIVITY_SAMPLE WHERE TIMESTAMP >= ?", - (month_start_ts,), + "SELECT SUM(STEPS) FROM XIAOMI_ACTIVITY_SAMPLE WHERE TIMESTAMP >= ? AND TIMESTAMP <= ?", + (month_start_ts, now_ts), ) return cursor.fetchone()[0] or 0 @@ -266,29 +310,41 @@ class GadgetbridgeMQTTPublisher: return row[0] if row else None def query_hr_resting(self, cursor) -> Any: + day_start_ts = self.get_day_start_timestamp() + cursor.execute( - "SELECT HR_RESTING FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1" + "SELECT HR_RESTING FROM XIAOMI_DAILY_SUMMARY_SAMPLE WHERE TIMESTAMP >= ? ORDER BY TIMESTAMP DESC LIMIT 1", + (day_start_ts,) ) row = cursor.fetchone() return row[0] if row else None def query_hr_max(self, cursor) -> Any: + day_start_ts = self.get_day_start_timestamp() + cursor.execute( - "SELECT HR_MAX FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1" + "SELECT HR_MAX FROM XIAOMI_DAILY_SUMMARY_SAMPLE WHERE TIMESTAMP >= ? ORDER BY TIMESTAMP DESC LIMIT 1", + (day_start_ts,) ) row = cursor.fetchone() return row[0] if row else None def query_hr_avg(self, cursor) -> Any: + day_start_ts = self.get_day_start_timestamp() + cursor.execute( - "SELECT HR_AVG FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1" + "SELECT HR_AVG FROM XIAOMI_DAILY_SUMMARY_SAMPLE WHERE TIMESTAMP >= ? ORDER BY TIMESTAMP DESC LIMIT 1", + (day_start_ts,) ) row = cursor.fetchone() return row[0] if row else None def query_calories(self, cursor) -> Any: + day_start_ts = self.get_day_start_timestamp() + cursor.execute( - "SELECT CALORIES FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1" + "SELECT CALORIES FROM XIAOMI_DAILY_SUMMARY_SAMPLE WHERE TIMESTAMP >= ? ORDER BY TIMESTAMP DESC LIMIT 1", + (day_start_ts,) ) row = cursor.fetchone() return row[0] if row else None @@ -309,6 +365,10 @@ class GadgetbridgeMQTTPublisher: # Convert minutes to hours, round to 2 decimals return round(row[0] / 60, 2) if row and row[0] is not None else None + def query_server_time(self, cursor) -> Any: + """Return current server time in ISO 8601 format for Home Assistant timestamp""" + return datetime.now().isoformat() + def get_sensor_data(self) -> Dict[str, Any]: """Query all sensors and return their values as a dict""" if not os.path.exists(self.db_path):