Merge pull request #640 from MichaelDvP/dev

mqtt ha settings, show memory in KB, etc.
This commit is contained in:
Proddy
2022-09-24 17:40:07 +02:00
committed by GitHub
20 changed files with 7020 additions and 2461 deletions

View File

@@ -6,6 +6,8 @@
- Translations in Web UI and all device entity names to German. [#22](https://github.com/emsesp/EMS-ESP32/issues/22) - Translations in Web UI and all device entity names to German. [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
- Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620) - Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620)
- Add Greenstar 30Ri boiler
- Add program memory info
## Fixed ## Fixed
@@ -13,9 +15,10 @@
## Changed ## Changed
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) - Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_`
- RF room temperature sensor are shown as thermostat
## **BREAKING CHANGES:** ## **BREAKING CHANGES:**
- MQTT Discovery (Home Assistant) entity names are now prefixed with the hostname, e.g. `select.thermostat_hc1_mode` becomes `select.emsesp_thermostat_hc1_mode`. You will need to recreate any custom dashboards.
- When upgrading from 3.4.x you may need to erase the flash on the ESP32 before uploading the firmware. Make sure you make a backup of the settings and customizations via the WebUI (System->Upload/Download) - When upgrading from 3.4.x you may need to erase the flash on the ESP32 before uploading the firmware. Make sure you make a backup of the settings and customizations via the WebUI (System->Upload/Download)

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ import ShowChartIcon from '@mui/icons-material/ShowChart';
import MemoryIcon from '@mui/icons-material/Memory'; import MemoryIcon from '@mui/icons-material/Memory';
import AppsIcon from '@mui/icons-material/Apps'; import AppsIcon from '@mui/icons-material/Apps';
import SdStorageIcon from '@mui/icons-material/SdStorage'; import SdStorageIcon from '@mui/icons-material/SdStorage';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import FolderIcon from '@mui/icons-material/Folder'; import FolderIcon from '@mui/icons-material/Folder';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
@@ -272,14 +273,14 @@ const SystemStatusForm: FC = () => {
primary={LL.HEAP()} primary={LL.HEAP()}
secondary={ secondary={
formatNumber(data.free_heap) + formatNumber(data.free_heap) +
' / ' + ' KB / ' +
formatNumber(data.max_alloc_heap) + formatNumber(data.max_alloc_heap) +
' bytes ' + ' KB ' +
(data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '') (data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '')
} }
/> />
</ListItem> </ListItem>
{data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0 && ( {data.esp_platform === EspPlatform.ESP32 && data.psram_size !== undefined && data.free_psram !== undefined && (
<> <>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -290,7 +291,7 @@ const SystemStatusForm: FC = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.PSRAM()} primary={LL.PSRAM()}
secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'} secondary={formatNumber(data.psram_size) + ' KB / ' + formatNumber(data.free_psram) + ' KB'}
/> />
</ListItem> </ListItem>
</> </>
@@ -305,11 +306,23 @@ const SystemStatusForm: FC = () => {
<ListItemText <ListItemText
primary={LL.FLASH()} primary={LL.FLASH()}
secondary={ secondary={
formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz' formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
} }
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<SdCardAlertIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.APPSIZE()}
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>
@@ -318,14 +331,7 @@ const SystemStatusForm: FC = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.FILESYSTEM()} primary={LL.FILESYSTEM()}
secondary={ secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
formatNumber(data.fs_used) +
' / ' +
formatNumber(data.fs_total) +
' bytes (' +
formatNumber(data.fs_total - data.fs_used) +
'\xa0bytes free)'
}
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />

View File

@@ -182,7 +182,8 @@ const de: Translation = {
HEAP: 'RAM Speicher (Frei / Max Belegt)', HEAP: 'RAM Speicher (Frei / Max Belegt)',
PSRAM: 'PSRAM (Größe / Frei)', PSRAM: 'PSRAM (Größe / Frei)',
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)', FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
FILESYSTEM: 'Dateisystem (Genutzt / Gesamt)', APPSIZE: 'Programm (Genutzt / Frei)',
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
BUFFER_SIZE: 'Puffergröße', BUFFER_SIZE: 'Puffergröße',
COMPACT: 'Kompakte Darstellung', COMPACT: 'Kompakte Darstellung',
ENABLE_OTA: 'OTA Updates verwenden', ENABLE_OTA: 'OTA Updates verwenden',

View File

@@ -182,7 +182,8 @@ const en: BaseTranslation = {
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
FILESYSTEM: 'File System (Used / Total)', APPSIZE: 'Application (Used / Free)',
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Buffer Size', BUFFER_SIZE: 'Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
ENABLE_OTA: 'Enable OTA Updates', ENABLE_OTA: 'Enable OTA Updates',

View File

@@ -730,7 +730,11 @@ type RootTranslation = {
*/ */
FLASH: string FLASH: string
/** /**
* File System (Used / Total) * Application (Used / Free)
*/
APPSIZE: string
/**
* File System (Used / Free)
*/ */
FILESYSTEM: string FILESYSTEM: string
/** /**
@@ -1743,7 +1747,11 @@ export type TranslationFunctions = {
*/ */
FLASH: () => LocalizedString FLASH: () => LocalizedString
/** /**
* File System (Used / Total) * Application (Used / Free)
*/
APPSIZE: () => LocalizedString
/**
* File System (Used / Free)
*/ */
FILESYSTEM: () => LocalizedString FILESYSTEM: () => LocalizedString
/** /**

View File

@@ -181,7 +181,8 @@ const nl: BaseTranslation = {
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
FILESYSTEM: 'File System (Used / Total)', APPSIZE: 'Application (Used / Free)',
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Buffer Size', BUFFER_SIZE: 'Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
ENABLE_OTA: 'Acitveer OTA Updates', ENABLE_OTA: 'Acitveer OTA Updates',

View File

@@ -182,7 +182,8 @@ const no: BaseTranslation = {
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
FILESYSTEM: 'File System (Used / Total)', APPSIZE: 'Application (Used / Free)',
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Buffer Size', BUFFER_SIZE: 'Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
ENABLE_OTA: 'Enable OTA Updates', ENABLE_OTA: 'Enable OTA Updates',

View File

@@ -184,7 +184,8 @@ const pl: BaseTranslation = {
HEAP: 'Pamięć (Wolna / Zaalokowana)', HEAP: 'Pamięć (Wolna / Zaalokowana)',
PSRAM: 'PSRAM (Rozmiar / Wolna)', PSRAM: 'PSRAM (Rozmiar / Wolna)',
FLASH: 'Pamięć flash ( Rozmiar / Taktowanie)', FLASH: 'Pamięć flash ( Rozmiar / Taktowanie)',
FILESYSTEM: 'System plików (Wykorzystano / Cała pojemność)', APPSIZE: 'Application (Wykorzystano / Wolna)',
FILESYSTEM: 'System plików (Wykorzystano / Wolna)',
BUFFER_SIZE: 'Rozmiar bufora', BUFFER_SIZE: 'Rozmiar bufora',
COMPACT: 'Kompaktowy', COMPACT: 'Kompaktowy',
ENABLE_OTA: 'Włącz aktualizację OTA', ENABLE_OTA: 'Włącz aktualizację OTA',

View File

@@ -181,7 +181,8 @@ const se: BaseTranslation = {
HEAP: 'Heap (Ledigt / Max allokerat)', HEAP: 'Heap (Ledigt / Max allokerat)',
PSRAM: 'PSRAM (Storlek / Ledigt)', PSRAM: 'PSRAM (Storlek / Ledigt)',
FLASH: 'Flash Chip (Storlek / Hastighet)', FLASH: 'Flash Chip (Storlek / Hastighet)',
FILESYSTEM: 'Filsystem (Använt / Totalt)', APPSIZE: 'Application (Använt / Ledigt)',
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
BUFFER_SIZE: 'Bufferstorlek', BUFFER_SIZE: 'Bufferstorlek',
COMPACT: 'Komprimera', COMPACT: 'Komprimera',
ENABLE_OTA: 'Aktivera OTA-uppdateringar', ENABLE_OTA: 'Aktivera OTA-uppdateringar',

View File

@@ -12,16 +12,18 @@ interface ESPSystemStatus {
sdk_version: string; sdk_version: string;
flash_chip_size: number; flash_chip_size: number;
flash_chip_speed: number; flash_chip_speed: number;
app_used: number;
app_free: number;
fs_used: number; fs_used: number;
fs_total: number; fs_free: number;
uptime: string; uptime: string;
free_mem: number; free_mem: number;
} }
export interface ESP32SystemStatus extends ESPSystemStatus { export interface ESP32SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP32; esp_platform: EspPlatform.ESP32;
psram_size: number; psram_size?: number;
free_psram: number; free_psram?: number;
} }
export interface ESP8266SystemStatus extends ESPSystemStatus { export interface ESP8266SystemStatus extends ESPSystemStatus {

View File

@@ -15,18 +15,22 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["emsesp_version"] = EMSESP_APP_VERSION; root["emsesp_version"] = EMSESP_APP_VERSION;
root["esp_platform"] = "ESP32"; root["esp_platform"] = "ESP32";
root["max_alloc_heap"] = ESP.getMaxAllocHeap(); root["max_alloc_heap"] = ESP.getMaxAllocHeap() / 1024;
root["psram_size"] = ESP.getPsramSize(); #if defined(BOARD_HAS_PSRAM)
root["free_psram"] = ESP.getFreePsram(); root["psram_size"] = ESP.getPsramSize() / 1024;
root["cpu_freq_mhz"] = ESP.getCpuFreqMHz(); root["free_psram"] = ESP.getFreePsram() / 1024;
root["free_heap"] = ESP.getFreeHeap(); #endif
root["sdk_version"] = ESP.getSdkVersion(); root["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
root["flash_chip_size"] = ESP.getFlashChipSize(); root["free_heap"] = ESP.getFreeHeap() / 1024;
root["flash_chip_speed"] = ESP.getFlashChipSpeed(); root["sdk_version"] = ESP.getSdkVersion();
root["flash_chip_size"] = ESP.getFlashChipSize() / 1024;
root["fs_total"] = emsesp::EMSESP::system_.FStotal(); root["flash_chip_speed"] = ESP.getFlashChipSpeed();
root["fs_used"] = LittleFS.usedBytes(); root["app_used"] = emsesp::EMSESP::system_.appUsed();
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); root["app_free"] = emsesp::EMSESP::system_.appFree();
uint32_t FSused = LittleFS.usedBytes() / 1024;
root["fs_used"] = FSused;
root["fs_free"] = emsesp::EMSESP::system_.FStotal() - FSused;
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
response->setLength(); response->setLength();
request->send(response); request->send(response);

View File

@@ -26,9 +26,11 @@ uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::D
void AnalogSensor::start() { void AnalogSensor::start() {
reload(); // fetch the list of sensors from our customization service reload(); // fetch the list of sensors from our customization service
if (analog_enabled_) { if (!analog_enabled_) {
analogSetAttenuation(ADC_2_5db); // for all channels 1.5V return;
} }
analogSetAttenuation(ADC_2_5db); // for all channels 1.5V
LOG_INFO(F("Starting Analog sensor service")); LOG_INFO(F("Starting Analog sensor service"));
@@ -354,7 +356,7 @@ void AnalogSensor::remove_ha_topic(const uint8_t gpio) const {
LOG_DEBUG(F("Removing HA config for analog sensor GPIO %d"), gpio); LOG_DEBUG(F("Removing HA config for analog sensor GPIO %d"), gpio);
#endif #endif
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::base().c_str(), gpio); snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::basename().c_str(), gpio);
Mqtt::publish_ha(topic); Mqtt::publish_ha(topic);
} }
@@ -376,8 +378,7 @@ void AnalogSensor::publish_values(const bool force) {
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
if (sensor.type() != AnalogType::NOTUSED) { if (sensor.type() != AnalogType::NOTUSED) {
if (Mqtt::is_nested() || Mqtt::ha_enabled()) { if (Mqtt::is_nested()) {
// nested
char s[10]; char s[10];
JsonObject dataSensor = doc.createNestedObject(Helpers::smallitoa(s, sensor.gpio())); JsonObject dataSensor = doc.createNestedObject(Helpers::smallitoa(s, sensor.gpio()));
dataSensor["name"] = sensor.name(); dataSensor["name"] = sensor.name();
@@ -395,51 +396,54 @@ void AnalogSensor::publish_values(const bool force) {
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0 dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
break; break;
} }
// create HA config
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
LOG_DEBUG(F("Recreating HA config for analog sensor GPIO %d"), sensor.gpio());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
char str[50];
snprintf(str, sizeof(str), "{{value_json['%d'].value}}", sensor.gpio());
config["val_tpl"] = str;
snprintf(str, sizeof(str), "analog_sensor_%s", sensor.name().c_str());
config["object_id"] = str;
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
config["name"] = str;
snprintf(str, sizeof(str), "analogsensor_%d", sensor.gpio());
config["uniq_id"] = str;
if (sensor.uom() != DeviceValueUOM::NONE) {
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
}
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::base().c_str(), sensor.gpio());
Mqtt::publish_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
}
} else { } else {
// not nested // not nested
doc[sensor.name()] = sensor.value(); doc[sensor.name()] = sensor.value();
} }
// create HA config
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
LOG_DEBUG(F("Recreating HA config for analog sensor GPIO %d"), sensor.gpio());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
char str[50];
if (Mqtt::is_nested()) {
snprintf(str, sizeof(str), "{{value_json['%d'].value}}", sensor.gpio());
} else {
snprintf(str, sizeof(str), "{{value_json['%s']}", sensor.name().c_str());
}
config["val_tpl"] = str;
// snprintf(str, sizeof(str), "%s_analog_sensor_%s", Mqtt::basename().c_str(), sensor.name().c_str());
snprintf(str, sizeof(str), "analog_sensor_%s", sensor.name().c_str());
config["object_id"] = str;
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
config["name"] = str;
snprintf(str, sizeof(str), "analogsensor_%d", sensor.gpio());
config["uniq_id"] = str;
if (sensor.uom() != DeviceValueUOM::NONE) {
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
}
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::basename().c_str(), sensor.gpio());
Mqtt::publish_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
}
} }
} }

View File

@@ -447,7 +447,7 @@ void DallasSensor::remove_ha_topic(const std::string & id) {
std::string sensorid = id; std::string sensorid = id;
std::replace(sensorid.begin(), sensorid.end(), '-', '_'); std::replace(sensorid.begin(), sensorid.end(), '-', '_');
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), sensorid.c_str()); snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic); Mqtt::publish_ha(topic);
} }
@@ -469,7 +469,7 @@ void DallasSensor::publish_values(const bool force) {
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
bool has_value = Helpers::hasValue(sensor.temperature_c); bool has_value = Helpers::hasValue(sensor.temperature_c);
if (Mqtt::is_nested() || Mqtt::ha_enabled()) { if (Mqtt::is_nested()) {
JsonObject dataSensor = doc.createNestedObject(sensor.id()); JsonObject dataSensor = doc.createNestedObject(sensor.id());
dataSensor["name"] = sensor.name(); dataSensor["name"] = sensor.name();
if (has_value) { if (has_value) {
@@ -495,9 +495,14 @@ void DallasSensor::publish_values(const bool force) {
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
char str[50]; char str[50];
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id().c_str()); if (Mqtt::is_nested()) {
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id().c_str());
} else {
snprintf(str, sizeof(str), "{{value_json['%s']}}", sensor.name().c_str());
}
config["val_tpl"] = str; config["val_tpl"] = str;
// snprintf(str, sizeof(str), "%s_temperature_sensor_%s", Mqtt::basename().c_str(), sensor.name().c_str());
snprintf(str, sizeof(str), "temperature_sensor_%s", sensor.name().c_str()); snprintf(str, sizeof(str), "temperature_sensor_%s", sensor.name().c_str());
config["object_id"] = str; config["object_id"] = str;
@@ -516,7 +521,7 @@ void DallasSensor::publish_values(const bool force) {
std::string sensorid = sensor.id(); std::string sensorid = sensor.id();
std::replace(sensorid.begin(), sensorid.end(), '-', '_'); std::replace(sensorid.begin(), sensorid.end(), '-', '_');
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), sensorid.c_str()); snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic, config.as<JsonObject>()); Mqtt::publish_ha(topic, config.as<JsonObject>());

View File

@@ -35,6 +35,7 @@
{131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{132, DeviceType::BOILER, F("GC7000F"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {132, DeviceType::BOILER, F("GC7000F"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{154, DeviceType::BOILER, F("Greenstar 30Ri Compact"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID}, {168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},

View File

@@ -26,8 +26,8 @@ namespace emsesp {
AsyncMqttClient * Mqtt::mqttClient_; AsyncMqttClient * Mqtt::mqttClient_;
// static parameters we make global // static parameters we make global
std::string Mqtt::system_hostname_; // copy from System::hostname()
std::string Mqtt::mqtt_base_; std::string Mqtt::mqtt_base_;
std::string Mqtt::mqtt_basename_;
uint8_t Mqtt::mqtt_qos_; uint8_t Mqtt::mqtt_qos_;
bool Mqtt::mqtt_retain_; bool Mqtt::mqtt_retain_;
uint32_t Mqtt::publish_time_boiler_; uint32_t Mqtt::publish_time_boiler_;
@@ -213,7 +213,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
// prefix base, only if it's not a discovery topic // prefix base, only if it's not a discovery topic
if (content->topic.compare(0, discovery_prefix().size(), discovery_prefix()) == 0) { if (content->topic.compare(0, discovery_prefix().size(), discovery_prefix()) == 0) {
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::base().c_str(), content->topic.c_str()); snprintf(topic, sizeof(topic), "%s/%s", mqtt_base_.c_str(), content->topic.c_str());
} else { } else {
snprintf(topic, sizeof(topic), "%s", content->topic.c_str()); snprintf(topic, sizeof(topic), "%s", content->topic.c_str());
} }
@@ -422,8 +422,9 @@ void Mqtt::load_settings() {
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000; publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
}); });
// get a local copy of the system hostname // create basename from base
system_hostname_ = EMSESP::system_.hostname(); mqtt_basename_ = mqtt_base_;
std::replace(mqtt_basename_.begin(), mqtt_basename_.end(), '/', '_');
} }
void Mqtt::start() { void Mqtt::start() {
@@ -580,13 +581,7 @@ void Mqtt::on_connect() {
publish(F_(info), doc.as<JsonObject>()); // topic called "info" publish(F_(info), doc.as<JsonObject>()); // topic called "info"
if (ha_enabled_) { if (ha_enabled_) {
LOG_INFO(F("start removing topics %s/+/%s/#"), discovery_prefix_.c_str(), system_hostname_.c_str()); queue_unsubscribe_message(discovery_prefix_ + "/+/" + mqtt_basename_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/climate/" + system_hostname_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/sensor/" + system_hostname_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/binary_sensor/" + system_hostname_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/number/" + system_hostname_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/select/" + system_hostname_ + "/#");
queue_unsubscribe_message(discovery_prefix_ + "/switch/" + system_hostname_ + "/#");
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any 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 ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
ha_climate_reset(true); ha_climate_reset(true);
@@ -594,13 +589,8 @@ void Mqtt::on_connect() {
// with disabled HA we subscribe and the broker sends all stored HA-emsesp-configs. // with disabled HA we subscribe and the broker sends all stored HA-emsesp-configs.
// In line 272 they are removed. If HA is enabled the subscriptions are removed. // In line 272 they are removed. If HA is enabled the subscriptions are removed.
// As described in the doc (https://emsesp.github.io/docs/#/Troubleshooting?id=home-assistant): // As described in the doc (https://emsesp.github.io/docs/#/Troubleshooting?id=home-assistant):
// disable HA, wait 5 minutes (to allow the broker to send all), than reenable HA again. // disable HA, wait 5 minutes (to allow the broker to send all), than reenable HA again.
queue_subscribe_message(discovery_prefix_ + "/climate/" + system_hostname_ + "/#"); queue_subscribe_message(discovery_prefix_ + "/+/" + mqtt_basename_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/sensor/" + system_hostname_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/binary_sensor/" + system_hostname_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/number/" + system_hostname_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/select/" + system_hostname_ + "/#");
queue_subscribe_message(discovery_prefix_ + "/switch/" + system_hostname_ + "/#");
} }
// send initial MQTT messages for some of our services // send initial MQTT messages for some of our services
@@ -649,7 +639,7 @@ void Mqtt::ha_status() {
ids.add("ems-esp"); ids.add("ems-esp");
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/system/config", system_hostname_.c_str()); snprintf(topic, sizeof(topic), "sensor/%s/system/config", mqtt_basename_.c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// create the sensors - must match the MQTT payload keys // create the sensors - must match the MQTT payload keys
@@ -938,6 +928,7 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
publish_ha_sensor_config(dv.type, publish_ha_sensor_config(dv.type,
dv.tag, dv.tag,
dv.get_fullname(), dv.get_fullname(),
dv.fullname[0],
dv.device_type, dv.device_type,
dv.short_name, dv.short_name,
dv.uom, dv.uom,
@@ -960,15 +951,16 @@ void Mqtt::publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelp
auto fullname = read_flash_string(name); auto fullname = read_flash_string(name);
publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, fullname, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json); publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, fullname, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json);
} }
// MQTT discovery configs // MQTT discovery configs
// entity must match the key/value pair in the *_data topic // entity must match the key/value pair in the *_data topic
// note: some extra string copying done here, it looks messy but does help with heap fragmentation issues // note: some extra string copying done here, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG uint8_t tag, // EMSdevice::DeviceValueTAG
const std::string & fullname, // fullname, already translated const std::string & fullname, // fullname, already translated
const __FlashStringHelper * const en_name,
const uint8_t device_type, // EMSdevice::DeviceType const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * const entity, // same as shortname const __FlashStringHelper * const entity, // same as shortname
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE) const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
@@ -980,7 +972,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
const int16_t dv_set_max, const int16_t dv_set_max,
const JsonObject & dev_json) { const JsonObject & dev_json) {
// ignore if name (fullname) is empty // ignore if name (fullname) is empty
if (fullname.empty()) { if (fullname.empty() || en_name == nullptr) {
return; return;
} }
@@ -1017,28 +1009,28 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
case DeviceValueType::ULONG: case DeviceValueType::ULONG:
// number - https://www.home-assistant.io/integrations/number.mqtt // number - https://www.home-assistant.io/integrations/number.mqtt
// https://developers.home-assistant.io/docs/core/entity/number // https://developers.home-assistant.io/docs/core/entity/number
snprintf(topic, sizeof(topic), "number/%s/%s/config", system_hostname_.c_str(), uniq); snprintf(topic, sizeof(topic), "number/%s/%s/config", mqtt_basename_.c_str(), uniq);
break; break;
case DeviceValueType::BOOL: case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt // switch - https://www.home-assistant.io/integrations/switch.mqtt
snprintf(topic, sizeof(topic), "switch/%s/%s/config", system_hostname_.c_str(), uniq); snprintf(topic, sizeof(topic), "switch/%s/%s/config", mqtt_basename_.c_str(), uniq);
break; break;
case DeviceValueType::ENUM: case DeviceValueType::ENUM:
// select - https://www.home-assistant.io/integrations/select.mqtt // select - https://www.home-assistant.io/integrations/select.mqtt
snprintf(topic, sizeof(topic), "select/%s/%s/config", system_hostname_.c_str(), uniq); snprintf(topic, sizeof(topic), "select/%s/%s/config", mqtt_basename_.c_str(), uniq);
break; break;
default: default:
// plain old sensor // plain old sensor
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", system_hostname_.c_str(), uniq); snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_basename_.c_str(), uniq);
break; break;
} }
} else { } else {
// plain old read only device entity // plain old read only device entity
if (type == DeviceValueType::BOOL) { if (type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", system_hostname_.c_str(), uniq); // binary sensor snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", mqtt_basename_.c_str(), uniq); // binary sensor
} else { } else {
use_ha_sensor = true; use_ha_sensor = true;
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", system_hostname_.c_str(), uniq); // normal HA sensor, not a boolean one snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_basename_.c_str(), uniq); // normal HA sensor, not a boolean one
} }
} }
@@ -1114,21 +1106,29 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
doc["stat_t"] = stat_t; doc["stat_t"] = stat_t;
// friendly name = <tag> <name> // friendly name = <tag> <name>
char ha_name[70]; char ha_name[70];
char * F_name = strdup(fullname.c_str());
F_name[0] = toupper(F_name[0]); // capitalize first letter
if (have_tag) { if (have_tag) {
snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), fullname.c_str()); snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), F_name);
} else { } else {
snprintf(ha_name, sizeof(ha_name), "%s", fullname.c_str()); snprintf(ha_name, sizeof(ha_name), "%s", F_name);
} }
ha_name[0] = toupper(ha_name[0]); // capitalize first letter free(F_name);
doc["name"] = ha_name; doc["name"] = ha_name;
// entity id is generated from the name, see https://www.home-assistant.io/docs/mqtt/discovery/#use-object_id-to-influence-the-entity-id // entity id is generated from the name, see https://www.home-assistant.io/docs/mqtt/discovery/#use-object_id-to-influence-the-entity-id
// so we override it to make it unique using entity_id // so we override it to make it unique using entity_id
// See https://github.com/emsesp/EMS-ESP32/issues/596 // See https://github.com/emsesp/EMS-ESP32/issues/596
// "<hostname>_<device>_<tag> <name>" // keep it compatible to v3.4, use english fullname, no prefix (basename prefix commmented out)
char object_id[130]; char object_id[130];
snprintf(object_id, sizeof(object_id), "%s_%s_%s", system_hostname_.c_str(), device_name, ha_name); if (have_tag) {
// snprintf(object_id, sizeof(object_id), "%s_%s_%s_%s", mqtt_basename_, device_name, EMSdevice::tag_to_string(tag).c_str(), read_flash_string(en_name).c_str());
snprintf(object_id, sizeof(object_id), "%s_%s_%s", device_name, EMSdevice::tag_to_string(tag).c_str(), read_flash_string(en_name).c_str());
} else {
// snprintf(object_id, sizeof(object_id), "%s_%s_%s", mqtt_basename_, device_name, read_flash_string(en_name).c_str());
snprintf(object_id, sizeof(object_id), "%s_%s", device_name, read_flash_string(en_name).c_str());
}
doc["object_id"] = object_id; doc["object_id"] = object_id;
// value template // value template
@@ -1265,7 +1265,7 @@ void Mqtt::publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove
char min_s[10]; char min_s[10];
char max_s[10]; char max_s[10];
snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", Mqtt::base().c_str(), hc_num); snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", mqtt_basename_.c_str(), hc_num);
if (remove) { if (remove) {
publish_ha(topic); // publish empty payload with retain flag publish_ha(topic); // publish empty payload with retain flag
return; return;
@@ -1305,7 +1305,7 @@ void Mqtt::publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["~"] = base(); doc["~"] = mqtt_base_;
doc["object_id"] = id_s; doc["object_id"] = id_s;
doc["name"] = name_s; doc["name"] = name_s;
doc["uniq_id"] = uniq_id_s; doc["uniq_id"] = uniq_id_s;

View File

@@ -95,6 +95,7 @@ class Mqtt {
static void publish_ha_sensor_config(uint8_t type, static void publish_ha_sensor_config(uint8_t type,
uint8_t tag, uint8_t tag,
const std::string & fullname, const std::string & fullname,
const __FlashStringHelper * const en_name,
const uint8_t device_type, const uint8_t device_type,
const __FlashStringHelper * const entity, const __FlashStringHelper * const entity,
const uint8_t uom, const uint8_t uom,
@@ -142,6 +143,10 @@ class Mqtt {
return mqtt_base_; return mqtt_base_;
} }
static std::string basename() {
return mqtt_basename_;
}
// returns the discovery MQTT topic prefix and adds a / // returns the discovery MQTT topic prefix and adds a /
static std::string discovery_prefix() { static std::string discovery_prefix() {
if (discovery_prefix_.empty()) { if (discovery_prefix_.empty()) {
@@ -286,9 +291,8 @@ class Mqtt {
static bool ha_climate_reset_; static bool ha_climate_reset_;
// settings, copied over // settings, copied over
static std::string system_hostname_;
static std::string mqtt_base_; static std::string mqtt_base_;
static std::string mqtt_basename_;
static uint8_t mqtt_qos_; static uint8_t mqtt_qos_;
static bool mqtt_retain_; static bool mqtt_retain_;
static uint32_t publish_time_; static uint32_t publish_time_;

View File

@@ -383,7 +383,9 @@ void System::start() {
if (low_clock_) { if (low_clock_) {
setCpuFrequencyMhz(160); setCpuFrequencyMhz(160);
} }
fstotal_ = LittleFS.totalBytes(); // read only once, it takes 500 ms to read fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read
appused_ = ESP.getSketchSize() / 1024;
appfree_ = ESP.getFreeSketchSpace() / 1024 - appused_;
#endif #endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
@@ -566,7 +568,7 @@ bool System::heartbeat_json(JsonObject & output) {
} }
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
output["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes output["freemem"] = ESP.getFreeHeap() / 1024; // kilobytes
#endif #endif
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -794,8 +796,10 @@ void System::show_system(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
shell.printfln(F(" SDK version: %s"), ESP.getSdkVersion()); shell.printfln(F(" SDK version: %s"), ESP.getSdkVersion());
shell.printfln(F(" CPU frequency: %lu MHz"), ESP.getCpuFreqMHz()); shell.printfln(F(" CPU frequency: %lu MHz"), ESP.getCpuFreqMHz());
shell.printfln(F(" Free heap: %lu bytes"), (uint32_t)ESP.getFreeHeap()); shell.printfln(F(" Free heap: %lu KB"), (uint32_t)ESP.getFreeHeap() / 1024);
shell.printfln(F(" FS used/total: %lu/%lu (bytes)"), LittleFS.usedBytes(), FStotal()); shell.printfln(F(" App used/free: %lu KB / %lu KB"), appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024;
shell.printfln(F(" FS used/free: %lu KB / %lu KB"), FSused, FStotal() - FSused);
shell.println(); shell.println();
shell.println("Network:"); shell.println("Network:");
@@ -975,7 +979,8 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
// node["uptime (seconds)"] = uuid::get_uptime_sec(); // node["uptime (seconds)"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes node["freemem"] = ESP.getFreeHeap() / 1024; // kilobytes
node["free_app"] = EMSESP::system_.appFree(); // kilobytes
#endif #endif
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1); node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);

View File

@@ -223,6 +223,12 @@ class System {
uint32_t FStotal() { uint32_t FStotal() {
return fstotal_; return fstotal_;
} }
uint32_t appFree() {
return appfree_;
}
uint32_t appUsed() {
return appused_;
}
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
@@ -299,6 +305,8 @@ class System {
uint8_t eth_clock_mode_; uint8_t eth_clock_mode_;
uint32_t fstotal_; uint32_t fstotal_;
uint32_t appused_;
uint32_t appfree_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -284,7 +284,7 @@ void WebSettingsService::onUpdate() {
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) {
emsesp::EMSESP::mqtt_.reset_mqtt(); // reload MQTT, init HA etc emsesp::Mqtt::reset_mqtt(); // reload MQTT, init HA etc
} }
WebSettings::reset_flags(); WebSettings::reset_flags();