mirror of
https://github.com/meshtastic/python.git
synced 2025-12-31 03:47:55 -05:00
Add support for NRF PPK2 power testing board.
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -172,7 +172,7 @@
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": false,
|
||||
"args": ["--power-riden", "/dev/ttyUSB0", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"]
|
||||
"args": ["--power-ppk", "--power-voltage", "3.3", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic test",
|
||||
|
||||
@@ -21,7 +21,7 @@ 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.powermon import RidenPowerSupply
|
||||
from meshtastic.powermon import RidenPowerSupply, PPK2PowerSupply
|
||||
from meshtastic.slog import StructuredLogger
|
||||
|
||||
def onReceive(packet, interface):
|
||||
@@ -1093,6 +1093,16 @@ def common():
|
||||
meter = None # assume no power meter
|
||||
if args.power_riden:
|
||||
meter = RidenPowerSupply(args.power_riden)
|
||||
elif args.power_ppk2:
|
||||
meter = PPK2PowerSupply()
|
||||
|
||||
if meter and args.power_voltage:
|
||||
v = float(args.power_voltage)
|
||||
if v < 1.0 or v >5.0:
|
||||
meshtastic.util.our_exit("Voltage must be between 1.0 and 5.0")
|
||||
logging.info(f"Setting power supply to {v} volts")
|
||||
meter.v = v
|
||||
meter.powerOn()
|
||||
|
||||
StructuredLogger(client, meter)
|
||||
|
||||
@@ -1521,6 +1531,11 @@ def initParser():
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--power-voltage",
|
||||
help="Set the specified voltage on the power-supply. Be VERY careful, you can burn things up.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--power-stress",
|
||||
help="Perform power monitor stress testing, to capture a power consumption profile for the device (also requires --power-mon)",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Support for logging from power meters/supplies."""
|
||||
|
||||
from .riden import *
|
||||
from .power_supply import PowerMeter, PowerSupply, PowerError
|
||||
from .riden import RidenPowerSupply
|
||||
from .ppk2 import PPK2PowerSupply
|
||||
49
meshtastic/powermon/power_supply.py
Normal file
49
meshtastic/powermon/power_supply.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import math
|
||||
from datetime import datetime
|
||||
|
||||
class PowerError(Exception):
|
||||
"""An exception class for powermon errors"""
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class PowerMeter:
|
||||
"""Abstract class for power meters."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the PowerMeter object."""
|
||||
self.prevPowerTime = datetime.now()
|
||||
self.prevWattHour = self._getRawWattHour()
|
||||
|
||||
def getWatts(self) -> float:
|
||||
"""Get the total amount of power that has been consumed since the previous call of this method"""
|
||||
now = datetime.now()
|
||||
nowWattHour = self._getRawWattHour()
|
||||
watts = (
|
||||
(nowWattHour - self.prevWattHour)
|
||||
/ (now - self.prevPowerTime).total_seconds()
|
||||
* 3600
|
||||
)
|
||||
self.prevPowerTime = now
|
||||
self.prevWattHour = nowWattHour
|
||||
return watts
|
||||
|
||||
def _getRawWattHour(self) -> float:
|
||||
"""Get the current watt-hour reading (without any offset correction)."""
|
||||
return math.nan
|
||||
|
||||
|
||||
|
||||
class PowerSupply(PowerMeter):
|
||||
"""Abstract class for power supplies."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the PowerSupply object."""
|
||||
super().__init__()
|
||||
self.v = 3.3
|
||||
|
||||
def powerOn(self):
|
||||
"""Turn on the power supply (using the voltage set in self.v)."""
|
||||
47
meshtastic/powermon/ppk2.py
Normal file
47
meshtastic/powermon/ppk2.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import logging
|
||||
from typing import *
|
||||
|
||||
from ppk2_api import ppk2_api
|
||||
from .power_supply import PowerSupply, PowerError
|
||||
|
||||
|
||||
|
||||
class PPK2PowerSupply(PowerSupply):
|
||||
"""Interface for talking with the NRF PPK2 high-resolution micro-power supply.
|
||||
Power Profiler Kit II is what you should google to find it for purchase.
|
||||
"""
|
||||
|
||||
def __init__(self, portName: Optional[str] = None):
|
||||
"""Initialize the PowerSupply object.
|
||||
|
||||
portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyACM0".
|
||||
"""
|
||||
if not portName:
|
||||
devs = ppk2_api.PPK2_API.list_devices()
|
||||
if not devs or len(devs) == 0:
|
||||
raise PowerError("No PPK2 devices found")
|
||||
elif len(devs) > 1:
|
||||
raise PowerError("Multiple PPK2 devices found, please specify the portName")
|
||||
else:
|
||||
portName = devs[0]
|
||||
|
||||
self.r = r = ppk2_api.PPK2_MP(portName) # serial port will be different for you
|
||||
r.get_modifiers()
|
||||
|
||||
logging.info("Connected to PPK2 power supply")
|
||||
|
||||
super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works
|
||||
|
||||
def powerOn(self):
|
||||
"""Power on the supply, with reasonable defaults for meshtastic devices."""
|
||||
self.r.use_source_meter() # set source meter mode
|
||||
self.r.set_source_voltage(self.v * 1000) # set source voltage in mV
|
||||
self.r.toggle_DUT_power("ON")
|
||||
self.r.start_measuring() # start measuring
|
||||
|
||||
|
||||
def _getRawWattHour(self) -> float:
|
||||
"""Get the current watt-hour reading."""
|
||||
return 4 # FIXME
|
||||
@@ -1,52 +1,14 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import logging
|
||||
import math
|
||||
from datetime import datetime
|
||||
|
||||
from riden import Riden
|
||||
|
||||
class PowerMeter:
|
||||
"""Abstract class for power meters."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the PowerMeter object."""
|
||||
self.prevPowerTime = datetime.now()
|
||||
self.prevWattHour = self._getRawWattHour()
|
||||
|
||||
def getWatts(self) -> float:
|
||||
"""Get the total amount of power that has been consumed since the previous call of this method"""
|
||||
now = datetime.now()
|
||||
nowWattHour = self._getRawWattHour()
|
||||
watts = (
|
||||
(nowWattHour - self.prevWattHour)
|
||||
/ (now - self.prevPowerTime).total_seconds()
|
||||
* 3600
|
||||
)
|
||||
self.prevPowerTime = now
|
||||
self.prevWattHour = nowWattHour
|
||||
return watts
|
||||
|
||||
def _getRawWattHour(self) -> float:
|
||||
"""Get the current watt-hour reading (without any offset correction)."""
|
||||
return math.nan
|
||||
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
|
||||
from .power_supply import PowerSupply
|
||||
|
||||
class RidenPowerSupply(PowerSupply):
|
||||
"""Interface for talking to programmable bench-top power supplies.
|
||||
Currently only the Riden supplies are supported (RD6006 tested)
|
||||
"""Interface for talking to Riden programmable bench-top power supplies.
|
||||
Only RD6006 tested but others should be similar.
|
||||
"""
|
||||
|
||||
def __init__(self, portName: str = "/dev/ttyUSB0"):
|
||||
@@ -65,9 +27,9 @@ class RidenPowerSupply(PowerSupply):
|
||||
"""Set the maximum current the supply will provide."""
|
||||
self.r.set_i_set(i)
|
||||
|
||||
def powerOn(self, v: float):
|
||||
def powerOn(self):
|
||||
"""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_v_set(self.v) # my WM1110 devboard header is directly connected to the 3.3V rail
|
||||
self.r.set_output(1)
|
||||
|
||||
def _getRawWattHour(self) -> float:
|
||||
|
||||
16
poetry.lock
generated
16
poetry.lock
generated
@@ -928,6 +928,20 @@ files = [
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "ppk2-api"
|
||||
version = "0.9.2"
|
||||
description = "API for Nordic Semiconductor's Power Profiler Kit II (PPK 2)."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ppk2-api-0.9.2.tar.gz", hash = "sha256:e8fb29f782ba6e5bd2e0286079163c495cfffce336f9b149430348c043b25300"},
|
||||
{file = "ppk2_api-0.9.2-py3-none-any.whl", hash = "sha256:b7fb02156f87d8430bbce0006876d38c8309ada671fbcd15848173b431198803"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyserial = "*"
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "5.27.1"
|
||||
@@ -1794,4 +1808,4 @@ tunnel = []
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9,<3.13"
|
||||
content-hash = "cc81bcd3e4671bde3375f2f08334d6a3b87b38c97f2676d7266cab980082659e"
|
||||
content-hash = "7719c01f112a7864acdec0a82e7131aa33a646d84459cc0cc6db0eadf94933c7"
|
||||
|
||||
@@ -25,6 +25,7 @@ packaging = "^24.0"
|
||||
riden = {git = "https://github.com/geeksville/riden.git#1.2.1"}
|
||||
pandas = "^2.2.2"
|
||||
parse = "^1.20.2"
|
||||
ppk2-api = "^0.9.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
hypothesis = "^6.103.2"
|
||||
|
||||
Reference in New Issue
Block a user