ESP32_ChinaDieselHeater_Con.../src/Utility/BTC_JSON.cpp
Ray Jones 2e25ad3da4 Fixed role ordering in NV for DS18B20 probe offsets
Replaced ` with clean degree symbol in standard font & arial 8
Removed temp offset from fuel cal screen
GUI alignment tweaks to avoid cutoffs
Better handling of single DS18B20 with BME280
Added bounds limits to Exponential Mean
Sorted inheritance from CSensor properly
2019-10-19 09:58:19 +11:00

574 lines
20 KiB
C++

/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2019 Ray Jones <ray@mrjones.id.au>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "BTC_JSON.h"
#include "DebugPort.h"
#include "NVStorage.h"
#include "../RTC/Clock.h"
#include "../RTC/RTCStore.h"
#include "../RTC/BTCDateTime.h"
#include "../RTC/Timers.h"
#include "../RTC/TimerManager.h"
#include "../Bluetooth/BluetoothAbstract.h"
#include "../WiFi/BTCWebServer.h"
#include "../WiFi/BTCWifi.h"
#include "../WiFi/ABmqtt.h"
#include "../cfg/BTCConfig.h"
#include "macros.h"
#include "../Protocol/Protocol.h"
#include <string.h>
#include "HourMeter.h"
#include "TempSense.h"
extern CModerator MQTTmoderator;
char defaultJSONstr[64];
CModerator JSONmoderator;
CTimerModerator TimerModerator;
int timerConflict = 0;
CModerator MQTTJSONmoderator;
CModerator IPmoderator;
CModerator GPIOmoderator;
CModerator SysModerator;
bool bTriggerSysParams = false;
bool bTriggerDateTime = false;
void Expand(std::string& str);
bool makeJSONString(CModerator& moderator, char* opStr, int len);
bool makeJSONStringEx(CModerator& moderator, char* opStr, int len);
bool makeJSONTimerString(int channel, char* opStr, int len);
bool makeJSONStringGPIO( CModerator& moderator, char* opStr, int len);
bool makeJSONStringSysInfo(CModerator& moderator, char* opStr, int len);
bool makeJSONStringMQTT(CModerator& moderator, char* opStr, int len);
bool makeJSONStringIP(CModerator& moderator, char* opStr, int len);
void DecodeCmd(const char* cmd, String& payload);
void triggerJSONTimeUpdate()
{
bTriggerDateTime = true;
}
void resetJSONTimerModerator(int timerID)
{
if(timerID)
TimerModerator.reset(timerID-1);
else
TimerModerator.reset();
}
void resetJSONIPmoderator()
{
IPmoderator.reset(); // force IP params to be sent
}
void resetJSONmoderator(const char* name)
{
if(name)
JSONmoderator.reset(name);
else
JSONmoderator.reset();
}
void resetJSONSysModerator()
{
SysModerator.reset(); // force MQTT params to be sent
bTriggerSysParams = true;
}
void resetJSONMQTTmoderator()
{
MQTTJSONmoderator.reset(); // force MQTT params to be sent
}
void interpretJsonCommand(char* pLine)
{
if(strlen(pLine) == 0)
return;
DebugPort.printf("JSON parse %s...", pLine);
StaticJsonBuffer<512> jsonBuffer; // create a JSON buffer on the heap
JsonObject& obj = jsonBuffer.parseObject(pLine);
if(!obj.success()) {
DebugPort.println(" FAILED");
return;
}
DebugPort.println(" OK");
JsonObject::iterator it;
for(it = obj.begin(); it != obj.end(); ++it) {
String payload(it->value.as<const char*>());
DecodeCmd(it->key, payload);
}
}
void validateTimer(int ID)
{
ID--; // supplied as +1
if(!INBOUNDS(ID, 0, 13))
return;
timerConflict = CTimerManager::conflictTest(ID); // check targeted timer against other timers
TimerModerator.reset(ID); // ensure we update client with our (real) version of the selected timer
}
bool makeJSONString(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
float tidyTemp = getTemperatureSensor();
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) {
bSend |= moderator.addJson("TempCurrent", tidyTemp, root);
}
if(getTempSensor().getNumSensors() > 1) {
getTempSensor().getTemperature(1, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) {
bSend |= moderator.addJson("Temp2Current", tidyTemp, root);
}
if(getTempSensor().getNumSensors() > 2) {
getTempSensor().getTemperature(2, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) {
bSend |= moderator.addJson("Temp3Current", tidyTemp, root);
}
}
if(getTempSensor().getNumSensors() > 3) {
getTempSensor().getTemperature(3, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) {
bSend |= moderator.addJson("Temp4Current", tidyTemp, root);
}
}
}
bSend |= moderator.addJson("TempDesired", getTemperatureDesired(), root);
bSend |= moderator.addJson("TempMode", NVstore.getUserSettings().degF, root);
if(NVstore.getUserSettings().menuMode < 2) {
bSend |= moderator.addJson("TempMin", getHeaterInfo().getTemperature_Min(), root);
bSend |= moderator.addJson("TempMax", getHeaterInfo().getTemperature_Max(), root);
bSend |= moderator.addJson("TempBody", getHeaterInfo().getTemperature_HeatExchg(), root);
bSend |= moderator.addJson("RunState", getHeaterInfo().getRunStateEx(), root);
bSend |= moderator.addJson("RunString", getHeaterInfo().getRunStateStr(), root); // verbose it up!
bSend |= moderator.addJson("ErrorState", getHeaterInfo().getErrState(), root );
bSend |= moderator.addJson("ErrorString", getHeaterInfo().getErrStateStrEx(), root); // verbose it up!
bSend |= moderator.addJson("Thermostat", getThermostatModeActive(), root );
bSend |= moderator.addJson("PumpFixed", getHeaterInfo().getPump_Fixed(), root );
bSend |= moderator.addJson("PumpMin", getHeaterInfo().getPump_Min(), root );
bSend |= moderator.addJson("PumpMax", getHeaterInfo().getPump_Max(), root );
bSend |= moderator.addJson("PumpActual", getHeaterInfo().getPump_Actual(), root );
bSend |= moderator.addJson("FanMin", getHeaterInfo().getFan_Min(), root );
bSend |= moderator.addJson("FanMax", getHeaterInfo().getFan_Max(), root );
bSend |= moderator.addJson("FanRPM", getFanSpeed(), root );
bSend |= moderator.addJson("FanVoltage", getHeaterInfo().getFan_Voltage(), root );
bSend |= moderator.addJson("FanSensor", getHeaterInfo().getFan_Sensor(), root );
bSend |= moderator.addJson("InputVoltage", getBatteryVoltage(false), root );
bSend |= moderator.addJson("SystemVoltage", getHeaterInfo().getSystemVoltage(), root );
bSend |= moderator.addJson("GlowVoltage", getGlowVolts(), root );
bSend |= moderator.addJson("GlowCurrent", getGlowCurrent(), root );
bSend |= moderator.addJson("BluewireStat", getBlueWireStatStr(), root );
}
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
bool makeJSONStringEx(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
if(NVstore.getUserSettings().menuMode < 2) {
bSend |= moderator.addJson("ThermostatMethod", NVstore.getUserSettings().ThermostatMethod, root);
bSend |= moderator.addJson("ThermostatWindow", NVstore.getUserSettings().ThermostatWindow, root);
int stop = NVstore.getUserSettings().cyclic.Stop;
if(stop) stop++; // deliver effective threshold, not internal working value
bSend |= moderator.addJson("ThermostatOvertemp", stop, root);
bSend |= moderator.addJson("ThermostatUndertemp", NVstore.getUserSettings().cyclic.Start, root);
bSend |= moderator.addJson("CyclicTemp", getDemandDegC(), root); // actual pivot point for cyclic mode
bSend |= moderator.addJson("CyclicOff", stop, root); // threshold of over temp for cyclic mode
bSend |= moderator.addJson("CyclicOn", NVstore.getUserSettings().cyclic.Start, root); // threshold of under temp for cyclic mode
bSend |= moderator.addJson("PumpCount", RTC_Store.getFuelGauge(), root); // running count of pump strokes
bSend |= moderator.addJson("PumpCal", NVstore.getHeaterTuning().pumpCal, root); // mL/stroke
bSend |= moderator.addJson("LowVoltCutout", NVstore.getHeaterTuning().getLVC(), root); // low voltage cutout
}
bSend |= moderator.addJson("TempOffset", getTempSensor().getOffset(0), root); // degC offset
bSend |= moderator.addJson("TempType", getTempSensor().getID(0), root); // BME280 vs DS18B20
if(getTempSensor().getNumSensors() > 1) {
bSend |= moderator.addJson("Temp2Offset", getTempSensor().getOffset(1), root); // degC offset
bSend |= moderator.addJson("Temp2Type", getTempSensor().getID(1), root); // BME280 vs DS18B20
}
if(getTempSensor().getNumSensors() > 2) {
bSend |= moderator.addJson("Temp3Offset", getTempSensor().getOffset(2), root); // degC offset
bSend |= moderator.addJson("Temp3Type", getTempSensor().getID(2), root); // BME280 vs DS18B20
}
if(getTempSensor().getNumSensors() > 3) {
bSend |= moderator.addJson("Temp4Offset", getTempSensor().getOffset(3), root); // degC offset
bSend |= moderator.addJson("Temp4Type", getTempSensor().getID(3), root); // BME280 vs DS18B20
}
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
// the way the JSON timer strings are crafted, we have to iterate over each timer's parameters
// individually, the JSON name is always the same for each timer, the payload IDs the specific
// timer
// Only timer parameters that have changed will be sent, after reset the typical string will be
// {"TimerStart":XX:XX,"TimerStop":XX:XX,"TimerDays":XX,"TimerRepeat":X}
bool makeJSONTimerString(int channel, char* opStr, int len)
{
bool bSend = false; // reset should send flag
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
sTimer timerInfo;
NVstore.getTimerInfo(channel, timerInfo);
bSend |= TimerModerator.addJson(channel, timerInfo, root );
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
bool makeJSONStringGPIO(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
sGPIO info;
getGPIOinfo(info);
bSend |= moderator.addJson("GPin1", info.inState[0], root);
bSend |= moderator.addJson("GPin2", info.inState[1], root);
bSend |= moderator.addJson("GPout1", info.outState[0], root);
bSend |= moderator.addJson("GPout2", info.outState[1], root);
bSend |= moderator.addJson("GPanlg", info.algVal * 100 / 4096, root);
bSend |= moderator.addJson("GPmodeIn1", GPIOin1Names[info.in1Mode], root);
bSend |= moderator.addJson("GPmodeIn2", GPIOin2Names[info.in2Mode], root);
bSend |= moderator.addJson("GPmodeOut1", GPIOout1Names[info.out1Mode], root);
bSend |= moderator.addJson("GPmodeOut2", GPIOout2Names[info.out2Mode], root);
bSend |= moderator.addJson("GPmodeAnlg", GPIOalgNames[info.algMode], root);
bSend |= moderator.addJson("ExtThermoTmout", (uint32_t)NVstore.getUserSettings().ExtThermoTimeout, root);
const char* stop = getExternalThermostatHoldTime();
if(stop)
bSend |= moderator.addJson("ExtThermoStop", stop, root);
else
bSend |= moderator.addJson("ExtThermoStop", "Off", root);
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
bool makeJSONStringMQTT(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
sMQTTparams info = NVstore.getMQTTinfo();
bSend |= moderator.addJson("MEn", info.enabled, root);
bSend |= moderator.addJson("MOnline", isMQTTconnected(), root);
bSend |= moderator.addJson("MHost", info.host, root);
bSend |= moderator.addJson("MPort", info.port, root);
bSend |= moderator.addJson("MUser", info.username, root);
bSend |= moderator.addJson("MPasswd", info.password, root);
bSend |= moderator.addJson("MQoS", info.qos, root);
bSend |= moderator.addJson("MTopic", info.topicPrefix, root);
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
bool makeJSONStringSysInfo(CModerator& moderator, char* opStr, int len)
{
bool bSend = false; // reset should send flag
if(bTriggerSysParams || bTriggerDateTime) {
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
const BTCDateTime& now = Clock.get();
char str[32];
sprintf(str, "%d/%d/%d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
bSend |= moderator.addJson("DateTime", str, root);
bSend |= moderator.addJson("Time12hr", NVstore.getUserSettings().clock12hr, root);
if(bTriggerSysParams) {
bSend |= moderator.addJson("SysUpTime", sysUptime(), root);
bSend |= moderator.addJson("SysVer", getVersionStr(), root);
bSend |= moderator.addJson("SysDate", getVersionDate(), root);
bSend |= moderator.addJson("SysFreeMem", ESP.getFreeHeap(), root);
if(NVstore.getUserSettings().menuMode < 2) {
bSend |= moderator.addJson("SysRunTime", pHourMeter->getRunTime(), root);
bSend |= moderator.addJson("SysGlowTime", pHourMeter->getGlowTime(), root);
}
}
if(bSend) {
root.printTo(opStr, len);
}
}
bTriggerSysParams = false;
bTriggerDateTime = false;
return bSend;
}
bool makeJSONStringIP(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
bSend |= moderator.addJson("IP_AP", getWifiAPAddrStr(), root);
bSend |= moderator.addJson("IP_APMAC", getWifiAPMACStr(), root);
bSend |= moderator.addJson("IP_STA", getWifiSTAAddrStr(), root);
bSend |= moderator.addJson("IP_STAMAC", getWifiSTAMACStr(), root);
bSend |= moderator.addJson("IP_STASSID", getSSID().c_str(), root);
bSend |= moderator.addJson("IP_OTA", NVstore.getUserSettings().enableOTA, root);
bSend |= moderator.addJson("BT_MAC", getBluetoothClient().getMAC(), root);
if(bSend) {
root.printTo(opStr, len);
}
return bSend;
}
void updateJSONclients(bool report)
{
// update general parameters
char jsonStr[800];
{
if(makeJSONString(JSONmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
// update extended params
{
if(makeJSONStringEx(JSONmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
// update timer parameters
bool bNewTimerInfo = false;
for(int tmr=0; tmr<14; tmr++)
{
if(makeJSONTimerString(tmr, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
bNewTimerInfo = true;
}
}
// request timer refesh upon clients
if(bNewTimerInfo) {
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
if(timerConflict) {
root.set("TimerConflict", timerConflict);
timerConflict = 0;
}
root.set("TimerRefresh", 1);
root.printTo(jsonStr, 800);
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
// report MQTT params
{
if(makeJSONStringMQTT(MQTTJSONmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
// report IP params
{
if(makeJSONStringIP(IPmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
// report System info
{
if(makeJSONStringSysInfo(SysModerator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
{
if(makeJSONStringGPIO(GPIOmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
}
}
}
void resetAllJSONmoderators()
{
JSONmoderator.reset();
#ifdef SALWAYS_SEND_TIMERS
TimerModerator.reset();
#else
initJSONTimermoderator();
#endif
initJSONMQTTmoderator();
initJSONIPmoderator();
initJSONSysModerator();
GPIOmoderator.reset();
}
void initJSONMQTTmoderator()
{
char jsonStr[800];
makeJSONStringMQTT(MQTTJSONmoderator, jsonStr, sizeof(jsonStr));
}
void initJSONIPmoderator()
{
char jsonStr[800];
makeJSONStringIP(IPmoderator, jsonStr, sizeof(jsonStr));
}
void initJSONSysModerator()
{
char jsonStr[800];
makeJSONStringSysInfo(SysModerator, jsonStr, sizeof(jsonStr));
}
void initJSONTimermoderator()
{
char jsonStr[800];
for(int tmr=0; tmr<14; tmr++)
makeJSONTimerString(tmr, jsonStr, sizeof(jsonStr));
}
void Expand(std::string& str)
{
const sUserSettings& userOptions = NVstore.getUserSettings();
if(userOptions.JSON.singleElement) {
size_t pos = str.find(",\"");
while(pos != std::string::npos) {
if(userOptions.JSON.LF)
str.replace(pos, 2, "}\n{\""); // converts {"name":value,"name2":value"} to {"name":value}\n{"name2":value}
else
str.replace(pos, 2, "}{\""); // converts {"name":value,"name2":value"} to {"name":value}{"name2":value}
pos = str.find(",\"");
}
if(userOptions.JSON.padding) { // converts {"name":value} to {"name": value}
pos = str.find("\":");
while(pos != std::string::npos) {
str.replace(pos, 2, "\": ");
pos = str.find("\":", pos+1);
}
}
if(userOptions.JSON.LF)
str.append("\n");
}
}