mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 08:49:52 +03:00
Merge pull request #1899 from MichaelDvP/dev
scheduler single and fixes
This commit is contained in:
@@ -160,6 +160,9 @@ const Scheduler: FC = () => {
|
|||||||
setCreating(false);
|
setCreating(false);
|
||||||
setSelectedScheduleItem(si);
|
setSelectedScheduleItem(si);
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
|
if (si.o_name === undefined) {
|
||||||
|
si.o_name = si.name;
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onDialogClose = () => {
|
const onDialogClose = () => {
|
||||||
@@ -280,7 +283,9 @@ const Scheduler: FC = () => {
|
|||||||
<Cell stiff>
|
<Cell stiff>
|
||||||
<Stack spacing={0.5} direction="row">
|
<Stack spacing={0.5} direction="row">
|
||||||
<Divider orientation="vertical" flexItem />
|
<Divider orientation="vertical" flexItem />
|
||||||
{si.flags < ScheduleFlag.SCHEDULE_TIMER ? (
|
{si.flags === 0 ? (
|
||||||
|
<></>
|
||||||
|
) : si.flags < ScheduleFlag.SCHEDULE_TIMER ? (
|
||||||
<>
|
<>
|
||||||
{dayBox(si, ScheduleFlag.SCHEDULE_MON)}
|
{dayBox(si, ScheduleFlag.SCHEDULE_MON)}
|
||||||
{dayBox(si, ScheduleFlag.SCHEDULE_TUE)}
|
{dayBox(si, ScheduleFlag.SCHEDULE_TUE)}
|
||||||
|
|||||||
@@ -72,6 +72,17 @@ const SchedulerDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveandactivate = async () => {
|
||||||
|
editItem.active = true;
|
||||||
|
try {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
await validate(validator, editItem);
|
||||||
|
onSave(editItem);
|
||||||
|
} catch (error) {
|
||||||
|
setFieldErrors(error as ValidateFieldsError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
editItem.deleted = true;
|
editItem.deleted = true;
|
||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
@@ -326,6 +337,8 @@ const SchedulerDialog = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors}
|
||||||
name="cmd"
|
name="cmd"
|
||||||
@@ -354,8 +367,6 @@ const SchedulerDialog = ({
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
{!creating && (
|
{!creating && (
|
||||||
@@ -386,6 +397,16 @@ const SchedulerDialog = ({
|
|||||||
>
|
>
|
||||||
{creating ? LL.ADD(0) : LL.UPDATE()}
|
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||||
</Button>
|
</Button>
|
||||||
|
{editItem.flags === 0 && editItem.cmd !== '' && (
|
||||||
|
<Button
|
||||||
|
startIcon={<DoneIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={saveandactivate}
|
||||||
|
color="warning"
|
||||||
|
>
|
||||||
|
{LL.EXECUTE()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ const Sensors: FC = () => {
|
|||||||
|
|
||||||
const updateTemperatureSensor = (ts: TemperatureSensor) => {
|
const updateTemperatureSensor = (ts: TemperatureSensor) => {
|
||||||
if (me.admin) {
|
if (me.admin) {
|
||||||
|
ts.o_n = ts.n;
|
||||||
setSelectedTemperatureSensor(ts);
|
setSelectedTemperatureSensor(ts);
|
||||||
setTemperatureDialogOpen(true);
|
setTemperatureDialogOpen(true);
|
||||||
}
|
}
|
||||||
@@ -282,6 +283,7 @@ const Sensors: FC = () => {
|
|||||||
const updateAnalogSensor = (as: AnalogSensor) => {
|
const updateAnalogSensor = (as: AnalogSensor) => {
|
||||||
if (me.admin) {
|
if (me.admin) {
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
|
as.o_n = as.n;
|
||||||
setSelectedAnalogSensor(as);
|
setSelectedAnalogSensor(as);
|
||||||
setAnalogDialogOpen(true);
|
setAnalogDialogOpen(true);
|
||||||
}
|
}
|
||||||
@@ -302,7 +304,8 @@ const Sensors: FC = () => {
|
|||||||
o: 0,
|
o: 0,
|
||||||
t: 0,
|
t: 0,
|
||||||
f: 1,
|
f: 1,
|
||||||
d: false
|
d: false,
|
||||||
|
o_n: ''
|
||||||
});
|
});
|
||||||
setAnalogDialogOpen(true);
|
setAnalogDialogOpen(true);
|
||||||
};
|
};
|
||||||
@@ -465,7 +468,10 @@ const Sensors: FC = () => {
|
|||||||
onClose={onTemperatureDialogClose}
|
onClose={onTemperatureDialogClose}
|
||||||
onSave={onTemperatureDialogSave}
|
onSave={onTemperatureDialogSave}
|
||||||
selectedItem={selectedTemperatureSensor}
|
selectedItem={selectedTemperatureSensor}
|
||||||
validator={temperatureSensorItemValidation(sensorData.ts)}
|
validator={temperatureSensorItemValidation(
|
||||||
|
sensorData.ts,
|
||||||
|
selectedTemperatureSensor
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sensorData?.analog_enabled === true && (
|
{sensorData?.analog_enabled === true && (
|
||||||
@@ -483,6 +489,7 @@ const Sensors: FC = () => {
|
|||||||
selectedItem={selectedAnalogSensor}
|
selectedItem={selectedAnalogSensor}
|
||||||
validator={analogSensorItemValidation(
|
validator={analogSensorItemValidation(
|
||||||
sensorData.as,
|
sensorData.as,
|
||||||
|
selectedAnalogSensor,
|
||||||
creating,
|
creating,
|
||||||
sensorData.platform
|
sensorData.platform
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export interface TemperatureSensor {
|
|||||||
t?: number; // temp, optional
|
t?: number; // temp, optional
|
||||||
o: number; // offset
|
o: number; // offset
|
||||||
u: number; // uom
|
u: number; // uom
|
||||||
|
o_n?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalogSensor {
|
export interface AnalogSensor {
|
||||||
@@ -91,6 +92,7 @@ export interface AnalogSensor {
|
|||||||
f: number;
|
f: number;
|
||||||
t: number;
|
t: number;
|
||||||
d: boolean; // deleted flag
|
d: boolean; // deleted flag
|
||||||
|
o_n?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WriteTemperatureSensor {
|
export interface WriteTemperatureSensor {
|
||||||
|
|||||||
@@ -241,6 +241,25 @@ export const createSettingsValidator = (settings: Settings) =>
|
|||||||
{ type: 'number', min: 0, max: 10, message: 'Must be between 0 and 10' }
|
{ type: 'number', min: 0, max: 10, message: 'Must be between 0 and 10' }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
...(settings.modbus_enabled && {
|
||||||
|
modbus_max_clients: [
|
||||||
|
{ required: true, message: 'Max clients is required' },
|
||||||
|
{ type: 'number', min: 0, max: 50, message: 'Invalid number' }
|
||||||
|
],
|
||||||
|
modbus_port: [
|
||||||
|
{ required: true, message: 'Port is required' },
|
||||||
|
{ type: 'number', min: 0, max: 65535, message: 'Invalid Port' }
|
||||||
|
],
|
||||||
|
modbus_timeout: [
|
||||||
|
{ required: true, message: 'Timeout is required' },
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
min: 100,
|
||||||
|
max: 20000,
|
||||||
|
message: 'Must be between 100 and 20000'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
...(settings.shower_timer && {
|
...(settings.shower_timer && {
|
||||||
shower_min_duration: [
|
shower_min_duration: [
|
||||||
{
|
{
|
||||||
@@ -388,9 +407,16 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
export const uniqueTemperatureNameValidator = (sensors: TemperatureSensor[]) => ({
|
export const uniqueTemperatureNameValidator = (
|
||||||
|
sensors: TemperatureSensor[],
|
||||||
|
o_name?: string
|
||||||
|
) => ({
|
||||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||||
if (n !== '' && sensors.find((ts) => ts.n.toLowerCase() === n.toLowerCase())) {
|
if (
|
||||||
|
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||||
|
n !== '' &&
|
||||||
|
sensors.find((ts) => ts.n.toLowerCase() === n.toLowerCase())
|
||||||
|
) {
|
||||||
callback('Name already in use');
|
callback('Name already in use');
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback();
|
||||||
@@ -398,7 +424,10 @@ export const uniqueTemperatureNameValidator = (sensors: TemperatureSensor[]) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const temperatureSensorItemValidation = (sensors: TemperatureSensor[]) =>
|
export const temperatureSensorItemValidation = (
|
||||||
|
sensors: TemperatureSensor[],
|
||||||
|
sensor: TemperatureSensor
|
||||||
|
) =>
|
||||||
new Schema({
|
new Schema({
|
||||||
n: [
|
n: [
|
||||||
{
|
{
|
||||||
@@ -406,7 +435,7 @@ export const temperatureSensorItemValidation = (sensors: TemperatureSensor[]) =>
|
|||||||
pattern: /^[a-zA-Z0-9_]{0,19}$/,
|
pattern: /^[a-zA-Z0-9_]{0,19}$/,
|
||||||
message: "Must be <20 characters: alphanumeric or '_'"
|
message: "Must be <20 characters: alphanumeric or '_'"
|
||||||
},
|
},
|
||||||
...[uniqueTemperatureNameValidator(sensors)]
|
...[uniqueTemperatureNameValidator(sensors, sensor.o_n)]
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -424,9 +453,16 @@ export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const uniqueAnalogNameValidator = (sensors: AnalogSensor[]) => ({
|
export const uniqueAnalogNameValidator = (
|
||||||
|
sensors: AnalogSensor[],
|
||||||
|
o_name?: string
|
||||||
|
) => ({
|
||||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||||
if (n !== '' && sensors.find((as) => as.n.toLowerCase() === n.toLowerCase())) {
|
if (
|
||||||
|
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||||
|
n !== '' &&
|
||||||
|
sensors.find((as) => as.n.toLowerCase() === n.toLowerCase())
|
||||||
|
) {
|
||||||
callback('Name already in use');
|
callback('Name already in use');
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback();
|
||||||
@@ -436,6 +472,7 @@ export const uniqueAnalogNameValidator = (sensors: AnalogSensor[]) => ({
|
|||||||
|
|
||||||
export const analogSensorItemValidation = (
|
export const analogSensorItemValidation = (
|
||||||
sensors: AnalogSensor[],
|
sensors: AnalogSensor[],
|
||||||
|
sensor: AnalogSensor,
|
||||||
creating: boolean,
|
creating: boolean,
|
||||||
platform: string
|
platform: string
|
||||||
) =>
|
) =>
|
||||||
@@ -446,7 +483,7 @@ export const analogSensorItemValidation = (
|
|||||||
pattern: /^[a-zA-Z0-9_]{0,19}$/,
|
pattern: /^[a-zA-Z0-9_]{0,19}$/,
|
||||||
message: "Must be <20 characters: alphanumeric or '_'"
|
message: "Must be <20 characters: alphanumeric or '_'"
|
||||||
},
|
},
|
||||||
...[uniqueAnalogNameValidator(sensors)]
|
...[uniqueAnalogNameValidator(sensors, sensor.o_n)]
|
||||||
],
|
],
|
||||||
g: [
|
g: [
|
||||||
{ required: true, message: 'GPIO is required' },
|
{ required: true, message: 'GPIO is required' },
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatusType) => {
|
|||||||
const IPs = (status: NetworkStatusType) => {
|
const IPs = (status: NetworkStatusType) => {
|
||||||
if (
|
if (
|
||||||
!status.local_ipv6 ||
|
!status.local_ipv6 ||
|
||||||
status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000'
|
status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000' ||
|
||||||
|
status.local_ipv6 === '::'
|
||||||
) {
|
) {
|
||||||
return status.local_ip;
|
return status.local_ip;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,3 +73,7 @@ the LICENSE file.
|
|||||||
#define EMC_SIZE_POOL_ELEMENTS 128
|
#define EMC_SIZE_POOL_ELEMENTS 128
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef TASMOTA_SDK
|
||||||
|
#define EMC_CLIENT_SECURE
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "IPUtils.h"
|
#include "IPUtils.h"
|
||||||
#include "SecurityManager.h"
|
#include "SecurityManager.h"
|
||||||
|
|
||||||
#define MAX_NETWORK_STATUS_SIZE 1024
|
|
||||||
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"
|
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"
|
||||||
|
|
||||||
class NetworkStatus {
|
class NetworkStatus {
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ void AnalogSensor::loop() {
|
|||||||
|
|
||||||
// update analog information name and offset
|
// update analog information name and offset
|
||||||
// a type value of -1 is used to delete the sensor
|
// a type value of -1 is used to delete the sensor
|
||||||
bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) {
|
bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) {
|
||||||
// first see if we can find the sensor in our customization list
|
// first see if we can find the sensor in our customization list
|
||||||
bool found_sensor = false;
|
bool found_sensor = false;
|
||||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||||
@@ -347,6 +347,11 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
|
|||||||
if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) {
|
if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) {
|
||||||
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
|
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
|
||||||
}
|
}
|
||||||
|
if (name.empty()) {
|
||||||
|
char n[20];
|
||||||
|
snprintf(n, sizeof(n), "%s_%02d", FL_(AnalogTypeName)[type], gpio);
|
||||||
|
name = n;
|
||||||
|
}
|
||||||
if (AnalogCustomization.gpio == gpio) {
|
if (AnalogCustomization.gpio == gpio) {
|
||||||
found_sensor = true; // found the record
|
found_sensor = true; // found the record
|
||||||
// see if it's marked for deletion
|
// see if it's marked for deletion
|
||||||
@@ -756,16 +761,6 @@ AnalogSensor::Sensor::Sensor(const uint8_t gpio, const std::string & name, const
|
|||||||
value_ = 0; // init value to 0 always
|
value_ = 0; // init value to 0 always
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns name of the analog sensor or creates one if its empty
|
|
||||||
std::string AnalogSensor::Sensor::name() const {
|
|
||||||
if (name_.empty()) {
|
|
||||||
char name[20];
|
|
||||||
snprintf(name, sizeof(name), "%s_%02d", FL_(AnalogTypeName)[type_], gpio_);
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
return name_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the dig_out/counter/DAC/PWM value, id is gpio-no
|
// set the dig_out/counter/DAC/PWM value, id is gpio-no
|
||||||
bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
||||||
float val;
|
float val;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
// names, same order as AnalogType
|
// names, same order as AnalogType
|
||||||
MAKE_ENUM_FIXED(AnalogTypeName, "disabled", "dig_in", "counter", "adc", "timer", "rate", "pwm0", "pwm1", "pwm2")
|
MAKE_ENUM_FIXED(AnalogTypeName, "disabled", "dig_in", "counter", "adc", "timer", "rate", "dig_out", "pwm0", "pwm1", "pwm2")
|
||||||
|
|
||||||
class AnalogSensor {
|
class AnalogSensor {
|
||||||
public:
|
public:
|
||||||
@@ -41,7 +41,9 @@ class AnalogSensor {
|
|||||||
offset_ = offset;
|
offset_ = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name() const;
|
std::string name() const {
|
||||||
|
return name_;
|
||||||
|
}
|
||||||
|
|
||||||
void set_name(const std::string & name) {
|
void set_name(const std::string & name) {
|
||||||
name_ = name;
|
name_ = name;
|
||||||
@@ -155,7 +157,7 @@ class AnalogSensor {
|
|||||||
return sensors_.size();
|
return sensors_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false);
|
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false);
|
||||||
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
||||||
void store_counters();
|
void store_counters();
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
|||||||
|
|
||||||
// some commands may be prefixed with hc. dhw. or hc/ or dhw/ so extract these if they exist
|
// some commands may be prefixed with hc. dhw. or hc/ or dhw/ so extract these if they exist
|
||||||
// parse_command_string returns the extracted command
|
// parse_command_string returns the extracted command
|
||||||
|
if (device_type > EMSdevice::DeviceType::BOILER) {
|
||||||
command_p = parse_command_string(command_p, id_n);
|
command_p = parse_command_string(command_p, id_n);
|
||||||
|
}
|
||||||
if (command_p == nullptr) {
|
if (command_p == nullptr) {
|
||||||
// handle dead endpoints like api/system or api/boiler
|
// handle dead endpoints like api/system or api/boiler
|
||||||
// default to 'info' for SYSTEM, the other devices to 'values' for shortname version
|
// default to 'info' for SYSTEM, the other devices to 'values' for shortname version
|
||||||
@@ -148,7 +150,10 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
|||||||
strlcpy(device_s, d, device_end - d + 1);
|
strlcpy(device_s, d, device_end - d + 1);
|
||||||
data_p = device_end + 1;
|
data_p = device_end + 1;
|
||||||
int8_t id_d = -1;
|
int8_t id_d = -1;
|
||||||
|
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
|
||||||
|
if (device_type > EMSdevice::DeviceType::BOILER) {
|
||||||
data_p = parse_command_string(data_p, id_d);
|
data_p = parse_command_string(data_p, id_d);
|
||||||
|
}
|
||||||
if (data_p == nullptr) {
|
if (data_p == nullptr) {
|
||||||
return CommandRet::INVALID;
|
return CommandRet::INVALID;
|
||||||
}
|
}
|
||||||
@@ -159,7 +164,6 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
|
|||||||
strcat(data_s, "/value");
|
strcat(data_s, "/value");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
|
|
||||||
if (Command::call(device_type, data_s, "", true, id_d, output) != CommandRet::OK) {
|
if (Command::call(device_type, data_s, "", true, id_d, output) != CommandRet::OK) {
|
||||||
return CommandRet::INVALID;
|
return CommandRet::INVALID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,10 +428,9 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
|||||||
to_app(shell).send_read_request(type_id, device_id, offset, length, true);
|
to_app(shell).send_read_request(type_id, device_id, offset, length, true);
|
||||||
} else if (arguments.size() == 3) {
|
} else if (arguments.size() == 3) {
|
||||||
uint16_t offset = Helpers::hextoint(arguments.back().c_str());
|
uint16_t offset = Helpers::hextoint(arguments.back().c_str());
|
||||||
to_app(shell).send_read_request(type_id, device_id, offset, EMS_MAX_TELEGRAM_LENGTH, true);
|
to_app(shell).send_read_request(type_id, device_id, offset, 0, true);
|
||||||
} else {
|
} else {
|
||||||
// send with length to send immediately and trigger publish read_id
|
to_app(shell).send_read_request(type_id, device_id, 0, 0, true);
|
||||||
to_app(shell).send_read_request(type_id, device_id, 0, EMS_MAX_TELEGRAM_LENGTH, true);
|
|
||||||
}
|
}
|
||||||
to_app(shell).set_read_id(type_id);
|
to_app(shell).set_read_id(type_id);
|
||||||
});
|
});
|
||||||
@@ -533,10 +532,14 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
|||||||
|
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
int8_t id = -1;
|
int8_t id = -1;
|
||||||
const char * cmd = Command::parse_command_string(arguments[1].c_str(), id);
|
const char * cmd = Helpers::toLower(arguments[1]).c_str();
|
||||||
uint8_t return_code = CommandRet::OK;
|
uint8_t return_code = CommandRet::OK;
|
||||||
JsonObject json = doc.to<JsonObject>();
|
JsonObject json = doc.to<JsonObject>();
|
||||||
|
bool has_data = false;
|
||||||
|
|
||||||
|
if (device_type >= EMSdevice::DeviceType::BOILER) {
|
||||||
|
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
|
||||||
|
}
|
||||||
if (cmd == nullptr) {
|
if (cmd == nullptr) {
|
||||||
cmd = device_type == EMSdevice::DeviceType::SYSTEM ? F_(info) : F_(values);
|
cmd = device_type == EMSdevice::DeviceType::SYSTEM ? F_(info) : F_(values);
|
||||||
}
|
}
|
||||||
@@ -551,6 +554,7 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
|||||||
} else if (arguments[2] == "?") {
|
} else if (arguments[2] == "?") {
|
||||||
return_code = Command::call(device_type, cmd, nullptr, true, id, json);
|
return_code = Command::call(device_type, cmd, nullptr, true, id, json);
|
||||||
} else {
|
} else {
|
||||||
|
has_data = true;
|
||||||
// has a value but no id so use -1
|
// has a value but no id so use -1
|
||||||
return_code = Command::call(device_type, cmd, arguments.back().c_str(), true, id, json);
|
return_code = Command::call(device_type, cmd, arguments.back().c_str(), true, id, json);
|
||||||
}
|
}
|
||||||
@@ -559,6 +563,7 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
|||||||
if (arguments[2] == "?") {
|
if (arguments[2] == "?") {
|
||||||
return_code = Command::call(device_type, cmd, nullptr, true, atoi(arguments[3].c_str()), json);
|
return_code = Command::call(device_type, cmd, nullptr, true, atoi(arguments[3].c_str()), json);
|
||||||
} else {
|
} else {
|
||||||
|
has_data = true;
|
||||||
return_code = Command::call(device_type, cmd, arguments[2].c_str(), true, atoi(arguments[3].c_str()), json);
|
return_code = Command::call(device_type, cmd, arguments[2].c_str(), true, atoi(arguments[3].c_str()), json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,10 +578,12 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
|||||||
serializeJsonPretty(doc, shell);
|
serializeJsonPretty(doc, shell);
|
||||||
shell.println();
|
shell.println();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if (!has_data) {
|
||||||
// show message if no data returned (e.g. for analogsensor, temperaturesensor, custom)
|
// show message if no data returned (e.g. for analogsensor, temperaturesensor, custom)
|
||||||
shell.println("Command executed");
|
shell.println("Command executed");
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (return_code == CommandRet::NOT_FOUND) {
|
} else if (return_code == CommandRet::NOT_FOUND) {
|
||||||
shell.println("Unknown command");
|
shell.println("Unknown command");
|
||||||
|
|||||||
@@ -2030,7 +2030,7 @@ int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject & jsonValue) {
|
bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue) {
|
||||||
//Serial.printf("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size());
|
//Serial.printf("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size());
|
||||||
|
|
||||||
// find device value by shortname
|
// find device value by shortname
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ class EMSdevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result);
|
int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result);
|
||||||
bool modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject & jsonValue);
|
bool modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue);
|
||||||
|
|
||||||
const char * brand_to_char();
|
const char * brand_to_char();
|
||||||
const std::string to_string();
|
const std::string to_string();
|
||||||
|
|||||||
@@ -711,15 +711,31 @@ void EMSESP::publish_sensor_values(const bool time, const bool force) {
|
|||||||
// MQTT publish a telegram as raw data to the topic 'response'
|
// MQTT publish a telegram as raw data to the topic 'response'
|
||||||
void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
|
void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
|
||||||
static char * buffer = nullptr;
|
static char * buffer = nullptr;
|
||||||
static uint8_t offset;
|
static uint8_t offset = 0;
|
||||||
|
static uint16_t type = 0;
|
||||||
|
// restart on mismatch while collecting telegram
|
||||||
|
if (buffer && (telegram->offset < offset || telegram->type_id != type)) {
|
||||||
|
delete[] buffer;
|
||||||
|
buffer = nullptr;
|
||||||
|
}
|
||||||
if (buffer == nullptr) {
|
if (buffer == nullptr) {
|
||||||
offset = telegram->offset; // store offset from first part
|
offset = telegram->offset; // store offset from first part
|
||||||
|
type = telegram->type_id;
|
||||||
buffer = new char[768]; // max 256 hex-codes, 255 spaces, 1 termination
|
buffer = new char[768]; // max 256 hex-codes, 255 spaces, 1 termination
|
||||||
buffer[0] = '\0';
|
for (uint16_t i = 0; i < 256; i++) {
|
||||||
|
buffer[i * 3] = '0';
|
||||||
|
buffer[i * 3 + 1] = '0';
|
||||||
|
buffer[i * 3 + 2] = ' ';
|
||||||
|
}
|
||||||
|
buffer[267] = '\0';
|
||||||
|
}
|
||||||
|
if (telegram->message_length) {
|
||||||
|
strlcpy(&buffer[(telegram->offset - offset) * 3], Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str(), 768);
|
||||||
|
} else {
|
||||||
|
strlcpy(&buffer[(telegram->offset - offset) * 3], "", 768);
|
||||||
}
|
}
|
||||||
strlcat(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str(), 768);
|
|
||||||
if (response_id_ != 0) {
|
if (response_id_ != 0) {
|
||||||
strlcat(buffer, " ", 768);
|
buffer[strlen(buffer)] = ' '; // overwrite termination \0
|
||||||
return; // do not delete buffer
|
return; // do not delete buffer
|
||||||
}
|
}
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
@@ -730,11 +746,8 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
|
|||||||
doc["offset"] = Helpers::hextoa(s, offset);
|
doc["offset"] = Helpers::hextoa(s, offset);
|
||||||
doc["data"] = buffer;
|
doc["data"] = buffer;
|
||||||
|
|
||||||
if (telegram->message_length <= 4 && strlen(buffer) <= 11) {
|
if (strlen(buffer) && strlen(buffer) <= 11) {
|
||||||
uint32_t value = 0;
|
uint32_t value = Helpers::hextoint(buffer);
|
||||||
for (uint8_t i = 0; i < telegram->message_length; i++) {
|
|
||||||
value = (value << 8) + telegram->message_data[i];
|
|
||||||
}
|
|
||||||
doc["value"] = value;
|
doc["value"] = value;
|
||||||
}
|
}
|
||||||
Mqtt::queue_publish("response", doc.as<JsonObject>());
|
Mqtt::queue_publish("response", doc.as<JsonObject>());
|
||||||
@@ -1179,31 +1192,16 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// first check to see if we already have it, if so update the record
|
// first check to see if we already have it, if so update the record
|
||||||
|
auto it = emsdevices.begin();
|
||||||
for (const auto & emsdevice : emsdevices) {
|
for (const auto & emsdevice : emsdevices) {
|
||||||
if (emsdevice && emsdevice->is_device_id(device_id)) {
|
if (emsdevice && emsdevice->is_device_id(device_id)) {
|
||||||
if (product_id == 0 || emsdevice->product_id() != 0) { // update only with valid product_id
|
if (product_id == 0 || emsdevice->product_id() != 0) { // update only with valid product_id
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
emsdevices.erase(it); // erase the old device without product_id and re detect
|
||||||
LOG_DEBUG("Updating details for already active deviceID 0x%02X", device_id);
|
break;
|
||||||
emsdevice->product_id(product_id);
|
|
||||||
emsdevice->version(version);
|
|
||||||
|
|
||||||
// only set brand if it doesn't already exist
|
|
||||||
if (emsdevice->brand() == EMSdevice::Brand::NO_BRAND) {
|
|
||||||
emsdevice->brand(brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the name and flags in our device library database
|
|
||||||
for (const auto & device : device_library_) {
|
|
||||||
if (device.product_id == product_id && device.device_type == emsdevice->device_type()) {
|
|
||||||
emsdevice->default_name(device.default_name); // custom name is set later
|
|
||||||
emsdevice->add_flags(device.flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true; // finish up
|
|
||||||
}
|
}
|
||||||
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// look up the rest of the details using the product_id and create the new device object
|
// look up the rest of the details using the product_id and create the new device object
|
||||||
@@ -1488,6 +1486,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
|||||||
read_next_ = true;
|
read_next_ = true;
|
||||||
txservice_.send();
|
txservice_.send();
|
||||||
} else {
|
} else {
|
||||||
|
read_next_ = false;
|
||||||
txservice_.send_poll(); // close the bus
|
txservice_.send_poll(); // close the bus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ class EMSESP {
|
|||||||
response_id_ = id;
|
response_id_ = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t response_id() {
|
||||||
|
return response_id_;
|
||||||
|
}
|
||||||
|
|
||||||
static bool wait_validate() {
|
static bool wait_validate() {
|
||||||
return (wait_validate_ != 0);
|
return (wait_validate_ != 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ uint32_t Helpers::hextoint(const char * hex) {
|
|||||||
// get current character then increment
|
// get current character then increment
|
||||||
char byte = *hex++;
|
char byte = *hex++;
|
||||||
// transform hex character to the 4bit equivalent number, using the ascii table indexes
|
// transform hex character to the 4bit equivalent number, using the ascii table indexes
|
||||||
|
if (byte == ' ') byte = *hex++; // skip spaces
|
||||||
if (byte >= '0' && byte <= '9')
|
if (byte >= '0' && byte <= '9')
|
||||||
byte = byte - '0';
|
byte = byte - '0';
|
||||||
else if (byte >= 'a' && byte <= 'f')
|
else if (byte >= 'a' && byte <= 'f')
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ class Mqtt {
|
|||||||
return lastresponse_;
|
return lastresponse_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clear_response() {
|
||||||
|
lastresponse_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void set_qos(uint8_t mqtt_qos) const {
|
void set_qos(uint8_t mqtt_qos) const {
|
||||||
mqtt_qos_ = mqtt_qos;
|
mqtt_qos_ = mqtt_qos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1305,7 +1305,7 @@ bool System::get_value_info(JsonObject root, const char * command) {
|
|||||||
(void)command_info("", 0, root);
|
(void)command_info("", 0, root);
|
||||||
|
|
||||||
// check for hardcoded "info"
|
// check for hardcoded "info"
|
||||||
if (!strcmp(cmd, F_(info))) {
|
if (!strcmp(cmd, F_(info)) || !strcmp(cmd, F_(value))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
|||||||
telegram_raw[3] = telegram->offset;
|
telegram_raw[3] = telegram->offset;
|
||||||
|
|
||||||
// EMS+ has different format for read and write
|
// EMS+ has different format for read and write
|
||||||
if (telegram->operation != Telegram::Operation::TX_READ) {
|
if (telegram->operation != Telegram::Operation::TX_READ && telegram->operation != Telegram::Operation::TX_RAW) {
|
||||||
// WRITE/NONE
|
// WRITE/NONE
|
||||||
telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
|
telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
|
||||||
telegram_raw[5] = telegram->type_id & 0xFF; // type, 2nd byte, low-byte
|
telegram_raw[5] = telegram->type_id & 0xFF; // type, 2nd byte, low-byte
|
||||||
@@ -404,6 +404,17 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (telegram->operation == Telegram::Operation::TX_RAW) {
|
||||||
|
tx_state(Telegram::Operation::TX_READ);
|
||||||
|
if (EMSESP::response_id() == 0) {
|
||||||
|
Mqtt::clear_response();
|
||||||
|
EMSESP::set_response_id(telegram->type_id);
|
||||||
|
if (telegram->message_data[0] >= (telegram->type_id > 0xFF ? 25 : 27)) {
|
||||||
|
EMSESP::set_read_id(telegram->type_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
tx_state(telegram->operation); // tx now in a wait state
|
tx_state(telegram->operation); // tx now in a wait state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,13 +523,8 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
|
|||||||
if (src != ems_bus_id() || dest == 0) {
|
if (src != ems_bus_id() || dest == 0) {
|
||||||
operation = Telegram::Operation::NONE; // do not check reply/ack for other ids and broadcasts
|
operation = Telegram::Operation::NONE; // do not check reply/ack for other ids and broadcasts
|
||||||
} else if (dest & 0x80) {
|
} else if (dest & 0x80) {
|
||||||
operation = Telegram::Operation::TX_READ;
|
// keep operation RAW to set the response when sending
|
||||||
EMSESP::set_response_id(type_id);
|
// Mqtt::clear_response(); // set here when receiving command or when sending?
|
||||||
// trigger read of all parts of telegram if requested length is more than 32
|
|
||||||
// compatibility to earlier versions
|
|
||||||
if (message_data[0] >= 32) {
|
|
||||||
EMSESP::set_read_id(type_id);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
operation = Telegram::Operation::TX_WRITE;
|
operation = Telegram::Operation::TX_WRITE;
|
||||||
validate_id = type_id;
|
validate_id = type_id;
|
||||||
@@ -540,7 +546,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
|
|||||||
|
|
||||||
LOG_DEBUG("New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
|
LOG_DEBUG("New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
|
||||||
|
|
||||||
if (front) {
|
if (front && (operation != Telegram::Operation::TX_RAW || EMSESP::response_id() == 0)) {
|
||||||
// tx_telegrams_.push_front(qtxt); // add to front of queue
|
// tx_telegrams_.push_front(qtxt); // add to front of queue
|
||||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to front of queue
|
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to front of queue
|
||||||
} else {
|
} else {
|
||||||
@@ -556,7 +562,7 @@ 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, const bool front) {
|
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length, const bool front) {
|
||||||
LOG_DEBUG("Tx read request to deviceID 0x%02X for typeID 0x%02X", dest, type_id);
|
LOG_DEBUG("Tx read request to deviceID 0x%02X for typeID 0x%02X", dest, type_id);
|
||||||
|
|
||||||
uint8_t message_data = (type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
|
uint8_t message_data = 0xFF;
|
||||||
if (length > 0 && length < message_data) {
|
if (length > 0 && length < message_data) {
|
||||||
message_data = length;
|
message_data = length;
|
||||||
}
|
}
|
||||||
@@ -570,11 +576,11 @@ bool TxService::send_raw(const char * telegram_data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
||||||
char telegram[EMS_MAX_TELEGRAM_LENGTH * 3];
|
char telegram[strlen(telegram_data) + 1];
|
||||||
strlcpy(telegram, telegram_data, sizeof(telegram));
|
strlcpy(telegram, telegram_data, sizeof(telegram));
|
||||||
|
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
uint8_t data[2 + strlen(telegram) / 3];
|
||||||
|
|
||||||
// get values
|
// get values
|
||||||
char * p = strtok(telegram, " ,"); // delimiter
|
char * p = strtok(telegram, " ,"); // delimiter
|
||||||
@@ -602,6 +608,10 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
|
|||||||
if (operation == Telegram::Operation::TX_READ) {
|
if (operation == Telegram::Operation::TX_READ) {
|
||||||
if (telegram_last_->offset > 0) { // ignore errors for higher offsets
|
if (telegram_last_->offset > 0) { // ignore errors for higher offsets
|
||||||
LOG_DEBUG("Last Tx Read operation failed after %d retries. Ignoring request: %s", MAXIMUM_TX_RETRIES, telegram_last_->to_string().c_str());
|
LOG_DEBUG("Last Tx Read operation failed after %d retries. Ignoring request: %s", MAXIMUM_TX_RETRIES, telegram_last_->to_string().c_str());
|
||||||
|
if (EMSESP::response_id()) {
|
||||||
|
EMSESP::set_response_id(0);
|
||||||
|
EMSESP::set_read_id(0);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
increment_telegram_read_fail_count(); // another Tx fail
|
increment_telegram_read_fail_count(); // another Tx fail
|
||||||
@@ -628,7 +638,10 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
|
|||||||
|
|
||||||
// add to the top of the queue
|
// add to the top of the queue
|
||||||
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
||||||
tx_telegrams_.pop_back();
|
LOG_WARNING("Tx queue overflow, skip retry");
|
||||||
|
reset_retry_count(); // give up
|
||||||
|
EMSESP::wait_validate(0); // do not wait for validation
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
|
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
|
||||||
@@ -637,14 +650,24 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
|
|||||||
// send a request to read the next block of data from longer telegrams
|
// send a request to read the next block of data from longer telegrams
|
||||||
uint16_t TxService::read_next_tx(const uint8_t offset, const uint8_t length) {
|
uint16_t TxService::read_next_tx(const uint8_t offset, const uint8_t length) {
|
||||||
uint8_t old_length = telegram_last_->type_id > 0xFF ? length - 7 : length - 5;
|
uint8_t old_length = telegram_last_->type_id > 0xFF ? length - 7 : length - 5;
|
||||||
uint8_t next_length = telegram_last_->type_id > 0xFF ? EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2 : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
|
uint8_t max_length = telegram_last_->type_id > 0xFF ? EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2 : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
|
||||||
|
uint8_t next_length = telegram_last_->message_data[0] > old_length ? telegram_last_->message_data[0] - old_length - offset + telegram_last_->offset : 0;
|
||||||
uint8_t next_offset = offset + old_length;
|
uint8_t next_offset = offset + old_length;
|
||||||
uint8_t message_data = (UINT8_MAX - next_offset) >= next_length ? next_length : UINT8_MAX - next_offset;
|
|
||||||
// check telegram, offset and overflow
|
// check telegram, length, offset and overflow
|
||||||
// some telegrams only reply with one byte less, but have higher offsets (0x10)
|
// some telegrams only reply with one byte less, but have higher offsets (boiler 0x10)
|
||||||
// some reply with higher offset than requestes and have not_set values intermediate (0xEA)
|
// some reply with higher offset than requestes and have not_set values intermediate (boiler 0xEA)
|
||||||
if (old_length >= (next_length - 1) || telegram_last_->offset < offset) {
|
|
||||||
add(Telegram::Operation::TX_READ, telegram_last_->dest, telegram_last_->type_id, next_offset, &message_data, 1, 0, true);
|
// We have th last byte received
|
||||||
|
if (offset + old_length >= telegram_last_->offset + telegram_last_->message_data[0]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// we request all and get a short telegram with requested offset
|
||||||
|
if ((next_length + next_offset) == 0xFF && old_length < max_length - 1 && offset <= telegram_last_->offset) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (offset >= telegram_last_->offset && old_length > 0 && next_length > 0) {
|
||||||
|
add(Telegram::Operation::TX_READ, telegram_last_->dest, telegram_last_->type_id, next_offset, &next_length, 1, 0, true);
|
||||||
return telegram_last_->type_id;
|
return telegram_last_->type_id;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -666,11 +689,10 @@ uint16_t TxService::post_send_query() {
|
|||||||
|
|
||||||
if (post_typeid) {
|
if (post_typeid) {
|
||||||
uint8_t dest = (this->telegram_last_->dest & 0x7F);
|
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, ...
|
// when set a value with large offset before and validate on same type and offset, or complete telegram
|
||||||
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
|
uint8_t length = (this->telegram_last_->type_id == post_typeid) ? this->telegram_last_->message_length : 0xFF;
|
||||||
uint8_t message_data =
|
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? this->telegram_last_->offset : 0;
|
||||||
(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, &length, 1, 0, true); // add to top/front of queue
|
||||||
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
|
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
|
||||||
LOG_DEBUG("Sending post validate read, type ID 0x%02X to dest 0x%02X", post_typeid, dest);
|
LOG_DEBUG("Sending post validate read, type ID 0x%02X to dest 0x%02X", post_typeid, dest);
|
||||||
set_post_send_query(0); // reset
|
set_post_send_query(0); // reset
|
||||||
|
|||||||
@@ -221,10 +221,6 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
|
|||||||
// using the unique ID from the web find the real device type
|
// using the unique ID from the web find the real device type
|
||||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||||
if (emsdevice->unique_id() == unique_id) {
|
if (emsdevice->unique_id() == unique_id) {
|
||||||
// parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp
|
|
||||||
int8_t id = -1; // default
|
|
||||||
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
|
|
||||||
|
|
||||||
// create JSON for output
|
// create JSON for output
|
||||||
auto * response = new AsyncJsonResponse(false);
|
auto * response = new AsyncJsonResponse(false);
|
||||||
JsonObject output = response->getRoot();
|
JsonObject output = response->getRoot();
|
||||||
@@ -233,6 +229,11 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
|
|||||||
// authenticated is always true
|
// authenticated is always true
|
||||||
uint8_t return_code = CommandRet::NOT_FOUND;
|
uint8_t return_code = CommandRet::NOT_FOUND;
|
||||||
uint8_t device_type = emsdevice->device_type();
|
uint8_t device_type = emsdevice->device_type();
|
||||||
|
// parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp
|
||||||
|
int8_t id = -1; // default
|
||||||
|
if (device_type >= EMSdevice::DeviceType::BOILER) {
|
||||||
|
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
|
||||||
|
}
|
||||||
if (data.is<const char *>()) {
|
if (data.is<const char *>()) {
|
||||||
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
|
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
|
||||||
} else if (data.is<int>()) {
|
} else if (data.is<int>()) {
|
||||||
@@ -263,13 +264,15 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
|
|||||||
|
|
||||||
// special check for custom entities (which have a unique id of 99)
|
// special check for custom entities (which have a unique id of 99)
|
||||||
if (unique_id == 99) {
|
if (unique_id == 99) {
|
||||||
// parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp
|
|
||||||
int8_t id = -1;
|
|
||||||
cmd = Command::parse_command_string(cmd, id);
|
|
||||||
auto * response = new AsyncJsonResponse(false);
|
auto * response = new AsyncJsonResponse(false);
|
||||||
JsonObject output = response->getRoot();
|
JsonObject output = response->getRoot();
|
||||||
uint8_t return_code = CommandRet::NOT_FOUND;
|
uint8_t return_code = CommandRet::NOT_FOUND;
|
||||||
uint8_t device_type = EMSdevice::DeviceType::CUSTOM;
|
uint8_t device_type = EMSdevice::DeviceType::CUSTOM;
|
||||||
|
// parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp
|
||||||
|
int8_t id = -1;
|
||||||
|
if (device_type >= EMSdevice::DeviceType::BOILER) {
|
||||||
|
cmd = Command::parse_command_string(cmd, id); // extract hc or dhw
|
||||||
|
}
|
||||||
if (data.is<const char *>()) {
|
if (data.is<const char *>()) {
|
||||||
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
|
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
|
||||||
} else if (data.is<int>()) {
|
} else if (data.is<int>()) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ void WebSchedulerService::begin() {
|
|||||||
snprintf(topic, sizeof(topic), "%s/#", F_(scheduler));
|
snprintf(topic, sizeof(topic), "%s/#", F_(scheduler));
|
||||||
Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback
|
Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
xTaskCreate((TaskFunction_t)scheduler_task, "scheduler_task", 4096, NULL, 3, NULL);
|
xTaskCreate((TaskFunction_t)scheduler_task, "scheduler_task", 4096, NULL, 1, NULL);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) {
|
|||||||
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
||||||
JsonObject si = schedule.add<JsonObject>();
|
JsonObject si = schedule.add<JsonObject>();
|
||||||
si["id"] = counter++; // id is only used to render the table and must be unique
|
si["id"] = counter++; // id is only used to render the table and must be unique
|
||||||
si["active"] = scheduleItem.active;
|
si["active"] = scheduleItem.flags ? scheduleItem.active : false;
|
||||||
si["flags"] = scheduleItem.flags;
|
si["flags"] = scheduleItem.flags;
|
||||||
si["time"] = scheduleItem.time;
|
si["time"] = scheduleItem.time;
|
||||||
si["cmd"] = scheduleItem.cmd;
|
si["cmd"] = scheduleItem.cmd;
|
||||||
@@ -192,6 +192,15 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
|||||||
char result[12];
|
char result[12];
|
||||||
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
|
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
|
||||||
}
|
}
|
||||||
|
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||||
|
output["condition"] = scheduleItem.time;
|
||||||
|
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
|
||||||
|
output["onchange"] = scheduleItem.time;
|
||||||
|
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER) {
|
||||||
|
output["timer"] = scheduleItem.time;
|
||||||
|
} else if (scheduleItem.flags != 0){
|
||||||
|
output["time"] = scheduleItem.time;
|
||||||
|
}
|
||||||
output["command"] = scheduleItem.cmd;
|
output["command"] = scheduleItem.cmd;
|
||||||
output["cmd_data"] = scheduleItem.value;
|
output["cmd_data"] = scheduleItem.value;
|
||||||
output["readable"] = true;
|
output["readable"] = true;
|
||||||
@@ -426,8 +435,8 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "shuntingYard.hpp"
|
// called from emsesp.cpp on every entity-change
|
||||||
|
// queue schedules to be handled executed in scheduler-loop
|
||||||
bool WebSchedulerService::onChange(const char * cmd) {
|
bool WebSchedulerService::onChange(const char * cmd) {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
|
||||||
@@ -439,6 +448,9 @@ bool WebSchedulerService::onChange(const char * cmd) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "shuntingYard.hpp"
|
||||||
|
|
||||||
|
// handle condition schedules, parse string stored in schedule.time field
|
||||||
void WebSchedulerService::condition() {
|
void WebSchedulerService::condition() {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||||
@@ -476,6 +488,13 @@ void WebSchedulerService::loop() {
|
|||||||
cmd_changed_.pop_front();
|
cmd_changed_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
|
if (scheduleItem.active && scheduleItem.flags == 0) {
|
||||||
|
command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
|
||||||
|
scheduleItem.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check conditions every 10 seconds
|
// check conditions every 10 seconds
|
||||||
uint32_t uptime_sec = uuid::get_uptime_sec() / 10;
|
uint32_t uptime_sec = uuid::get_uptime_sec() / 10;
|
||||||
if (last_uptime_sec != uptime_sec) {
|
if (last_uptime_sec != uptime_sec) {
|
||||||
@@ -529,9 +548,10 @@ void WebSchedulerService::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process schedules async
|
||||||
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
||||||
while (1) {
|
while (1) {
|
||||||
delay(100); // no need to hurry
|
delay(10); // no need to hurry
|
||||||
if (!EMSESP::system_.upload_status()) {
|
if (!EMSESP::system_.upload_status()) {
|
||||||
EMSESP::webSchedulerService.loop();
|
EMSESP::webSchedulerService.loop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user