begin support for multiple power meter types

This commit is contained in:
Kevin Hester
2024-06-22 12:09:41 -07:00
parent a1f86a351a
commit cc60f3ebc0
6 changed files with 84 additions and 63 deletions

2
.vscode/launch.json vendored
View File

@@ -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",

View File

@@ -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(

View File

@@ -0,0 +1 @@
from .riden import *

View File

@@ -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

View File

View File

@@ -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}
)