Added calibrated meter (usually for ampere meter)
This commit is contained in:
95
mipyiot/calibratedmeter.py
Normal file
95
mipyiot/calibratedmeter.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from machine import PWM, Pin
|
||||||
|
from array import array
|
||||||
|
|
||||||
|
class CalibratedMeter:
|
||||||
|
def __init__(self, pin, freq=1000, input_min=0, input_max=100,
|
||||||
|
output_min=0, output_max=65535):
|
||||||
|
self._pwm = PWM(Pin(pin), freq=freq)
|
||||||
|
self._input_min = input_min
|
||||||
|
self._input_max = input_max
|
||||||
|
self._output_min = output_min
|
||||||
|
self._output_max = output_max
|
||||||
|
self._cal_inputs = array('f', [input_min, input_max])
|
||||||
|
self._cal = array('f', [output_min, output_max])
|
||||||
|
|
||||||
|
# --- Input range property ---
|
||||||
|
@property
|
||||||
|
def input_range(self):
|
||||||
|
return (self._input_min, self._input_max)
|
||||||
|
|
||||||
|
@input_range.setter
|
||||||
|
def input_range(self, value):
|
||||||
|
self._input_min, self._input_max = value
|
||||||
|
|
||||||
|
# --- Output range property ---
|
||||||
|
@property
|
||||||
|
def output_range(self):
|
||||||
|
return (self._output_min, self._output_max)
|
||||||
|
|
||||||
|
@output_range.setter
|
||||||
|
def output_range(self, value):
|
||||||
|
self._output_min, self._output_max = value
|
||||||
|
|
||||||
|
# --- Calibration ---
|
||||||
|
def set_calibration(self, points):
|
||||||
|
"""
|
||||||
|
points: list of (input_value, duty_cycle) tuples
|
||||||
|
input_value: in real-world units
|
||||||
|
duty_cycle: must be within output_min and output_max
|
||||||
|
Example:
|
||||||
|
meter.set_calibration([
|
||||||
|
(0, 0),
|
||||||
|
(25, 170),
|
||||||
|
(50, 335),
|
||||||
|
(75, 508),
|
||||||
|
(100, 1023),
|
||||||
|
])
|
||||||
|
"""
|
||||||
|
points = sorted(points, key=lambda p: p[0])
|
||||||
|
|
||||||
|
# Sanity checks
|
||||||
|
for i, (inp, out) in enumerate(points):
|
||||||
|
if not (self._output_min <= out <= self._output_max):
|
||||||
|
raise ValueError(
|
||||||
|
f"Calibration point {i}: duty {out} outside output range "
|
||||||
|
f"[{self._output_min}, {self._output_max}]"
|
||||||
|
)
|
||||||
|
if not (self._input_min <= inp <= self._input_max):
|
||||||
|
raise ValueError(
|
||||||
|
f"Calibration point {i}: input {inp} outside input range "
|
||||||
|
f"[{self._input_min}, {self._input_max}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cal_inputs = array('f', [p[0] for p in points])
|
||||||
|
self._cal = array('f', [p[1] for p in points])
|
||||||
|
|
||||||
|
# --- Internal helpers ---
|
||||||
|
def _interpolate(self, value):
|
||||||
|
"""Linearly interpolate duty cycle from calibration table."""
|
||||||
|
if value <= self._cal_inputs[0]:
|
||||||
|
return self._cal[0]
|
||||||
|
if value >= self._cal_inputs[-1]:
|
||||||
|
return self._cal[-1]
|
||||||
|
for i in range(len(self._cal_inputs) - 1):
|
||||||
|
x0, x1 = self._cal_inputs[i], self._cal_inputs[i + 1]
|
||||||
|
if x0 <= value <= x1:
|
||||||
|
y0, y1 = self._cal[i], self._cal[i + 1]
|
||||||
|
t = (value - x0) / (x1 - x0)
|
||||||
|
return y0 + t * (y1 - y0)
|
||||||
|
return self._cal[-1]
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
def write(self, value):
|
||||||
|
"""Write a value in real-world units to the meter."""
|
||||||
|
value = max(self._input_min, min(self._input_max, value))
|
||||||
|
duty = int(self._interpolate(value))
|
||||||
|
duty = max(self._output_min, min(self._output_max, duty))
|
||||||
|
self._pwm.duty_u16(duty)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
"""Set meter to zero."""
|
||||||
|
self._pwm.duty_u16(self._output_min)
|
||||||
|
|
||||||
|
def full(self):
|
||||||
|
"""Set meter to full scale."""
|
||||||
|
self._pwm.duty_u16(self._output_max)
|
||||||
Reference in New Issue
Block a user