improvements to rendering floats

This commit is contained in:
proddy
2019-03-24 11:54:49 +01:00
parent 599171202c
commit c7c07eb1c4
6 changed files with 177 additions and 150 deletions

View File

@@ -30,7 +30,6 @@ There are 3 parts to this project, first the design of the circuit, secondly the
- [Home Assistant Configuration](#home-assistant-configuration)
- [Building The Firmware](#building-the-firmware)
- [Using PlatformIO Standalone](#using-platformio-standalone)
- [Building Using Arduino IDE](#building-using-arduino-ide)
- [Using the Pre-built Firmware](#using-the-pre-built-firmware)
- [Troubleshooting](#troubleshooting)
- [Known Issues](#known-issues)
@@ -83,7 +82,7 @@ The code and circuit has been tested with a few ESP8266 development boards such
13. Connect the EMS lines to the ESP. This can be done via the two EMS wires or via the 3.5mm service jack if you have an bbqkees board.
14. Reboot the ESP, either by the reset switch or pulling the power.
15. The ESP will first perform an autodetect to try and discover the EMS devices attached. If your boiler and thermostat are recognized it will set these types and store them for ever and ever. You can trace the output by telnet'ing to the board `telnet ems-esp.local`. Also use `info` to check the status.
16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and product ID. These will be added to the file `ems_devices.h` in a future release.
16. If your boiler/thermostat is not discovered create a GitHub issue stating the type and Product ID. These will be added to the file `ems_devices.h` in a future release.
17. If all is well and there is traffic on the EMS bus the onboard LED will stop blinking and be permanently on. If this is annoying you can disable with `set led off`. To see the EMS messages type `set log v` for verbose logging.
18. And all is not well, check the wiring, make sure serial is off and look at the telnet session for errors. If in doubt, wipe the ESP with `pio run -t erase` and start again with step #3

View File

@@ -9,7 +9,7 @@
#ifndef MyEMS_h
#define MyEMS_h
#define MYESP_VERSION "1.1.6b4"
#define MYESP_VERSION "1.1.6"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>

View File

@@ -182,6 +182,7 @@ int16_t DS18::getRawValue(unsigned char index) {
}
// return real value as a double
// The raw temperature data is in units of sixteenths of a degree, so the value must be divided by 16 in order to convert it to degrees.
double DS18::getValue(unsigned char index) {
double value = (float)getRawValue(index) / 16.0;
return value;

View File

@@ -161,56 +161,42 @@ char * _bool_to_char(char * s, uint8_t value) {
// convert short (two bytes) to text value
// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c)
char * _short_to_char(char * s, int16_t value, uint8_t div = 10) {
char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1) {
// remove errors on invalid values
if (abs(value) >= EMS_VALUE_SHORT_NOTSET) {
strlcpy(s, "?", sizeof(s));
return (s);
}
if (div != 0) {
if (decimals == 0) {
itoa(value, s, 10);
return (s);
}
// floating point
char s2[5] = {0};
// check for negative values
if (value < 0) {
strlcpy(s, "-", 2);
strlcat(s, itoa(abs(value) / div, s2, 10), 5);
} else {
strlcpy(s, itoa(value / div, s2, 10), 5);
value = abs(value);
}
strlcpy(s, itoa(value / (decimals * 10), s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, itoa(abs(value) % div, s2, 10), 5);
} else {
itoa(value, s, 10);
}
strlcat(s, itoa(value % (decimals * 10), s2, 10), 5);
return s;
}
// convert int (single byte) to text value
char * _int_to_char(char * s, uint8_t value, uint8_t div = 0) {
if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(s, "?", sizeof(s));
} else {
if (div != 0) {
char s2[5] = {0};
strlcpy(s, itoa(value / div, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, itoa(value % div, s2, 10), 5);
} else {
itoa(value, s, 10);
}
}
return s;
}
// takes an int value (1 byte), converts to a fraction
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 0) {
// takes a short value (2 bytes), converts to a fraction
// most values stored a s short are either *10 or *100
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1) {
char buffer[200] = {0};
char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer));
strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer));
if (postfix != NULL) {
strlcat(buffer, " ", sizeof(buffer));
@@ -220,15 +206,49 @@ void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, u
myDebug(buffer);
}
// takes a short value (2 bytes), converts to a fraction
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t div = 10) {
// convert int (single byte) to text value
char * _int_to_char(char * s, uint8_t value, uint8_t div = 1) {
if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(s, "?", sizeof(s));
return (s);
}
char s2[5] = {0};
switch (div) {
case 1:
itoa(value, s, 10);
break;
case 2:
strlcpy(s, itoa(value >> 1, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, ((value & 0x01) ? "5" : "0"), 5);
break;
case 10:
strlcpy(s, itoa(value / 10, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, itoa(value % 10, s2, 10), 5);
break;
default:
itoa(value, s, 10);
break;
}
return s;
}
// takes an int value (1 byte), converts to a fraction
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1) {
char buffer[200] = {0};
char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _short_to_char(s, value, div), sizeof(buffer));
strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer));
if (postfix != NULL) {
strlcat(buffer, " ", sizeof(buffer));
@@ -422,11 +442,16 @@ void showInfo() {
if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) {
// for easy temps are * 100
// also we don't have the time or mode
_renderShortValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 100);
_renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 100);
} else {
_renderShortValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2);
_renderShortValue("Set room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 10);
_renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10);
} else {
// because we store in 2 bytes short, when converting to a single byte we'll loose the negative value if its unset
if ((EMS_Thermostat.setpoint_roomTemp <= 0) || (EMS_Thermostat.curr_roomTemp <= 0)) {
EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_INT_NOTSET;
EMS_Thermostat.curr_roomTemp = EMS_VALUE_INT_NOTSET;
}
_renderIntValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2); // convert to a single byte * 2
_renderIntValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); // is *10
myDebug(" Thermostat time is %02d:%02d:%02d %d/%d/%d",
EMS_Thermostat.hour,
@@ -451,12 +476,12 @@ void showInfo() {
// Dallas
if (EMSESP_Status.dallas_sensors != 0) {
char s[80] = {0};
//char s[80] = {0};
char buffer[128] = {0};
char valuestr[8] = {0}; // for formatting temp
myDebug("%sExternal temperature sensors:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF);
for (uint8_t i = 0; i < EMSESP_Status.dallas_sensors; i++) {
snprintf(s, sizeof(s), "Sensor #%d %s", i + 1, ds18.getDeviceString(buffer, i));
_renderShortValue(s, "C", ds18.getRawValue(i), 16); // divide by 16
myDebug(" Sensor #%d %s: %s C", i + 1, ds18.getDeviceString(buffer, i), _float_to_char(valuestr, ds18.getValue(i) ));
}
myDebug(""); // newline
}
@@ -525,8 +550,7 @@ void publishValues(bool force) {
}
rootBoiler["wWCurTmp"] = _short_to_char(s, EMS_Boiler.wWCurTmp);
snprintf(s, sizeof(s), "%i.%i", EMS_Boiler.wWCurFlow / 10, EMS_Boiler.wWCurFlow % 10);
rootBoiler["wWCurFlow"] = s;
rootBoiler["wWCurFlow"] = _int_to_char(s, EMS_Boiler.wWCurFlow, 10);
rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
rootBoiler["curFlowTemp"] = _short_to_char(s, EMS_Boiler.curFlowTemp);
rootBoiler["retTemp"] = _short_to_char(s, EMS_Boiler.retTemp);
@@ -571,7 +595,7 @@ void publishValues(bool force) {
// handle the thermostat values separately
if (ems_getThermostatEnabled()) {
// only send thermostat values if we actually have them
if ((EMS_Thermostat.curr_roomTemp == 0) || (EMS_Thermostat.setpoint_roomTemp == 0))
if ((EMS_Thermostat.curr_roomTemp <= 0) || (EMS_Thermostat.setpoint_roomTemp <= 0))
return;
// build new json object
@@ -579,11 +603,11 @@ void publishValues(bool force) {
JsonObject rootThermostat = doc.to<JsonObject>();
if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) {
rootThermostat[THERMOSTAT_CURRTEMP] = _short_to_char(s, EMS_Thermostat.curr_roomTemp, 100);
rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 100);
} else {
rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 10);
rootThermostat[THERMOSTAT_CURRTEMP] = _short_to_char(s, EMS_Thermostat.curr_roomTemp, 10);
rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 2);
} else {
rootThermostat[THERMOSTAT_SELTEMP] = _int_to_char(s, EMS_Thermostat.setpoint_roomTemp, 2);
rootThermostat[THERMOSTAT_CURRTEMP] = _int_to_char(s, EMS_Thermostat.curr_roomTemp, 10);
}
// RC20 has different mode settings
@@ -701,6 +725,61 @@ char * _readWord() {
return word;
}
// publish external dallas sensor temperature values to MQTT
void do_publishSensorValues() {
if (EMSESP_Status.dallas_sensors != 0) {
publishSensorValues();
}
}
// call PublishValues without forcing, so using CRC to see if we really need to publish
void do_publishValues() {
// don't publish if we're not connected to the EMS bus
if ((ems_getBusConnected()) && (!myESP.getUseSerial()) && myESP.isMQTTConnected()) {
publishValues(false);
}
}
// callback to light up the LED, called via Ticker every second
// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off
void do_ledcheck() {
if (EMSESP_Status.led) {
if (ems_getBusConnected()) {
digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off
} else {
int state = digitalRead(EMSESP_Status.led_gpio);
digitalWrite(EMSESP_Status.led_gpio, !state);
}
}
}
// Thermostat scan
void do_scanThermostat() {
if ((ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebug("> Scanning thermostat message type #0x%02X...", scanThermostat_count);
ems_doReadCommand(scanThermostat_count, EMS_Thermostat.type_id);
scanThermostat_count++;
}
}
// do a system health check every now and then to see if we all connections
void do_systemCheck() {
if ((!ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebug("Error! Unable to read from EMS bus. Retrying in %d seconds...", SYSTEMCHECK_TIME);
}
}
// force calls to get data from EMS for the types that aren't sent as broadcasts
// only if we have a EMS connection
void do_regularUpdates() {
if ((ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebugLog("Calling scheduled data refresh from EMS devices...");
ems_getThermostatValues();
ems_getBoilerValues();
ems_getOtherValues();
}
}
// initiate a force scan by sending type read requests from 0 to FF to the thermostat
// used to analyze responses for debugging
void startThermostatScan(uint8_t start) {
@@ -713,6 +792,27 @@ void startThermostatScan(uint8_t start) {
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
}
// turn back on the hot water for the shower
void _showerColdShotStop() {
if (EMSESP_Shower.doingColdShot) {
myDebugLog("[Shower] finished shot of cold. hot water back on");
ems_setWarmTapWaterActivated(true);
EMSESP_Shower.doingColdShot = false;
showerColdShotStopTimer.detach(); // disable the timer
}
}
// turn off hot water to send a shot of cold
void _showerColdShotStart() {
if (EMSESP_Status.shower_alert) {
myDebugLog("[Shower] doing a shot of cold water");
ems_setWarmTapWaterActivated(false);
EMSESP_Shower.doingColdShot = true;
// start the timer for n seconds which will reset the water back to hot
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
}
}
// callback for loading/saving settings to the file system (SPIFFS)
bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
bool recreate_config = true;
@@ -1235,82 +1335,6 @@ void initEMSESP() {
EMSESP_Shower.doingColdShot = false;
}
// publish external dallas sensor temperature values to MQTT
void do_publishSensorValues() {
if (EMSESP_Status.dallas_sensors != 0) {
publishSensorValues();
}
}
// call PublishValues without forcing, so using CRC to see if we really need to publish
void do_publishValues() {
// don't publish if we're not connected to the EMS bus
if ((ems_getBusConnected()) && (!myESP.getUseSerial()) && myESP.isMQTTConnected()) {
publishValues(false);
}
}
// callback to light up the LED, called via Ticker every second
// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off
void do_ledcheck() {
if (EMSESP_Status.led) {
if (ems_getBusConnected()) {
digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off
} else {
int state = digitalRead(EMSESP_Status.led_gpio);
digitalWrite(EMSESP_Status.led_gpio, !state);
}
}
}
// Thermostat scan
void do_scanThermostat() {
if ((ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebug("> Scanning thermostat message type #0x%02X...", scanThermostat_count);
ems_doReadCommand(scanThermostat_count, EMS_Thermostat.type_id);
scanThermostat_count++;
}
}
// do a system health check every now and then to see if we all connections
void do_systemCheck() {
if ((!ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebug("Error! Unable to read from EMS bus. Retrying in %d seconds...", SYSTEMCHECK_TIME);
}
}
// force calls to get data from EMS for the types that aren't sent as broadcasts
// only if we have a EMS connection
void do_regularUpdates() {
if ((ems_getBusConnected()) && (!myESP.getUseSerial())) {
myDebugLog("Calling scheduled data refresh from EMS devices...");
ems_getThermostatValues();
ems_getBoilerValues();
ems_getOtherValues();
}
}
// turn off hot water to send a shot of cold
void _showerColdShotStart() {
if (EMSESP_Status.shower_alert) {
myDebugLog("[Shower] doing a shot of cold water");
ems_setWarmTapWaterActivated(false);
EMSESP_Shower.doingColdShot = true;
// start the timer for n seconds which will reset the water back to hot
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
}
}
// turn back on the hot water for the shower
void _showerColdShotStop() {
if (EMSESP_Shower.doingColdShot) {
myDebugLog("[Shower] finished shot of cold. hot water back on");
ems_setWarmTapWaterActivated(true);
EMSESP_Shower.doingColdShot = false;
showerColdShotStopTimer.detach(); // disable the timer
}
}
/*
* Shower Logic
*/

View File

@@ -14,11 +14,6 @@
#include <MyESP.h>
#include <list> // std::list
#define _toByte(i) (data[i])
#define _toShort(i) ((data[i] << 8) + data[i + 1])
#define _toLong(i) ((data[i] << 16) + (data[i + 1] << 8) + (data[i + 2]))
#define _bitRead(i, bit) (((data[i]) >> (bit)) & 0x01)
// myESP for logging to telnet and serial
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
@@ -26,7 +21,15 @@ _EMS_Sys_Status EMS_Sys_Status; // EMS Status
CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO queue for Tx send buffer
// callbacks per type
//
// process callbacks per type
//
// macros used in the _process* functions
#define _toByte(i) (data[i])
#define _toShort(i) ((data[i] << 8) + data[i + 1])
#define _toLong(i) ((data[i] << 16) + (data[i + 1] << 8) + (data[i + 2]))
#define _bitRead(i, bit) (((data[i]) >> (bit)) & 0x01)
// generic
void _process_Version(uint8_t src, uint8_t * data, uint8_t length);
@@ -1940,7 +1943,7 @@ void ems_setWarmTapWaterActivated(bool activated) {
}
/*
* Start up sequence for UBA Master
* Start up sequence for UBA Master, hopefully to initialize a handshake
* Still experimental
*/
void ems_startupTelegrams() {

View File

@@ -6,5 +6,5 @@
#pragma once
#define APP_NAME "EMS-ESP"
#define APP_VERSION "1.6.0b5"
#define APP_VERSION "1.6.0b6"
#define APP_HOSTNAME "ems-esp"