├── .github └── stale.yml ├── .gitignore ├── .travis.yml ├── EspaperClient ├── Board.cpp ├── Board.h ├── EspaperClient.ino ├── EspaperParser.cpp ├── EspaperParser.h ├── artwork.h ├── configportal.h ├── settings.h ├── timezones.h └── timezones.txt ├── LICENSE ├── README.md ├── include └── README ├── lib ├── ADXL345 │ └── src │ │ ├── ADXL345.cpp │ │ └── ADXL345.h └── README ├── pio └── hooks.py ├── platformio.ini └── test └── README /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | target/* 5 | .vscode 6 | .clang_complete 7 | .gcc-flags.json 8 | firmware.map 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /EspaperClient/Board.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include "Board.h" 25 | 26 | 27 | 28 | BoardClass Board; 29 | 30 | BoardClass::BoardClass() { 31 | 32 | } 33 | 34 | String BoardClass::getChipId() { 35 | 36 | #if defined(ESP8266) 37 | return String(ESP.getChipId()); 38 | #elif defined(ESP32) 39 | uint64_t chipId = ESP.getEfuseMac(); 40 | uint32_t low = chipId % 0xFFFFFFFF; 41 | uint32_t high = (chipId >> 32) % 0xFFFFFFFF; 42 | return String(low) + String(high); 43 | #endif 44 | } 45 | 46 | void BoardClass::deepSleep(uint64_t sleepSeconds) { 47 | #if defined(ESP8266) 48 | uint64_t sleepMicroSeconds = sleepSeconds * 1000000; 49 | // Take 80% of maxDeepSleep, since we suspect it to be too optimistic in some cases 50 | // This would cause some devices to never wake up again on their own. 51 | uint64_t maxSleepMicroSeconds = ESP.deepSleepMax() * 0.8; 52 | 53 | uint64_t calculatedSleepMicroSeconds = maxSleepMicroSeconds < sleepMicroSeconds ? maxSleepMicroSeconds : sleepMicroSeconds; 54 | Serial.printf_P(PSTR("Going to sleep for: %d[s]\n"), calculatedSleepMicroSeconds / 1000000); 55 | ESP.deepSleep(calculatedSleepMicroSeconds, WAKE_RF_DISABLED); 56 | #elif defined(ESP32) 57 | Serial.printf_P(PSTR("Going to sleep for: %d[s]\n"), sleepSeconds); 58 | esp_sleep_enable_timer_wakeup(sleepSeconds * 1000000); 59 | esp_deep_sleep_start(); 60 | #endif 61 | } 62 | 63 | boolean BoardClass::connectWifi(String wifiSsid, String wifiPassword) { 64 | // Wake up WiFi 65 | #if defined(ESP8266) 66 | wifi_fpm_do_wakeup(); 67 | wifi_fpm_close(); 68 | 69 | Serial.println("Reconnecting"); 70 | wifi_set_opmode(STATION_MODE); 71 | wifi_station_connect(); 72 | #elif defined(ESP32) 73 | 74 | #endif 75 | 76 | 77 | // Disable the WiFi persistence. The ESP8266 will not load and save WiFi settings in the flash memory. 78 | // https://www.bakke.online/index.php/2017/05/21/reducing-wifi-power-consumption-on-esp8266-part-1/ 79 | WiFi.persistent(false); 80 | 81 | if (WiFi.status() == WL_CONNECTED) return true; 82 | 83 | // this 3 lines for a fix IP-address (and faster connection) 84 | /*IPAddress ip(192, 168, 0, 60); 85 | IPAddress gateway(192, 168, 0, 1); 86 | IPAddress subnet(255, 255, 255, 0); 87 | IPAddress dns(8, 8, 8, 8); 88 | WiFi.config (ip, gateway, subnet, dns);*/ 89 | 90 | if (wifiPassword == "") { 91 | Serial.println("Only SSID without password"); 92 | WiFi.begin(wifiSsid.c_str()); 93 | } else { 94 | WiFi.begin(wifiSsid.c_str(), wifiPassword.c_str()); 95 | } 96 | 97 | int i = 0; 98 | uint32_t startTime = millis(); 99 | Serial.print("WiFi connect"); 100 | while (WiFi.status() != WL_CONNECTED) { 101 | delay(100); 102 | i++; 103 | if (millis() - startTime > 20000) { 104 | Serial.println("\nFailed to connect to WiFi"); 105 | return false; 106 | } 107 | Serial.print("."); 108 | } 109 | Serial.println(WiFi.localIP()); 110 | return true; 111 | } 112 | 113 | void BoardClass::sleepWifi() { 114 | 115 | // https://github.com/esp8266/Arduino/issues/4082 116 | delay(200); 117 | WiFi.mode(WIFI_OFF); 118 | 119 | #if defined(ESP8266) 120 | delay(10); 121 | WiFi.forceSleepBegin(); 122 | #elif defined(ESP32) 123 | 124 | #endif 125 | delay(100); 126 | } 127 | 128 | uint32_t BoardClass::getBattery() { 129 | #if defined(ESP8266) 130 | return analogRead(A0); 131 | #elif defined(ESP32) 132 | // n/a, see https://github.com/espressif/arduino-esp32/issues/469 133 | 134 | // actually for TTGO T5 V2.2 board, not ESP32 in general 135 | return analogRead(A7); 136 | #endif 137 | 138 | } 139 | 140 | uint32_t BoardClass::getFreeSPIFFSBytes() { 141 | 142 | #if defined(ESP8266) 143 | FSInfo fs_info; 144 | SPIFFS.info(fs_info); 145 | return fs_info.totalBytes - fs_info.usedBytes; 146 | #elif defined(ESP32) 147 | return SPIFFS.totalBytes() - SPIFFS.usedBytes(); 148 | #endif 149 | } 150 | 151 | uint32_t BoardClass::getTotalSPIFFSBytes() { 152 | #if defined(ESP8266) 153 | FSInfo fs_info; 154 | SPIFFS.info(fs_info); 155 | return fs_info.totalBytes; 156 | #elif defined(ESP32) 157 | return SPIFFS.totalBytes(); 158 | #endif 159 | } 160 | 161 | WiFiClient* BoardClass::createWifiClient(const char *rootCertificate) { 162 | // it would be correct to do this via 163 | // #ifdef DEV_ENV 164 | // as it's a compile-time configuration rather than a runtime configuration but including 165 | // #include "settings.h" leads to odd compile errors 166 | #ifdef USE_SECURE_WIFI_CLIENT 167 | Serial.println("Using secure WiFi client"); 168 | WiFiClientSecure *client = new WiFiClientSecure(); 169 | Serial.println("[HTTP] configuring server root cert in client"); 170 | #if defined(ESP8266) 171 | client->setTrustAnchors(new X509List(rootCertificate)); 172 | /*bool mfln = client->probeMaxFragmentLength(url.host, url.port, 1024); 173 | Serial.printf("MFLN supported: %s\n", mfln ? "yes" : "no"); 174 | if (mfln) { 175 | client->setBufferSizes(1024, 1024); 176 | }*/ 177 | #elif defined(ESP32) 178 | client->setCACert(rootCertificate); 179 | #endif 180 | return client; 181 | #else 182 | Serial.println("Using non-secure WiFi client"); 183 | return new WiFiClient(); 184 | #endif 185 | 186 | } -------------------------------------------------------------------------------- /EspaperClient/Board.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef BOARD_H 25 | #define BOARD_H 26 | 27 | #include 28 | #include "settings.h" 29 | 30 | #if defined(ESP8266) 31 | extern "C" { 32 | #include "user_interface.h" // Required for wifi_station_connect() to work 33 | } 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #define HTTP_UPDATER ESPhttpUpdate 40 | #elif defined(ESP32) 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #define HTTP_UPDATER httpUpdate 47 | #endif 48 | 49 | class BoardClass { 50 | 51 | public: 52 | BoardClass(); 53 | 54 | String getChipId(); 55 | 56 | void deepSleep(uint64_t sleepSeconds); 57 | 58 | boolean connectWifi(String wifiSsid, String wifiPassword); 59 | 60 | static void sleepWifi(); 61 | 62 | uint32_t getBattery(); 63 | 64 | uint32_t getFreeSPIFFSBytes(); 65 | 66 | uint32_t getTotalSPIFFSBytes(); 67 | 68 | WiFiClient* createWifiClient(const char *rootCertificate); 69 | 70 | }; 71 | 72 | extern BoardClass Board; 73 | 74 | #endif //BOARD_H -------------------------------------------------------------------------------- /EspaperClient/EspaperClient.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #define xstr(a) str(a) 25 | #define str(a) #a 26 | 27 | #include 28 | #include 29 | #include "settings.h" 30 | 31 | #if defined(EPD29) 32 | #include 33 | #elif defined(EPD42) 34 | #include 35 | #elif defined(EPD75) 36 | #include 37 | #else 38 | #error Please define in settings.h for which device you want to compile: EPD29 or EPD42 39 | #endif 40 | #include "configportal.h" 41 | #include "EspaperParser.h" 42 | 43 | #define MINI_BLACK 0 44 | #define MINI_WHITE 1 45 | 46 | uint16_t palette[] = {MINI_BLACK, MINI_WHITE}; 47 | 48 | #define BITS_PER_PIXEL 1 49 | 50 | #ifdef EPD29 51 | EPD_WaveShare29 epd(CS, RST, DC, BUSY); 52 | #define DEVICE_ROTATION 1 53 | #endif 54 | #ifdef EPD42 55 | EPD_WaveShare42 epd(CS, RST, DC, BUSY); 56 | #define DEVICE_ROTATION 2 57 | #endif 58 | #ifdef EPD75 59 | EPD_WaveShare75 epd(CS, RST, DC, BUSY); 60 | #define DEVICE_ROTATION 0 61 | #endif 62 | 63 | time_t startTime; 64 | int startMillis; 65 | 66 | MiniGrafx gfx = MiniGrafx(&epd, BITS_PER_PIXEL, palette); 67 | 68 | void showMessageOverScreen(String message) { 69 | gfx.init(); 70 | gfx.fillBuffer(1); 71 | gfx.drawPalettedBitmapFromFile(0, 0, "/screen"); 72 | gfx.setColor(MINI_BLACK); 73 | gfx.fillRect(0, 0, gfx.getWidth(), 15); 74 | gfx.setColor(MINI_WHITE); 75 | gfx.setTextAlignment(TEXT_ALIGN_LEFT); 76 | gfx.setFont(ArialMT_Plain_10); 77 | gfx.drawString(2, 1, message); 78 | gfx.commit(); 79 | gfx.freeBuffer(); 80 | } 81 | 82 | void showMessage(String message) { 83 | gfx.init(); 84 | gfx.fillBuffer(1); 85 | gfx.setColor(MINI_BLACK); 86 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 87 | gfx.setFont(ArialMT_Plain_16); 88 | gfx.drawStringMaxWidth(gfx.getWidth() / 2, STD_MESSAGE_Y_POSITION, gfx.getWidth() * MAX_TEXT_WIDTH_FACTOR, message); 89 | gfx.commit(); 90 | gfx.freeBuffer(); 91 | } 92 | 93 | bool initTime() { 94 | //#ifndef DEV_ENV 95 | Serial.print("NTP sync"); 96 | String timezoneCode = getTimeZoneSettings(); 97 | configTime(0, 0, getNtpServer(0).c_str(), getNtpServer(1).c_str(), getNtpServer(2).c_str()); 98 | setenv("TZ", timezoneCode.c_str(), 0); 99 | tzset(); 100 | 101 | // wait until NTP time was correctly syncronized 102 | uint8_t retryCounter = 0; 103 | time_t now; 104 | uint32_t ntpStartMillis = millis(); 105 | uint16_t ntpTimeoutMillis = NTP_SYNC_TIMEOUT_SECONDS * 1000; 106 | while((now = time(nullptr)) < NTP_MIN_VALID_EPOCH) { 107 | uint32_t runtimeMillis = millis() - ntpStartMillis; 108 | if (runtimeMillis > ntpTimeoutMillis) { 109 | Serial.printf("\nFailed to sync time through NTP. Giving up after %dms.\n", runtimeMillis); 110 | return false; 111 | } 112 | Serial.print("."); 113 | if (retryCounter > 3) { 114 | Serial.println("Re-initializing NTP"); 115 | configTime(0, 0, getNtpServer(0).c_str(), getNtpServer(1).c_str(), getNtpServer(2).c_str()); 116 | retryCounter = 0; 117 | } 118 | retryCounter++; 119 | delay(300); 120 | } 121 | Serial.println(); 122 | Serial.printf("Current time: %d\n", now); 123 | startTime = now; 124 | startMillis = millis(); 125 | //#endif 126 | 127 | return true; 128 | } 129 | 130 | String buildRegistrationRequestBody() { 131 | return String(F("{\"macAddress\": \"")) + WiFi.macAddress() + String(F("\", \"deviceType\": \"")) + xstr(DEVICE_TYPE) + String(F("\", \"timeZone\": \"")) + getTimeZoneName() + String(F("\"}")); 132 | } 133 | 134 | String buildOptionalHeaderFields(DeviceData *deviceData) { 135 | String EOL = String(F("\r\n")); 136 | return String(F("X-ESPAPER-TOTAL-DEVICE-STARTS: ")) + String(deviceData->totalDeviceStarts) + EOL + 137 | String(F("X-ESPAPER-SUCCESSFUL-DEVICE-STARTS: ")) + String(deviceData->successfulDeviceStarts) + EOL + 138 | String(F("X-ESPAPER-LAST-NTP-SYNC-TIME: ")) + String(deviceData->lastNtpSyncTime) + EOL + 139 | String(F("X-ESPAPER-STARTS-WITHOUT-NTP-SYNC: ")) + String(deviceData->startsWithoutNtpSync) + EOL + 140 | String(F("X-ESPAPER-LAST-CYCLE-DURATION: ")) + String(deviceData->lastCycleDuration) + EOL; 141 | } 142 | 143 | void formatFileSystem() { 144 | showMessage(String(F("File System error.\nFormatting File System\nPlease wait..."))); 145 | Serial.println(F("Formating FS..")); 146 | SPIFFS.format(); 147 | Serial.println(F("Done Formatting")); 148 | SPIFFS.begin(); 149 | showMessage(String(F("Done formatting..."))); 150 | } 151 | 152 | 153 | void startDeviceSleep(uint32_t sleepSeconds, uint32_t sleepUntilEpoch) { 154 | Serial.printf_P(PSTR("Free mem: %d.\n"), ESP.getFreeHeap()); 155 | epd.Sleep(); 156 | int currentMillis = millis(); 157 | time_t now = startTime + ((currentMillis - startMillis) / 1000); 158 | Serial.printf_P(PSTR("Start millis: %d, start time: %d, current millis: %d -> now: %d\n"), startMillis, startTime, currentMillis, now); 159 | uint64_t effectiveSleepSeconds; 160 | if (sleepUntilEpoch == 0) { 161 | // use the local client configuration if server sends "nothing" (0 -> undefined) 162 | Serial.printf_P(PSTR("'sleepUntilEpoch' is 0. Using configured default update interval of %dmin.\n"), UPDATE_INTERVAL_MINS); 163 | effectiveSleepSeconds = UPDATE_INTERVAL_MINS * 60; 164 | } else { 165 | effectiveSleepSeconds = sleepUntilEpoch - now; 166 | // If 'now' is completely off in either direction, for whatever reason, the device would not sleep at all or sleep 167 | // for far too long. It should be roughly the same value as 'sleepSeconds' provided by the server. 168 | if (abs(sleepSeconds - effectiveSleepSeconds) > 600) { 169 | effectiveSleepSeconds = sleepSeconds; 170 | } 171 | } 172 | Board.deepSleep(effectiveSleepSeconds); 173 | } 174 | 175 | EspaperParser::ResourceResponse fetchAndDrawScreen(EspaperParser *parser, DeviceData *deviceData) { 176 | EspaperParser::ResourceResponse response = parser->getAndDrawScreen(SERVER_API_DEVICES_PATH + "/" + DEVICE_ID + "/screen", buildOptionalHeaderFields(deviceData), &BoardClass::sleepWifi); 177 | if (response.httpCode == 410) { 178 | saveDeviceRegistration("", ""); 179 | ESP.restart(); 180 | } 181 | if (response.httpCode == HTTP_INTERNAL_CODE_UPGRADE_CLIENT) { 182 | Serial.println(F("Firmware upgrade requested by server")); 183 | deviceData->actionAfterReboot = ACTION_AFTER_REBOOT_UPGRADE_FIRMWARE; 184 | saveDeviceData(deviceData); 185 | ESP.restart(); 186 | } else { 187 | deviceData->actionAfterReboot = ACTION_AFTER_REBOOT_UPDATE_SCREEN; 188 | } 189 | return response; 190 | } 191 | 192 | void updateFirmware(EspaperParser *parser, DeviceData *deviceData) { 193 | Serial.printf_P(PSTR("Current free heap: %d\n"), ESP.getFreeHeap()); 194 | parser->updateFirmware(SERVER_API_DEVICES_PATH + "/" + DEVICE_ID + "/firmware"); 195 | deviceData->actionAfterReboot = ACTION_AFTER_REBOOT_UPDATE_SCREEN; 196 | saveDeviceData(deviceData); 197 | Serial.println(F("Updated firmware. Restarting.")); 198 | ESP.restart(); 199 | } 200 | 201 | void registerDevice(EspaperParser *parser, DeviceData *deviceData) { 202 | Serial.printf_P(PSTR("Free mem: %d\n"), ESP.getFreeHeap()); 203 | Serial.println(F("Device id and/or secret are not set yet -> registering device with server now")); 204 | EspaperParser::DeviceIdAndSecret d = parser->registerDevice(SERVER_API_DEVICES_PATH, buildRegistrationRequestBody()); 205 | if (d.deviceId == "-1") { 206 | Serial.println(F("Device registration failed")); 207 | showMessage(String(F("Sorry, device registration failed. Please ensure the device has access to ")) + SERVER_URL + String(F(" and try again. Else contact ThingPulse Support and provide the device MAC address: ")) + WiFi.macAddress()); 208 | } else { 209 | Serial.println(F("Device registration successful, now fetching OTP screen")); 210 | saveDeviceRegistration(d.deviceId, d.deviceSecret); 211 | fetchAndDrawScreen(parser, deviceData); 212 | } 213 | } 214 | 215 | void setup() { 216 | Serial.begin(115200); 217 | Serial.println(); 218 | Serial.println(F("Boot sequence arrived in setup()")); 219 | Serial.printf_P(PSTR("******** Client Version: \"%s\" ********\n"), xstr(CLIENT_VERSION)); 220 | Serial.printf_P(PSTR("******** Device Type: \"%s\" ********\n"), xstr(DEVICE_TYPE)); 221 | Serial.printf_P(PSTR("******** Screen Type: \"%s\" ********\n"), xstr(SCREEN_TYPE)); 222 | Serial.printf_P(PSTR("Display Settings. CS: %d, RST: %d, DC: %d, BUSY: %d\n"), CS, RST, DC, BUSY); 223 | 224 | 225 | // Turn WiFi off until we really need it 226 | Board.sleepWifi(); 227 | 228 | Serial.printf_P(PSTR("Current free heap: %d\n"), ESP.getFreeHeap()); 229 | 230 | gfx.setRotation(DEVICE_ROTATION); 231 | gfx.setFastRefresh(true); 232 | gfx.setFastRefresh(false); 233 | 234 | pinMode(USR_BTN, INPUT_PULLUP); 235 | int btnState = digitalRead(USR_BTN); 236 | Serial.println(F("Checking FS")); 237 | boolean isMounted = SPIFFS.begin(); 238 | if (!isMounted) { 239 | formatFileSystem(); 240 | } 241 | boolean isConfigLoaded = loadConfig(); 242 | 243 | DeviceData deviceData; 244 | loadDeviceData(&deviceData); 245 | if (deviceData.successfulDeviceStarts >= 1) { 246 | deviceData.totalDeviceStarts = 0; 247 | deviceData.successfulDeviceStarts = 0; 248 | } 249 | deviceData.totalDeviceStarts++; 250 | saveDeviceData(&deviceData); 251 | 252 | Serial.printf(PSTR("Button state: %d\n"), btnState); 253 | if (btnState == LOW || !isConfigLoaded) { 254 | startConfigPortal(&gfx); 255 | } else { 256 | Serial.printf_P(PSTR("\n\n***Time before connecting to WiFi %d\n"), millis()); 257 | boolean success = Board.connectWifi(WIFI_SSID, WIFI_PASS); 258 | EspaperParser::ResourceResponse response; 259 | // Initialize to restarting in 60s, comes into effect if WiFi or NTP connect fails (server cannot 260 | // deliver an effective value). In that case we probably want a fast retry rather than e.g. waiting 261 | // for the default 20min or so. 262 | response.sleepSeconds = 60; 263 | response.sleepUntilEpoch = 60; 264 | if (success) { 265 | delay(200); 266 | boolean timeInit = initTime(); 267 | if (timeInit) { 268 | EspaperParser parser(&gfx, rootCaCert, SERVER_URL, DEVICE_SECRET, String(xstr(CLIENT_VERSION))); 269 | Serial.printf_P(PSTR("\n\n***Time before going to fetching data %d\n"), millis()); 270 | deviceData.successfulDeviceStarts++; 271 | deviceData.lastNtpSyncTime = time(nullptr); 272 | if (deviceData.actionAfterReboot == ACTION_AFTER_REBOOT_UPGRADE_FIRMWARE) { 273 | showMessageOverScreen(String(F("Firmware upgrade in progress..."))); 274 | updateFirmware(&parser, &deviceData); 275 | } else if (isDeviceRegistered()) { 276 | response = fetchAndDrawScreen(&parser, &deviceData); 277 | } else { 278 | registerDevice(&parser, &deviceData); 279 | } 280 | deviceData.startsWithoutNtpSync = 0; 281 | } else { 282 | showMessageOverScreen(String(F("Failed to update time from internet (NTP)"))); 283 | deviceData.startsWithoutNtpSync++; 284 | } 285 | } else { 286 | showMessageOverScreen(String(F("Failed to connect to WiFi '")) + WIFI_SSID + String(F("'"))); 287 | } 288 | 289 | deviceData.lastCycleDuration = millis(); 290 | saveDeviceData(&deviceData); 291 | startDeviceSleep(response.sleepSeconds, response.sleepUntilEpoch); 292 | } 293 | } 294 | 295 | void loop() { 296 | 297 | } 298 | -------------------------------------------------------------------------------- /EspaperClient/EspaperParser.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include "EspaperParser.h" 25 | 26 | EspaperParser::EspaperParser(MiniGrafx *gfx, const char *rootCertificate, String baseUrl, String deviceSecret, String clientVersion) { 27 | this->gfx = gfx; 28 | this->rootCertificate = rootCertificate; 29 | this->baseUrl = baseUrl; 30 | this->deviceSecret = deviceSecret; 31 | this->clientVersion = clientVersion; 32 | } 33 | 34 | EspaperParser::DeviceIdAndSecret EspaperParser::registerDevice(String requestPath, String jsonData) { 35 | DeviceIdAndSecret result; 36 | // set default values signifying an error condition 37 | result.deviceId = "-1"; 38 | result.deviceSecret = "-2"; 39 | 40 | Url url = this->dissectUrl(this->baseUrl + requestPath); 41 | Serial.printf("Free mem: %d", ESP.getFreeHeap()); 42 | WiFiClient *client = this->createWifiClient(url); 43 | 44 | Serial.println("[HTTP] begin..."); 45 | 46 | Serial.printf("Connecting to %s:%d\n", url.host.c_str(), url.port); 47 | client->connect(url.host.c_str(), url.port); 48 | if (!client->connected()) { 49 | Serial.println("*** Can't connect. ***\n-------"); 50 | return result; 51 | } 52 | 53 | String EOL = "\r\n"; 54 | 55 | String request = "POST " + url.path + " HTTP/1.1\r\n" + 56 | "Host: " + url.host + "\r\n" + 57 | "User-Agent: ESPaperClient/1.0\r\n" + 58 | "Content-Type: application/json\r\n" + 59 | "Content-Length: " + jsonData.length() + "\r\n" + 60 | "X-ESPAPER-CLIENT-VERSION: " + this->clientVersion + EOL + 61 | "X-ESPAPER-BATTERY: " + String(Board.getBattery()) + EOL + 62 | "X-ESPAPER-WIFI-RSSI: " + String(WiFi.RSSI()) + EOL + 63 | "Connection: close\r\n\r\n" + jsonData; 64 | 65 | Serial.println("Sending request: " + request); 66 | 67 | client->print(request); 68 | 69 | unsigned long timeout = millis(); 70 | while (client->available() == 0) { 71 | if (millis() - timeout > 10000) { 72 | Serial.println(">>> Client Timeout !"); 73 | client->stop(); 74 | delete client; 75 | return result; 76 | } 77 | } 78 | 79 | long lastUpdate = millis(); 80 | 81 | int httpCode = 0; 82 | while (client->available() || client->connected()) { 83 | String line = client->readStringUntil('\n'); 84 | int endOfKey = line.indexOf(':'); 85 | String key = ""; 86 | if (endOfKey > 0) { 87 | key = line.substring(0, endOfKey); 88 | key.toUpperCase(); 89 | } 90 | Serial.println(line); 91 | 92 | if (line.startsWith("HTTP/1.")) { 93 | httpCode = line.substring(9, line.indexOf(' ', 9)).toInt(); 94 | Serial.printf("HTTP Code: %d\n", httpCode); 95 | } 96 | if (key == "X-ESPAPER-SECRET") { 97 | this->deviceSecret = line.substring(18, line.indexOf('\r')); 98 | result.deviceSecret = this->deviceSecret; 99 | } 100 | if (key == "X-ESPAPER-DEVICE-ID") { 101 | result.deviceId = line.substring(21, line.indexOf('\r')); 102 | } 103 | if (line == "\r" || line == "\r\n") { 104 | Serial.println("headers received"); 105 | Serial.println("Parsed values:"); 106 | Serial.printf("- HTTP code: %d\n", httpCode); 107 | Serial.printf("- Device id: %s\n", result.deviceId.c_str()); 108 | Serial.printf("- Device secret: %s\n", result.deviceSecret.c_str()); 109 | break; 110 | } 111 | if (millis() - lastUpdate > 500) { 112 | lastUpdate = millis(); 113 | Serial.printf("-"); 114 | } 115 | 116 | } 117 | 118 | if (!client->available() == 0) { 119 | Serial.println("Client disconnected before body parsing"); 120 | } 121 | delete client; 122 | return result; 123 | } 124 | 125 | EspaperParser::ResourceResponse EspaperParser::getAndDrawScreen(String requestPath, String optionalHeaderFields, EspaperParser::HandlerFunction downloadCompletedFunction) { 126 | 127 | String url = this->baseUrl + requestPath; 128 | EspaperParser::ResourceResponse response = downloadResource(this->dissectUrl(url), "/screen", optionalHeaderFields); 129 | downloadCompletedFunction(); 130 | 131 | if (response.httpCode == HTTP_INTERNAL_CODE_UPGRADE_CLIENT) { 132 | // In case of update return before the framebuffer is allocated, since it fragments the 133 | // memory too much 134 | Serial.println(F("Update requested")); 135 | return response; 136 | } 137 | 138 | gfx->init(); 139 | gfx->fillBuffer(1); 140 | 141 | if (response.httpCode == 200) { 142 | gfx->drawPalettedBitmapFromFile(0, 0, "/screen"); 143 | } else { 144 | uint16_t halfWidth = gfx->getWidth() / 2; 145 | uint16_t maxTextWidth = gfx->getWidth() * 0.85; 146 | 147 | gfx->setColor(0); 148 | gfx->setTextAlignment(TEXT_ALIGN_CENTER); 149 | gfx->setFont(ArialMT_Plain_16); 150 | 151 | String message = ""; 152 | switch(response.httpCode) { 153 | case -2: message = String(F("Connection to the server could not be established or it timed out. Verify this device has access to the internet. Will retry in 1min.")); 154 | response.sleepSeconds = 60; 155 | response.sleepUntilEpoch = 60; // anything beyond 0 is ok, see startDeviceSleep() in sketch 156 | break; 157 | // TODO: "Starting registration process." is not correct, this parser can't possibly know that... 158 | case 410: message = String(F("This device is unknown to the server. It might have been deleted. Starting registration process.")); 159 | break; 160 | default: message = String(F("Error communicating with the server. HTTP status: ")) + String(response.httpCode) + ". Will retry in 1min."; 161 | response.sleepSeconds = 60; 162 | response.sleepUntilEpoch = 60; // anything beyond 0 is ok, see startDeviceSleep() in sketch 163 | break; 164 | } 165 | 166 | gfx->drawStringMaxWidth(halfWidth, 20, maxTextWidth, message); 167 | } 168 | Serial.println(F("Writting image to screen")); 169 | gfx->commit(); 170 | Serial.println(F("De-allocating frame buffer")); 171 | gfx->freeBuffer(); 172 | 173 | return response; 174 | } 175 | 176 | EspaperParser::Url EspaperParser::dissectUrl(String url) { 177 | Url result; 178 | 179 | // Code from https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp 180 | // check for : (http: or https: 181 | int index = url.indexOf(':'); 182 | if (index < 0) { 183 | Serial.print("[HTTP-Client][begin] failed to parse protocol\n"); 184 | } 185 | 186 | String _protocol = url.substring(0, index); 187 | url.remove(0, (index + 3)); // remove http:// or https:// 188 | 189 | index = url.indexOf('/'); 190 | String host = url.substring(0, index); 191 | String _host; 192 | int _port = 0; 193 | url.remove(0, index); // remove host part 194 | 195 | // get Authorization 196 | index = host.indexOf('@'); 197 | if (index >= 0) { 198 | // auth info 199 | String auth = host.substring(0, index); 200 | host.remove(0, index + 1); // remove auth part including @ 201 | } 202 | 203 | // get port 204 | index = host.indexOf(':'); 205 | if (index >= 0) { 206 | _host = host.substring(0, index); // hostname 207 | host.remove(0, (index + 1)); // remove hostname + : 208 | _port = host.toInt(); // get port 209 | } else { 210 | _host = host; 211 | _port = _protocol == "http" ? 80 : 443; 212 | } 213 | String _path = url; 214 | 215 | 216 | result.protocol = _protocol; 217 | result.host = _host; 218 | result.port = _port; 219 | result.path = _path; 220 | 221 | return result; 222 | } 223 | 224 | WiFiClient* EspaperParser::createWifiClient(Url url) { 225 | // it would be correct to do this via 226 | // #ifdef DEV_ENV 227 | // as it's a compile-time configuration rather than a runtime configuration but including 228 | // #include "settings.h" leads to odd compile errors 229 | if (String("http").equals(url.protocol)) { 230 | Serial.println("Using non-secure WiFi client"); 231 | return new WiFiClient(); 232 | } else { 233 | Serial.println("Using secure WiFi client"); 234 | WiFiClientSecure *client = new WiFiClientSecure(); 235 | Serial.println("[HTTP] configuring server root cert in client"); 236 | #if defined(ESP8266) 237 | client->setTrustAnchors(new X509List(this->rootCertificate)); 238 | bool mfln = client->probeMaxFragmentLength(url.host, url.port, 1024); 239 | Serial.printf("MFLN supported: %s\n", mfln ? "yes" : "no"); 240 | if (mfln) { 241 | client->setBufferSizes(1024, 1024); 242 | } 243 | #elif defined(ESP32) 244 | client->setCACert(this->rootCertificate); 245 | #endif 246 | return client; 247 | } 248 | 249 | } 250 | 251 | EspaperParser::ResourceResponse EspaperParser::downloadResource(Url url, String fileName, String optionalHeaderFields) { 252 | EspaperParser::ResourceResponse response; 253 | response.httpCode = 0; 254 | response.sleepSeconds = 0; 255 | response.sleepUntilEpoch = 0; 256 | 257 | Serial.printf(PSTR("Downloading resource from:\n\tScheme: %s\n\tHost: %s\n\tPort: %d\n\tPath: %s\n"), url.protocol.c_str(), url.host.c_str(), url.port, url.path.c_str()); 258 | 259 | WiFiClient *client = this->createWifiClient(url); 260 | 261 | Serial.println("[HTTP] begin..."); 262 | 263 | Serial.printf("Connecting to %s:%d\n", url.host.c_str(), url.port); 264 | Serial.printf("Free mem: %d\n", ESP.getFreeHeap()); 265 | client->connect(url.host.c_str(), url.port); 266 | if (!client->connected()) { 267 | Serial.println("*** Can't connect. ***\n-------"); 268 | delete client; 269 | response.httpCode = -2; 270 | return response; 271 | } 272 | 273 | String EOL = "\r\n"; 274 | 275 | // keep the X-ESPAPER headers alphabetically sorted 276 | String request = "GET " + url.path + " HTTP/1.1\r\n" + 277 | "Host: " + url.host + "\r\n" + 278 | "User-Agent: ESPaperClient/1.0\r\n" + 279 | "X-ESPAPER-BATTERY: " + String(Board.getBattery()) + EOL + 280 | "X-ESPAPER-CLIENT-VERSION: " + this->clientVersion + EOL + 281 | "X-ESPAPER-FREE-HEAP: " + String(ESP.getFreeHeap()) + EOL + 282 | "X-ESPAPER-MILLIS: " + String(millis()) + "\r\n" + 283 | // "X-ESPAPER-RESET-REASON: " + rtc_get_reset_reason(0) + EOL + 284 | "X-ESPAPER-SECRET: " + this->deviceSecret + EOL + 285 | "X-ESPAPER-SPIFFS-FREE: " + String(Board.getFreeSPIFFSBytes()) + EOL + 286 | "X-ESPAPER-SPIFFS-TOTAL: " + String(Board.getTotalSPIFFSBytes()) + EOL + 287 | "X-ESPAPER-WIFI-RSSI: " + String(WiFi.RSSI()) + EOL + 288 | optionalHeaderFields + 289 | "Connection: close\r\n\r\n"; 290 | 291 | Serial.println("Sending request: " + request); 292 | 293 | client->print(request); 294 | 295 | unsigned long timeout = millis(); 296 | while (client->available() == 0) { 297 | if (millis() - timeout > 10000) { 298 | Serial.println(">>> Client Timeout !"); 299 | client->stop(); 300 | delete client; 301 | response.httpCode = -2; 302 | return response; 303 | } 304 | } 305 | 306 | long lastUpdate = millis(); 307 | 308 | while (client->available() || client->connected()) { 309 | String line = client->readStringUntil('\n'); 310 | Serial.println(line); 311 | line.toUpperCase(); 312 | if (line.startsWith("HTTP/1.")) { 313 | response.httpCode = line.substring(9, line.indexOf(' ', 9)).toInt(); 314 | } else if (line.startsWith("X-ESPAPER-COMMAND: UPDATE")) { 315 | Serial.println("Server requests firmware update"); 316 | response.httpCode = HTTP_INTERNAL_CODE_UPGRADE_CLIENT; 317 | return response; 318 | } else if (line.startsWith("X-ESPAPER-SLEEP-SECONDS:")) { 319 | response.sleepSeconds = line.substring(24).toInt(); 320 | } else if (line.startsWith("X-ESPAPER-SLEEP-UNTIL:")) { 321 | response.sleepUntilEpoch = line.substring(22).toInt(); 322 | } else if (line == "\r" || line == "\r\n") { 323 | Serial.println("headers received"); 324 | Serial.printf("Parsed HTTP code: %d\n", response.httpCode); 325 | break; 326 | } 327 | if (millis() - lastUpdate > 500) { 328 | lastUpdate = millis(); 329 | Serial.printf("-"); 330 | } 331 | 332 | } 333 | 334 | if (!client->available() == 0) { 335 | Serial.println("Client disconnected before body parsing"); 336 | } 337 | Serial.println("Processing body"); 338 | 339 | long downloadedBytes = 0; 340 | if (response.httpCode > 0) { 341 | 342 | // file found at server 343 | if (response.httpCode == 200) { 344 | File file = SPIFFS.open(fileName, "w+"); 345 | if (!file) { 346 | Serial.println("Creating file failed: " + fileName); 347 | } 348 | // get lenght of document (is -1 when Server sends no Content-Length header) 349 | 350 | // Serial.printf("Payload size: %d\n", len); 351 | // create buffer for read 352 | uint8_t buff[128] = { 0 }; 353 | 354 | Serial.println("Starting resource download"); 355 | // read all data from server 356 | 357 | while (client->available() || client->connected()) { 358 | // get available data size 359 | size_t size = client->available(); 360 | 361 | if (size > 0) { 362 | // read up to 1024 byte 363 | int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 364 | downloadedBytes += c; 365 | 366 | file.write(buff, c); 367 | Serial.print("#"); 368 | } 369 | if (millis() - lastUpdate > 500) { 370 | lastUpdate = millis(); 371 | Serial.println(); 372 | } 373 | 374 | } 375 | file.close(); 376 | client->stop(); 377 | delete client; 378 | Serial.printf("Downloaded file %s with size %d", fileName.c_str(), downloadedBytes); 379 | Serial.println(); 380 | Serial.print("[HTTP] connection closed or file end.\n"); 381 | return response; 382 | } else { 383 | client->stop(); 384 | delete client; 385 | return response; 386 | } 387 | } else { 388 | client->stop(); 389 | delete client; 390 | return response; 391 | } 392 | 393 | client->stop(); 394 | delete client; 395 | response.httpCode = -2; 396 | return response; 397 | } 398 | 399 | // As per https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html#advanced-updater 400 | void EspaperParser::updateFirmware(String requestPath) { 401 | String urlPath = this->baseUrl + requestPath; 402 | Url url = this->dissectUrl(urlPath); 403 | Serial.printf(PSTR("Updating firmware from:\n\tScheme: %s\n\tHost: %s\n\tPort: %d\n\tPath: %s\n"), url.protocol.c_str(), url.host.c_str(), url.port, url.path.c_str()); 404 | WiFiClient *client = this->createWifiClient(url); 405 | 406 | Serial.printf(PSTR("Free sketch space: %d\n"), ESP.getFreeSketchSpace()); 407 | HTTP_UPDATER.rebootOnUpdate(false); 408 | t_httpUpdate_return ret = HTTP_UPDATER.update(*client, urlPath); 409 | 410 | Serial.print("Status code after firmware update: "); 411 | Serial.println(ret); 412 | // As per https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h#L57 there are currently 3 values supported 413 | switch (ret) { 414 | case HTTP_UPDATE_FAILED: 415 | Serial.printf(PSTR("HTTP_UPDATE_FAILED Error (%d): %s\n"), HTTP_UPDATER.getLastError(), HTTP_UPDATER.getLastErrorString().c_str()); 416 | break; 417 | 418 | case HTTP_UPDATE_NO_UPDATES: 419 | // no further info to print 420 | break; 421 | 422 | case HTTP_UPDATE_OK: 423 | // no further info to print 424 | break; 425 | 426 | default: 427 | Serial.printf_P(PSTR("%s is an unexpected return value. Did the library change?\n"), ret); 428 | break; 429 | } 430 | 431 | client->stop(); 432 | delete client; 433 | } 434 | -------------------------------------------------------------------------------- /EspaperClient/EspaperParser.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef ESPAPER_PARSER_H 25 | #define ESPAPER_PARSER_H 26 | 27 | #include "Board.h" 28 | 29 | 30 | 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | //#include 38 | 39 | #define HTTP_INTERNAL_CODE_UPGRADE_CLIENT -5 40 | 41 | class EspaperParser { 42 | public: 43 | typedef std::function HandlerFunction; 44 | 45 | typedef struct DeviceIdAndSecret { 46 | String deviceId; 47 | String deviceSecret; 48 | } DeviceIdAndSecret; 49 | 50 | typedef struct ResourceResponse { 51 | int httpCode; 52 | uint32_t sleepSeconds; 53 | uint32_t sleepUntilEpoch; 54 | } ResourceResponse; 55 | 56 | EspaperParser(MiniGrafx *gfx, const char *rootCertificate, String baseUrl, String deviceSecret, String clientVersion); 57 | 58 | void updateFirmware(String url); 59 | 60 | DeviceIdAndSecret registerDevice(String requestPath, String jsonData); 61 | 62 | ResourceResponse getAndDrawScreen(String requestPath, String optionalHeaderFields, HandlerFunction downloadCompletedFunction); 63 | 64 | private: 65 | MiniGrafx *gfx; 66 | uint8_t screenWidth; 67 | String baseUrl; 68 | String requestPath; 69 | String deviceSecret; 70 | String clientVersion; 71 | const char *rootCertificate; 72 | 73 | typedef struct Url { 74 | String protocol; 75 | String host; 76 | uint16_t port; 77 | String path; 78 | } Url; 79 | 80 | ResourceResponse downloadResource(Url url, String fileName, String optionalHeaderFields); 81 | 82 | Url dissectUrl(String url); 83 | WiFiClient* createWifiClient(Url url); 84 | 85 | 86 | }; 87 | 88 | 89 | #endif -------------------------------------------------------------------------------- /EspaperClient/configportal.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef CONFIG_PORTAL_H 25 | #define CONFIG_PORTAL_H 26 | 27 | #if defined(ESP8266) 28 | #include 29 | #elif defined(ESP32) 30 | #include 31 | #endif 32 | 33 | #include 34 | #include "Board.h" 35 | #include "artwork.h" 36 | #include "timezones.h" 37 | 38 | const String CONFIG_FILE = "/espaper.txt"; 39 | const String DATA_FILE = "/device-data.txt"; 40 | 41 | const char HTTP_HEAD_START[] PROGMEM = "{v}"; // 42 | const char HTTP_STYLE[] PROGMEM = ""; 43 | const char HTTP_SCRIPT[] PROGMEM = ""; 82 | const char HTTP_HEAD_END[] PROGMEM = "
"; 83 | const char HTTP_FORM_START[] PROGMEM = "

"; 84 | const char HTTP_FORM_PARAM[] PROGMEM = "


"; 85 | const char HTTP_FORM_END[] PROGMEM = "


"; 86 | const char HTTP_SAVED[] PROGMEM = "
Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again
"; 87 | const char HTTP_END[] PROGMEM = "
"; 88 | const char HTTP_OPTION_ITEM[] PROGMEM = ""; 89 | const char HTTP_LOGO[] PROGMEM = ""; 90 | 91 | #define ACTION_AFTER_REBOOT_UPDATE_SCREEN 0 92 | #define ACTION_AFTER_REBOOT_UPGRADE_FIRMWARE 10 93 | 94 | typedef struct DeviceData { 95 | uint16_t totalDeviceStarts; 96 | uint16_t successfulDeviceStarts; 97 | time_t lastNtpSyncTime; 98 | uint8_t startsWithoutNtpSync; 99 | uint32_t lastCycleDuration; 100 | uint8_t actionAfterReboot; 101 | } DeviceData; 102 | 103 | 104 | #if defined(ESP8266) 105 | ESP8266WebServer server (80); 106 | #elif defined(ESP32) 107 | WebServer server (80); 108 | #endif 109 | 110 | String getFormField(String id, String placeholder, String length, String value, String customHTML) { 111 | String pitem = FPSTR(HTTP_FORM_PARAM); 112 | 113 | pitem.replace("{i}", id); 114 | pitem.replace("{n}", id); 115 | pitem.replace("{p}", placeholder); 116 | pitem.replace("{l}", length); 117 | pitem.replace("{v}", value); 118 | pitem.replace("{c}", customHTML); 119 | return pitem; 120 | } 121 | 122 | 123 | boolean saveConfig() { 124 | File f = SPIFFS.open(CONFIG_FILE, "w+"); 125 | if (!f) { 126 | Serial.println("Failed to open config file"); 127 | return false; 128 | } 129 | f.print("WIFI_SSID="); 130 | f.println(WIFI_SSID); 131 | f.print("WIFI_PASS="); 132 | f.println(WIFI_PASS); 133 | f.print("UPDATE_INTERVAL_MINS="); 134 | f.println(UPDATE_INTERVAL_MINS); 135 | f.print("TIMEZONE="); 136 | f.println(TIMEZONE); 137 | f.print("NTP_SERVERS="); 138 | f.println(NTP_SERVERS); 139 | f.print("DEVICE_ID="); 140 | f.println(DEVICE_ID); 141 | f.print("DEVICE_SECRET="); 142 | f.println(DEVICE_SECRET); 143 | f.close(); 144 | Serial.println("Saved values"); 145 | return true; 146 | } 147 | 148 | boolean loadConfig() { 149 | File f = SPIFFS.open(CONFIG_FILE, "r"); 150 | if (!f) { 151 | Serial.println("Failed to open config file"); 152 | return false; 153 | } 154 | while (f.available()) { 155 | String key = f.readStringUntil('='); 156 | String value = f.readStringUntil('\r'); 157 | f.read(); 158 | Serial.println(key + " = [" + value + "]"); 159 | if (key == "WIFI_SSID") { 160 | WIFI_SSID = value; 161 | } 162 | if (key == "WIFI_PASS") { 163 | WIFI_PASS = value; 164 | } 165 | if (key == "UPDATE_INTERVAL_MINS") { 166 | UPDATE_INTERVAL_MINS = value.toInt(); 167 | } 168 | if (key == "TIMEZONE") { 169 | TIMEZONE = value; 170 | } 171 | if (key == "NTP_SERVERS") { 172 | NTP_SERVERS = value; 173 | } 174 | if (key == "DEVICE_ID") { 175 | DEVICE_ID = value; 176 | } 177 | if (key == "DEVICE_SECRET" || key == "DEVICE_KEY") { // support pre-V011 clients 178 | DEVICE_SECRET = value; 179 | } 180 | // break infinite loop if file is present but somehow empty or corrupt 181 | if (key == "" && value == "") { 182 | Serial.println(F("Config file available on SPIFFS but at least one line with empty key and value found. Exiting read loop.")); 183 | break; 184 | } 185 | } 186 | if (WIFI_SSID == "" || WIFI_PASS == "" || TIMEZONE == "") { 187 | Serial.println("At least one configuration property not yet defined"); 188 | return false; 189 | } 190 | f.close(); 191 | Serial.println("Loaded config"); 192 | return true; 193 | } 194 | 195 | 196 | 197 | void loadDeviceData(DeviceData *deviceData) { 198 | File f = SPIFFS.open(DATA_FILE, "r"); 199 | if (!f) { 200 | Serial.println(F("Failed to open data file. Initializing struct.")); 201 | 202 | deviceData->totalDeviceStarts = 0; 203 | deviceData->successfulDeviceStarts = 0; 204 | deviceData->lastNtpSyncTime = 0; 205 | deviceData->startsWithoutNtpSync = 0; 206 | deviceData->lastCycleDuration = 0; 207 | deviceData->actionAfterReboot = ACTION_AFTER_REBOOT_UPDATE_SCREEN; 208 | return; 209 | } 210 | while (f.available()) { 211 | String key = f.readStringUntil('='); 212 | String value = f.readStringUntil('\r'); 213 | f.read(); 214 | Serial.println(key + " = [" + value + "]"); 215 | if (key == "TOTAL_DEVICE_STARTS") { 216 | deviceData->totalDeviceStarts = value.toInt(); 217 | } 218 | if (key == "SUCCESSFUL_DEVICE_STARTS") { 219 | deviceData->successfulDeviceStarts = value.toInt(); 220 | } 221 | if (key == "LAST_NTP_SYNC_TIME") { 222 | deviceData->lastNtpSyncTime = value.toInt(); 223 | } 224 | if (key == "STARTS_WITHOUT_NTP_SYNC") { 225 | deviceData->startsWithoutNtpSync = value.toInt(); 226 | } 227 | if (key == "LAST_CYCLE_DURATION") { 228 | deviceData->lastCycleDuration = value.toInt(); 229 | } 230 | if (key == "ACTION_AFTER_REBOOT") { 231 | deviceData->actionAfterReboot = value.toInt(); 232 | } 233 | // break infinite loop if file is present but somehow empty or corrupt 234 | if (key == "" && value == "") { 235 | Serial.println(F("Device data file available on SPIFFS but at least one line with empty key and value found. Exiting read loop.")); 236 | break; 237 | } 238 | } 239 | 240 | f.close(); 241 | Serial.println(F("Loaded data file")); 242 | } 243 | 244 | void saveDeviceData(DeviceData* deviceData) { 245 | File f = SPIFFS.open(DATA_FILE, "w+"); 246 | if (!f) { 247 | Serial.println(F("Failed to open data file")); 248 | return; 249 | } 250 | f.print(F("TOTAL_DEVICE_STARTS=")); 251 | f.println(deviceData->totalDeviceStarts); 252 | f.print(F("SUCCESSFUL_DEVICE_STARTS=")); 253 | f.println(deviceData->successfulDeviceStarts); 254 | f.print(F("LAST_NTP_SYNC_TIME=")); 255 | f.println(deviceData->lastNtpSyncTime); 256 | f.print(F("STARTS_WITHOUT_NTP_SYNC=")); 257 | f.println(deviceData->startsWithoutNtpSync); 258 | f.print(F("LAST_CYCLE_DURATION=")); 259 | f.println(deviceData->lastCycleDuration); 260 | f.print(F("ACTION_AFTER_REBOOT=")); 261 | f.println(deviceData->actionAfterReboot); 262 | f.close(); 263 | Serial.println(F("Saved values in data file.")); 264 | } 265 | 266 | void handleRoot() { 267 | Serial.println("Serving /"); 268 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 269 | server.sendHeader("Pragma", "no-cache"); 270 | server.sendHeader("Expires", "-1"); 271 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 272 | server.send(200, "text/html", ""); 273 | String header = FPSTR(HTTP_HEAD_START); 274 | header.replace("{v}", "Options"); 275 | server.sendContent(header); 276 | server.sendContent(FPSTR(HTTP_SCRIPT)); 277 | server.sendContent(FPSTR(HTTP_STYLE)); 278 | server.sendContent(FPSTR(HTTP_HEAD_END)); 279 | server.sendContent(FPSTR(HTTP_LOGO)); 280 | server.sendContent("

ESPaper Configuration

"); 281 | server.sendContent("

Device Information

"); 282 | server.sendContent("WiFi MAC address: "); 283 | server.sendContent(WiFi.macAddress()); 284 | server.sendContent("
Free Heap: "); 285 | server.sendContent(String(ESP.getFreeHeap() / 1024.0)); 286 | server.sendContent(" KB
Chip Id: "); 287 | server.sendContent(Board.getChipId()); 288 | server.sendContent("
Flash Chip Size: "); 289 | server.sendContent(String(ESP.getFlashChipSize() / (1024 * 1024))); // no floating point required, it's always n MB 290 | server.sendContent(" MB

Settings

"); 291 | server.sendContent(FPSTR(HTTP_FORM_START)); 292 | server.sendContent(getFormField("ssid", "WiFi SSID", "20", WIFI_SSID, "")); 293 | server.sendContent(getFormField("password", "WiFi Password", "32", WIFI_PASS, "type='password'")); 294 | // 3h = 180min seem to be a save value. Since ESP.deepSleepMax() returns a different value every time validation 295 | // based on a dynamic value can be very irritating for the user. 296 | String validationRules = String("type='number' min='1' max='180'"); 297 | server.sendContent(getFormField("updateIntervalMins", "Screen Update Interval in Minutes", "10", String(UPDATE_INTERVAL_MINS), validationRules)); 298 | server.sendContent(""); 299 | server.sendContent(""); 338 | server.sendContent("

"); 339 | server.sendContent(getFormField("ntpServers", "NTP Servers", "300", NTP_SERVERS, "")); 340 | server.sendContent(FPSTR(HTTP_FORM_END)); 341 | server.sendContent(FPSTR(HTTP_END)); 342 | server.sendContent(""); 343 | server.client().stop(); 344 | } 345 | 346 | void sendRedirectToRoot() { 347 | server.sendHeader("Location", "/"); 348 | server.send(303, "text/html", ""); 349 | } 350 | 351 | void handleDelete() { 352 | Serial.println("Handling 'delete' request"); 353 | SPIFFS.remove(CONFIG_FILE); 354 | resetUserSettings(); 355 | sendRedirectToRoot(); 356 | } 357 | 358 | void handleReset() { 359 | pinMode(2, INPUT); 360 | pinMode(0, INPUT); 361 | ESP.restart(); 362 | } 363 | 364 | void handleSave() { 365 | Serial.println("Handling 'save' request"); 366 | WIFI_SSID = server.arg("ssid"); 367 | WIFI_PASS = server.arg("password"); 368 | UPDATE_INTERVAL_MINS = server.arg("updateIntervalMins").toInt(); 369 | TIMEZONE = server.arg("timeZone"); 370 | NTP_SERVERS = server.arg("ntpServers"); 371 | Serial.println(WIFI_SSID); 372 | Serial.println(WIFI_PASS); 373 | Serial.println(UPDATE_INTERVAL_MINS); 374 | Serial.println(TIMEZONE); 375 | Serial.println(NTP_SERVERS); 376 | saveConfig(); 377 | sendRedirectToRoot(); 378 | } 379 | 380 | void handleNotFound() { 381 | Serial.println("Handling HTTP 404"); 382 | String message = "File Not Found\n\n"; 383 | message += "URI: "; 384 | message += server.uri(); 385 | message += "\nMethod: "; 386 | message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; 387 | message += "\nArguments: "; 388 | message += server.args(); 389 | message += "\n"; 390 | 391 | for ( uint8_t i = 0; i < server.args(); i++ ) { 392 | message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; 393 | } 394 | 395 | server.send(404, "text/plain", message); 396 | } 397 | 398 | void registerServerCallbackHandlers() { 399 | server.on("/", handleRoot); 400 | server.on("/logo.svg", []() { 401 | Serial.println("Serving /logo.svg"); 402 | server.setContentLength(ThingPulse_svg_len); 403 | server.send(200, "image/svg+xml", ""); 404 | for (int i = 0; i < ThingPulse_svg_len; i++) { 405 | char svgChar = pgm_read_byte(ThingPulse_svg + i); 406 | server.sendContent(String(svgChar)); 407 | } 408 | server.sendContent(""); 409 | server.client().stop(); 410 | } ); 411 | server.on("/favicon.png", []() { 412 | // TODO don't understand why this doesn't work :( 413 | Serial.println("Serving /favicon.png"); 414 | server.setContentLength(favicon_png_len); 415 | server.send(200, "image/png", ""); 416 | for (int k = 0; k < favicon_png_len; k++) { 417 | char pngChar = pgm_read_byte(favicon_png + k); 418 | server.sendContent(String(pngChar)); 419 | } 420 | server.sendContent(""); 421 | server.client().stop(); 422 | } ); 423 | server.on("/save", handleSave); 424 | server.on("/delete", handleDelete); 425 | server.on("/reset", handleReset); 426 | server.onNotFound(handleNotFound); 427 | } 428 | 429 | String getJoinWifiMessage() { 430 | return "Join WiFi with SSID '" + CONFIG_SSID + "' then open browser at http://" + WiFi.softAPIP().toString(); 431 | } 432 | 433 | void startConfigPortal(MiniGrafx *gfx) { 434 | Serial.println("Starting config portal..."); 435 | 436 | WiFi.mode(WIFI_AP); 437 | WiFi.softAP(CONFIG_SSID.c_str()); 438 | 439 | gfx->init(); 440 | gfx->fillBuffer(1); 441 | gfx->drawPalettedBitmapFromPgm(0, 0, boot_screen); 442 | gfx->setColor(0); 443 | gfx->setTextAlignment(TEXT_ALIGN_CENTER); 444 | 445 | File f = SPIFFS.open(CONFIG_FILE, "r"); 446 | uint16_t halfWidth = gfx->getWidth() / 2; 447 | uint16_t maxTextWidth = gfx->getWidth() * MAX_TEXT_WIDTH_FACTOR; 448 | if (f) { 449 | Serial.println("Configuration file present -> assume user manually started config portal"); 450 | #ifdef EPD29 451 | uint8_t yPosition = 39; 452 | #endif 453 | #ifdef EPD42 454 | uint8_t yPosition = 180; 455 | #endif 456 | #ifdef EPD75 457 | uint8_t yPosition = 180; 458 | #endif 459 | #ifdef COLOR_TFT_24 460 | uint8_t yPosition = 120; 461 | #endif 462 | gfx->setFont(ArialMT_Plain_16); 463 | gfx->drawString(halfWidth, yPosition, "ESPaper Configuration Mode"); 464 | gfx->drawStringMaxWidth(halfWidth, yPosition + 19, maxTextWidth, getJoinWifiMessage()); 465 | } else { 466 | Serial.println("Configuration file doesn't exist or is not readable -> assume virgin device"); 467 | #ifdef EPD29 468 | gfx->setFont(ArialMT_Plain_16); 469 | gfx->drawString(halfWidth, 39, "ESPaper Initial Setup"); 470 | gfx->drawStringMaxWidth(halfWidth, 58, maxTextWidth, getJoinWifiMessage()); 471 | #endif 472 | #ifdef EPD42 473 | gfx->setFont(ArialMT_Plain_24); 474 | gfx->drawString(halfWidth, 172, "ESPaper Initial Setup"); 475 | gfx->setFont(ArialMT_Plain_16); 476 | gfx->drawStringMaxWidth(halfWidth, 202, maxTextWidth, getJoinWifiMessage()); 477 | #endif 478 | } 479 | 480 | Serial.println("Committing screen"); 481 | gfx->commit(); 482 | gfx->freeBuffer(); 483 | 484 | registerServerCallbackHandlers(); 485 | server.begin(); 486 | Serial.println("HTTP server started"); 487 | 488 | while (true) { 489 | server.handleClient(); 490 | yield(); 491 | } 492 | } 493 | 494 | String getTimeZoneSettings() { 495 | uint8_t indexOfSeparator = TIMEZONE.indexOf(" "); 496 | if (indexOfSeparator <= 0) { 497 | return ""; 498 | } 499 | return TIMEZONE.substring(indexOfSeparator + 1); 500 | } 501 | 502 | String getTimeZoneName() { 503 | uint8_t indexOfSeparator = TIMEZONE.indexOf(" "); 504 | if (indexOfSeparator <= 0) { 505 | return ""; 506 | } 507 | return TIMEZONE.substring(0, indexOfSeparator); 508 | } 509 | 510 | String getNtpServer(int index) { 511 | int found = 0; 512 | int strIndex[] = {0, -1}; 513 | int maxIndex = NTP_SERVERS.length() - 1; 514 | 515 | for (int i = 0; i <= maxIndex && found <= index; i++) { 516 | if (NTP_SERVERS.charAt(i) == ',' || i == maxIndex) { 517 | found++; 518 | strIndex[0] = strIndex[1] + 1; 519 | strIndex[1] = (i == maxIndex) ? i + 1 : i; 520 | } 521 | } 522 | 523 | return found > index ? NTP_SERVERS.substring(strIndex[0], strIndex[1]) : ""; 524 | } 525 | 526 | void saveDeviceRegistration(String deviceId, String deviceSecret) { 527 | DEVICE_ID = deviceId; 528 | DEVICE_SECRET = deviceSecret; 529 | saveConfig(); 530 | } 531 | 532 | #endif -------------------------------------------------------------------------------- /EspaperClient/settings.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef SETTINGS_H 25 | #define SETTINGS_H 26 | 27 | #include 28 | 29 | /*************************** 30 | * Device Type 31 | **************************/ 32 | 33 | // enable one of these devices for the Arduino IDE 34 | // If you are using platformio enable the right device 35 | // in platformio.ini 36 | //#define ESPAPER29BW 37 | //#define ESPAPER42BW 38 | //#define TTGOT529BW 39 | 40 | #if defined(ESPAPER29BW) 41 | #define EPD29 42 | 43 | #define CS 15 // D8 44 | #define RST 2 // D4 45 | #define DC 5 // D1 46 | #define BUSY 4 // D2 47 | #define USR_BTN 12 // D6 48 | 49 | #define DEVICE_TYPE Espaper29Bw 50 | #elif defined(ESPAPER42BW) 51 | #define EPD42 52 | 53 | #define CS 15 // D8 54 | #define RST 2 // D4 55 | #define DC 5 // D1 56 | #define BUSY 4 // D2 57 | #define USR_BTN 12 // D6 58 | 59 | #define DEVICE_TYPE Espaper42Bw 60 | #elif defined(ESPAPER75BW) 61 | #define EPD75 62 | 63 | #define CS 2 64 | #define RST 15 65 | #define DC 5 66 | #define BUSY 4 67 | #define USR_BTN 12 68 | #define IMU_SDA 21 69 | #define IMU_SCL 22 70 | #define WAKE_UP_PIN GPIO_NUM_27 71 | #define BUZZER_PIN GPIO_NUM_32 72 | #define LED_PIN 26 73 | 74 | #define DEVICE_TYPE Espaper75Bw 75 | #elif defined(ESP_COLOR_KIT) 76 | #define COLOR_TFT_24 77 | 78 | #define TFT_CS D1 79 | #define TFT_DC D2 80 | #define TFT_LED D8 81 | #define USR_BTN D4 82 | 83 | #define DEVICE_TYPE Espaper42Bw 84 | #elif defined(TTGOT529BW) 85 | #define EPD29 86 | 87 | #define CS 5 // D8 88 | #define RST 12 // D4 89 | #define DC 19 // D1 90 | #define BUSY 4 // D2 91 | #define USR_BTN 37 // D6 92 | 93 | #define DEVICE_TYPE TTGOT529Bw 94 | #endif 95 | 96 | 97 | /*************************** 98 | * User Settings 99 | **************************/ 100 | 101 | static String WIFI_SSID = ""; 102 | static String WIFI_PASS = ""; 103 | static String TIMEZONE = ""; 104 | static String NTP_SERVERS = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"; 105 | 106 | static String DEVICE_ID = ""; 107 | static String DEVICE_SECRET = ""; 108 | 109 | static uint8_t UPDATE_INTERVAL_MINS = 20; 110 | 111 | 112 | /*************************** 113 | * Internal Settings 114 | **************************/ 115 | 116 | //#define CLIENT_VERSION V027 117 | 118 | 119 | //#define DEV_ENV 120 | //#define TEST_ENV 121 | 122 | const String CONFIG_SSID = "ESPaperConfig"; 123 | const String CONFIG_MODE_INSTRUCTION = "Press and hold LEFT button and press & release RIGHT button to enter configuration mode."; 124 | 125 | // August 1st, 2018 126 | #define NTP_MIN_VALID_EPOCH 1533081600 127 | #define NTP_SYNC_TIMEOUT_SECONDS 5 128 | 129 | #if defined(EPD29) 130 | const float MAX_TEXT_WIDTH_FACTOR = 0.95; 131 | const uint8_t STD_MESSAGE_Y_POSITION = 12; 132 | #define SCREEN_TYPE EPD29 133 | #elif defined(EPD42) 134 | const float MAX_TEXT_WIDTH_FACTOR = 0.85; 135 | const uint8_t STD_MESSAGE_Y_POSITION = 25; 136 | #define SCREEN_TYPE EPD42 137 | #elif defined(EPD75) 138 | const float MAX_TEXT_WIDTH_FACTOR = 0.75; 139 | const uint8_t STD_MESSAGE_Y_POSITION = 40; 140 | #define SCREEN_TYPE EPD75 141 | #elif defined(COLOR_TFT_24) 142 | const float MAX_TEXT_WIDTH_FACTOR = 0.75; 143 | const uint8_t STD_MESSAGE_Y_POSITION = 40; 144 | #define SCREEN_TYPE COLOR_TFT_24 145 | #endif 146 | 147 | /********************************** 148 | * ESPaper Server-related Settings 149 | *********************************/ 150 | 151 | const String SERVER_API_DEVICES_PATH = "/public/devices"; 152 | 153 | #ifdef DEV_ENV 154 | // use empty array as a placeholder, as the scheme is HTTP rather 155 | // than HTTPS it won't actually be used, see EspaperParser::createWifiClient 156 | static const char rootCaCert[] PROGMEM = {}; 157 | const String SERVER_URL = "http://192.168.0.146:8080"; 158 | #define USE_SECURE_WIFI_CLIENT 0 159 | #else 160 | #define USE_SECURE_WIFI_CLIENT 1 161 | // exported from Firefox as x509.pem format 162 | static const char rootCaCert[] PROGMEM = R"EOF( 163 | -----BEGIN CERTIFICATE----- 164 | MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ 165 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 166 | DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow 167 | PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD 168 | Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 169 | AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O 170 | rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq 171 | OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b 172 | xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 173 | 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD 174 | aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV 175 | HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG 176 | SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 177 | ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr 178 | AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz 179 | R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 180 | JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo 181 | Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ 182 | -----END CERTIFICATE----- 183 | )EOF"; 184 | #ifdef TEST_ENV 185 | const String SERVER_URL = "https://test.espaper.com"; 186 | 187 | #else 188 | // PROD 189 | const String SERVER_URL = "https://www.espaper.com"; 190 | #endif 191 | 192 | #endif 193 | 194 | /*************************** 195 | * Functions 196 | **************************/ 197 | 198 | static bool isDeviceRegistered() { 199 | return DEVICE_ID.length() != 0 && DEVICE_SECRET.length() != 0; 200 | } 201 | 202 | static void resetUserSettings() { 203 | WIFI_SSID = ""; 204 | WIFI_PASS = ""; 205 | UPDATE_INTERVAL_MINS = 20; 206 | TIMEZONE = ""; 207 | NTP_SERVERS = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"; 208 | DEVICE_ID = ""; 209 | DEVICE_SECRET = ""; 210 | } 211 | 212 | #endif //SETTINGS_H 213 | -------------------------------------------------------------------------------- /EspaperClient/timezones.txt: -------------------------------------------------------------------------------- 1 | UTC GMT0 2 | Africa/Abidjan GMT0 3 | Africa/Accra GMT0 4 | Africa/Addis_Ababa EAT-3 5 | Africa/Algiers CET-1 6 | Africa/Asmara EAT-3 7 | Africa/Asmera EAT-3 8 | Africa/Bamako GMT0 9 | Africa/Bangui WAT-1 10 | Africa/Banjul GMT0 11 | Africa/Bissau GMT0 12 | Africa/Blantyre CAT-2 13 | Africa/Brazzaville WAT-1 14 | Africa/Bujumbura CAT-2 15 | Africa/Cairo EEST 16 | Africa/Casablanca WET0 17 | Africa/Ceuta CET-1CEST,M3.5.0,M10.5.0/3 18 | Africa/Conakry GMT0 19 | Africa/Dakar GMT0 20 | Africa/Dar_es_Salaam EAT-3 21 | Africa/Djibouti EAT-3 22 | Africa/Douala WAT-1 23 | Africa/El_Aaiun WET0 24 | Africa/Freetown GMT0 25 | Africa/Gaborone CAT-2 26 | Africa/Harare CAT-2 27 | Africa/Johannesburg SAST-2 28 | Africa/Kampala EAT-3 29 | Africa/Khartoum EAT-3 30 | Africa/Kigali CAT-2 31 | Africa/Kinshasa WAT-1 32 | Africa/Lagos WAT-1 33 | Africa/Libreville WAT-1 34 | Africa/Lome GMT0 35 | Africa/Luanda WAT-1 36 | Africa/Lubumbashi CAT-2 37 | Africa/Lusaka CAT-2 38 | Africa/Malabo WAT-1 39 | Africa/Maputo CAT-2 40 | Africa/Maseru SAST-2 41 | Africa/Mbabane SAST-2 42 | Africa/Mogadishu EAT-3 43 | Africa/Monrovia GMT0 44 | Africa/Nairobi EAT-3 45 | Africa/Ndjamena WAT-1 46 | Africa/Niamey WAT-1 47 | Africa/Nouakchott GMT0 48 | Africa/Ouagadougou GMT0 49 | Africa/Porto-Novo WAT-1 50 | Africa/Sao_Tome GMT0 51 | Africa/Timbuktu GMT0 52 | Africa/Tripoli EET-2 53 | Africa/Tunis CET-1CEST,M3.5.0,M10.5.0/3 54 | Africa/Windhoek WAT-1WAST,M9.1.0,M4.1.0 55 | America/Adak HAST10HADT,M3.2.0,M11.1.0 56 | America/Anchorage AKST9AKDT,M3.2.0,M11.1.0 57 | America/Anguilla AST4 58 | America/Antigua AST4 59 | America/Araguaina BRT3 60 | America/Argentina/Buenos_Aires ART3ARST,M10.1.0/0,M3.3.0/0 61 | America/Argentina/Catamarca ART3ARST,M10.1.0/0,M3.3.0/0 62 | America/Argentina/ComodRivadavia ART3ARST,M10.1.0/0,M3.3.0/0 63 | America/Argentina/Cordoba ART3ARST,M10.1.0/0,M3.3.0/0 64 | America/Argentina/Jujuy ART3ARST,M10.1.0/0,M3.3.0/0 65 | America/Argentina/La_Rioja ART3ARST,M10.1.0/0,M3.3.0/0 66 | America/Argentina/Mendoza ART3ARST,M10.1.0/0,M3.3.0/0 67 | America/Argentina/Rio_Gallegos ART3ARST,M10.1.0/0,M3.3.0/0 68 | America/Argentina/San_Juan ART3ARST,M10.1.0/0,M3.3.0/0 69 | America/Argentina/San_Luis ART3 70 | America/Argentina/Tucuman ART3ARST,M10.1.0/0,M3.3.0/0 71 | America/Argentina/Ushuaia ART3ARST,M10.1.0/0,M3.3.0/0 72 | America/Aruba AST4 73 | America/Asuncion PYT4PYST,M10.3.0/0,M3.2.0/0 74 | America/Atikokan EST5 75 | America/Atka HAST10HADT,M3.2.0,M11.1.0 76 | America/Bahia BRT3 77 | America/Barbados AST4 78 | America/Belem BRT3 79 | America/Belize CST6 80 | America/Blanc-Sablon AST4 81 | America/Boa_Vista AMT4 82 | America/Bogota COT5 83 | America/Boise MST7MDT,M3.2.0,M11.1.0 84 | America/Buenos_Aires ART3ARST,M10.1.0/0,M3.3.0/0 85 | America/Cambridge_Bay MST7MDT,M3.2.0,M11.1.0 86 | America/Campo_Grande AMT4AMST,M10.2.0/0,M2.3.0/0 87 | America/Cancun CST6CDT,M4.1.0,M10.5.0 88 | America/Caracas VET4:30 89 | America/Catamarca ART3ARST,M10.1.0/0,M3.3.0/0 90 | America/Cayenne GFT3 91 | America/Cayman EST5 92 | America/Chicago CST6CDT,M3.2.0,M11.1.0 93 | America/Chihuahua MST7MDT,M4.1.0,M10.5.0 94 | America/Coral_Harbour EST5 95 | America/Cordoba ART3ARST,M10.1.0/0,M3.3.0/0 96 | America/Costa_Rica CST6 97 | America/Cuiaba AMT4AMST,M10.2.0/0,M2.3.0/0 98 | America/Curacao AST4 99 | America/Danmarkshavn GMT0 100 | America/Dawson PST8PDT,M3.2.0,M11.1.0 101 | America/Dawson_Creek MST7 102 | America/Denver MST7MDT,M3.2.0,M11.1.0 103 | America/Detroit EST5EDT,M3.2.0,M11.1.0 104 | America/Dominica AST4 105 | America/Edmonton MST7MDT,M3.2.0,M11.1.0 106 | America/Eirunepe ACT5 107 | America/El_Salvador CST6 108 | America/Ensenada PST8PDT,M4.1.0,M10.5.0 109 | America/Fortaleza BRT3 110 | America/Fort_Wayne EST5EDT,M3.2.0,M11.1.0 111 | America/Glace_Bay AST4ADT,M3.2.0,M11.1.0 112 | America/Godthab WGST 113 | America/Goose_Bay AST4ADT,M3.2.0/0:01,M11.1.0/0:01 114 | America/Grand_Turk EST5EDT,M3.2.0,M11.1.0 115 | America/Grenada AST4 116 | America/Guadeloupe AST4 117 | America/Guatemala CST6 118 | America/Guayaquil ECT5 119 | America/Guyana GYT4 120 | America/Halifax AST4ADT,M3.2.0,M11.1.0 121 | America/Havana CST5CDT,M3.3.0/0,M10.5.0/1 122 | America/Hermosillo MST7 123 | America/Indiana/Indianapolis EST5EDT,M3.2.0,M11.1.0 124 | America/Indiana/Knox CST6CDT,M3.2.0,M11.1.0 125 | America/Indiana/Marengo EST5EDT,M3.2.0,M11.1.0 126 | America/Indiana/Petersburg EST5EDT,M3.2.0,M11.1.0 127 | America/Indianapolis EST5EDT,M3.2.0,M11.1.0 128 | America/Indiana/Tell_City CST6CDT,M3.2.0,M11.1.0 129 | America/Indiana/Vevay EST5EDT,M3.2.0,M11.1.0 130 | America/Indiana/Vincennes EST5EDT,M3.2.0,M11.1.0 131 | America/Indiana/Winamac EST5EDT,M3.2.0,M11.1.0 132 | America/Inuvik MST7MDT,M3.2.0,M11.1.0 133 | America/Iqaluit EST5EDT,M3.2.0,M11.1.0 134 | America/Jamaica EST5 135 | America/Jujuy ART3ARST,M10.1.0/0,M3.3.0/0 136 | America/Juneau AKST9AKDT,M3.2.0,M11.1.0 137 | America/Kentucky/Louisville EST5EDT,M3.2.0,M11.1.0 138 | America/Kentucky/Monticello EST5EDT,M3.2.0,M11.1.0 139 | America/Knox_IN CST6CDT,M3.2.0,M11.1.0 140 | America/La_Paz BOT4 141 | America/Lima PET5 142 | America/Los_Angeles PST8PDT,M3.2.0,M11.1.0 143 | America/Louisville EST5EDT,M3.2.0,M11.1.0 144 | America/Maceio BRT3 145 | America/Managua CST6 146 | America/Manaus AMT4 147 | America/Marigot AST4 148 | America/Martinique AST4 149 | America/Mazatlan MST7MDT,M4.1.0,M10.5.0 150 | America/Mendoza ART3ARST,M10.1.0/0,M3.3.0/0 151 | America/Menominee CST6CDT,M3.2.0,M11.1.0 152 | America/Merida CST6CDT,M4.1.0,M10.5.0 153 | America/Mexico_City CST6CDT,M4.1.0,M10.5.0 154 | America/Miquelon PMST3PMDT,M3.2.0,M11.1.0 155 | America/Moncton AST4ADT,M3.2.0,M11.1.0 156 | America/Monterrey CST6CDT,M4.1.0,M10.5.0 157 | America/Montevideo UYT3UYST,M10.1.0,M3.2.0 158 | America/Montreal EST5EDT,M3.2.0,M11.1.0 159 | America/Montserrat AST4 160 | America/Nassau EST5EDT,M3.2.0,M11.1.0 161 | America/New_York EST5EDT,M3.2.0,M11.1.0 162 | America/Nipigon EST5EDT,M3.2.0,M11.1.0 163 | America/Nome AKST9AKDT,M3.2.0,M11.1.0 164 | America/Noronha FNT2 165 | America/North_Dakota/Center CST6CDT,M3.2.0,M11.1.0 166 | America/North_Dakota/New_Salem CST6CDT,M3.2.0,M11.1.0 167 | America/Panama EST5 168 | America/Pangnirtung EST5EDT,M3.2.0,M11.1.0 169 | America/Paramaribo SRT3 170 | America/Phoenix MST7 171 | America/Port-au-Prince EST5 172 | America/Porto_Acre ACT5 173 | America/Port_of_Spain AST4 174 | America/Porto_Velho AMT4 175 | America/Puerto_Rico AST4 176 | America/Rainy_River CST6CDT,M3.2.0,M11.1.0 177 | America/Rankin_Inlet CST6CDT,M3.2.0,M11.1.0 178 | America/Recife BRT3 179 | America/Regina CST6 180 | America/Resolute EST5 181 | America/Rio_Branco ACT5 182 | America/Rosario ART3ARST,M10.1.0/0,M3.3.0/0 183 | America/Santiago CLST 184 | America/Santo_Domingo AST4 185 | America/Sao_Paulo BRT3BRST,M10.2.0/0,M2.3.0/0 186 | America/Scoresbysund EGT1EGST,M3.5.0/0,M10.5.0/1 187 | America/Shiprock MST7MDT,M3.2.0,M11.1.0 188 | America/St_Barthelemy AST4 189 | America/St_Johns NST3:30NDT,M3.2.0/0:01,M11.1.0/0:01 190 | America/St_Kitts AST4 191 | America/St_Lucia AST4 192 | America/St_Thomas AST4 193 | America/St_Vincent AST4 194 | America/Swift_Current CST6 195 | America/Tegucigalpa CST6 196 | America/Thule AST4ADT,M3.2.0,M11.1.0 197 | America/Thunder_Bay EST5EDT,M3.2.0,M11.1.0 198 | America/Tijuana PST8PDT,M4.1.0,M10.5.0 199 | America/Toronto EST5EDT,M3.2.0,M11.1.0 200 | America/Tortola AST4 201 | America/Vancouver PST8PDT,M3.2.0,M11.1.0 202 | America/Virgin AST4 203 | America/Whitehorse PST8PDT,M3.2.0,M11.1.0 204 | America/Winnipeg CST6CDT,M3.2.0,M11.1.0 205 | America/Yakutat AKST9AKDT,M3.2.0,M11.1.0 206 | America/Yellowknife MST7MDT,M3.2.0,M11.1.0 207 | Antarctica/Casey WST-8 208 | Antarctica/Davis DAVT-7 209 | Antarctica/DumontDUrville DDUT-10 210 | Antarctica/Mawson MAWT-6 211 | Antarctica/McMurdo NZST-12NZDT,M9.5.0,M4.1.0/3 212 | Antarctica/Palmer CLST 213 | Antarctica/Rothera ROTT3 214 | Antarctica/South_Pole NZST-12NZDT,M9.5.0,M4.1.0/3 215 | Antarctica/Syowa SYOT-3 216 | Antarctica/Vostok VOST-6 217 | Arctic/Longyearbyen CET-1CEST,M3.5.0,M10.5.0/3 218 | Asia/Aden AST-3 219 | Asia/Almaty ALMT-6 220 | Asia/Amman EET-2EEST,M3.5.4/0,M10.5.5/1 221 | Asia/Anadyr ANAT-12ANAST,M3.5.0,M10.5.0/3 222 | Asia/Aqtau AQTT-5 223 | Asia/Aqtobe AQTT-5 224 | Asia/Ashgabat TMT-5 225 | Asia/Ashkhabad TMT-5 226 | Asia/Baghdad AST-3 227 | Asia/Bahrain AST-3 228 | Asia/Baku AZT-4AZST,M3.5.0/4,M10.5.0/5 229 | Asia/Bangkok ICT-7 230 | Asia/Beirut EET-2EEST,M3.5.0/0,M10.5.0/0 231 | Asia/Bishkek KGT-6 232 | Asia/Brunei BNT-8 233 | Asia/Calcutta IST-5:30 234 | Asia/Choibalsan CHOT-9 235 | Asia/Chongqing CST-8 236 | Asia/Chungking CST-8 237 | Asia/Colombo IST-5:30 238 | Asia/Dacca BDT-6 239 | Asia/Damascus EET-2EEST,M4.1.5/0,J274/0 240 | Asia/Dhaka BDT-6 241 | Asia/Dili TLT-9 242 | Asia/Dubai GST-4 243 | Asia/Dushanbe TJT-5 244 | Asia/Gaza EET-2EEST,J91/0,M9.2.4 245 | Asia/Harbin CST-8 246 | Asia/Ho_Chi_Minh ICT-7 247 | Asia/Hong_Kong HKT-8 248 | Asia/Hovd HOVT-7 249 | Asia/Irkutsk IRKT-8IRKST,M3.5.0,M10.5.0/3 250 | Asia/Istanbul EET-2EEST,M3.5.0/3,M10.5.0/4 251 | Asia/Jakarta WIT-7 252 | Asia/Jayapura EIT-9 253 | Asia/Jerusalem IDDT 254 | Asia/Kabul AFT-4:30 255 | Asia/Kamchatka PETT-12PETST,M3.5.0,M10.5.0/3 256 | Asia/Karachi PKT-5 257 | Asia/Kashgar CST-8 258 | Asia/Katmandu NPT-5:45 259 | Asia/Kolkata IST-5:30 260 | Asia/Krasnoyarsk KRAT-7KRAST,M3.5.0,M10.5.0/3 261 | Asia/Kuala_Lumpur MYT-8 262 | Asia/Kuching MYT-8 263 | Asia/Kuwait AST-3 264 | Asia/Macao CST-8 265 | Asia/Macau CST-8 266 | Asia/Magadan MAGT-11MAGST,M3.5.0,M10.5.0/3 267 | Asia/Makassar CIT-8 268 | Asia/Manila PHT-8 269 | Asia/Muscat GST-4 270 | Asia/Nicosia EET-2EEST,M3.5.0/3,M10.5.0/4 271 | Asia/Novosibirsk NOVT-6NOVST,M3.5.0,M10.5.0/3 272 | Asia/Omsk OMST-6OMSST,M3.5.0,M10.5.0/3 273 | Asia/Oral ORAT-5 274 | Asia/Phnom_Penh ICT-7 275 | Asia/Pontianak WIT-7 276 | Asia/Pyongyang KST-9 277 | Asia/Qatar AST-3 278 | Asia/Qyzylorda QYZT-6 279 | Asia/Rangoon MMT-6:30 280 | Asia/Riyadh AST-3 281 | Asia/Riyadh87 zzz-3:07:04 282 | Asia/Riyadh88 zzz-3:07:04 283 | Asia/Riyadh89 zzz-3:07:04 284 | Asia/Saigon ICT-7 285 | Asia/Sakhalin SAKT-10SAKST,M3.5.0,M10.5.0/3 286 | Asia/Samarkand UZT-5 287 | Asia/Seoul KST-9 288 | Asia/Shanghai CST-8 289 | Asia/Singapore SGT-8 290 | Asia/Taipei CST-8 291 | Asia/Tashkent UZT-5 292 | Asia/Tbilisi GET-4 293 | Asia/Tehran IRDT 294 | Asia/Tel_Aviv IDDT 295 | Asia/Thimbu BTT-6 296 | Asia/Thimphu BTT-6 297 | Asia/Tokyo JST-9 298 | Asia/Ujung_Pandang CIT-8 299 | Asia/Ulaanbaatar ULAT-8 300 | Asia/Ulan_Bator ULAT-8 301 | Asia/Urumqi CST-8 302 | Asia/Vientiane ICT-7 303 | Asia/Vladivostok VLAT-10VLAST,M3.5.0,M10.5.0/3 304 | Asia/Yakutsk YAKT-9YAKST,M3.5.0,M10.5.0/3 305 | Asia/Yekaterinburg YEKT-5YEKST,M3.5.0,M10.5.0/3 306 | Asia/Yerevan AMT-4AMST,M3.5.0,M10.5.0/3 307 | Atlantic/Azores AZOT1AZOST,M3.5.0/0,M10.5.0/1 308 | Atlantic/Bermuda AST4ADT,M3.2.0,M11.1.0 309 | Atlantic/Canary WET0WEST,M3.5.0/1,M10.5.0 310 | Atlantic/Cape_Verde CVT1 311 | Atlantic/Faeroe WET0WEST,M3.5.0/1,M10.5.0 312 | Atlantic/Faroe WET0WEST,M3.5.0/1,M10.5.0 313 | Atlantic/Jan_Mayen CET-1CEST,M3.5.0,M10.5.0/3 314 | Atlantic/Madeira WET0WEST,M3.5.0/1,M10.5.0 315 | Atlantic/Reykjavik GMT0 316 | Atlantic/South_Georgia GST2 317 | Atlantic/Stanley FKT4FKST,M9.1.0,M4.3.0 318 | Atlantic/St_Helena GMT0 319 | Australia/ACT EST-10EST,M10.1.0,M4.1.0/3 320 | Australia/Adelaide CST-9:30CST,M10.1.0,M4.1.0/3 321 | Australia/Brisbane EST-10 322 | Australia/Broken_Hill CST-9:30CST,M10.1.0,M4.1.0/3 323 | Australia/Canberra EST-10EST,M10.1.0,M4.1.0/3 324 | Australia/Currie EST-10EST,M10.1.0,M4.1.0/3 325 | Australia/Darwin CST-9:30 326 | Australia/Eucla CWST-8:45 327 | Australia/Hobart EST-10EST,M10.1.0,M4.1.0/3 328 | Australia/LHI LHST-10:30LHST-11,M10.1.0,M4.1.0 329 | Australia/Lindeman EST-10 330 | Australia/Lord_Howe LHST-10:30LHST-11,M10.1.0,M4.1.0 331 | Australia/Melbourne EST-10EST,M10.1.0,M4.1.0/3 332 | Australia/North CST-9:30 333 | Australia/NSW EST-10EST,M10.1.0,M4.1.0/3 334 | Australia/Perth WST-8 335 | Australia/Queensland EST-10 336 | Australia/South CST-9:30CST,M10.1.0,M4.1.0/3 337 | Australia/Sydney EST-10EST,M10.1.0,M4.1.0/3 338 | Australia/Tasmania EST-10EST,M10.1.0,M4.1.0/3 339 | Australia/Victoria EST-10EST,M10.1.0,M4.1.0/3 340 | Australia/West WST-8 341 | Australia/Yancowinna CST-9:30CST,M10.1.0,M4.1.0/3 342 | Brazil/Acre ACT5 343 | Brazil/DeNoronha FNT2 344 | Brazil/East BRT3BRST,M10.2.0/0,M2.3.0/0 345 | Brazil/West AMT4 346 | Canada/Atlantic AST4ADT,M3.2.0,M11.1.0 347 | Canada/Central CST6CDT,M3.2.0,M11.1.0 348 | Canada/Eastern EST5EDT,M3.2.0,M11.1.0 349 | Canada/East-Saskatchewan CST6 350 | Canada/Mountain MST7MDT,M3.2.0,M11.1.0 351 | Canada/Newfoundland NST3:30NDT,M3.2.0/0:01,M11.1.0/0:01 352 | Canada/Pacific PST8PDT,M3.2.0,M11.1.0 353 | Canada/Saskatchewan CST6 354 | Canada/Yukon PST8PDT,M3.2.0,M11.1.0 355 | Chile/Continental CLST 356 | Chile/EasterIsland EASST 357 | Europe/Amsterdam CET-1CEST,M3.5.0,M10.5.0/3 358 | Europe/Andorra CET-1CEST,M3.5.0,M10.5.0/3 359 | Europe/Athens EET-2EEST,M3.5.0/3,M10.5.0/4 360 | Europe/Belfast GMT0BST,M3.5.0/1,M10.5.0 361 | Europe/Belgrade CET-1CEST,M3.5.0,M10.5.0/3 362 | Europe/Berlin CET-1CEST,M3.5.0,M10.5.0/3 363 | Europe/Bratislava CET-1CEST,M3.5.0,M10.5.0/3 364 | Europe/Brussels CET-1CEST,M3.5.0,M10.5.0/3 365 | Europe/Bucharest EET-2EEST,M3.5.0/3,M10.5.0/4 366 | Europe/Budapest CET-1CEST,M3.5.0,M10.5.0/3 367 | Europe/Chisinau EET-2EEST,M3.5.0/3,M10.5.0/4 368 | Europe/Copenhagen CET-1CEST,M3.5.0,M10.5.0/3 369 | Europe/Dublin GMT0IST,M3.5.0/1,M10.5.0 370 | Europe/Gibraltar CET-1CEST,M3.5.0,M10.5.0/3 371 | Europe/Guernsey GMT0BST,M3.5.0/1,M10.5.0 372 | Europe/Helsinki EET-2EEST,M3.5.0/3,M10.5.0/4 373 | Europe/Isle_of_Man GMT0BST,M3.5.0/1,M10.5.0 374 | Europe/Istanbul EET-2EEST,M3.5.0/3,M10.5.0/4 375 | Europe/Jersey GMT0BST,M3.5.0/1,M10.5.0 376 | Europe/Kaliningrad EET-2EEST,M3.5.0,M10.5.0/3 377 | Europe/Kiev EET-2EEST,M3.5.0/3,M10.5.0/4 378 | Europe/Lisbon WET0WEST,M3.5.0/1,M10.5.0 379 | Europe/Ljubljana CET-1CEST,M3.5.0,M10.5.0/3 380 | Europe/London GMT0BST,M3.5.0/1,M10.5.0 381 | Europe/Luxembourg CET-1CEST,M3.5.0,M10.5.0/3 382 | Europe/Madrid CET-1CEST,M3.5.0,M10.5.0/3 383 | Europe/Malta CET-1CEST,M3.5.0,M10.5.0/3 384 | Europe/Mariehamn EET-2EEST,M3.5.0/3,M10.5.0/4 385 | Europe/Minsk EET-2EEST,M3.5.0,M10.5.0/3 386 | Europe/Monaco CET-1CEST,M3.5.0,M10.5.0/3 387 | Europe/Moscow MSK-3MSD,M3.5.0,M10.5.0/3 388 | Europe/Nicosia EET-2EEST,M3.5.0/3,M10.5.0/4 389 | Europe/Oslo CET-1CEST,M3.5.0,M10.5.0/3 390 | Europe/Paris CET-1CEST,M3.5.0,M10.5.0/3 391 | Europe/Podgorica CET-1CEST,M3.5.0,M10.5.0/3 392 | Europe/Prague CET-1CEST,M3.5.0,M10.5.0/3 393 | Europe/Riga EET-2EEST,M3.5.0/3,M10.5.0/4 394 | Europe/Rome CET-1CEST,M3.5.0,M10.5.0/3 395 | Europe/Samara SAMT-4SAMST,M3.5.0,M10.5.0/3 396 | Europe/San_Marino CET-1CEST,M3.5.0,M10.5.0/3 397 | Europe/Sarajevo CET-1CEST,M3.5.0,M10.5.0/3 398 | Europe/Simferopol EET-2EEST,M3.5.0/3,M10.5.0/4 399 | Europe/Skopje CET-1CEST,M3.5.0,M10.5.0/3 400 | Europe/Sofia EET-2EEST,M3.5.0/3,M10.5.0/4 401 | Europe/Stockholm CET-1CEST,M3.5.0,M10.5.0/3 402 | Europe/Tallinn EET-2EEST,M3.5.0/3,M10.5.0/4 403 | Europe/Tirane CET-1CEST,M3.5.0,M10.5.0/3 404 | Europe/Tiraspol EET-2EEST,M3.5.0/3,M10.5.0/4 405 | Europe/Uzhgorod EET-2EEST,M3.5.0/3,M10.5.0/4 406 | Europe/Vaduz CET-1CEST,M3.5.0,M10.5.0/3 407 | Europe/Vatican CET-1CEST,M3.5.0,M10.5.0/3 408 | Europe/Vienna CET-1CEST,M3.5.0,M10.5.0/3 409 | Europe/Vilnius EET-2EEST,M3.5.0/3,M10.5.0/4 410 | Europe/Volgograd VOLT-3VOLST,M3.5.0,M10.5.0/3 411 | Europe/Warsaw CET-1CEST,M3.5.0,M10.5.0/3 412 | Europe/Zagreb CET-1CEST,M3.5.0,M10.5.0/3 413 | Europe/Zaporozhye EET-2EEST,M3.5.0/3,M10.5.0/4 414 | Europe/Zurich CET-1CEST,M3.5.0,M10.5.0/3 415 | Indian/Antananarivo EAT-3 416 | Indian/Chagos IOT-6 417 | Indian/Christmas CXT-7 418 | Indian/Cocos CCT-6:30 419 | Indian/Comoro EAT-3 420 | Indian/Kerguelen TFT-5 421 | Indian/Mahe SCT-4 422 | Indian/Maldives MVT-5 423 | Indian/Mauritius MUT-4 424 | Indian/Mayotte EAT-3 425 | Indian/Reunion RET-4 426 | Mexico/BajaNorte PST8PDT,M4.1.0,M10.5.0 427 | Mexico/BajaSur MST7MDT,M4.1.0,M10.5.0 428 | Mexico/General CST6CDT,M4.1.0,M10.5.0 429 | Mideast/Riyadh87 zzz-3:07:04 430 | Mideast/Riyadh88 zzz-3:07:04 431 | Mideast/Riyadh89 zzz-3:07:04 432 | Pacific/Apia WST11 433 | Pacific/Auckland NZST-12NZDT,M9.5.0,M4.1.0/3 434 | Pacific/Chatham CHAST-12:45CHADT,M9.5.0/2:45,M4.1.0/3:45 435 | Pacific/Easter EASST 436 | Pacific/Efate VUT-11 437 | Pacific/Enderbury PHOT-13 438 | Pacific/Fakaofo TKT10 439 | Pacific/Fiji FJT-12 440 | Pacific/Funafuti TVT-12 441 | Pacific/Galapagos GALT6 442 | Pacific/Gambier GAMT9 443 | Pacific/Guadalcanal SBT-11 444 | Pacific/Guam ChST-10 445 | Pacific/Honolulu HST10 446 | Pacific/Johnston HST10 447 | Pacific/Kiritimati LINT-14 448 | Pacific/Kosrae KOST-11 449 | Pacific/Kwajalein MHT-12 450 | Pacific/Majuro MHT-12 451 | Pacific/Marquesas MART9:30 452 | Pacific/Midway SST11 453 | Pacific/Nauru NRT-12 454 | Pacific/Niue NUT11 455 | Pacific/Norfolk NFT-11:30 456 | Pacific/Noumea NCT-11 457 | Pacific/Pago_Pago SST11 458 | Pacific/Palau PWT-9 459 | Pacific/Pitcairn PST8 460 | Pacific/Ponape PONT-11 461 | Pacific/Port_Moresby PGT-10 462 | Pacific/Rarotonga CKT10 463 | Pacific/Saipan ChST-10 464 | Pacific/Samoa SST11 465 | Pacific/Tahiti TAHT10 466 | Pacific/Tarawa GILT-12 467 | Pacific/Tongatapu TOT-13 468 | Pacific/Truk TRUT-10 469 | Pacific/Wake WAKT-12 470 | Pacific/Wallis WFT-12 471 | Pacific/Yap TRUT-10 472 | SystemV/AST4 AST4 473 | SystemV/AST4ADT AST4ADT,M3.2.0,M11.1.0 474 | SystemV/CST6 CST6 475 | SystemV/CST6CDT CST6CDT,M3.2.0,M11.1.0 476 | SystemV/EST5 EST5 477 | SystemV/EST5EDT EST5EDT,M3.2.0,M11.1.0 478 | SystemV/HST10 HST10 479 | SystemV/MST7 MST7 480 | SystemV/MST7MDT MST7MDT,M3.2.0,M11.1.0 481 | SystemV/PST8 PST8 482 | SystemV/PST8PDT PST8PDT,M3.2.0,M11.1.0 483 | SystemV/YST9 GAMT9 484 | SystemV/YST9YDT AKST9AKDT,M3.2.0,M11.1.0 485 | US/Alaska AKST9AKDT,M3.2.0,M11.1.0 486 | US/Aleutian HAST10HADT,M3.2.0,M11.1.0 487 | US/Arizona MST7 488 | US/Central CST6CDT,M3.2.0,M11.1.0 489 | US/Eastern EST5EDT,M3.2.0,M11.1.0 490 | US/East-Indiana EST5EDT,M3.2.0,M11.1.0 491 | US/Hawaii HST10 492 | US/Indiana-Starke CST6CDT,M3.2.0,M11.1.0 493 | US/Michigan EST5EDT,M3.2.0,M11.1.0 494 | US/Mountain MST7MDT,M3.2.0,M11.1.0 495 | US/Pacific PST8PDT,M3.2.0,M11.1.0 496 | US/Samoa SST11 497 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThingPulse ESPaper client 2 | 3 | How about *designing* your [ESPaper](https://thingpulse.com/product-category/espaper-epaper-kits/) content rather than programming it? You can fully customize the content to display. And load content from remote resources (calendars etc.). And much more. All with the help of [https://www.espaper.com](https://www.espaper.com). 4 | 5 | No Arduino programming required anymore! 6 | 7 | This client here is responsible for a simple & smooth registration process and for the general update cycle: 8 | 9 | - connect to WiFi 10 | - pull image from server 11 | - render it 12 | - go back to deep sleep 13 | - repeat 14 | 15 | Note that **the client has support for OTA** (Over-The-Air upgrades). See [details](#ota) below. 16 | 17 | ![](https://docs.thingpulse.com/img/products/ThingPulse-ESPaper-plus-kit.jpg) 18 | 19 | ## Service level promise 20 | 21 |
22 | This is a ThingPulse prime project. See our open-source commitment declaration for what this means.
23 | 24 | 25 | ## Setup 26 | 27 | 1. Go to [https://www.espaper.com](https://www.espaper.com) and create an account. You can either use email and password for authentication or use one of the social login options. 28 | 1. [Install drivers for USB-to-Serial](https://docs.thingpulse.com/how-tos/install-drivers/) 29 | 1. [Prepare the Arduino IDE for ESP8266](https://docs.thingpulse.com/how-tos/Arduino-IDE-for-ESP8266/) 30 | - You need at least ESP8266 Arduino Core 2.5.0 31 | 1. Download or clone this repository to your computer. Then open it in the Arduino IDE. 32 | 1. Install the [MiniGrafx library](https://github.com/ThingPulse/minigrafx) through the library manager in the Arduino IDE. 33 | 1. Define the device type in [`settings.h:33ff`](https://github.com/ThingPulse/espaper-client/blob/master/EspaperClient/settings.h#L33). Hints: 'EDP' = ESPaper Display, '29' = 2.9'' 34 | 1. Compile and upload the sketch to your ESPaper module. Then Restart it. 35 | - in Tools > Board: * > select "Generic ESP8266 Module" 36 | - in Tools > Flash Mode select "QIO" 37 | - in Tools > Flash Size select "2M (512K SPIFFS)" 38 | 1. Follow the instructions displayed on screen. 39 | 1. Initiate the registration process by restarting the device. 40 | 1. Now go back to [https://www.espaper.com](https://www.espaper.com) and complete registration process by adding your device. 41 | 1. Design the screen for your device. 42 | 43 | ## OTA 44 | This firmware/application supports OTA (Over-The-Air upgrades). With every request for an updated screen it sends its client version to the server. Should that version be different from the current version then the server will, in addition to the screen, respond with instructions for the client to go fetch an updated binary. It will then load the new version from the server and update itself. 45 | 46 | **That means that you only need to manually install the client once.** After that it will keep itself up-to-date automatically. 47 | 48 | ## Support 49 | 50 | Pre-sales: [https://thingpulse.com/about/contact/](https://thingpulse.com/about/contact/) 51 | 52 | Customer support: [https://support.thingpulse.com/](https://support.thingpulse.com/) 53 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/ADXL345/src/ADXL345.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ADXL345.cpp - Class file for the ADXL345 Triple Axis Accelerometer Arduino Library. 3 | 4 | Version: 1.1.0 5 | (c) 2014 Korneliusz Jarzebski 6 | www.jarzebski.pl 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the version 3 GNU General Public License as 10 | published by the Free Software Foundation. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #if ARDUINO >= 100 22 | #include "Arduino.h" 23 | #else 24 | #include "WProgram.h" 25 | #endif 26 | 27 | #include 28 | 29 | #include "ADXL345.h" 30 | 31 | bool ADXL345::begin(int sda, int scl) 32 | { 33 | f.XAxis = 0; 34 | f.YAxis = 0; 35 | f.ZAxis = 0; 36 | 37 | Wire.begin(sda, scl); 38 | 39 | // Check ADXL345 REG DEVID 40 | if (fastRegister8(ADXL345_REG_DEVID) != 0xE5) 41 | { 42 | return false; 43 | } 44 | 45 | // Enable measurement mode (0b00001000) 46 | writeRegister8(ADXL345_REG_POWER_CTL, 0x08); 47 | 48 | clearSettings(); 49 | 50 | return true; 51 | } 52 | 53 | // Set Range 54 | void ADXL345::setRange(adxl345_range_t range) 55 | { 56 | // Get actual value register 57 | uint8_t value = readRegister8(ADXL345_REG_DATA_FORMAT); 58 | 59 | // Update the data rate 60 | // (&) 0b11110000 (0xF0 - Leave HSB) 61 | // (|) 0b0000xx?? (range - Set range) 62 | // (|) 0b00001000 (0x08 - Set Full Res) 63 | value &= 0xF0; 64 | value |= range; 65 | value |= 0x08; 66 | 67 | writeRegister8(ADXL345_REG_DATA_FORMAT, value); 68 | } 69 | 70 | // Get Range 71 | adxl345_range_t ADXL345::getRange(void) 72 | { 73 | return (adxl345_range_t)(readRegister8(ADXL345_REG_DATA_FORMAT) & 0x03); 74 | } 75 | 76 | // Set Data Rate 77 | void ADXL345::setDataRate(adxl345_dataRate_t dataRate) 78 | { 79 | writeRegister8(ADXL345_REG_BW_RATE, dataRate); 80 | } 81 | 82 | // Get Data Rate 83 | adxl345_dataRate_t ADXL345::getDataRate(void) 84 | { 85 | return (adxl345_dataRate_t)(readRegister8(ADXL345_REG_BW_RATE) & 0x0F); 86 | } 87 | 88 | // Low Pass Filter 89 | Vector ADXL345::lowPassFilter(Vector vector, float alpha) 90 | { 91 | f.XAxis = vector.XAxis * alpha + (f.XAxis * (1.0 - alpha)); 92 | f.YAxis = vector.YAxis * alpha + (f.YAxis * (1.0 - alpha)); 93 | f.ZAxis = vector.ZAxis * alpha + (f.ZAxis * (1.0 - alpha)); 94 | return f; 95 | } 96 | 97 | // Read raw values 98 | Vector ADXL345::readRaw(void) 99 | { 100 | r.XAxis = readRegister16(ADXL345_REG_DATAX0); 101 | r.YAxis = readRegister16(ADXL345_REG_DATAY0); 102 | r.ZAxis = readRegister16(ADXL345_REG_DATAZ0); 103 | return r; 104 | } 105 | 106 | // Read normalized values 107 | Vector ADXL345::readNormalize(float gravityFactor) 108 | { 109 | readRaw(); 110 | 111 | // (4 mg/LSB scale factor in Full Res) * gravity factor 112 | n.XAxis = r.XAxis * 0.004 * gravityFactor; 113 | n.YAxis = r.YAxis * 0.004 * gravityFactor; 114 | n.ZAxis = r.ZAxis * 0.004 * gravityFactor; 115 | 116 | return n; 117 | } 118 | 119 | // Read scaled values 120 | Vector ADXL345::readScaled(void) 121 | { 122 | readRaw(); 123 | 124 | // (4 mg/LSB scale factor in Full Res) 125 | n.XAxis = r.XAxis * 0.004; 126 | n.YAxis = r.YAxis * 0.004; 127 | n.ZAxis = r.ZAxis * 0.004; 128 | 129 | return n; 130 | } 131 | 132 | void ADXL345::clearSettings(void) 133 | { 134 | setRange(ADXL345_RANGE_2G); 135 | setDataRate(ADXL345_DATARATE_100HZ); 136 | 137 | writeRegister8(ADXL345_REG_THRESH_TAP, 0x00); 138 | writeRegister8(ADXL345_REG_DUR, 0x00); 139 | writeRegister8(ADXL345_REG_LATENT, 0x00); 140 | writeRegister8(ADXL345_REG_WINDOW, 0x00); 141 | writeRegister8(ADXL345_REG_THRESH_ACT, 0x00); 142 | writeRegister8(ADXL345_REG_THRESH_INACT, 0x00); 143 | writeRegister8(ADXL345_REG_TIME_INACT, 0x00); 144 | writeRegister8(ADXL345_REG_THRESH_FF, 0x00); 145 | writeRegister8(ADXL345_REG_TIME_FF, 0x00); 146 | 147 | uint8_t value; 148 | 149 | value = readRegister8(ADXL345_REG_ACT_INACT_CTL); 150 | value &= 0b10001000; 151 | writeRegister8(ADXL345_REG_ACT_INACT_CTL, value); 152 | 153 | value = readRegister8(ADXL345_REG_TAP_AXES); 154 | value &= 0b11111000; 155 | writeRegister8(ADXL345_REG_TAP_AXES, value); 156 | } 157 | 158 | // Set Tap Threshold (62.5mg / LSB) 159 | void ADXL345::setTapThreshold(float threshold) 160 | { 161 | uint8_t scaled = constrain(threshold / 0.0625f, 0, 255); 162 | writeRegister8(ADXL345_REG_THRESH_TAP, scaled); 163 | } 164 | 165 | // Get Tap Threshold (62.5mg / LSB) 166 | float ADXL345::getTapThreshold(void) 167 | { 168 | return readRegister8(ADXL345_REG_THRESH_TAP) * 0.0625f; 169 | } 170 | 171 | // Set Tap Duration (625us / LSB) 172 | void ADXL345::setTapDuration(float duration) 173 | { 174 | uint8_t scaled = constrain(duration / 0.000625f, 0, 255); 175 | writeRegister8(ADXL345_REG_DUR, scaled); 176 | } 177 | 178 | // Get Tap Duration (625us / LSB) 179 | float ADXL345::getTapDuration(void) 180 | { 181 | return readRegister8(ADXL345_REG_DUR) * 0.000625f; 182 | } 183 | 184 | // Set Double Tap Latency (1.25ms / LSB) 185 | void ADXL345::setDoubleTapLatency(float latency) 186 | { 187 | uint8_t scaled = constrain(latency / 0.00125f, 0, 255); 188 | writeRegister8(ADXL345_REG_LATENT, scaled); 189 | } 190 | 191 | // Get Double Tap Latency (1.25ms / LSB) 192 | float ADXL345::getDoubleTapLatency() 193 | { 194 | return readRegister8(ADXL345_REG_LATENT) * 0.00125f; 195 | } 196 | 197 | // Set Double Tap Window (1.25ms / LSB) 198 | void ADXL345::setDoubleTapWindow(float window) 199 | { 200 | uint8_t scaled = constrain(window / 0.00125f, 0, 255); 201 | writeRegister8(ADXL345_REG_WINDOW, scaled); 202 | } 203 | 204 | // Get Double Tap Window (1.25ms / LSB) 205 | float ADXL345::getDoubleTapWindow(void) 206 | { 207 | return readRegister8(ADXL345_REG_WINDOW) * 0.00125f; 208 | } 209 | 210 | // Set Activity Threshold (62.5mg / LSB) 211 | void ADXL345::setActivityThreshold(float threshold) 212 | { 213 | uint8_t scaled = constrain(threshold / 0.0625f, 0, 255); 214 | writeRegister8(ADXL345_REG_THRESH_ACT, scaled); 215 | } 216 | 217 | // Get Activity Threshold (65.5mg / LSB) 218 | float ADXL345::getActivityThreshold(void) 219 | { 220 | return readRegister8(ADXL345_REG_THRESH_ACT) * 0.0625f; 221 | } 222 | 223 | // Set Inactivity Threshold (65.5mg / LSB) 224 | void ADXL345::setInactivityThreshold(float threshold) 225 | { 226 | uint8_t scaled = constrain(threshold / 0.0625f, 0, 255); 227 | writeRegister8(ADXL345_REG_THRESH_INACT, scaled); 228 | } 229 | 230 | // Get Incactivity Threshold (65.5mg / LSB) 231 | float ADXL345::getInactivityThreshold(void) 232 | { 233 | return readRegister8(ADXL345_REG_THRESH_INACT) * 0.0625f; 234 | } 235 | 236 | // Set Inactivity Time (s / LSB) 237 | void ADXL345::setTimeInactivity(uint8_t time) 238 | { 239 | writeRegister8(ADXL345_REG_TIME_INACT, time); 240 | } 241 | 242 | // Get Inactivity Time (s / LSB) 243 | uint8_t ADXL345::getTimeInactivity(void) 244 | { 245 | return readRegister8(ADXL345_REG_TIME_INACT); 246 | } 247 | 248 | // Set Free Fall Threshold (65.5mg / LSB) 249 | void ADXL345::setFreeFallThreshold(float threshold) 250 | { 251 | uint8_t scaled = constrain(threshold / 0.0625f, 0, 255); 252 | writeRegister8(ADXL345_REG_THRESH_FF, scaled); 253 | } 254 | 255 | // Get Free Fall Threshold (65.5mg / LSB) 256 | float ADXL345::getFreeFallThreshold(void) 257 | { 258 | return readRegister8(ADXL345_REG_THRESH_FF) * 0.0625f; 259 | } 260 | 261 | // Set Free Fall Duratiom (5ms / LSB) 262 | void ADXL345::setFreeFallDuration(float duration) 263 | { 264 | uint8_t scaled = constrain(duration / 0.005f, 0, 255); 265 | writeRegister8(ADXL345_REG_TIME_FF, scaled); 266 | } 267 | 268 | // Get Free Fall Duratiom 269 | float ADXL345::getFreeFallDuration() 270 | { 271 | return readRegister8(ADXL345_REG_TIME_FF) * 0.005f; 272 | } 273 | 274 | void ADXL345::setActivityX(bool state) 275 | { 276 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 6, state); 277 | } 278 | 279 | bool ADXL345::getActivityX(void) 280 | { 281 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 6); 282 | } 283 | 284 | void ADXL345::setActivityY(bool state) 285 | { 286 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 5, state); 287 | } 288 | 289 | bool ADXL345::getActivityY(void) 290 | { 291 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 5); 292 | } 293 | 294 | void ADXL345::setActivityZ(bool state) 295 | { 296 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 4, state); 297 | } 298 | 299 | bool ADXL345::getActivityZ(void) 300 | { 301 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 4); 302 | } 303 | 304 | void ADXL345::setActivityXYZ(bool state) 305 | { 306 | uint8_t value; 307 | 308 | value = readRegister8(ADXL345_REG_ACT_INACT_CTL); 309 | 310 | if (state) 311 | { 312 | value |= 0b00111000; 313 | } else 314 | { 315 | value &= 0b11000111; 316 | } 317 | 318 | writeRegister8(ADXL345_REG_ACT_INACT_CTL, value); 319 | } 320 | 321 | 322 | void ADXL345::setInactivityX(bool state) 323 | { 324 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 2, state); 325 | } 326 | 327 | bool ADXL345::getInactivityX(void) 328 | { 329 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 2); 330 | } 331 | 332 | void ADXL345::setInactivityY(bool state) 333 | { 334 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 1, state); 335 | } 336 | 337 | bool ADXL345::getInactivityY(void) 338 | { 339 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 1); 340 | } 341 | 342 | void ADXL345::setInactivityZ(bool state) 343 | { 344 | writeRegisterBit(ADXL345_REG_ACT_INACT_CTL, 0, state); 345 | } 346 | 347 | bool ADXL345::getInactivityZ(void) 348 | { 349 | return readRegisterBit(ADXL345_REG_ACT_INACT_CTL, 0); 350 | } 351 | 352 | void ADXL345::setInactivityXYZ(bool state) 353 | { 354 | uint8_t value; 355 | 356 | value = readRegister8(ADXL345_REG_ACT_INACT_CTL); 357 | 358 | if (state) 359 | { 360 | value |= 0b00000111; 361 | } else 362 | { 363 | value &= 0b11111000; 364 | } 365 | 366 | writeRegister8(ADXL345_REG_ACT_INACT_CTL, value); 367 | } 368 | 369 | void ADXL345::setTapDetectionX(bool state) 370 | { 371 | writeRegisterBit(ADXL345_REG_TAP_AXES, 2, state); 372 | } 373 | 374 | bool ADXL345::getTapDetectionX(void) 375 | { 376 | return readRegisterBit(ADXL345_REG_TAP_AXES, 2); 377 | } 378 | 379 | void ADXL345::setTapDetectionY(bool state) 380 | { 381 | writeRegisterBit(ADXL345_REG_TAP_AXES, 1, state); 382 | } 383 | 384 | bool ADXL345::getTapDetectionY(void) 385 | { 386 | return readRegisterBit(ADXL345_REG_TAP_AXES, 1); 387 | } 388 | 389 | void ADXL345::setTapDetectionZ(bool state) 390 | { 391 | writeRegisterBit(ADXL345_REG_TAP_AXES, 0, state); 392 | } 393 | 394 | bool ADXL345::getTapDetectionZ(void) 395 | { 396 | return readRegisterBit(ADXL345_REG_TAP_AXES, 0); 397 | } 398 | 399 | void ADXL345::setTapDetectionXYZ(bool state) 400 | { 401 | uint8_t value; 402 | 403 | value = readRegister8(ADXL345_REG_TAP_AXES); 404 | 405 | if (state) 406 | { 407 | value |= 0b00000111; 408 | } else 409 | { 410 | value &= 0b11111000; 411 | } 412 | 413 | writeRegister8(ADXL345_REG_TAP_AXES, value); 414 | } 415 | 416 | 417 | void ADXL345::useInterrupt(adxl345_int_t interrupt) 418 | { 419 | if (interrupt == 0) 420 | { 421 | writeRegister8(ADXL345_REG_INT_MAP, 0x00); 422 | } else 423 | { 424 | writeRegister8(ADXL345_REG_INT_MAP, 0xFF); 425 | } 426 | 427 | writeRegister8(ADXL345_REG_INT_ENABLE, 0x70); 428 | } 429 | 430 | Activites ADXL345::readActivites(void) 431 | { 432 | uint8_t data = readRegister8(ADXL345_REG_INT_SOURCE); 433 | 434 | a.isOverrun = ((data >> ADXL345_OVERRUN) & 1); 435 | a.isWatermark = ((data >> ADXL345_WATERMARK) & 1); 436 | a.isFreeFall = ((data >> ADXL345_FREE_FALL) & 1); 437 | a.isInactivity = ((data >> ADXL345_INACTIVITY) & 1); 438 | a.isActivity = ((data >> ADXL345_ACTIVITY) & 1); 439 | a.isDoubleTap = ((data >> ADXL345_DOUBLE_TAP) & 1); 440 | a.isTap = ((data >> ADXL345_SINGLE_TAP) & 1); 441 | a.isDataReady = ((data >> ADXL345_DATA_READY) & 1); 442 | 443 | data = readRegister8(ADXL345_REG_ACT_TAP_STATUS); 444 | 445 | a.isActivityOnX = ((data >> 6) & 1); 446 | a.isActivityOnY = ((data >> 5) & 1); 447 | a.isActivityOnZ = ((data >> 4) & 1); 448 | a.isTapOnX = ((data >> 2) & 1); 449 | a.isTapOnY = ((data >> 1) & 1); 450 | a.isTapOnZ = ((data >> 0) & 1); 451 | 452 | return a; 453 | } 454 | 455 | // Write byte to register 456 | void ADXL345::writeRegister8(uint8_t reg, uint8_t value) 457 | { 458 | Wire.beginTransmission(ADXL345_ADDRESS); 459 | #if ARDUINO >= 100 460 | Wire.write(reg); 461 | Wire.write(value); 462 | #else 463 | Wire.send(reg); 464 | Wire.send(value); 465 | #endif 466 | Wire.endTransmission(); 467 | } 468 | 469 | // Read byte to register 470 | uint8_t ADXL345::fastRegister8(uint8_t reg) 471 | { 472 | uint8_t value; 473 | Wire.beginTransmission(ADXL345_ADDRESS); 474 | #if ARDUINO >= 100 475 | Wire.write(reg); 476 | #else 477 | Wire.send(reg); 478 | #endif 479 | Wire.endTransmission(); 480 | 481 | Wire.requestFrom(ADXL345_ADDRESS, 1); 482 | #if ARDUINO >= 100 483 | value = Wire.read(); 484 | #else 485 | value = Wire.receive(); 486 | #endif; 487 | Wire.endTransmission(); 488 | 489 | return value; 490 | } 491 | 492 | // Read byte from register 493 | uint8_t ADXL345::readRegister8(uint8_t reg) 494 | { 495 | uint8_t value; 496 | Wire.beginTransmission(ADXL345_ADDRESS); 497 | #if ARDUINO >= 100 498 | Wire.write(reg); 499 | #else 500 | Wire.send(reg); 501 | #endif 502 | Wire.endTransmission(); 503 | 504 | Wire.beginTransmission(ADXL345_ADDRESS); 505 | Wire.requestFrom(ADXL345_ADDRESS, 1); 506 | while(!Wire.available()) {}; 507 | #if ARDUINO >= 100 508 | value = Wire.read(); 509 | #else 510 | value = Wire.receive(); 511 | #endif; 512 | Wire.endTransmission(); 513 | 514 | return value; 515 | } 516 | 517 | // Read word from register 518 | int16_t ADXL345::readRegister16(uint8_t reg) 519 | { 520 | int16_t value; 521 | Wire.beginTransmission(ADXL345_ADDRESS); 522 | #if ARDUINO >= 100 523 | Wire.write(reg); 524 | #else 525 | Wire.send(reg); 526 | #endif 527 | Wire.endTransmission(); 528 | 529 | Wire.beginTransmission(ADXL345_ADDRESS); 530 | Wire.requestFrom(ADXL345_ADDRESS, 2); 531 | while(!Wire.available()) {}; 532 | #if ARDUINO >= 100 533 | uint8_t vla = Wire.read(); 534 | uint8_t vha = Wire.read(); 535 | #else 536 | uint8_t vla = Wire.receive(); 537 | uint8_t vha = Wire.receive(); 538 | #endif; 539 | Wire.endTransmission(); 540 | 541 | value = vha << 8 | vla; 542 | 543 | return value; 544 | } 545 | 546 | void ADXL345::writeRegisterBit(uint8_t reg, uint8_t pos, bool state) 547 | { 548 | uint8_t value; 549 | value = readRegister8(reg); 550 | 551 | if (state) 552 | { 553 | value |= (1 << pos); 554 | } else 555 | { 556 | value &= ~(1 << pos); 557 | } 558 | 559 | writeRegister8(reg, value); 560 | } 561 | 562 | bool ADXL345::readRegisterBit(uint8_t reg, uint8_t pos) 563 | { 564 | uint8_t value; 565 | value = readRegister8(reg); 566 | return ((value >> pos) & 1); 567 | } 568 | -------------------------------------------------------------------------------- /lib/ADXL345/src/ADXL345.h: -------------------------------------------------------------------------------- 1 | /* 2 | ADXL345.h - Header file for the ADXL345 Triple Axis Accelerometer Arduino Library. 3 | 4 | Version: 1.1.0 5 | (c) 2014 Korneliusz Jarzebski 6 | www.jarzebski.pl 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the version 3 GNU General Public License as 10 | published by the Free Software Foundation. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef ADXL345_h 22 | #define ADXL345_h 23 | 24 | #if ARDUINO >= 100 25 | #include "Arduino.h" 26 | #else 27 | #include "WProgram.h" 28 | #endif 29 | 30 | #define ADXL345_ADDRESS (0x53) 31 | #define ADXL345_REG_DEVID (0x00) 32 | #define ADXL345_REG_THRESH_TAP (0x1D) // 1 33 | #define ADXL345_REG_OFSX (0x1E) 34 | #define ADXL345_REG_OFSY (0x1F) 35 | #define ADXL345_REG_OFSZ (0x20) 36 | #define ADXL345_REG_DUR (0x21) // 2 37 | #define ADXL345_REG_LATENT (0x22) // 3 38 | #define ADXL345_REG_WINDOW (0x23) // 4 39 | #define ADXL345_REG_THRESH_ACT (0x24) // 5 40 | #define ADXL345_REG_THRESH_INACT (0x25) // 6 41 | #define ADXL345_REG_TIME_INACT (0x26) // 7 42 | #define ADXL345_REG_ACT_INACT_CTL (0x27) 43 | #define ADXL345_REG_THRESH_FF (0x28) // 8 44 | #define ADXL345_REG_TIME_FF (0x29) // 9 45 | #define ADXL345_REG_TAP_AXES (0x2A) 46 | #define ADXL345_REG_ACT_TAP_STATUS (0x2B) 47 | #define ADXL345_REG_BW_RATE (0x2C) 48 | #define ADXL345_REG_POWER_CTL (0x2D) 49 | #define ADXL345_REG_INT_ENABLE (0x2E) 50 | #define ADXL345_REG_INT_MAP (0x2F) 51 | #define ADXL345_REG_INT_SOURCE (0x30) // A 52 | #define ADXL345_REG_DATA_FORMAT (0x31) 53 | #define ADXL345_REG_DATAX0 (0x32) 54 | #define ADXL345_REG_DATAX1 (0x33) 55 | #define ADXL345_REG_DATAY0 (0x34) 56 | #define ADXL345_REG_DATAY1 (0x35) 57 | #define ADXL345_REG_DATAZ0 (0x36) 58 | #define ADXL345_REG_DATAZ1 (0x37) 59 | #define ADXL345_REG_FIFO_CTL (0x38) 60 | #define ADXL345_REG_FIFO_STATUS (0x39) 61 | 62 | #define ADXL345_GRAVITY_SUN 273.95f 63 | #define ADXL345_GRAVITY_EARTH 9.80665f 64 | #define ADXL345_GRAVITY_MOON 1.622f 65 | #define ADXL345_GRAVITY_MARS 3.69f 66 | #define ADXL345_GRAVITY_NONE 1.00f 67 | 68 | typedef enum 69 | { 70 | ADXL345_DATARATE_3200HZ = 0b1111, 71 | ADXL345_DATARATE_1600HZ = 0b1110, 72 | ADXL345_DATARATE_800HZ = 0b1101, 73 | ADXL345_DATARATE_400HZ = 0b1100, 74 | ADXL345_DATARATE_200HZ = 0b1011, 75 | ADXL345_DATARATE_100HZ = 0b1010, 76 | ADXL345_DATARATE_50HZ = 0b1001, 77 | ADXL345_DATARATE_25HZ = 0b1000, 78 | ADXL345_DATARATE_12_5HZ = 0b0111, 79 | ADXL345_DATARATE_6_25HZ = 0b0110, 80 | ADXL345_DATARATE_3_13HZ = 0b0101, 81 | ADXL345_DATARATE_1_56HZ = 0b0100, 82 | ADXL345_DATARATE_0_78HZ = 0b0011, 83 | ADXL345_DATARATE_0_39HZ = 0b0010, 84 | ADXL345_DATARATE_0_20HZ = 0b0001, 85 | ADXL345_DATARATE_0_10HZ = 0b0000 86 | } adxl345_dataRate_t; 87 | 88 | typedef enum 89 | { 90 | ADXL345_INT2 = 0b01, 91 | ADXL345_INT1 = 0b00 92 | } adxl345_int_t; 93 | 94 | typedef enum 95 | { 96 | ADXL345_DATA_READY = 0x07, 97 | ADXL345_SINGLE_TAP = 0x06, 98 | ADXL345_DOUBLE_TAP = 0x05, 99 | ADXL345_ACTIVITY = 0x04, 100 | ADXL345_INACTIVITY = 0x03, 101 | ADXL345_FREE_FALL = 0x02, 102 | ADXL345_WATERMARK = 0x01, 103 | ADXL345_OVERRUN = 0x00 104 | } adxl345_activity_t; 105 | 106 | typedef enum 107 | { 108 | ADXL345_RANGE_16G = 0b11, 109 | ADXL345_RANGE_8G = 0b10, 110 | ADXL345_RANGE_4G = 0b01, 111 | ADXL345_RANGE_2G = 0b00 112 | } adxl345_range_t; 113 | 114 | #ifndef VECTOR_STRUCT_H 115 | #define VECTOR_STRUCT_H 116 | struct Vector 117 | { 118 | float XAxis; 119 | float YAxis; 120 | float ZAxis; 121 | }; 122 | #endif 123 | 124 | struct Activites 125 | { 126 | bool isOverrun; 127 | bool isWatermark; 128 | bool isFreeFall; 129 | bool isInactivity; 130 | bool isActivity; 131 | bool isActivityOnX; 132 | bool isActivityOnY; 133 | bool isActivityOnZ; 134 | bool isDoubleTap; 135 | bool isTap; 136 | bool isTapOnX; 137 | bool isTapOnY; 138 | bool isTapOnZ; 139 | bool isDataReady; 140 | }; 141 | 142 | class ADXL345 143 | { 144 | public: 145 | 146 | bool begin(int sda, int scl); 147 | void clearSettings(void); 148 | 149 | Vector readRaw(void); 150 | Vector readNormalize(float gravityFactor = ADXL345_GRAVITY_EARTH); 151 | Vector readScaled(void); 152 | 153 | Activites readActivites(void); 154 | 155 | Vector lowPassFilter(Vector vector, float alpha = 0.5); 156 | 157 | void setRange(adxl345_range_t range); 158 | adxl345_range_t getRange(void); 159 | 160 | void setDataRate(adxl345_dataRate_t dataRate); 161 | adxl345_dataRate_t getDataRate(void); 162 | 163 | void setTapThreshold(float threshold); 164 | float getTapThreshold(void); 165 | 166 | void setTapDuration(float duration); 167 | float getTapDuration(void); 168 | 169 | void setDoubleTapLatency(float latency); 170 | float getDoubleTapLatency(void); 171 | 172 | void setDoubleTapWindow(float window); 173 | float getDoubleTapWindow(void); 174 | 175 | void setActivityThreshold(float threshold); 176 | float getActivityThreshold(void); 177 | 178 | void setInactivityThreshold(float threshold); 179 | float getInactivityThreshold(void); 180 | 181 | void setTimeInactivity(uint8_t time); 182 | uint8_t getTimeInactivity(void); 183 | 184 | void setFreeFallThreshold(float threshold); 185 | float getFreeFallThreshold(void); 186 | 187 | void setFreeFallDuration(float duration); 188 | float getFreeFallDuration(); 189 | 190 | void setActivityX(bool state); 191 | bool getActivityX(void); 192 | void setActivityY(bool state); 193 | bool getActivityY(void); 194 | void setActivityZ(bool state); 195 | bool getActivityZ(void); 196 | void setActivityXYZ(bool state); 197 | 198 | void setInactivityX(bool state); 199 | bool getInactivityX(void); 200 | void setInactivityY(bool state); 201 | bool getInactivityY(void); 202 | void setInactivityZ(bool state); 203 | bool getInactivityZ(void); 204 | void setInactivityXYZ(bool state); 205 | 206 | void setTapDetectionX(bool state); 207 | bool getTapDetectionX(void); 208 | void setTapDetectionY(bool state); 209 | bool getTapDetectionY(void); 210 | void setTapDetectionZ(bool state); 211 | bool getTapDetectionZ(void); 212 | void setTapDetectionXYZ(bool state); 213 | 214 | void useInterrupt(adxl345_int_t interrupt); 215 | 216 | 217 | private: 218 | Vector r; 219 | Vector n; 220 | Vector f; 221 | Activites a; 222 | adxl345_range_t _range; 223 | 224 | void writeRegister8(uint8_t reg, uint8_t value); 225 | uint8_t readRegister8(uint8_t reg); 226 | uint8_t fastRegister8(uint8_t reg); 227 | int16_t readRegister16(uint8_t reg); 228 | void writeRegisterBit(uint8_t reg, uint8_t pos, bool state); 229 | bool readRegisterBit(uint8_t reg, uint8_t pos); 230 | 231 | 232 | }; 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /pio/hooks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import errno 3 | from shutil import copyfile 4 | 5 | 6 | Import("env") 7 | 8 | 9 | # process release build flags 10 | my_flags = env.ParseFlags(env['BUILD_FLAGS']) 11 | #defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} 12 | 13 | # name release target 14 | #RELEASE_NAME = defines.get("RELEASE_NAME") 15 | #RELEASE_DIR = defines.get("RELEASE_DIR") 16 | #RELEASE_TARGET = "/".join([RELEASE_DIR, RELEASE_NAME + ".hex"]) 17 | 18 | 19 | for key in my_flags['CPPDEFINES']: 20 | if isinstance(key, list) and key[0] == 'CLIENT_VERSION': 21 | CLIENT_VERSION = key[1] 22 | print("Building client version {}.".format(CLIENT_VERSION)) 23 | 24 | # get environment vars for built target 25 | PROJECTBUILD_DIR = env["PROJECTBUILD_DIR"] 26 | PIOENV = env["PIOENV"] 27 | PROGNAME = env["PROGNAME"] 28 | TARGET_DIR = "target" 29 | BUILD_TARGET = "/".join([PROJECTBUILD_DIR, PIOENV, PROGNAME + ".bin"]) 30 | VERSION_FILE_DEV = "/".join([TARGET_DIR, "current-DEV.version"]) 31 | VERSION_FILE_TEST = "/".join([TARGET_DIR, "current-TEST.version"]) 32 | VERSION_FILE_PROD = "/".join([TARGET_DIR, "current-PROD.version"]) 33 | RELEASE_TARGET = "/".join([TARGET_DIR, PIOENV + "-" + CLIENT_VERSION + ".bin"]) 34 | 35 | # copy binaries to target directory 36 | def copy2release(source, target, env): 37 | print("copying build {} to release {}".format(BUILD_TARGET, RELEASE_TARGET)) 38 | ensureDirectoryExists(RELEASE_TARGET) 39 | copyfile(BUILD_TARGET, RELEASE_TARGET) 40 | writeVersionFile() 41 | 42 | def ensureDirectoryExists(filename): 43 | if not os.path.exists(os.path.dirname(filename)): 44 | try: 45 | os.makedirs(os.path.dirname(filename)) 46 | except OSError as exc: # Guard against race condition 47 | if exc.errno != errno.EEXIST: 48 | raise 49 | 50 | def writeVersionFile(): 51 | with open(VERSION_FILE_DEV, "w") as versionFile: 52 | versionFile.write("{}".format(CLIENT_VERSION)) 53 | with open(VERSION_FILE_TEST, "w") as versionFile: 54 | versionFile.write("{}".format(CLIENT_VERSION)) 55 | with open(VERSION_FILE_PROD, "w") as versionFile: 56 | versionFile.write("{}".format(CLIENT_VERSION)) 57 | 58 | # add post hook 59 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", copy2release) 60 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | ; 11 | ; This got inspiration from https://github.com/arendst/Sonoff-Tasmota 12 | 13 | [platformio] 14 | src_dir = EspaperClient 15 | 16 | 17 | ; *** Uncomment one of the lines below to build/upload only one environment 18 | ; env_default = Espaper29Bw-PROD 19 | ; env_default = Espaper29Bw-TEST 20 | ; env_default = Espaper29Bw-DEV 21 | ; env_default = Espaper42Bw-PROD 22 | ; env_default = Espaper42Bw-TEST 23 | ; env_default = Espaper42Bw-DEV 24 | ; env_default = TTGOT529Bw-PROD 25 | ; env_default = TTGOT529Bw-TEST 26 | ; env_default = TTGOT529Bw-DEV 27 | ; env_default = Espaper75Bw-PROD 28 | ; env_default = Espaper75Bw-TEST 29 | ; env_default = Espaper75Bw-DEV 30 | ; env_default = ESPColorKit-TEST 31 | 32 | [common] 33 | client_version = -DCLIENT_VERSION=V031 34 | monitor_speed = 115200 35 | upload_speed = 921600 36 | upload_resetmethod = nodemcu 37 | 38 | lib_deps = Mini Grafx@1.2.2 39 | 40 | ; *** Upload Serial reset method for Wemos and NodeMCU 41 | upload_port = /dev/cu.SLAB_USBtoUART 42 | extra_scripts = pio/hooks.py 43 | 44 | 45 | [esp8266] 46 | ; *** Esp8266 core for Arduino version 2.5.0 47 | platform = espressif8266@2.2.0 48 | build_flags = -D NDEBUG 49 | -mtarget-align 50 | ${common.client_version} 51 | -Wl,-Map,firmware.map 52 | -Wl,-Teagle.flash.2m512.ld 53 | ; lwIP 1.4 (Default) 54 | ; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH 55 | ; lwIP 2 - Low Memory 56 | ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY 57 | ; lwIP 2 - Higher Bandwidth 58 | ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH 59 | ; lwIP 2 - Higher Bandwidth Low Memory no Features 60 | ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY_LOW_FLASH 61 | ; lwIP 2 - Higher Bandwidth no Features (Tasmota default) 62 | -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH 63 | -DVTABLES_IN_FLASH 64 | -fno-exceptions 65 | -lstdc++ 66 | framework = arduino 67 | board = esp_wroom_02 68 | board_build.flash_mode = dout 69 | build_unflags = -Wall 70 | 71 | ; *** Fix espressif8266@1.7.0 induced undesired all warnings 72 | build_unflags = -Wall 73 | 74 | ; set CPU frequency to 80MHz (default) or 160MHz 75 | board_build.f_cpu = 80000000L 76 | ;board_build.f_cpu = 160000000L 77 | 78 | [esp32] 79 | framework = arduino 80 | board = esp-wrover-kit 81 | platform = espressif32 82 | ;board_build.flash_mode = ${common.board_build.flash_mode} 83 | board_build.f_cpu = 80000000L 84 | ;board_build.f_cpu = 160000000L 85 | ;build_unflags = ${common.build_unflags} 86 | build_flags = ${common.client_version} 87 | 88 | [env:Espaper29Bw-PROD] 89 | platform = ${esp8266.platform} 90 | framework = ${esp8266.framework} 91 | board = ${esp8266.board} 92 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 93 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 94 | build_unflags = ${esp8266.build_unflags} 95 | build_flags = ${esp8266.build_flags} -DESPAPER29BW 96 | monitor_speed = ${common.monitor_speed} 97 | upload_port = ${common.upload_port} 98 | upload_resetmethod = ${common.upload_resetmethod} 99 | upload_speed = ${common.upload_speed} 100 | extra_scripts = ${common.extra_scripts} 101 | lib_deps = ${common.lib_deps} 102 | 103 | [env:Espaper29Bw-TEST] 104 | platform = ${esp8266.platform} 105 | framework = ${esp8266.framework} 106 | board = ${esp8266.board} 107 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 108 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 109 | build_unflags = ${esp8266.build_unflags} 110 | build_flags = ${esp8266.build_flags} -DESPAPER29BW -DTEST_ENV 111 | monitor_speed = ${common.monitor_speed} 112 | upload_port = ${common.upload_port} 113 | upload_resetmethod = ${common.upload_resetmethod} 114 | upload_speed = ${common.upload_speed} 115 | extra_scripts = ${common.extra_scripts} 116 | lib_deps = ${common.lib_deps} 117 | 118 | [env:Espaper29Bw-DEV] 119 | platform = ${esp8266.platform} 120 | framework = ${esp8266.framework} 121 | board = ${esp8266.board} 122 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 123 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 124 | build_unflags = ${esp8266.build_unflags} 125 | build_flags = ${esp8266.build_flags} -DESPAPER29BW -DDEV_ENV 126 | monitor_speed = ${common.monitor_speed} 127 | upload_port = ${common.upload_port} 128 | upload_resetmethod = ${common.upload_resetmethod} 129 | upload_speed = ${common.upload_speed} 130 | extra_scripts = ${common.extra_scripts} 131 | lib_deps = ${common.lib_deps} 132 | 133 | 134 | [env:Espaper42Bw-PROD] 135 | platform = ${esp8266.platform} 136 | framework = ${esp8266.framework} 137 | board = ${esp8266.board} 138 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 139 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 140 | build_unflags = ${esp8266.build_unflags} 141 | build_flags = ${esp8266.build_flags} -DESPAPER42BW 142 | monitor_speed = ${common.monitor_speed} 143 | upload_port = ${common.upload_port} 144 | upload_resetmethod = ${common.upload_resetmethod} 145 | upload_speed = ${common.upload_speed} 146 | extra_scripts = ${common.extra_scripts} 147 | lib_deps = ${common.lib_deps} 148 | 149 | [env:Espaper42Bw-TEST] 150 | platform = ${esp8266.platform} 151 | framework = ${esp8266.framework} 152 | board = ${esp8266.board} 153 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 154 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 155 | build_unflags = ${esp8266.build_unflags} 156 | build_flags = ${esp8266.build_flags} -DESPAPER42BW -DTEST_ENV 157 | monitor_speed = ${common.monitor_speed} 158 | upload_port = ${common.upload_port} 159 | upload_resetmethod = ${common.upload_resetmethod} 160 | upload_speed = ${common.upload_speed} 161 | extra_scripts = ${common.extra_scripts} 162 | lib_deps = ${common.lib_deps} 163 | 164 | [env:Espaper42Bw-DEV] 165 | platform = ${esp8266.platform} 166 | framework = ${esp8266.framework} 167 | board = ${esp8266.board} 168 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 169 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 170 | build_unflags = ${esp8266.build_unflags} 171 | build_flags = ${esp8266.build_flags} -DESPAPER42BW -DDEV_ENV 172 | monitor_speed = ${common.monitor_speed} 173 | upload_port = ${common.upload_port} 174 | upload_resetmethod = ${common.upload_resetmethod} 175 | upload_speed = ${common.upload_speed} 176 | extra_scripts = ${common.extra_scripts} 177 | lib_deps = ${common.lib_deps} 178 | 179 | [env:ESPColorKit-TEST] 180 | platform = ${esp8266.platform} 181 | framework = ${esp8266.framework} 182 | board = ${esp8266.board} 183 | board_build.flash_mode = ${esp8266.board_build.flash_mode} 184 | board_build.f_cpu = ${esp8266.board_build.f_cpu} 185 | build_unflags = ${esp8266.build_unflags} 186 | build_flags = ${esp8266.build_flags} -DESP_COLOR_KIT -DTEST_ENV 187 | monitor_speed = ${common.monitor_speed} 188 | upload_port = ${common.upload_port} 189 | upload_resetmethod = ${common.upload_resetmethod} 190 | upload_speed = ${common.upload_speed} 191 | extra_scripts = ${common.extra_scripts} 192 | lib_deps = ${common.lib_deps} 193 | 194 | [env:TTGOT529Bw-PROD] 195 | framework = arduino 196 | board = esp-wrover-kit 197 | platform = espressif32 198 | ;board_build.flash_mode = ${common.board_build.flash_mode} 199 | board_build.f_cpu = ${esp32.board_build.f_cpu} 200 | ;build_unflags = ${common.build_unflags} 201 | build_flags = ${esp32.build_flags} -DTTGOT529BW 202 | monitor_speed = ${common.monitor_speed} 203 | upload_port = ${common.upload_port} 204 | upload_resetmethod = ${common.upload_resetmethod} 205 | upload_speed = ${common.upload_speed} 206 | extra_scripts = ${common.extra_scripts} 207 | lib_deps = ${common.lib_deps} 208 | 209 | [env:TTGOT529Bw-TEST] 210 | framework = arduino 211 | board = esp-wrover-kit 212 | platform = espressif32 213 | ;board_build.flash_mode = ${common.board_build.flash_mode} 214 | board_build.f_cpu = ${esp32.board_build.f_cpu} 215 | ;build_unflags = ${common.build_unflags} 216 | build_flags = ${esp32.build_flags} -DTTGOT529BW -DTEST_ENV 217 | monitor_speed = ${common.monitor_speed} 218 | upload_port = ${common.upload_port} 219 | upload_resetmethod = ${common.upload_resetmethod} 220 | upload_speed = ${common.upload_speed} 221 | extra_scripts = ${common.extra_scripts} 222 | lib_deps = ${common.lib_deps} 223 | 224 | [env:TTGOT529Bw-DEV] 225 | framework = arduino 226 | board = esp-wrover-kit 227 | platform = espressif32 228 | ;board_build.flash_mode = ${common.board_build.flash_mode} 229 | board_build.f_cpu = ${esp32.board_build.f_cpu} 230 | ;build_unflags = ${common.build_unflags} 231 | build_flags = ${esp32.build_flags} -DTTGOT529BW -DDEV_ENV 232 | monitor_speed = ${common.monitor_speed} 233 | upload_port = ${common.upload_port} 234 | upload_resetmethod = ${common.upload_resetmethod} 235 | upload_speed = ${common.upload_speed} 236 | extra_scripts = ${common.extra_scripts} 237 | lib_deps = ${common.lib_deps} 238 | 239 | [env:Espaper75Bw-PROD] 240 | framework = arduino 241 | board = esp-wrover-kit 242 | platform = espressif32 243 | ;board_build.flash_mode = ${common.board_build.flash_mode} 244 | board_build.f_cpu = ${esp32.board_build.f_cpu} 245 | ;build_unflags = ${common.build_unflags} 246 | build_flags = ${esp32.build_flags} -DESPAPER75BW 247 | monitor_speed = ${common.monitor_speed} 248 | upload_port = ${common.upload_port} 249 | upload_resetmethod = ${common.upload_resetmethod} 250 | upload_speed = ${common.upload_speed} 251 | extra_scripts = ${common.extra_scripts} 252 | lib_deps = ${common.lib_deps} 253 | 254 | [env:Espaper75Bw-TEST] 255 | framework = arduino 256 | board = esp-wrover-kit 257 | platform = espressif32 258 | ;board_build.flash_mode = ${common.board_build.flash_mode} 259 | board_build.f_cpu = ${esp32.board_build.f_cpu} 260 | ;build_unflags = ${common.build_unflags} 261 | build_flags = ${esp32.build_flags} -DESPAPER75BW -DTEST_ENV 262 | monitor_speed = ${common.monitor_speed} 263 | upload_port = ${common.upload_port} 264 | upload_resetmethod = ${common.upload_resetmethod} 265 | upload_speed = ${common.upload_speed} 266 | extra_scripts = ${common.extra_scripts} 267 | lib_deps = ${common.lib_deps} 268 | 269 | [env:Espaper75Bw-DEV] 270 | framework = arduino 271 | board = esp-wrover-kit 272 | platform = espressif32 273 | ;board_build.flash_mode = ${common.board_build.flash_mode} 274 | board_build.f_cpu = ${esp32.board_build.f_cpu} 275 | ;build_unflags = ${common.build_unflags} 276 | build_flags = ${esp32.build_flags} -DESPAPER75BW -DDEV_ENV 277 | monitor_speed = ${common.monitor_speed} 278 | upload_port = ${common.upload_port} 279 | upload_resetmethod = ${common.upload_resetmethod} 280 | upload_speed = ${common.upload_speed} 281 | extra_scripts = ${common.extra_scripts} 282 | lib_deps = ${common.lib_deps} 283 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------