mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 18:06:37 +01:00
Compare commits
8 Commits
ccca784ac2
...
64950c3974
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64950c3974 | ||
|
|
fb2fbc85a4 | ||
|
|
6b92a5e69c | ||
|
|
ef4546a2e6 | ||
|
|
6870c9b8a4 | ||
|
|
a70d976a6e | ||
|
|
fbba6ffff2 | ||
|
|
d516c82041 |
@@ -49,8 +49,10 @@ void sensorTask(void *param){
|
|||||||
|
|
||||||
// Init sensor stuff
|
// Init sensor stuff
|
||||||
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
||||||
|
bool iRTC_ready = false; // Software RTC initialized and ready to use
|
||||||
bool RTC_ready = false; // DS1388 initialized and ready to use
|
bool RTC_ready = false; // DS1388 initialized and ready to use
|
||||||
bool GPS_ready = false; // GPS initialized and ready to use
|
bool GPS_ready = false; // GPS initialized and ready to use
|
||||||
|
bool N2K_GPS_ready = false; // GPS time on N2K bus
|
||||||
bool BME280_ready = false; // BME280 initialized and ready to use
|
bool BME280_ready = false; // BME280 initialized and ready to use
|
||||||
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
||||||
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
||||||
@@ -382,6 +384,7 @@ void sensorTask(void *param){
|
|||||||
if (getLocalTime(&timeinfo)) {
|
if (getLocalTime(&timeinfo)) {
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||||
rtc.setTimeStruct(timeinfo);
|
rtc.setTimeStruct(timeinfo);
|
||||||
|
iRTC_ready = true;
|
||||||
sensors.rtcValid = true;
|
sensors.rtcValid = true;
|
||||||
} else {
|
} else {
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
||||||
@@ -400,7 +403,7 @@ void sensorTask(void *param){
|
|||||||
if (millis() > starttime0 + 100)
|
if (millis() > starttime0 + 100)
|
||||||
{
|
{
|
||||||
starttime0 = millis();
|
starttime0 = millis();
|
||||||
// Send NMEA0183 GPS data on several bus systems all 100ms
|
// Send NMEA0183 GPS data on several bus systems (N2K an 0183) all 100ms
|
||||||
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
||||||
{
|
{
|
||||||
SNMEA0183Msg NMEA0183Msg;
|
SNMEA0183Msg NMEA0183Msg;
|
||||||
@@ -412,9 +415,55 @@ void sensorTask(void *param){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If RTC DS1388 ready, then copy GPS data to RTC all 5min
|
/*
|
||||||
|
Time set logic for RTC and N2K
|
||||||
|
###############################
|
||||||
|
|
||||||
|
iRTC = Software RTC updatetd with NTP via internet
|
||||||
|
RTC = RTC chip on PCB
|
||||||
|
GPS = GPS Receiver on PCB
|
||||||
|
N2K = GPS time on N2K od 183 bus
|
||||||
|
0 = device not ready
|
||||||
|
1 = device ready
|
||||||
|
X = undependend
|
||||||
|
() = source for set time N2K
|
||||||
|
-> = set RTC via iRTC
|
||||||
|
<- = set RTC via GPS
|
||||||
|
|
||||||
|
iRTC RTC GPS N2K
|
||||||
|
0 0 0 (1)
|
||||||
|
0 0 (1) (X)
|
||||||
|
0 (1) 0 (X)
|
||||||
|
0 1 <-(1) (X)
|
||||||
|
(1) 0 0 (X)
|
||||||
|
1 0 (1) (X)
|
||||||
|
1 ->(1) 0 (X)
|
||||||
|
1 1 <-(1) (X)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 5min
|
||||||
if(millis() > starttime11 + 5*60*1000){
|
if(millis() > starttime11 + 5*60*1000){
|
||||||
starttime11 = millis();
|
starttime11 = millis();
|
||||||
|
// Set RTC chip via iRTC (NTP)
|
||||||
|
if(iRTC_ready == true && RTC_ready == true && GPS_ready == false){
|
||||||
|
GwApi::Status status;
|
||||||
|
api->getStatus(status);
|
||||||
|
// Check WiFi connection
|
||||||
|
if (status.wifiClientConnected) {
|
||||||
|
sensors.rtcTime = rtc.getTimeStruct(); // Get time from software RTC (iRTC)
|
||||||
|
DateTime now = DateTime(
|
||||||
|
sensors.rtcTime.tm_year + 1900,
|
||||||
|
sensors.rtcTime.tm_mon + 1,
|
||||||
|
sensors.rtcTime.tm_mday,
|
||||||
|
sensors.rtcTime.tm_hour,
|
||||||
|
sensors.rtcTime.tm_min,
|
||||||
|
sensors.rtcTime.tm_sec
|
||||||
|
);
|
||||||
|
ds1388.adjust(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set RTC chip via internal GPS
|
||||||
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
||||||
api->getBoatDataValues(3,valueList);
|
api->getBoatDataValues(3,valueList);
|
||||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||||
@@ -422,40 +471,33 @@ void sensorTask(void *param){
|
|||||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||||
DateTime adjusttime(ts);
|
DateTime adjusttime(ts);
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via internal GPS: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||||
// Adjust RTC time as unix time value
|
// Adjust RTC time as unix time value
|
||||||
ds1388.adjust(adjusttime);
|
ds1388.adjust(adjusttime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send 1Wire data for all temperature sensors all 2s
|
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
||||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
if(N2K_GPS_ready == false && RTC_ready == true && GPS_ready == false){
|
||||||
starttime13 = millis();
|
api->getBoatDataValues(3,valueList);
|
||||||
float tempC;
|
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
long ts = tNMEA0183Msg::daysToTime_t(gpsdays->value - (30*365+7))+floor(gpsseconds->value); // Adjusted to reference year 2000 (-30 years and 7 days for switch years)
|
||||||
for(int i=0;i<numberOfDevices; i++){
|
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||||
// Send only one 1Wire data per loop step (time reduction)
|
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||||
if(i == loopCounter % numberOfDevices){
|
DateTime adjusttime(ts);
|
||||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via N2K/183: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||||
// Read temperature value in Celsius
|
// Adjust RTC time as unix time value
|
||||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
ds1388.adjust(adjusttime);
|
||||||
}
|
// N2K GPS time ready
|
||||||
// Send to NMEA200 bus for each sensor with instance number
|
N2K_GPS_ready = true;
|
||||||
if(!isnan(tempC)){
|
}
|
||||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
|
||||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
|
||||||
api->sendN2kMessage(N2kMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loopCounter++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current RTC date and time all 500ms
|
// Send RTC date and time to N2K all 500ms
|
||||||
if (millis() > starttime12 + 500) {
|
if (millis() > starttime12 + 500) {
|
||||||
starttime12 = millis();
|
starttime12 = millis();
|
||||||
|
// Send date and time from RTC chip
|
||||||
if (rtcOn == "DS1388" && RTC_ready) {
|
if (rtcOn == "DS1388" && RTC_ready) {
|
||||||
DateTime dt = ds1388.now();
|
DateTime dt = ds1388.now();
|
||||||
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
||||||
@@ -481,21 +523,62 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
// N2K sysTime is double in n2klib
|
// N2K sysTime is double in n2klib
|
||||||
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
||||||
// WHY? isnan should always fail here
|
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||||
//if(!isnan(daysAt1970) && !isnan(sysTime)){
|
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||||
api->sendN2kMessage(N2kMsg);
|
api->sendN2kMessage(N2kMsg);
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send date and time from software RTC (iRTC)
|
||||||
|
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||||
|
// Use internal RTC feature
|
||||||
|
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
|
||||||
|
// TODO implement daysAt1970 and sysTime as methods of DateTime
|
||||||
|
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||||
|
uint16_t switchYear = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400;
|
||||||
|
long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1;
|
||||||
|
// If switch year then add one day
|
||||||
|
if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) {
|
||||||
|
daysAt1970 += 1;
|
||||||
|
}
|
||||||
|
// N2K sysTime is double in n2klib
|
||||||
|
double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + sensors.rtcTime.tm_sec;
|
||||||
|
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||||
|
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
|
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||||
|
api->sendN2kMessage(N2kMsg);
|
||||||
}
|
}
|
||||||
} else if (sensors.rtcValid) {
|
|
||||||
// use internal rtc feature
|
|
||||||
sensors.rtcTime = rtc.getTimeStruct();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send supply voltage value all 1s
|
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||||
|
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||||
|
starttime13 = millis();
|
||||||
|
float tempC;
|
||||||
|
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||||
|
for(int i=0;i<numberOfDevices; i++){
|
||||||
|
// Send only one 1Wire data per loop step (time reduction)
|
||||||
|
if(i == loopCounter % numberOfDevices){
|
||||||
|
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||||
|
// Read temperature value in Celsius
|
||||||
|
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||||
|
}
|
||||||
|
// Send to NMEA200 bus for each sensor with instance number
|
||||||
|
if(!isnan(tempC)){
|
||||||
|
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||||
|
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||||
|
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||||
|
api->sendN2kMessage(N2kMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loopCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send supply voltage value to N2K all 1s
|
||||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||||
starttime5 = millis();
|
starttime5 = millis();
|
||||||
float rawVoltage = 0; // Default value
|
float rawVoltage = 0; // Default value
|
||||||
@@ -565,7 +648,7 @@ void sensorTask(void *param){
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data from environment sensor all 2s
|
// Send data from environment sensor to N2K all 2s
|
||||||
if(millis() > starttime6 + 2000){
|
if(millis() > starttime6 + 2000){
|
||||||
starttime6 = millis();
|
starttime6 = millis();
|
||||||
unsigned char TempSource = 2; // Inside temperature
|
unsigned char TempSource = 2; // Inside temperature
|
||||||
@@ -630,7 +713,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send rotation angle all 500ms
|
// Send rotation angle to N2K all 500ms
|
||||||
if(millis() > starttime7 + 500){
|
if(millis() > starttime7 + 500){
|
||||||
starttime7 = millis();
|
starttime7 = millis();
|
||||||
double rotationAngle=0;
|
double rotationAngle=0;
|
||||||
@@ -678,7 +761,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send battery power value all 1s
|
// Send battery power value to N2K all 1s
|
||||||
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
||||||
starttime8 = millis();
|
starttime8 = millis();
|
||||||
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
||||||
@@ -720,7 +803,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send solar power value all 1s
|
// Send solar power value to N2K all 1s
|
||||||
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
||||||
starttime9 = millis();
|
starttime9 = millis();
|
||||||
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
||||||
@@ -750,7 +833,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send generator power value all 1s
|
// Send generator power value to N2K all 1s
|
||||||
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
||||||
starttime10 = millis();
|
starttime10 = millis();
|
||||||
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
467
lib/obp60task/PageClock.old
Normal file
467
lib/obp60task/PageClock.old
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO mode: race timer: keys
|
||||||
|
* - prepare: set countdown to 5min
|
||||||
|
* reset: abort current countdown and start over with 5min preparation
|
||||||
|
* - 5min: key press
|
||||||
|
* - 4min: key press to sync
|
||||||
|
* - 1min: buzzer signal
|
||||||
|
* - start: buzzer signal for start
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PageClock : public Page
|
||||||
|
{
|
||||||
|
bool simulation = false;
|
||||||
|
int simtime;
|
||||||
|
bool keylock = false;
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
char source = 'G'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer
|
||||||
|
char tz = 'L'; // time zone (L)ocal | (U)TC
|
||||||
|
double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75
|
||||||
|
double homelat;
|
||||||
|
double homelon;
|
||||||
|
bool homevalid = false; // homelat and homelon are valid
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageClock(CommonData &common){
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG,"Instantiate PageClock");
|
||||||
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
|
timezone = common.config->getString(common.config->timeZone).toDouble();
|
||||||
|
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||||
|
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||||
|
homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||||
|
simtime = 38160; // time value 11:36
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setupKeys(){
|
||||||
|
Page::setupKeys();
|
||||||
|
commonData->keydata[0].label = "SRC";
|
||||||
|
commonData->keydata[1].label = "MODE";
|
||||||
|
commonData->keydata[4].label = "TZ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key functions
|
||||||
|
virtual int handleKey(int key){
|
||||||
|
// Time source
|
||||||
|
if (key == 1) {
|
||||||
|
switch (source) {
|
||||||
|
case 'G': source = 'R'; break;
|
||||||
|
case 'R': source = 'G'; break;
|
||||||
|
default: source = 'G'; break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (key == 2) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'A': mode = 'D'; break;
|
||||||
|
case 'D': mode = 'T'; break;
|
||||||
|
case 'T': mode = 'A'; break;
|
||||||
|
default: mode = 'A'; break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Time zone: Local / UTC
|
||||||
|
if (key == 5) {
|
||||||
|
switch (tz) {
|
||||||
|
case 'L': tz = 'U'; break;
|
||||||
|
case 'U': tz = 'L'; break;
|
||||||
|
default: tz = 'L'; break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keylock function
|
||||||
|
if(key == 11){ // Code for keylock
|
||||||
|
keylock = !keylock; // Toggle keylock
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData &pageData)
|
||||||
|
{
|
||||||
|
GwConfigHandler *config = commonData->config;
|
||||||
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
|
static String svalue1old = "";
|
||||||
|
static String unit1old = "";
|
||||||
|
static String svalue2old = "";
|
||||||
|
static String unit2old = "";
|
||||||
|
static String svalue3old = "";
|
||||||
|
static String unit3old = "";
|
||||||
|
|
||||||
|
static String svalue5old = "";
|
||||||
|
static String svalue6old = "";
|
||||||
|
|
||||||
|
double value1 = 0;
|
||||||
|
double value2 = 0;
|
||||||
|
double value3 = 0;
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
|
String dateformat = config->getString(config->dateFormat);
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
|
// Get boat values for GPS time
|
||||||
|
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||||
|
String name1 = bvalue1->getName().c_str(); // Value name
|
||||||
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
|
if(simulation == false){
|
||||||
|
value1 = bvalue1->value; // Value as double in SI unit
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||||
|
} // Other simulation data see OBP60Formatter.cpp
|
||||||
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
if(valid1 == true){
|
||||||
|
svalue1old = svalue1; // Save old value
|
||||||
|
unit1old = unit1; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for GPS date
|
||||||
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name2 = bvalue2->getName().c_str(); // Value name
|
||||||
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
|
value2 = bvalue2->value; // Value as double in SI unit
|
||||||
|
bool valid2 = bvalue2->valid; // Valid information
|
||||||
|
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
if(valid2 == true){
|
||||||
|
svalue2old = svalue2; // Save old value
|
||||||
|
unit2old = unit2; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for HDOP date
|
||||||
|
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue)
|
||||||
|
String name3 = bvalue3->getName().c_str(); // Value name
|
||||||
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
|
value3 = bvalue3->value; // Value as double in SI unit
|
||||||
|
bool valid3 = bvalue3->valid; // Valid information
|
||||||
|
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||||
|
if(valid3 == true){
|
||||||
|
svalue3old = svalue3; // Save old value
|
||||||
|
unit3old = unit3; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if(String(flashLED) == "Limit Violation"){
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging boat values
|
||||||
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm *local_tm = localtime(&tv);
|
||||||
|
|
||||||
|
// Show values GPS date
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 65);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
// GPS value
|
||||||
|
getdisplay().print(svalue2);
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
// RTC value
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalue2old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 95);
|
||||||
|
getdisplay().print("Date"); // Name
|
||||||
|
|
||||||
|
// Horizintal separator left
|
||||||
|
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values GPS time
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 250);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print(svalue1); // Value
|
||||||
|
}
|
||||||
|
else if (commonData->data.rtcValid) {
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getdisplay().print(svalue1old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 220);
|
||||||
|
getdisplay().print("Time"); // Name
|
||||||
|
|
||||||
|
// Show values sunrise
|
||||||
|
String sunrise = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
|
||||||
|
svalue5old = sunrise;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunrise = String("06:42");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 65);
|
||||||
|
if(holdvalues == false) getdisplay().print(sunrise); // Value
|
||||||
|
else getdisplay().print(svalue5old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 95);
|
||||||
|
getdisplay().print("SunR"); // Name
|
||||||
|
|
||||||
|
// Horizintal separator right
|
||||||
|
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values sunset
|
||||||
|
String sunset = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
|
||||||
|
svalue6old = sunset;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunset = String("21:03");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 250);
|
||||||
|
if(holdvalues == false) getdisplay().print(sunset); // Value
|
||||||
|
else getdisplay().print(svalue6old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 220);
|
||||||
|
getdisplay().print("SunS"); // Name
|
||||||
|
|
||||||
|
//*******************************************************************************************
|
||||||
|
|
||||||
|
// Draw clock
|
||||||
|
int rInstrument = 110; // Radius of clock
|
||||||
|
float pi = 3.141592;
|
||||||
|
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
||||||
|
|
||||||
|
for(int i=0; i<360; i=i+1)
|
||||||
|
{
|
||||||
|
// Scaling values
|
||||||
|
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots
|
||||||
|
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
||||||
|
const char *ii = "";
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: ii="12"; break;
|
||||||
|
case 30 : ii=""; break;
|
||||||
|
case 60 : ii=""; break;
|
||||||
|
case 90 : ii="3"; break;
|
||||||
|
case 120 : ii=""; break;
|
||||||
|
case 150 : ii=""; break;
|
||||||
|
case 180 : ii="6"; break;
|
||||||
|
case 210 : ii=""; break;
|
||||||
|
case 240 : ii=""; break;
|
||||||
|
case 270 : ii="9"; break;
|
||||||
|
case 300 : ii=""; break;
|
||||||
|
case 330 : ii=""; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print text centered on position x, y
|
||||||
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
|
uint16_t w, h; // Return values of getTextBounds
|
||||||
|
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
|
||||||
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
|
if(i % 30 == 0){
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().print(ii);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with dots
|
||||||
|
float sinx = 0;
|
||||||
|
float cosx = 0;
|
||||||
|
if(i % 6 == 0){
|
||||||
|
float x1c = 200 + rInstrument*sin(i/180.0*pi);
|
||||||
|
float y1c = 150 - rInstrument*cos(i/180.0*pi);
|
||||||
|
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
||||||
|
sinx=sin(i/180.0*pi);
|
||||||
|
cosx=cos(i/180.0*pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with lines (two triangles)
|
||||||
|
if(i % 30 == 0){
|
||||||
|
float dx=2; // Line thickness = 2*dx+1
|
||||||
|
float xx1 = -dx;
|
||||||
|
float xx2 = +dx;
|
||||||
|
float yy1 = -(rInstrument-10);
|
||||||
|
float yy2 = -(rInstrument+10);
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||||
|
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||||
|
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),commonData->fgcolor);
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||||
|
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),
|
||||||
|
200+(int)(cosx*xx2-sinx*yy2),150+(int)(sinx*xx2+cosx*yy2),commonData->fgcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print Unit in clock
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(175, 110);
|
||||||
|
if(holdvalues == false){
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock values
|
||||||
|
double hour = 0;
|
||||||
|
double minute = 0;
|
||||||
|
if (source == 'R') {
|
||||||
|
if (tz == 'L') {
|
||||||
|
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm *local_tm = localtime(&tv);
|
||||||
|
minute = local_tm->tm_min;
|
||||||
|
hour = local_tm->tm_hour;
|
||||||
|
} else {
|
||||||
|
minute = commonData->data.rtcTime.tm_min;
|
||||||
|
hour = commonData->data.rtcTime.tm_hour;
|
||||||
|
}
|
||||||
|
hour += minute / 60;
|
||||||
|
} else {
|
||||||
|
if (tz == 'L') {
|
||||||
|
value1 += timezone * 3600;
|
||||||
|
}
|
||||||
|
if (value1 > 86400) {value1 -= 86400;}
|
||||||
|
if (value1 < 0) {value1 += 86400;}
|
||||||
|
hour = (value1 / 3600.0);
|
||||||
|
// minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving
|
||||||
|
minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute
|
||||||
|
}
|
||||||
|
if (hour > 12) {
|
||||||
|
hour -= 12.0;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute);
|
||||||
|
|
||||||
|
// Draw hour pointer
|
||||||
|
float startwidth = 8; // Start width of pointer
|
||||||
|
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){
|
||||||
|
float sinx=sin(hour * 30.0 * pi / 180); // Hour
|
||||||
|
float cosx=cos(hour * 30.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument * 0.5);
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||||
|
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||||
|
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument * 0.5);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1),
|
||||||
|
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1),
|
||||||
|
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw minute pointer
|
||||||
|
startwidth = 8; // Start width of pointer
|
||||||
|
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){
|
||||||
|
float sinx=sin(minute * 6.0 * pi / 180); // Minute
|
||||||
|
float cosx=cos(minute * 6.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument - 15);
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||||
|
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||||
|
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument - 15);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1),
|
||||||
|
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1),
|
||||||
|
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center circle
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page *createPage(CommonData &common){
|
||||||
|
return new PageClock(common);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* and we provide the number of user parameters we expect (0 here)
|
||||||
|
* and will will provide the names of the fixed values we need
|
||||||
|
*/
|
||||||
|
PageDescription registerPageClock(
|
||||||
|
"Clock", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{"GPST", "GPSD", "HDOP"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
548
lib/obp60task/PageClock2.new
Normal file
548
lib/obp60task/PageClock2.new
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Variant of PageClock with switchable analog / digital clock display.
|
||||||
|
* mode: (A)nalog | (D)igital | race (T)imer (T not implemented yet, falls back to analog)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PageClock2 : public Page
|
||||||
|
{
|
||||||
|
bool simulation = false;
|
||||||
|
int simtime;
|
||||||
|
bool keylock = false;
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
char source = 'G'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer
|
||||||
|
char tz = 'L'; // time zone (L)ocal | (U)TC
|
||||||
|
double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75
|
||||||
|
double homelat;
|
||||||
|
double homelon;
|
||||||
|
bool homevalid = false; // homelat and homelon are valid
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageClock2(CommonData& common)
|
||||||
|
{
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG, "Instantiate PageClock2");
|
||||||
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
|
timezone = common.config->getString(common.config->timeZone).toDouble();
|
||||||
|
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||||
|
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||||
|
homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||||
|
simtime = 38160; // time value 11:36
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setupKeys()
|
||||||
|
{
|
||||||
|
Page::setupKeys();
|
||||||
|
commonData->keydata[0].label = "SRC";
|
||||||
|
commonData->keydata[1].label = "MODE";
|
||||||
|
commonData->keydata[4].label = "TZ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key functions
|
||||||
|
virtual int handleKey(int key)
|
||||||
|
{
|
||||||
|
// Time source
|
||||||
|
if (key == 1) {
|
||||||
|
switch (source) {
|
||||||
|
case 'G':
|
||||||
|
source = 'R';
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
source = 'G';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
source = 'G';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (key == 2) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'A':
|
||||||
|
mode = 'D';
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
mode = 'T';
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
mode = 'A';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = 'A';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Time zone: Local / UTC
|
||||||
|
if (key == 5) {
|
||||||
|
switch (tz) {
|
||||||
|
case 'L':
|
||||||
|
tz = 'U';
|
||||||
|
break;
|
||||||
|
case 'U':
|
||||||
|
tz = 'L';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tz = 'L';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keylock function
|
||||||
|
if (key == 11) { // Code for keylock
|
||||||
|
keylock = !keylock; // Toggle keylock
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData& pageData)
|
||||||
|
{
|
||||||
|
GwConfigHandler* config = commonData->config;
|
||||||
|
|
||||||
|
static String svalue1old = "";
|
||||||
|
static String unit1old = "";
|
||||||
|
static String svalue2old = "";
|
||||||
|
static String unit2old = "";
|
||||||
|
static String svalue3old = "";
|
||||||
|
static String unit3old = "";
|
||||||
|
|
||||||
|
static String svalue5old = "";
|
||||||
|
static String svalue6old = "";
|
||||||
|
|
||||||
|
double value1 = 0;
|
||||||
|
double value2 = 0;
|
||||||
|
double value3 = 0;
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
|
String dateformat = config->getString(config->dateFormat);
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
|
// Get boat values for GPS time
|
||||||
|
GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||||
|
String name1 = bvalue1->getName().c_str(); // Value name
|
||||||
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
|
if (simulation == false) {
|
||||||
|
value1 = bvalue1->value; // Value as double in SI unit
|
||||||
|
} else {
|
||||||
|
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||||
|
} // Other simulation data see OBP60Formatter.cpp
|
||||||
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
if (valid1 == true) {
|
||||||
|
svalue1old = svalue1; // Save old value
|
||||||
|
unit1old = unit1; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for GPS date
|
||||||
|
GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name2 = bvalue2->getName().c_str(); // Value name
|
||||||
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
|
value2 = bvalue2->value; // Value as double in SI unit
|
||||||
|
bool valid2 = bvalue2->valid; // Valid information
|
||||||
|
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
if (valid2 == true) {
|
||||||
|
svalue2old = svalue2; // Save old value
|
||||||
|
unit2old = unit2; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for HDOP date
|
||||||
|
GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue)
|
||||||
|
String name3 = bvalue3->getName().c_str(); // Value name
|
||||||
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
|
value3 = bvalue3->value; // Value as double in SI unit
|
||||||
|
bool valid3 = bvalue3->valid; // Valid information
|
||||||
|
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||||
|
if (valid3 == true) {
|
||||||
|
svalue3old = svalue3; // Save old value
|
||||||
|
unit3old = unit3; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if (String(flashLED) == "Limit Violation") {
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging boat values
|
||||||
|
if (bvalue1 == NULL)
|
||||||
|
return PAGE_OK; // WTF why this statement?
|
||||||
|
LOG_DEBUG(GwLog::LOG, "Drawing at PageClock2, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm* local_tm = localtime(&tv);
|
||||||
|
|
||||||
|
// Show values GPS date
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 65);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
// GPS value
|
||||||
|
getdisplay().print(svalue2);
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
// RTC value
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||||
|
} else {
|
||||||
|
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalue2old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 95);
|
||||||
|
getdisplay().print("Date"); // Name
|
||||||
|
|
||||||
|
// Horizintal separator left
|
||||||
|
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values GPS time (small text bottom left, same as original)
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 250);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print(svalue1); // Value
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||||
|
} else {
|
||||||
|
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalue1old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 220);
|
||||||
|
getdisplay().print("Time"); // Name
|
||||||
|
|
||||||
|
// Show values sunrise
|
||||||
|
String sunrise = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
|
||||||
|
svalue5old = sunrise;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunrise = String("06:42");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 65);
|
||||||
|
if (holdvalues == false)
|
||||||
|
getdisplay().print(sunrise); // Value
|
||||||
|
else
|
||||||
|
getdisplay().print(svalue5old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 95);
|
||||||
|
getdisplay().print("SunR"); // Name
|
||||||
|
|
||||||
|
// Horizintal separator right
|
||||||
|
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values sunset
|
||||||
|
String sunset = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
|
||||||
|
svalue6old = sunset;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunset = String("21:03");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 250);
|
||||||
|
if (holdvalues == false)
|
||||||
|
getdisplay().print(sunset); // Value
|
||||||
|
else
|
||||||
|
getdisplay().print(svalue6old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 220);
|
||||||
|
getdisplay().print("SunS"); // Name
|
||||||
|
|
||||||
|
//*******************************************************************************************
|
||||||
|
|
||||||
|
if (mode == 'D') {
|
||||||
|
// Digital clock mode: large 7-segment time centered on display
|
||||||
|
|
||||||
|
// Determine current time in hours/minutes/seconds (24h)
|
||||||
|
int hour24 = 0;
|
||||||
|
int minute24 = 0;
|
||||||
|
int second24 = 0;
|
||||||
|
|
||||||
|
if (source == 'R' && commonData->data.rtcValid) {
|
||||||
|
// RTC based
|
||||||
|
time_t tv2 = mktime(&commonData->data.rtcTime);
|
||||||
|
if (tz == 'L') {
|
||||||
|
tv2 += static_cast<time_t>(timezone * 3600);
|
||||||
|
}
|
||||||
|
struct tm* tm2 = localtime(&tv2);
|
||||||
|
hour24 = tm2->tm_hour;
|
||||||
|
minute24 = tm2->tm_min;
|
||||||
|
second24 = tm2->tm_sec;
|
||||||
|
} else {
|
||||||
|
// GPS / simulation based
|
||||||
|
double t = value1;
|
||||||
|
if (tz == 'L') {
|
||||||
|
t += timezone * 3600;
|
||||||
|
}
|
||||||
|
if (t >= 86400)
|
||||||
|
t -= 86400;
|
||||||
|
if (t < 0)
|
||||||
|
t += 86400;
|
||||||
|
hour24 = static_cast<int>(t / 3600.0);
|
||||||
|
int rest = static_cast<int>(t) - hour24 * 3600;
|
||||||
|
minute24 = rest / 60;
|
||||||
|
second24 = rest % 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[9]; // "HH:MM:SS" + '\0'
|
||||||
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24);
|
||||||
|
String timeStr = String(buf);
|
||||||
|
|
||||||
|
// Clear central area and draw large digital time
|
||||||
|
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b);
|
||||||
|
|
||||||
|
int16_t x1b, y1b;
|
||||||
|
uint16_t wb, hb;
|
||||||
|
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
|
||||||
|
|
||||||
|
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||||
|
int16_t y = 150 + hb / 2; // vertically around center (y=150)
|
||||||
|
|
||||||
|
getdisplay().setCursor(x, y);
|
||||||
|
getdisplay().print(timeStr);
|
||||||
|
|
||||||
|
// Small indicators inside the digital area
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(180, 110);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
} else {
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Analog clock mode (A/T) - original drawing
|
||||||
|
|
||||||
|
int rInstrument = 110; // Radius of clock
|
||||||
|
float pi = 3.141592;
|
||||||
|
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
||||||
|
|
||||||
|
for (int i = 0; i < 360; i = i + 1)
|
||||||
|
{
|
||||||
|
// Scaling values
|
||||||
|
float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots
|
||||||
|
float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots
|
||||||
|
const char* ii = "";
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: ii = "12"; break;
|
||||||
|
case 90: ii = "3"; break;
|
||||||
|
case 180: ii = "6"; break;
|
||||||
|
case 270: ii = "9"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print text centered on position x, y
|
||||||
|
int16_t x1c, y1c; // Return values of getTextBounds
|
||||||
|
uint16_t wc, hc; // Return values of getTextBounds
|
||||||
|
getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
|
||||||
|
getdisplay().setCursor(x - wc / 2, y + hc / 2);
|
||||||
|
if (i % 90 == 0) {
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().print(ii);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with dots
|
||||||
|
float sinx = 0;
|
||||||
|
float cosx = 0;
|
||||||
|
if (i % 6 == 0) {
|
||||||
|
float x1d = 200 + rInstrument * sin(i / 180.0 * pi);
|
||||||
|
float y1d = 150 - rInstrument * cos(i / 180.0 * pi);
|
||||||
|
getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor);
|
||||||
|
sinx = sin(i / 180.0 * pi);
|
||||||
|
cosx = cos(i / 180.0 * pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with lines (two triangles)
|
||||||
|
if (i % 30 == 0) {
|
||||||
|
float dx = 2; // Line thickness = 2*dx+1
|
||||||
|
float xx1 = -dx;
|
||||||
|
float xx2 = +dx;
|
||||||
|
float yy1 = -(rInstrument - 10);
|
||||||
|
float yy2 = -(rInstrument + 10);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print Unit in clock
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(175, 110);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
} else {
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock values
|
||||||
|
double hour = 0;
|
||||||
|
double minute = 0;
|
||||||
|
if (source == 'R') {
|
||||||
|
if (tz == 'L') {
|
||||||
|
time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm* local_tm2 = localtime(&tv2);
|
||||||
|
minute = local_tm2->tm_min;
|
||||||
|
hour = local_tm2->tm_hour;
|
||||||
|
} else {
|
||||||
|
minute = commonData->data.rtcTime.tm_min;
|
||||||
|
hour = commonData->data.rtcTime.tm_hour;
|
||||||
|
}
|
||||||
|
hour += minute / 60;
|
||||||
|
} else {
|
||||||
|
if (tz == 'L') {
|
||||||
|
value1 += timezone * 3600;
|
||||||
|
}
|
||||||
|
if (value1 > 86400) { value1 -= 86400; }
|
||||||
|
if (value1 < 0) { value1 += 86400; }
|
||||||
|
hour = (value1 / 3600.0);
|
||||||
|
// minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving
|
||||||
|
minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute
|
||||||
|
}
|
||||||
|
if (hour > 12) {
|
||||||
|
hour -= 12.0;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "... PageClock2, value1: %f hour: %f minute:%f", value1, hour, minute);
|
||||||
|
|
||||||
|
// Draw hour pointer
|
||||||
|
float startwidth = 8; // Start width of pointer
|
||||||
|
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||||
|
float sinx = sin(hour * 30.0 * pi / 180); // Hour
|
||||||
|
float cosx = cos(hour * 30.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument * 0.5);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument * 0.5);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw minute pointer
|
||||||
|
startwidth = 8; // Start width of pointer
|
||||||
|
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||||
|
float sinx = sin(minute * 6.0 * pi / 180); // Minute
|
||||||
|
float cosx = cos(minute * 6.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument - 15);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument - 15);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center circle
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page* createPage(CommonData& common)
|
||||||
|
{
|
||||||
|
return new PageClock2(common);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* we provide the number of user parameters we expect (0 here)
|
||||||
|
* and we provide the names of the fixed values we need
|
||||||
|
*/
|
||||||
|
PageDescription registerPageClock2(
|
||||||
|
"Clock2", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{"GPST", "GPSD", "HDOP"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
777
lib/obp60task/PageClock3.new
Normal file
777
lib/obp60task/PageClock3.new
Normal file
@@ -0,0 +1,777 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PageClock3: Clock page with
|
||||||
|
* - Analog mode (mode == 'A')
|
||||||
|
* - Digital mode (mode == 'D')
|
||||||
|
* - Countdown timer mode (mode == 'T')
|
||||||
|
*
|
||||||
|
* Timer mode:
|
||||||
|
* - Format HH:MM:SS (24h, leading zeros)
|
||||||
|
* - Keys in timer mode:
|
||||||
|
* K1: MODE (A/D/T)
|
||||||
|
* K2: POS (select field: HH / MM / SS)
|
||||||
|
* K3: + (increment selected field)
|
||||||
|
* K4: - (decrement selected field)
|
||||||
|
* K5: RUN (start/stop countdown)
|
||||||
|
* - Selection marker: line under active field (width 2px, not wider than digits)
|
||||||
|
* - Editing only possible when timer is not running
|
||||||
|
* - When page is left, running timer continues in background using RTC time
|
||||||
|
* (on re-entry, remaining time is recalculated from RTC)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PageClock3 : public Page
|
||||||
|
{
|
||||||
|
bool simulation = false;
|
||||||
|
int simtime;
|
||||||
|
bool keylock = false;
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
char source = 'G'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||||
|
#endif
|
||||||
|
char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer
|
||||||
|
char tz = 'L'; // time zone (L)ocal | (U)TC
|
||||||
|
double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75
|
||||||
|
double homelat;
|
||||||
|
double homelon;
|
||||||
|
bool homevalid = false; // homelat and homelon are valid
|
||||||
|
|
||||||
|
// Timer state (static so it survives page switches)
|
||||||
|
static bool timerInitialized;
|
||||||
|
static bool timerRunning;
|
||||||
|
static int timerHours;
|
||||||
|
static int timerMinutes;
|
||||||
|
static int timerSeconds;
|
||||||
|
static int selectedField; // 0 = hours, 1 = minutes, 2 = seconds
|
||||||
|
static bool showSelectionMarker;
|
||||||
|
static time_t timerEndEpoch; // absolute end time based on RTC
|
||||||
|
|
||||||
|
void setupTimerDefaults()
|
||||||
|
{
|
||||||
|
if (!timerInitialized) {
|
||||||
|
timerInitialized = true;
|
||||||
|
timerRunning = false;
|
||||||
|
timerHours = 0;
|
||||||
|
timerMinutes = 0;
|
||||||
|
timerSeconds = 0;
|
||||||
|
selectedField = 0;
|
||||||
|
showSelectionMarker = true;
|
||||||
|
timerEndEpoch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clamp(int value, int minVal, int maxVal)
|
||||||
|
{
|
||||||
|
if (value < minVal) return minVal;
|
||||||
|
if (value > maxVal) return maxVal;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void incrementSelected()
|
||||||
|
{
|
||||||
|
if (selectedField == 0) {
|
||||||
|
timerHours = clamp(timerHours + 1, 0, 23);
|
||||||
|
} else if (selectedField == 1) {
|
||||||
|
timerMinutes = clamp(timerMinutes + 1, 0, 59);
|
||||||
|
} else {
|
||||||
|
timerSeconds = clamp(timerSeconds + 1, 0, 59);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decrementSelected()
|
||||||
|
{
|
||||||
|
if (selectedField == 0) {
|
||||||
|
timerHours = clamp(timerHours - 1, 0, 23);
|
||||||
|
} else if (selectedField == 1) {
|
||||||
|
timerMinutes = clamp(timerMinutes - 1, 0, 59);
|
||||||
|
} else {
|
||||||
|
timerSeconds = clamp(timerSeconds - 1, 0, 59);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalTimerSeconds() const
|
||||||
|
{
|
||||||
|
return timerHours * 3600 + timerMinutes * 60 + timerSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageClock3(CommonData& common)
|
||||||
|
{
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG, "Instantiate PageClock3");
|
||||||
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
|
timezone = common.config->getString(common.config->timeZone).toDouble();
|
||||||
|
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||||
|
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||||
|
homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||||
|
simtime = 38160; // time value 11:36
|
||||||
|
setupTimerDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setupKeys()
|
||||||
|
{
|
||||||
|
Page::setupKeys();
|
||||||
|
|
||||||
|
if (mode == 'T') {
|
||||||
|
// Timer mode: MODE, POS, +, -, RUN
|
||||||
|
commonData->keydata[0].label = "MODE";
|
||||||
|
commonData->keydata[1].label = "POS";
|
||||||
|
commonData->keydata[2].label = "+";
|
||||||
|
commonData->keydata[3].label = "-";
|
||||||
|
commonData->keydata[4].label = "RUN";
|
||||||
|
} else {
|
||||||
|
// Clock modes: like original
|
||||||
|
commonData->keydata[0].label = "SRC";
|
||||||
|
commonData->keydata[1].label = "MODE";
|
||||||
|
commonData->keydata[4].label = "TZ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key functions
|
||||||
|
virtual int handleKey(int key)
|
||||||
|
{
|
||||||
|
setupTimerDefaults();
|
||||||
|
|
||||||
|
// Keylock function
|
||||||
|
if (key == 11) { // Code for keylock
|
||||||
|
keylock = !keylock; // Toggle keylock
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == 'T') {
|
||||||
|
// Timer mode key handling
|
||||||
|
|
||||||
|
// MODE (K1): cycle display mode A/D/T
|
||||||
|
if (key == 1) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'A': mode = 'D'; break;
|
||||||
|
case 'D': mode = 'T'; break;
|
||||||
|
case 'T': mode = 'A'; break;
|
||||||
|
default: mode = 'A'; break;
|
||||||
|
}
|
||||||
|
setupKeys();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POS (K2): select field HH / MM / SS (only if timer not running)
|
||||||
|
if (key == 2 && !timerRunning) {
|
||||||
|
selectedField = (selectedField + 1) % 3;
|
||||||
|
showSelectionMarker = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// + (K3): increment selected field (only if timer not running)
|
||||||
|
if (key == 3 && !timerRunning) {
|
||||||
|
incrementSelected();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - (K4): decrement selected field (only if timer not running)
|
||||||
|
if (key == 4 && !timerRunning) {
|
||||||
|
decrementSelected();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RUN (K5): start/stop timer
|
||||||
|
if (key == 5) {
|
||||||
|
if (!timerRunning) {
|
||||||
|
// Start timer if a non-zero duration is set
|
||||||
|
int total = totalTimerSeconds();
|
||||||
|
if (total > 0 && commonData->data.rtcValid) {
|
||||||
|
struct tm rtcCopy = commonData->data.rtcTime;
|
||||||
|
time_t nowEpoch = mktime(&rtcCopy);
|
||||||
|
timerEndEpoch = nowEpoch + total;
|
||||||
|
timerRunning = true;
|
||||||
|
showSelectionMarker = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stop timer: compute remaining time and keep as new setting
|
||||||
|
if (commonData->data.rtcValid) {
|
||||||
|
struct tm rtcCopy = commonData->data.rtcTime;
|
||||||
|
time_t nowEpoch = mktime(&rtcCopy);
|
||||||
|
time_t remaining = timerEndEpoch - nowEpoch;
|
||||||
|
if (remaining < 0) remaining = 0;
|
||||||
|
int rem = static_cast<int>(remaining);
|
||||||
|
timerHours = rem / 3600;
|
||||||
|
rem -= timerHours * 3600;
|
||||||
|
timerMinutes = rem / 60;
|
||||||
|
timerSeconds = rem % 60;
|
||||||
|
}
|
||||||
|
timerRunning = false;
|
||||||
|
// marker will become visible again only after POS press
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In timer mode, other keys are passed through
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock (A/D) modes key handling – like original PageClock
|
||||||
|
|
||||||
|
// Time source (K1)
|
||||||
|
if (key == 1) {
|
||||||
|
switch (source) {
|
||||||
|
case 'G': source = 'R'; break;
|
||||||
|
case 'R': source = 'G'; break;
|
||||||
|
default: source = 'G'; break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODE (K2)
|
||||||
|
if (key == 2) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'A': mode = 'D'; break;
|
||||||
|
case 'D': mode = 'T'; break;
|
||||||
|
case 'T': mode = 'A'; break;
|
||||||
|
default: mode = 'A'; break;
|
||||||
|
}
|
||||||
|
setupKeys();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time zone: Local / UTC (K5)
|
||||||
|
if (key == 5) {
|
||||||
|
switch (tz) {
|
||||||
|
case 'L': tz = 'U'; break;
|
||||||
|
case 'U': tz = 'L'; break;
|
||||||
|
default: tz = 'L'; break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData& pageData)
|
||||||
|
{
|
||||||
|
GwConfigHandler* config = commonData->config;
|
||||||
|
GwLog* logger = commonData->logger;
|
||||||
|
|
||||||
|
setupTimerDefaults();
|
||||||
|
setupKeys(); // ensure correct key labels for current mode
|
||||||
|
|
||||||
|
static String svalue1old = "";
|
||||||
|
static String unit1old = "";
|
||||||
|
static String svalue2old = "";
|
||||||
|
static String unit2old = "";
|
||||||
|
static String svalue3old = "";
|
||||||
|
static String unit3old = "";
|
||||||
|
|
||||||
|
static String svalue5old = "";
|
||||||
|
static String svalue6old = "";
|
||||||
|
|
||||||
|
double value1 = 0;
|
||||||
|
double value2 = 0;
|
||||||
|
double value3 = 0;
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
|
String dateformat = config->getString(config->dateFormat);
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
|
// Get boat values for GPS time
|
||||||
|
GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list
|
||||||
|
String name1 = bvalue1->getName().c_str(); // Value name
|
||||||
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
|
if (simulation == false) {
|
||||||
|
value1 = bvalue1->value; // Value as double in SI unit
|
||||||
|
} else {
|
||||||
|
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||||
|
} // Other simulation data see OBP60Formatter.cpp
|
||||||
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value
|
||||||
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
if (valid1 == true) {
|
||||||
|
svalue1old = svalue1; // Save old value
|
||||||
|
unit1old = unit1; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for GPS date
|
||||||
|
GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list
|
||||||
|
String name2 = bvalue2->getName().c_str(); // Value name
|
||||||
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
|
value2 = bvalue2->value; // Value as double in SI unit
|
||||||
|
bool valid2 = bvalue2->valid; // Valid information
|
||||||
|
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value
|
||||||
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
if (valid2 == true) {
|
||||||
|
svalue2old = svalue2; // Save old value
|
||||||
|
unit2old = unit2; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get boat values for HDOP
|
||||||
|
GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list
|
||||||
|
String name3 = bvalue3->getName().c_str(); // Value name
|
||||||
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
|
value3 = bvalue3->value; // Value as double in SI unit
|
||||||
|
bool valid3 = bvalue3->valid; // Valid information
|
||||||
|
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value
|
||||||
|
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||||
|
if (valid3 == true) {
|
||||||
|
svalue3old = svalue3; // Save old value
|
||||||
|
unit3old = unit3; // Save old unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if (String(flashLED) == "Limit Violation") {
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging boat values
|
||||||
|
if (bvalue1 == NULL) return PAGE_OK;
|
||||||
|
LOG_DEBUG(GwLog::LOG, "Drawing at PageClock3, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm* local_tm = localtime(&tv);
|
||||||
|
|
||||||
|
// Show values GPS date
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 65);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
// GPS value
|
||||||
|
getdisplay().print(svalue2);
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
// RTC value
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||||
|
} else {
|
||||||
|
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalue2old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 95);
|
||||||
|
getdisplay().print("Date"); // Name
|
||||||
|
|
||||||
|
// Horizontal separator left
|
||||||
|
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values GPS time (small text bottom left)
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 250);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print(svalue1); // Value
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
if (tz == 'L') {
|
||||||
|
getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||||
|
} else {
|
||||||
|
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print("---");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalue1old);
|
||||||
|
}
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 220);
|
||||||
|
getdisplay().print("Time"); // Name
|
||||||
|
|
||||||
|
// Show values sunrise
|
||||||
|
String sunrise = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
|
||||||
|
svalue5old = sunrise;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunrise = String("06:42");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 65);
|
||||||
|
if (holdvalues == false) getdisplay().print(sunrise); // Value
|
||||||
|
else getdisplay().print(svalue5old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 95);
|
||||||
|
getdisplay().print("SunR"); // Name
|
||||||
|
|
||||||
|
// Horizontal separator right
|
||||||
|
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Show values sunset
|
||||||
|
String sunset = "---";
|
||||||
|
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||||
|
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
|
||||||
|
svalue6old = sunset;
|
||||||
|
} else if (simulation) {
|
||||||
|
sunset = String("21:03");
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(335, 250);
|
||||||
|
if (holdvalues == false) getdisplay().print(sunset); // Value
|
||||||
|
else getdisplay().print(svalue6old);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(335, 220);
|
||||||
|
getdisplay().print("SunS"); // Name
|
||||||
|
|
||||||
|
//*******************************************************************************************
|
||||||
|
|
||||||
|
if (mode == 'T') {
|
||||||
|
// TIMER MODE: countdown timer HH:MM:SS in the center with 7-segment font
|
||||||
|
|
||||||
|
int dispH = timerHours;
|
||||||
|
int dispM = timerMinutes;
|
||||||
|
int dispS = timerSeconds;
|
||||||
|
|
||||||
|
// Update remaining time if timer is running (based on RTC)
|
||||||
|
if (timerRunning && commonData->data.rtcValid) {
|
||||||
|
struct tm rtcCopy = commonData->data.rtcTime;
|
||||||
|
time_t nowEpoch = mktime(&rtcCopy);
|
||||||
|
time_t remaining = timerEndEpoch - nowEpoch;
|
||||||
|
if (remaining <= 0) {
|
||||||
|
remaining = 0;
|
||||||
|
timerRunning = false;
|
||||||
|
}
|
||||||
|
int rem = static_cast<int>(remaining);
|
||||||
|
dispH = rem / 3600;
|
||||||
|
rem -= dispH * 3600;
|
||||||
|
dispM = rem / 60;
|
||||||
|
dispS = rem % 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[9]; // "HH:MM:SS"
|
||||||
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dispH, dispM, dispS);
|
||||||
|
String timeStr = String(buf);
|
||||||
|
|
||||||
|
// Clear central area and draw large digital time
|
||||||
|
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b);
|
||||||
|
|
||||||
|
// Determine widths for digits and colon to position selection underline exactly
|
||||||
|
int16_t x0, y0;
|
||||||
|
uint16_t wDigit, hDigit;
|
||||||
|
uint16_t wColon, hColon;
|
||||||
|
|
||||||
|
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
|
||||||
|
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
|
||||||
|
|
||||||
|
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
|
||||||
|
|
||||||
|
int16_t baseX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(totalWidth)) / 2;
|
||||||
|
int16_t centerY = 150;
|
||||||
|
|
||||||
|
// Draw time string centered
|
||||||
|
int16_t x1b, y1b;
|
||||||
|
uint16_t wb, hb;
|
||||||
|
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
|
||||||
|
int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||||
|
int16_t textY = centerY + hb / 2;
|
||||||
|
|
||||||
|
getdisplay().setCursor(textX, textY);
|
||||||
|
getdisplay().print(timeStr);
|
||||||
|
|
||||||
|
// Selection marker (only visible when not running and POS pressed)
|
||||||
|
if (!timerRunning && showSelectionMarker) {
|
||||||
|
int16_t selX = baseX;
|
||||||
|
if (selectedField == 1) {
|
||||||
|
selX = baseX + wDigit + wColon; // minutes start
|
||||||
|
} else if (selectedField == 2) {
|
||||||
|
selX = baseX + 2 * wDigit + 2 * wColon; // seconds start
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t underlineY = centerY + hb / 2 + 2;
|
||||||
|
getdisplay().fillRect(selX, underlineY, wDigit, 2, commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small indicators: timezone and source
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(180, 110);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
} else {
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (mode == 'D') {
|
||||||
|
// DIGITAL CLOCK MODE: large 7-segment time based on GPS/RTC
|
||||||
|
|
||||||
|
int hour24 = 0;
|
||||||
|
int minute24 = 0;
|
||||||
|
int second24 = 0;
|
||||||
|
|
||||||
|
if (source == 'R' && commonData->data.rtcValid) {
|
||||||
|
time_t tv2 = mktime(&commonData->data.rtcTime);
|
||||||
|
if (tz == 'L') {
|
||||||
|
tv2 += static_cast<time_t>(timezone * 3600);
|
||||||
|
}
|
||||||
|
struct tm* tm2 = localtime(&tv2);
|
||||||
|
hour24 = tm2->tm_hour;
|
||||||
|
minute24 = tm2->tm_min;
|
||||||
|
second24 = tm2->tm_sec;
|
||||||
|
} else {
|
||||||
|
double t = value1;
|
||||||
|
if (tz == 'L') {
|
||||||
|
t += timezone * 3600;
|
||||||
|
}
|
||||||
|
if (t >= 86400) t -= 86400;
|
||||||
|
if (t < 0) t += 86400;
|
||||||
|
hour24 = static_cast<int>(t / 3600.0);
|
||||||
|
int rest = static_cast<int>(t) - hour24 * 3600;
|
||||||
|
minute24 = rest / 60;
|
||||||
|
second24 = rest % 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[9]; // "HH:MM:SS"
|
||||||
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24);
|
||||||
|
String timeStr = String(buf);
|
||||||
|
|
||||||
|
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b);
|
||||||
|
|
||||||
|
int16_t x1b, y1b;
|
||||||
|
uint16_t wb, hb;
|
||||||
|
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
|
||||||
|
|
||||||
|
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||||
|
int16_t y = 150 + hb / 2;
|
||||||
|
|
||||||
|
getdisplay().setCursor(x, y);
|
||||||
|
getdisplay().print(timeStr);
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(180, 110);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
} else {
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ANALOG CLOCK MODE (mode == 'A')
|
||||||
|
|
||||||
|
int rInstrument = 110; // Radius of clock
|
||||||
|
float pi = 3.141592;
|
||||||
|
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
||||||
|
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
||||||
|
|
||||||
|
for (int i = 0; i < 360; i = i + 1)
|
||||||
|
{
|
||||||
|
// Scaling values
|
||||||
|
float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots
|
||||||
|
float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots
|
||||||
|
const char* ii = "";
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: ii = "12"; break;
|
||||||
|
case 90: ii = "3"; break;
|
||||||
|
case 180: ii = "6"; break;
|
||||||
|
case 270: ii = "9"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print text centered on position x, y
|
||||||
|
int16_t x1c, y1c; // Return values of getTextBounds
|
||||||
|
uint16_t wc, hc; // Return values of getTextBounds
|
||||||
|
getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
|
||||||
|
getdisplay().setCursor(x - wc / 2, y + hc / 2);
|
||||||
|
if (i % 90 == 0) {
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().print(ii);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with dots
|
||||||
|
float sinx = 0;
|
||||||
|
float cosx = 0;
|
||||||
|
if (i % 6 == 0) {
|
||||||
|
float x1d = 200 + rInstrument * sin(i / 180.0 * pi);
|
||||||
|
float y1d = 150 - rInstrument * cos(i / 180.0 * pi);
|
||||||
|
getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor);
|
||||||
|
sinx = sin(i / 180.0 * pi);
|
||||||
|
cosx = cos(i / 180.0 * pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sub scale with lines (two triangles)
|
||||||
|
if (i % 30 == 0) {
|
||||||
|
float dx = 2; // Line thickness = 2*dx+1
|
||||||
|
float xx1 = -dx;
|
||||||
|
float xx2 = +dx;
|
||||||
|
float yy1 = -(rInstrument - 10);
|
||||||
|
float yy2 = -(rInstrument + 10);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print Unit in clock
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(175, 110);
|
||||||
|
if (holdvalues == false) {
|
||||||
|
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||||
|
} else {
|
||||||
|
getdisplay().print(unit2old); // date unit
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(185, 190);
|
||||||
|
if (source == 'G') {
|
||||||
|
getdisplay().print("GPS");
|
||||||
|
} else {
|
||||||
|
getdisplay().print("RTC");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock values
|
||||||
|
double hour = 0;
|
||||||
|
double minute = 0;
|
||||||
|
if (source == 'R') {
|
||||||
|
if (tz == 'L') {
|
||||||
|
time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||||
|
struct tm* local_tm2 = localtime(&tv2);
|
||||||
|
minute = local_tm2->tm_min;
|
||||||
|
hour = local_tm2->tm_hour;
|
||||||
|
} else {
|
||||||
|
minute = commonData->data.rtcTime.tm_min;
|
||||||
|
hour = commonData->data.rtcTime.tm_hour;
|
||||||
|
}
|
||||||
|
hour += minute / 60;
|
||||||
|
} else {
|
||||||
|
if (tz == 'L') {
|
||||||
|
value1 += timezone * 3600;
|
||||||
|
}
|
||||||
|
if (value1 > 86400) { value1 -= 86400; }
|
||||||
|
if (value1 < 0) { value1 += 86400; }
|
||||||
|
hour = (value1 / 3600.0);
|
||||||
|
// minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving
|
||||||
|
minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute
|
||||||
|
}
|
||||||
|
if (hour > 12) {
|
||||||
|
hour -= 12.0;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "... PageClock3, value1: %f hour: %f minute:%f", value1, hour, minute);
|
||||||
|
|
||||||
|
// Draw hour pointer
|
||||||
|
float startwidth = 8; // Start width of pointer
|
||||||
|
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||||
|
float sinx = sin(hour * 30.0 * pi / 180); // Hour
|
||||||
|
float cosx = cos(hour * 30.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument * 0.5);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument * 0.5);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw minute pointer
|
||||||
|
startwidth = 8; // Start width of pointer
|
||||||
|
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||||
|
float sinx = sin(minute * 6.0 * pi / 180); // Minute
|
||||||
|
float cosx = cos(minute * 6.0 * pi / 180);
|
||||||
|
// Normal pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float xx1 = -startwidth;
|
||||||
|
float xx2 = startwidth;
|
||||||
|
float yy1 = -startwidth;
|
||||||
|
float yy2 = -(rInstrument - 15);
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||||
|
// Inverted pointer
|
||||||
|
// Pointer as triangle with center base 2*width
|
||||||
|
float endwidth = 2; // End width of pointer
|
||||||
|
float ix1 = endwidth;
|
||||||
|
float ix2 = -endwidth;
|
||||||
|
float iy1 = -(rInstrument - 15);
|
||||||
|
float iy2 = -endwidth;
|
||||||
|
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||||
|
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center circle
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static member definitions
|
||||||
|
bool PageClock3::timerInitialized = false;
|
||||||
|
bool PageClock3::timerRunning = false;
|
||||||
|
int PageClock3::timerHours = 0;
|
||||||
|
int PageClock3::timerMinutes = 0;
|
||||||
|
int PageClock3::timerSeconds = 0;
|
||||||
|
int PageClock3::selectedField = 0;
|
||||||
|
bool PageClock3::showSelectionMarker = true;
|
||||||
|
time_t PageClock3::timerEndEpoch = 0;
|
||||||
|
|
||||||
|
static Page* createPage(CommonData& common)
|
||||||
|
{
|
||||||
|
return new PageClock3(common);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* we provide the number of user parameters we expect (0 here)
|
||||||
|
* and we provide the names of the fixed values we need
|
||||||
|
*/
|
||||||
|
PageDescription registerPageClock3(
|
||||||
|
"Clock3", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{"GPST", "GPSD", "HDOP"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
224
lib/obp60task/PageClockDigital.new
Normal file
224
lib/obp60task/PageClockDigital.new
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple digital clock page.
|
||||||
|
*
|
||||||
|
* - Shows system time as large digital value in the center
|
||||||
|
* - Uses same data sources and configuration as PageClock (GPS / RTC, time zone)
|
||||||
|
* - Keys:
|
||||||
|
* K1: toggle time source (GPS / RTC)
|
||||||
|
* K5: toggle time zone (Local / UTC)
|
||||||
|
* K11: keylock
|
||||||
|
*/
|
||||||
|
class PageClockDigital : public Page
|
||||||
|
{
|
||||||
|
bool simulation = false;
|
||||||
|
int simtime = 0;
|
||||||
|
char source; // time source (R)TC | (G)PS
|
||||||
|
char tz = 'L'; // time zone (L)ocal | (U)TC
|
||||||
|
double timezone = 0.0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageClockDigital(CommonData& common)
|
||||||
|
{
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG, "Instantiate PageClockDigital");
|
||||||
|
|
||||||
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
|
timezone = common.config->getString(common.config->timeZone).toDouble();
|
||||||
|
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
source = 'G'; // default to GPS time on OBP60
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
source = 'R'; // default to RTC time on OBP40
|
||||||
|
#endif
|
||||||
|
simtime = 38160; // time value 11:36 for simulation (seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setupKeys()
|
||||||
|
{
|
||||||
|
Page::setupKeys();
|
||||||
|
commonData->keydata[0].label = "SRC";
|
||||||
|
commonData->keydata[4].label = "TZ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key functions
|
||||||
|
virtual int handleKey(int key)
|
||||||
|
{
|
||||||
|
// Time source
|
||||||
|
if (key == 1) {
|
||||||
|
switch (source) {
|
||||||
|
case 'G':
|
||||||
|
source = 'R';
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
source = 'G';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
source = 'G';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time zone: Local / UTC
|
||||||
|
if (key == 5) {
|
||||||
|
switch (tz) {
|
||||||
|
case 'L':
|
||||||
|
tz = 'U';
|
||||||
|
break;
|
||||||
|
case 'U':
|
||||||
|
tz = 'L';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tz = 'L';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keylock function
|
||||||
|
if (key == 11) { // Code for keylock
|
||||||
|
commonData->keylock = !commonData->keylock;
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData& pageData)
|
||||||
|
{
|
||||||
|
GwConfigHandler* config = commonData->config;
|
||||||
|
|
||||||
|
static String svalueTimeOld = "";
|
||||||
|
static String svalueDateOld = "";
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
|
||||||
|
// Get boat values for GPS time and date (same as PageClock)
|
||||||
|
if (pageData.values.size() < 2) {
|
||||||
|
return PAGE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
GwApi::BoatValue* bvalueTime = pageData.values[0];
|
||||||
|
GwApi::BoatValue* bvalueDate = pageData.values[1];
|
||||||
|
|
||||||
|
if (bvalueTime == nullptr || bvalueDate == nullptr) {
|
||||||
|
return PAGE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
double valueTime = 0;
|
||||||
|
if (!simulation) {
|
||||||
|
valueTime = bvalueTime->value; // Value as double in SI unit (seconds)
|
||||||
|
} else {
|
||||||
|
valueTime = simtime++; // Simulation data
|
||||||
|
}
|
||||||
|
bool validTime = bvalueTime->valid;
|
||||||
|
String svalueTime = formatValue(bvalueTime, *commonData).svalue; // formatted time string
|
||||||
|
if (validTime) {
|
||||||
|
svalueTimeOld = svalueTime; // Save old value
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validDate = bvalueDate->valid;
|
||||||
|
String svalueDate = formatValue(bvalueDate, *commonData).svalue; // formatted date string
|
||||||
|
if (validDate) {
|
||||||
|
svalueDateOld = svalueDate; // Save old value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if (flashLED == "Limit Violation") {
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
// Build time string depending on source and configuration
|
||||||
|
String timeStr = "---";
|
||||||
|
|
||||||
|
if (!holdvalues) {
|
||||||
|
if (source == 'G') {
|
||||||
|
// GPS value as formatted by formatter
|
||||||
|
timeStr = svalueTime;
|
||||||
|
} else if (commonData->data.rtcValid) {
|
||||||
|
// RTC value
|
||||||
|
time_t tv = mktime(&commonData->data.rtcTime);
|
||||||
|
if (tz == 'L') {
|
||||||
|
tv += static_cast<time_t>(timezone * 3600);
|
||||||
|
}
|
||||||
|
struct tm* local_tm = localtime(&tv);
|
||||||
|
timeStr = formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeStr = svalueTimeOld;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear central area and draw large digital time
|
||||||
|
getdisplay().fillRect(0, 80, getdisplay().width(), 140, commonData->bgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b);
|
||||||
|
|
||||||
|
int16_t x1, y1;
|
||||||
|
uint16_t w, h;
|
||||||
|
getdisplay().getTextBounds(timeStr, 0, 0, &x1, &y1, &w, &h);
|
||||||
|
|
||||||
|
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(w)) / 2;
|
||||||
|
int16_t y = (static_cast<int16_t>(getdisplay().height()) + static_cast<int16_t>(h)) / 2;
|
||||||
|
|
||||||
|
getdisplay().setCursor(x, y);
|
||||||
|
getdisplay().print(timeStr);
|
||||||
|
|
||||||
|
// Show date in the upper left corner
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(10, 40);
|
||||||
|
getdisplay().print("Date");
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(10, 60);
|
||||||
|
if (!holdvalues) {
|
||||||
|
getdisplay().print(svalueDate);
|
||||||
|
} else {
|
||||||
|
getdisplay().print(svalueDateOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show small labels for source and timezone in the lower right corner
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(getdisplay().width() - 80, getdisplay().height() - 40);
|
||||||
|
getdisplay().print(source == 'G' ? "GPS" : "RTC");
|
||||||
|
|
||||||
|
getdisplay().setCursor(getdisplay().width() - 80, getdisplay().height() - 20);
|
||||||
|
getdisplay().print(tz == 'L' ? "LOC" : "UTC");
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page* createPage(CommonData& common)
|
||||||
|
{
|
||||||
|
return new PageClockDigital(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register page so it can be selected in the configuration.
|
||||||
|
* Uses the same fixed values as PageClock.
|
||||||
|
*/
|
||||||
|
PageDescription registerPageClockDigital(
|
||||||
|
"ClockDigital", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of user parameters
|
||||||
|
{"GPST", "GPSD", "HDOP"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ typedef struct{
|
|||||||
double rotationAngle = 0; // Rotation angle in radiant
|
double rotationAngle = 0; // Rotation angle in radiant
|
||||||
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
||||||
struct tm rtcTime; // UTC time from internal RTC
|
struct tm rtcTime; // UTC time from internal RTC
|
||||||
bool rtcValid = false;
|
bool rtcValid = false; // Internal RTC chip
|
||||||
int sunsetHour = 0;
|
int sunsetHour = 0;
|
||||||
int sunsetMinute = 0;
|
int sunsetMinute = 0;
|
||||||
int sunriseHour = 0;
|
int sunriseHour = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user