mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-01-26 16:49:11 +03:00
Merge pull request #361 from MichaelDvP/dev_mqtt
mqtt, analog, fahrenheit and other changes
This commit is contained in:
@@ -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:**
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,5 +40,6 @@ export interface MqttSettings {
|
||||
nested_format: number;
|
||||
send_response: boolean;
|
||||
publish_single: boolean;
|
||||
publish_single2cmd: boolean;
|
||||
discovery_prefix: string;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
122
src/mqtt.cpp
122
src/mqtt.cpp
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/mqtt.h
39
src/mqtt.h
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
103
src/system.cpp
103
src/system.cpp
@@ -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') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.4.0b5"
|
||||
#define EMSESP_APP_VERSION "3.4.0b6"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user