add sleep sensors
This commit is contained in:
parent
7ba2db2a6f
commit
ffd8493167
@ -4,15 +4,10 @@ This is a Gadgetbridge MQTT bridge for TrueNAS Scale, which allows you to connec
|
||||
|
||||
## Setup
|
||||
|
||||
- edit [```compose.yaml```](./compose.yaml) and set
|
||||
- copy [```compose.yaml```](./compose.yaml) your TrueNAS Scale Custom App and set
|
||||
- mount points for your data
|
||||
- mount point for your Gadgetbridge database
|
||||
- your Timezone
|
||||
- environment variables for your MQTT broker
|
||||
- your DEVICE_NAME. (Can be skipped first time: If you start the app with "unknown" as DEVICE_NAME, you will see all devices from the Gadgetbridge Database in your App Log.)
|
||||
- create ```Config``` Dataset in TrueNAS Scale with App Preset "Generic"
|
||||
- write your compose.yaml into the Config Dataset, e.g. ```sudo nano /mnt/Data/Apps/GadgetbridgeMqtt/Config/compose.yaml```
|
||||
- use the Console give admin access (instead of root) e.g:```sudo chown -R 950:950 /mnt/Data/Apps/GadgetbridgeMqtt/App``` (this should prevent apps to modify the config?)
|
||||
- include it in your TrueNAS Scale Custom Apps e.g. ```include: [/mnt/Data/Apps/GadgetbridgeMqtt/Config/compose.yaml]```
|
||||
- start the app
|
||||
- find your DEVICE_NAME in the App Log and set it in the compose.yaml, then restart
|
||||
|
||||
@ -15,7 +15,6 @@ services:
|
||||
- MQTT_USERNAME=*****
|
||||
- MQTT_PASSWORD=*****
|
||||
- GADGETBRIDGE_DB_PATH=/data/Gadgetbridge.db
|
||||
- DEVICE_NAME="unknown"
|
||||
- PYTHONUNBUFFERED=1
|
||||
- PUBLISH_INTERVAL_SECONDS=300
|
||||
- DAY_END_TIME=5 # 5 AM
|
||||
|
||||
113
main.py
113
main.py
@ -25,7 +25,6 @@ class GadgetbridgeMQTTPublisher:
|
||||
self.load_config()
|
||||
self.mqtt_client = None
|
||||
self.publish_interval = int(os.getenv("PUBLISH_INTERVAL_SECONDS", "300"))
|
||||
# Define sensors here for easy extension
|
||||
self.sensors = [
|
||||
{
|
||||
"name": "Daily Steps",
|
||||
@ -89,6 +88,67 @@ class GadgetbridgeMQTTPublisher:
|
||||
"state_class": "measurement",
|
||||
"query": self.query_latest_heart_rate,
|
||||
},
|
||||
{
|
||||
"name": "Resting Heart Rate",
|
||||
"unique_id": "hr_resting",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/hr_resting",
|
||||
"unit_of_measurement": "bpm",
|
||||
"icon": "mdi:heart-pulse",
|
||||
"state_class": "measurement",
|
||||
"query": self.query_hr_resting,
|
||||
},
|
||||
{
|
||||
"name": "Max Heart Rate",
|
||||
"unique_id": "hr_max",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/hr_max",
|
||||
"unit_of_measurement": "bpm",
|
||||
"icon": "mdi:heart-pulse",
|
||||
"state_class": "measurement",
|
||||
"query": self.query_hr_max,
|
||||
},
|
||||
{
|
||||
"name": "Average Heart Rate",
|
||||
"unique_id": "hr_avg",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/hr_avg",
|
||||
"unit_of_measurement": "bpm",
|
||||
"icon": "mdi:heart-pulse",
|
||||
"state_class": "measurement",
|
||||
"query": self.query_hr_avg,
|
||||
},
|
||||
{
|
||||
"name": "Calories",
|
||||
"unique_id": "calories",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/calories",
|
||||
"unit_of_measurement": "kcal",
|
||||
"icon": "mdi:fire",
|
||||
"state_class": "total_increasing",
|
||||
"query": self.query_calories,
|
||||
},
|
||||
{
|
||||
"name": "Wakeup Time",
|
||||
"unique_id": "wakeup_time",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/wakeup_time",
|
||||
"icon": "mdi:weather-sunset-up",
|
||||
"device_class": "timestamp",
|
||||
"query": self.query_wakeup_time,
|
||||
},
|
||||
{
|
||||
"name": "Is Awake",
|
||||
"unique_id": "is_awake",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/is_awake",
|
||||
"icon": "mdi:power-sleep",
|
||||
"device_class": "enum",
|
||||
"query": self.query_is_awake,
|
||||
},
|
||||
{
|
||||
"name": "Total Sleep Duration",
|
||||
"unique_id": "total_sleep_duration",
|
||||
"state_topic": f"gadgetbridge/{self.device_name}/total_sleep_duration",
|
||||
"unit_of_measurement": "h",
|
||||
"icon": "mdi:sleep",
|
||||
"state_class": "measurement",
|
||||
"query": self.query_total_sleep_duration,
|
||||
},
|
||||
]
|
||||
|
||||
def setup_logging(self):
|
||||
@ -212,6 +272,57 @@ class GadgetbridgeMQTTPublisher:
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
def query_hr_resting(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT HR_RESTING FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
def query_hr_max(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT HR_MAX FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
def query_hr_avg(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT HR_AVG FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
def query_calories(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT CALORIES FROM XIAOMI_DAILY_SUMMARY_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
def query_wakeup_time(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT WAKEUP_TIME FROM XIAOMI_SLEEP_TIME_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return datetime.fromtimestamp(row[0]).isoformat() if row and row[0] else None
|
||||
|
||||
def query_is_awake(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT IS_AWAKE FROM XIAOMI_SLEEP_TIME_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
# Return as boolean or string for Home Assistant
|
||||
return bool(row[0]) if row else None
|
||||
|
||||
def query_total_sleep_duration(self, cursor) -> Any:
|
||||
cursor.execute(
|
||||
"SELECT TOTAL_DURATION FROM XIAOMI_SLEEP_TIME_SAMPLE ORDER BY TIMESTAMP DESC LIMIT 1"
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
# 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 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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user