mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
initial commit
This commit is contained in:
parent
16e0b78c64
commit
0bcdc4bf56
13 changed files with 435 additions and 0 deletions
|
@ -11,6 +11,8 @@ esphome/*.py @esphome/core
|
||||||
esphome/core/* @esphome/core
|
esphome/core/* @esphome/core
|
||||||
|
|
||||||
# Integrations
|
# Integrations
|
||||||
|
esphome/components/MCP3428/* @mdop
|
||||||
|
esphome/components/MCP3428/sensor/* @mdop
|
||||||
esphome/components/a01nyub/* @MrSuicideParrot
|
esphome/components/a01nyub/* @MrSuicideParrot
|
||||||
esphome/components/a02yyuw/* @TH-Braemer
|
esphome/components/a02yyuw/* @TH-Braemer
|
||||||
esphome/components/absolute_humidity/* @DAVe3283
|
esphome/components/absolute_humidity/* @DAVe3283
|
||||||
|
|
33
esphome/components/MCP3428/__init__.py
Normal file
33
esphome/components/MCP3428/__init__.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
CODEOWNERS = ["@mdop"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
mcp3428_ns = cg.esphome_ns.namespace("mcp3428")
|
||||||
|
MCP3428Component = mcp3428_ns.class_("MCP3428Component", cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||||
|
CONF_MCP3428_ID = "mcp3428_id"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(MCP3428Component),
|
||||||
|
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(None))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
141
esphome/components/MCP3428/mcp3428.cpp
Normal file
141
esphome/components/MCP3428/mcp3428.cpp
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
#include "mcp3428.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3428 {
|
||||||
|
|
||||||
|
static const char *const TAG = "mcp3426/7/8";
|
||||||
|
|
||||||
|
void MCP3428Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MCP3426/7/8...");
|
||||||
|
uint8_t anwser[3];
|
||||||
|
if (!this->read(anwser, 3)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, "Configuring MCP3426/7/8...");
|
||||||
|
|
||||||
|
/* config byte structure: (bit|description)
|
||||||
|
* 7 | ready bit, 0 means new data in the result
|
||||||
|
* 6 | Channel selection bit 1
|
||||||
|
* 5 | Channel selection bit 0
|
||||||
|
* 4 | conversion mode bit (1 continuous mode, 0 singe shot)
|
||||||
|
* 3 | Resolution bit 1
|
||||||
|
* 2 | Resolution bit 0
|
||||||
|
* 1 | Gain selection bit 1
|
||||||
|
* 0 | Gain selection bit 0
|
||||||
|
*/
|
||||||
|
uint8_t config = 0;
|
||||||
|
// set ready bit and conversion mode bit
|
||||||
|
// 0b0xx0xxxx
|
||||||
|
if (this->continuous_mode_) {
|
||||||
|
// initial state should be no new measurement in continuous mode
|
||||||
|
config = config | 0b10010000;
|
||||||
|
} else {
|
||||||
|
// don't initiate measurement in single shot mode
|
||||||
|
config = config & 0b01101111;
|
||||||
|
}
|
||||||
|
// leave channel at 1, gain at 1x, and resolution at 12 bit
|
||||||
|
|
||||||
|
if (!this->write(&config, 1)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->prev_config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP3428Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MCP3426/7/8...");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with MCP3426/7/8 failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float MCP3428Component::request_measurement(MCP3428Multiplexer multiplexer, MCP3428Gain gain,
|
||||||
|
MCP3428Resolution resolution) {
|
||||||
|
uint8_t config = 0;
|
||||||
|
// set ready bit to 1, will starts measurement in single shot mode and mark measurement as not yet ready in continuous
|
||||||
|
// mode
|
||||||
|
config |= 1 << 7;
|
||||||
|
// set channel
|
||||||
|
config |= multiplexer << 5;
|
||||||
|
// set conversion mode
|
||||||
|
if (this->continuous_mode_) {
|
||||||
|
config |= 1 << 4;
|
||||||
|
}
|
||||||
|
// set resolution
|
||||||
|
config |= resolution << 2;
|
||||||
|
// set gain
|
||||||
|
config |= gain;
|
||||||
|
|
||||||
|
// If continuous mode and config (besides ready bit) are the same there is no need to upload new config, reading the
|
||||||
|
// result is enough
|
||||||
|
if (!((this->prev_config_ & 0b00010000) > 0 and (this->prev_config_ & 0b01111111) == (config & 0b01111111))) {
|
||||||
|
if (!this->write(&config, 1)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
this->prev_config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCP is now configured, read output until ready flag is 0 for a valid measurement
|
||||||
|
uint32_t start = millis();
|
||||||
|
uint8_t anwser[3];
|
||||||
|
while (true) {
|
||||||
|
if (!this->read(anwser, 3)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
if ((anwser[2] & 0b10000000) == 0) {
|
||||||
|
// ready flag is 0, valid measurement received
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (millis() - start > 100) {
|
||||||
|
ESP_LOGW(TAG, "Reading MCP3428 measurement timed out");
|
||||||
|
this->status_set_warning();
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// got valid measurement, clean up unused bits from the anwser code and prepare tick size
|
||||||
|
float tick_voltage = 2.048f / 32768; // ref voltage 2.048V/non-sign bits, default 15 bits
|
||||||
|
switch (resolution) {
|
||||||
|
case MCP3428Resolution::MCP3428_12_BITS:
|
||||||
|
// Structure [sign][sign][sign][sign][sign][value][value][value], only keep sign at MSB for int16_t
|
||||||
|
anwser[0] &= 0b10000111;
|
||||||
|
tick_voltage *= 16;
|
||||||
|
break;
|
||||||
|
case MCP3428Resolution::MCP3428_14_BITS:
|
||||||
|
// Structure [sign][sign][sign][value][value][value][value][value]
|
||||||
|
anwser[0] &= 0b10011111;
|
||||||
|
tick_voltage *= 4;
|
||||||
|
break;
|
||||||
|
default: // nothing to do for 16 bit
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (gain) {
|
||||||
|
case MCP3428Gain::MCP3428_GAIN_2:
|
||||||
|
tick_voltage /= 2;
|
||||||
|
break;
|
||||||
|
case MCP3428Gain::MCP3428_GAIN_4:
|
||||||
|
tick_voltage /= 4;
|
||||||
|
break;
|
||||||
|
case MCP3428Gain::MCP3428_GAIN_8:
|
||||||
|
tick_voltage /= 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// convert code (first 2 bytes of cleaned up anwser) into voltage ticks
|
||||||
|
int16_t ticks = anwser[0] << 8 | anwser[1];
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
return tick_voltage * ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mcp3428
|
||||||
|
} // namespace esphome
|
47
esphome/components/MCP3428/mcp3428.h
Normal file
47
esphome/components/MCP3428/mcp3428.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3428 {
|
||||||
|
|
||||||
|
// the second bit is ignored in MCP3426/7 and will result in measurement of channel 1 or 2
|
||||||
|
enum MCP3428Multiplexer {
|
||||||
|
MCP3428_MULTIPLEXER_CHANNEL_1 = 0b00,
|
||||||
|
MCP3428_MULTIPLEXER_CHANNEL_2 = 0b01,
|
||||||
|
MCP3428_MULTIPLEXER_CHANNEL_3 = 0b10,
|
||||||
|
MCP3428_MULTIPLEXER_CHANNEL_4 = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MCP3428Gain {
|
||||||
|
MCP3428_GAIN_1 = 0b00,
|
||||||
|
MCP3428_GAIN_2 = 0b01,
|
||||||
|
MCP3428_GAIN_4 = 0b10,
|
||||||
|
MCP3428_GAIN_8 = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MCP3428Resolution {
|
||||||
|
MCP3428_12_BITS = 0b00,
|
||||||
|
MCP3428_14_BITS = 0b01,
|
||||||
|
MCP3428_16_BITS = 0b10,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MCP3428Component : public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
/// HARDWARE_LATE setup priority
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||||
|
|
||||||
|
/// Helper method to request a measurement from a sensor.
|
||||||
|
float request_measurement(MCP3428Multiplexer multiplexer, MCP3428Gain gain, MCP3428Resolution resolution);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t prev_config_{0};
|
||||||
|
bool continuous_mode_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcp3428
|
||||||
|
} // namespace esphome
|
75
esphome/components/MCP3428/sensor/__init__.py
Normal file
75
esphome/components/MCP3428/sensor/__init__.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, voltage_sampler
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_GAIN,
|
||||||
|
CONF_MULTIPLEXER,
|
||||||
|
CONF_RESOLUTION,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
|
from .. import mcp3428_ns, MCP3428Component, CONF_MCP3428_ID
|
||||||
|
|
||||||
|
CODEOWNERS = ["@mdop"]
|
||||||
|
AUTO_LOAD = ["voltage_sampler"]
|
||||||
|
DEPENDENCIES = ["mcp3428"]
|
||||||
|
|
||||||
|
MCP3428Multiplexer = mcp3428_ns.enum("MCP3428Multiplexer")
|
||||||
|
MUX = {
|
||||||
|
1: MCP3428Multiplexer.MCP3428_MULTIPLEXER_CHANNEL_1,
|
||||||
|
2: MCP3428Multiplexer.MCP3428_MULTIPLEXER_CHANNEL_2,
|
||||||
|
3: MCP3428Multiplexer.MCP3428_MULTIPLEXER_CHANNEL_3,
|
||||||
|
4: MCP3428Multiplexer.MCP3428_MULTIPLEXER_CHANNEL_4,
|
||||||
|
}
|
||||||
|
|
||||||
|
MCP3428Gain = mcp3428_ns.enum("MCP3428Gain")
|
||||||
|
GAIN = {
|
||||||
|
1: MCP3428Gain.MCP3428_GAIN_1,
|
||||||
|
2: MCP3428Gain.MCP3428_GAIN_2,
|
||||||
|
4: MCP3428Gain.MCP3428_GAIN_4,
|
||||||
|
8: MCP3428Gain.MCP3428_GAIN_8,
|
||||||
|
}
|
||||||
|
|
||||||
|
MCP3428Resolution = mcp3428_ns.enum("MCP3428Resolution")
|
||||||
|
RESOLUTION = {
|
||||||
|
12: MCP3428Resolution.MCP3428_12_BITS,
|
||||||
|
14: MCP3428Resolution.MCP3428_14_BITS,
|
||||||
|
16: MCP3428Resolution.MCP3428_16_BITS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MCP3428Sensor = mcp3428_ns.class_(
|
||||||
|
"MCP3428Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
MCP3428Sensor,
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=6,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_MCP3428_ID): cv.use_id(MCP3428Component),
|
||||||
|
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, int=True),
|
||||||
|
cv.Optional(CONF_GAIN, default=1): cv.enum(GAIN, int=True),
|
||||||
|
cv.Optional(CONF_RESOLUTION, default=16): cv.enum(RESOLUTION, int=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cg.register_parented(var, config[CONF_MCP3428_ID])
|
||||||
|
|
||||||
|
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||||
|
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||||
|
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
30
esphome/components/MCP3428/sensor/mcp3428_sensor.cpp
Normal file
30
esphome/components/MCP3428/sensor/mcp3428_sensor.cpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include "mcp3428_sensor.h"
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3428 {
|
||||||
|
|
||||||
|
static const char *const TAG = "mcp3426/7/8.sensor";
|
||||||
|
|
||||||
|
float MCP3428Sensor::sample() {
|
||||||
|
return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP3428Sensor::update() {
|
||||||
|
float v = this->sample();
|
||||||
|
if (!std::isnan(v)) {
|
||||||
|
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||||
|
this->publish_state(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP3428Sensor::dump_config() {
|
||||||
|
LOG_SENSOR(" ", "MCP3426/7/8 Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mcp3428
|
||||||
|
} // namespace esphome
|
35
esphome/components/MCP3428/sensor/mcp3428_sensor.h
Normal file
35
esphome/components/MCP3428/sensor/mcp3428_sensor.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||||
|
|
||||||
|
#include "../mcp3428.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3428 {
|
||||||
|
|
||||||
|
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||||
|
class MCP3428Sensor : public sensor::Sensor,
|
||||||
|
public PollingComponent,
|
||||||
|
public voltage_sampler::VoltageSampler,
|
||||||
|
public Parented<MCP3428Component> {
|
||||||
|
public:
|
||||||
|
void update() override;
|
||||||
|
void set_multiplexer(MCP3428Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||||
|
void set_gain(MCP3428Gain gain) { this->gain_ = gain; }
|
||||||
|
void set_resolution(MCP3428Resolution resolution) { this->resolution_ = resolution; }
|
||||||
|
float sample() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MCP3428Multiplexer multiplexer_;
|
||||||
|
MCP3428Gain gain_;
|
||||||
|
MCP3428Resolution resolution_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcp3428
|
||||||
|
} // namespace esphome
|
12
tests/components/MCP3428/test.esp32-c3-idf.yaml
Normal file
12
tests/components/MCP3428/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 16
|
||||||
|
sda: 17
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
12
tests/components/MCP3428/test.esp32-c3.yaml
Normal file
12
tests/components/MCP3428/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 5
|
||||||
|
sda: 4
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
12
tests/components/MCP3428/test.esp32-idf.yaml.yaml
Normal file
12
tests/components/MCP3428/test.esp32-idf.yaml.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 16
|
||||||
|
sda: 17
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
12
tests/components/MCP3428/test.esp32.yaml
Normal file
12
tests/components/MCP3428/test.esp32.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 16
|
||||||
|
sda: 17
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
12
tests/components/MCP3428/test.esp8266.yaml
Normal file
12
tests/components/MCP3428/test.esp8266.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 5
|
||||||
|
sda: 4
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
12
tests/components/MCP3428/test.rp2040.yaml
Normal file
12
tests/components/MCP3428/test.rp2040.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_mcp3428
|
||||||
|
scl: 5
|
||||||
|
sda: 4
|
||||||
|
|
||||||
|
mcp3428:
|
||||||
|
address: 0b1101000
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: mcp3428
|
||||||
|
multiplexer: 1
|
||||||
|
id: mcp3428_sensor
|
Loading…
Reference in a new issue