mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-03-21 17:16:34 +03:00
Merge pull request #2091 from MichaelDvP/dev
S3-Temperature, Thermostat dhw circuits
This commit is contained in:
@@ -97,7 +97,8 @@ const HardwareStatus = () => {
|
||||
(data.cpu_cores === 1 ? 'single-core)' : 'dual-core)') +
|
||||
' @ ' +
|
||||
data.cpu_freq_mhz +
|
||||
' Mhz'
|
||||
' Mhz' +
|
||||
(data.temperature ? ', T: ' + data.temperature + ' °C' : '')
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface SystemStatus {
|
||||
has_loader: boolean;
|
||||
has_partition: boolean;
|
||||
status: string;
|
||||
temperature?: number;
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
|
||||
@@ -210,7 +210,7 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
sensor.set_offset(EMSESP::nvs_.getChar(sensor.name().c_str()));
|
||||
}
|
||||
}
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() != 0));
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
|
||||
sensor.set_value(sensor.offset());
|
||||
}
|
||||
publish_sensor(sensor);
|
||||
@@ -756,7 +756,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
||||
sensor.set_offset(v);
|
||||
sensor.set_value(v);
|
||||
pinMode(sensor.gpio(), OUTPUT);
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() != 0));
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
|
||||
if (sensor.uom() == 0 && EMSESP::nvs_.getChar(sensor.name().c_str()) != (int8_t)sensor.offset()) {
|
||||
EMSESP::nvs_.putChar(sensor.name().c_str(), (int8_t)sensor.offset());
|
||||
}
|
||||
|
||||
@@ -286,6 +286,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
MAKE_CF_CB(set_pumpCharacter));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &pumpDelay_, DeviceValueType::UINT8, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay), 0, 60);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &pumpOnTemp_, DeviceValueType::UINT8, FL_(pumpOnTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_pumpOnTemp), 0, 60);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &selBurnPow_, DeviceValueType::UINT8, FL_(selBurnPow), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_burn_power), 0, 254);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &curBurnPow_, DeviceValueType::UINT8, FL_(curBurnPow), DeviceValueUOM::PERCENT);
|
||||
@@ -546,6 +548,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
FL_(hpMaxPower),
|
||||
DeviceValueUOM::PERCENT,
|
||||
MAKE_CF_CB(set_hpMaxPower));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&pvMaxComp_,
|
||||
DeviceValueType::UINT8,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(pvMaxComp),
|
||||
DeviceValueUOM::KW,
|
||||
MAKE_CF_CB(set_pvMaxComp));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&hpSetDiffPress_,
|
||||
DeviceValueType::UINT8,
|
||||
@@ -1296,6 +1305,7 @@ void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, pumpMode_, 11);
|
||||
has_update(telegram, boil2HystOff_, 12);
|
||||
has_update(telegram, boil2HystOn_, 13);
|
||||
has_update(telegram, pumpOnTemp_, 23); // https://github.com/emsesp/EMS-ESP32/issues/2088
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1929,6 +1939,7 @@ void Boiler::process_HpSilentMode(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, hpMaxPower_, 31);
|
||||
has_update(telegram, silentFrom_, 52); // in steps of 15 min
|
||||
has_update(telegram, silentTo_, 53); // in steps of 15 min
|
||||
has_update(telegram, pvMaxComp_, 54); // #2062
|
||||
has_update(telegram, hpshutdown_, 58); // 1 powers off
|
||||
}
|
||||
|
||||
@@ -2559,6 +2570,17 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// set pump logic temperature
|
||||
bool Boiler::set_pumpOnTemp(const char * value, const int8_t id) {
|
||||
int v;
|
||||
if (!Helpers::value2temperature(value, v)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
write_command(EMS_TYPE_UBAParameters, 23, v, EMS_TYPE_UBAParameters);
|
||||
return true;
|
||||
}
|
||||
|
||||
// note some boilers do not have this setting, than it's done by thermostat
|
||||
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent
|
||||
// on a RC310 it's 1=high, 2=eco
|
||||
@@ -3104,6 +3126,15 @@ bool Boiler::set_hpMaxPower(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_pvMaxComp(const char * value, const int8_t id) {
|
||||
float v;
|
||||
if (Helpers::value2float(value, v)) {
|
||||
write_command(0x484, 54, (uint8_t)(v * 10), 0x484);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_hpDiffPress(const char * value, const int8_t id) {
|
||||
int v;
|
||||
if (Helpers::value2number(value, v)) {
|
||||
|
||||
@@ -131,6 +131,7 @@ class Boiler : public EMSdevice {
|
||||
uint8_t pumpMode_; // pump setting proportional/deltaP
|
||||
uint8_t pumpCharacter_; // pump setting proportional/deltaP
|
||||
uint8_t pumpDelay_;
|
||||
uint8_t pumpOnTemp_;
|
||||
uint8_t burnMinPeriod_;
|
||||
uint8_t burnMinPower_;
|
||||
uint8_t burnMaxPower_;
|
||||
@@ -254,6 +255,7 @@ class Boiler : public EMSdevice {
|
||||
uint8_t maxHeatHeat_;
|
||||
uint8_t maxHeatDhw_;
|
||||
uint8_t hpMaxPower_;
|
||||
uint8_t pvMaxComp_;
|
||||
|
||||
uint8_t pvCooling_;
|
||||
uint8_t manDefrost_;
|
||||
@@ -419,6 +421,7 @@ class Boiler : public EMSdevice {
|
||||
bool set_max_pump(const char * value, const int8_t id);
|
||||
bool set_pumpMode(const char * value, const int8_t id);
|
||||
bool set_pumpCharacter(const char * value, const int8_t id);
|
||||
bool set_pumpOnTemp(const char * value, const int8_t id);
|
||||
bool set_hyst_on(const char * value, const int8_t id);
|
||||
bool set_hyst_off(const char * value, const int8_t id);
|
||||
inline bool set_hyst2_on(const char * value, const int8_t id) {
|
||||
@@ -476,6 +479,7 @@ class Boiler : public EMSdevice {
|
||||
bool set_hpCircPumpWw(const char * value, const int8_t id);
|
||||
bool set_hpPumpMode(const char * value, const int8_t id);
|
||||
bool set_hpMaxPower(const char * value, const int8_t id);
|
||||
bool set_pvMaxComp(const char * value, const int8_t id);
|
||||
bool set_hpDiffPress(const char * value, const int8_t id);
|
||||
bool set_hpPowerLimit(const char * value, const int8_t id);
|
||||
|
||||
|
||||
@@ -557,10 +557,10 @@ uint8_t Thermostat::HeatingCircuit::get_mode_type() const {
|
||||
return HeatingCircuit::Mode::DAY;
|
||||
}
|
||||
|
||||
std::shared_ptr<Thermostat::DhwCircuit> Thermostat::dhw_circuit(const uint8_t offset, const uint8_t dhw_num, const bool create) {
|
||||
std::shared_ptr<Thermostat::DhwCircuit> Thermostat::dhw_circuit(const uint8_t offset, const bool create) {
|
||||
// check for existing circuit
|
||||
for (const auto & dhw_circuit : dhw_circuits_) {
|
||||
if (dhw_circuit->dhw() == dhw_num - 1 || dhw_circuit->offset() == offset) {
|
||||
if (dhw_circuit->offset() == offset) {
|
||||
return dhw_circuit;
|
||||
}
|
||||
}
|
||||
@@ -568,7 +568,7 @@ std::shared_ptr<Thermostat::DhwCircuit> Thermostat::dhw_circuit(const uint8_t of
|
||||
return nullptr;
|
||||
}
|
||||
// create a new circuit object and add to the list
|
||||
auto new_dhw = std::make_shared<Thermostat::DhwCircuit>(offset, dhw_num);
|
||||
auto new_dhw = std::make_shared<Thermostat::DhwCircuit>(offset);
|
||||
dhw_circuits_.push_back(new_dhw);
|
||||
// register the device values
|
||||
register_device_values_dhw(new_dhw);
|
||||
@@ -600,7 +600,7 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
|
||||
if (hc == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
has_update(telegram, ibaCalIntTemperature_, 0);
|
||||
has_update(telegram, backlight_, 1);
|
||||
has_update(telegram, dhw->wwMode_, 2);
|
||||
@@ -867,7 +867,7 @@ void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// Settings WW 0x37 - RC35
|
||||
void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram) {
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
has_update(telegram, dhw->wwProgMode_, 0); // 0-like hc, 0xFF own prog
|
||||
has_update(telegram, dhw->wwCircProg_, 1); // 0-like hc, 0xFF own prog
|
||||
has_update(telegram, dhw->wwMode_, 2); // 0-off, 1-on, 2-auto
|
||||
@@ -881,7 +881,7 @@ void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram
|
||||
|
||||
// Settings WW 0x3A - RC30
|
||||
void Thermostat::process_RC30wwSettings(std::shared_ptr<const Telegram> telegram) {
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
has_update(telegram, dhw->wwMode_, 0); // 0-on, 1-off, 2-auto
|
||||
has_update(telegram, dhw->wwWhenModeOff_, 1); // 0-off, 0xFF on
|
||||
has_update(telegram, dhw->wwDisinfecting_, 2); // 0-off, 0xFF on
|
||||
@@ -891,7 +891,7 @@ void Thermostat::process_RC30wwSettings(std::shared_ptr<const Telegram> telegram
|
||||
|
||||
// type 0x38 (ww) and 0x39 (circ)
|
||||
void Thermostat::process_RC35wwTimer(std::shared_ptr<const Telegram> telegram) {
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1))
|
||||
|| (!telegram->offset && telegram->type_id == 0x38 && !strlen(dhw->wwSwitchTime_) && telegram->message_length > 1)
|
||||
|| (!telegram->offset && telegram->type_id == 0x39 && !strlen(dhw->wwCircSwitchTime_) && telegram->message_length > 1)) {
|
||||
@@ -1004,7 +1004,7 @@ void Thermostat::process_JunkersSetMixer(std::shared_ptr<const Telegram> telegra
|
||||
}
|
||||
|
||||
void Thermostat::process_JunkersWW(std::shared_ptr<const Telegram> telegram) {
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
has_bitupdate(telegram, dhw->wwCharge_, 0, 3);
|
||||
}
|
||||
|
||||
@@ -1185,7 +1185,7 @@ void Thermostat::process_RC300Curve(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// types 0x31B
|
||||
void Thermostat::process_RC300WWtemp(std::shared_ptr<const Telegram> telegram) {
|
||||
auto dhw = dhw_circuit(0, 1, true);
|
||||
auto dhw = dhw_circuit(0, true);
|
||||
has_update(telegram, dhw->wwSetTemp_, 0);
|
||||
has_update(telegram, dhw->wwSetTempLow_, 1);
|
||||
}
|
||||
@@ -1195,8 +1195,8 @@ void Thermostat::process_RC300WWtemp(std::shared_ptr<const Telegram> telegram) {
|
||||
// RC300WWmode(0x2F6), data: 02 FF 04 00 00 00 08 05 00 08 04 00 00 00 00 00 00 00 00 00 01
|
||||
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
|
||||
uint8_t circuit = 0;
|
||||
telegram->read_value(circuit, 0);
|
||||
auto dhw = dhw_circuit(telegram->type_id - 0x2F5, circuit, circuit != 0);
|
||||
telegram->read_value(circuit, 0); // 00-no circuit, 01-boiler, 02-mixer
|
||||
auto dhw = dhw_circuit(telegram->type_id - 0x2F5, circuit != 0);
|
||||
if (dhw == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -2105,7 +2105,10 @@ bool Thermostat::set_roomsensor(const char * value, const int8_t id) {
|
||||
|
||||
// sets the thermostat ww working mode, where mode is a string, ems and ems+
|
||||
bool Thermostat::set_wwmode(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint8_t set;
|
||||
|
||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC10) {
|
||||
@@ -2118,24 +2121,24 @@ bool Thermostat::set_wwmode(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t modes[] = {0, 5, 1, 2, 4};
|
||||
write_command(0x02F5 + dhw, 2, modes[set], 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_CR120) {
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwMode6))) { // normal, comfort, auto
|
||||
return false;
|
||||
}
|
||||
const uint8_t modes[] = {0, 2, 4};
|
||||
write_command(0x02F5 + dhw, 2, modes[set], 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) { // Rego3000 - https://github.com/emsesp/EMS-ESP32/issues/1692
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwMode5))) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t modes[] = {1, 2, 5};
|
||||
write_command(0x02F5 + dhw, 2, modes[set], 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 2, modes[set], 0x02F5 + dhw->offset());
|
||||
} else if ((model() == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwMode))) {
|
||||
return false;
|
||||
}
|
||||
write_command(0x02F5 + dhw, 2, set, 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 2, set, 0x02F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwMode3))) {
|
||||
return false;
|
||||
@@ -2189,7 +2192,10 @@ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) {
|
||||
|
||||
// Set ww charge RC300, ems+
|
||||
bool Thermostat::set_wwcharge(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
@@ -2198,7 +2204,7 @@ bool Thermostat::set_wwcharge(const char * value, const int8_t id) {
|
||||
if ((model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS)) {
|
||||
write_command(0x0115, 0, b ? 0xFF : 0x00, 0x01D3);
|
||||
} else {
|
||||
write_command(0x02F5 + dhw, 11, b ? 0xFF : 0x00, 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 11, b ? 0xFF : 0x00, 0x02F5 + dhw->offset());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2206,14 +2212,17 @@ bool Thermostat::set_wwcharge(const char * value, const int8_t id) {
|
||||
|
||||
// Set ww charge duration in steps of 15 min, ems+
|
||||
bool Thermostat::set_wwchargeduration(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int t;
|
||||
if (!Helpers::value2number(value, t)) {
|
||||
return false;
|
||||
}
|
||||
t = (t + 8) / 15;
|
||||
|
||||
write_command(0x2F5 + dhw, 10, t, 0x02F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 10, t, 0x02F5 + dhw->offset());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2296,14 +2305,17 @@ bool Thermostat::set_switchProgMode(const char * value, const int8_t id) {
|
||||
|
||||
// sets the thermostat ww circulation working mode, where mode is a string
|
||||
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint8_t set;
|
||||
|
||||
if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwCircMode))) {
|
||||
return false;
|
||||
}
|
||||
write_command(0x02F5 + dhw, 3, set, 0x02F5 + dhw);
|
||||
write_command(0x02F5 + dhw->offset(), 3, set, 0x02F5 + dhw->offset());
|
||||
return true;
|
||||
}
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_wwMode2))) {
|
||||
@@ -2316,18 +2328,24 @@ bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
bool Thermostat::set_wwDailyHeating(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
write_command(0x2F5 + dhw, 8, b ? 0xFF : 0x00, 0x2F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 8, b ? 0xFF : 0x00, 0x2F5 + dhw->offset());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Thermostat::set_wwDailyHeatTime(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int set;
|
||||
if (!Helpers::value2number(value, set)) {
|
||||
return false;
|
||||
@@ -2339,20 +2357,23 @@ bool Thermostat::set_wwDailyHeatTime(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
write_command(0x2F5 + dhw, 9, t, 0x2F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 9, t, 0x2F5 + dhw->offset());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Thermostat::set_wwDisinfect(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
write_command(0x2F5 + dhw, 5, b ? 0xFF : 0x00, 0x2F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 5, b ? 0xFF : 0x00, 0x2F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
write_command(EMS_TYPE_RC30wwSettings, 2, b ? 0xFF : 0x00, EMS_TYPE_RC30wwSettings);
|
||||
} else {
|
||||
@@ -2363,14 +2384,17 @@ bool Thermostat::set_wwDisinfect(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
bool Thermostat::set_wwDisinfectDay(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint8_t set;
|
||||
if (!Helpers::value2enum(value, set, FL_(enum_dayOfWeek))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
write_command(0x2F5 + dhw, 7, set, 0x2F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 7, set, 0x2F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
write_command(EMS_TYPE_RC30wwSettings, 3, set, EMS_TYPE_RC30wwSettings);
|
||||
} else {
|
||||
@@ -2381,13 +2405,16 @@ bool Thermostat::set_wwDisinfectDay(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
bool Thermostat::set_wwDisinfectHour(const char * value, const int8_t id) {
|
||||
uint8_t dhw = id2dhw(id);
|
||||
auto dhw = dhw_circuit( id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int set;
|
||||
if (isRC300() || (model() == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
if (!Helpers::value2number(value, set, 0, 1431)) {
|
||||
return false;
|
||||
}
|
||||
write_command(0x2F5 + dhw, 6, (set + 8) / 15, 0x2F5 + dhw);
|
||||
write_command(0x2F5 + dhw->offset(), 6, (set + 8) / 15, 0x2F5 + dhw->offset());
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) {
|
||||
if (!Helpers::value2number(value, set, 0, 23)) {
|
||||
return false;
|
||||
@@ -3401,7 +3428,7 @@ bool Thermostat::set_switchtime2(const char * value, const int8_t id) {
|
||||
}
|
||||
// sets a single switchtime in the thermostat dhw program for RC35
|
||||
bool Thermostat::set_wwCircSwitchTime(const char * value, const int8_t id) {
|
||||
auto dhw = dhw_circuit(255, id2dhw(id));
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -3418,7 +3445,7 @@ bool Thermostat::set_wwCircSwitchTime(const char * value, const int8_t id) {
|
||||
|
||||
// sets a single switchtime in the thermostat circulation program for RC35
|
||||
bool Thermostat::set_wwSwitchTime(const char * value, const int8_t id) {
|
||||
auto dhw = dhw_circuit(255, id2dhw(id));
|
||||
auto dhw = dhw_circuit(id2dhw(id));
|
||||
if (dhw != nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -4366,7 +4393,7 @@ void Thermostat::register_device_values() {
|
||||
register_device_values_hc(new_hc);
|
||||
|
||||
// also a dhw circuit...
|
||||
auto new_dhw = std::make_shared<Thermostat::DhwCircuit>(0, 1); // offset 0, dhw num 1
|
||||
auto new_dhw = std::make_shared<Thermostat::DhwCircuit>(0); // offset 0, dhw num 1
|
||||
dhw_circuits_.push_back(new_dhw);
|
||||
register_device_values_dhw(new_dhw);
|
||||
#endif
|
||||
@@ -4836,7 +4863,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
|
||||
|
||||
// registers the values for a heating circuit
|
||||
void Thermostat::register_device_values_dhw(std::shared_ptr<Thermostat::DhwCircuit> dhw) {
|
||||
int8_t tag = DeviceValueTAG::TAG_DHW1 + dhw->dhw();
|
||||
int8_t tag = dhw->id();
|
||||
switch (this->model()) {
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC100:
|
||||
case EMSdevice::EMS_DEVICE_FLAG_RC300:
|
||||
|
||||
@@ -173,9 +173,8 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
class DhwCircuit {
|
||||
public:
|
||||
DhwCircuit(const uint8_t offset, const uint8_t dhw_num)
|
||||
: offset_(offset)
|
||||
, dhw_num_(dhw_num) {
|
||||
DhwCircuit(const uint8_t offset)
|
||||
: dhw_num_(offset) {
|
||||
}
|
||||
~DhwCircuit() = default;
|
||||
uint8_t wwExtra_;
|
||||
@@ -201,16 +200,16 @@ class Thermostat : public EMSdevice {
|
||||
char wwHoliday_[22];
|
||||
char wwVacation_[22];
|
||||
|
||||
uint8_t dhw() const {
|
||||
return dhw_num_ - 1;
|
||||
uint8_t id() const { // returns TAG(id)
|
||||
return DeviceValueTAG::TAG_DHW1 + dhw_num_;
|
||||
}
|
||||
uint8_t offset() const {
|
||||
return offset_;
|
||||
|
||||
uint8_t offset() const { // returns telegram offset
|
||||
return dhw_num_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t offset_; // telegram offset to base telegram
|
||||
uint8_t dhw_num_; // dhw circuit number 1..10
|
||||
uint8_t dhw_num_; // dhw circuit number 0..10
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -230,7 +229,7 @@ class Thermostat : public EMSdevice {
|
||||
|| (model() == EMSdevice::EMS_DEVICE_FLAG_CR120));
|
||||
}
|
||||
|
||||
inline uint8_t id2dhw(const int8_t id) const {
|
||||
inline uint8_t id2dhw(const int8_t id) const { // returns telegram offset for TAG(id)
|
||||
return id - DeviceValueTAG::TAG_DHW1;
|
||||
}
|
||||
|
||||
@@ -394,7 +393,7 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const int8_t id);
|
||||
std::shared_ptr<Thermostat::DhwCircuit> dhw_circuit(const uint8_t offset = 0, const uint8_t dhw_num = 255, const bool create = false);
|
||||
std::shared_ptr<Thermostat::DhwCircuit> dhw_circuit(const uint8_t offset, const bool create = false);
|
||||
|
||||
void register_device_values_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc);
|
||||
void register_device_values_dhw(std::shared_ptr<Thermostat::DhwCircuit> dhw);
|
||||
|
||||
@@ -332,6 +332,9 @@ std::string EMSdevice::to_string_short() {
|
||||
|
||||
// for each telegram that has the fetch value set (true) do a read request
|
||||
void EMSdevice::fetch_values() {
|
||||
if (!active_) {
|
||||
return;
|
||||
}
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("Fetching values for deviceID 0x%02X", device_id());
|
||||
#endif
|
||||
|
||||
@@ -111,6 +111,10 @@ class EMSdevice {
|
||||
return brand_;
|
||||
}
|
||||
|
||||
inline void active(bool active) {
|
||||
active_ = active;
|
||||
}
|
||||
|
||||
// set custom device name
|
||||
inline void custom_name(std::string const & custom_name) {
|
||||
custom_name_ = custom_name;
|
||||
@@ -497,6 +501,7 @@ class EMSdevice {
|
||||
std::string custom_name_ = ""; // custom name
|
||||
uint8_t flags_ = 0;
|
||||
uint8_t brand_ = Brand::NO_BRAND;
|
||||
bool active_ = true;
|
||||
|
||||
bool ha_config_done_ = false;
|
||||
bool has_update_ = false;
|
||||
|
||||
@@ -56,7 +56,7 @@ WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP
|
||||
using DeviceFlags = EMSdevice;
|
||||
using DeviceType = EMSdevice::DeviceType;
|
||||
|
||||
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
|
||||
std::deque<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
|
||||
std::vector<EMSESP::Device_record> EMSESP::device_library_; // library of all our known EMS devices, in heap
|
||||
|
||||
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
|
||||
@@ -943,11 +943,10 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
|
||||
// for each byte, check the bits and determine the device_id
|
||||
for (uint8_t data_byte = 0; data_byte < telegram->message_length; data_byte++) {
|
||||
uint8_t next_byte = telegram->message_data[data_byte];
|
||||
|
||||
if (next_byte) {
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
if (next_byte & 0x01) {
|
||||
uint8_t device_id = ((data_byte + 1) * 8) + bit;
|
||||
EMSESP::device_active(device_id, next_byte & 0x01);
|
||||
if (next_byte & 0x01) {
|
||||
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
|
||||
// when the version info is received, it will automagically add the device
|
||||
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) {
|
||||
@@ -958,7 +957,6 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
|
||||
next_byte = next_byte >> 1; // advance 1 bit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the Version telegram (type 0x02), which is a common type
|
||||
@@ -1141,6 +1139,15 @@ bool EMSESP::device_exists(const uint8_t device_id) {
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
void EMSESP::device_active(const uint8_t device_id, const bool active) {
|
||||
for (auto & emsdevice : emsdevices) {
|
||||
if (emsdevice && emsdevice->is_device_id(device_id)) {
|
||||
emsdevice->active(active);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for each associated EMS device go and get its system information
|
||||
void EMSESP::show_devices(uuid::console::Shell & shell) {
|
||||
if (emsdevices.empty()) {
|
||||
|
||||
@@ -125,6 +125,7 @@ class EMSESP {
|
||||
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
|
||||
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
static void device_active(const uint8_t device_id, const bool active);
|
||||
static bool cmd_is_readonly(const uint8_t device_type, const uint8_t device_id, const char * cmd, const int8_t id);
|
||||
|
||||
static uint8_t device_id_from_cmd(const uint8_t device_type, const char * cmd, const int8_t id);
|
||||
@@ -219,7 +220,7 @@ class EMSESP {
|
||||
|
||||
static bool return_not_found(JsonObject output, const char * msg, const char * cmd);
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
static std::deque<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
|
||||
// services
|
||||
static Mqtt mqtt_;
|
||||
|
||||
@@ -360,6 +360,7 @@ MAKE_TRANSLATION(emergencyOps, "emergencyops", "emergency operation", "Notbetrie
|
||||
MAKE_TRANSLATION(emergencyTemp, "emergencytemp", "emergency temperature", "Notfalltemperatur", "Noodtemperatuur", "Nöddrift temperatur", "temperatura w trybie awaryjnym", "nødtemperatur", "température d'urgence", "acil durum sıcaklığı", "temperatura di emergenza", "núdzová teplota")
|
||||
MAKE_TRANSLATION(pumpMode, "pumpmode", "boiler pump mode", "Kesselpumpenmodus", "Ketelpomp modus", "", "tryb pracy pompy kotła", "pumpemodus", "", "pompa modu", "modalità pompa caldaia", "režim kotlového čerpadla") // TODO translate
|
||||
MAKE_TRANSLATION(pumpCharacter, "pumpcharacter", "boiler pump characteristic", "Charakteristik der Kesselpumpe", "karakteristiek ketelpomp", "pannpumpsegenskaper", "charakterystyka pompy kotłowej", "kjelepumpekarakteristikk", "caractéristique de la pompe de la chaudière", "gazan nasosy", "caratteristica della pompa della caldaia", "charakteristika kotlového čerpadla") // TODO translate
|
||||
MAKE_TRANSLATION(pumpOnTemp, "pumpontemp", "pump logic temperature", "Pumpenlogiktemperatur", "", "", "", "", "", "", "", "") // TODO translate
|
||||
MAKE_TRANSLATION(headertemp, "headertemp", "low loss header", "Hydr. Weiche", "open verdeler", "", "sprzęgło hydrauliczne", "", "bouteille de déc. hydr.", "isı bloğu gidiş suyu sıc.", "comp. idr.", "nízkostratová hlavica") // TODO translate
|
||||
MAKE_TRANSLATION(heatblock, "heatblock", "heating block", "Wärmezelle", "Aanvoertemp. warmtecel", "", "blok grzewczy", "", "départ corps de chauffe", "Hid.denge kabı sıcaklığı", "mandata scamb. pr.", "vykurovací blok") // TODO translate
|
||||
|
||||
@@ -409,6 +410,7 @@ MAKE_TRANSLATION(hpSwitchValve, "hpswitchvalve", "switch valve", "Schaltventil",
|
||||
MAKE_TRANSLATION(hpActivity, "hpactivity", "compressor activity", "Kompressoraktivität", "Compressoractiviteit", "Kompressoraktivitet", "pompa ciepła, aktywność sprężarki", "kompressoraktivitet", "", "hp ısı pompası", "attività compressore", "činnosť kompresora") // TODO translate
|
||||
|
||||
MAKE_TRANSLATION(hpMaxPower, "hpmaxpower", "compressor max power", "max. Kompressorleistung", "", "", "maksymalna wydajność sprężarki", "", "", "", "", "max výkon kompresora") // TODO translate
|
||||
MAKE_TRANSLATION(pvMaxComp, "pvmaxcomp", "pv compressor max power", "PV max. Kompressorleistung", "", "", "maksymalna wydajność sprężarki", "", "", "", "", "pv max výkon kompresora") // TODO translate
|
||||
MAKE_TRANSLATION(hpPower, "hppower", "compressor power output", "Kompressorleistung", "Compressorvermogen", "Kompressoreffekt", "moc wyjściowa sprężarki", "kompressoreffekt", "puissance de sortie compresseur", "ısı pompası güç çıkışı", "potenza uscita compressore", "výkon kompresora")
|
||||
MAKE_TRANSLATION(hpTc0, "hptc0", "heat carrier return (TC0)", "Kältemittelrücklauf (TC0)", "Koudemiddel retour (TC0)", "Värmebärare Retur (TC0)", "temperatura nośnika ciepła na powrocie (TC0)", "kjølemiddel retur (TC0)", "retour caloporteur (TC0)", "sıcak su dönüşü (TC0)", "ritorno del refrigerante (TC0)", "návrat nosiča tepla (TC0)")
|
||||
MAKE_TRANSLATION(hpTc1, "hptc1", "heat carrier forward (TC1)", "Kältemittelvorlauf (TC1)", "Koudemiddel aanvoer (TC1)", "Värmebärare Framledning (TC1)", "temperatura nośnika ciepła pierwotna (TC1)", "kjølemiddel tur (TC1)", "avance caloporteur (TC1)", "sıcak su çıkışı (TC1)", "flusso di refrigerante (TC1)", "nosič tepla vpred (TC1)")
|
||||
|
||||
@@ -525,7 +525,7 @@ void Mqtt::on_connect() {
|
||||
resubscribe();
|
||||
|
||||
// publish to the last will topic (see Mqtt::start() function) to say we're alive
|
||||
queue_publish_retain("status", "online", false); // with retain off
|
||||
queue_publish_retain("status", "online", true); // retain: https://github.com/emsesp/EMS-ESP32/discussions/2086
|
||||
}
|
||||
|
||||
// Home Assistant Discovery - the main master Device called EMS-ESP
|
||||
|
||||
@@ -458,6 +458,21 @@ void System::start() {
|
||||
appused_ = ESP.getSketchSize() / 1024;
|
||||
appfree_ = esp_ota_get_running_partition()->size / 1024 - appused_;
|
||||
refreshHeapMem(); // refresh free heap and max alloc heap
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();
|
||||
temp_sensor_get_config(&temp_sensor);
|
||||
temp_sensor.dac_offset = TSENS_DAC_DEFAULT; // DEFAULT: range:-10℃ ~ 80℃, error < 1℃.
|
||||
temp_sensor_set_config(temp_sensor);
|
||||
temp_sensor_start();
|
||||
temp_sensor_read_celsius(&temperature_);
|
||||
#else
|
||||
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||
temperature_sensor_install(&temp_sensor_config, &temperature_handle_);
|
||||
temperature_sensor_enable(temperature_handle_);
|
||||
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
|
||||
@@ -692,6 +707,9 @@ void System::heartbeat_json(JsonObject output) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
output["freemem"] = getHeapMem();
|
||||
output["max_alloc"] = getMaxAllocMem();
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
output["temperature"] = temperature_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -770,6 +788,16 @@ void System::system_check() {
|
||||
if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) {
|
||||
last_system_check_ = uuid::get_uptime();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
temp_sensor_read_celsius(&temperature_);
|
||||
#else
|
||||
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef EMSESP_PINGTEST
|
||||
static uint64_t ping_count = 0;
|
||||
LOG_NOTICE("Ping test, #%d", ping_count++);
|
||||
@@ -987,6 +1015,9 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
|
||||
shell.printfln(" SDK version: %s", ESP.getSdkVersion());
|
||||
shell.printfln(" CPU frequency: %lu MHz", ESP.getCpuFreqMHz());
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
shell.printfln(" CPU temperature: %d °C", (int)temperature());
|
||||
#endif
|
||||
shell.printfln(" Free heap/Max alloc: %lu KB / %lu KB", getHeapMem(), getMaxAllocMem());
|
||||
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
|
||||
uint32_t FSused = LittleFS.usedBytes() / 1024;
|
||||
@@ -1444,6 +1475,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
node["freePsram"] = ESP.getFreePsram() / 1024;
|
||||
}
|
||||
node["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
node["temperature"] = EMSESP::system_.temperature();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// Network Status
|
||||
|
||||
21
src/system.h
21
src/system.h
@@ -39,6 +39,14 @@
|
||||
#include <uuid/log.h>
|
||||
#include <PButton.h>
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
#include "driver/temp_sensor.h"
|
||||
#else
|
||||
#include "driver/temperature_sensor.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
#define EMSESP_FS_CONFIG_DIRECTORY "/config"
|
||||
@@ -303,6 +311,12 @@ class System {
|
||||
test_set_all_active_ = n;
|
||||
}
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
float temperature() {
|
||||
return temperature_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
static bool restart_requested_;
|
||||
@@ -395,6 +409,13 @@ class System {
|
||||
uint32_t psram_;
|
||||
uint32_t appused_;
|
||||
uint32_t appfree_;
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
temperature_sensor_handle_t temperature_handle_ = NULL;
|
||||
#endif
|
||||
float temperature_ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -119,6 +119,9 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
root["free_psram"] = ESP.getFreePsram() / 1024;
|
||||
}
|
||||
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
root["temperature"] = EMSESP::system_.temperature();
|
||||
#endif
|
||||
|
||||
// check for a factory partition first
|
||||
const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr);
|
||||
|
||||
Reference in New Issue
Block a user