2018-12-16 07:38:34 +00:00
/*
* This file is part of the " bluetoothheater " distribution
* ( https : //gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright ( C ) 2018 Ray Jones < ray @ mrjones . id . au >
* Copyright ( C ) 2018 James Clark
*
* 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/>.
*
*/
/*
Chinese Heater Half Duplex Serial Data Sending Tool
Connects to the blue wire of a Chinese heater , which is the half duplex serial link .
Sends and receives data from hardware serial port 1.
Terminology : Tx is to the heater unit , Rx is from the heater unit .
Typical data frame timing on the blue wire is :
__Tx_Rx____________________________Tx_Rx____________________________Tx_Rx___________
This software can connect to the blue wire in a normal OEM system , detecting the
OEM controller and allowing extraction of the data or injecting on / off commands .
If Pin 21 is grounded on the Due , this simple stream will be reported over Serial and
no control from the Arduino will be allowed .
This allows passive sniffing of the blue wire in a normal system .
The binary data is received from the line .
If it has been > 100 ms since the last blue wire activity this indicates a new frame
sequence is starting from the OEM controller .
Synchronise as such then count off the next 24 bytes storing them in the Controller ' s
data array . These bytes are then reported over Serial to the PC in ASCII .
It is then expected the heater will respond with it ' s 24 bytes .
Capture those bytes and store them in the Heater1 data array .
Once again these bytes are then reported over Serial to the PC in ASCII .
If no activity is sensed in a second , it is assumed no OEM controller is attached and we
have full control over the heater .
Either way we can now inject a message onto the blue wire allowing our custom
on / off control .
We must remain synchronous with an OEM controller if it exists otherwise E - 07
faults will be caused .
Typical data frame timing on the blue wire is then :
__OEMTx_HtrRx__OurTx_HtrRx____________OEMTx_HtrRx__OurTx_HtrRx____________OEMTx_HtrRx__OurTx_HtrRx_________
The second HtrRx to the next OEMTx delay is always > 100 ms and is paced by the OEM controller .
The delay before seeing Heater Rx data after any Tx is usually much less than 10 ms .
But this does rise if new max / min or voltage settings are sent .
* * The heater only ever sends Rx data in response to a data frame from a controller * *
For Bluetooth connectivity , a HC - 05 Bluetooth module is attached to Serial2 :
TXD - > Rx2 ( pin 17 )
RXD - > Tx2 ( pin 16 )
EN ( key ) - > pin 15
STATE - > pin 4
This code only works with boards that have more than one hardware serial port like Arduino
Mega , Due , Zero , ESP32 etc .
The circuit :
- a Tx Rx multiplexer is required to combine the Arduino ' s Tx1 And Rx1 pins onto the blue wire .
- a Tx Enable signal from pin 22 controls the multiplexer , high for Tx , low for Rx
- Serial logging software on Serial0 via USB link
created 23 Sep 2018 by Ray Jones
This example code is in the public domain .
*/
2019-08-27 13:09:07 +00:00
# include "WiFi/ABMqtt.h"
2019-07-23 12:32:36 +00:00
# include "cfg/BTCConfig.h"
# include "cfg/pins.h"
# include "RTC/Timers.h"
# include "RTC/Clock.h"
2019-07-24 09:25:07 +00:00
# include "RTC/RTCStore.h"
2019-07-23 12:32:36 +00:00
# include "WiFi/BTCWifi.h"
# include "WiFi/BTCWebServer.h"
# include "WiFi/BTCota.h"
# include "Protocol/Protocol.h"
# include "Protocol/TxManage.h"
# include "Protocol/SmartError.h"
# include "Utility/helpers.h"
# include "Utility/NVStorage.h"
# include "Utility/DebugPort.h"
# include "Utility/macros.h"
# include "Utility/UtilClasses.h"
# include "Utility/BTC_JSON.h"
# include "Utility/BTC_GPIO.h"
# include "Utility/BoardDetect.h"
# include "Utility/FuelGauge.h"
# include "OLED/ScreenManager.h"
# include "OLED/KeyPad.h"
# include "Utility/TempSense.h"
# include "Utility/DataFilter.h"
2019-07-24 09:25:07 +00:00
# include "Utility/HourMeter.h"
2019-06-29 08:08:37 +00:00
# include <rom/rtc.h>
2019-06-25 07:49:07 +00:00
# include <esp_spiffs.h>
2019-01-20 10:14:45 +00:00
# include <SPIFFS.h>
2019-08-01 12:57:18 +00:00
# include <nvs.h>
2018-12-16 07:38:34 +00:00
2019-05-21 12:01:42 +00:00
// SSID & password now stored in NV storage - these are still the default values.
2019-05-16 11:12:29 +00:00
//#define AP_SSID "Afterburner"
//#define AP_PASSWORD "thereisnospoon"
2018-12-16 07:38:34 +00:00
# define RX_DATA_TIMOUT 50
2019-08-31 07:41:01 +00:00
const int FirmwareRevision = 31 ;
2019-09-01 04:43:00 +00:00
const int FirmwareSubRevision = 1 ;
const char * FirmwareDate = " 1 Sep 2019 " ;
2019-04-19 11:38:39 +00:00
2018-12-16 07:38:34 +00:00
# ifdef ESP32
2019-07-23 12:32:36 +00:00
# include "Bluetooth/BluetoothESP32.h"
2018-12-16 07:38:34 +00:00
# else
2019-07-23 12:32:36 +00:00
# include "Bluetooth/BluetoothHC05.h"
2018-12-16 07:38:34 +00:00
# endif
// Setup Serial Port Definitions
# if defined(__arm__)
// Required for Arduino Due, UARTclass is derived from HardwareSerial
static UARTClass & BlueWireSerial ( Serial1 ) ;
# else
// for ESP32, Mega
// HardwareSerial is it for these boards
static HardwareSerial & BlueWireSerial ( Serial1 ) ;
# endif
void initBlueWireSerial ( ) ;
bool validateFrame ( const CProtocol & frame , const char * name ) ;
void checkDisplayUpdate ( ) ;
void checkDebugCommands ( ) ;
2019-04-04 10:48:05 +00:00
void manageCyclicMode ( ) ;
2019-08-04 01:15:41 +00:00
bool preemptCyclicMode ( ) ;
2019-06-02 09:19:08 +00:00
void doStreaming ( ) ;
2019-06-15 23:09:29 +00:00
void heaterOn ( ) ;
void heaterOff ( ) ;
2019-07-06 13:46:20 +00:00
void updateFilteredData ( ) ;
2019-08-31 07:34:56 +00:00
bool HandleMQTTsetup ( char rxVal ) ;
void showMainmenu ( ) ;
void showMQTTmenu ( ) ;
2018-12-16 07:38:34 +00:00
// DS18B20 temperature sensor support
2019-06-29 08:08:37 +00:00
// Uses the RMT timeslot driver to operate as a one-wire bus
CTempSense TempSensor ;
2018-12-16 07:38:34 +00:00
long lastTemperatureTime ; // used to moderate DS18B20 access
2019-01-11 05:40:25 +00:00
int DS18B20holdoff = 2 ;
2019-06-29 08:08:37 +00:00
2019-04-12 23:18:07 +00:00
int BoardRevision = 0 ;
2019-07-27 02:47:16 +00:00
bool bTestBTModule = false ;
2019-08-31 07:34:56 +00:00
bool bSetupMQTT = false ;
2018-12-16 07:38:34 +00:00
unsigned long lastAnimationTime ; // used to sequence updates to LCD for animation
2019-07-06 13:46:20 +00:00
sFilteredData FilteredSamples ;
2018-12-16 07:38:34 +00:00
CommStates CommState ;
CTxManage TxManage ( TxEnbPin , BlueWireSerial ) ;
CModeratedFrame OEMCtrlFrame ; // data packet received from heater in response to OEM controller packet
CModeratedFrame HeaterFrame1 ; // data packet received from heater in response to OEM controller packet
CProtocol HeaterFrame2 ; // data packet received from heater in response to our packet
CProtocol DefaultBTCParams ( CProtocol : : CtrlMode ) ; // defines the default parameters, used in case of no OEM controller
CSmartError SmartError ;
CKeyPad KeyPad ;
CScreenManager ScreenManager ;
TelnetSpy DebugPort ;
2019-04-06 10:45:25 +00:00
CGPIOin GPIOin ;
2019-04-08 23:12:42 +00:00
CGPIOout GPIOout ;
2019-04-12 23:18:07 +00:00
CGPIOalg GPIOalg ;
2019-08-31 07:34:56 +00:00
sMQTTparams MQTTsetup ;
2018-12-16 07:38:34 +00:00
2019-07-18 12:28:40 +00:00
2018-12-16 07:38:34 +00:00
sRxLine PCline ;
long lastRxTime ; // used to observe inter character delays
2018-12-20 06:29:00 +00:00
bool bHasOEMController = false ;
2019-02-27 11:51:02 +00:00
bool bHasOEMLCDController = false ;
2018-12-20 06:29:00 +00:00
bool bHasHtrData = false ;
2019-06-29 08:08:37 +00:00
// these variables will persist over a soft reboot.
2019-07-24 20:06:47 +00:00
__NOINIT_ATTR float persistentRunTime ;
__NOINIT_ATTR float persistentGlowTime ;
2019-07-24 09:25:07 +00:00
2019-07-18 12:28:40 +00:00
CFuelGauge FuelGauge ;
2019-07-19 20:53:12 +00:00
CRTC_Store RTC_Store ;
2019-07-24 20:06:47 +00:00
CHourMeter * pHourMeter = NULL ;
2018-12-20 09:59:53 +00:00
2018-12-20 04:19:59 +00:00
bool bReportBlueWireData = REPORT_RAW_DATA ;
bool bReportJSONData = REPORT_JSON_TRANSMIT ;
2018-12-20 07:33:44 +00:00
bool bReportRecyleEvents = REPORT_BLUEWIRE_RECYCLES ;
2018-12-20 09:59:53 +00:00
bool bReportOEMresync = REPORT_OEM_RESYNC ;
2018-12-16 07:38:34 +00:00
2018-12-21 21:48:39 +00:00
CProtocolPackage reportHeaterData ;
CProtocolPackage primaryHeaterData ;
2018-12-16 07:38:34 +00:00
unsigned long moderator ;
bool bUpdateDisplay = false ;
bool bHaveWebClient = false ;
bool bBTconnected = false ;
2019-07-15 09:56:36 +00:00
long BootTime ;
2018-12-16 07:38:34 +00:00
2019-05-17 06:08:35 +00:00
hw_timer_t * watchdogTimer = NULL ;
2018-12-16 07:38:34 +00:00
////////////////////////////////////////////////////////////////////////////////////////////////////////
// Bluetooth instantiation
//
# ifdef ESP32
// Bluetooth options for ESP32
# if USE_HC05_BLUETOOTH == 1
CBluetoothESP32HC05 Bluetooth ( HC05_KeyPin , HC05_SensePin , Rx2Pin , Tx2Pin ) ; // Instantiate ESP32 using a HC-05
# elif USE_BLE_BLUETOOTH == 1
CBluetoothESP32BLE Bluetooth ; // Instantiate ESP32 BLE server
# elif USE_CLASSIC_BLUETOOTH == 1
CBluetoothESP32Classic Bluetooth ; // Instantiate ESP32 Classic Bluetooth server
# else // none selected
CBluetoothAbstract Bluetooth ; // default no bluetooth support - empty shell
# endif
# else // !ESP32
// Bluetooth for boards other than ESP32
# if USE_HC05_BLUETOOTH == 1
CBluetoothHC05 Bluetooth ( HC05_KeyPin , HC05_SensePin ) ; // Instantiate a HC-05
# else // none selected
CBluetoothAbstract Bluetooth ; // default no bluetooth support - empty shell
# endif // closing USE_HC05_BLUETOOTH
# endif // closing ESP32
//
// END Bluetooth instantiation
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
// setup Non Volatile storage
// this is very much hardware dependent, we can use the ESP32's FLASH
//
# ifdef ESP32
CESP32HeaterStorage actualNVstore ;
# else
CHeaterStorage actualNVstore ; // dummy, for now
# endif
// create reference to CHeaterStorage
// via the magic of polymorphism we can use this to access whatever
// storage is required for a specific platform in a uniform way
CHeaterStorage & NVstore = actualNVstore ;
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
CBluetoothAbstract & getBluetoothClient ( )
{
return Bluetooth ;
}
// callback function for Keypad events.
// must be an absolute function, cannot be a class member due the "this" element!
void parentKeyHandler ( uint8_t event )
{
ScreenManager . keyHandler ( event ) ; // call into the Screen Manager
}
2019-01-10 13:10:05 +00:00
2019-05-17 06:08:35 +00:00
void interruptReboot ( )
{
ets_printf ( " Software watchdog reboot...... \r \n " ) ;
esp_restart ( ) ;
}
2019-07-15 09:56:36 +00:00
//**************************************************************************************************
//** **
//** WORKAROUND for crap ESP32 millis() standard function **
//** **
//**************************************************************************************************
//
// Substitute shitfull ESP32 millis() with a true and proper ms counter
// The standard millis() on ESP32 is actually micros()/1000.
// This wraps every 71.5 minutes in a **very non linear fashion**.
//
// The FreeRTOS Tick Counter however does increment each ms, and rolls naturally past 0 every 49days.
// With this proper linear behaviour you can use valid timeout calcualtions even through wrap around.
// This elegance breaks using the standard library function, leading to many weird and obtuse issues.
//
// *** IMPORTANT ***
//
// You **MUST** use --wrap millis in the linker command, or -Wl,--wrap,millis in the GCC command.
// platformio.ini file for this project defines the latter as a build_flags entry.
//
// The linker will now link to __wrap_millis() instead of millis() for *any* usage of millis().
// Best of all this includes any library usages of millis() :-D
// If you really must call the shitty ESP32 Arduino millis(), you must call __real_millis()
// from your dubious code ;-) - basically DON'T do this.
extern " C " unsigned long __wrap_millis ( ) {
return xTaskGetTickCount ( ) ;
}
2019-01-10 13:10:05 +00:00
void setup ( ) {
2019-06-25 07:49:07 +00:00
2019-06-30 00:37:24 +00:00
// ensure cyclic mode is disabled after power on
2019-07-24 09:25:07 +00:00
bool bESP32PowerUpInit = false ;
if ( rtc_get_reset_reason ( 0 ) = = 1 /* || bForceInit*/ ) {
bESP32PowerUpInit = true ;
2019-07-19 20:53:12 +00:00
// bForceInit = false;
2019-07-24 09:25:07 +00:00
}
2019-07-27 14:28:39 +00:00
2019-06-25 07:49:07 +00:00
// initially, ensure the GPIO outputs are not activated during startup
// (GPIO2 tends to be one with default chip startup)
pinMode ( GPIOout1_pin , OUTPUT ) ;
pinMode ( GPIOout2_pin , OUTPUT ) ;
digitalWrite ( GPIOout1_pin , LOW ) ;
digitalWrite ( GPIOout2_pin , LOW ) ;
2019-08-01 12:57:18 +00:00
nvs_stats_t nvs_stats ;
2019-08-07 08:59:30 +00:00
nvs_get_stats ( NULL , & nvs_stats ) ;
2019-08-01 12:57:18 +00:00
2018-12-16 07:38:34 +00:00
// initialise TelnetSpy (port 23) as well as Serial to 115200
// Serial is the usual USB connection to a PC
// DO THIS BEFORE WE TRY AND SEND DEBUG INFO!
2019-06-26 20:04:24 +00:00
DebugPort . setWelcomeMsg ( ( char * ) (
" ************************************************* \r \n "
2018-12-16 07:38:34 +00:00
" * Connected to BTC heater controller debug port * \r \n "
2019-06-26 20:04:24 +00:00
" ************************************************* \r \n "
) ) ;
2018-12-16 07:38:34 +00:00
DebugPort . setBufferSize ( 8192 ) ;
DebugPort . begin ( 115200 ) ;
2019-01-14 03:30:41 +00:00
DebugPort . println ( " _______________________________________________________________ " ) ;
2019-06-29 08:08:37 +00:00
DebugPort . printf ( " Reset reason: core0:%d, core1:%d \r \n " , rtc_get_reset_reason ( 0 ) , rtc_get_reset_reason ( 0 ) ) ;
2019-07-19 20:53:12 +00:00
// DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-)
2019-06-29 08:08:37 +00:00
// initialise DS18B20 sensor interface
TempSensor . begin ( DS18B20_Pin ) ;
TempSensor . startConvert ( ) ; // kick off initial temperature sample
2018-12-16 07:38:34 +00:00
2019-06-29 08:08:37 +00:00
lastTemperatureTime = millis ( ) ;
lastAnimationTime = millis ( ) ;
2019-04-12 23:18:07 +00:00
BoardRevision = BoardDetect ( ) ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Board revision: V%.1f \r \n " , float ( BoardRevision ) * 0.1 ) ;
2019-04-12 23:18:07 +00:00
2019-06-02 09:19:08 +00:00
DebugPort . printf ( " ESP32 IDF Version: %s \r \n " , esp_get_idf_version ( ) ) ;
2019-08-01 12:57:18 +00:00
DebugPort . printf ( " NVS: entries- free=%d used=%d total=%d namespace count=%d \r \n " , nvs_stats . free_entries , nvs_stats . used_entries , nvs_stats . total_entries , nvs_stats . namespace_count ) ;
2019-06-02 09:19:08 +00:00
2019-01-20 10:14:45 +00:00
// Initialize SPIFFS
if ( ! SPIFFS . begin ( true ) ) {
DebugPort . println ( " An Error has occurred while mounting SPIFFS " ) ;
}
else {
DebugPort . println ( " Mounted SPIFFS OK " ) ;
2019-06-26 20:04:24 +00:00
DebugPort . printf ( " SPIFFS usage: %d/%d \r \n " , SPIFFS . usedBytes ( ) , SPIFFS . totalBytes ( ) ) ;
2019-06-30 00:37:24 +00:00
DebugPort . println ( " Listing SPIFFS contents: " ) ;
String report ;
2019-07-07 07:18:38 +00:00
listSPIFFS ( " / " , 2 , report ) ;
2019-01-20 10:14:45 +00:00
}
2019-01-14 03:30:41 +00:00
NVstore . init ( ) ;
NVstore . load ( ) ;
2019-06-15 23:09:29 +00:00
2019-06-02 09:19:08 +00:00
initMQTTJSONmoderator ( ) ; // prevents JSON for MQTT unless requested
2019-07-11 12:03:27 +00:00
initIPJSONmoderator ( ) ; // prevents JSON for IP unless requested
2019-06-02 09:19:08 +00:00
initTimerJSONmoderator ( ) ; // prevents JSON for timers unless requested
2019-07-15 09:56:36 +00:00
initSysModerator ( ) ;
2019-05-30 10:31:34 +00:00
2019-01-14 03:30:41 +00:00
KeyPad . begin ( keyLeft_pin , keyRight_pin , keyCentre_pin , keyUp_pin , keyDown_pin ) ;
KeyPad . setCallback ( parentKeyHandler ) ;
// Initialize the rtc object
Clock . begin ( ) ;
2019-01-27 01:17:49 +00:00
bool bNoClock = true ;
const BTCDateTime & now = Clock . get ( ) ;
if ( now . day ( ) ! = 0xa5 )
bNoClock = false ;
2019-07-15 09:56:36 +00:00
BootTime = Clock . get ( ) . secondstime ( ) ;
2019-01-14 03:30:41 +00:00
2019-01-27 01:17:49 +00:00
ScreenManager . begin ( bNoClock ) ;
2019-07-19 20:53:12 +00:00
if ( ! bNoClock & & Clock . lostPower ( ) ) {
ScreenManager . selectMenu ( CScreenManager : : BranchMenu , CScreenManager : : SetClockUI ) ;
}
2018-12-16 07:38:34 +00:00
# if USE_WIFI == 1
2019-05-16 11:12:29 +00:00
sCredentials creds = NVstore . getCredentials ( ) ;
2019-06-06 01:32:43 +00:00
if ( NVstore . getUserSettings ( ) . enableWifi ) {
2019-05-16 11:12:29 +00:00
initWifi ( WiFi_TriggerPin , creds . SSID , creds . APpassword ) ;
2018-12-16 07:38:34 +00:00
# if USE_OTA == 1
2019-06-06 01:32:43 +00:00
if ( NVstore . getUserSettings ( ) . enableOTA ) {
2019-03-24 05:48:03 +00:00
initOTA ( ) ;
}
2018-12-16 07:38:34 +00:00
# endif // USE_OTA
# if USE_WEBSERVER == 1
2019-03-24 05:48:03 +00:00
initWebServer ( ) ;
2018-12-16 07:38:34 +00:00
# endif // USE_WEBSERVER
2019-08-27 13:09:07 +00:00
# if USE_MQTT == 1
2019-08-31 07:34:56 +00:00
mqttInit ( ) ;
2019-08-27 13:09:07 +00:00
# endif // USE_MQTT
2019-03-24 05:48:03 +00:00
}
2018-12-16 07:38:34 +00:00
# endif // USE_WIFI
pinMode ( LED_Pin , OUTPUT ) ; // On board LED indicator
digitalWrite ( LED_Pin , LOW ) ;
initBlueWireSerial ( ) ;
// prepare for first long delay detection
lastRxTime = millis ( ) ;
TxManage . begin ( ) ; // ensure Tx enable pin is setup
// define defaults should OEM controller be missing
2019-06-15 23:09:29 +00:00
DefaultBTCParams . setHeaterDemand ( 23 ) ;
2018-12-16 07:38:34 +00:00
DefaultBTCParams . setTemperature_Actual ( 22 ) ;
2019-01-11 05:40:25 +00:00
DefaultBTCParams . setSystemVoltage ( 12.0 ) ;
2018-12-16 07:38:34 +00:00
DefaultBTCParams . setPump_Min ( 1.6f ) ;
DefaultBTCParams . setPump_Max ( 5.5f ) ;
DefaultBTCParams . setFan_Min ( 1680 ) ;
DefaultBTCParams . setFan_Max ( 4500 ) ;
2019-01-11 05:40:25 +00:00
DefaultBTCParams . Controller . FanSensor = 1 ;
2018-12-16 07:38:34 +00:00
bBTconnected = false ;
Bluetooth . begin ( ) ;
2019-04-09 23:28:46 +00:00
setupGPIO ( ) ;
2019-04-28 10:23:11 +00:00
2019-05-17 06:08:35 +00:00
# if USE_SW_WATCHDOG == 1
// create a watchdog timer
watchdogTimer = timerBegin ( 0 , 80 , true ) ; //timer 0, divisor 80
2019-06-02 09:19:08 +00:00
timerAlarmWrite ( watchdogTimer , 15000000 , false ) ; //set time in uS must be fed within this time or reboot
2019-05-17 06:08:35 +00:00
timerAttachInterrupt ( watchdogTimer , & interruptReboot , true ) ;
timerAlarmEnable ( watchdogTimer ) ; //enable interrupt
# endif
2019-07-06 13:46:20 +00:00
FilteredSamples . ipVolts . setRounding ( 0.1 ) ;
FilteredSamples . GlowAmps . setRounding ( 0.01 ) ;
FilteredSamples . GlowVolts . setRounding ( 0.1 ) ;
FilteredSamples . Fan . setRounding ( 10 ) ;
FilteredSamples . Fan . setAlpha ( 0.7 ) ;
FilteredSamples . AmbientTemp . reset ( - 100.0 ) ;
2019-07-21 11:17:54 +00:00
FilteredSamples . FastipVolts . setRounding ( 0.1 ) ;
FilteredSamples . FastipVolts . setAlpha ( 0.7 ) ;
FilteredSamples . FastGlowAmps . setRounding ( 0.01 ) ;
FilteredSamples . FastGlowAmps . setAlpha ( 0.7 ) ;
2019-07-19 20:53:12 +00:00
RTC_Store . begin ( ) ;
FuelGauge . init ( RTC_Store . getFuelGauge ( ) ) ;
// demandDegC = RTC_Store.getDesiredTemp();
// demandPump = RTC_Store.getDesiredPump();
// bCyclicEngaged = RTC_Store.getCyclicEngaged();
DebugPort . printf ( " Previous cyclic active = %d \r \n " , RTC_Store . getCyclicEngaged ( ) ) ; // state flag required for cyclic mode to persist properly after a WD reboot :-)
2019-07-18 12:28:40 +00:00
2019-07-26 12:13:46 +00:00
pHourMeter = new CHourMeter ( persistentRunTime , persistentGlowTime ) ; // persistent vars passed by reference so they can be valid after SW reboots
pHourMeter - > init ( bESP32PowerUpInit | | RTC_Store . getBootInit ( ) ) ; // ensure persistent memory variable are reset after powerup, or OTA update
2019-07-25 12:31:31 +00:00
RTC_Store . setBootInit ( false ) ;
2019-07-24 09:25:07 +00:00
2019-04-28 10:23:11 +00:00
delay ( 1000 ) ; // just to hold the splash screeen for while
2018-12-16 07:38:34 +00:00
}
// main functional loop is based about a state machine approach, waiting for data
// to appear upon the blue wire, and marshalling into an appropriate receive buffers
// according to the state.
void loop ( )
{
float fTemperature ;
unsigned long timenow = millis ( ) ;
2019-06-02 09:19:08 +00:00
// DebugPort.handle(); // keep telnet spy alive
2018-12-16 07:38:34 +00:00
//////////////////////////////////////////////////////////////////////////////////////
// Blue wire data reception
// Reads data from the "blue wire" Serial port, (to/from heater)
// If an OEM controller exists we will also see it's data frames
// Note that the data is read now, then held for later use in the state machine
//
sRxData BlueWireData ;
2018-12-19 19:13:28 +00:00
// calc elapsed time since last rxd byte
// used to detect no OEM controller, or the start of an OEM frame sequence
unsigned long RxTimeElapsed = timenow - lastRxTime ;
2018-12-16 07:38:34 +00:00
if ( BlueWireSerial . available ( ) ) {
2019-04-19 11:38:39 +00:00
// Data is available, read and store it now, use it later
2018-12-16 07:38:34 +00:00
// Note that if not in a recognised data receive frame state, the data
// will be deliberately lost!
BlueWireData . setValue ( BlueWireSerial . read ( ) ) ; // read hex byte, store for later use
lastRxTime = timenow ; // tickle last rx time, for rx data timeout purposes
}
// precautionary state machine action if all 24 bytes were not received
// whilst expecting a frame from the blue wire
if ( RxTimeElapsed > RX_DATA_TIMOUT ) {
if ( CommState . is ( CommStates : : OEMCtrlRx ) | |
CommState . is ( CommStates : : HeaterRx1 ) | |
CommState . is ( CommStates : : HeaterRx2 ) ) {
if ( RxTimeElapsed > = moderator ) {
2019-07-15 09:56:36 +00:00
moderator + = 10 ;
2018-12-20 07:33:44 +00:00
if ( bReportRecyleEvents ) {
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " %ldms - " , RxTimeElapsed ) ;
2018-12-20 07:33:44 +00:00
}
2018-12-16 07:38:34 +00:00
if ( CommState . is ( CommStates : : OEMCtrlRx ) ) {
2018-12-20 06:29:00 +00:00
bHasOEMController = false ;
2019-02-27 11:51:02 +00:00
bHasOEMLCDController = false ;
2018-12-20 09:59:53 +00:00
if ( bReportRecyleEvents )
DebugPort . println ( " Timeout collecting OEM controller data, returning to Idle State " ) ;
2018-12-16 07:38:34 +00:00
}
else if ( CommState . is ( CommStates : : HeaterRx1 ) ) {
2018-12-20 06:29:00 +00:00
bHasHtrData = false ;
2018-12-20 09:59:53 +00:00
if ( bReportRecyleEvents )
DebugPort . println ( " Timeout collecting OEM heater response data, returning to Idle State " ) ;
2018-12-16 07:38:34 +00:00
}
else {
2018-12-20 06:29:00 +00:00
bHasHtrData = false ;
2018-12-20 09:59:53 +00:00
if ( bReportRecyleEvents )
DebugPort . println ( " Timeout collecting BTC heater response data, returning to Idle State " ) ;
2018-12-16 07:38:34 +00:00
}
}
2018-12-20 09:59:53 +00:00
if ( bReportRecyleEvents )
DebugPort . println ( " Recycling blue wire serial interface " ) ;
2018-12-16 07:38:34 +00:00
initBlueWireSerial ( ) ;
CommState . set ( CommStates : : TemperatureRead ) ; // revert to idle mode, after passing thru temperature mode
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// do our state machine to track the reception and delivery of blue wire data
long tDelta ;
switch ( CommState . get ( ) ) {
case CommStates : : Idle :
2019-05-17 06:08:35 +00:00
# if USE_SW_WATCHDOG == 1
2019-05-18 08:49:22 +00:00
feedWatchdog ( ) ; //reset timer (feed watchdog)
2019-05-17 06:08:35 +00:00
# endif
2019-06-02 09:19:08 +00:00
doStreaming ( ) ; // do wifi, BT tx etc when NOT in midst of handling blue wire
// this especially avoids E-07 faults due to larger data transfers
2019-07-15 09:56:36 +00:00
moderator = 50 ;
2018-12-16 07:38:34 +00:00
# if RX_LED == 1
digitalWrite ( LED_Pin , LOW ) ;
# endif
// Detect the possible start of a new frame sequence from an OEM controller
// This will be the first activity for considerable period on the blue wire
// The heater always responds to a controller frame, but otherwise never by itself
2019-05-11 02:18:06 +00:00
2019-06-06 01:32:43 +00:00
if ( RxTimeElapsed > = ( NVstore . getUserSettings ( ) . FrameRate - 60 ) ) { // compensate for the time spent just doing things in this state machine
2018-12-16 07:38:34 +00:00
// have not seen any receive data for a second.
// OEM controller is probably not connected.
// Skip state machine immediately to BTC_Tx, sending our own settings.
2018-12-21 10:58:39 +00:00
bHasHtrData = false ;
2018-12-20 06:29:00 +00:00
bHasOEMController = false ;
2019-02-27 11:51:02 +00:00
bHasOEMLCDController = false ;
2018-12-16 07:38:34 +00:00
bool isBTCmaster = true ;
TxManage . PrepareFrame ( DefaultBTCParams , isBTCmaster ) ; // use our parameters, and mix in NV storage values
TxManage . Start ( timenow ) ;
CommState . set ( CommStates : : BTC_Tx ) ;
break ;
}
# if SUPPORT_OEM_CONTROLLER == 1
2019-07-15 09:56:36 +00:00
if ( BlueWireData . available ( ) & & ( RxTimeElapsed > ( RX_DATA_TIMOUT + 10 ) ) ) {
2018-12-20 09:59:53 +00:00
if ( bReportOEMresync ) {
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Re-sync'd with OEM Controller. %ldms Idle time. \r \n " , RxTimeElapsed ) ;
2018-12-20 09:59:53 +00:00
}
2018-12-21 06:27:25 +00:00
bHasHtrData = false ;
2018-12-20 06:29:00 +00:00
bHasOEMController = true ;
2018-12-16 07:38:34 +00:00
CommState . set ( CommStates : : OEMCtrlRx ) ; // we must add this new byte!
//
// ** IMPORTANT - we must drop through to OEMCtrlRx *NOW* (skipping break) **
2018-12-19 19:13:28 +00:00
// ** otherwise the first byte will be lost! **
2018-12-16 07:38:34 +00:00
//
}
else {
Clock . update ( ) ;
checkDisplayUpdate ( ) ;
break ; // only break if we fail all Idle state tests
}
# else
Clock . update ( ) ;
checkDisplayUpdate ( ) ;
break ;
# endif
case CommStates : : OEMCtrlRx :
# if RX_LED == 1
digitalWrite ( LED_Pin , HIGH ) ;
# endif
// collect OEM controller frame
if ( BlueWireData . available ( ) ) {
if ( CommState . collectData ( OEMCtrlFrame , BlueWireData . getValue ( ) ) ) {
2018-12-20 09:59:53 +00:00
CommState . set ( CommStates : : OEMCtrlValidate ) ; // collected 24 bytes, move on!
2018-12-16 07:38:34 +00:00
}
}
break ;
2018-12-20 09:59:53 +00:00
case CommStates : : OEMCtrlValidate :
2018-12-16 07:38:34 +00:00
# if RX_LED == 1
digitalWrite ( LED_Pin , LOW ) ;
# endif
// test for valid CRC, abort and restarts Serial1 if invalid
if ( ! validateFrame ( OEMCtrlFrame , " OEM " ) ) {
break ;
}
2018-12-20 04:19:59 +00:00
// filled OEM controller frame
OEMCtrlFrame . setTime ( ) ;
2019-02-27 11:51:02 +00:00
// LCD controllers use 0x76 as first byte, rotary knobs use 0x78
bHasOEMLCDController = ( OEMCtrlFrame . Controller . Byte0 ! = 0x78 ) ;
2018-12-16 07:38:34 +00:00
CommState . set ( CommStates : : HeaterRx1 ) ;
break ;
case CommStates : : HeaterRx1 :
# if RX_LED == 1
digitalWrite ( LED_Pin , HIGH ) ;
# endif
// collect heater frame, always in response to an OEM controller frame
if ( BlueWireData . available ( ) ) {
if ( CommState . collectData ( HeaterFrame1 , BlueWireData . getValue ( ) ) ) {
2018-12-20 09:59:53 +00:00
CommState . set ( CommStates : : HeaterValidate1 ) ;
2018-12-16 07:38:34 +00:00
}
}
break ;
2018-12-20 09:59:53 +00:00
case CommStates : : HeaterValidate1 :
2018-12-16 07:38:34 +00:00
# if RX_LED == 1
digitalWrite ( LED_Pin , LOW ) ;
# endif
// test for valid CRC, abort and restarts Serial1 if invalid
if ( ! validateFrame ( HeaterFrame1 , " RX1 " ) ) {
2018-12-20 06:29:00 +00:00
bHasHtrData = false ;
2018-12-16 07:38:34 +00:00
break ;
}
2018-12-20 06:29:00 +00:00
bHasHtrData = true ;
2018-12-16 07:38:34 +00:00
// received heater frame (after controller message), report
// do some monitoring of the heater state variable
// if abnormal transitions, introduce a smart error!
2018-12-20 04:19:59 +00:00
// This routine also cancels ON/OFF requests if runstate in startup/shutdown periods
2018-12-16 07:38:34 +00:00
SmartError . monitor ( HeaterFrame1 ) ;
2018-12-20 04:19:59 +00:00
HeaterFrame1 . setTime ( ) ;
2018-12-16 07:38:34 +00:00
2018-12-21 21:48:39 +00:00
while ( BlueWireSerial . available ( ) ) {
DebugPort . println ( " DUMPED ROGUE RX DATA " ) ;
BlueWireSerial . read ( ) ;
2018-12-16 07:38:34 +00:00
}
2018-12-21 21:48:39 +00:00
BlueWireSerial . flush ( ) ;
primaryHeaterData . set ( HeaterFrame1 , OEMCtrlFrame ) ; // OEM is always *the* controller
if ( bReportBlueWireData ) {
primaryHeaterData . reportFrames ( true ) ;
CommState . setDelay ( 20 ) ; // let serial get sent before we send blue wire
2018-12-16 07:38:34 +00:00
}
2018-12-21 21:48:39 +00:00
else {
CommState . setDelay ( 0 ) ;
}
CommState . set ( CommStates : : HeaterReport1 ) ;
break ;
case CommStates : : HeaterReport1 :
if ( CommState . delayExpired ( ) ) {
2019-07-07 07:18:38 +00:00
bool isBTCmaster = false ;
TxManage . PrepareFrame ( OEMCtrlFrame , isBTCmaster ) ; // parrot OEM parameters, but block NV modes
TxManage . Start ( timenow ) ;
CommState . set ( CommStates : : BTC_Tx ) ;
2018-12-16 07:38:34 +00:00
}
break ;
case CommStates : : BTC_Tx :
// Handle time interval where we send data to the blue wire
lastRxTime = timenow ; // *we* are pumping onto blue wire, track this activity!
if ( TxManage . CheckTx ( timenow ) ) { // monitor progress of our data delivery
CommState . set ( CommStates : : HeaterRx2 ) ; // then await heater repsonse
}
break ;
case CommStates : : HeaterRx2 :
# if RX_LED == 1
digitalWrite ( LED_Pin , HIGH ) ;
# endif
// collect heater frame, in response to our control frame
if ( BlueWireData . available ( ) ) {
# ifdef BADSTARTCHECK
if ( ! CommState . checkValidStart ( BlueWireData . getValue ( ) ) ) {
DebugPort . println ( " ***** Invalid start of frame - restarting Serial port ***** " ) ;
initBlueWireSerial ( ) ;
CommState . set ( CommStates : : Idle ) ;
}
else {
if ( CommState . collectData ( HeaterFrame2 , BlueWireData . getValue ( ) ) ) {
2018-12-20 09:59:53 +00:00
CommState . set ( CommStates : : HeaterValidate2 ) ;
2018-12-16 07:38:34 +00:00
}
}
# else
if ( CommState . collectData ( HeaterFrame2 , BlueWireData . getValue ( ) ) ) {
2018-12-20 09:59:53 +00:00
CommState . set ( CommStates : : HeaterValidate2 ) ;
2018-12-16 07:38:34 +00:00
}
# endif
}
break ;
2018-12-20 09:59:53 +00:00
case CommStates : : HeaterValidate2 :
2018-12-16 07:38:34 +00:00
# if RX_LED == 1
2018-12-21 21:48:39 +00:00
digitalWrite ( LED_Pin , LOW ) ;
2018-12-16 07:38:34 +00:00
# endif
2018-12-20 04:19:59 +00:00
// test for valid CRC, abort and restart Serial1 if invalid
2018-12-16 07:38:34 +00:00
if ( ! validateFrame ( HeaterFrame2 , " RX2 " ) ) {
2018-12-20 06:29:00 +00:00
bHasHtrData = false ;
2018-12-16 07:38:34 +00:00
break ;
}
2018-12-20 06:29:00 +00:00
bHasHtrData = true ;
2018-12-16 07:38:34 +00:00
// received heater frame (after our control message), report
// do some monitoring of the heater state variables
// if abnormal transitions, introduce a smart error!
SmartError . monitor ( HeaterFrame2 ) ;
2018-12-21 21:48:39 +00:00
if ( ! bHasOEMController ) // no OEM controller - BTC is *the* controller
primaryHeaterData . set ( HeaterFrame2 , TxManage . getFrame ( ) ) ;
if ( bReportBlueWireData ) {
reportHeaterData . set ( HeaterFrame2 , TxManage . getFrame ( ) ) ;
reportHeaterData . reportFrames ( false ) ;
CommState . setDelay ( 20 ) ; // let serial get sent before we send blue wire
}
else {
CommState . setDelay ( 0 ) ;
}
CommState . set ( CommStates : : HeaterReport2 ) ;
break ;
case CommStates : : HeaterReport2 :
if ( CommState . delayExpired ( ) ) {
CommState . set ( CommStates : : TemperatureRead ) ;
}
2018-12-16 07:38:34 +00:00
break ;
2018-12-21 21:48:39 +00:00
2018-12-16 07:38:34 +00:00
case CommStates : : TemperatureRead :
// update temperature reading,
// synchronised with serial reception as interrupts do get disabled in the OneWire library
tDelta = timenow - lastTemperatureTime ;
2019-07-15 09:56:36 +00:00
if ( tDelta > MIN_TEMPERATURE_INTERVAL ) { // maintain a minimum holdoff period
2019-06-26 20:04:24 +00:00
lastTemperatureTime = millis ( ) ; // reset time to observe temeprature
2019-06-29 08:08:37 +00:00
if ( TempSensor . readTemperature ( fTemperature ) ) {
if ( DS18B20holdoff ) {
DS18B20holdoff - - ;
DebugPort . printf ( " Skipped initial DS18B20 reading: %f \r \n " , fTemperature ) ;
} // first value upon sensor connect is bad
2019-01-11 05:40:25 +00:00
else {
// exponential mean to stabilse readings
2019-07-06 13:46:20 +00:00
FilteredSamples . AmbientTemp . update ( fTemperature ) ;
2019-04-04 10:48:05 +00:00
manageCyclicMode ( ) ;
2019-01-11 05:40:25 +00:00
}
}
else {
2019-06-29 08:08:37 +00:00
DS18B20holdoff = 3 ;
2019-07-06 13:46:20 +00:00
FilteredSamples . AmbientTemp . reset ( - 100.0 ) ;
}
2019-06-29 08:08:37 +00:00
TempSensor . startConvert ( ) ; // request a new conversion, will be ready by the time we loop back around
2019-01-10 13:10:05 +00:00
2018-12-16 07:38:34 +00:00
ScreenManager . reqUpdate ( ) ;
}
2019-07-22 10:58:36 +00:00
2019-07-18 12:28:40 +00:00
if ( bHasHtrData ) {
2019-07-22 10:58:36 +00:00
// apply exponential mean to the anlogue readings for some smoothing
2019-07-11 12:03:27 +00:00
updateFilteredData ( ) ;
2019-07-22 10:58:36 +00:00
// integrate fuel pump activity for fuel gauge
FuelGauge . Integrate ( getHeaterInfo ( ) . getPump_Actual ( ) ) ;
// test for low volts shutdown during normal run
if ( INBOUNDS ( getHeaterInfo ( ) . getRunState ( ) , 1 , 5 ) ) { // check for Low Voltage Cutout
SmartError . checkVolts ( FilteredSamples . FastipVolts . getValue ( ) , FilteredSamples . FastGlowAmps . getValue ( ) ) ;
}
// trap being in state 0 with a heater error - cancel user on memory to avoid unexpected cyclic restarts
if ( RTC_Store . getCyclicEngaged ( ) & & ( getHeaterInfo ( ) . getRunState ( ) = = 0 ) & & ( getHeaterInfo ( ) . getErrState ( ) > 1 ) ) {
DebugPort . println ( " Forcing cyclic cancel due to error induced shutdown " ) ;
RTC_Store . setCyclicEngaged ( false ) ;
}
2019-07-25 11:27:57 +00:00
2019-07-24 20:06:47 +00:00
pHourMeter - > monitor ( HeaterFrame2 ) ;
2019-07-21 11:17:54 +00:00
}
2018-12-20 04:19:59 +00:00
updateJSONclients ( bReportJSONData ) ;
2019-01-11 05:40:25 +00:00
CommState . set ( CommStates : : Idle ) ;
2019-06-29 08:08:37 +00:00
NVstore . doSave ( ) ; // now is a good time to store to the NV storage, well away from any blue wire activity
2018-12-16 07:38:34 +00:00
break ;
} // switch(CommState)
BlueWireData . reset ( ) ; // ensure we flush any used data
} // loop
void DebugReportFrame ( const char * hdr , const CProtocol & Frame , const char * ftr )
{
DebugPort . print ( hdr ) ; // header
for ( int i = 0 ; i < 24 ; i + + ) {
char str [ 16 ] ;
sprintf ( str , " %02X " , Frame . Data [ i ] ) ; // build 2 dig hex values
DebugPort . print ( str ) ; // and print
}
DebugPort . print ( ftr ) ; // footer
}
2019-04-04 10:48:05 +00:00
void manageCyclicMode ( )
{
2019-07-03 13:01:36 +00:00
const sCyclicThermostat & cyclic = NVstore . getUserSettings ( ) . cyclic ;
2019-07-19 20:53:12 +00:00
if ( cyclic . Stop & & RTC_Store . getCyclicEngaged ( ) ) { // cyclic mode enabled, and user has started heater
2019-04-27 10:41:47 +00:00
int stopDeltaT = cyclic . Stop + 1 ; // bump up by 1 degree - no point invoking at 1 deg over!
2019-07-21 11:17:54 +00:00
float deltaT = getTemperatureSensor ( ) - getDemandDegC ( ) ;
2019-05-12 10:15:18 +00:00
// DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT);
2019-04-04 10:48:05 +00:00
// ensure we cancel user ON mode if heater throws an error
int errState = getHeaterInfo ( ) . getErrState ( ) ;
2019-06-16 08:13:16 +00:00
if ( ( errState > 1 ) & & ( errState < 12 ) & & ( errState ! = 8 ) ) {
// excludes errors 0,1(OK), 12(E1-11,Retry) & 8(E-07,Comms Error)
2019-04-04 10:48:05 +00:00
DebugPort . println ( " CYCLIC MODE: cancelling user ON status " ) ;
requestOff ( ) ; // forcibly cancel cyclic operation - pretend user pressed OFF
}
int heaterState = getHeaterInfo ( ) . getRunState ( ) ;
// check if over temp, turn off heater
2019-04-27 10:41:47 +00:00
if ( deltaT > stopDeltaT ) {
2019-04-04 10:48:05 +00:00
if ( heaterState > 0 & & heaterState < = 5 ) {
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " CYCLIC MODE: Stopping heater, deltaT > +%d \r \n " , stopDeltaT ) ;
2019-04-04 10:48:05 +00:00
heaterOff ( ) ; // over temp - request heater stop
}
}
// check if under temp, turn on heater
2019-04-27 10:41:47 +00:00
if ( deltaT < cyclic . Start ) {
// typ. 1 degree below set point - restart heater
2019-04-04 10:48:05 +00:00
if ( heaterState = = 0 ) {
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " CYCLIC MODE: Restarting heater, deltaT <%d \r \n " , cyclic . Start ) ;
2019-04-04 10:48:05 +00:00
heaterOn ( ) ;
}
}
}
}
2018-12-16 07:38:34 +00:00
2019-08-04 01:15:41 +00:00
bool preemptCyclicMode ( )
{
const sCyclicThermostat & cyclic = NVstore . getUserSettings ( ) . cyclic ;
if ( cyclic . Stop ) { // cyclic mode enabled, and user has started heater
int stopDeltaT = cyclic . Stop + 1 ; // bump up by 1 degree - no point invoking at 1 deg over!
float deltaT = getTemperatureSensor ( ) - getDemandDegC ( ) ;
// check if over temp, skip straight to suspend
if ( deltaT > stopDeltaT ) {
DebugPort . printf ( " CYCLIC MODE: Skipping directly to suspend, deltaT > +%d \r \n " , stopDeltaT ) ;
heaterOff ( ) ; // over temp - request heater stop
return true ;
}
}
return false ;
}
2018-12-16 07:38:34 +00:00
void initBlueWireSerial ( )
{
// initialize serial port to interact with the "blue wire"
// 25000 baud, Tx and Rx channels of Chinese heater comms interface:
// Tx/Rx data to/from heater,
// Note special baud rate for Chinese heater controllers
# if defined(__arm__) || defined(__AVR__)
BlueWireSerial . begin ( 25000 ) ;
pinMode ( Rx1Pin , INPUT_PULLUP ) ; // required for MUX to work properly
# elif ESP32
// ESP32
BlueWireSerial . begin ( 25000 , SERIAL_8N1 , Rx1Pin , Tx1Pin ) ; // need to explicitly specify pins for pin multiplexer!
pinMode ( Rx1Pin , INPUT_PULLUP ) ; // required for MUX to work properly
# endif
}
bool validateFrame ( const CProtocol & frame , const char * name )
{
if ( ! frame . verifyCRC ( ) ) {
// Bad CRC - restart blue wire Serial port
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " \007 Bad CRC detected for %s frame - restarting blue wire's serial port \r \n " , name ) ;
2018-12-20 04:19:59 +00:00
DebugReportFrame ( " BAD CRC: " , frame , " \r \n " ) ;
2018-12-16 07:38:34 +00:00
initBlueWireSerial ( ) ;
2018-12-21 21:48:39 +00:00
CommState . set ( CommStates : : TemperatureRead ) ;
2018-12-16 07:38:34 +00:00
return false ;
}
return true ;
}
void requestOn ( )
2019-04-04 10:48:05 +00:00
{
2019-07-26 12:13:46 +00:00
if ( bHasHtrData & & ( 0 = = SmartError . checkVolts ( FilteredSamples . FastipVolts . getValue ( ) , FilteredSamples . FastGlowAmps . getValue ( ) ) ) ) {
2019-07-21 11:17:54 +00:00
RTC_Store . setCyclicEngaged ( true ) ; // for cyclic mode
2019-08-04 01:15:41 +00:00
if ( ! preemptCyclicMode ( ) ) { // only start if below cyclic threshold when enabled
heaterOn ( ) ;
}
2019-07-21 11:17:54 +00:00
}
2019-04-04 10:48:05 +00:00
}
void requestOff ( )
{
heaterOff ( ) ;
2019-07-19 20:53:12 +00:00
RTC_Store . setCyclicEngaged ( false ) ; // for cyclic mode
2019-04-04 10:48:05 +00:00
}
void heaterOn ( )
2018-12-16 07:38:34 +00:00
{
TxManage . queueOnRequest ( ) ;
SmartError . reset ( ) ;
}
2019-04-04 10:48:05 +00:00
void heaterOff ( )
2018-12-16 07:38:34 +00:00
{
TxManage . queueOffRequest ( ) ;
SmartError . inhibit ( ) ;
}
2019-07-19 20:53:12 +00:00
bool reqDemand ( uint8_t newDemand , bool save )
2018-12-16 07:38:34 +00:00
{
2018-12-20 06:29:00 +00:00
if ( bHasOEMController )
2018-12-20 04:19:59 +00:00
return false ;
2019-07-06 13:46:20 +00:00
uint8_t max = DefaultBTCParams . getTemperature_Max ( ) ;
uint8_t min = DefaultBTCParams . getTemperature_Min ( ) ;
2019-07-19 20:53:12 +00:00
if ( newDemand > = max )
newDemand = max ;
if ( newDemand < = min )
newDemand = min ;
2019-06-06 01:32:43 +00:00
2019-06-15 23:09:29 +00:00
// set and save the demand to NV storage
// note that we now maintain fixed Hz and Thermostat set points seperately
2019-07-19 20:53:12 +00:00
if ( getThermostatModeActive ( ) ) {
RTC_Store . setDesiredTemp ( newDemand ) ;
}
else {
RTC_Store . setDesiredPump ( newDemand ) ;
}
2018-12-16 07:38:34 +00:00
ScreenManager . reqUpdate ( ) ;
2018-12-20 04:19:59 +00:00
return true ;
2018-12-16 07:38:34 +00:00
}
2019-07-19 20:53:12 +00:00
bool reqDemandDelta ( int delta )
2018-12-16 07:38:34 +00:00
{
2019-07-19 20:53:12 +00:00
uint8_t newDemand ;
if ( getThermostatModeActive ( ) ) {
newDemand = RTC_Store . getDesiredTemp ( ) + delta ;
}
else {
newDemand = RTC_Store . getDesiredPump ( ) + delta ;
}
2018-12-16 07:38:34 +00:00
2019-07-19 20:53:12 +00:00
return reqDemand ( newDemand ) ;
2018-12-16 07:38:34 +00:00
}
2018-12-20 04:19:59 +00:00
bool reqThermoToggle ( )
2018-12-16 07:38:34 +00:00
{
2019-03-14 10:52:59 +00:00
return setThermostatMode ( getThermostatModeActive ( ) ? 0 : 1 ) ;
2018-12-16 07:38:34 +00:00
}
2019-07-06 13:46:20 +00:00
bool setThermostatMode ( uint8_t val )
2018-12-16 07:38:34 +00:00
{
2018-12-20 06:29:00 +00:00
if ( bHasOEMController )
2018-12-20 04:19:59 +00:00
return false ;
2019-06-06 01:32:43 +00:00
sUserSettings settings = NVstore . getUserSettings ( ) ;
2019-07-03 13:01:36 +00:00
if ( INBOUNDS ( val , 0 , 1 ) )
settings . useThermostat = val ;
2019-06-06 01:32:43 +00:00
NVstore . setUserSettings ( settings ) ;
2018-12-20 04:19:59 +00:00
return true ;
2018-12-16 07:38:34 +00:00
}
2019-06-06 01:32:43 +00:00
void setDegFMode ( bool state )
{
sUserSettings settings = NVstore . getUserSettings ( ) ;
settings . degF = state ? 0x01 : 0x00 ;
NVstore . setUserSettings ( settings ) ;
}
2018-12-16 07:38:34 +00:00
2019-03-14 10:52:59 +00:00
bool getThermostatModeActive ( )
2018-12-16 07:38:34 +00:00
{
2019-03-14 10:52:59 +00:00
if ( bHasOEMController ) {
return getHeaterInfo ( ) . isThermostat ( ) ;
}
else {
2019-06-06 01:32:43 +00:00
return NVstore . getUserSettings ( ) . useThermostat ! = 0 ;
2019-03-14 10:52:59 +00:00
}
2018-12-16 07:38:34 +00:00
}
2019-08-07 08:59:30 +00:00
bool getExternalThermostatModeActive ( )
{
return GPIOin . usesExternalThermostat ( ) ;
}
bool getExternalThermostatOn ( )
{
return GPIOin . getState ( 1 ) ;
}
2019-08-26 20:13:01 +00:00
const char * getExternalThermostatHoldTime ( )
{
return GPIOin . getExtThermHoldTime ( ) ;
}
2019-08-07 08:59:30 +00:00
2018-12-16 07:38:34 +00:00
void checkDisplayUpdate ( )
{
// only update OLED when not processing blue wire
if ( ScreenManager . checkUpdate ( ) ) {
2019-07-15 09:56:36 +00:00
lastAnimationTime = millis ( ) + 100 ;
2018-12-16 07:38:34 +00:00
ScreenManager . animate ( ) ;
ScreenManager . refresh ( ) ; // always refresh post major update
}
long tDelta = millis ( ) - lastAnimationTime ;
if ( tDelta > = 100 ) {
2019-07-15 09:56:36 +00:00
lastAnimationTime = millis ( ) + 100 ;
2018-12-16 07:38:34 +00:00
if ( ScreenManager . animate ( ) )
ScreenManager . refresh ( ) ;
}
}
void reqPumpPrime ( bool on )
{
DefaultBTCParams . setPump_Prime ( on ) ;
}
2019-06-29 08:08:37 +00:00
void forceBootInit ( )
{
2019-07-25 11:27:57 +00:00
RTC_Store . setBootInit ( ) ;
2019-06-29 08:08:37 +00:00
}
uint8_t getDemandDegC ( )
{
2019-07-19 20:53:12 +00:00
return RTC_Store . getDesiredTemp ( ) ;
2019-06-29 08:08:37 +00:00
}
2019-07-03 13:01:36 +00:00
void setDemandDegC ( uint8_t val )
{
2019-07-06 13:46:20 +00:00
uint8_t max = DefaultBTCParams . getTemperature_Max ( ) ;
uint8_t min = DefaultBTCParams . getTemperature_Min ( ) ;
2019-07-03 13:01:36 +00:00
BOUNDSLIMIT ( val , min , max ) ;
2019-07-19 20:53:12 +00:00
RTC_Store . setDesiredTemp ( val ) ;
2019-07-03 13:01:36 +00:00
}
2019-06-29 08:08:37 +00:00
uint8_t getDemandPump ( )
{
2019-07-19 20:53:12 +00:00
return RTC_Store . getDesiredPump ( ) ;
2019-06-29 08:08:37 +00:00
}
2019-03-14 10:52:59 +00:00
float getTemperatureDesired ( )
{
if ( bHasOEMController ) {
2019-06-15 23:09:29 +00:00
return getHeaterInfo ( ) . getHeaterDemand ( ) ;
2019-03-14 10:52:59 +00:00
}
else {
2019-08-07 08:59:30 +00:00
// if(getThermostatModeActive())
2019-07-19 20:53:12 +00:00
return RTC_Store . getDesiredTemp ( ) ;
2019-08-07 08:59:30 +00:00
// else
// return RTC_Store.getDesiredPump();
2019-03-14 10:52:59 +00:00
}
}
float getTemperatureSensor ( )
2018-12-16 07:38:34 +00:00
{
2019-07-21 11:17:54 +00:00
return FilteredSamples . AmbientTemp . getValue ( ) + NVstore . getHeaterTuning ( ) . tempOfs ;
2018-12-16 07:38:34 +00:00
}
void setPumpMin ( float val )
{
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
tuning . setPmin ( val ) ;
NVstore . setHeaterTuning ( tuning ) ;
2018-12-16 07:38:34 +00:00
}
void setPumpMax ( float val )
{
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
tuning . setPmax ( val ) ;
NVstore . setHeaterTuning ( tuning ) ;
2018-12-16 07:38:34 +00:00
}
2019-07-06 13:46:20 +00:00
void setFanMin ( uint16_t cVal )
2018-12-16 07:38:34 +00:00
{
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
2019-07-03 13:01:36 +00:00
if ( INBOUNDS ( cVal , 500 , 5000 ) )
tuning . Fmin = cVal ;
2019-06-06 01:32:43 +00:00
NVstore . setHeaterTuning ( tuning ) ;
2018-12-16 07:38:34 +00:00
}
2019-07-06 13:46:20 +00:00
void setFanMax ( uint16_t cVal )
2018-12-16 07:38:34 +00:00
{
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
2019-07-03 13:01:36 +00:00
if ( INBOUNDS ( cVal , 500 , 5000 ) )
tuning . Fmax = cVal ;
2019-06-06 01:32:43 +00:00
NVstore . setHeaterTuning ( tuning ) ;
2018-12-16 07:38:34 +00:00
}
2019-07-06 13:46:20 +00:00
void setFanSensor ( uint8_t cVal )
2019-01-11 05:40:25 +00:00
{
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
2019-07-03 13:01:36 +00:00
if ( INBOUNDS ( cVal , 1 , 2 ) )
tuning . fanSensor = cVal ;
2019-06-06 01:32:43 +00:00
NVstore . setHeaterTuning ( tuning ) ;
2019-01-11 05:40:25 +00:00
}
void setSystemVoltage ( float val ) {
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
tuning . setSysVoltage ( val ) ;
NVstore . setHeaterTuning ( tuning ) ;
2019-01-11 05:40:25 +00:00
}
2019-07-06 13:46:20 +00:00
void setGlowDrive ( uint8_t val ) {
2019-06-06 01:32:43 +00:00
sHeaterTuning tuning = NVstore . getHeaterTuning ( ) ;
2019-07-03 13:01:36 +00:00
if ( INBOUNDS ( val , 1 , 6 ) )
tuning . glowDrive = val ;
2019-06-06 01:32:43 +00:00
NVstore . setHeaterTuning ( tuning ) ;
2019-01-19 11:05:14 +00:00
}
2018-12-16 07:38:34 +00:00
void saveNV ( )
{
NVstore . save ( ) ;
}
const CProtocolPackage & getHeaterInfo ( )
{
2018-12-21 21:48:39 +00:00
return primaryHeaterData ;
2018-12-16 07:38:34 +00:00
}
bool isWebClientConnected ( )
{
return bHaveWebClient ;
}
void checkDebugCommands ( )
{
2019-08-10 11:45:28 +00:00
static uint8_t nGetString = 0 ;
static uint8_t nGetConf = 0 ;
static String pw1 ;
static String pw2 ;
2018-12-16 07:38:34 +00:00
// check for test commands received from PC Over USB
if ( DebugPort . available ( ) ) {
2019-06-26 20:04:24 +00:00
# ifdef PROTOCOL_INVESTIGATION
2018-12-16 07:38:34 +00:00
static int mode = 0 ;
static int val = 0 ;
2019-06-26 20:04:24 +00:00
# endif
2018-12-16 07:38:34 +00:00
char rxVal = DebugPort . read ( ) ;
2019-07-27 02:47:16 +00:00
if ( bTestBTModule ) {
bTestBTModule = Bluetooth . test ( rxVal ) ;
return ;
}
2019-08-31 07:34:56 +00:00
if ( bSetupMQTT ) {
bSetupMQTT = HandleMQTTsetup ( rxVal ) ;
if ( ! bSetupMQTT ) { // left MQTT setup menu
showMainmenu ( ) ;
mqttInit ( ) ;
}
return ;
}
2019-07-27 02:47:16 +00:00
2019-08-10 11:45:28 +00:00
if ( nGetConf ) {
DebugPort . print ( rxVal ) ;
bool bSave = ( rxVal = = ' y ' ) | | ( rxVal = = ' Y ' ) ;
if ( ! bSave ) {
DebugPort . println ( " ABORTED! " ) ;
nGetConf = 0 ;
return ;
}
switch ( nGetConf ) {
case 1 :
setSSID ( PCline . Line ) ;
break ;
case 2 :
setAPpassword ( pw2 . c_str ( ) ) ;
break ;
}
nGetConf = 0 ;
return ;
}
else if ( nGetString ) {
if ( rxVal < ' ' ) {
if ( rxVal = = 0x1b ) { // ESCAPE
nGetString = 0 ;
DebugPort . println ( " \r \n ABORTED! " ) ;
return ;
}
if ( rxVal = = ' \n ' | | rxVal = = ' \r ' ) {
switch ( nGetString ) {
case 1 :
if ( PCline . Len < = 31 ) {
nGetConf = 1 ;
DebugPort . printf ( " \r \n Set AP SSID to %s? (y/n) - " , PCline . Line ) ;
}
else {
DebugPort . println ( " \r \n New name is longer than 31 characters - ABORTING " ) ;
}
nGetString = 0 ;
return ;
case 2 :
pw1 = PCline . Line ;
PCline . clear ( ) ;
pw2 = NVstore . getCredentials ( ) . APpassword ;
if ( pw1 ! = pw2 ) {
DebugPort . println ( " \r \n Password does not match existing - ABORTING " ) ;
nGetString = 0 ;
}
else {
nGetString = 3 ;
DebugPort . print ( " \r \n Please enter new password - " ) ;
}
return ;
case 3 :
pw1 = PCline . Line ;
if ( PCline . Len < = 31 ) {
nGetString = 4 ;
DebugPort . print ( " \r \n Please confirm new password - " ) ;
}
else {
DebugPort . println ( " \r \n New password is longer than 31 characters - ABORTING " ) ;
nGetString = 0 ;
}
PCline . clear ( ) ;
return ;
case 4 :
pw2 = PCline . Line ;
PCline . clear ( ) ;
if ( pw1 ! = pw2 ) {
DebugPort . println ( " \r \n New passwords do not match - ABORTING " ) ;
}
else {
nGetConf = 2 ;
DebugPort . print ( " \r \n Set new password (y/n) - " ) ;
}
nGetString = 0 ;
return ;
}
}
}
else {
if ( nGetString = = 1 )
DebugPort . print ( rxVal ) ;
else
DebugPort . print ( ' * ' ) ;
PCline . append ( rxVal ) ;
return ;
}
}
2018-12-20 04:19:59 +00:00
rxVal = toLowerCase ( rxVal ) ;
# ifdef PROTOCOL_INVESTIGATION
2018-12-16 07:38:34 +00:00
bool bSendVal = false ;
2018-12-20 04:19:59 +00:00
# endif
2019-04-04 10:48:05 +00:00
if ( rxVal = = ' \n ' ) { // "End of Line"
2018-12-20 04:19:59 +00:00
# ifdef PROTOCOL_INVESTIGATION
2018-12-16 07:38:34 +00:00
String convert ( PCline . Line ) ;
val = convert . toInt ( ) ;
bSendVal = true ;
PCline . clear ( ) ;
2018-12-20 04:19:59 +00:00
# endif
2018-12-16 07:38:34 +00:00
}
else {
2018-12-20 04:19:59 +00:00
if ( rxVal = = ' ' ) { // SPACE to bring up menu
2019-08-31 07:34:56 +00:00
showMainmenu ( ) ;
2018-12-20 04:19:59 +00:00
}
# ifdef PROTOCOL_INVESTIGATION
else if ( isDigit ( rxVal ) ) {
2018-12-16 07:38:34 +00:00
PCline . append ( rxVal ) ;
}
2018-12-20 04:19:59 +00:00
else if ( rxVal = = ' p ' ) {
2018-12-16 07:38:34 +00:00
DebugPort . println ( " Test Priming Byte... " ) ;
mode = 1 ;
}
2018-12-20 04:19:59 +00:00
else if ( rxVal = = ' g ' ) {
2018-12-16 07:38:34 +00:00
DebugPort . println ( " Test glow power byte... " ) ;
mode = 2 ;
}
2018-12-20 04:19:59 +00:00
else if ( rxVal = = ' i ' ) {
2018-12-16 07:38:34 +00:00
DebugPort . println ( " Test fan bytes " ) ;
mode = 3 ;
}
2019-04-04 10:48:05 +00:00
else if ( rxVal = = ' c ' ) {
DebugPort . println ( " Test Command Byte... " ) ;
mode = 4 ;
}
else if ( rxVal = = ' x ' ) {
DebugPort . println ( " Special mode cancelled " ) ;
val = 0 ;
mode = 0 ;
DefaultBTCParams . Controller . Command = 0 ;
}
2018-12-16 07:38:34 +00:00
else if ( rxVal = = ' ] ' ) {
val + + ;
bSendVal = true ;
}
else if ( rxVal = = ' [ ' ) {
val - - ;
bSendVal = true ;
}
2018-12-20 04:19:59 +00:00
# endif
else if ( rxVal = = ' b ' ) {
bReportBlueWireData = ! bReportBlueWireData ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Toggled raw blue wire data reporting %s \r \n " , bReportBlueWireData ? " ON " : " OFF " ) ;
2018-12-20 04:19:59 +00:00
}
else if ( rxVal = = ' j ' ) {
bReportJSONData = ! bReportJSONData ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Toggled JSON data reporting %s \r \n " , bReportJSONData ? " ON " : " OFF " ) ;
2018-12-20 07:33:44 +00:00
}
else if ( rxVal = = ' w ' ) {
bReportRecyleEvents = ! bReportRecyleEvents ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Toggled blue wire recycling event reporting %s \r \n " , bReportRecyleEvents ? " ON " : " OFF " ) ;
2018-12-20 04:19:59 +00:00
}
2019-08-10 11:45:28 +00:00
else if ( rxVal = = ' n ' ) {
DebugPort . print ( " Please enter new SSID name for Access Point - " ) ;
nGetString = 1 ;
PCline . clear ( ) ;
}
2019-08-31 07:34:56 +00:00
else if ( rxVal = = ' m ' ) {
MQTTsetup = NVstore . getMQTTinfo ( ) ;
bSetupMQTT = true ;
showMQTTmenu ( ) ;
}
2018-12-20 09:59:53 +00:00
else if ( rxVal = = ' o ' ) {
bReportOEMresync = ! bReportOEMresync ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Toggled OEM resync event reporting %s \r \n " , bReportOEMresync ? " ON " : " OFF " ) ;
2018-12-20 09:59:53 +00:00
}
2019-08-10 11:45:28 +00:00
else if ( rxVal = = ' p ' ) {
DebugPort . print ( " Please enter current AP password - " ) ;
nGetString = 2 ;
PCline . clear ( ) ;
}
2018-12-21 21:48:39 +00:00
else if ( rxVal = = ' s ' ) {
CommState . toggleReporting ( ) ;
}
2018-12-20 04:19:59 +00:00
else if ( rxVal = = ' + ' ) {
TxManage . queueOnRequest ( ) ;
2018-12-21 21:48:39 +00:00
// HeaterData.setRefTime();
2018-12-20 04:19:59 +00:00
}
else if ( rxVal = = ' - ' ) {
TxManage . queueOffRequest ( ) ;
2018-12-21 21:48:39 +00:00
// HeaterData.setRefTime();
2018-12-20 04:19:59 +00:00
}
else if ( rxVal = = ' r ' ) {
ESP . restart ( ) ; // reset the esp
}
2019-07-25 12:31:31 +00:00
else if ( rxVal = = ( ' h ' & 0x1f ) ) { // CTRL-H hourmeter reset
2019-07-26 12:13:46 +00:00
pHourMeter - > resetHard ( ) ;
2019-07-25 12:31:31 +00:00
}
2019-07-27 02:47:16 +00:00
else if ( rxVal = = ( ' b ' & 0x1f ) ) { // CTRL-B Tst Mdoe: bluetooth module route
bTestBTModule = ! bTestBTModule ;
Bluetooth . test ( bTestBTModule ? 0xff : 0x00 ) ; // special enter or leave BT test commands
}
2018-12-16 07:38:34 +00:00
}
2018-12-20 04:19:59 +00:00
# ifdef PROTOCOL_INVESTIGATION
2018-12-16 07:38:34 +00:00
if ( bSendVal ) {
switch ( mode ) {
case 1 :
DefaultBTCParams . Controller . Prime = val & 0xff ; // always 0x32:Thermostat, 0xCD:Fixed
break ;
case 2 :
2019-04-04 10:48:05 +00:00
DefaultBTCParams . Controller . GlowDrive = val & 0xff ; // always 0x05
2018-12-16 07:38:34 +00:00
break ;
case 3 :
DefaultBTCParams . Controller . Unknown2_MSB = ( val > > 8 ) & 0xff ; // always 0x0d
DefaultBTCParams . Controller . Unknown2_LSB = ( val > > 0 ) & 0xff ; // always 0xac 16bit: "3500" ?? Ignition fan max RPM????
break ;
2019-04-04 10:48:05 +00:00
case 4 :
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " Forced controller command = %d \r \n " , val & 0xff ) ;
2019-04-04 10:48:05 +00:00
DefaultBTCParams . Controller . Command = val & 0xff ;
break ;
2018-12-16 07:38:34 +00:00
}
}
2018-12-20 04:19:59 +00:00
# endif
2018-12-16 07:38:34 +00:00
}
}
2018-12-20 22:22:29 +00:00
// 0x00 - Normal: BTC, with heater responding
// 0x01 - Error: BTC, heater not responding
// 0x02 - Special: OEM controller & heater responding
// 0x03 - Error: OEM controller, heater not responding
int getBlueWireStat ( )
{
int stat = 0 ;
if ( ! bHasHtrData ) {
stat | = 0x01 ;
}
if ( bHasOEMController ) {
stat | = 0x02 ;
}
return stat ;
}
2018-12-19 19:13:28 +00:00
2018-12-20 22:22:29 +00:00
const char * getBlueWireStatStr ( )
2018-12-19 19:13:28 +00:00
{
2019-05-12 10:15:18 +00:00
static const char * BlueWireStates [ ] = { " BTC,Htr " , " BTC " , " OEM,Htr " , " OEM " } ;
2018-12-20 22:22:29 +00:00
return BlueWireStates [ getBlueWireStat ( ) ] ;
2018-12-20 06:29:00 +00:00
}
bool hasOEMcontroller ( )
{
return bHasOEMController ;
2018-12-21 10:58:39 +00:00
}
2018-12-22 06:34:10 +00:00
2019-02-27 11:51:02 +00:00
bool hasOEMLCDcontroller ( )
{
return bHasOEMLCDController ;
}
2018-12-22 06:34:10 +00:00
int getSmartError ( )
{
return SmartError . getError ( ) ;
2019-04-04 10:48:05 +00:00
}
bool isCyclicActive ( )
{
2019-07-19 20:53:12 +00:00
return RTC_Store . getCyclicEngaged ( ) & & NVstore . getUserSettings ( ) . cyclic . isEnabled ( ) ;
2019-04-04 10:48:05 +00:00
}
2019-04-09 23:28:46 +00:00
void setupGPIO ( )
{
2019-08-01 12:57:18 +00:00
if ( BoardRevision = = 10 | | BoardRevision = = 20 | | BoardRevision = = 21 ) {
2019-04-12 23:18:07 +00:00
// some special considerations for GPIO inputs, depending upon PCB hardware
2019-04-13 09:05:53 +00:00
// V1.0 PCBs only expose bare inputs, which are pulled high. Active state into ESP32 is LOW.
// V2.0+ PCBs use an input transistor buffer. Active state into ESP32 is HIGH (inverted).
int activePinState = ( BoardRevision = = 10 ) ? LOW : HIGH ;
2019-04-12 23:18:07 +00:00
int Input1 = BoardRevision = = 20 ? GPIOin1_pinV20 : GPIOin1_pinV21V10 ;
2019-08-08 20:13:02 +00:00
GPIOin . begin ( Input1 ,
GPIOin2_pin ,
NVstore . getUserSettings ( ) . GPIO . in1Mode ,
NVstore . getUserSettings ( ) . GPIO . in2Mode ,
activePinState ) ;
2019-04-12 23:18:07 +00:00
// GPIO out is always active high from ESP32
// V1.0 PCBs only expose the bare pins
// V2.0+ PCBs provide an open collector output that conducts when active
2019-08-08 20:13:02 +00:00
GPIOout . begin ( GPIOout1_pin ,
GPIOout2_pin ,
NVstore . getUserSettings ( ) . GPIO . out1Mode ,
NVstore . getUserSettings ( ) . GPIO . out2Mode ) ;
2019-04-12 23:18:07 +00:00
// ### MAJOR ISSUE WITH ADC INPUTS ###
//
// V2.0 PCBs that have not been modified connect the analogue input to GPIO26.
// This is ADC2 channel (#9).
// Unfortunately it was subsequently discovered that any ADC2 input cannot be
// used if Wifi is enabled.
// THIS ISSUE IS NOT RESOLBVABLE IN SOFTWARE.
// *** It is not possible to use ANY of the 10 ADC2 channels if Wifi is enabled :-( ***
//
// Fix is to cut traces to GPIO33 & GPIO26 and swap the connections.
// This directs GPIO input1 into GPIO26 and the analogue input into GPIO33 (ADC1_CHANNEL_5)
// This will be properly fixed in V2.1 PCBs
//
// As V1.0 PCBS expose the bare pins, the correct GPIO33 input can be readily chosen.
2019-08-08 20:13:02 +00:00
CGPIOalg : : Modes algMode = NVstore . getUserSettings ( ) . GPIO . algMode ;
2019-04-12 23:18:07 +00:00
if ( BoardRevision = = 20 )
2019-08-08 20:13:02 +00:00
algMode = CGPIOalg : : Disabled ; // force off analogue support in V2.0 PCBs
2019-04-12 23:18:07 +00:00
GPIOalg . begin ( GPIOalg_pin , algMode ) ;
}
else {
2019-08-01 12:57:18 +00:00
// unknown board or forced no GPIO by grounding pin26 - deny all GPIO operation
2019-08-07 08:59:30 +00:00
// set all pins as inputs with pull ups
pinMode ( GPIOin2_pin , INPUT_PULLUP ) ;
pinMode ( GPIOin1_pinV21V10 , INPUT_PULLUP ) ;
pinMode ( GPIOin1_pinV20 , INPUT_PULLUP ) ;
pinMode ( GPIOout1_pin , INPUT_PULLUP ) ;
pinMode ( GPIOout2_pin , INPUT_PULLUP ) ;
2019-08-08 20:13:02 +00:00
GPIOin . begin ( 0 , 0 , CGPIOin1 : : Disabled , CGPIOin2 : : Disabled , LOW ) ; // ensure modes disabled (should already be by constructors)
GPIOout . begin ( 0 , 0 , CGPIOout1 : : Disabled , CGPIOout2 : : Disabled ) ;
GPIOalg . begin ( ADC1_CHANNEL_5 , CGPIOalg : : Disabled ) ;
2019-04-12 23:18:07 +00:00
}
2019-04-09 23:28:46 +00:00
}
2019-06-02 12:47:35 +00:00
bool toggleGPIOout ( int channel )
{
2019-08-07 12:04:20 +00:00
if ( channel = = 0 ) {
2019-08-08 20:13:02 +00:00
if ( NVstore . getUserSettings ( ) . GPIO . out1Mode = = CGPIOout1 : : User ) {
2019-08-07 12:04:20 +00:00
setGPIOout ( channel , ! getGPIOout ( channel ) ) ; // toggle selected GPIO output
return true ;
}
}
else if ( channel = = 1 ) {
2019-08-08 20:13:02 +00:00
if ( NVstore . getUserSettings ( ) . GPIO . out2Mode = = CGPIOout2 : : User ) {
2019-08-07 12:04:20 +00:00
setGPIOout ( channel , ! getGPIOout ( channel ) ) ; // toggle selected GPIO output
return true ;
}
2019-06-02 12:47:35 +00:00
}
return false ;
}
2019-08-01 12:57:18 +00:00
bool setGPIOout ( int channel , bool state )
2019-04-09 23:28:46 +00:00
{
2019-08-07 12:04:20 +00:00
if ( channel = = 0 ) {
2019-08-08 20:13:02 +00:00
if ( GPIOout . getMode1 ( ) ! = CGPIOout1 : : Disabled ) {
2019-08-07 12:04:20 +00:00
DebugPort . printf ( " setGPIO: Output #%d = %d \r \n " , channel + 1 , state ) ;
GPIOout . setState ( channel , state ) ;
return true ;
}
}
else if ( channel = = 1 ) {
2019-08-08 20:13:02 +00:00
if ( GPIOout . getMode2 ( ) ! = CGPIOout2 : : Disabled ) {
2019-08-07 12:04:20 +00:00
DebugPort . printf ( " setGPIO: Output #%d = %d \r \n " , channel + 1 , state ) ;
GPIOout . setState ( channel , state ) ;
return true ;
}
2019-08-01 12:57:18 +00:00
}
return false ;
2019-04-09 23:28:46 +00:00
}
2019-06-02 12:47:35 +00:00
bool getGPIOout ( int channel )
2019-04-09 23:28:46 +00:00
{
bool retval = GPIOout . getState ( channel ) ;
2019-05-12 10:15:18 +00:00
DebugPort . printf ( " getGPIO: Output #%d = %d \r \n " , channel + 1 , retval ) ;
2019-04-09 23:28:46 +00:00
return retval ;
}
2019-04-19 11:38:39 +00:00
float getVersion ( )
{
2019-04-27 10:41:47 +00:00
return float ( FirmwareRevision ) * 0.1f + float ( FirmwareSubRevision ) * .001f ;
2019-04-19 11:38:39 +00:00
}
2019-04-28 10:23:11 +00:00
const char * getVersionStr ( ) {
static char vStr [ 32 ] ;
sprintf ( vStr , " V%.1f.%d " , float ( FirmwareRevision ) * 0.1f , FirmwareSubRevision ) ;
return vStr ;
}
2019-04-19 11:38:39 +00:00
const char * getVersionDate ( )
{
return FirmwareDate ;
}
int getBoardRevision ( )
{
return BoardRevision ;
2019-04-20 09:13:24 +00:00
}
2019-06-02 20:34:45 +00:00
void ShowOTAScreen ( int percent , eOTAmodes updateType )
2019-05-11 02:18:06 +00:00
{
2019-06-02 20:34:45 +00:00
ScreenManager . showOTAMessage ( percent , updateType ) ;
2019-05-18 08:49:22 +00:00
}
void feedWatchdog ( )
{
2019-06-02 09:19:08 +00:00
uint64_t timeRem = timerRead ( watchdogTimer ) ;
2019-06-26 20:04:24 +00:00
if ( timeRem > 500000 ) // 500ms
2019-07-17 09:35:34 +00:00
DebugPort . printf ( " WD time = %lld \r \n " , timeRem ) ; // print longer WD intervals
2019-06-02 09:19:08 +00:00
2019-05-18 08:49:22 +00:00
timerWrite ( watchdogTimer , 0 ) ; //reset timer (feed watchdog)
2019-08-03 02:42:49 +00:00
timerAlarmWrite ( watchdogTimer , 15000000 , false ) ; //set time in uS must be fed within this time or reboot
2019-05-18 08:49:22 +00:00
}
2019-06-02 09:19:08 +00:00
void doStreaming ( )
{
# if USE_WIFI == 1
doWiFiManager ( ) ;
# if USE_OTA == 1
DoOTA ( ) ;
# endif // USE_OTA
# if USE_WEBSERVER == 1
bHaveWebClient = doWebServer ( ) ;
# endif //USE_WEBSERVER
2019-08-27 13:09:07 +00:00
# if USE_MQTT == 1
2019-09-01 04:43:00 +00:00
// most MQTT is managed via callbacks, but need some sundry housekeeping
doMQTT ( ) ;
2019-08-27 13:09:07 +00:00
# endif
2019-06-02 09:19:08 +00:00
# endif // USE_WIFI
checkDebugCommands ( ) ;
KeyPad . update ( ) ; // scan keypad - key presses handler via callback functions!
Bluetooth . check ( ) ; // check for Bluetooth activity
GPIOin . manage ( ) ;
GPIOout . manage ( ) ;
GPIOalg . manage ( ) ;
// manage changes in Bluetooth connection status
if ( Bluetooth . isConnected ( ) ) {
if ( ! bBTconnected ) {
resetJSONmoderator ( ) ; // force full send upon BT client connect
}
bBTconnected = true ;
}
else {
bBTconnected = false ;
}
// manage changes in number of wifi clients
2019-07-07 07:18:38 +00:00
if ( isWebSocketClientChange ( ) ) {
2019-06-02 09:19:08 +00:00
resetJSONmoderator ( ) ; // force full send upon number of Wifi clients change
}
DebugPort . handle ( ) ; // keep telnet spy alive
2019-06-04 20:15:12 +00:00
}
void getGPIOinfo ( sGPIO & info )
{
info . inState [ 0 ] = GPIOin . getState ( 0 ) ;
info . inState [ 1 ] = GPIOin . getState ( 1 ) ;
info . outState [ 0 ] = GPIOout . getState ( 0 ) ;
info . outState [ 1 ] = GPIOout . getState ( 1 ) ;
info . algVal = GPIOalg . getValue ( ) ;
2019-08-07 12:04:20 +00:00
info . in1Mode = GPIOin . getMode1 ( ) ;
info . in2Mode = GPIOin . getMode2 ( ) ;
info . out1Mode = GPIOout . getMode1 ( ) ;
info . out2Mode = GPIOout . getMode2 ( ) ;
2019-06-04 20:15:12 +00:00
info . algMode = GPIOalg . getMode ( ) ;
}
// hook for JSON input, simulating a GPIO key press
void simulateGPIOin ( uint8_t newKey )
{
GPIOin . simulateKey ( newKey ) ;
2019-07-06 13:46:20 +00:00
}
2019-07-21 11:17:54 +00:00
float getBatteryVoltage ( bool fast )
2019-07-06 13:46:20 +00:00
{
# ifdef RAW_SAMPLES
return getHeaterInfo ( ) . getBattVoltage ( ) ;
# else
2019-07-21 11:17:54 +00:00
if ( fast )
return FilteredSamples . FastipVolts . getValue ( ) ;
else
return FilteredSamples . ipVolts . getValue ( ) ;
2019-07-06 13:46:20 +00:00
# endif
}
float getGlowVolts ( )
{
# ifdef RAW_SAMPLES
return getHeaterInfo ( ) . getGlow_Voltage ( ) ;
# else
return FilteredSamples . GlowVolts . getValue ( ) ;
# endif
}
float getGlowCurrent ( )
{
# ifdef RAW_SAMPLES
return getHeaterInfo ( ) . getGlow_Current ( ) ;
# else
return FilteredSamples . GlowAmps . getValue ( ) ;
# endif
}
float getFanSpeed ( )
{
# ifdef RAW_SAMPLES
return getHeaterInfo ( ) . getFan_Actual ( ) ;
# else
return FilteredSamples . Fan . getValue ( ) ;
# endif
}
void updateFilteredData ( )
{
2019-07-22 10:58:36 +00:00
FilteredSamples . ipVolts . update ( getHeaterInfo ( ) . getBattVoltage ( ) ) ;
FilteredSamples . GlowVolts . update ( getHeaterInfo ( ) . getGlow_Voltage ( ) ) ;
FilteredSamples . GlowAmps . update ( getHeaterInfo ( ) . getGlow_Current ( ) ) ;
FilteredSamples . Fan . update ( getHeaterInfo ( ) . getFan_Actual ( ) ) ;
FilteredSamples . FastipVolts . update ( getHeaterInfo ( ) . getBattVoltage ( ) ) ;
FilteredSamples . FastGlowAmps . update ( getHeaterInfo ( ) . getGlow_Current ( ) ) ;
2019-07-15 09:56:36 +00:00
}
int sysUptime ( )
{
return Clock . get ( ) . secondstime ( ) - BootTime ;
2019-07-22 10:58:36 +00:00
}
2019-08-10 11:45:28 +00:00
void setSSID ( const char * name )
{
sCredentials creds = NVstore . getCredentials ( ) ;
strncpy ( creds . SSID , name , 31 ) ;
creds . SSID [ 31 ] = 0 ;
NVstore . setCredentials ( creds ) ;
NVstore . save ( ) ;
NVstore . doSave ( ) ; // ensure NV storage
DebugPort . println ( " Restarting ESP to invoke new network credentials " ) ;
delay ( 1000 ) ;
ESP . restart ( ) ;
}
void setAPpassword ( const char * name )
{
sCredentials creds = NVstore . getCredentials ( ) ;
strncpy ( creds . APpassword , name , 31 ) ;
creds . APpassword [ 31 ] = 0 ;
NVstore . setCredentials ( creds ) ;
NVstore . save ( ) ;
NVstore . doSave ( ) ; // ensure NV storage
DebugPort . println ( " Restarting ESP to invoke new network credentials " ) ;
delay ( 1000 ) ;
ESP . restart ( ) ;
}
2019-08-31 07:34:56 +00:00
const char * MQTTsetupmodes [ ] = {
" Enter MQTT broker's IP address " ,
" Enter MQTT broker's port " ,
" Enter MQTT broker's username " ,
" Enter MQTT broker's password " ,
" Enter root topic name " ,
" Enter QoS level " ,
" Enable? (Y)es / (N)o "
} ;
bool HandleMQTTsetup ( char rxVal )
{
static int mode = 0 ;
static int idx = 0 ;
static char lclbuffer [ 128 ] ;
switch ( mode ) {
case 0 :
if ( rxVal = = 0x1b ) {
MQTTsetup = NVstore . getMQTTinfo ( ) ;
return false ;
}
// if(rxVal == '\r' || rxVal == '\n') {
if ( rxVal = = ' \n ' ) {
NVstore . setMQTTinfo ( MQTTsetup ) ;
NVstore . save ( ) ;
return false ;
}
if ( rxVal > = ' 1 ' & & rxVal < = ' 7 ' ) {
mode = rxVal - ' 0 ' ;
idx = 0 ;
DebugPort . print ( " \014 " ) ;
DebugPort . print ( MQTTsetupmodes [ mode - 1 ] ) ;
switch ( mode ) {
case 1 : DebugPort . printf ( " (%s) " , MQTTsetup . host ) ; break ;
case 2 : DebugPort . printf ( " (%d) " , MQTTsetup . port ) ; break ;
case 3 : DebugPort . printf ( " (%s) " , MQTTsetup . username ) ; break ;
case 4 : DebugPort . printf ( " (%s) " , MQTTsetup . password ) ; break ;
case 5 : DebugPort . printf ( " (%s) " , MQTTsetup . topic ) ; break ;
case 6 : DebugPort . printf ( " (%d) " , MQTTsetup . qos ) ; break ;
case 7 : DebugPort . printf ( " (%s) " , MQTTsetup . enabled ? " YES " : " NO " ) ; break ;
}
DebugPort . print ( " ... " ) ;
}
else {
showMQTTmenu ( ) ;
}
return true ;
case 1 : // enter MQTT broker IP
if ( rxVal < ' ' ) {
if ( idx = = 0 ) strcpy ( lclbuffer , MQTTsetup . host ) ;
if ( rxVal = = ' \n ' ) {
strncpy ( MQTTsetup . host , lclbuffer , 127 ) ;
MQTTsetup . host [ 127 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
}
if ( rxVal = = 0x1b ) {
mode = 0 ;
showMQTTmenu ( ) ;
}
return true ;
}
if ( idx = = 0 ) memset ( lclbuffer , 0 , sizeof ( lclbuffer ) ) ;
DebugPort . print ( rxVal ) ;
lclbuffer [ idx + + ] = rxVal ;
if ( idx = = 127 ) {
strncpy ( MQTTsetup . host , lclbuffer , 127 ) ;
MQTTsetup . host [ 127 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
break ;
case 2 : // enter MQTT broker port
if ( rxVal < ' ' ) {
if ( idx = = 0 ) sprintf ( lclbuffer , " %d " , MQTTsetup . port ) ;
if ( rxVal = = ' \n ' ) {
int val = atoi ( lclbuffer ) ;
MQTTsetup . port = val ;
mode = 0 ;
showMQTTmenu ( ) ;
}
if ( rxVal = = 0x1b ) {
mode = 0 ;
showMQTTmenu ( ) ;
}
return true ;
}
DebugPort . print ( rxVal ) ;
if ( isdigit ( rxVal ) ) {
if ( idx = = 0 ) memset ( lclbuffer , 0 , sizeof ( lclbuffer ) ) ;
lclbuffer [ idx + + ] = rxVal ;
if ( idx = = 5 ) {
int val = atoi ( lclbuffer ) ;
MQTTsetup . port = val ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
}
else {
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
break ;
case 3 : // enter MQTT broker username
if ( rxVal < ' ' ) {
if ( idx = = 0 ) strcpy ( lclbuffer , MQTTsetup . username ) ;
if ( rxVal = = ' \n ' ) {
strncpy ( MQTTsetup . username , lclbuffer , 31 ) ;
MQTTsetup . username [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
}
if ( rxVal = = 0x1b ) {
mode = 0 ;
showMQTTmenu ( ) ;
}
return true ;
}
if ( idx = = 0 ) memset ( lclbuffer , 0 , sizeof ( lclbuffer ) ) ;
DebugPort . print ( rxVal ) ;
lclbuffer [ idx + + ] = rxVal ;
if ( idx = = 31 ) {
strncpy ( MQTTsetup . username , lclbuffer , 31 ) ;
MQTTsetup . username [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
break ;
case 4 : // enter MQTT broker username
if ( rxVal < ' ' ) {
if ( idx = = 0 ) strcpy ( lclbuffer , MQTTsetup . password ) ;
if ( rxVal = = ' \n ' ) {
strncpy ( MQTTsetup . password , lclbuffer , 31 ) ;
MQTTsetup . password [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
}
if ( rxVal = = 0x1b ) {
mode = 0 ;
showMQTTmenu ( ) ;
}
return true ;
}
if ( idx = = 0 ) memset ( lclbuffer , 0 , sizeof ( lclbuffer ) ) ;
DebugPort . print ( rxVal ) ;
lclbuffer [ idx + + ] = rxVal ;
if ( idx = = 31 ) {
strncpy ( MQTTsetup . password , lclbuffer , 31 ) ;
MQTTsetup . password [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
break ;
case 5 : // enter root topic name
if ( rxVal < ' ' ) {
if ( idx = = 0 ) strcpy ( lclbuffer , MQTTsetup . topic ) ;
if ( rxVal = = ' \n ' ) {
strncpy ( MQTTsetup . topic , lclbuffer , 31 ) ;
MQTTsetup . topic [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
}
if ( rxVal = = 0x1b ) {
mode = 0 ;
showMQTTmenu ( ) ;
}
return true ;
}
if ( idx = = 0 ) memset ( lclbuffer , 0 , sizeof ( lclbuffer ) ) ;
DebugPort . print ( rxVal ) ;
lclbuffer [ idx + + ] = rxVal ;
if ( idx = = 31 ) {
strncpy ( MQTTsetup . topic , lclbuffer , 31 ) ;
MQTTsetup . topic [ 31 ] = 0 ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
break ;
case 6 :
if ( rxVal > = ' 0 ' & & rxVal < = ' 2 ' ) {
MQTTsetup . qos = rxVal - ' 0 ' ;
}
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
case 7 :
if ( tolower ( rxVal ) = = ' y ' )
MQTTsetup . enabled = true ;
if ( tolower ( rxVal ) = = ' n ' )
MQTTsetup . enabled = false ;
mode = 0 ;
showMQTTmenu ( ) ;
return true ;
}
return true ;
}
void showMainmenu ( )
{
DebugPort . print ( " \014 " ) ;
DebugPort . println ( " MENU options " ) ;
DebugPort . println ( " " ) ;
DebugPort . printf ( " <B> - toggle raw blue wire data reporting, currently %s \r \n " , bReportBlueWireData ? " ON " : " OFF " ) ;
DebugPort . printf ( " <J> - toggle output JSON reporting, currently %s \r \n " , bReportJSONData ? " ON " : " OFF " ) ;
DebugPort . printf ( " <W> - toggle reporting of blue wire timeout/recycling event, currently %s \r \n " , bReportRecyleEvents ? " ON " : " OFF " ) ;
DebugPort . printf ( " <O> - toggle reporting of OEM resync event, currently %s \r \n " , bReportOEMresync ? " ON " : " OFF " ) ;
DebugPort . printf ( " <S> - toggle reporting of state machine transits %s \r \n " , CommState . isReporting ( ) ? " ON " : " OFF " ) ;
DebugPort . printf ( " <N> - change AP SSID, currently \" %s \" \r \n " , NVstore . getCredentials ( ) . SSID ) ;
DebugPort . println ( " <P> - change AP password " ) ;
DebugPort . println ( " <M> - configure MQTT " ) ;
DebugPort . println ( " <+> - request heater turns ON " ) ;
DebugPort . println ( " <-> - request heater turns OFF " ) ;
DebugPort . println ( " <R> - restart the ESP " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
DebugPort . println ( " " ) ;
}
void showMQTTmenu ( )
{
DebugPort . print ( " \014 " ) ;
DebugPort . println ( " MQTT broker configuration " ) ;
DebugPort . println ( " " ) ;
DebugPort . printf ( " <1> - set IP address, currently \" %s \" \r \n " , MQTTsetup . host ) ;
DebugPort . printf ( " <2> - set port, currently %d \r \n " , MQTTsetup . port ) ;
DebugPort . printf ( " <3> - set username, currently \" %s \" \r \n " , MQTTsetup . username ) ;
DebugPort . printf ( " <4> - set password, currently \" %s \" \r \n " , MQTTsetup . password ) ;
DebugPort . printf ( " <5> - set root topic, currently \" %s \" \r \n " , MQTTsetup . topic ) ;
DebugPort . printf ( " <6> - set QoS, currently %d \r \n " , MQTTsetup . qos ) ;
DebugPort . printf ( " <7> - set enabled, currently %s \r \n " , MQTTsetup . enabled ? " ON " : " OFF " ) ;
DebugPort . printf ( " <ENTER> - save and exit \r \n " ) ;
DebugPort . printf ( " <ESC> - abort \r \n " ) ;
}