add counter 0..2 for short pulses, high frequency, #2758

This commit is contained in:
MichaelDvP
2025-11-26 18:14:58 +01:00
parent 9edcf47073
commit 2bcd548747
7 changed files with 120 additions and 40 deletions

View File

@@ -33,6 +33,8 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
- pumpmode enum for HT3 boilers, add commands for manual defrost, chimneysweeper [#2727](https://github.com/emsesp/EMS-ESP32/issues/2727)
- pid settings [#2735](https://github.com/emsesp/EMS-ESP32/issues/2735)
- refresh MQTT button added to MQTT Settings page
- heating assistance, rounding custum settings [#2763](https://github.com/emsesp/EMS-ESP32/discussions/2763)
- add counter 0..2 for short pulses, high frequency [#2758](https://github.com/emsesp/EMS-ESP32/issues/2758)
## Fixed

View File

@@ -586,6 +586,7 @@ const Sensors = () => {
creating={creating}
selectedItem={selectedAnalogSensor}
analogGPIOList={sensorData.available_gpios}
disabledTypeList={sensorData.exclude_types}
validator={analogSensorItemValidation(sensorData.as, selectedAnalogSensor)}
/>
)}

View File

@@ -35,6 +35,7 @@ interface DashboardSensorsAnalogDialogProps {
creating: boolean;
selectedItem: AnalogSensor;
analogGPIOList: number[];
disabledTypeList: number[];
validator: Schema;
}
@@ -45,6 +46,7 @@ const SensorsAnalogDialog = ({
creating,
selectedItem,
analogGPIOList,
disabledTypeList,
validator
}: DashboardSensorsAnalogDialogProps) => {
const { LL } = useI18nContext();
@@ -66,7 +68,16 @@ const SensorsAnalogDialog = ({
// Memoize helper functions to check sensor type conditions
const isCounterOrRate = useMemo(
() => editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE,
() =>
editItem.t === AnalogType.COUNTER ||
editItem.t === AnalogType.RATE ||
(editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2),
[editItem.t]
);
const isCounter = useMemo(
() =>
editItem.t === AnalogType.COUNTER ||
(editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2),
[editItem.t]
);
const isFreqType = useMemo(
@@ -80,13 +91,13 @@ const SensorsAnalogDialog = ({
editItem.t === AnalogType.PWM_2,
[editItem.t]
);
const isDigitalOutGPIO = useMemo(
const isDACOutGPIO = useMemo(
() =>
editItem.t === AnalogType.DIGITAL_OUT &&
(editItem.g === 25 || editItem.g === 26),
[editItem.t, editItem.g]
);
const isDigitalOutNonGPIO = useMemo(
const isDigitalOutGPIO = useMemo(
() =>
editItem.t === AnalogType.DIGITAL_OUT &&
editItem.g !== 25 &&
@@ -98,7 +109,11 @@ const SensorsAnalogDialog = ({
const analogTypeMenuItems = useMemo(
() =>
AnalogTypeNames.map((val, i) => (
<MenuItem key={val} value={i + 1}>
<MenuItem
key={val}
value={i + 1}
disabled={disabledTypeList.includes(i + 1)}
>
{val}
</MenuItem>
)),
@@ -264,7 +279,7 @@ const SensorsAnalogDialog = ({
/>
</Grid>
)}
{editItem.t === AnalogType.COUNTER && (
{isCounter && (
<Grid>
<ValidatedTextField
name="o"
@@ -309,7 +324,7 @@ const SensorsAnalogDialog = ({
/>
</Grid>
)}
{isDigitalOutGPIO && (
{isDACOutGPIO && (
<Grid>
<ValidatedTextField
name="o"
@@ -325,7 +340,7 @@ const SensorsAnalogDialog = ({
/>
</Grid>
)}
{isDigitalOutNonGPIO && (
{isDigitalOutGPIO && (
<>
<Grid>
<ValidatedTextField

View File

@@ -112,6 +112,7 @@ export interface SensorData {
as: AnalogSensor[];
analog_enabled: boolean;
available_gpios: number[];
exclude_types: number[];
platform: string;
}
@@ -245,7 +246,10 @@ export enum AnalogType {
PULSE = 12,
FREQ_0 = 13,
FREQ_1 = 14,
FREQ_2 = 15
FREQ_2 = 15,
CNT_0 = 16,
CNT_1 = 17,
CNT_2 = 18
}
export const AnalogTypeNames = [
@@ -263,7 +267,10 @@ export const AnalogTypeNames = [
'Pulse', // 12
'Freq 0', // 13
'Freq 1', // 14
'Freq 2' // 15
'Freq 2', // 15
'Counter 0', // 16
'Counter 1', // 17
'Counter2' // 18
] as const;
export const BOARD_PROFILES = {

View File

@@ -22,6 +22,7 @@
namespace emsesp {
uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON};
std::vector<uint8_t> AnalogSensor::exclude_types_;
#ifndef EMSESP_STANDALONE
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
@@ -30,20 +31,26 @@ unsigned long AnalogSensor::edgecnt[] = {0, 0, 0};
void IRAM_ATTR AnalogSensor::freqIrq0() {
portENTER_CRITICAL_ISR(&mux);
if (micros() - edge[0] > 10) { // limit to 100kHz
edgecnt[0]++;
edge[0] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq1() {
portENTER_CRITICAL_ISR(&mux);
if (micros() - edge[1] > 10) { // limit to 100kHz
edgecnt[1]++;
edge[1] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
void IRAM_ATTR AnalogSensor::freqIrq2() {
portENTER_CRITICAL_ISR(&mux);
if (micros() - edge[2] > 10) { // limit to 100kHz
edgecnt[2]++;
edge[2] = micros();
}
portEXIT_CRITICAL_ISR(&mux);
}
#endif
@@ -97,6 +104,7 @@ void AnalogSensor::start(const bool factory_settings) {
// load settings from the customization file, sorts them and initializes the GPIOs
void AnalogSensor::reload(bool get_nvs) {
exclude_types_.clear();
EMSESP::webSettingsService.read([&](WebSettings & settings) { analog_enabled_ = settings.analog_enabled; });
#if defined(EMSESP_STANDALONE)
@@ -126,8 +134,8 @@ void AnalogSensor::reload(bool get_nvs) {
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
sensor_.set_value(sensor.offset);
}
if (sensor.type == AnalogType::COUNTER && sensor_.offset() != sensor.offset
&& sensor.offset != EMSESP::nvs_.getDouble(sensor.name.c_str(), 0)) {
if ((sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2))
&& sensor_.offset() != sensor.offset && sensor.offset != EMSESP::nvs_.getDouble(sensor.name.c_str(), 0)) {
EMSESP::nvs_.putDouble(sensor.name.c_str(), sensor.offset);
sensor_.set_value(sensor.offset);
}
@@ -160,7 +168,8 @@ void AnalogSensor::reload(bool get_nvs) {
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, type, sensor.is_system);
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT
|| (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2)) {
sensors_.back().set_value(sensor.offset);
} else {
sensors_.back().set_value(0); // reset value only for new sensors
@@ -169,12 +178,12 @@ void AnalogSensor::reload(bool get_nvs) {
// add the command to set the value of the sensor
if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE) {
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2)) {
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
sensor.name.c_str(),
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
sensor.type == AnalogType::COUNTER ? FL_(counter)
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: sensor.type == AnalogType::RGB ? FL_(RGB)
: sensor.type == AnalogType::PULSE ? FL_(pulse)
@@ -191,9 +200,20 @@ void AnalogSensor::reload(bool get_nvs) {
// activate each sensor
for (auto & sensor : sensors_) {
sensor.ha_registered = false; // force HA configs to be re-created
if ((sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2)) {
exclude_types_.push_back(sensor.type());
exclude_types_.push_back(sensor.type() + 3);
}
if ((sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
exclude_types_.push_back(sensor.type());
exclude_types_.push_back(sensor.type() - 3);
}
if ((sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2)) {
exclude_types_.push_back(sensor.type());
}
if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::RATE
|| sensor.type() == AnalogType::TIMER) {
|| sensor.type() == AnalogType::TIMER || (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::CNT_2)) {
// pullup is mapped to DAC, so set to 3.3V
#if CONFIG_IDF_TARGET_ESP32
if (sensor.gpio() == 25 || sensor.gpio() == 26) {
@@ -204,6 +224,7 @@ void AnalogSensor::reload(bool get_nvs) {
dacWrite(sensor.gpio(), 255);
}
#endif
pinMode(sensor.gpio(), INPUT_PULLUP);
}
if (sensor.type() == AnalogType::ADC) {
LOG_DEBUG("ADC Sensor on GPIO %02d", sensor.gpio());
@@ -216,7 +237,6 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.set_uom(DeviceValueUOM::DEGREES);
} else if (sensor.type() == AnalogType::COUNTER) {
LOG_DEBUG("I/O Counter on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.gpio());
if (double_t val = EMSESP::nvs_.getDouble(sensor.name().c_str(), 0)) {
@@ -225,7 +245,6 @@ void AnalogSensor::reload(bool get_nvs) {
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
LOG_DEBUG("Timer/Rate on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.polltime_ = uuid::get_uptime();
sensor.last_polltime_ = uuid::get_uptime();
sensor.poll_ = digitalRead(sensor.gpio());
@@ -234,25 +253,33 @@ void AnalogSensor::reload(bool get_nvs) {
publish_sensor(sensor);
#ifndef EMSESP_STANDALONE
} else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) {
LOG_DEBUG("Frequency on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
auto index = sensor.type() - AnalogType::FREQ_0;
LOG_DEBUG("Frequency %d on GPIO %02d", index, sensor.gpio());
sensor.set_offset(0);
sensor.set_value(0);
publish_sensor(sensor);
auto index = sensor.type() - AnalogType::FREQ_0;
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
lastedge[index] = edge[index] = micros();
edgecnt[index] = 0;
} else if (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2) {
auto index = sensor.type() - AnalogType::CNT_0;
LOG_DEBUG("Counter %d on GPIO %02d", index, sensor.gpio());
if (double_t val = EMSESP::nvs_.getDouble(sensor.name().c_str(), 0)) {
sensor.set_value(val);
}
publish_sensor(sensor);
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
edgecnt[index] = 0;
#endif
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG("Digital Read on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.set_value(digitalRead(sensor.gpio())); // initial value
sensor.set_uom(0); // no uom, just for safe measures
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::RGB) {
sensor.set_uom(0); // no uom
LOG_DEBUG("RGB on GPIO %02d", sensor.gpio());
uint32_t v = sensor.value();
uint8_t r = v / 10000;
@@ -295,7 +322,10 @@ void AnalogSensor::reload(bool get_nvs) {
} else {
sensor.set_offset(EMSESP::nvs_.getChar(sensor.name().c_str()));
}
} else if (sensor.uom() > 1) {
sensor.set_uom(2);
}
sensor.set_offset(sensor.offset() > 0 ? 1 : 0);
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
sensor.set_value(sensor.offset());
}
@@ -393,6 +423,18 @@ void AnalogSensor::measure() {
changed_ = true;
publish_sensor(sensor);
}
} else if (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2) {
auto index = sensor.type() - AnalogType::CNT_0;
auto oldval = sensor.value();
portENTER_CRITICAL_ISR(&mux);
auto c = edgecnt[index];
edgecnt[index] = 0;
portEXIT_CRITICAL_ISR(&mux);
sensor.set_value(oldval + sensor.factor() * c);
if (sensor.value() != oldval) {
changed_ = true;
publish_sensor(sensor);
}
#endif
}
}
@@ -453,7 +495,7 @@ void AnalogSensor::measure() {
// store counters to NVS, called every hour, on restart and update
void AnalogSensor::store_counters() {
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::COUNTER) {
if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name().c_str())) {
EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value());
}
@@ -746,7 +788,7 @@ void AnalogSensor::publish_values(const bool force) {
config["max"] = 999999;
config["mode"] = "box"; // auto, slider or box
config["step"] = 1;
} else if (sensor.type() == AnalogType::COUNTER) {
} else if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
config["cmd_t"] = command_topic;
@@ -833,10 +875,10 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["value"] = sensor.value();
output["readable"] = true;
output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2);
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2)|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2);
output["visible"] = true;
output["is_system"] = sensor.is_system();
if (sensor.type() == AnalogType::COUNTER) {
if (sensor.type() == AnalogType::COUNTER|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
output["min"] = 0;
output["max"] = 4000000;
output["start_value"] = sensor.offset();
@@ -899,7 +941,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
for (auto & sensor : sensors_) {
if (sensor.gpio() == gpio) {
double oldoffset = sensor.offset();
if (sensor.type() == AnalogType::COUNTER) {
if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
if (val < 0 || value[0] == '+') { // sign corrects values
// sensor.set_offset(sensor.value() + val);
sensor.set_value(sensor.value() + val);

View File

@@ -132,7 +132,10 @@ class AnalogSensor {
PULSE = 12,
FREQ_0 = 13,
FREQ_1 = 14,
FREQ_2 = 15
FREQ_2 = 15,
CNT_0 = 16,
CNT_1 = 17,
CNT_2 = 18
};
void start(const bool factory_settings = false);
@@ -174,6 +177,9 @@ class AnalogSensor {
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
void store_counters();
static std::vector<uint8_t> exclude_types() {
return exclude_types_;
}
private:
static constexpr double Beta = 4260;
@@ -185,7 +191,6 @@ class AnalogSensor {
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;
static uuid::log::Logger logger_;
void remove_ha_topic(const int8_t type, const uint8_t id) const;
bool command_setvalue(const char * value, const int8_t gpio);
void measure();
@@ -193,6 +198,7 @@ class AnalogSensor {
void get_value_json(JsonObject output, const Sensor & sensor);
std::vector<Sensor> sensors_; // our list of sensors
static std::vector<uint8_t> exclude_types_;
bool analog_enabled_;
bool changed_ = true; // this will force a publish of all sensors when initialising

View File

@@ -158,6 +158,12 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
available_gpios.add(gpio);
}
// disable types that can only be used once
JsonArray exclude_types = root["exclude_types"].to<JsonArray>();
for (uint8_t type : EMSESP::analogsensor_.exclude_types()) {
exclude_types.add(type);
}
response->setLength();
request->send(response);
}
@@ -462,7 +468,8 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
}
if (sensor.type() == AnalogSensor::AnalogType::COUNTER
|| (sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT && sensor.type() <= AnalogSensor::AnalogType::PWM_2)
|| sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE) {
|| sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE
|| (sensor.type() >= AnalogSensor::AnalogType::CNT_0 && sensor.type() <= AnalogSensor::AnalogType::CNT_2)) {
dv["c"] = sensor.name();
}
}