Add support for NRF PPK2 power testing board.

This commit is contained in:
Kevin Hester
2024-06-23 18:42:15 -07:00
parent 5ff4025ed6
commit ea18057c1f
8 changed files with 137 additions and 47 deletions

2
.vscode/launch.json vendored
View File

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

View File

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

View File

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

View 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)."""

View 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

View File

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

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

View File

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