Added CPU, storage and RAM sensors. Added custom sensor.

This commit is contained in:
Mark Qvist 2024-06-01 11:11:29 +02:00
parent 3bf13d7435
commit 4c9de8b184
2 changed files with 744 additions and 323 deletions

View File

@ -53,6 +53,7 @@ class Telemeter():
Sensor.SID_PROXIMITY: Proximity, Sensor.SID_POWER_CONSUMPTION: PowerConsumption, Sensor.SID_PROXIMITY: Proximity, Sensor.SID_POWER_CONSUMPTION: PowerConsumption,
Sensor.SID_POWER_PRODUCTION: PowerProduction, Sensor.SID_PROCESSOR: Processor, Sensor.SID_POWER_PRODUCTION: PowerProduction, Sensor.SID_PROCESSOR: Processor,
Sensor.SID_RAM: RandomAccessMemory, Sensor.SID_NVM: NonVolatileMemory, Sensor.SID_RAM: RandomAccessMemory, Sensor.SID_NVM: NonVolatileMemory,
Sensor.SID_CUSTOM: Custom,
} }
self.available = { self.available = {
"time": Sensor.SID_TIME, "time": Sensor.SID_TIME,
@ -65,6 +66,7 @@ class Telemeter():
"acceleration": Sensor.SID_ACCELERATION, "proximity": Sensor.SID_PROXIMITY, "acceleration": Sensor.SID_ACCELERATION, "proximity": Sensor.SID_PROXIMITY,
"power_consumption": Sensor.SID_POWER_CONSUMPTION, "power_production": Sensor.SID_POWER_PRODUCTION, "power_consumption": Sensor.SID_POWER_CONSUMPTION, "power_production": Sensor.SID_POWER_PRODUCTION,
"processor": Sensor.SID_PROCESSOR, "ram": Sensor.SID_RAM, "nvm": Sensor.SID_NVM, "processor": Sensor.SID_PROCESSOR, "ram": Sensor.SID_RAM, "nvm": Sensor.SID_NVM,
"custom": Sensor.SID_CUSTOM
} }
self.from_packed = from_packed self.from_packed = from_packed
self.sensors = {} self.sensors = {}
@ -191,6 +193,7 @@ class Sensor():
SID_PROCESSOR = 0x13 SID_PROCESSOR = 0x13
SID_RAM = 0x14 SID_RAM = 0x14
SID_NVM = 0x15 SID_NVM = 0x15
SID_CUSTOM = 0xff
def __init__(self, sid = None, stale_time = None): def __init__(self, sid = None, stale_time = None):
self._telemeter = None self._telemeter = None
@ -1339,7 +1342,7 @@ class PowerConsumption(Sensor):
def teardown_sensor(self): def teardown_sensor(self):
self.data = None self.data = None
def update_consumer(self, power, type_label=None): def update_consumer(self, power, type_label=None, custom_icon=None):
if type_label == None: if type_label == None:
type_label = 0x00 type_label = 0x00
elif type(type_label) != str: elif type(type_label) != str:
@ -1348,7 +1351,7 @@ class PowerConsumption(Sensor):
if self.data == None: if self.data == None:
self.data = {} self.data = {}
self.data[type_label] = power self.data[type_label] = [power, custom_icon]
return True return True
def remove_consumer(self, type_label=None): def remove_consumer(self, type_label=None):
@ -1397,7 +1400,7 @@ class PowerConsumption(Sensor):
label = "Power consumption" label = "Power consumption"
else: else:
label = type_label label = type_label
consumers.append({"label": label, "w": self.data[type_label]}) consumers.append({"label": label, "w": self.data[type_label][0], "custom_icon": self.data[type_label][1]})
rendered = { rendered = {
"icon": "power-plug-outline", "icon": "power-plug-outline",
@ -1420,7 +1423,7 @@ class PowerProduction(Sensor):
def teardown_sensor(self): def teardown_sensor(self):
self.data = None self.data = None
def update_producer(self, power, type_label=None): def update_producer(self, power, type_label=None, custom_icon=None):
if type_label == None: if type_label == None:
type_label = 0x00 type_label = 0x00
elif type(type_label) != str: elif type(type_label) != str:
@ -1429,7 +1432,7 @@ class PowerProduction(Sensor):
if self.data == None: if self.data == None:
self.data = {} self.data = {}
self.data[type_label] = power self.data[type_label] = [power, custom_icon]
return True return True
def remove_producer(self, type_label=None): def remove_producer(self, type_label=None):
@ -1478,7 +1481,7 @@ class PowerProduction(Sensor):
label = "Power Production" label = "Power Production"
else: else:
label = type_label label = type_label
producers.append({"label": label, "w": self.data[type_label]}) producers.append({"label": label, "w": self.data[type_label][0], "custom_icon": self.data[type_label][1]})
rendered = { rendered = {
"icon": "lightning-bolt", "icon": "lightning-bolt",
@ -1488,12 +1491,347 @@ class PowerProduction(Sensor):
return rendered return rendered
# TODO: Implement
class Processor(Sensor): class Processor(Sensor):
SID = Sensor.SID_PROCESSOR
STALE_TIME = 5
def __init__(self):
super().__init__(type(self).SID, type(self).STALE_TIME)
def setup_sensor(self):
self.update_data()
def teardown_sensor(self):
self.data = None
def update_entry(self, current_load=0, load_avgs=None, clock=None, type_label=None):
if type_label == None:
type_label = 0x00
elif type(type_label) != str:
return False
if self.data == None:
self.data = {}
self.data[type_label] = [current_load, load_avgs, clock]
return True
def remove_entry(self, type_label=None):
if type_label == None:
type_label = 0x00
if type_label in self.data:
self.data.pop(type_label)
return True
return False
def update_data(self):
pass pass
def pack(self):
d = self.data
if d == None:
return None
else:
packed = []
for type_label in self.data:
packed.append([type_label, self.data[type_label]])
return packed
def unpack(self, packed):
try:
if packed == None:
return None
else:
unpacked = {}
for entry in packed:
unpacked[entry[0]] = entry[1]
return unpacked
except:
return None
def render(self, relative_to=None):
if self.data == None:
return None
entries = []
for type_label in self.data:
if type_label == 0x00:
label = "Storage"
else:
label = type_label
entries.append({
"label": label,
"current_load": self.data[type_label][0],
"load_avgs": self.data[type_label][1],
"clock": self.data[type_label][2],
})
rendered = {
"icon": "chip",
"name": "Processor",
"values": entries,
}
return rendered
class RandomAccessMemory(Sensor): class RandomAccessMemory(Sensor):
SID = Sensor.SID_RAM
STALE_TIME = 5
def __init__(self):
super().__init__(type(self).SID, type(self).STALE_TIME)
def setup_sensor(self):
self.update_data()
def teardown_sensor(self):
self.data = None
def update_entry(self, capacity=0, used=0, type_label=None):
if type_label == None:
type_label = 0x00
elif type(type_label) != str:
return False
if self.data == None:
self.data = {}
self.data[type_label] = [capacity, used]
return True
def remove_entry(self, type_label=None):
if type_label == None:
type_label = 0x00
if type_label in self.data:
self.data.pop(type_label)
return True
return False
def update_data(self):
pass pass
def pack(self):
d = self.data
if d == None:
return None
else:
packed = []
for type_label in self.data:
packed.append([type_label, self.data[type_label]])
return packed
def unpack(self, packed):
try:
if packed == None:
return None
else:
unpacked = {}
for entry in packed:
unpacked[entry[0]] = entry[1]
return unpacked
except:
return None
def render(self, relative_to=None):
if self.data == None:
return None
entries = []
for type_label in self.data:
if type_label == 0x00:
label = "Storage"
else:
label = type_label
entries.append({
"label": label,
"capacity": self.data[type_label][0],
"used": self.data[type_label][1],
"free": self.data[type_label][0]-self.data[type_label][1],
"percent": (self.data[type_label][1]/self.data[type_label][0])*100,
})
rendered = {
"icon": "memory",
"name": "Random Access Memory",
"values": entries,
}
return rendered
class NonVolatileMemory(Sensor): class NonVolatileMemory(Sensor):
SID = Sensor.SID_NVM
STALE_TIME = 5
def __init__(self):
super().__init__(type(self).SID, type(self).STALE_TIME)
def setup_sensor(self):
self.update_data()
def teardown_sensor(self):
self.data = None
def update_entry(self, capacity=0, used=0, type_label=None):
if type_label == None:
type_label = 0x00
elif type(type_label) != str:
return False
if self.data == None:
self.data = {}
self.data[type_label] = [capacity, used]
return True
def remove_entry(self, type_label=None):
if type_label == None:
type_label = 0x00
if type_label in self.data:
self.data.pop(type_label)
return True
return False
def update_data(self):
pass pass
def pack(self):
d = self.data
if d == None:
return None
else:
packed = []
for type_label in self.data:
packed.append([type_label, self.data[type_label]])
return packed
def unpack(self, packed):
try:
if packed == None:
return None
else:
unpacked = {}
for entry in packed:
unpacked[entry[0]] = entry[1]
return unpacked
except:
return None
def render(self, relative_to=None):
if self.data == None:
return None
entries = []
for type_label in self.data:
if type_label == 0x00:
label = "Storage"
else:
label = type_label
entries.append({
"label": label,
"capacity": self.data[type_label][0],
"used": self.data[type_label][1],
"free": self.data[type_label][0]-self.data[type_label][1],
"percent": (self.data[type_label][1]/self.data[type_label][0])*100,
})
rendered = {
"icon": "harddisk",
"name": "Non-Volatile Memory",
"values": entries,
}
return rendered
class Custom(Sensor):
SID = Sensor.SID_CUSTOM
STALE_TIME = 5
def __init__(self):
super().__init__(type(self).SID, type(self).STALE_TIME)
def setup_sensor(self):
self.update_data()
def teardown_sensor(self):
self.data = None
def update_entry(self, value=None, type_label=None, custom_icon=None):
if type_label == None:
type_label = 0x00
elif type(type_label) != str:
return False
if self.data == None:
self.data = {}
self.data[type_label] = [value, custom_icon]
return True
def remove_entry(self, type_label=None):
if type_label == None:
type_label = 0x00
if type_label in self.data:
self.data.pop(type_label)
return True
return False
def update_data(self):
pass
def pack(self):
d = self.data
if d == None:
return None
else:
packed = []
for type_label in self.data:
packed.append([type_label, self.data[type_label]])
return packed
def unpack(self, packed):
try:
if packed == None:
return None
else:
unpacked = {}
for entry in packed:
unpacked[entry[0]] = entry[1]
return unpacked
except:
return None
def render(self, relative_to=None):
if self.data == None:
return None
entries = []
for type_label in self.data:
if type_label == 0x00:
label = "Custom"
else:
label = type_label
entries.append({
"label": label,
"value": self.data[type_label][0],
"custom_icon": self.data[type_label][1],
})
rendered = {
"icon": "ruler",
"name": "Custom",
"values": entries,
}
return rendered

View File

@ -305,6 +305,7 @@ class RVDetails(MDRecycleView):
rendered_telemetry = [] rendered_telemetry = []
sort = { sort = {
"Information": 5,
"Physical Link": 10, "Physical Link": 10,
"Location": 20, "Location": 20,
"Ambient Light": 30, "Ambient Light": 30,
@ -312,9 +313,12 @@ class RVDetails(MDRecycleView):
"Relative Humidity": 50, "Relative Humidity": 50,
"Ambient Pressure": 60, "Ambient Pressure": 60,
"Battery": 70, "Battery": 70,
"Processor": 72,
"Random Access Memory": 74,
"Non-Volatile Memory": 76,
"Timestamp": 80, "Timestamp": 80,
"Custom": 85,
"Received": 90, "Received": 90,
"Information": 5,
} }
def pass_job(sender=None): def pass_job(sender=None):
@ -323,10 +327,12 @@ class RVDetails(MDRecycleView):
self.entries = [] self.entries = []
rendered_telemetry.sort(key=lambda s: sort[s["name"]] if s["name"] in sort else 1000) rendered_telemetry.sort(key=lambda s: sort[s["name"]] if s["name"] in sort else 1000)
for s in rendered_telemetry: for s in rendered_telemetry:
try:
extra_entries = [] extra_entries = []
release_function = pass_job release_function = pass_job
formatted_values = None formatted_values = None
name = s["name"] name = s["name"]
if name == "Timestamp": if name == "Timestamp":
ts = s["values"]["UTC"] ts = s["values"]["UTC"]
if ts != None: if ts != None:
@ -336,6 +342,7 @@ class RVDetails(MDRecycleView):
Clipboard.copy(ts_str) Clipboard.copy(ts_str)
toast("Copied to clipboard") toast("Copied to clipboard")
release_function = copy_info release_function = copy_info
elif name == "Information": elif name == "Information":
info = s["values"]["contents"] info = s["values"]["contents"]
if info != None: if info != None:
@ -346,6 +353,7 @@ class RVDetails(MDRecycleView):
release_function = copy_info release_function = copy_info
external_text = multilingual_markup(escape_markup(istr).encode("utf-8")).decode("utf-8") external_text = multilingual_markup(escape_markup(istr).encode("utf-8")).decode("utf-8")
formatted_values = f"[b]Information[/b]: {external_text}" formatted_values = f"[b]Information[/b]: {external_text}"
elif name == "Received": elif name == "Received":
formatted_values = "" formatted_values = ""
by = s["values"]["by"]; by = s["values"]["by"];
@ -390,6 +398,7 @@ class RVDetails(MDRecycleView):
cs = s["values"]["_meta"] cs = s["values"]["_meta"]
if cs != None: cs_str = f" ({cs})" if cs != None: cs_str = f" ({cs})"
if p != None: formatted_values = f"{name} [b]{p}%[/b]"+cs_str if p != None: formatted_values = f"{name} [b]{p}%[/b]"+cs_str
elif name == "Ambient Pressure": elif name == "Ambient Pressure":
p = s["values"]["mbar"] p = s["values"]["mbar"]
if p != None: formatted_values = f"{name} [b]{p} mbar[/b]" if p != None: formatted_values = f"{name} [b]{p} mbar[/b]"
@ -397,6 +406,7 @@ class RVDetails(MDRecycleView):
if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None:
d = s["deltas"][dt] d = s["deltas"][dt]
formatted_values += f" (Δ = {d} mbar)" formatted_values += f" (Δ = {d} mbar)"
elif name == "Ambient Temperature": elif name == "Ambient Temperature":
c = s["values"]["c"] c = s["values"]["c"]
if c != None: formatted_values = f"{name} [b]{c}° C[/b]" if c != None: formatted_values = f"{name} [b]{c}° C[/b]"
@ -404,6 +414,7 @@ class RVDetails(MDRecycleView):
if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None:
d = s["deltas"][dt] d = s["deltas"][dt]
formatted_values += f" (Δ = {d}° C)" formatted_values += f" (Δ = {d}° C)"
elif name == "Relative Humidity": elif name == "Relative Humidity":
r = s["values"]["percent"] r = s["values"]["percent"]
if r != None: formatted_values = f"{name} [b]{r}%[/b]" if r != None: formatted_values = f"{name} [b]{r}%[/b]"
@ -411,6 +422,7 @@ class RVDetails(MDRecycleView):
if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None:
d = s["deltas"][dt] d = s["deltas"][dt]
formatted_values += f" (Δ = {d}%)" formatted_values += f" (Δ = {d}%)"
elif name == "Physical Link": elif name == "Physical Link":
rssi = s["values"]["rssi"]; rssi_str = None rssi = s["values"]["rssi"]; rssi_str = None
snr = s["values"]["snr"]; snr_str = None snr = s["values"]["snr"]; snr_str = None
@ -424,11 +436,13 @@ class RVDetails(MDRecycleView):
if q != None or rssi != None: snr_str = ", "+snr_str if q != None or rssi != None: snr_str = ", "+snr_str
if q_str or rssi_str or snr_str: if q_str or rssi_str or snr_str:
formatted_values = q_str+rssi_str+snr_str formatted_values = q_str+rssi_str+snr_str
elif name == "Power Consumption": elif name == "Power Consumption":
cs = s["values"] cs = s["values"]
if cs != None: if cs != None:
for c in cs: for c in cs:
label = c["label"] label = c["label"]
cicon = c["custom_icon"]
watts = c["w"] watts = c["w"]
prefix = "" prefix = ""
if watts < 1/1e6: if watts < 1/1e6:
@ -456,15 +470,21 @@ class RVDetails(MDRecycleView):
watts /= 1e3 watts /= 1e3
prefix = "K" prefix = "K"
if cicon:
set_icon = cicon
else:
set_icon = s["icon"]
watts = round(watts, 2) watts = round(watts, 2)
p_text = f"{label} [b]{watts} {prefix}W[/b]" p_text = f"{label} [b]{watts} {prefix}W[/b]"
extra_entries.append({"icon": s["icon"], "text": p_text}) extra_entries.append({"icon": set_icon, "text": p_text})
elif name == "Power Production": elif name == "Power Production":
cs = s["values"] cs = s["values"]
if cs != None: if cs != None:
for c in cs: for c in cs:
label = c["label"] label = c["label"]
cicon = c["custom_icon"]
watts = c["w"] watts = c["w"]
prefix = "" prefix = ""
if watts < 1/1e6: if watts < 1/1e6:
@ -492,9 +512,68 @@ class RVDetails(MDRecycleView):
watts /= 1e3 watts /= 1e3
prefix = "K" prefix = "K"
if cicon:
set_icon = cicon
else:
set_icon = s["icon"]
watts = round(watts, 2) watts = round(watts, 2)
p_text = f"{label} [b]{watts} {prefix}W[/b]" p_text = f"{label} [b]{watts} {prefix}W[/b]"
extra_entries.append({"icon": s["icon"], "text": p_text}) extra_entries.append({"icon": set_icon, "text": p_text})
elif name == "Custom":
cs = s["values"]
if cs != None:
for c in cs:
label = c["label"]
cicon = c["custom_icon"]
value = str(c["value"])
set_icon = cicon if cicon else s["icon"]
e_text = f"{label} [b]{value}[/b]"
extra_entries.append({"icon": set_icon, "text": e_text})
elif name == "Processor":
cs = s["values"]
if cs != None:
for c in cs:
label = c["label"]
load = c["current_load"]
avgs = c["load_avgs"]
clock = c["clock"]
pct = round(load*100, 1)
avgs_str = f", averages are [b]{round(avgs[0],2)}[/b], [b]{round(avgs[1],2)}[/b], [b]{round(avgs[2],2)}[/b]" if avgs != None and len(avgs) == 3 else ""
clock_str = " at [b]"+RNS.prettyfrequency(clock)+"[/b]" if clock != None else ""
e_text = f"Using [b]{pct}%[/b] of {label}{clock_str}{avgs_str}"
e_text = f"{label} use is [b]{pct}%[/b]{clock_str}{avgs_str}"
extra_entries.append({"icon": s["icon"], "text": e_text})
elif name == "Non-Volatile Memory":
cs = s["values"]
if cs != None:
for c in cs:
label = c["label"]
cap = RNS.prettysize(c["capacity"])
use = RNS.prettysize(c["used"])
free = RNS.prettysize(c["free"])
pct = round(c["percent"], 1)
e_text = f"{label} use is [b]{use}[/b] ([b]{pct}%[/b]) of [b]{cap}[/b], with [b]{free}[/b] free"
extra_entries.append({"icon": s["icon"], "text": e_text})
elif name == "Random Access Memory":
cs = s["values"]
if cs != None:
for c in cs:
label = c["label"]
cap = RNS.prettysize(c["capacity"])
use = RNS.prettysize(c["used"])
free = RNS.prettysize(c["free"])
pct = round(c["percent"], 1)
e_text = f"{label} use is [b]{use}[/b] ([b]{pct}%[/b]) of [b]{cap}[/b], with [b]{free}[/b] free"
extra_entries.append({"icon": s["icon"], "text": e_text})
elif name == "Location": elif name == "Location":
lat = s["values"]["latitude"] lat = s["values"]["latitude"]
@ -645,6 +724,10 @@ class RVDetails(MDRecycleView):
for extra in extra_entries: for extra in extra_entries:
self.entries.append(extra) self.entries.append(extra)
except Exception as e:
RNS.log("An error ocurred while displaying telemetry for object", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
try: try:
nh = RNS.Transport.hops_to(self.delegate.object_hash) nh = RNS.Transport.hops_to(self.delegate.object_hash)