mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Add calibrate_polynomial sensor filter (#642)
* Add calibrate_polynomial sensor filter * Fix * Lint * Format
This commit is contained in:
parent
f95d4ca106
commit
d2938e82db
4 changed files with 114 additions and 0 deletions
|
@ -73,6 +73,7 @@ HeartbeatFilter = sensor_ns.class_('HeartbeatFilter', Filter, cg.Component)
|
||||||
DeltaFilter = sensor_ns.class_('DeltaFilter', Filter)
|
DeltaFilter = sensor_ns.class_('DeltaFilter', Filter)
|
||||||
OrFilter = sensor_ns.class_('OrFilter', Filter)
|
OrFilter = sensor_ns.class_('OrFilter', Filter)
|
||||||
CalibrateLinearFilter = sensor_ns.class_('CalibrateLinearFilter', Filter)
|
CalibrateLinearFilter = sensor_ns.class_('CalibrateLinearFilter', Filter)
|
||||||
|
CalibratePolynomialFilter = sensor_ns.class_('CalibratePolynomialFilter', Filter)
|
||||||
SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter)
|
SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter)
|
||||||
|
|
||||||
unit_of_measurement = cv.string_strict
|
unit_of_measurement = cv.string_strict
|
||||||
|
@ -194,6 +195,32 @@ def calibrate_linear_filter_to_code(config, filter_id):
|
||||||
yield cg.new_Pvariable(filter_id, k, b)
|
yield cg.new_Pvariable(filter_id, k, b)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_DATAPOINTS = 'datapoints'
|
||||||
|
CONF_DEGREE = 'degree'
|
||||||
|
|
||||||
|
|
||||||
|
def validate_calibrate_polynomial(config):
|
||||||
|
if config[CONF_DEGREE] >= len(config[CONF_DATAPOINTS]):
|
||||||
|
raise cv.Invalid("Degree is too high! Maximum possible degree with given datapoints is "
|
||||||
|
"{}".format(len(config[CONF_DATAPOINTS]) - 1), [CONF_DEGREE])
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@FILTER_REGISTRY.register('calibrate_polynomial', CalibratePolynomialFilter, cv.All(cv.Schema({
|
||||||
|
cv.Required(CONF_DATAPOINTS): cv.All(cv.ensure_list(validate_datapoint), cv.Length(min=1)),
|
||||||
|
cv.Required(CONF_DEGREE): cv.positive_int,
|
||||||
|
}), validate_calibrate_polynomial))
|
||||||
|
def calibrate_polynomial_filter_to_code(config, filter_id):
|
||||||
|
x = [conf[CONF_FROM] for conf in config[CONF_DATAPOINTS]]
|
||||||
|
y = [conf[CONF_TO] for conf in config[CONF_DATAPOINTS]]
|
||||||
|
degree = config[CONF_DEGREE]
|
||||||
|
a = [[1] + [x_**(i+1) for i in range(degree)] for x_ in x]
|
||||||
|
# Column vector
|
||||||
|
b = [[v] for v in y]
|
||||||
|
res = [v[0] for v in _lstsq(a, b)]
|
||||||
|
yield cg.new_Pvariable(filter_id, res)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
@coroutine
|
||||||
def build_filters(config):
|
def build_filters(config):
|
||||||
yield cg.build_registry_list(FILTER_REGISTRY, config)
|
yield cg.build_registry_list(FILTER_REGISTRY, config)
|
||||||
|
@ -303,6 +330,66 @@ def fit_linear(x, y):
|
||||||
return k, b
|
return k, b
|
||||||
|
|
||||||
|
|
||||||
|
def _mat_copy(m):
|
||||||
|
return [list(row) for row in m]
|
||||||
|
|
||||||
|
|
||||||
|
def _mat_transpose(m):
|
||||||
|
return _mat_copy(zip(*m))
|
||||||
|
|
||||||
|
|
||||||
|
def _mat_identity(n):
|
||||||
|
return [[int(i == j) for j in range(n)] for i in range(n)]
|
||||||
|
|
||||||
|
|
||||||
|
def _mat_dot(a, b):
|
||||||
|
b_t = _mat_transpose(b)
|
||||||
|
return [[sum(x*y for x, y in zip(row_a, col_b)) for col_b in b_t] for row_a in a]
|
||||||
|
|
||||||
|
|
||||||
|
def _mat_inverse(m):
|
||||||
|
n = len(m)
|
||||||
|
m = _mat_copy(m)
|
||||||
|
id = _mat_identity(n)
|
||||||
|
|
||||||
|
for diag in range(n):
|
||||||
|
# If diag element is 0, swap rows
|
||||||
|
if m[diag][diag] == 0:
|
||||||
|
for i in range(diag+1, n):
|
||||||
|
if m[i][diag] != 0:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("Singular matrix, inverse cannot be calculated!")
|
||||||
|
|
||||||
|
# Swap rows
|
||||||
|
m[diag], m[i] = m[i], m[diag]
|
||||||
|
id[diag], id[i] = id[i], id[diag]
|
||||||
|
|
||||||
|
# Scale row to 1 in diagonal
|
||||||
|
scaler = 1.0 / m[diag][diag]
|
||||||
|
for j in range(n):
|
||||||
|
m[diag][j] *= scaler
|
||||||
|
id[diag][j] *= scaler
|
||||||
|
|
||||||
|
# Subtract diag row
|
||||||
|
for i in range(n):
|
||||||
|
if i == diag:
|
||||||
|
continue
|
||||||
|
scaler = m[i][diag]
|
||||||
|
for j in range(n):
|
||||||
|
m[i][j] -= scaler * m[diag][j]
|
||||||
|
id[i][j] -= scaler * id[diag][j]
|
||||||
|
|
||||||
|
return id
|
||||||
|
|
||||||
|
|
||||||
|
def _lstsq(a, b):
|
||||||
|
# min_x ||b - ax||^2_2 => x = (a^T a)^{-1} a^T b
|
||||||
|
a_t = _mat_transpose(a)
|
||||||
|
x = _mat_inverse(_mat_dot(a_t, a))
|
||||||
|
return _mat_dot(_mat_dot(x, a_t), b)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(40.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
cg.add_define('USE_SENSOR')
|
cg.add_define('USE_SENSOR')
|
||||||
|
|
|
@ -228,5 +228,15 @@ float HeartbeatFilter::get_setup_priority() const { return setup_priority::HARDW
|
||||||
optional<float> CalibrateLinearFilter::new_value(float value) { return value * this->slope_ + this->bias_; }
|
optional<float> CalibrateLinearFilter::new_value(float value) { return value * this->slope_ + this->bias_; }
|
||||||
CalibrateLinearFilter::CalibrateLinearFilter(float slope, float bias) : slope_(slope), bias_(bias) {}
|
CalibrateLinearFilter::CalibrateLinearFilter(float slope, float bias) : slope_(slope), bias_(bias) {}
|
||||||
|
|
||||||
|
optional<float> CalibratePolynomialFilter::new_value(float value) {
|
||||||
|
float res = 0.0f;
|
||||||
|
float x = 1.0f;
|
||||||
|
for (float coefficient : this->coefficients_) {
|
||||||
|
res += x * coefficient;
|
||||||
|
x *= value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sensor
|
} // namespace sensor
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -243,5 +243,14 @@ class CalibrateLinearFilter : public Filter {
|
||||||
float bias_;
|
float bias_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CalibratePolynomialFilter : public Filter {
|
||||||
|
public:
|
||||||
|
CalibratePolynomialFilter(const std::vector<float> &coefficients) : coefficients_(coefficients) {}
|
||||||
|
optional<float> new_value(float value) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<float> coefficients_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace sensor
|
} // namespace sensor
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -133,6 +133,14 @@ sensor:
|
||||||
- calibrate_linear:
|
- calibrate_linear:
|
||||||
- 0 -> 0
|
- 0 -> 0
|
||||||
- 100 -> 100
|
- 100 -> 100
|
||||||
|
- calibrate_polynomial:
|
||||||
|
degree: 3
|
||||||
|
datapoints:
|
||||||
|
- 0 -> 0
|
||||||
|
- 100 -> 200
|
||||||
|
- 400 -> 500
|
||||||
|
- -50 -> -1000
|
||||||
|
- -100 -> -10000
|
||||||
- platform: resistance
|
- platform: resistance
|
||||||
sensor: my_sensor
|
sensor: my_sensor
|
||||||
configuration: DOWNSTREAM
|
configuration: DOWNSTREAM
|
||||||
|
|
Loading…
Reference in a new issue