From cc60f3ebc0fbc55966e90a53ad9343e923019b72 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 12:09:41 -0700 Subject: [PATCH] begin support for multiple power meter types --- .vscode/launch.json | 2 +- meshtastic/__main__.py | 18 +++++--- meshtastic/powermon/__init__.py | 1 + meshtastic/powermon/riden.py | 58 +++++++++++++++++++++++++ meshtastic/slog/__init__.py | 0 meshtastic/{ => slog}/power_mon.py | 68 +++++------------------------- 6 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 meshtastic/powermon/__init__.py create mode 100644 meshtastic/powermon/riden.py create mode 100644 meshtastic/slog/__init__.py rename meshtastic/{ => slog}/power_mon.py (57%) diff --git a/.vscode/launch.json b/.vscode/launch.json index b093786..3da5923 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,7 +172,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-mon", "/dev/ttyUSB1", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--power-riden", "/dev/ttyUSB0", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 3db80ba..3593edf 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -21,7 +21,8 @@ from meshtastic import channel_pb2, config_pb2, portnums_pb2, remote_hardware, B from meshtastic.version import get_active_version from meshtastic.ble_interface import BLEInterface from meshtastic.mesh_interface import MeshInterface -from meshtastic.power_mon import PowerMonClient +from meshtastic.powermon import RidenPowerSupply +from meshtastic.slog.power_mon import PowerMonClient def onReceive(packet, interface): """Callback invoked when a packet arrives""" @@ -1090,8 +1091,9 @@ def common(): # We assume client is fully connected now onConnected(client) - if args.power_mon: - PowerMonClient(args.power_mon, client) + if args.power_riden: + meter = RidenPowerSupply(args.power_riden) + PowerMonClient(meter, client) have_tunnel = platform.system() == "Linux" @@ -1506,8 +1508,14 @@ def initParser(): ) group.add_argument( - "--power-mon", - help="Capture any power monitor records. You must use --power-mon /dev/ttyUSBxxx to specify which port the power supply is on", + "--power-riden", + help="Talk to a Riden power-supply. You must specify the device path, i.e. /dev/ttyUSBxxx", + ) + + group.add_argument( + "--power-ppk2", + help="Talk to a Nordic Power Profiler Kit 2", + action="store_true", ) group.add_argument( diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py new file mode 100644 index 0000000..d83d496 --- /dev/null +++ b/meshtastic/powermon/__init__.py @@ -0,0 +1 @@ +from .riden import * \ No newline at end of file diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py new file mode 100644 index 0000000..41b82cd --- /dev/null +++ b/meshtastic/powermon/riden.py @@ -0,0 +1,58 @@ +"""code logging power consumption of meshtastic devices.""" + +import logging + +from datetime import datetime + +from riden import Riden + +class PowerMeter: + """Abstract class for power meters.""" + + def getWattHour(self) -> float: + """Get the current watt-hour reading.""" + + + +class PowerSupply(PowerMeter): + """Abstract class for power supplies.""" + + def setMaxCurrent(self, i: float): + """Set the maximum current the supply will provide.""" + + def powerOn(self, v: float): + """Turn on the power supply.""" + + + +class RidenPowerSupply(PowerSupply): + """Interface for talking to programmable bench-top power supplies. + Currently only the Riden supplies are supported (RD6006 tested) + """ + + def __init__(self, portName: str = "/dev/ttyUSB0"): + """Initialize the RidenPowerSupply object. + + Args: + portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyUSB0". + """ + self.r = r = Riden(port=portName, baudrate=115200, address=1) + logging.info( + f"Connected to Riden power supply: model {r.type}, sn {r.sn}, firmware {r.fw}. Date/time updated." + ) + r.set_date_time(datetime.now()) + + def setMaxCurrent(self, i: float): + """Set the maximum current the supply will provide.""" + self.r.set_i_set(i) + + def powerOn(self, v: float): + """Power on the supply, with reasonable defaults for meshtastic devices.""" + self.r.set_v_set(v) # my WM1110 devboard header is directly connected to the 3.3V rail + self.r.set_output(1) + + def getWattHour(self) -> float: + """Get the current watt-hour reading.""" + self.r.update() + return self.r.wh + diff --git a/meshtastic/slog/__init__.py b/meshtastic/slog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/meshtastic/power_mon.py b/meshtastic/slog/power_mon.py similarity index 57% rename from meshtastic/power_mon.py rename to meshtastic/slog/power_mon.py index e2bab35..c1fd12e 100644 --- a/meshtastic/power_mon.py +++ b/meshtastic/slog/power_mon.py @@ -6,77 +6,35 @@ import atexit from datetime import datetime import pandas as pd -from riden import Riden from meshtastic.mesh_interface import MeshInterface from meshtastic.observable import Event +from meshtastic.powermon import PowerSupply - -class PowerSupply: - """Interface for talking to programmable bench-top power supplies. - Currently only the Riden supplies are supported (RD6006 tested) - """ - - def __init__(self, portName: str = "/dev/ttyUSB0"): - """Initialize the PowerSupply object.""" - self.r = r = Riden(port=portName, baudrate=115200, address=1) - logging.info( - f"Connected to Riden power supply: model {r.type}, sn {r.sn}, firmware {r.fw}. Date/time updated." - ) - r.set_date_time(datetime.now()) - - def powerOn(self): - """Power on the supply, with reasonable defaults for meshtastic devices.""" - self.r.set_i_set( - 0.300 - ) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit - - # self.r.set_v_set(3.7) # default to a nominal LiPo voltage - self.r.set_v_set(3.3) # my WM1110 devboard header is directly connected to the 3.3V rail - self.r.set_output(1) - - """Get current watts out. - But for most applications you probably want getWattHour() instead (to prevent integration errors from accumulating). - """ - return self.r.get_p_out() - - def getWattHour(self): - """Get current Wh out, since power was turned on.""" - # FIXME: Individual reads seem busted in the riden lib. So for now I just read everything. - self.r.update() - return self.r.wh - # return self.r.get_wh() - - def clearWattHour(self): - """Clear the watt-hour counter FIXME.""" - - -"""Used to match power mon log lines: -INFO | ??:??:?? 7 [Blink] S:PM:0x00000080,reason -""" logRegex = re.compile(".*S:PM:0x([0-9A-Fa-f]+),(.*)") class PowerMonClient: """Client for monitoring power consumption of meshtastic devices.""" - def __init__(self, portName: str, client: MeshInterface) -> None: + def __init__(self, power: PowerSupply, client: MeshInterface) -> None: """Initialize the PowerMonClient object. Args: + power (PowerSupply): The power supply object. client (MeshInterface): The MeshInterface object to monitor. - """ self.client = client self.state = 0 # The current power mon state bitfields self.columns = ["time", "power", "reason", "bitmask"] - self.rawData = pd.DataFrame(columns=self.columns) # use time as the index + self.rawData = pd.DataFrame(columns=self.columns) # use time as the index - # for efficiency reasons we keep new data in a list - only adding to rawData when needfed + # for efficiency reasons we keep new data in a list - only adding to rawData when needed self.newData: list[dict] = [] - self.power = power = PowerSupply(portName) - power.powerOn() + self.power = power + power.setMaxCurrent(0.300) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit + power.powerOn(3.3) # Used to calculate watts over an interval self.prevPowerTime = datetime.now() @@ -89,7 +47,6 @@ class PowerMonClient: Returns: pd.DataFrame: The raw data. - """ df = pd.DataFrame(self.newData, columns=self.columns) self.rawData = pd.concat([self.rawData, df], ignore_index=True) @@ -107,8 +64,7 @@ class PowerMonClient: """Callback function for handling log messages. Args: - message (str): The log message. - + ev (Event): The log event. """ m = logRegex.match(ev.message) if m: @@ -124,9 +80,7 @@ class PowerMonClient: Args: mask (int): The power mon state bitfields. reason (str): The reason for the power mon state change. - """ - now = datetime.now() nowWattHour = self.power.getWattHour() watts = ( @@ -139,5 +93,5 @@ class PowerMonClient: self.state = mask self.newData.append( - {"time": now, "power": watts, "reason": reason, "bitmask": mask}) - # self.getRawData() + {"time": now, "power": watts, "reason": reason, "bitmask": mask} + )