From aa3c10fab8c9332b5b836221ac71f84ac96b5626 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Fri, 15 Aug 2025 17:35:56 +0000 Subject: [PATCH] feat: Add P2 Pro scale sensor and secure history sharing --- main.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 982168f..4dff2df 100644 --- a/main.py +++ b/main.py @@ -23,6 +23,7 @@ import aiomqtt from datetime import datetime, timedelta import pytz TIMEZONE = pytz.timezone('America/Edmonton') +import hashlib app = web.Application() http_session = None @@ -104,6 +105,13 @@ class Sensor(): return str(before) != str(after) + def check_cooldown(self): + if self.last_update and self.skip_cooldown and time.time() - self.last_update < self.skip_cooldown: + # ignore data point + return True + else: + return False + def log(self): if not self.value: return @@ -112,7 +120,7 @@ class Sensor(): logging.debug('Skipping writing %s, data hasn\'t changed', self) return - if self.last_update and self.skip_cooldown and time.time() - self.last_update < self.skip_cooldown: + if self.check_cooldown(): logging.debug('Skipping writing %s, cooldown limit', self) return @@ -261,6 +269,23 @@ class SoilSensor(Sensor): except TypeError: pass +class P2ProScaleSensor(Sensor): + type_ = 'scale' + skip_cooldown = 60.0 * 60 * 18 # 18 hours + + def check_cooldown(self): + if 'weight' not in self.value: + return False + + skip = super().check_cooldown() + + if skip: + controller_message('Cooldown skipping scale weight: ' + str(self.value['weight'])) + + return skip + + + class SolarSensor(Sensor): type_ = 'solar' @@ -326,7 +351,7 @@ async def poll_sensors(): await sensor.poll() sensor.check_update() - await asyncio.sleep(1) + await asyncio.sleep(5) async def process_data(data): sensor = sensors.get(data['id']) @@ -388,6 +413,11 @@ async def owntracks(request): return web.Response() + +def share_sha256(measurement, share_start, share_end, api_key): + s = f'{measurement}-{share_start}-{share_end}-{api_key}' + return hashlib.sha256(s.encode()).hexdigest() + async def history(request): api_key = request.rel_url.query.get('api_key', '') authed = api_key == settings.SENSORS_API_KEY @@ -395,6 +425,14 @@ async def history(request): measurement = request.match_info.get('measurement') name = request.match_info.get('name') + share_start = request.rel_url.query.get('shareStart', '') + share_end = request.rel_url.query.get('shareEnd', '') + share_sig = request.rel_url.query.get('shareSig', '') + + share_authed = share_sig == share_sha256(measurement, share_start, share_end, settings.SENSORS_API_KEY) + authed = authed or share_authed + + if not authed and measurement in ['owntracks', 'sleep']: return web.json_response([]) @@ -443,6 +481,13 @@ async def history(request): start = int(start.timestamp()) end = int(end.timestamp()) + if share_authed: + if start < int(share_start): + start = int(share_start) + if end > int(share_end): + end = int(share_end) + + if measurement == 'temperature': client = sensors_client q = 'select mean("temperature_C") as temperature_C, mean("humidity") as humidity from temperature where "name" = \'{}\' and time >= {}s and time < {}s group by time({}) fill(linear)'.format(name, start, end, window) @@ -553,6 +598,9 @@ if __name__ == '__main__': sensors.add(SleepSensor('sleep1', 'Bedroom')) sensors.add(SolarSensor('solar', 'Solar')) sensors.add(SoilSensor('soil1', 'Dumb Cane')) + sensors.add(SoilSensor('soil2', 'Kitchen Pothos')) + sensors.add(SoilSensor('soil3', 'Dracaena')) + sensors.add(P2ProScaleSensor('scale1', 'Master Bathroom')) sensors.add(QotMotionSensor('qot_dc3c', 'Bedroom')) sensors.add(QotMotionSensor('qot_88c3', 'Lower Stairs Hi'))