From 5def6199306964dffa18ae3b8f225458a1617507 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 22 Jan 2025 22:30:27 +0100 Subject: [PATCH] Added MQTT renderers to Telemeter --- sbapp/sideband/sense.py | 438 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 433 insertions(+), 5 deletions(-) diff --git a/sbapp/sideband/sense.py b/sbapp/sideband/sense.py index 82b7ddb..002e6c7 100644 --- a/sbapp/sideband/sense.py +++ b/sbapp/sideband/sense.py @@ -33,6 +33,7 @@ class Telemeter(): s.data = s.unpack(p[sid]) s.synthesized = True s.active = True + s._telemeter = t t.sensors[name] = s return t @@ -53,8 +54,8 @@ class Telemeter(): Sensor.SID_PROXIMITY: Proximity, Sensor.SID_POWER_CONSUMPTION: PowerConsumption, Sensor.SID_POWER_PRODUCTION: PowerProduction, Sensor.SID_PROCESSOR: Processor, Sensor.SID_RAM: RandomAccessMemory, Sensor.SID_NVM: NonVolatileMemory, - Sensor.SID_CUSTOM: Custom, Sensor.SID_TANK: Tank, Sensor.SID_FUEL: Fuel, - } + Sensor.SID_CUSTOM: Custom, Sensor.SID_TANK: Tank, Sensor.SID_FUEL: Fuel} + self.available = { "time": Sensor.SID_TIME, "information": Sensor.SID_INFORMATION, "received": Sensor.SID_RECEIVED, @@ -66,8 +67,12 @@ class Telemeter(): "acceleration": Sensor.SID_ACCELERATION, "proximity": Sensor.SID_PROXIMITY, "power_consumption": Sensor.SID_POWER_CONSUMPTION, "power_production": Sensor.SID_POWER_PRODUCTION, "processor": Sensor.SID_PROCESSOR, "ram": Sensor.SID_RAM, "nvm": Sensor.SID_NVM, - "custom": Sensor.SID_CUSTOM, "tank": Sensor.SID_TANK, "fuel": Sensor.SID_FUEL - } + "custom": Sensor.SID_CUSTOM, "tank": Sensor.SID_TANK, "fuel": Sensor.SID_FUEL} + + self.names = {} + for name in self.available: + self.names[self.available[name]] = name + self.from_packed = from_packed self.sensors = {} if not self.from_packed: @@ -77,6 +82,12 @@ class Telemeter(): self.android_context = android_context self.service = service + def get_name(self, sid): + if sid in self.names: + return self.names[sid] + else: + return None + def synthesize(self, sensor): if sensor in self.available: if not sensor in self.sensors: @@ -268,6 +279,15 @@ class Sensor(): def render(self, relative_to=None): return None + def render_mqtt(self, relative_to=None): + return None + + def name(self): + if self._telemeter != None: + return self._telemeter.get_name(self._sid) + else: + return None + def check_permission(self, permission): if self._telemeter != None: return self._telemeter.check_permission(permission) @@ -319,6 +339,19 @@ class Time(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + topic = self.name() + rendered = { + f"{topic}/name": "Timestamp", + f"{topic}/icon": "clock-time-ten-outline", + f"{topic}/utc": self.data["utc"], + } + else: + rendered = None + + return rendered + class Information(Sensor): SID = Sensor.SID_INFORMATION STALE_TIME = 5 @@ -364,6 +397,19 @@ class Information(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + topic = self.name() + rendered = { + f"{topic}/name": "Information", + f"{topic}/icon": "information-variant", + f"{topic}/text": self.data["contents"], + } + else: + rendered = None + + return rendered + class Received(Sensor): SID = Sensor.SID_RECEIVED STALE_TIME = 5 @@ -430,6 +476,22 @@ class Received(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + topic = self.name() + rendered = { + f"{topic}/name": "Received", + f"{topic}/icon": "arrow-down-bold-hexagon-outline", + f"{topic}/by": mqtt_desthash(self.data["by"]), + f"{topic}/via": mqtt_desthash(self.data["via"]), + f"{topic}/distance/geodesic": self.data["distance"]["geodesic"], + f"{topic}/distance/euclidian": self.data["distance"]["euclidian"], + } + else: + rendered = None + + return rendered + class Battery(Sensor): SID = Sensor.SID_BATTERY STALE_TIME = 10 @@ -555,6 +617,22 @@ class Battery(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render() + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/percent": r["values"]["percent"], + f"{topic}/temperature": r["values"]["temperature"], + f"{topic}/meta": r["values"]["_meta"], + } + else: + rendered = None + + return rendered + class Pressure(Sensor): SID = Sensor.SID_PRESSURE STALE_TIME = 5 @@ -621,6 +699,20 @@ class Pressure(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render() + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/mbar": r["values"]["mbar"], + } + else: + rendered = None + + return rendered + class Location(Sensor): SID = Sensor.SID_LOCATION @@ -876,6 +968,45 @@ class Location(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/latitude": r["values"]["latitude"], + f"{topic}/longitude": r["values"]["longitude"], + f"{topic}/altitude": r["values"]["altitude"], + f"{topic}/speed": r["values"]["speed"], + f"{topic}/heading": r["values"]["heading"], + f"{topic}/accuracy": r["values"]["accuracy"], + f"{topic}/updated": r["values"]["updated"], + f"{topic}/angle_to_horizon": r["values"]["angle_to_horizon"], + f"{topic}/radio_horizon": r["values"]["radio_horizon"]} + if "distance" in r: + rendered[f"{topic}/distance/euclidian"] = r["distance"]["euclidian"] + rendered[f"{topic}/distance/orthodromic"] = r["distance"]["orthodromic"] + rendered[f"{topic}/distance/vertical"] = r["distance"]["vertical"] + if "azalt" in r: + rendered[f"{topic}/azalt/azimuth"] = r["azalt"]["azimuth"] + rendered[f"{topic}/azalt/altitude"] = r["azalt"]["altitude"] + rendered[f"{topic}/azalt/above_horizon"] = r["azalt"]["above_horizon"] + rendered[f"{topic}/azalt/altitude_delta"] = r["azalt"]["altitude_delta"] + rendered[f"{topic}/azalt/local_angle_to_horizon"] = r["azalt"]["local_angle_to_horizon"] + if "radio_horizon" in r: + rendered[f"{topic}/radio_horizon/object_range"] = r["radio_horizon"]["object_range"] + rendered[f"{topic}/radio_horizon/related_range"] = r["radio_horizon"]["related_range"] + rendered[f"{topic}/radio_horizon/combined_range"] = r["radio_horizon"]["combined_range"] + rendered[f"{topic}/radio_horizon/within_range"] = r["radio_horizon"]["within_range"] + rendered[f"{topic}/radio_horizon/geodesic_distance"] = r["radio_horizon"]["geodesic_distance"] + rendered[f"{topic}/radio_horizon/antenna_distance"] = r["radio_horizon"]["antenna_distance"] + + else: + rendered = None + + return rendered + class PhysicalLink(Sensor): SID = Sensor.SID_PHYSICAL_LINK STALE_TIME = 5 @@ -932,6 +1063,22 @@ class PhysicalLink(Sensor): if q > 90: rendered["icon"] = "network-strength-4" return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/rssi": r["values"]["rssi"], + f"{topic}/snr": r["values"]["snr"], + f"{topic}/q": r["values"]["q"], + } + else: + rendered = None + + return rendered + class Temperature(Sensor): SID = Sensor.SID_TEMPERATURE STALE_TIME = 5 @@ -988,6 +1135,20 @@ class Temperature(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/c": r["values"]["c"], + } + else: + rendered = None + + return rendered + class Humidity(Sensor): SID = Sensor.SID_HUMIDITY STALE_TIME = 5 @@ -1044,6 +1205,20 @@ class Humidity(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/percent_relative": r["values"]["percent"], + } + else: + rendered = None + + return rendered + class MagneticField(Sensor): SID = Sensor.SID_MAGNETIC_FIELD STALE_TIME = 1 @@ -1101,6 +1276,22 @@ class MagneticField(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/x": r["values"]["x"], + f"{topic}/y": r["values"]["y"], + f"{topic}/z": r["values"]["z"], + } + else: + rendered = None + + return rendered + class AmbientLight(Sensor): SID = Sensor.SID_AMBIENT_LIGHT STALE_TIME = 1 @@ -1167,6 +1358,22 @@ class AmbientLight(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/lux": r["values"]["lux"], + } + if "deltas" in r: + rendered[f"{topic}/deltas/lux"] = r["deltas"]["lux"] + else: + rendered = None + + return rendered + class Gravity(Sensor): SID = Sensor.SID_GRAVITY STALE_TIME = 1 @@ -1224,6 +1431,22 @@ class Gravity(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/x": r["values"]["x"], + f"{topic}/y": r["values"]["y"], + f"{topic}/z": r["values"]["z"], + } + else: + rendered = None + + return rendered + class AngularVelocity(Sensor): SID = Sensor.SID_ANGULAR_VELOCITY STALE_TIME = 1 @@ -1281,6 +1504,22 @@ class AngularVelocity(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/x": r["values"]["x"], + f"{topic}/y": r["values"]["y"], + f"{topic}/z": r["values"]["z"], + } + else: + rendered = None + + return rendered + class Acceleration(Sensor): SID = Sensor.SID_ACCELERATION STALE_TIME = 1 @@ -1327,6 +1566,33 @@ class Acceleration(Sensor): except: return None + def render(self, relative_to=None): + if self.data == None: + return None + + rendered = { + "icon": "arrow-right-thick", + "name": "Acceleration", + "values": { "x": self.data["x"], "y": self.data["y"], "z": self.data["z"] }, + } + return rendered + + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/x": r["values"]["x"], + f"{topic}/y": r["values"]["y"], + f"{topic}/z": r["values"]["z"], + } + else: + rendered = None + + return rendered + class Proximity(Sensor): SID = Sensor.SID_PROXIMITY STALE_TIME = 1 @@ -1383,6 +1649,20 @@ class Proximity(Sensor): } return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + f"{topic}/triggered": r["values"]["triggered"], + } + else: + rendered = None + + return rendered + class PowerConsumption(Sensor): SID = Sensor.SID_POWER_CONSUMPTION STALE_TIME = 5 @@ -1464,6 +1744,22 @@ class PowerConsumption(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for consumer in r["values"]: + rendered[f"{topic}/{consumer["label"]}/w"] = consumer["w"] + rendered[f"{topic}/{consumer["label"]}/icon"] = consumer["custom_icon"] + else: + rendered = None + + return rendered + class PowerProduction(Sensor): SID = Sensor.SID_POWER_PRODUCTION STALE_TIME = 5 @@ -1545,6 +1841,22 @@ class PowerProduction(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for producer in r["values"]: + rendered[f"{topic}/{producer["label"]}/w"] = producer["w"] + rendered[f"{topic}/{producer["label"]}/icon"] = producer["custom_icon"] + else: + rendered = None + + return rendered + class Processor(Sensor): SID = Sensor.SID_PROCESSOR STALE_TIME = 5 @@ -1631,6 +1943,25 @@ class Processor(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for cpu in r["values"]: + rendered[f"{topic}/{cpu["label"]}/current_load"] = cpu["current_load"] + rendered[f"{topic}/{cpu["label"]}/avgs/1m"] = cpu["load_avgs"][0] + rendered[f"{topic}/{cpu["label"]}/avgs/5m"] = cpu["load_avgs"][1] + rendered[f"{topic}/{cpu["label"]}/avgs/15m"] = cpu["load_avgs"][2] + rendered[f"{topic}/{cpu["label"]}/clock"] = cpu["clock"] + else: + rendered = None + + return rendered + class RandomAccessMemory(Sensor): SID = Sensor.SID_RAM STALE_TIME = 5 @@ -1718,6 +2049,24 @@ class RandomAccessMemory(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for ram in r["values"]: + rendered[f"{topic}/{ram["label"]}/capacity"] = ram["capacity"] + rendered[f"{topic}/{ram["label"]}/used"] = ram["used"] + rendered[f"{topic}/{ram["label"]}/free"] = ram["free"] + rendered[f"{topic}/{ram["label"]}/percent"] = ram["percent"] + else: + rendered = None + + return rendered + class NonVolatileMemory(Sensor): SID = Sensor.SID_NVM STALE_TIME = 5 @@ -1805,6 +2154,24 @@ class NonVolatileMemory(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for nvm in r["values"]: + rendered[f"{topic}/{nvm["label"]}/capacity"] = nvm["capacity"] + rendered[f"{topic}/{nvm["label"]}/used"] = nvm["used"] + rendered[f"{topic}/{nvm["label"]}/free"] = nvm["free"] + rendered[f"{topic}/{nvm["label"]}/percent"] = nvm["percent"] + else: + rendered = None + + return rendered + class Custom(Sensor): SID = Sensor.SID_CUSTOM STALE_TIME = 5 @@ -1890,6 +2257,21 @@ class Custom(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for custom in r["values"]: + rendered[f"{topic}/{custom["label"]}/value"] = custom["value"] + rendered[f"{topic}/{custom["label"]}/icon"] = custom["custom_icon"] + else: + rendered = None + + return rendered class Tank(Sensor): SID = Sensor.SID_TANK @@ -1984,6 +2366,26 @@ class Tank(Sensor): return rendered + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for tank in r["values"]: + rendered[f"{topic}/{tank["label"]}/unit"] = tank["unit"] + rendered[f"{topic}/{tank["label"]}/capacity"] = tank["capacity"] + rendered[f"{topic}/{tank["label"]}/level"] = tank["level"] + rendered[f"{topic}/{tank["label"]}/free"] = tank["free"] + rendered[f"{topic}/{tank["label"]}/percent"] = tank["percent"] + rendered[f"{topic}/{tank["label"]}/icon"] = tank["custom_icon"] + else: + rendered = None + + return rendered + class Fuel(Sensor): SID = Sensor.SID_FUEL STALE_TIME = 5 @@ -2075,4 +2477,30 @@ class Fuel(Sensor): "values": entries, } - return rendered \ No newline at end of file + return rendered + + def render_mqtt(self, relative_to=None): + if self.data != None: + r = self.render(relative_to=relative_to) + topic = self.name() + rendered = { + f"{topic}/name": r["name"], + f"{topic}/icon": r["icon"], + } + for tank in r["values"]: + rendered[f"{topic}/{tank["label"]}/unit"] = tank["unit"] + rendered[f"{topic}/{tank["label"]}/capacity"] = tank["capacity"] + rendered[f"{topic}/{tank["label"]}/level"] = tank["level"] + rendered[f"{topic}/{tank["label"]}/free"] = tank["free"] + rendered[f"{topic}/{tank["label"]}/percent"] = tank["percent"] + rendered[f"{topic}/{tank["label"]}/icon"] = tank["custom_icon"] + else: + rendered = None + + return rendered + +def mqtt_desthash(desthash): + if type(desthash) == bytes: + return RNS.prettyhexrep(desthash) + else: + return None \ No newline at end of file