This commit is contained in:
Ray Jones 2019-07-20 06:53:12 +10:00
parent a543ba0748
commit 04ae988d2d
19 changed files with 349 additions and 343 deletions

View file

@ -183,10 +183,14 @@ bool bHasHtrData = false;
// these variables will persist over a soft reboot. // these variables will persist over a soft reboot.
__NOINIT_ATTR bool bForceInit; // = false; __NOINIT_ATTR bool bForceInit; // = false;
__NOINIT_ATTR bool bUserON; // = false; //__NOINIT_ATTR bool bUserON; // = false;
__NOINIT_ATTR uint8_t demandDegC; //__NOINIT_ATTR uint8_t demandDegC;
__NOINIT_ATTR uint8_t demandPump; //__NOINIT_ATTR uint8_t demandPump;
//uint8_t demandDegC;
//uint8_t demandPump;
//bool bCyclicEngaged;
CFuelGauge FuelGauge; CFuelGauge FuelGauge;
CRTC_Store RTC_Store;
bool bReportBlueWireData = REPORT_RAW_DATA; bool bReportBlueWireData = REPORT_RAW_DATA;
bool bReportJSONData = REPORT_JSON_TRANSMIT; bool bReportJSONData = REPORT_JSON_TRANSMIT;
@ -305,13 +309,11 @@ extern "C" unsigned long __wrap_millis() {
void setup() { void setup() {
// ensure cyclic mode is disabled after power on // ensure cyclic mode is disabled after power on
bool bPowerUpInit = false; // bool bPowerUpInit = false;
if(rtc_get_reset_reason(0) == 1/* || bForceInit*/) { // if(rtc_get_reset_reason(0) == 1/* || bForceInit*/) {
bPowerUpInit = true; // bPowerUpInit = true;
bForceInit = false; // bForceInit = false;
bUserON = false; // }
demandPump = demandDegC = 22;
}
// initially, ensure the GPIO outputs are not activated during startup // initially, ensure the GPIO outputs are not activated during startup
// (GPIO2 tends to be one with default chip startup) // (GPIO2 tends to be one with default chip startup)
@ -334,7 +336,7 @@ void setup() {
DebugPort.println("_______________________________________________________________"); DebugPort.println("_______________________________________________________________");
DebugPort.printf("Reset reason: core0:%d, core1:%d\r\n", rtc_get_reset_reason(0), rtc_get_reset_reason(0)); DebugPort.printf("Reset reason: core0:%d, core1:%d\r\n", rtc_get_reset_reason(0), rtc_get_reset_reason(0));
DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-) // DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-)
// initialise DS18B20 sensor interface // initialise DS18B20 sensor interface
TempSensor.begin(DS18B20_Pin); TempSensor.begin(DS18B20_Pin);
@ -383,6 +385,9 @@ void setup() {
BootTime = Clock.get().secondstime(); BootTime = Clock.get().secondstime();
ScreenManager.begin(bNoClock); ScreenManager.begin(bNoClock);
if(!bNoClock && Clock.lostPower()) {
ScreenManager.selectMenu(CScreenManager::BranchMenu, CScreenManager::SetClockUI);
}
#if USE_WIFI == 1 #if USE_WIFI == 1
@ -442,9 +447,12 @@ void setup() {
FilteredSamples.Fan.setAlpha(0.7); FilteredSamples.Fan.setAlpha(0.7);
FilteredSamples.AmbientTemp.reset(-100.0); FilteredSamples.AmbientTemp.reset(-100.0);
// if(bPowerUpInit) { RTC_Store.begin();
FuelGauge.init(); 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 :-)
delay(1000); // just to hold the splash screeen for while delay(1000); // just to hold the splash screeen for while
} }
@ -812,7 +820,7 @@ void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr)
void manageCyclicMode() void manageCyclicMode()
{ {
const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic; const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic;
if(cyclic.Stop && bUserON) { // cyclic mode enabled, and user has started heater if(cyclic.Stop && RTC_Store.getCyclicEngaged()) { // 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! int stopDeltaT = cyclic.Stop + 1; // bump up by 1 degree - no point invoking at 1 deg over!
float deltaT = FilteredSamples.AmbientTemp.getValue() - getDemandDegC(); float deltaT = FilteredSamples.AmbientTemp.getValue() - getDemandDegC();
// DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT); // DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT);
@ -876,13 +884,13 @@ bool validateFrame(const CProtocol& frame, const char* name)
void requestOn() void requestOn()
{ {
heaterOn(); heaterOn();
bUserON = true; // for cyclic mode RTC_Store.setCyclicEngaged(true); // for cyclic mode
} }
void requestOff() void requestOff()
{ {
heaterOff(); heaterOff();
bUserON = false; // for cyclic mode RTC_Store.setCyclicEngaged(false); // for cyclic mode
} }
void heaterOn() void heaterOn()
@ -898,38 +906,42 @@ void heaterOff()
} }
bool reqTemp(uint8_t newTemp, bool save) bool reqDemand(uint8_t newDemand, bool save)
{ {
if(bHasOEMController) if(bHasOEMController)
return false; return false;
uint8_t max = DefaultBTCParams.getTemperature_Max(); uint8_t max = DefaultBTCParams.getTemperature_Max();
uint8_t min = DefaultBTCParams.getTemperature_Min(); uint8_t min = DefaultBTCParams.getTemperature_Min();
if(newTemp >= max) if(newDemand >= max)
newTemp = max; newDemand = max;
if(newTemp <= min) if(newDemand <= min)
newTemp = min; newDemand = min;
// set and save the demand to NV storage // set and save the demand to NV storage
// note that we now maintain fixed Hz and Thermostat set points seperately // note that we now maintain fixed Hz and Thermostat set points seperately
if(getThermostatModeActive()) if(getThermostatModeActive()) {
demandDegC = newTemp; RTC_Store.setDesiredTemp(newDemand);
else }
demandPump = newTemp; else {
RTC_Store.setDesiredPump(newDemand);
}
ScreenManager.reqUpdate(); ScreenManager.reqUpdate();
return true; return true;
} }
bool reqTempDelta(int delta) bool reqDemandDelta(int delta)
{ {
uint8_t newTemp; uint8_t newDemand;
if(getThermostatModeActive()) if(getThermostatModeActive()) {
newTemp = demandDegC + delta; newDemand = RTC_Store.getDesiredTemp() + delta;
else }
newTemp = demandPump + delta; else {
newDemand = RTC_Store.getDesiredPump() + delta;
}
return reqTemp(newTemp); return reqDemand(newDemand);
} }
bool reqThermoToggle() bool reqThermoToggle()
@ -997,7 +1009,7 @@ void forceBootInit()
uint8_t getDemandDegC() uint8_t getDemandDegC()
{ {
return demandDegC; return RTC_Store.getDesiredTemp();
} }
void setDemandDegC(uint8_t val) void setDemandDegC(uint8_t val)
@ -1005,12 +1017,12 @@ void setDemandDegC(uint8_t val)
uint8_t max = DefaultBTCParams.getTemperature_Max(); uint8_t max = DefaultBTCParams.getTemperature_Max();
uint8_t min = DefaultBTCParams.getTemperature_Min(); uint8_t min = DefaultBTCParams.getTemperature_Min();
BOUNDSLIMIT(val, min, max); BOUNDSLIMIT(val, min, max);
demandDegC = val; RTC_Store.setDesiredTemp(val);
} }
uint8_t getDemandPump() uint8_t getDemandPump()
{ {
return demandPump; return RTC_Store.getDesiredPump();
} }
@ -1021,9 +1033,9 @@ float getTemperatureDesired()
} }
else { else {
if(getThermostatModeActive()) if(getThermostatModeActive())
return demandDegC; return RTC_Store.getDesiredTemp();
else else
return demandPump; return RTC_Store.getDesiredPump();
} }
} }
@ -1273,7 +1285,7 @@ int getSmartError()
bool isCyclicActive() bool isCyclicActive()
{ {
return bUserON && NVstore.getUserSettings().cyclic.isEnabled(); return RTC_Store.getCyclicEngaged() && NVstore.getUserSettings().cyclic.isEnabled();
} }
void setupGPIO() void setupGPIO()

View file

@ -1,53 +0,0 @@
Hi there,
I've been somewhat overwhelmed with requests and actually getting units built.
At the moment all PCBs I have built for the cased variety are depleted, but I will be building more PCBs over the coming days.
I have the parts, but they lack a touch of solder in the right places :-)
Please be aware this is part time affair after a day's work and currently this is consuming all my spare time!
An important question for the sale of the early units is does the prospect of reflashing firmware concern you?
There are still some firmware aspects I'm working on, and the web pages still need a fair bit of work. I would hate for units to become stale with old out dated firmware!!
This requires the Arduino environment to be set up for an ESP32, and in cases of dire emergency a USB to serial adapter.
Over The Air programming using WiFi is also available via batch file and a pre-compiled binary which makes this a lot easier for the firmware, but web content presently must be via the Arduino environment.
I have two possibilities:
1/ the controller as you can currently view in the GitLab repo.
This would be supplied as is, without a case, but I have produced a 3D model you can print.
GitLab repo: https://gitlab.com/mrjones.id.au/bluetoothheater/wikis/home
2/ a newer version that is housed in a case.
It also provides a few conditioned I/O expansion lines:
2 digital inputs, 2 digital outputs, 1 analogue input.
The software is still yet to be fully dealt with for this aspect, but you can presently start and stop the heater, and turn outputs on or off upon command.
Pricing:
Option 1: 50AUD + post and handling
Option 2: 75AUD + post and handling
For domestic customers in Australia, P&H is 10AUD
For international customers P&H tends to be around 30-36AUD depending upon which region you reside.
Payment is accepted via PayPal.
Also please confirm your present controller is a digital style.
Please refer to my wiki at the GitLab repo: https://gitlab.com/mrjones.id.au/bluetoothheater/wikis/home
Another criteria here is what style connector you presently have.
Most have a triangular 3 pin plug, but newer units are coming with a smaller round 3 pin connector.
Please advise.
Option 2 with the case, has a range of colours available.
Black or White case.
Black, Grey, Blue or Green buttons.
Case for Option 1 https://www.thingiverse.com/thing:3398068
Rest assured the pricing is purely for the hardware.
The software will always be open source and freely available for upgrades and bug fixes.
And as mentioned, you can expect extra features to appear as time goes on.
An example of things in the todo list are Low Voltage shutdown, and a "de-coke burn" which will be full power, but a touch leaner than usual.
I'm also yet to explore a 433MHz Receive option using the garage style remote controls.
Thanks again for your interest
Cheers, Ray

Binary file not shown.

Binary file not shown.

View file

@ -1,23 +0,0 @@
To:
Luke Girard,
3494 Cedar Hill Road,
Victoria,
British Columbia,
V8P 3Z1,
CANADA
From:
Ray Jones,
31 Wiggins Place,
Wallan,
Victoria, 3756,
AUSTRALIA

View file

@ -1 +0,0 @@
C:\Users\ray\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.1/tools/espota.exe -i 192.168.20.40 -p 3232 --auth= -f C:\Users\ray\AppData\Local\Temp\arduino_build_241840/BTCDieselHeater.ino.bin

View file

@ -1 +0,0 @@
C:\Users\ray\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\2.6.0/esptool.exe --chip esp32 --port COM11 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 C:\Users\ray\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.1/tools/partitions/boot_app0.bin 0x1000 C:\Users\ray\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.1/tools/sdk/bin/bootloader_qio_80m.bin 0x10000 C:\Users\ray\AppData\Local\Temp\arduino_build_241840/BTCDieselHeater.ino.bin 0x8000 C:\Users\ray\AppData\Local\Temp\arduino_build_241840/BTCDieselHeater.ino.partitions.bin

View file

@ -1,28 +0,0 @@
Thank you ??? for your interest.
Yes I do have some units ready to sell.
I have two possibilities:
1/ the controller as you can currently view in the GitLab repo.
This would be supplied as is without a case.
2/ a newer version that will be housed in a case.
It will also provide a few conditioned I/O expansion lines:
2 digital inputs, 2 digital outputs, 1 analogue input.
The software is still yet to be fully dealt with for this aspect.
Pricing:
Option 1: 50AUD + post and handling
Option 2: 65AUD + post and handling
Post and handling to ??? would be ???AUD
An important question for the sale of the early units is does the prospect of reflashing firmware concern you?
As you can see there are still some aspects I'm working on, and the web pages need a fair bit of work still.
This requires the Arduino environment to be set up for an ESP32, and a USB to serial adapter.
Over The Air programming using WiFi is also available which makes this a lot easier.
Rest assured the pricing is purely for the hardware.
The software will always be open source and freely available for upgrades and bug fixes.
Thanks again for your interest
Cheers, Ray

View file

@ -1,55 +0,0 @@
Hi there,
An important question for the sale of the early units is does the prospect of re-flashing firmware concern you?
There are still some firmware aspects I'm working on, and the web pages do still need a fair bit of work.
I would hate for units to become stale with old out dated firmware!!
In extreme cases this may require the Arduino environment to be set up for an ESP32, and a USB to serial adapter.
The most recent firmware though now has the ability to be readily updated via web browser, or indeed directly from my web server if connected to a wifi network when new firmware is released (only upon user authorisation).
At an intermediate level, Over The Air programming using WiFi is also available via batch file and a pre-compiled binary which is also quite straight forward.
Web page content can be updated via the web browser interface, or the Arduino environment.
For the unit itself I have two possibilities:
1/ the Mk1 controller as you can currently view in the GitLab repo: https://gitlab.com/mrjones.id.au/bluetoothheater/wikis/home
This would be supplied as is, without a case, but I have produced a 3D model you can print: https://www.thingiverse.com/thing:3398068
2/ a newer Mk2 version that is housed in a case.
It also provides a few conditioned I/O expansion lines:
2 digital inputs, 2 digital outputs, 1 analogue input.
The software is still yet to be fully dealt with for this aspect, but you can presently start and stop the heater, and turn outputs on or off upon command.
Please visit my webpage http://www.mrjones.id.au/afterburner for examples of the unit in a case, and the full capabilities of the unit.
There you can also find the user manual.
Pricing:
Option 1: 50AUD + post and handling
Option 2: 75AUD + post and handling
For domestic customers in Australia, P&H is 10AUD
For international customers P&H tends to be around 30-36AUD depending upon which region you reside.
Payment is accepted via PayPal.
Also please confirm your present controller is a digital style.
Please refer to my wiki at the GitLab repo: https://gitlab.com/mrjones.id.au/bluetoothheater/wikis/home or the user manual from my website.
Another important criteria here is what style connector you presently have.
Most have a triangular 3 pin plug, but newer units are coming with a smaller round 3 pin connector.
Please advise, i will supply the appropriate style connector with the unit.
Option 2 with the case, has a range of colours available.
Black or White case.
Black, Grey, Blue or Green buttons.
Please note there is typically a week delay as I do this in my "spare time" and demand can be high at times.
Rest assured the pricing is purely for the hardware and my time to build.
The software will always be open source and freely available for upgrades and bug fixes.
And as mentioned, you can expect extra features to appear as time goes on.
An example of things in the todo list are Low Voltage shutdown, and a "de-coke burn" which will be full power, but a touch leaner than usual.
I'm also yet to explore a 433MHz Receive option using the garage style remote controls.
Thanks again for your interest
Cheers, Ray

View file

@ -183,10 +183,14 @@ bool bHasHtrData = false;
// these variables will persist over a soft reboot. // these variables will persist over a soft reboot.
__NOINIT_ATTR bool bForceInit; // = false; __NOINIT_ATTR bool bForceInit; // = false;
__NOINIT_ATTR bool bUserON; // = false; //__NOINIT_ATTR bool bUserON; // = false;
__NOINIT_ATTR uint8_t demandDegC; //__NOINIT_ATTR uint8_t demandDegC;
__NOINIT_ATTR uint8_t demandPump; //__NOINIT_ATTR uint8_t demandPump;
//uint8_t demandDegC;
//uint8_t demandPump;
//bool bCyclicEngaged;
CFuelGauge FuelGauge; CFuelGauge FuelGauge;
CRTC_Store RTC_Store;
bool bReportBlueWireData = REPORT_RAW_DATA; bool bReportBlueWireData = REPORT_RAW_DATA;
bool bReportJSONData = REPORT_JSON_TRANSMIT; bool bReportJSONData = REPORT_JSON_TRANSMIT;
@ -305,13 +309,11 @@ extern "C" unsigned long __wrap_millis() {
void setup() { void setup() {
// ensure cyclic mode is disabled after power on // ensure cyclic mode is disabled after power on
bool bPowerUpInit = false; // bool bPowerUpInit = false;
if(rtc_get_reset_reason(0) == 1/* || bForceInit*/) { // if(rtc_get_reset_reason(0) == 1/* || bForceInit*/) {
bPowerUpInit = true; // bPowerUpInit = true;
bForceInit = false; // bForceInit = false;
bUserON = false; // }
demandPump = demandDegC = 22;
}
// initially, ensure the GPIO outputs are not activated during startup // initially, ensure the GPIO outputs are not activated during startup
// (GPIO2 tends to be one with default chip startup) // (GPIO2 tends to be one with default chip startup)
@ -334,7 +336,7 @@ void setup() {
DebugPort.println("_______________________________________________________________"); DebugPort.println("_______________________________________________________________");
DebugPort.printf("Reset reason: core0:%d, core1:%d\r\n", rtc_get_reset_reason(0), rtc_get_reset_reason(0)); DebugPort.printf("Reset reason: core0:%d, core1:%d\r\n", rtc_get_reset_reason(0), rtc_get_reset_reason(0));
DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-) // DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-)
// initialise DS18B20 sensor interface // initialise DS18B20 sensor interface
TempSensor.begin(DS18B20_Pin); TempSensor.begin(DS18B20_Pin);
@ -383,6 +385,9 @@ void setup() {
BootTime = Clock.get().secondstime(); BootTime = Clock.get().secondstime();
ScreenManager.begin(bNoClock); ScreenManager.begin(bNoClock);
if(!bNoClock && Clock.lostPower()) {
ScreenManager.selectMenu(CScreenManager::BranchMenu, CScreenManager::SetClockUI);
}
#if USE_WIFI == 1 #if USE_WIFI == 1
@ -442,9 +447,12 @@ void setup() {
FilteredSamples.Fan.setAlpha(0.7); FilteredSamples.Fan.setAlpha(0.7);
FilteredSamples.AmbientTemp.reset(-100.0); FilteredSamples.AmbientTemp.reset(-100.0);
// if(bPowerUpInit) { RTC_Store.begin();
FuelGauge.init(); 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 :-)
delay(1000); // just to hold the splash screeen for while delay(1000); // just to hold the splash screeen for while
} }
@ -812,7 +820,7 @@ void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr)
void manageCyclicMode() void manageCyclicMode()
{ {
const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic; const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic;
if(cyclic.Stop && bUserON) { // cyclic mode enabled, and user has started heater if(cyclic.Stop && RTC_Store.getCyclicEngaged()) { // 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! int stopDeltaT = cyclic.Stop + 1; // bump up by 1 degree - no point invoking at 1 deg over!
float deltaT = FilteredSamples.AmbientTemp.getValue() - getDemandDegC(); float deltaT = FilteredSamples.AmbientTemp.getValue() - getDemandDegC();
// DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT); // DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT);
@ -876,13 +884,13 @@ bool validateFrame(const CProtocol& frame, const char* name)
void requestOn() void requestOn()
{ {
heaterOn(); heaterOn();
bUserON = true; // for cyclic mode RTC_Store.setCyclicEngaged(true); // for cyclic mode
} }
void requestOff() void requestOff()
{ {
heaterOff(); heaterOff();
bUserON = false; // for cyclic mode RTC_Store.setCyclicEngaged(false); // for cyclic mode
} }
void heaterOn() void heaterOn()
@ -898,38 +906,42 @@ void heaterOff()
} }
bool reqTemp(uint8_t newTemp, bool save) bool reqDemand(uint8_t newDemand, bool save)
{ {
if(bHasOEMController) if(bHasOEMController)
return false; return false;
uint8_t max = DefaultBTCParams.getTemperature_Max(); uint8_t max = DefaultBTCParams.getTemperature_Max();
uint8_t min = DefaultBTCParams.getTemperature_Min(); uint8_t min = DefaultBTCParams.getTemperature_Min();
if(newTemp >= max) if(newDemand >= max)
newTemp = max; newDemand = max;
if(newTemp <= min) if(newDemand <= min)
newTemp = min; newDemand = min;
// set and save the demand to NV storage // set and save the demand to NV storage
// note that we now maintain fixed Hz and Thermostat set points seperately // note that we now maintain fixed Hz and Thermostat set points seperately
if(getThermostatModeActive()) if(getThermostatModeActive()) {
demandDegC = newTemp; RTC_Store.setDesiredTemp(newDemand);
else }
demandPump = newTemp; else {
RTC_Store.setDesiredPump(newDemand);
}
ScreenManager.reqUpdate(); ScreenManager.reqUpdate();
return true; return true;
} }
bool reqTempDelta(int delta) bool reqDemandDelta(int delta)
{ {
uint8_t newTemp; uint8_t newDemand;
if(getThermostatModeActive()) if(getThermostatModeActive()) {
newTemp = demandDegC + delta; newDemand = RTC_Store.getDesiredTemp() + delta;
else }
newTemp = demandPump + delta; else {
newDemand = RTC_Store.getDesiredPump() + delta;
}
return reqTemp(newTemp); return reqDemand(newDemand);
} }
bool reqThermoToggle() bool reqThermoToggle()
@ -997,7 +1009,7 @@ void forceBootInit()
uint8_t getDemandDegC() uint8_t getDemandDegC()
{ {
return demandDegC; return RTC_Store.getDesiredTemp();
} }
void setDemandDegC(uint8_t val) void setDemandDegC(uint8_t val)
@ -1005,12 +1017,12 @@ void setDemandDegC(uint8_t val)
uint8_t max = DefaultBTCParams.getTemperature_Max(); uint8_t max = DefaultBTCParams.getTemperature_Max();
uint8_t min = DefaultBTCParams.getTemperature_Min(); uint8_t min = DefaultBTCParams.getTemperature_Min();
BOUNDSLIMIT(val, min, max); BOUNDSLIMIT(val, min, max);
demandDegC = val; RTC_Store.setDesiredTemp(val);
} }
uint8_t getDemandPump() uint8_t getDemandPump()
{ {
return demandPump; return RTC_Store.getDesiredPump();
} }
@ -1021,9 +1033,9 @@ float getTemperatureDesired()
} }
else { else {
if(getThermostatModeActive()) if(getThermostatModeActive())
return demandDegC; return RTC_Store.getDesiredTemp();
else else
return demandPump; return RTC_Store.getDesiredPump();
} }
} }
@ -1273,7 +1285,7 @@ int getSmartError()
bool isCyclicActive() bool isCyclicActive()
{ {
return bUserON && NVstore.getUserSettings().cyclic.isEnabled(); return RTC_Store.getCyclicEngaged() && NVstore.getUserSettings().cyclic.isEnabled();
} }
void setupGPIO() void setupGPIO()

View file

@ -231,7 +231,7 @@ CBasicScreen::keyHandler(uint8_t event)
if(!_showModeTime) { if(!_showModeTime) {
// release DOWN key to reduce set demand, provided we are not in mode select // release DOWN key to reduce set demand, provided we are not in mode select
if(event & key_Down) { if(event & key_Down) {
if(reqTempDelta(-1)) { if(reqDemandDelta(-1)) {
_showSetModeTime = millis() + 2000; _showSetModeTime = millis() + 2000;
_feedbackType = 0; _feedbackType = 0;
_ScreenManager.reqUpdate(); _ScreenManager.reqUpdate();
@ -241,7 +241,7 @@ CBasicScreen::keyHandler(uint8_t event)
} }
// release UP key to increase set demand, provided we are not in mode select // release UP key to increase set demand, provided we are not in mode select
if(event & key_Up) { if(event & key_Up) {
if(reqTempDelta(+1)) { if(reqDemandDelta(+1)) {
_showSetModeTime = millis() + 2000; _showSetModeTime = millis() + 2000;
_feedbackType = 0; _feedbackType = 0;
_ScreenManager.reqUpdate(); _ScreenManager.reqUpdate();

View file

@ -251,11 +251,11 @@ CDetailedScreen::keyHandler(uint8_t event)
if(event & keyReleased) { if(event & keyReleased) {
if(_keyRepeatCount == 0) { // short Up press - lower target if(_keyRepeatCount == 0) { // short Up press - lower target
if(event & key_Up) { if(event & key_Up) {
if(reqTempDelta(+1)) _showTarget = millis() + 3500; if(reqDemandDelta(+1)) _showTarget = millis() + 3500;
else _reqOEMWarning(); else _reqOEMWarning();
} }
if(event & key_Down) { // short Down press - lower target if(event & key_Down) { // short Down press - lower target
if(reqTempDelta(-1)) _showTarget = millis() + 3500; if(reqDemandDelta(-1)) _showTarget = millis() + 3500;
else _reqOEMWarning(); else _reqOEMWarning();
} }
if(event & key_Centre) { // short Centre press - show target if(event & key_Centre) { // short Centre press - show target

View file

@ -385,7 +385,7 @@ CScreenManager::checkUpdate()
if(runState > 0 && prevRunState == 0) { if(runState > 0 && prevRunState == 0) {
// heater has started // heater has started
uint8_t userStartMenu = NVstore.getUserSettings().HomeMenu.onStart; uint8_t userStartMenu = NVstore.getUserSettings().HomeMenu.onStart;
if(userStartMenu && userStartMenu <= 3) { // allow user to override defualt screen if(userStartMenu && userStartMenu <= 3) { // allow user to override default screen
userStartMenu--; userStartMenu--;
DebugPort.print("Screen Manager: Heater start detected, switching to user preferred screen: "); DebugPort.print("Screen Manager: Heater start detected, switching to user preferred screen: ");
switch(userStartMenu) { switch(userStartMenu) {
@ -397,10 +397,10 @@ CScreenManager::checkUpdate()
_enterScreen(); _enterScreen();
} }
} }
if(runState == 0 && prevRunState != 0) { if(runState == 0 && prevRunState > 0) {
// heater has stopped // heater has stopped
uint8_t userStopMenu = NVstore.getUserSettings().HomeMenu.onStop; uint8_t userStopMenu = NVstore.getUserSettings().HomeMenu.onStop;
if(userStopMenu && userStopMenu <= 3) { // allow user to override defualt screen if(userStopMenu && userStopMenu <= 3) { // allow user to override default screen
userStopMenu--; userStopMenu--;
DebugPort.print("Screen Manager: Heater stop detected, switching to user preferred screen: "); DebugPort.print("Screen Manager: Heater stop detected, switching to user preferred screen: ");
switch(userStopMenu) { switch(userStopMenu) {
@ -544,6 +544,7 @@ CScreenManager::selectMenu(eUIMenuSets menuSet, int specific)
// targetting a specific menu // targetting a specific menu
_subMenu = specific; _subMenu = specific;
UPPERLIMIT(_subMenu, _Screens[_menu].size()-1); // check bounds! UPPERLIMIT(_subMenu, _Screens[_menu].size()-1); // check bounds!
DebugPort.printf("selectMenu %d %d\r\n", _menu, _subMenu);
} }
else { else {
// default sub menu behaviour // default sub menu behaviour
@ -574,7 +575,7 @@ CScreenManager::showOTAMessage(int percent, eOTAmodes updateType)
static long prevTime = millis(); static long prevTime = millis();
long tDelta = millis() - prevTime; long tDelta = millis() - prevTime;
if(percent != prevPercent && tDelta > 500) { if(percent != prevPercent/* && tDelta > 500*/) {
prevTime = millis(); prevTime = millis();
_pDisplay->clearDisplay(); _pDisplay->clearDisplay();
_pDisplay->setCursor(64,22); _pDisplay->setCursor(64,22);

View file

@ -117,6 +117,17 @@ CClock::readData(uint8_t* pData, int len, int ofs)
_rtc.readData(pData, len, ofs); _rtc.readData(pData, len, ofs);
} }
bool
CClock::lostPower()
{
return _rtc.lostPower();
}
void
CClock::resetLostPower()
{
_rtc.resetLostPower();
}
void setDateTime(const char* newTime) void setDateTime(const char* newTime)
{ {
@ -153,7 +164,8 @@ void setTime(const char* newTime)
#define _I2C_WRITE write #define _I2C_WRITE write
#define _I2C_READ read #define _I2C_READ read
void RTC_DS3231Ex::writeData(uint8_t* pData, int len, int ofs) { void
RTC_DS3231Ex::writeData(uint8_t* pData, int len, int ofs) {
Wire.beginTransmission(DS3231_ADDRESS); Wire.beginTransmission(DS3231_ADDRESS);
Wire._I2C_WRITE((byte)(7+ofs)); // start at alarm bytes Wire._I2C_WRITE((byte)(7+ofs)); // start at alarm bytes
for(int i=0; i<len; i++) { for(int i=0; i<len; i++) {
@ -162,7 +174,8 @@ void RTC_DS3231Ex::writeData(uint8_t* pData, int len, int ofs) {
Wire.endTransmission(); Wire.endTransmission();
} }
void RTC_DS3231Ex::readData(uint8_t* pData, int len, int ofs) { void
RTC_DS3231Ex::readData(uint8_t* pData, int len, int ofs) {
Wire.beginTransmission(DS3231_ADDRESS); Wire.beginTransmission(DS3231_ADDRESS);
Wire._I2C_WRITE((byte)(7+ofs)); // start at alarm bytes Wire._I2C_WRITE((byte)(7+ofs)); // start at alarm bytes
Wire.endTransmission(); Wire.endTransmission();
@ -174,32 +187,171 @@ void RTC_DS3231Ex::readData(uint8_t* pData, int len, int ofs) {
Wire.endTransmission(); Wire.endTransmission();
} }
void storeFuelGauge(float val) bool
RTC_DS3231Ex::resetLostPower()
{ {
Wire.beginTransmission(DS3231_ADDRESS);
Wire._I2C_WRITE(DS3231_STATUSREG);
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDRESS, 1);
uint8_t sts = Wire._I2C_READ();
sts &= 0x7f; // clear power loss flag
Wire.beginTransmission(DS3231_ADDRESS);
Wire._I2C_WRITE(DS3231_STATUSREG);
Wire._I2C_WRITE(sts);
Wire.endTransmission();
}
// RTC storage, using alarm registers as GP storage
// MAXIMUM OF 7 BYTES
//
// [0..3] float fuelGauge strokes
// [4] uint8_t DesiredTemp (typ. 8-35)
// [5] uint8_t DesiredPump (typ. 8-35)
// [6] uint8_t spare
//
// ____________________________________________________
// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
// |---------------|------|-----------------------------|
// Byte[4]: | CyclicEngaged | bit6 | Desired Deg Celcius |
// |---------------|------|-----------------------------|
// Byte[5]: | | | Desired Pump Speed |
// ----------------------------------------------------
CRTC_Store::CRTC_Store()
{
_accessed[0] = false;
_accessed[1] = false;
_accessed[2] = false;
_accessed[3] = false;
_fuelgauge = 0;
_demandDegC = 22;
_demandPump = 22;
_CyclicEngaged = false;
}
void
CRTC_Store::begin()
{
if(Clock.lostPower()) {
// RTC lost power - reset internal NV values to defaults
DebugPort.println("CRTC_Store::begin() RTC lost power, re-initialising NV aspect");
_demandPump = _demandDegC = 22;
_CyclicEngaged = false;
setFuelGauge(0);
setDesiredTemp(_demandDegC);
setDesiredPump(_demandPump);
Clock.resetLostPower();
}
getFuelGauge();
getDesiredTemp();
getDesiredPump();
}
void
CRTC_Store::setFuelGauge(float val)
{
_accessed[0] = true;
_fuelgauge = val;
Clock.saveData((uint8_t*)&val, 4, 0); Clock.saveData((uint8_t*)&val, 4, 0);
} }
void getStoredFuelGauge(float& val) float
CRTC_Store::getFuelGauge()
{ {
Clock.readData((uint8_t*)&val, 4, 0); if(!_accessed[0]) {
float NVval;
Clock.readData((uint8_t*)&NVval, 4, 0);
_fuelgauge = NVval;
_accessed[0] = true;
DebugPort.printf("RTC_Store - read fuel gauge %.2f\r\n", _fuelgauge);
}
return _fuelgauge;
} }
void storeDesiredTemp(uint8_t val) void
CRTC_Store::setDesiredTemp(uint8_t val)
{ {
Clock.saveData((uint8_t*)&val, 1, 4); _demandDegC = val;
_PackAndSaveByte4();
} }
void getStoredDesiredTemp(uint8_t& val) uint8_t
CRTC_Store::getDesiredTemp()
{ {
Clock.readData((uint8_t*)&val, 1, 4); _ReadAndUnpackByte4();
return _demandDegC;
} }
void storeDesiredPump(uint8_t val) bool
CRTC_Store::getCyclicEngaged()
{ {
Clock.saveData((uint8_t*)&val, 1, 5); _ReadAndUnpackByte4();
return _CyclicEngaged;
} }
void getStoredDesiredPump(uint8_t& val) void
CRTC_Store::setCyclicEngaged(bool active)
{ {
Clock.readData((uint8_t*)&val, 1, 5); _CyclicEngaged = active;
_PackAndSaveByte4();
}
void
CRTC_Store::setDesiredPump(uint8_t val)
{
_demandPump = val;
_PackAndSaveByte5();
}
uint8_t
CRTC_Store::getDesiredPump()
{
_ReadAndUnpackByte5();
return _demandPump;
}
void
CRTC_Store::_ReadAndUnpackByte4()
{
if(!_accessed[1]) {
uint8_t NVval = 0;
Clock.readData((uint8_t*)&NVval, 1, 4);
_demandDegC = NVval & 0x3f;
_CyclicEngaged = (NVval & 0x80) != 0;
_bit6 = (NVval & 0x40) != 0;
_accessed[1] = true;
DebugPort.printf("RTC_Store - read byte4: degC=%d, CyclicOn=%d, bit6=%d\r\n", _demandDegC, _CyclicEngaged, _bit6);
}
}
void
CRTC_Store::_PackAndSaveByte4()
{
uint8_t NVval = (_CyclicEngaged ? 0x80 : 0x00)
| (_bit6 ? 0x40 : 0x00)
| (_demandDegC & 0x3f);
Clock.saveData((uint8_t*)&NVval, 1, 4);
}
void
CRTC_Store::_ReadAndUnpackByte5()
{
if(!_accessed[2]) {
uint8_t NVval = 0;
Clock.readData((uint8_t*)&NVval, 1, 5);
_demandPump = NVval & 0x3f;
_accessed[2] = true;
DebugPort.printf("RTC_Store - read byte5: pump=%d\r\n", _demandPump);
}
}
void
CRTC_Store::_PackAndSaveByte5()
{
uint8_t NVval = (_demandPump & 0x3f);
Clock.saveData((uint8_t*)&NVval, 1, 5);
} }

View file

@ -30,6 +30,7 @@ class RTC_DS3231Ex : public RTC_DS3231 {
public: public:
void writeData(uint8_t* pData, int len, int ofs=0); void writeData(uint8_t* pData, int len, int ofs=0);
void readData(uint8_t* pData, int len, int ofs=0); void readData(uint8_t* pData, int len, int ofs=0);
bool resetLostPower();
}; };
@ -68,8 +69,35 @@ public:
void set(const DateTime& newTime); void set(const DateTime& newTime);
void saveData(uint8_t* pData, int len, int ofs); void saveData(uint8_t* pData, int len, int ofs);
void readData(uint8_t* pData, int len, int ofs); void readData(uint8_t* pData, int len, int ofs);
bool lostPower();
void resetLostPower();
};
class CRTC_Store {
bool _accessed[4]; // [0] - bytes 0..3, [1] byte 4, [2] byte 5, [3] byte 6
float _fuelgauge;
uint8_t _demandDegC;
uint8_t _demandPump;
bool _CyclicEngaged;
bool _bit6;
void _ReadAndUnpackByte4();
void _PackAndSaveByte4();
void _ReadAndUnpackByte5();
void _PackAndSaveByte5();
public:
CRTC_Store();
void begin();
void setFuelGauge(float val);
void setDesiredTemp(uint8_t val);
void setDesiredPump(uint8_t val);
void setCyclicEngaged(bool _CyclicEngaged);
float getFuelGauge();
uint8_t getDesiredTemp();
uint8_t getDesiredPump();
bool getCyclicEngaged();
}; };
extern CClock Clock; extern CClock Clock;
extern CRTC_Store RTC_Store;
#endif // __BTC_TIMERS_H__ #endif // __BTC_TIMERS_H__

View file

@ -73,7 +73,7 @@ void interpretJsonCommand(char* pLine)
for(it = obj.begin(); it != obj.end(); ++it) { for(it = obj.begin(); it != obj.end(); ++it) {
if(strcmp("TempDesired", it->key) == 0) { if(strcmp("TempDesired", it->key) == 0) {
if( !reqTemp(it->value.as<uint8_t>(), false) ) { // this request is blocked if OEM controller active if( !reqDemand(it->value.as<uint8_t>(), false) ) { // this request is blocked if OEM controller active
JSONmoderator.reset("TempDesired"); JSONmoderator.reset("TempDesired");
} }
} }
@ -265,6 +265,21 @@ void interpretJsonCommand(char* pLine)
NVstore.setUserSettings(us); NVstore.setUserSettings(us);
NVstore.save(); NVstore.save();
} }
else if(strcmp("PumpCount", it->key) == 0) { // reset fuel gauge
int Count = it->value.as<int>();
if(Count == 0) {
RTC_Store.setFuelGauge(0);
}
}
else if(strcmp("PumpCal", it->key) == 0) {
float fCal = it->value.as<float>();
if(INBOUNDS(fCal, 0.001, 1)) {
sHeaterTuning ht = NVstore.getHeaterTuning();
ht.pumpCal = fCal;
NVstore.setHeaterTuning(ht);
NVstore.save();
}
}
} }
} }
@ -340,6 +355,8 @@ bool makeJSONStringEx(CModerator& moderator, char* opStr, int len)
bSend |= moderator.addJson("CyclicTemp", getDemandDegC(), root); // actual pivot point for cyclic mode 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("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("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
if(bSend) { if(bSend) {
root.printTo(opStr, len); root.printTo(opStr, len);

View file

@ -19,78 +19,28 @@
* *
*/ */
//
// We need to identify the PCB the firmware is running upon for 2 reasons related to GPIO functions
//
// 1: Digital Inputs
// To the outside world, the digital inputs are always treated as contact closures to ground.
// V1.0 PCBs expose the bare ESP inputs for GPIO, they are normally pulled HIGH.
// V2.0+ PCBs use an input conditioning transistor that inverts the sense state.
// Inactive state for V1.0 is HIGH
// Inactive state for V2.0+ is LOW
//
// 2: Analogue input
// Unfortunately the pin originally chosen for the analogue input on the V2.0 PCB goes to
// an ADC2 channel of the ESP32.
// It turns out NONE of the 10 ADC2 channels can be used if Wifi is enabled!
// The remedy on V2.0 PCBs is to cut the traces leading from Digital input 1 and the Analogue input.
// The signals are then tranposed.
// This then presents Digital Input #1 to GPIO26, and analogue to GPIO33.
// As GPIO33 uses an ADC1 channel no issue is present reading analogue values with wifi enabled.
//
// Board Detection
// Fortunately due to the use of the digital input transistors on V2.0+ PCBs, a logical
// determination of the board configuration can be made.
// By setting the pins as digital inputs with pull ups enabled, the logic level presented
// can be read and thus the input signal paths can be determined.
// Due to the input conditioning transistors, V2.0 PCBs will hold the inputs to the ESP32
// LOW when inactive, V1.0 PCBs will pull HIGH.
// Likewise, the analogue input is left to float, so it will always be pulled HIGH.
// NOTE: a 100nF capacitor exists on the analogue input so a delay is required to ensure
// a reliable read.
//
// Input state truth table
// GPIO26 GPIO33
// ------ ------
// V1.0 HIGH HIGH
// unmodified V2.0 HIGH LOW
// modified V2.0 LOW HIGH
// V2.1 LOW HIGH
//
//
// ****************************************************************************************
// This test only needs to be performed upon the very first firmware execution.
// Once the board has been identified, the result is saved to non volatile memory
// If a valid value is detected, the test is bypassed.
// This avoids future issues should the GPIO inputs be legitimately connected to
// extension hardware that may distort the test results when the system is repowered.
// ****************************************************************************************
//
#include "FuelGauge.h" #include "FuelGauge.h"
#include "NVStorage.h" #include "NVStorage.h"
#include "DebugPort.h" #include "DebugPort.h"
#include "../RTC/Clock.h"
CFuelGauge::CFuelGauge() CFuelGauge::CFuelGauge()
{ {
_tank_mL = 0; _pumpStrokes = 0;
_pumpCal = 0.02; _pumpCal = 0.02;
record.lastsave = millis(); _lastStoredVal = _pumpStrokes;
record.storedval = _tank_mL;
DebugPort.println("CFuelGauge::CFuelGauge"); DebugPort.println("CFuelGauge::CFuelGauge");
} }
void void
CFuelGauge::init() CFuelGauge::init(float fuelUsed)
{ {
_pumpCal = NVstore.getHeaterTuning().pumpCal; _pumpCal = NVstore.getHeaterTuning().pumpCal;
float testVal;
getStoredFuelGauge(testVal); // RTC registers used to store this _pumpStrokes = fuelUsed;
if(INBOUNDS(testVal, 0, 200000)) { DebugPort.printf("Initialising fuel gauge with %.2f strokes\r\n", _pumpStrokes);
DebugPort.printf("Initialising fuel gauge with %.2fmL\r\n", testVal); _lastStoredVal = _pumpStrokes;
_tank_mL = testVal;
record.storedval = _tank_mL;
}
} }
@ -101,16 +51,14 @@ CFuelGauge::Integrate(float Hz)
long tSample = timenow - _lasttime; long tSample = timenow - _lasttime;
_lasttime = timenow; _lasttime = timenow;
_tank_mL += Hz * tSample * 0.001 * _pumpCal; // Hz * seconds * mL / stroke _pumpStrokes += Hz * tSample * 0.001; // Hz * seconds
long tDiff = millis() - record.lastsave; float fuelDelta = _pumpStrokes - _lastStoredVal;
float fuelDelta = _tank_mL - record.storedval; bool bStoppedSave = (Hz == 0) && (_pumpStrokes != _lastStoredVal);
bool bStoppedSave = (Hz == 0) && (_tank_mL != record.storedval); if(fuelDelta > 10 || bStoppedSave) { // record fuel usage every 10 minutes, or every 10 strokes
if(tDiff > 600000 || fuelDelta > 1 || bStoppedSave) { // record fuel usage every 10 minutes, or every 5mL used DebugPort.printf("Storing fuel gauge: %.2f strokes\r\n", _pumpStrokes);
DebugPort.printf("Storing fuel gauge: %.2fmL\r\n", _tank_mL); RTC_Store.setFuelGauge(_pumpStrokes); // uses RTC registers to store this
storeFuelGauge(_tank_mL); // uses RTC registers to store this _lastStoredVal = _pumpStrokes;
record.lastsave = millis();
record.storedval = _tank_mL;
} }
} }
@ -118,5 +66,5 @@ CFuelGauge::Integrate(float Hz)
float float
CFuelGauge::Used_mL() CFuelGauge::Used_mL()
{ {
return _tank_mL; return _pumpStrokes * _pumpCal; // strokes * mL / stroke
} }

View file

@ -25,16 +25,13 @@
#include <stdint.h> #include <stdint.h>
class CFuelGauge { class CFuelGauge {
float _tank_mL; float _pumpStrokes;
unsigned long _lasttime; unsigned long _lasttime;
float _pumpCal; float _pumpCal;
struct { float _lastStoredVal;
unsigned long lastsave;
float storedval;
} record;
public: public:
CFuelGauge(); CFuelGauge();
void init(); void init(float fuelUsed = 0);
void Integrate(float Hz); void Integrate(float Hz);
float Used_mL(); float Used_mL();
}; };

View file

@ -31,8 +31,8 @@ extern void forceBootInit();
extern void requestOn(); extern void requestOn();
extern void requestOff(); extern void requestOff();
extern bool reqTempDelta(int delta); extern bool reqDemandDelta(int delta);
extern bool reqTemp(uint8_t newTemp, bool save=true); extern bool reqDemand(uint8_t newTemp, bool save=true);
extern bool reqThermoToggle(); extern bool reqThermoToggle();
extern bool setThermostatMode(uint8_t); extern bool setThermostatMode(uint8_t);
extern bool getThermostatModeActive(); // OEM: actual mode from blue wire, BTC: or our NV extern bool getThermostatModeActive(); // OEM: actual mode from blue wire, BTC: or our NV
@ -84,12 +84,12 @@ extern float getGlowCurrent();
extern float getFanSpeed(); extern float getFanSpeed();
extern int sysUptime(); extern int sysUptime();
extern void storeFuelGauge(float val); /* extern void setFuelGauge_RTC(float val);
extern void getStoredFuelGauge(float& val); extern void getFuelGauge_RTC(float& val);
extern void storeDesiredTemp(uint8_t val); extern void setDesiredTemp_RTC(uint8_t val);
extern void getStoredDesiredTemp(uint8_t& val); extern void getDesiredTemp_RTC(uint8_t& val);
extern void storeDesiredPump(uint8_t val); extern void setDesiredPump_RTC(uint8_t val);
extern void getStoredDesiredPump(uint8_t& val); extern void getDesiredPump_RTC(uint8_t& val);*/
extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal); extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal);