Merge pull request #361 from MichaelDvP/dev_mqtt

mqtt, analog, fahrenheit and other changes
This commit is contained in:
Proddy
2022-02-17 10:04:25 +01:00
committed by GitHub
32 changed files with 824 additions and 278 deletions

View File

@@ -22,6 +22,10 @@
- Help text for string commands in WebUI [#320](https://github.com/emsesp/EMS-ESP32/issues/320)
- Germany translations (at compile time)
- #entities added to system/info` endpoint [#322](https://github.com/emsesp/EMS-ESP32/issues/322)
- analog outputs digital/pwm/dac
- remove MQTT retained configs if discovery is disabled
- timeout 10 min for MQTT-QoS wait
- Moduline 300 auto-temperatures T1-T4, RC300 romminfluencefactor
### Fixed
@@ -38,6 +42,7 @@
- Fix uploading firmware on OSX [#345](https://github.com/emsesp/EMS-ESP32/issues/345)
- Non-nested MQTT would corrupt the json [#354](https://github.com/emsesp/EMS-ESP32/issues/354)
- Burner selected max power can have a value higher than 100% [#314](https://github.com/emsesp/EMS-ESP32/issues/314)
- some missing fahrenheit calculations
### Changed
@@ -51,6 +56,8 @@
- Show ems tx reads and writes separatly
- Show ems device handlers separated for received, fetched and pending handlers.
- Wired renamed to Ethernet
- removed system/pin command, new commands in analogsensors
- system/info device-info split to name/version/brand
## **BREAKING CHANGES:**

View File

@@ -183,29 +183,43 @@ const MqttSettingsForm: FC = () => {
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
label="Publish command output to a 'response' topic"
/>
<BlockFormControlLabel
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
label="Publish single value topics on change"
/>
<Grid item>
<BlockFormControlLabel
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
label="Enable MQTT Discovery (for Home Assistant, Domoticz)"
/>
</Grid>
{data.ha_enabled && (
<Grid item xs={6}>
<ValidatedTextField
name="discovery_prefix"
label="Prefix for the Discovery topic"
fullWidth
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item>
<BlockFormControlLabel
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
label="Publish single value topics on change"
/>
</Grid>
)}
{data.publish_single && (
<Grid item>
<BlockFormControlLabel
control={<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />}
label="publish to command topics (ioBroker)"
/>
</Grid>
)}
</Grid>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item>
<BlockFormControlLabel
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
label="Enable MQTT Discovery (for Home Assistant, Domoticz)"
/>
</Grid>
{data.ha_enabled && (
<Grid item xs={6}>
<ValidatedTextField
name="discovery_prefix"
label="Prefix for the Discovery topic"
fullWidth
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
)}
</Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Publish Intervals (in seconds, 0=automatic)
</Typography>

View File

@@ -59,7 +59,8 @@ import {
DeviceValue,
DeviceValueUOM,
DeviceValueUOM_s,
AnalogTypes,
AnalogType,
AnalogTypeNames,
Sensor,
Analog
} from './types';
@@ -613,7 +614,7 @@ const DashboardData: FC = () => {
{analog_data.i}
</StyledTableCell>
<StyledTableCell>{analog_data.n}</StyledTableCell>
<StyledTableCell>{AnalogTypes[analog_data.t]}</StyledTableCell>
<StyledTableCell>{AnalogTypeNames[analog_data.t]}</StyledTableCell>
<StyledTableCell align="right">{formatValue(analog_data.v, analog_data.u)}</StyledTableCell>
</StyledTableRow>
))}
@@ -708,14 +709,14 @@ const DashboardData: FC = () => {
</Grid>
<Grid item>
<ValidatedTextField name="t" label="Type" value={analog.t} select onChange={updateValue(setAnalog)}>
{AnalogTypes.map((val, i) => (
{AnalogTypeNames.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</ValidatedTextField>
</Grid>
{analog.t === 3 && (
{analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && (
<>
<Grid item>
<ValidatedTextField name="u" label="UoM" value={analog.u} select onChange={updateValue(setAnalog)}>
@@ -726,22 +727,37 @@ const DashboardData: FC = () => {
))}
</ValidatedTextField>
</Grid>
<Grid item></Grid>
<Grid item>
<ValidatedTextField
name="o"
label="Offset"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '3300', step: '1' }}
InputProps={{
endAdornment: <InputAdornment position="end">mV</InputAdornment>
}}
/>
</Grid>
{analog.t === AnalogType.ADC && (
<Grid item>
<ValidatedTextField
name="o"
label="Offset"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '3300', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">mV</InputAdornment>
}}
/>
</Grid>
)}
{analog.t === AnalogType.COUNTER && (
<Grid item>
<ValidatedTextField
name="o"
label="Start Value"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', step: '1' }}
/>
</Grid>
)}
<Grid item>
<ValidatedTextField
name="f"
@@ -756,6 +772,73 @@ const DashboardData: FC = () => {
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && (analog.i === 25 || analog.i === 26) && (
<>
<Grid item>
<ValidatedTextField
name="o"
label="DAC Value"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '255', step: '1' }}
/>
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && analog.i !== 25 && analog.i !== 26 && (
<>
<Grid item>
<ValidatedTextField
name="o"
label="Value"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '1', step: '1' }}
/>
</Grid>
</>
)}
{analog.t >= AnalogType.PWM_0 && (
<>
<Grid item>
<ValidatedTextField
name="f"
label="Frequency"
value={numberValue(analog.f)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '1', max: '5000', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">Hz</InputAdornment>
}}
/>
</Grid>
<Grid item>
<ValidatedTextField
name="o"
label="Dutycycle"
value={numberValue(analog.o)}
sx={{ width: '20ch' }}
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '100', step: '0.1' }}
InputProps={{
startAdornment: <InputAdornment position="start">%</InputAdornment>
}}
/>
</Grid>
</>
)}
</Grid>
<Box color="warning.main" mt={2}>
<Typography variant="body2">Warning: be careful when assigning a GPIO!</Typography>

View File

@@ -176,7 +176,9 @@ export enum DeviceValueUOM {
DBM,
FAHRENHEIT,
MV,
SQM
SQM,
M3,
L
}
export const DeviceValueUOM_s = [
@@ -199,10 +201,36 @@ export const DeviceValueUOM_s = [
'°F',
'mV',
'sqm',
"o'clock"
"m3",
"l"
];
export enum AnalogType {
NOTUSED = 0,
DIGITAL_IN,
COUNTER,
ADC,
TIMER,
RATE,
DIGITAL_OUT,
PWM_0,
PWM_1,
PWM_2
}
export const AnalogTypeNames = [
'(disabled)',
'Digital in',
'Counter',
'ADC',
'Timer',
'Rate',
'Digital out',
'PWM 0',
'PWM 1',
'PWM 2'
];
export const AnalogTypes = ['(disabled)', 'Digital in', 'Counter', 'ADC'];
type BoardProfiles = {
[name: string]: string;

View File

@@ -40,5 +40,6 @@ export interface MqttSettings {
nested_format: number;
send_response: boolean;
publish_single: boolean;
publish_single2cmd: boolean;
discovery_prefix: string;
}

View File

@@ -176,6 +176,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
root["nested_format"] = settings.nested_format;
root["discovery_prefix"] = settings.discovery_prefix;
root["publish_single"] = settings.publish_single;
root["publish_single2cmd"] = settings.publish_single2cmd;
root["send_response"] = settings.send_response;
}
@@ -203,11 +204,12 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
if (newSettings.enabled != settings.enabled) {
changed = true;
@@ -230,6 +232,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
changed = true;
}
if (newSettings.publish_single2cmd != settings.publish_single2cmd) {
changed = true;
}
if (newSettings.send_response != settings.send_response) {
changed = true;
}

View File

@@ -90,6 +90,7 @@ class MqttSettings {
uint8_t nested_format;
String discovery_prefix;
bool publish_single;
bool publish_single2cmd;
bool send_response;
static void read(MqttSettings & settings, JsonObject & root);

View File

@@ -27,7 +27,7 @@ void AnalogSensor::start() {
reload(); // fetch the list of sensors from our customization service
if (analog_enabled_) {
analogSetAttenuation(ADC_2_5db); // for all channels
analogSetAttenuation(ADC_2_5db); // for all channels 1.5V
}
LOG_INFO(F("Starting Analog sensor service"));
@@ -40,10 +40,15 @@ void AnalogSensor::start() {
F_(info_cmd));
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(counter),
[&](const char * value, const int8_t id) { return command_counter(value, id); },
F("set counter value"),
F_(setvalue),
[&](const char * value, const int8_t id) { return command_setvalue(value, id); },
F("set io value"),
CommandFlag::ADMIN_ONLY);
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd));
}
// load settings from the customization file, sorts them and initializes the GPIOs
@@ -58,11 +63,47 @@ void AnalogSensor::reload() {
// and store them locally and then activate them
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
auto sensors = settings.analogCustomizations;
sensors_.clear(); // start with an empty list
if (sensors.size() != 0) {
for (auto & sensor : sensors) {
auto it = sensors_.begin();
for (auto & sensor_ : sensors_) {
// update existing sensors
bool found = false;
for (auto & sensor : sensors) { //search customlist
if (sensor_.id() == sensor.id) {
// for output sensors set value to new start-value
if ((sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT)
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
sensor_.set_value(sensor.offset);
}
sensor_.set_name(sensor.name);
sensor_.set_type(sensor.type);
sensor_.set_offset(sensor.offset);
sensor_.set_factor(sensor.factor);
sensor_.set_uom(sensor.uom);
sensor_.ha_registered = false;
found = true;
}
}
if (!found) {
sensors_.erase(it);
}
it++;
}
// add new sensors from list
for (auto & sensor : sensors) {
bool found = false;
for (auto & sensor_ : sensors_) {
if (sensor_.id() == sensor.id) {
found = true;
}
}
if (!found) {
sensors_.emplace_back(sensor.id, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type);
sensors_.back().ha_registered = false; // this will trigger recrate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
sensors_.back().set_value(sensor.offset);
} else {
sensors_.back().set_value(0); // reset value only for new sensors
}
}
}
return true;
@@ -82,11 +123,21 @@ void AnalogSensor::reload() {
} else if (sensor.type() == AnalogType::COUNTER) {
LOG_DEBUG(F("Adding analog I/O Counter sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), INPUT_PULLUP);
sensor.set_value(0); // reset count
sensor.set_uom(0); // no uom, just for safe measures
if (sensor.id() == 25 || sensor.id() == 26) {
dacWrite(sensor.id(), 255);
}
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.id());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
LOG_DEBUG(F("Adding analog Timer/Rate sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), INPUT_PULLUP);
sensor.polltime_ = uuid::get_uptime();
sensor.last_polltime_ = uuid::get_uptime();
sensor.poll_ = digitalRead(sensor.id());
sensor.set_offset(0);
sensor.set_value(0);
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG(F("Adding analog Read sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), INPUT_PULLUP);
@@ -95,18 +146,49 @@ void AnalogSensor::reload() {
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.id());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG(F("Adding analog Write sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), OUTPUT);
if (sensor.id() == 25 || sensor.id() == 26) {
if (sensor.offset() > 255) {
sensor.set_offset(255);
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
dacWrite(sensor.id(), sensor.offset());
sensor.set_value(sensor.offset());
} else {
digitalWrite(sensor.id(), sensor.offset() > 0 ? 1 : 0);
sensor.set_value(digitalRead(sensor.id()));
}
sensor.set_uom(0); // no uom, just for safe measures
publish_sensor(sensor);
} else if (sensor.type() >= AnalogType::PWM_0) {
LOG_DEBUG(F("Adding PWM output sensor on GPIO%d"), sensor.id());
uint channel = sensor.type() - AnalogType::PWM_0;
ledcSetup(channel, sensor.factor(), 13);
ledcAttachPin(sensor.id(), channel);
if (sensor.offset() > 100) {
sensor.set_offset(100);
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
ledcWrite(channel, (uint32_t)(sensor.offset() * 8191 / 100));
sensor.set_value(sensor.offset());
sensor.set_uom(DeviceValueUOM::PERCENT);
publish_sensor(sensor);
}
}
}
// measure and moving average adc
// measure input sensors and moving average adc
void AnalogSensor::measure() {
static uint32_t measure_last_ = 0;
// measure interval 500ms for analog sensors
// measure interval 500ms for adc sensors
if (!measure_last_ || (uuid::get_uptime() - measure_last_) >= MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
// go through the list of ADC sensors
// go through the list of adc sensors
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::ADC) {
uint16_t a = analogReadMilliVolts(sensor.id()); // e.g. ADC1_CHANNEL_0_GPIO_NUM
@@ -128,34 +210,38 @@ void AnalogSensor::measure() {
}
}
}
// poll digital io every time
// poll digital io every time with debounce
// go through the list of digital sensors
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER) {
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::TIMER
|| sensor.type() == AnalogType::RATE) {
auto old_value = sensor.value(); // remember current value before reading
auto current_reading = digitalRead(sensor.id());
if (sensor.poll_ != current_reading) { // check for pinchange
sensor.polltime_ = uuid::get_uptime();
if (sensor.poll_ != current_reading) { // check for pinchange
sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange
sensor.poll_ = current_reading;
}
if (uuid::get_uptime() - sensor.polltime_ >= 15) { // debounce
// debounce and check for real pinchange
if (uuid::get_uptime() - sensor.polltime_ >= 15 && sensor.poll_ != sensor.last_reading_) {
sensor.last_reading_ = sensor.poll_;
if (sensor.type() == AnalogType::DIGITAL_IN) {
sensor.set_value(sensor.poll_);
} else if (sensor.type() == AnalogType::COUNTER) {
// capture reading and compare with the last one to see if there is high/low change
if (sensor.poll_ != sensor.last_reading_) {
sensor.last_reading_ = sensor.poll_;
if (!sensor.poll_) {
sensor.set_value(old_value + 1);
}
} else if (!sensor.poll_) { // falling edge
if (sensor.type() == AnalogType::COUNTER) {
sensor.set_value(old_value + sensor.factor());
} else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1
sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_));
} else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1
sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000);
}
sensor.last_polltime_ = sensor.polltime_;
}
// see if there is a change and increment # reads
if (old_value != sensor.value()) {
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
}
// see if there is a change and increment # reads
if (old_value != sensor.value()) {
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
}
}
@@ -170,7 +256,7 @@ void AnalogSensor::loop() {
}
// update analog information name and offset
bool AnalogSensor::update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type) {
bool AnalogSensor::update(uint8_t id, const std::string & name, float offset, float factor, uint8_t uom, int8_t type) {
boolean found_sensor = false; // see if we can find the sensor in our customization list
EMSESP::webCustomizationService.update(
@@ -240,7 +326,11 @@ bool AnalogSensor::updated_values() {
void AnalogSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str());
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str());
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(analogsensor)).c_str(), "_data", sensor.name().c_str());
}
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as floats
}
@@ -285,12 +375,16 @@ void AnalogSensor::publish_values(const bool force) {
dataSensor["name"] = sensor.name();
switch (sensor.type()) {
case AnalogType::COUNTER:
dataSensor["value"] = (uint16_t)sensor.value(); // convert to integer
break;
case AnalogType::TIMER:
case AnalogType::RATE:
case AnalogType::ADC:
case AnalogType::PWM_0:
case AnalogType::PWM_1:
case AnalogType::PWM_2:
dataSensor["value"] = (float)sensor.value(); // float
break;
case AnalogType::DIGITAL_IN:
case AnalogType::DIGITAL_OUT:
default:
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
break;
@@ -373,6 +467,15 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["offset"] = sensor.offset();
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() == AnalogType::COUNTER) {
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["start_value"] = sensor.offset();
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() >= AnalogType::PWM_0) {
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["frequency"] = sensor.factor();
}
dataSensor["value"] = sensor.value();
} else {
@@ -384,7 +487,7 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
}
// this creates the sensor, initializing everything
AnalogSensor::Sensor::Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type)
AnalogSensor::Sensor::Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type)
: id_(id)
, name_(name)
, offset_(offset)
@@ -405,28 +508,70 @@ std::string AnalogSensor::Sensor::name() const {
}
// set the counter value, id is gpio-no
bool AnalogSensor::command_counter(const char * value, const int8_t id) {
int val;
if (!Helpers::value2number(value, val)) {
bool AnalogSensor::command_setvalue(const char * value, const int8_t id) {
float val;
if (!Helpers::value2float(value, val)) {
return false;
}
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::COUNTER && sensor.id() == id) {
if (val < 0) { // negative values corrects
sensor.set_value(sensor.value() + val);
} else { // positive values are set
if (sensor.id() == id) {
if (sensor.type() == AnalogType::COUNTER) {
if (val < 0 || value[0] == '+') { // sign corrects values
sensor.set_offset(sensor.value() + val);
sensor.set_value(sensor.value() + val);
} else { // positive values are set
sensor.set_offset(val);
sensor.set_value(val);
}
publish_sensor(sensor);
return true;
} else if (sensor.type() == AnalogType::ADC) {
sensor.set_offset(val);
return true;
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
uint8_t v = val;
if ((sensor.id() == 25 || sensor.id() == 26) && v <= 255) {
sensor.set_offset(v);
sensor.set_value(v);
pinMode(sensor.id(), OUTPUT);
dacWrite(sensor.id(), sensor.offset());
publish_sensor(sensor);
return true;
} else if (v == 0 || v == 1) {
sensor.set_offset(v);
sensor.set_value(v);
pinMode(sensor.id(), OUTPUT);
digitalWrite(sensor.id(), sensor.offset() > 0 ? 1 : 0);
publish_sensor(sensor);
return true;
}
} else if (sensor.type() >= AnalogType::PWM_0) {
uint8_t channel = sensor.type() - AnalogType::PWM_0;
if (val > 100) {
val = 100;
} else if (val < 0) {
val = 0;
}
sensor.set_offset(val);
sensor.set_value(val);
ledcWrite(channel, (uint32_t)(val * 8191 / 100));
publish_sensor(sensor);
return true;
}
return true;
}
}
return false;
}
// list commands
bool AnalogSensor::command_commands(const char * value, const int8_t id, JsonObject & output) {
return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output);
}
// hard coded tests
#ifdef EMSESP_DEBUG
void AnalogSensor::test() {
// Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type);
// Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type);
sensors_.emplace_back(36, "test12", 0, 0.1, 17, AnalogType::ADC);
sensors_.back().set_value(12.4);

View File

@@ -36,10 +36,10 @@ class AnalogSensor {
public:
class Sensor {
public:
Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type);
Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type);
~Sensor() = default;
void set_offset(const uint16_t offset) {
void set_offset(const float offset) {
offset_ = offset;
}
@@ -68,7 +68,7 @@ class AnalogSensor {
factor_ = factor;
}
uint16_t offset() const {
float offset() const {
return offset_;
}
@@ -90,16 +90,18 @@ class AnalogSensor {
bool ha_registered = false;
uint16_t analog_ = 0; // ADC - average value
uint32_t sum_ = 0; // ADC - rolling sum
uint16_t last_reading_ = 0; // IO COUNTER & ADC - last reading
uint32_t polltime_ = 0; // digital IO & COUNTER debounce time
int poll_ = 0;
uint16_t analog_ = 0; // ADC - average value
uint32_t sum_ = 0; // ADC - rolling sum
uint16_t last_reading_ = 0; // IO COUNTER & ADC - last reading
uint16_t count_ = 0; // counter raw counts
uint32_t polltime_ = 0; // digital IO & COUNTER debounce time
int poll_ = 0;
uint32_t last_polltime_ = 0; // for timer
private:
uint8_t id_;
std::string name_;
uint16_t offset_;
float offset_;
float factor_;
uint8_t uom_;
float value_; // float because of the factor is a float
@@ -112,9 +114,15 @@ class AnalogSensor {
enum AnalogType : int8_t {
MARK_DELETED = -1, // mark for deletion
NOTUSED, // 0 - disabled
DIGITAL_IN, // 1
COUNTER, // 2
ADC // 3
DIGITAL_IN,
COUNTER,
ADC,
TIMER,
RATE,
DIGITAL_OUT,
PWM_0,
PWM_1,
PWM_2
};
void start();
@@ -149,7 +157,7 @@ class AnalogSensor {
return sensors_.size();
}
bool update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type);
bool update(uint8_t id, const std::string & name, float offset, float factor, uint8_t uom, int8_t type);
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id);
#ifdef EMSESP_DEBUG
@@ -163,9 +171,10 @@ class AnalogSensor {
static uuid::log::Logger logger_;
void remove_ha_topic(const uint8_t id);
bool command_counter(const char * value, const int8_t id);
bool command_setvalue(const char * value, const int8_t id);
void measure();
bool command_info(const char * value, const int8_t id, JsonObject & output);
bool command_commands(const char * value, const int8_t id, JsonObject & output);
std::vector<Sensor> sensors_; // our list of sensors

View File

@@ -317,14 +317,13 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co
}
// add a command to the list, which does return a json object as output
// flag is fixed to MqttSubFlag::MQTT_SUB_FLAG_NOSUB so there will be no topic subscribed to this
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) {
// if the command already exists for that device type don't add it
if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) {
return;
}
cmdfunctions_.emplace_back(device_type, (CommandFlag::MQTT_SUB_FLAG_NOSUB | flags), cmd, nullptr, cb, description); // callback for json is included
cmdfunctions_.emplace_back(device_type, flags, cmd, nullptr, cb, description); // callback for json is included
}
// see if a command exists for that device type

View File

@@ -32,10 +32,9 @@ enum CommandFlag : uint8_t {
MQTT_SUB_FLAG_DEFAULT = 0, // 0 no flags set, always subscribe to MQTT
MQTT_SUB_FLAG_HC = (1 << 0), // 1 TAG_HC1 - TAG_HC8
MQTT_SUB_FLAG_WWC = (1 << 1), // 2 TAG_WWC1 - TAG_WWC4
MQTT_SUB_FLAG_NOSUB = (1 << 2), // 4
MQTT_SUB_FLAG_WW = (1 << 3), // 8 TAG_DEVICE_DATA_WW
HIDDEN = (1 << 4), // 16 do not show in API or Web
ADMIN_ONLY = (1 << 5) // 32 requires authentication
MQTT_SUB_FLAG_WW = (1 << 2), // 4 TAG_DEVICE_DATA_WW
HIDDEN = (1 << 3), // 8 do not show in API or Web
ADMIN_ONLY = (1 << 4) // 16 requires authentication
};

View File

@@ -361,10 +361,10 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["id_str"] = sensor.id_str();
if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.name()] = (float)(sensor.temperature_c) / 10;
output[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
}
@@ -378,11 +378,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
output["id_str"] = sensor.id_str();
output["name"] = sensor.name();
if (Helpers::hasValue(sensor.temperature_c)) {
output["value"] = (float)(sensor.temperature_c) / 10;
output["value"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
output["type"] = F_(number);
output["min"] = -55;
output["max"] = 125;
output["min"] = Helpers::round2(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
output["max"] = Helpers::round2(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
output["unit"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
output["writeable"] = false;
return true;
@@ -395,7 +395,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
void DallasSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", sensor.name().c_str());
}
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
@@ -440,10 +444,10 @@ void DallasSensor::publish_values(const bool force) {
JsonObject dataSensor = doc.createNestedObject(sensor.id_str());
dataSensor["name"] = sensor.name();
if (has_value) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
} else if (has_value) {
doc[sensor.name()] = (float)(sensor.temperature_c) / 10;
doc[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
// create the HA MQTT config
@@ -459,11 +463,7 @@ void DallasSensor::publish_values(const bool force) {
snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
if (EMSESP::system_.fahrenheit()) {
config["unit_of_meas"] = FJSON("°F");
} else {
config["unit_of_meas"] = FJSON("°C");
}
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
char str[50];
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id_str().c_str());

View File

@@ -128,10 +128,6 @@
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#endif
#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
#endif
#ifndef EMSESP_DEFAULT_MQTT_QOS
#define EMSESP_DEFAULT_MQTT_QOS 0
#endif
@@ -160,6 +156,10 @@
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE2CMD
#define EMSESP_DEFAULT_PUBLISH_SINGLE2CMD false
#endif
#ifndef EMSESP_DEFAULT_SEND_RESPONSE
#define EMSESP_DEFAULT_SEND_RESPONSE false
#endif

View File

@@ -150,8 +150,15 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
MAKE_CF_CB(set_burn_period));
register_device_value(
DeviceValueTAG::TAG_BOILER_DATA, &burnMinPower_, DeviceValueType::UINT, nullptr, FL_(burnMinPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_power));
register_device_value(
DeviceValueTAG::TAG_BOILER_DATA, &burnMaxPower_, DeviceValueType::UINT, nullptr, FL_(burnMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_power));
register_device_value(DeviceValueTAG::TAG_BOILER_DATA,
&burnMaxPower_,
DeviceValueType::UINT,
nullptr,
FL_(burnMaxPower),
DeviceValueUOM::PERCENT,
MAKE_CF_CB(set_max_power),
0,
130);
register_device_value(
DeviceValueTAG::TAG_BOILER_DATA, &boilHystOn_, DeviceValueType::INT, nullptr, FL_(boilHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_on));
register_device_value(
@@ -318,7 +325,9 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
nullptr,
FL_(wwMaxPower),
DeviceValueUOM::PERCENT,
MAKE_CF_CB(set_ww_maxpower));
MAKE_CF_CB(set_ww_maxpower),
0,
130);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwCircPump_,
DeviceValueType::BOOL,

View File

@@ -155,10 +155,6 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
FL_(solarPumpTurnoffDiff),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_TurnoffDiff));
register_device_value(
DeviceValueTAG::TAG_NONE, &setting3_, DeviceValueType::UINT, nullptr, FL_(setting3), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMaxTemp));
register_device_value(
DeviceValueTAG::TAG_NONE, &setting4_, DeviceValueType::UINT, nullptr, FL_(setting4), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMinTemp));
register_device_value(DeviceValueTAG::TAG_NONE, &solarPower_, DeviceValueType::SHORT, nullptr, FL_(solarPower), DeviceValueUOM::W);
register_device_value(DeviceValueTAG::TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
register_device_value(DeviceValueTAG::TAG_NONE, &maxFlow_, DeviceValueType::UINT, FL_(div10), FL_(maxFlow), DeviceValueUOM::LMIN, MAKE_CF_CB(set_SM10MaxFlow));
@@ -177,10 +173,16 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
DeviceValueUOM::NONE,
MAKE_CF_CB(set_solarEnabled));
/* unknown values for testing and logging. Used by MichaelDvP
register_device_value(
DeviceValueTAG::TAG_NONE, &setting3_, DeviceValueType::UINT, nullptr, FL_(setting3), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMaxTemp));
register_device_value(
DeviceValueTAG::TAG_NONE, &setting4_, DeviceValueType::UINT, nullptr, FL_(setting4), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMinTemp));
register_device_value(DeviceValueTAG::TAG_NONE, &data11_, DeviceValueType::UINT, nullptr, FL_(data11), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_NONE, &data12_, DeviceValueType::UINT, nullptr, FL_(data12), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_NONE, &data1_, DeviceValueType::UINT, nullptr, FL_(data1), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_NONE, &data0_, DeviceValueType::UINT, nullptr, FL_(data0), DeviceValueUOM::NONE);
*/
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_device_value(DeviceValueTAG::TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
@@ -400,6 +402,7 @@ void Solar::process_SM10Config(std::shared_ptr<const Telegram> telegram) {
}
// SM10Monitor - type 0x97
// Solar(0x30) -> All(0x00), SM10Monitor(0x97), data: 00 00 00 22 00 00 D2 01 00 F6 2A 00 00
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
uint8_t solarpumpmod = solarPumpMod_;
@@ -436,11 +439,11 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
energy.pop_front();
}
energy.push_back(solarPower_);
uint32_t sum = 0;
int32_t sum = 0;
for (auto e : energy) {
sum += e;
}
energyLastHour_ = sum / 6; // counts in 0.1 Wh
energyLastHour_ = sum > 0 ? sum / 6 : 0; // counts in 0.1 Wh
has_update(&solarPower_);
has_update(&energyLastHour_);
}

View File

@@ -80,10 +80,14 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
monitor_typeids = {0x91};
set_typeids = {0xA8};
curve_typeids = {0x90};
timer_typeids = {0x8F};
if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor));
register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set));
register_telegram_type(curve_typeids[i], F("RC20Temp"), false, MAKE_PF_CB(process_RC20Temp));
register_telegram_type(timer_typeids[i], F("RC20Timer"), false, MAKE_PF_CB(process_RC20Timer));
}
}
// remote thermostat uses only 0xAF, register it also for master (in case of early detect)
@@ -345,6 +349,9 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
}
// set the flag saying we want its data during the next auto fetch
// monitor is broadcasted, but not frequently in some thermostats (IVT, #356)
toggle_fetch(monitor_typeids[hc_num - 1], toggle_);
if (set_typeids.size()) {
toggle_fetch(set_typeids[hc_num - 1], toggle_);
}
@@ -384,7 +391,7 @@ void Thermostat::publish_ha_config_hc(std::shared_ptr<Thermostat::HeatingCircuit
// before you had to have a seltemp and roomtemp for the HA to work, now its optional
bool have_current_room_temp = Helpers::hasValue(hc->roomTemp);
if (Mqtt::nested_format() == 1) {
if (Mqtt::is_nested()) {
// nested format
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num);
snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.hc%d.seltemp}}", hc_num);
@@ -727,6 +734,48 @@ void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->manualtemp, 29);
}
// 0x90 - for reading curve temperature from the RC20 thermostat (0x17)
//
void Thermostat::process_RC20Temp(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->nighttemp, 3); // 0:off, 1:manual, 2:auto
has_update(telegram, hc->daylowtemp, 4);
has_update(telegram, hc->daymidtemp, 5);
has_update(telegram, hc->daytemp, 6);
}
// 0x8F - for reading timer from the RC20 thermostat (0x17)
// data: 04 5D 01 78 24 5D 21 6E 43 5D 41 78 64 5D 61 78 84 5D 81 78 E7 90 E7 90 E7 90 E7
// data: 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 (offset 27)
// data: E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 (offset 54)
// data: 90 E7 90 01 00 00 01 01 00 01 01 00 01 01 00 01 01 00 00 (offset 81)
void Thermostat::process_RC20Timer(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1))
|| (!telegram->offset && telegram->message_length > 1 && !strlen(hc->switchtime1))) {
char data[sizeof(hc->switchtime1)];
uint8_t no = telegram->offset / 2;
uint8_t day = telegram->message_data[0] >> 5;
uint8_t temp = telegram->message_data[0] & 1;
uint8_t time = telegram->message_data[1];
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]);
if (day == 7) {
snprintf(data, sizeof(data), "%02d not_set", no);
} else {
snprintf(data, sizeof(data), "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), temp);
}
strlcpy(hc->switchtime1, data, sizeof(hc->switchtime1));
has_update(hc->switchtime1); // always publish
}
}
// type 0xAE - data from the RC20 thermostat (0x17) - not for RC20's
// 17 00 AE 00 80 12 2E 00 D0 00 00 64 (#data=8)
// https://github.com/emsesp/EMS-ESP/issues/361
@@ -1022,6 +1071,7 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
}
has_update(telegram, hc->roominfluence, 0);
has_update(telegram, hc->roominfl_factor, 1); // is * 10
has_update(telegram, hc->offsettemp, 2);
if (!is_fetch(summer2_typeids[hc->hc()])) {
has_update(telegram, hc->summertemp, 6);
@@ -1911,6 +1961,23 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
return true;
}
// set RC300 roominfluence factor
bool Thermostat::set_roominfl_factor(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
float val = 0;
if (!Helpers::value2float(value, val)) {
return false;
}
write_command(summer_typeids[hc->hc()], 1, (uint8_t)(val * 10));
return true;
}
// sets the thermostat working mode, where mode is a string
// converts string mode to HeatingCircuit::Mode
bool Thermostat::set_mode(const char * value, const int8_t id) {
@@ -2302,14 +2369,25 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
data[1] = time;
}
if (no > 41 || time > 0x90 || (on > 1 && on != 7)) {
LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value);
uint8_t max_on = 3;
if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) {
max_on = 1;
}
if (no > 41 || time > 0x90 || (on > max_on && on != 7)) {
// LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value);
// LOG_WARNING(F("Setting switchtime: Invalid data: %02d.%1d.0x%02X.%1d"), no, day, time, on);
return false;
}
if (data[0] != 0xE7) {
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]);
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) {
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
} else if (model() == EMS_DEVICE_FLAG_RC20) {
snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on);
} else {
std::string son = read_flash_string(FL_(enum_switchmode)[on]);
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), son.c_str());
}
} else {
snprintf(out, len, "%02d not_set", no);
}
@@ -2449,6 +2527,26 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
}
} else if (model == EMS_DEVICE_FLAG_RC20) {
switch (mode) {
case HeatingCircuit::Mode::NIGHT:
offset = 3;
set_typeid = curve_typeids[hc->hc()];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::DAYLOW:
offset = 4;
set_typeid = curve_typeids[hc->hc()];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::DAYMID:
offset = 5;
set_typeid = curve_typeids[hc->hc()];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::DAY:
offset = 6;
set_typeid = curve_typeids[hc->hc()];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::MANUAL:
offset = EMS_OFFSET_RC20Set_temp_manual;
break;
@@ -2482,7 +2580,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = 0x0A; // manual offset
break;
case HeatingCircuit::Mode::TEMPAUTO:
offset = 0x08; // manual offset
offset = 0x08; // auto offset
if (temperature == -1) {
factor = 1; // to write 0xFF
}
@@ -2758,6 +2856,14 @@ bool Thermostat::set_daytemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAY);
}
bool Thermostat::set_daylowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAYLOW);
}
bool Thermostat::set_daymidtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::DAYMID);
}
bool Thermostat::set_comforttemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT);
}
@@ -3101,7 +3207,9 @@ void Thermostat::register_device_values() {
FL_(div10),
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_calinttemp));
MAKE_CF_CB(set_calinttemp),
-5,
5);
register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA,
&ibaMinExtTemperature_,
DeviceValueType::INT,
@@ -3269,6 +3377,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp));
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_roominfluence));
register_device_value(
tag, &hc->roominfl_factor, DeviceValueType::UINT, FL_(div10), FL_(roominfl_factor), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfl_factor));
register_device_value(tag, &hc->curroominfl, DeviceValueType::SHORT, FL_(div10), FL_(curroominfl), DeviceValueUOM::DEGREES_R);
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
@@ -3292,6 +3402,11 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
case EMS_DEVICE_FLAG_RC20:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp));
register_device_value(tag, &hc->daylowtemp, DeviceValueType::UINT, FL_(div2), FL_(daylowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daylowtemp));
register_device_value(tag, &hc->daymidtemp, DeviceValueType::UINT, FL_(div2), FL_(daymidtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daymidtemp));
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(dayhightemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp));
register_device_value(tag, &hc->switchtime1, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime1));
break;
case EMS_DEVICE_FLAG_RC20_N:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));

View File

@@ -43,6 +43,8 @@ class Thermostat : public EMSdevice {
uint8_t summermode;
uint8_t holidaymode;
uint8_t daytemp;
uint8_t daylowtemp;
uint8_t daymidtemp;
uint8_t nighttemp;
uint8_t holidaytemp;
uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
@@ -54,6 +56,7 @@ class Thermostat : public EMSdevice {
uint8_t manualtemp;
uint8_t summer_setmode;
uint8_t roominfluence;
uint8_t roominfl_factor;
int16_t curroominfl;
uint8_t flowtempoffset;
uint8_t minflowtemp;
@@ -125,6 +128,8 @@ class Thermostat : public EMSdevice {
TEMPAUTO,
NOREDUCE,
ON,
DAYLOW,
DAYMID,
UNKNOWN
};
@@ -313,6 +318,8 @@ class Thermostat : public EMSdevice {
void process_RC30Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC20Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Temp(std::shared_ptr<const Telegram> telegram);
void process_RC20Timer(std::shared_ptr<const Telegram> telegram);
void process_RC20Remote(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram);
void process_RC20Set_2(std::shared_ptr<const Telegram> telegram);
@@ -358,6 +365,8 @@ class Thermostat : public EMSdevice {
bool set_temp(const char * value, const int8_t id);
bool set_nighttemp(const char * value, const int8_t id);
bool set_daytemp(const char * value, const int8_t id);
bool set_daylowtemp(const char * value, const int8_t id);
bool set_daymidtemp(const char * value, const int8_t id);
bool set_comforttemp(const char * value, const int8_t id);
bool set_nofrosttemp(const char * value, const int8_t id);
bool set_ecotemp(const char * value, const int8_t id);
@@ -371,6 +380,7 @@ class Thermostat : public EMSdevice {
bool set_noreducetemp(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id);
bool set_roominfl_factor(const char * value, const int8_t id);
bool set_flowtempoffset(const char * value, const int8_t id);
bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id);

View File

@@ -52,6 +52,9 @@ const std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
}
const std::string EMSdevice::uom_to_string(uint8_t uom) {
if (EMSESP::system_.fahrenheit() && (uom == DeviceValueUOM::DEGREES || uom == DeviceValueUOM::DEGREES_R)) {
return read_flash_string(DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT]);
}
return read_flash_string(DeviceValue::DeviceValueUOM_s[uom]);
}
@@ -306,11 +309,7 @@ void EMSdevice::list_device_entries(JsonObject & output) {
// add uom
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
if (EMSESP::system_.fahrenheit() && (dv.uom == DeviceValueUOM::DEGREES || dv.uom == DeviceValueUOM::DEGREES_R)) {
details.add(EMSdevice::uom_to_string(DeviceValueUOM::FAHRENHEIT));
} else {
details.add(EMSdevice::uom_to_string(dv.uom));
}
details.add(EMSdevice::uom_to_string(dv.uom));
}
}
}
@@ -533,16 +532,26 @@ void EMSdevice::publish_value(void * value_p) {
for (auto & dv : devicevalues_) {
if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_HC8)
|| (dv.tag >= DeviceValueTAG::TAG_WWC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) {
if (Mqtt::publish_single2cmd()) {
if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) {
snprintf(topic,
sizeof(topic),
"%s/%s/%s",
device_type_2_device_name(device_type_).c_str(),
tag_to_mqtt(dv.tag).c_str(),
read_flash_string(dv.short_name).c_str());
} else {
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str());
}
} else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) {
snprintf(topic,
sizeof(topic),
"%s/%s/%s",
device_type_2_device_name(device_type_).c_str(),
Mqtt::tag_to_topic(device_type_, dv.tag).c_str(),
tag_to_mqtt(dv.tag).c_str(),
read_flash_string(dv.short_name).c_str());
} else {
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str());
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str());
}
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
@@ -1014,7 +1023,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// add uom if it's not a " " (single space)
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
json["uom"] = fahrenheit ? "°F" : uom_to_string(dv.uom);
json["uom"] = uom_to_string(dv.uom);
}
json["writeable"] = dv.has_cmd;

View File

@@ -66,7 +66,9 @@ class DeviceValue {
DBM, // 15
FAHRENHEIT, // 16
MV, // 17
SQM // 18
SQM, // 18 squaremeter
M3, // 19 cubic meter
L // 20
};
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp

View File

@@ -1136,6 +1136,9 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
fetch_device_values(device_id); // go and fetch its data
// Print to LOG showing we've added a new device
LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id);
// add command commands for all devices, except for connect, controller and gateway
if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) {
return true;
@@ -1170,9 +1173,6 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// MQTT subscribe to the device e.g. "ems-esp/boiler/#"
Mqtt::subscribe(device_type, EMSdevice::device_type_2_device_name(device_type) + "/#", nullptr);
// Print to LOG showing we've added a new device
LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id);
return true;
}
@@ -1413,6 +1413,28 @@ void EMSESP::start() {
webServer.begin(); // start the web server
}
// fetch devices one by one
void EMSESP::scheduled_fetch_values() {
static uint8_t no = 0;
if (no || (uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
if (!no) {
last_fetch_ = uuid::get_uptime();
no = 1;
}
if (txservice_.tx_queue_empty()) {
uint8_t i = 0;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && ++i >= no) {
emsdevice->fetch_values();
no++;
return;
}
}
no = 0;
}
}
}
// main loop calling all services
void EMSESP::loop() {
esp8266React.loop(); // web services
@@ -1429,10 +1451,7 @@ void EMSESP::loop() {
mqtt_.loop(); // sends out anything in the MQTT queue
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
last_fetch_ = uuid::get_uptime();
fetch_device_values();
}
scheduled_fetch_values();
}
console_.loop(); // telnet/serial console

View File

@@ -206,6 +206,7 @@ class EMSESP {
static void fetch_device_values(const uint8_t device_id = 0);
static void fetch_device_values_type(const uint8_t device_type);
static bool valid_device(const uint8_t device_id);
static void scheduled_fetch_values();
static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand);
static void scan_devices();

View File

@@ -526,6 +526,10 @@ bool Helpers::value2float(const char * v, float & value) {
value = atof((char *)v);
return true;
}
if (v[0] == '+' && (v[1] == '.' || (v[1] >= '0' && v[1] <= '9'))) {
value = atof((char *)(v + 1));
return true;
}
return false;
}

View File

@@ -70,7 +70,7 @@ MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(counter)
MAKE_PSTR_WORD(setvalue)
// for commands
MAKE_PSTR_WORD(call)
@@ -121,7 +121,7 @@ MAKE_STR(productid_fmt, "%s EMS ProductID")
MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all"))
MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown))
MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"))
MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2"))
// strings
MAKE_PSTR(EMSESP, "EMS-ESP")
@@ -196,6 +196,8 @@ MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(fahrenheit, "°F")
MAKE_PSTR(mv, "mV")
MAKE_PSTR(sqm, "sqm")
MAKE_PSTR(m3, "m3")
MAKE_PSTR(l, "l")
// MAKE_PSTR(times, "mal")
// MAKE_PSTR(oclock, "Uhr")
@@ -585,6 +587,9 @@ MAKE_PSTR_LIST(mode, F("mode"), F("modus"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("modus Typ"))
MAKE_PSTR_LIST(fastheatup, F("fastheatup"), F("fast heatup"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("Tagestemperatur"))
MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("Tagestemperatur T2"))
MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("Tagestemperatur T3"))
MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("Tagestemperatur T4"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("Heizen Temperatur"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("Nachttemperatur"))
MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco Temperatur"))
@@ -597,7 +602,8 @@ MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("Temperaturanhebung"))
MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min Flusstemperatur"))
MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max Flusstemperatur"))
MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("Raumeinfluss"))
MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("current room influence"))
MAKE_PSTR_LIST(roominfl_factor, F("roominflfactor"), F("Raumeinfluss Factor"))
MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("aktueller Raumeinfluss"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("Frostschutztemperatur"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("berechnete Flusstemperatur"))
MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("Heizungstyp"))

View File

@@ -70,7 +70,7 @@ MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(counter)
MAKE_PSTR_WORD(setvalue)
// for commands
MAKE_PSTR_WORD(call)
@@ -121,7 +121,7 @@ MAKE_STR(productid_fmt, "%s EMS ProductID")
MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all"))
MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown))
MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"))
MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2"))
// strings
MAKE_PSTR(EMSESP, "EMS-ESP")
@@ -196,6 +196,8 @@ MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(fahrenheit, "°F")
MAKE_PSTR(mv, "mV")
MAKE_PSTR(sqm, "sqm")
MAKE_PSTR(m3, "m3")
MAKE_PSTR(l, "l")
// MAKE_PSTR(times, "times")
// MAKE_PSTR(oclock, "o'clock")
@@ -545,6 +547,7 @@ MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("maximum temperature"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("one time key function"))
// mqtt values / commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("program switchtime"))
MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime"))
MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime"))
MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime"))
@@ -585,6 +588,9 @@ MAKE_PSTR_LIST(mode, F("mode"), F("mode"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("mode type"))
MAKE_PSTR_LIST(fastheatup, F("fastheatup"), F("fast heatup"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("day temperature"))
MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("day temperature T2"))
MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("day temperature T3"))
MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("day temperature T4"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature"))
MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco temperature"))
@@ -597,6 +603,7 @@ MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("offset temperature"))
MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min flow temperature"))
MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max flow temperature"))
MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("room influence"))
MAKE_PSTR_LIST(roominfl_factor, F("roominflfactor"), F("room influence factor"))
MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("current room influence"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("nofrost temperature"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("target flow temperature"))

View File

@@ -41,6 +41,7 @@ uint8_t Mqtt::nested_format_;
std::string Mqtt::discovery_prefix_;
bool Mqtt::send_response_;
bool Mqtt::publish_single_;
bool Mqtt::publish_single2cmd_;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
@@ -264,6 +265,14 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
LOG_DEBUG(F("Received topic `%s`"), topic);
}
#endif
// remove HA topics if we don't use discovery
if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) {
if (!ha_enabled_ && len) { // don't ping pong the empty message
queue_publish_message(topic, "", true);
LOG_DEBUG(F("Remove topic %s"), topic);
}
return;
}
// check first againts any of our subscribed topics
for (const auto & mf : mqtt_subfunctions_) {
@@ -290,7 +299,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
// convert payload into a json doc
// if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value'
if (len != 0) {
DeserializationError error = deserializeJson(input_doc, message);
DeserializationError error = deserializeJson(input_doc, (const char *)message);
if ((!input_doc.containsKey("value") && !input_doc.containsKey("data")) || error) {
input_doc.clear();
input_doc["value"] = (const char *)message; // always a string
@@ -387,15 +396,16 @@ void Mqtt::reset_mqtt() {
void Mqtt::load_settings() {
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain;
mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
nested_format_ = mqttSettings.nested_format;
publish_single_ = mqttSettings.publish_single;
send_response_ = mqttSettings.send_response;
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain;
mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
nested_format_ = mqttSettings.nested_format;
publish_single_ = mqttSettings.publish_single;
publish_single2cmd_ = mqttSettings.publish_single2cmd;
send_response_ = mqttSettings.send_response;
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
// convert to milliseconds
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
@@ -444,14 +454,6 @@ void Mqtt::start() {
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
LOG_INFO(F("MQTT disconnected: Not authorized"));
}
// remove message with pending ack
if (!mqtt_messages_.empty()) {
auto mqtt_message = mqtt_messages_.front();
if (mqtt_message.packet_id_ != 0) {
mqtt_messages_.pop_front();
}
}
// mqtt_messages_.clear();
});
// create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference
@@ -570,9 +572,23 @@ void Mqtt::on_connect() {
#endif
publish(F_(info), doc.as<JsonObject>()); // topic called "info"
// create the EMS-ESP device in HA, which is MQTT retained
if (ha_enabled()) {
ha_status();
if (ha_enabled_) {
queue_unsubscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#");
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
} else {
queue_subscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#");
LOG_INFO(F("start removing topics %s/+/%s/#"), discovery_prefix_.c_str(), mqtt_base_.c_str());
}
// send initial MQTT messages for some of our services
@@ -582,8 +598,6 @@ void Mqtt::on_connect() {
// re-subscribe to all custom registered MQTT topics
resubscribe();
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
mqtt_publish_fails_ = 0; // reset fail count to 0
@@ -669,6 +683,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front();
LOG_WARNING(F("Queue overflow, removing one message"));
mqtt_publish_fails_++;
}
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
@@ -688,6 +703,11 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_subscribe_message(const std::stri
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
}
// add MQTT unsubscribe message to queue
std::shared_ptr<const MqttMessage> Mqtt::queue_unsubscribe_message(const std::string & topic) {
return queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload
}
// MQTT Publish, using a user's retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload) {
queue_publish_message(topic, payload, mqtt_retain_);
@@ -712,11 +732,6 @@ void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
publish_retain(topic, payload, mqtt_retain_);
}
// no payload
void Mqtt::publish(const std::string & topic) {
queue_publish_message(topic, "", false);
}
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
queue_publish_message(read_flash_string(topic), payload, retain);
@@ -750,7 +765,7 @@ void Mqtt::publish_ha(const std::string & topic) {
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str());
#endif
publish(fulltopic);
queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker
}
// publish a Home Assistant config topic and payload, with retain flag off.
@@ -792,12 +807,29 @@ void Mqtt::process_queue() {
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str());
}
// if this has already been published and we're waiting for an ACK, don't publish again
// it will have a real packet ID
if (mqtt_message.packet_id_ > 0) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK"));
#endif
// if we don't get the ack within 10 minutes, republish with new packet_id
if (uuid::get_uptime_sec() - last_publish_queue_ < 600) {
return;
}
}
last_publish_queue_ = uuid::get_uptime_sec();
// if we're subscribing...
if (message->operation == Operation::SUBSCRIBE) {
LOG_DEBUG(F("Subscribing to topic '%s'"), topic);
uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_);
if (!packet_id) {
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
return;
}
LOG_ERROR(F("Error subscribing to topic '%s'"), topic);
mqtt_publish_fails_++; // increment failure counter
}
mqtt_messages_.pop_front(); // remove the message from the queue
@@ -805,12 +837,20 @@ void Mqtt::process_queue() {
return;
}
// if this has already been published and we're waiting for an ACK, don't publish again
// it will have a real packet ID
if (mqtt_message.packet_id_ > 0) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK"));
#endif
// if we're unsubscribing...
if (message->operation == Operation::UNSUBSCRIBE) {
LOG_DEBUG(F("Subscribing to topic '%s'"), topic);
uint16_t packet_id = mqttClient_->unsubscribe(topic);
if (!packet_id) {
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
return;
}
LOG_ERROR(F("Error unsubscribing to topic '%s'"), topic);
mqtt_publish_fails_++; // increment failure counter
}
mqtt_messages_.pop_front(); // remove the message from the queue
return;
}
@@ -992,8 +1032,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
return;
}
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
bool is_nested = (nested_format_ == 1); // nested_format is 1 if nested, otherwise 2 for single topics
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
// build the payload
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
@@ -1069,7 +1108,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// value template
// if its nested mqtt format then use the appended entity name, otherwise take the original
char val_tpl[75];
if (is_nested) {
if (is_nested()) {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
} else {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str());
@@ -1193,10 +1232,11 @@ const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
}
// if there is a tag add it
if ((EMSdevice::tag_to_mqtt(tag).empty()) || ((nested_format_ == 1) && (device_type != EMSdevice::DeviceType::BOILER))) {
return EMSdevice::device_type_2_device_name(device_type) + "_data";
} else {
if (!EMSdevice::tag_to_mqtt(tag).empty()
&& ((device_type == EMSdevice::DeviceType::BOILER && tag == DeviceValueTAG::TAG_DEVICE_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) {
return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag);
} else {
return EMSdevice::device_type_2_device_name(device_type) + "_data";
}
}

View File

@@ -29,9 +29,6 @@
using uuid::console::Shell;
// time between HA publishes
#define MQTT_HA_PUBLISH_DELAY 50
// size of queue
#define MAX_MQTT_MESSAGES 300
@@ -70,16 +67,10 @@ class Mqtt {
void set_publish_time_sensor(uint16_t publish_time);
bool get_publish_onchange(uint8_t device_type);
enum Operation { PUBLISH, SUBSCRIBE };
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
enum NestedFormat : uint8_t { NESTED = 1, SINGLE };
enum HA_Climate_Format : uint8_t {
CURRENT = 1, // 1
SETPOINT, // 2
ZERO // 3
};
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = FACTORY_MQTT_MAX_TOPIC_LENGTH; // fixed, not a user setting anymore
static void on_connect();
@@ -92,7 +83,6 @@ class Mqtt {
static void publish(const std::string & topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const std::string & payload);
static void publish(const std::string & topic);
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
@@ -125,10 +115,6 @@ class Mqtt {
static void ha_status();
void disconnect() {
mqttClient_->disconnect();
}
#if defined(EMSESP_DEBUG)
void incoming(const char * topic, const char * payload = ""); // for testing only
#endif
@@ -179,13 +165,10 @@ class Mqtt {
static void reset_mqtt();
// nested_format is 1 if nested, otherwise 2 for single topics
static uint8_t nested_format() {
return nested_format_;
}
static bool is_nested() {
return nested_format_ == 1;
return nested_format_ == NestedFormat::NESTED;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
}
@@ -193,6 +176,11 @@ class Mqtt {
static bool publish_single() {
return publish_single_;
}
static bool publish_single2cmd() {
return publish_single2cmd_;
}
static void publish_single(bool publish_single) {
publish_single_ = publish_single;
}
@@ -200,6 +188,7 @@ class Mqtt {
static bool ha_enabled() {
return ha_enabled_;
}
static void ha_enabled(bool ha_enabled) {
ha_enabled_ = ha_enabled;
}
@@ -207,6 +196,7 @@ class Mqtt {
static bool send_response() {
return send_response_;
}
static void send_response(bool send_response) {
send_response_ = send_response;
}
@@ -232,7 +222,7 @@ class Mqtt {
uint16_t packet_id_;
~QueuedMqttMessage() = default;
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
QueuedMqttMessage(uint32_t id, std::shared_ptr<MqttMessage> && content)
: id_(id)
, content_(std::move(content)) {
retry_count_ = 0;
@@ -254,6 +244,7 @@ class Mqtt {
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, bool retain);
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
static std::shared_ptr<const MqttMessage> queue_unsubscribe_message(const std::string & topic);
void on_publish(uint16_t packetId);
void on_message(const char * topic, const char * payload, size_t len);
@@ -281,6 +272,7 @@ class Mqtt {
uint32_t last_publish_mixer_ = 0;
uint32_t last_publish_other_ = 0;
uint32_t last_publish_sensor_ = 0;
uint32_t last_publish_queue_ = 0;
static bool connecting_;
static bool initialized_;
@@ -303,6 +295,7 @@ class Mqtt {
static uint8_t nested_format_;
static std::string discovery_prefix_;
static bool publish_single_;
static bool publish_single2cmd_;
static bool send_response_;
};

View File

@@ -73,18 +73,18 @@ bool System::command_pin(const char * value, const int8_t id) {
} else if (Helpers::value2bool(value, v)) {
pinMode(id, OUTPUT);
digitalWrite(id, v);
LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW");
// LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW");
return true;
} else if (Helpers::value2string(value, v1)) {
if (v1 == "input" || v1 == "in" || v1 == "-1") {
pinMode(id, INPUT);
v = digitalRead(id);
LOG_INFO(F("GPIO %d set input, state %s"), id, v ? "HIGH" : "LOW");
// LOG_INFO(F("GPIO %d set input, state %s"), id, v ? "HIGH" : "LOW");
return true;
}
}
LOG_INFO(F("GPIO %d: invalid value"), id);
// LOG_INFO(F("GPIO %d: invalid value"), id);
#endif
return false;
@@ -162,13 +162,19 @@ bool System::command_publish(const char * value, const int8_t id) {
bool System::command_syslog_level(const char * value, const int8_t id) {
uint8_t s = 0xff;
if (Helpers::value2enum(value, s, FL_(enum_syslog_level))) {
bool changed = false;
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.syslog_level = (int8_t)s - 1;
if (settings.syslog_level != (int8_t)s - 1) {
settings.syslog_level = (int8_t)s - 1;
changed = true;
}
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::system_.syslog_init();
if (changed) {
EMSESP::system_.syslog_init();
}
return true;
}
return false;
@@ -176,29 +182,35 @@ bool System::command_syslog_level(const char * value, const int8_t id) {
// watch
bool System::command_watch(const char * value, const int8_t id) {
uint8_t w = 0xff;
uint8_t w = 0xff;
uint16_t i = Helpers::hextoint(value);
if (Helpers::value2enum(value, w, FL_(enum_watch))) {
if (w == 0 || EMSESP::watch() == EMSESP::Watch::WATCH_OFF) {
EMSESP::watch_id(0);
}
EMSESP::watch(w);
if (Mqtt::publish_single()) {
Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[w]).c_str());
if (Mqtt::publish_single() && w != EMSESP::watch()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::publish(F("system/watch"),
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : read_flash_string(FL_(enum_watch)[w]).c_str());
} else {
Mqtt::publish(F("system_data/watch"),
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : read_flash_string(FL_(enum_watch)[w]).c_str());
}
}
EMSESP::watch(w);
return true;
}
uint16_t i = Helpers::hextoint(value);
if (i) {
} else if (i) {
if (Mqtt::publish_single() && i != EMSESP::watch_id()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::publish(F("system/watch"), Helpers::hextoa(i));
} else {
Mqtt::publish(F("system_data/watch"), Helpers::hextoa(i));
}
}
EMSESP::watch_id(i);
if (EMSESP::watch() == EMSESP::Watch::WATCH_OFF) {
EMSESP::watch(EMSESP::Watch::WATCH_ON);
}
if (Mqtt::publish_single()) {
char s[10];
snprintf(s, sizeof(s), "0x%04X", i);
Mqtt::publish(F("system/watch"), s);
// Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
}
return true;
}
return false;
@@ -273,13 +285,25 @@ void System::syslog_init() {
}
if (Mqtt::publish_single()) {
Mqtt::publish(F("system/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
if (Mqtt::publish_single2cmd()) {
Mqtt::publish(F("system/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish(F("system/watch"),
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch())
: read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
} else {
Mqtt::publish(F("system/watch"), Helpers::hextoa(EMSESP::watch_id()));
}
} else {
char s[10];
snprintf(s, sizeof(s), "0x%04X", EMSESP::watch_id());
Mqtt::publish(F("system/watch"), s);
Mqtt::publish(F("system_data/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish(F("system_data/watch"),
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch())
: read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
} else {
Mqtt::publish(F("system_data/watch"), Helpers::hextoa(EMSESP::watch_id()));
}
}
}
#endif
@@ -524,7 +548,7 @@ bool System::heartbeat_json(JsonObject & output) {
output["txfails"] = EMSESP::txservice_.telegram_read_fail_count() + EMSESP::txservice_.telegram_write_fail_count();
if (Mqtt::enabled()) {
output["mqttfails"] = Mqtt::publish_fails();
output["mqttcount"] = Mqtt::publish_count();
output["mqttfails"] = Mqtt::publish_fails();
}
output["apicalls"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
@@ -636,16 +660,12 @@ void System::system_check() {
// commands - takes static function pointers
void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM,
F_(pin),
System::command_pin,
F("set a GPIO on/off"),
CommandFlag::MQTT_SUB_FLAG_NOSUB | CommandFlag::ADMIN_ONLY); // dont create a MQTT topic for this
// Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set a GPIO on/off"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restart EMS-ESP"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, F("watch incoming telegrams"));
// register syslog command in syslog init
// Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, F("set syslog level"), CommandFlag::ADMIN_ONLY);
if (Mqtt::enabled()) {
@@ -822,7 +842,7 @@ void System::show_system(uuid::console::Shell & shell) {
// show Ethernet if connected
if (ethernet_connected_) {
shell.println();
shell.printfln(F(" Wired Network: connected"));
shell.printfln(F(" Ethernet Network: connected"));
shell.printfln(F(" MAC address: %s"), ETH.macAddress().c_str());
shell.printfln(F(" Hostname: %s"), ETH.getHostname());
shell.printfln(F(" IPv4 address: %s/%s"), uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
@@ -1021,6 +1041,13 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
sensorJson["offset"] = sensor.offset;
sensorJson["factor"] = sensor.factor;
sensorJson["uom"] = EMSdevice::uom_to_string(sensor.uom);
} else if (sensor.type == AnalogSensor::AnalogType::COUNTER || sensor.type == AnalogSensor::AnalogType::TIMER
|| sensor.type == AnalogSensor::AnalogType::RATE) {
sensorJson["factor"] = sensor.factor;
sensorJson["uom"] = EMSdevice::uom_to_string(sensor.uom);
} else if (sensor.type >= AnalogSensor::AnalogType::PWM_0) {
sensorJson["frequency"] = sensor.factor;
sensorJson["factor"] = sensor.factor;
}
}
@@ -1154,10 +1181,14 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
JsonObject obj = devices.createNestedObject();
obj["type"] = emsdevice->device_type_name();
obj["name"] = emsdevice->to_string();
obj["entities"] = emsdevice->count_entities();
JsonObject obj = devices.createNestedObject();
obj["type"] = emsdevice->device_type_name();
// obj["name"] = emsdevice->to_string();
obj["name"] = emsdevice->name();
obj["device id"] = Helpers::hextoa(emsdevice->device_id());
obj["product id"] = emsdevice->product_id();
obj["version"] = emsdevice->version();
obj["entities"] = emsdevice->count_entities();
char result[200];
(void)emsdevice->show_telegram_handlers(result, EMSdevice::Handlers::RECEIVED);
if (result[0] != '\0') {

View File

@@ -536,13 +536,13 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
uint8_t message_data = (type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
// if length set, publish result and set telegram to front
if (length) {
message_data[0] = length;
message_data = length;
EMSESP::set_read_id(type_id);
}
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1, 0, length != 0);
add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, length != 0);
}
// Send a raw telegram to the bus, telegram is a text string of hex values
@@ -663,9 +663,10 @@ uint16_t TxService::post_send_query() {
if (post_typeid) {
uint8_t dest = (this->telegram_last_->dest & 0x7F);
// when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ...
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, message_data, 1, 0, true); // add to top/front of queue
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
uint8_t message_data =
(this->telegram_last_->type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, &message_data, 1, 0, true); // add to top/front of queue
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), post_typeid, dest);
set_post_send_query(0); // reset

View File

@@ -399,6 +399,10 @@ class TxService : public EMSbus {
return tx_telegrams_;
}
bool tx_queue_empty() {
return tx_telegrams_.size() == 0;
}
#if defined(EMSESP_DEBUG)
static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry
#else

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.4.0b5"
#define EMSESP_APP_VERSION "3.4.0b6"

View File

@@ -44,7 +44,7 @@ class AnalogCustomization {
public:
uint8_t id;
std::string name;
uint16_t offset;
float offset;
float factor;
uint8_t uom; // 0 is none
int8_t type; // -1 is for deletion

View File

@@ -144,7 +144,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
obj["t"] = sensor.type();
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
obj["v"] = Helpers::round2(sensor.value(), 1); // is optional and is a float
obj["v"] = Helpers::round2(sensor.value(), 0); // is optional and is a float
}
}
}
@@ -275,7 +275,7 @@ void WebDataService::write_analog(AsyncWebServerRequest * request, JsonVariant &
uint8_t id = analog["id"]; // this is the unique key
std::string name = analog["name"];
float factor = analog["factor"];
int16_t offset = analog["offset"];
float offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
ok = EMSESP::analogsensor_.update(id, name, offset, factor, uom, type);