From 695f7380102ba07d29214af58bf867ae796a43b5 Mon Sep 17 00:00:00 2001 From: "Zechert, Frank (EXTERN: Capgemini)" Date: Tue, 8 Dec 2020 04:03:56 +0100 Subject: [PATCH] low memory version of arduino meeting-clock code, suitable for the arduino nano --- .gitignore | 5 + .vscode/extensions.json | 7 + include/fonts.h | 262 +++++++++++++++++ lib/README | 46 +++ platformio.ini | 17 ++ src/main.cpp | 630 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 967 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 include/fonts.h create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e80666b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/include/fonts.h b/include/fonts.h new file mode 100644 index 0000000..fa6b97b --- /dev/null +++ b/include/fonts.h @@ -0,0 +1,262 @@ +#pragma once +#include + +MD_MAX72XX::fontType_t font_fw_3x5[] PROGMEM = + { + 0, // 0 + 0, // 1 + 0, // 2 + 0, // 3 + 0, // 4 + 0, // 5 + 0, // 6 + 0, // 7 + 0, // 8 + 0, // 9 + 0, // 10 + 0, // 11 + 0, // 12 + 0, // 13 + 0, // 14 + 0, // 15 + 0, // 16 + 0, // 17 + 0, // 18 + 0, // 19 + 0, // 20 + 0, // 21 + 0, // 22 + 0, // 23 + 0, // 24 + 0, // 25 + 0, // 26 + 0, // 27 + 0, // 28 + 0, // 29 + 0, // 30 + 0, // 31 + 1, 0, // 32 + 0, // 33 + 0, // 34 + 0, // 35 + 0, // 36 + 3, 25, 4, 19, // 37 + 0, // 38 + 1, 3, // 39 + 3, 0, 14, 17, // 40 + 3, 17, 14, 0, // 41 + 3, 5, 2, 5, // 42 + 3, 4, 14, 4, // 43 + 1, 24, // 44 + 3, 4, 4, 4, // 45 + 1, 16, // 46 + 3, 24, 4, 3, // 47 + 3, 31, 17, 31, // 48 + 3, 0, 0, 31, // 49 + 3, 29, 21, 23, // 50 + 3, 21, 21, 31, // 51 + 3, 7, 4, 31, // 52 + 3, 23, 21, 29, // 53 + 3, 31, 21, 29, // 54 + 3, 1, 1, 31, // 55 + 3, 31, 21, 31, // 56 + 3, 23, 21, 31, // 57 + 1, 10, // 58 + 1, 26, // 59 + 0, // 60 + 0, // 61 + 0, // 62 + 0, // 63 + 0, // 64 + 0, // 65 + 0, // 66 + 3, 31, 17, 17, // 67 + 0, // 68 + 0, // 69 + 3, 31, 5, 1, // 70 + 0, // 71 + 0, // 72 + 0, // 73 + 0, // 74 + 0, // 75 + 0, // 76 + 0, // 77 + 0, // 78 + 0, // 79 + 0, // 80 + 0, // 81 + 0, // 82 + 0, // 83 + 0, // 84 + 0, // 85 + 0, // 86 + 0, // 87 + 0, // 88 + 0, // 89 + 0, // 90 + 0, // 91 + 0, // 92 + 0, // 93 + 0, // 94 + 0, // 95 + 0, // 96 + 0, // 97 + 0, // 98 + 0, // 99 + 0, // 100 + 0, // 101 + 0, // 102 + 0, // 103 + 0, // 104 + 0, // 105 + 0, // 106 + 0, // 107 + 0, // 108 + 0, // 109 + 0, // 110 + 0, // 111 + 0, // 112 + 0, // 113 + 3, 28, 4, 4, // 114 + 0, // 115 + 3, 16, 16, 16, // 116 + 0, // 117 + 3, 12, 16, 12, // 118 + 0, // 119 + 0, // 120 + 0, // 121 + 2, 3, 3, // 122 + 0, // 123 + 0, // 124 + 0, // 125 + 0, // 126 + 0, // 127 + 0, // 128 + 0, // 129 + 0, // 130 + 0, // 131 + 0, // 132 + 0, // 133 + 0, // 134 + 0, // 135 + 0, // 136 + 0, // 137 + 0, // 138 + 0, // 139 + 0, // 140 + 0, // 141 + 0, // 142 + 0, // 143 + 0, // 144 + 0, // 145 + 0, // 146 + 0, // 147 + 0, // 148 + 0, // 149 + 0, // 150 + 0, // 151 + 0, // 152 + 0, // 153 + 0, // 154 + 0, // 155 + 0, // 156 + 0, // 157 + 0, // 158 + 0, // 159 + 0, // 160 + 0, // 161 + 0, // 162 + 0, // 163 + 0, // 164 + 0, // 165 + 0, // 166 + 0, // 167 + 0, // 168 + 0, // 169 + 0, // 170 + 0, // 171 + 0, // 172 + 0, // 173 + 0, // 174 + 0, // 175 + 2, 3, 3, // 176 + 0, // 177 + 0, // 178 + 0, // 179 + 0, // 180 + 0, // 181 + 0, // 182 + 0, // 183 + 0, // 184 + 0, // 185 + 0, // 186 + 0, // 187 + 0, // 188 + 0, // 189 + 0, // 190 + 0, // 191 + 0, // 192 + 0, // 193 + 0, // 194 + 0, // 195 + 0, // 196 + 0, // 197 + 0, // 198 + 0, // 199 + 0, // 200 + 0, // 201 + 0, // 202 + 0, // 203 + 0, // 204 + 0, // 205 + 0, // 206 + 0, // 207 + 0, // 208 + 0, // 209 + 0, // 210 + 0, // 211 + 0, // 212 + 0, // 213 + 0, // 214 + 0, // 215 + 0, // 216 + 0, // 217 + 0, // 218 + 0, // 219 + 0, // 220 + 0, // 221 + 0, // 222 + 0, // 223 + 0, // 224 + 0, // 225 + 0, // 226 + 0, // 227 + 0, // 228 + 0, // 229 + 0, // 230 + 0, // 231 + 0, // 232 + 0, // 233 + 0, // 234 + 0, // 235 + 0, // 236 + 0, // 237 + 0, // 238 + 0, // 239 + 0, // 240 + 0, // 241 + 0, // 242 + 0, // 243 + 0, // 244 + 0, // 245 + 0, // 246 + 0, // 247 + 0, // 248 + 0, // 249 + 0, // 250 + 0, // 251 + 0, // 252 + 0, // 253 + 0, // 254 + 0, // 255 +}; diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..44be066 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328] +platform = atmelavr +board = nanoatmega328 +framework = arduino +lib_deps = + majicdesigns/MD_MAX72XX@^3.2.3 + adafruit/RTClib@^1.12.4 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5dc1275 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,630 @@ +#include +#include +#include +#include +#include +#include "fonts.h" + +#define VERSION F("v. 1.0.0") + +#define SERIAL_SPEED 9600 +#define SERIAL_BT_SPEED 9600 +#define SERIAL_BT_STATE_PIN 3 +#define SERIAL_BT_TX_PIN 4 +#define SERIAL_BT_RX_PIN 5 +#define LED_HW_TYPE MD_MAX72XX::FC16_HW +#define LED_CS_PIN 6 +#define LED_MODULES 4 + +#define STATE_TIME 0 +#define STATE_DATE 1 +#define STATE_TEMP 2 + +// config addresses +#define ADDR_CHECKSUM 0x0 +#define ADDR_INTENSITY 0x1 +#define ADDR_TIME_ENABLED_INTERVAL 0x2 +#define ADDR_DATE_ENABLED_INTERVAL 0x3 +#define ADDR_TEMP_ENABLED_INTERVAL 0x4 +#define ADDR_DATE_ENABLED 0x5 +#define ADDR_TEMP_ENABLED 0x6 +#define ADDR_SCROLL_SPEED 0x7 +#define ADDR_SCROLL_WAIT 0x8 + +// config default values +#define CFG_CHECKSUM 0x2 +#define CFG_INTENSITY_DEFAULT 0xf +#define CFG_TIME_ENABLED_INTERVAL_DEFAULT 35 +#define CFG_DATE_ENABLED_INTERVAL_DEFAULT 3 +#define CFG_TEMP_ENABLED_INTERVAL_DEFAULT 3 +#define CFG_DATE_ENABLED_DEFAULT 1 +#define CFG_TEMP_ENABLED_DEFAULT 1 +#define CFG_SCROLL_SPEED_DEFAULT 5 +#define CFG_SCROLL_WAIT_DEFAULT 40 + +// config shortcut macros +#define CFG_INTENSITY EEPROM.read(ADDR_INTENSITY) +#define CFG_TIME_ENABLED_INTERVAL (((uint16_t)EEPROM.read(ADDR_TIME_ENABLED_INTERVAL)) * 1000) +#define CFG_DATE_ENABLED_INTERVAL (((uint16_t)EEPROM.read(ADDR_DATE_ENABLED_INTERVAL)) * 1000) +#define CFG_TEMP_ENABLED_INTERVAL (((uint16_t)EEPROM.read(ADDR_TEMP_ENABLED_INTERVAL)) * 1000) +#define CFG_DATE_ENABLED EEPROM.read(ADDR_DATE_ENABLED) +#define CFG_TEMP_ENABLED EEPROM.read(ADDR_TEMP_ENABLED) +#define CFG_SCROLL_SPEED (((uint16_t)EEPROM.read(ADDR_SCROLL_SPEED)) * 10) +#define CFG_SCROLL_WAIT (((uint16_t)EEPROM.read(ADDR_SCROLL_WAIT)) * 100) + +// global variables +SoftwareSerial btSerial(SERIAL_BT_RX_PIN, SERIAL_BT_TX_PIN); +MD_MAX72XX led(LED_HW_TYPE, LED_CS_PIN, LED_MODULES); +RTC_DS3231 rtc; + +#define BUFFER_SIZE 20 +char buffer[BUFFER_SIZE]; + +uint8_t state = STATE_TIME; +unsigned long lastClockCall = 0; +unsigned long lastScrollCall = 0; + +// functions +uint8_t conv2d(const char *p, uint8_t maxLen) +{ + uint8_t v = 0; + for (uint8_t i = 0; *(p + i) != '\0' && maxLen-- > 0; i++) + { + v = (v * 10) + (*(p + i) - '0'); + } + return v; +} + +void setup() +{ + // setup pins + pinMode(SERIAL_BT_RX_PIN, INPUT); + pinMode(SERIAL_BT_TX_PIN, OUTPUT); + pinMode(LED_CS_PIN, OUTPUT); + + // setup serial + Serial.begin(SERIAL_SPEED); + + // setup bluetooth serial + btSerial.begin(SERIAL_BT_SPEED); + + // setup RTC + rtc.begin(); + + // setup led matrix + led.begin(); + + // clear flash + if (EEPROM.read(ADDR_CHECKSUM) != CFG_CHECKSUM) + { + EEPROM.write(ADDR_CHECKSUM, CFG_CHECKSUM); + EEPROM.write(ADDR_INTENSITY, CFG_INTENSITY_DEFAULT); + EEPROM.write(ADDR_TIME_ENABLED_INTERVAL, CFG_TIME_ENABLED_INTERVAL_DEFAULT); + EEPROM.write(ADDR_DATE_ENABLED_INTERVAL, CFG_DATE_ENABLED_INTERVAL_DEFAULT); + EEPROM.write(ADDR_TEMP_ENABLED_INTERVAL, CFG_TEMP_ENABLED_INTERVAL_DEFAULT); + EEPROM.write(ADDR_DATE_ENABLED, CFG_DATE_ENABLED_DEFAULT); + EEPROM.write(ADDR_TEMP_ENABLED, CFG_TEMP_ENABLED_DEFAULT); + EEPROM.write(ADDR_SCROLL_SPEED, CFG_SCROLL_SPEED_DEFAULT); + EEPROM.write(ADDR_SCROLL_WAIT, CFG_SCROLL_WAIT_DEFAULT); + } + + // clear led + led.clear(); + led.control(MD_MAX72XX::INTENSITY, CFG_INTENSITY); + led.update(MD_MAX72XX::OFF); // no automatic updates wanted + + lastClockCall = lastScrollCall = millis(); +} + +// command processing +#define COMMAND_SIZE 255 +char command[COMMAND_SIZE]; +bool clearCommand = true; + +char *readCommand() +{ + // if clearCommand flag was set, clear the command buffer before we receive new data + if (clearCommand) + { + clearCommand = false; + memset(command, 0, sizeof(command) / sizeof(command[0])); + } + + // current length of the command + uint8_t commandLength = strnlen(command, COMMAND_SIZE); + + // receive available bytes + while (btSerial.available() > 0) + { + // check for command overflow + if (commandLength == COMMAND_SIZE) + { + clearCommand = true; // clear the command buffer and pointers next time + return NULL; // no command is available for processing + } + + command[commandLength] = btSerial.read(); // receive one byte + if (command[commandLength] == '\r' || command[commandLength] == '\n') // the received byte is end-of-command identifier + { + // end of command was received, null terminate the string, the \r or \n is not needed as part of the command + command[commandLength] = '\0'; + if (strnlen(command, COMMAND_SIZE) == 0) // if the command length is 0, we did not receive anything else but the \r or \n, ignore + { + clearCommand = true; + return NULL; + } + + // first 3 bytes are the command (e.g. SET, ADJ, ), then 0 terminate, this is just for convenience + // possible arguments to the command will be from command[4] onwards. Maybe all of command[4] and onwards will be NULL if the command + // had no parameters. This needs to be checked in code consuming the command + command[3] = '\0'; + + // next time we need to start with a fresh command buffer + clearCommand = true; + + // return that we received a command + return command; + } + + commandLength++; // the command buffer is now one character longer + } + + // no complete command was received yet + return NULL; +} + +uint8_t charBuf[8]; +#define SCROLL_BUFF_SIZE 255 +char scrollBuf[SCROLL_BUFF_SIZE]; +bool showText(char *text, MD_MAX72XX::fontType_t *font, uint8_t tsd, uint16_t scrollSkip = 0) +{ + led.setFont(font); + uint16_t position = led.getColumnCount() - 1; + bool center = scrollSkip == 0; + + // render the text + while (*text != '\0') + { + uint8_t width = led.getChar((uint8_t)*text, 8, charBuf); + for (uint8_t i = 0; i < width; i++) + { + if (scrollSkip > 0) + { + // if we are still about to skip columns, reduce the scrollSkip and do nothing else + scrollSkip--; + } + else + { + // we are not skipping characters to scroll, so now we can actually draw + led.setColumn(position, charBuf[i]); + if (position == 0) + { + break; // there is no more space on the led matrix + } + else + { + position--; // move to the next column + } + } + } + + if (position == 0) + { + break; // there is no more space on the led matrix + } + + text++; // go to next char + + if (scrollSkip > 0) + { + scrollSkip--; + } + else + { + position--; // move to the next column (one column space between characters) + } + } + + if (scrollSkip > 0) + { + // we are done scrolling, but we had not enough text to draw anything (text completely scrolled out of the screen) + return true; + } + + // center the text if it is shorter than the display (centering is only enabled when not scrolling) + while (position > 0 && center) + { + led.transform(MD_MAX72XX::TSR); + position = min(position - 1, position - 2); // avoid underflow of position + } + + // translate downwards if requested + while (tsd-- > 0) + { + led.transform(MD_MAX72XX::TSD); + } + + return false; +} + +void showClock() +{ + if (rtc.lostPower()) + { + buffer[0] = buffer[1] = buffer[2] = buffer[3] = '-'; + buffer[4] = '\0'; + showText(buffer, NULL, 0); + lastClockCall = millis(); + return; + } + unsigned long diff = millis() - lastClockCall; + DateTime now = rtc.now(); + + if (state == STATE_TIME) // display the time + { + snprintf_P(buffer, BUFFER_SIZE, PSTR("%02d:%02d:%02d"), now.hour(), now.minute(), now.second()); + showText(buffer, font_fw_3x5, 1); + + if (diff >= CFG_TIME_ENABLED_INTERVAL) + { + lastClockCall = millis(); + if (CFG_DATE_ENABLED) + { + state = STATE_DATE; + } + else if (CFG_TEMP_ENABLED) + { + state = STATE_TEMP; + } + else + { + state = STATE_TIME; + } + } + } + else if (state == STATE_DATE) // display the date + { + snprintf_P(buffer, BUFFER_SIZE, PSTR("%02d.%02d."), now.day(), now.month()); + showText(buffer, font_fw_3x5, 1); + + if (diff >= CFG_DATE_ENABLED_INTERVAL) + { + lastClockCall = millis(); + if (CFG_TEMP_ENABLED) + { + state = STATE_TEMP; + } + else + { + state = STATE_TIME; + } + } + } + else if (state == STATE_TEMP) // display the temperature + { + snprintf_P(buffer, BUFFER_SIZE, PSTR("%02d °C"), (int)rtc.getTemperature()); + showText(buffer, font_fw_3x5, 1); + + if (diff >= CFG_TEMP_ENABLED_INTERVAL) + { + lastClockCall = millis(); + state = STATE_TIME; + } + } +} + +bool scrollEnabled = false; +uint16_t scrollSkip; +uint16_t scrollWait; + +void beginScrollText(const char *txt) +{ + + strncpy(scrollBuf, txt, SCROLL_BUFF_SIZE); + scrollEnabled = true; + scrollSkip = 0; + scrollWait = CFG_SCROLL_WAIT; + lastScrollCall = millis(); + showText(scrollBuf, NULL, 0); +} + +void endScrollText() +{ + scrollEnabled = false; + memset(scrollBuf, 0, sizeof(scrollBuf) / sizeof(scrollBuf[0])); +} + +void scrollText() +{ + if (!scrollEnabled) + { + return; + } + + unsigned long diff = millis() - lastScrollCall; + if (scrollWait > 0) + { + lastScrollCall = millis(); + scrollWait = scrollWait - min(scrollWait, diff); + if (scrollWait > 0) + { + showText(scrollBuf, NULL, 0, scrollSkip); + return; + } + } + + if (diff >= CFG_SCROLL_SPEED) + { + bool scrollDone = showText(scrollBuf, NULL, 0, scrollSkip++); + if (scrollDone) + { + scrollWait = CFG_SCROLL_WAIT; + scrollSkip = 0; + showText(scrollBuf, NULL, 0); + } + lastScrollCall = millis(); + } + else + { + showText(scrollBuf, NULL, 0, scrollSkip); + } +} + +bool blink = false; +bool blinkState = false; +unsigned long lastBlinkCall; + +void startBlink() +{ + if (blink == false) + { + blink = true; + lastBlinkCall = millis(); + } +} + +void stopBlink() +{ + blink = false; +} + +void doBlink() +{ + if (!blink) + { + return; + } + + if (blinkState) + { + led.transform(MD_MAX72XX::TINV); + } + + unsigned long diff = millis() - lastBlinkCall; + if (diff > 1000) + { + blinkState = !blinkState; + lastBlinkCall = millis(); + } +} + +DateTime *nextTime = 0; + +void loop() +{ + // clear the output + led.clear(); + + if (!digitalRead(SERIAL_BT_STATE_PIN)) // BT is not connected + { + showClock(); + + // indicate that there is no bluetooth connection + led.setPoint(7, 0, true); + } + else // BT is connected + { + // read command + char *cmd = readCommand(); + if (cmd != NULL) // command received? + { + if (strncmp_P(cmd, PSTR("MTC"), 3) == 0) + { + btSerial.print(F("MTC ")); + btSerial.println(VERSION); + } + else if (strncmp_P(cmd, PSTR("ADJ"), 3) == 0) + { + // Adjust the rtc clock, arguments from position 4 "2000-01-01T00:00:00" + cmd[24] = '\0'; + rtc.adjust(DateTime(&cmd[4])); + btSerial.println(F("ADJ")); + } + else if (strncmp_P(cmd, PSTR("SET"), 3) == 0) // SET a config value + { + // Set a setting value, arguments: + // - form position 4 to 5 contains the setting name (6 is the \0 byte) + // - from position 7 the new value begins + cmd[6] = '\0'; + if (strncmp_P(&cmd[4], PSTR("IN"), 2) == 0) // intensity + { + uint8_t intensity = conv2d(&cmd[7], 2); + intensity = (intensity == 0) ? intensity = 1 : ((intensity > MAX_INTENSITY) ? MAX_INTENSITY : intensity); + EEPROM.update(ADDR_INTENSITY, intensity); + led.control(MD_MAX72XX::INTENSITY, intensity); + btSerial.println(F("SET IN")); + } + else if (strncmp_P(&cmd[4], PSTR("TI"), 2) == 0) // time interval + { + uint8_t interval = conv2d(&cmd[7], 3); + EEPROM.update(ADDR_TIME_ENABLED_INTERVAL, interval); + btSerial.println(F("SET TI")); + } + else if (strncmp_P(&cmd[4], PSTR("DI"), 2) == 0) // date interval + { + uint8_t interval = conv2d(&cmd[7], 3); + EEPROM.update(ADDR_DATE_ENABLED_INTERVAL, interval); + btSerial.println(F("SET TI")); + } + else if (strncmp_P(&cmd[4], PSTR("CI"), 2) == 0) // temperature interval + { + uint8_t interval = conv2d(&cmd[7], 3); + EEPROM.update(ADDR_TEMP_ENABLED_INTERVAL, interval); + btSerial.println(F("SET CI")); + } + else if (strncmp_P(&cmd[4], PSTR("DE"), 2) == 0) // date enabled + { + EEPROM.update(ADDR_DATE_ENABLED, cmd[7] == '1'); + btSerial.println(F("SET DE")); + } + else if (strncmp_P(&cmd[4], PSTR("CE"), 2) == 0) // temperature enabled + { + EEPROM.update(ADDR_TEMP_ENABLED, cmd[7] == '1'); + btSerial.println(F("SET CE")); + } + else if (strncmp_P(&cmd[4], PSTR("SS"), 2) == 0) // scroll speed + { + uint8_t speed = conv2d(&cmd[7], 3); + EEPROM.update(ADDR_SCROLL_SPEED, speed); + btSerial.println(F("SET SS")); + } + else if (strncmp_P(&cmd[4], PSTR("SW"), 2) == 0) // scroll wait + { + uint8_t interval = conv2d(&cmd[7], 3); + EEPROM.update(ADDR_SCROLL_WAIT, interval); + btSerial.println(F("SET SW")); + } + else + { + btSerial.println(F("ERR")); + } + } + else if (strncmp_P(cmd, PSTR("GET"), 3) == 0) // GET a config value + { + // Get a setting value, arguments: + // - form position 4 to 5 contains the setting name (6 is the \0 byte) + cmd[6] = '\0'; + if (strncmp_P(&cmd[4], PSTR("IN"), 2) == 0) // intensity + { + btSerial.print(F("GET IN ")); + btSerial.println(CFG_INTENSITY); + } + else if (strncmp_P(&cmd[4], PSTR("TI"), 2) == 0) // time interval + { + btSerial.print(F("GET TI ")); + btSerial.println(CFG_TIME_ENABLED_INTERVAL / 1000); + } + else if (strncmp_P(&cmd[4], PSTR("DI"), 2) == 0) // date interval + { + btSerial.print(F("GET DI ")); + btSerial.println(CFG_DATE_ENABLED_INTERVAL / 1000); + } + else if (strncmp_P(&cmd[4], PSTR("CI"), 2) == 0) // temperature interval + { + btSerial.print(F("GET CI ")); + btSerial.println(CFG_TEMP_ENABLED_INTERVAL / 1000); + } + else if (strncmp_P(&cmd[4], PSTR("DE"), 2) == 0) // date enabled + { + btSerial.print(F("GET DE ")); + btSerial.println(CFG_DATE_ENABLED); + } + else if (strncmp_P(&cmd[4], PSTR("CE"), 2) == 0) // temperature enabled + { + btSerial.print(F("GET CE ")); + btSerial.println(CFG_TEMP_ENABLED); + } + else if (strncmp_P(&cmd[4], PSTR("SS"), 2) == 0) // scroll speed + { + btSerial.print(F("GET SS ")); + btSerial.println(CFG_SCROLL_SPEED / 10); + } + else if (strncmp_P(&cmd[4], PSTR("SW"), 2) == 0) // scroll wait + { + btSerial.print(F("GET SW ")); + btSerial.println(CFG_TEMP_ENABLED_INTERVAL / 100); + } + else + { + btSerial.println(F("ERR")); + } + } + else if (strncmp_P(cmd, PSTR("SCR"), 3) == 0) // Scroll Text + { + // position 4 is set to 0 and from position 5 the text to scroll starts + // it might be empty when we want to stop the scroll + if (cmd[4] == '\0') + { + endScrollText(); // do not scroll text anymore + } + else + { + beginScrollText(&cmd[4]); + } + btSerial.println(F("SCR")); + } + else if (strncmp_P(cmd, PSTR("NXT"), 3) == 0) // Next Time + { + // position 4 is set to 0 and from position 5 the time will start + // Next appointmen starts at this time. Format "2000-01-01T00:00:00" + if (nextTime != 0) + { + delete nextTime; + } + cmd[24] = '\0'; + nextTime = new DateTime(&cmd[4]); + + btSerial.println(F("NXT")); + } + else // everything else is unknown (ERR) + { + btSerial.println(F("ERR")); + } + } + + if (strnlen(scrollBuf, SCROLL_BUFF_SIZE) > 0) + { + scrollText(); + } + else + { + showClock(); + } + + if (nextTime != 0) + { + TimeSpan ts = *nextTime - rtc.now(); + uint8_t devices = led.getDeviceCount(); + if (ts.totalseconds() < -30) + { + // time is up + // 30 seconds past the time, stop blinking and delete time + delete nextTime; + nextTime = 0; + stopBlink(); + // also now we can stop scrolling + memset(scrollBuf, 0, sizeof(scrollBuf) / sizeof(scrollBuf[0])); + } + else if (ts.totalseconds() < 30) + { + // only 30 seconds left, alarm by blinking + startBlink(); + } + else if (ts.totalseconds() < ((devices + 1) * 60)) + { + // only some minutes left, start warning + for (uint16_t device = 0; device < devices; device++) + { + if (ts.totalseconds() < (device + 1) * 60) + { + led.transform(device, device, MD_MAX72XX::TINV); + } + } + } + } + } + + doBlink(); + led.update(); + delay(5); // sleep some tme +}