├── .gitignore ├── LICENSE ├── README.md ├── analizeNext.py ├── arduinoIDE2platformIO.py ├── crossReference.py ├── saveNext.sh └── testProject └── ESP_ticker ├── Debug.h ├── ESP_ticker.h ├── ESP_ticker.ino ├── FSexplorer.ino ├── TimeSyncClass.cpp ├── TimeSyncClass.h ├── data ├── FSexplorer.css ├── FSexplorer.html ├── FSexplorer.png ├── Uno_reset-en_snippet.jpg ├── favicon.ico ├── index.css ├── index.js ├── newsFiles │ ├── LCL-001 │ └── NWS-001 ├── settings.ini └── settings.png ├── helperStuff.ino ├── jsonStuff.ino ├── littlefsStuff.ino ├── networkStuff.ino ├── newsapi_org.ino ├── newsapi_org.txt ├── parola_Fonts_data.h ├── platformio.ini ├── restAPI.ino ├── sendIndexPage.ino ├── settingStuff.ino ├── updateServerHtml.h └── weerlive_nl.ino /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pio.nosync 3 | .vscode/.browse.c_cpp.db* 4 | .vscode/c_cpp_properties.json 5 | .vscode/launch.json 6 | .vscode/ipch 7 | .vscode/**/* 8 | testProject/*/PlatformIO/* 9 | claude-dev 10 | changes 11 | backups 12 | changes/* 13 | next.py 14 | saveNext.py 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Willem Aandewiel 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 | # How to convert a ArduinoIDE project to PlatformIO 2 | 3 | 1) You have to clone the repo to your computer. 4 | 2) In a terminal window you `cd` to the folder where you cloned the repo. 5 | 3) Then you type `python3 arduinoIDE2platformIO.py --project_dir ` 6 | (note, there are two `--` before `project_dir`) 7 | 8 | The converted project is located in `/platformIO/` 9 | 10 | All you have to do is edit the `platformio.ini` file to your needs. 11 | 12 | Mind you: the convertor will not always do everything for you. Sometimes you have to iterate [compile] -> solve compile errors -> [compile] -> solve compile errors ... 13 | 14 | If it compiles without errors test it. If it works as expected you can cleanup the ‘arduinoGlue.h’ file with the ‘crossReference.py’ file. 15 | 16 | Next step can be to look for prototypes that are not used in any other file then where the function is defined. You can then move that prototype definition to the '.h' file of the corresponding '.cpp' file. 17 | It is not necessary but it makes better C(++) code. 18 | 19 | 20 | ## structure ArduinoIDE project 21 | 22 | ## structure PlatformIO project 23 | 24 | -------------------------------------------------------------------------------- /analizeNext.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | # Read the content of the uploaded file 4 | file_path = '../../next.py' 5 | with open(file_path, 'r') as file: 6 | file_content = file.read() 7 | 8 | # Parse the content of the file 9 | parsed_ast = ast.parse(file_content) 10 | 11 | # Initialize a dictionary to store function calls 12 | function_calls = {} 13 | 14 | class FunctionVisitor(ast.NodeVisitor): 15 | def __init__(self): 16 | self.current_function = None 17 | 18 | def visit_FunctionDef(self, node): 19 | self.current_function = node.name 20 | function_calls[self.current_function] = [] 21 | self.generic_visit(node) 22 | 23 | def visit_Call(self, node): 24 | if isinstance(node.func, ast.Name) and self.current_function: 25 | function_calls[self.current_function].append(node.func.id) 26 | self.generic_visit(node) 27 | 28 | # Visit all nodes in the AST 29 | visitor = FunctionVisitor() 30 | visitor.visit(parsed_ast) 31 | 32 | # Filter only the functions defined in the file 33 | defined_functions = set(function_calls.keys()) 34 | 35 | # Sort functions based on the order of calling 36 | sorted_function_calls = sorted( 37 | [(func, [call for call in calls if call in defined_functions]) 38 | for func, calls in function_calls.items()], 39 | key=lambda item: item[0] 40 | ) 41 | 42 | # Print the sorted function calls 43 | for function, calls in sorted_function_calls: 44 | if function != "main": 45 | #print(f"Function '{function}' calls: {', '.join(calls) if calls else 'No calls'}") 46 | print(f"Function '{function}' calls:") 47 | for func in calls: 48 | print(f"\t{func}") 49 | for function, calls in sorted_function_calls: 50 | if function == "main": 51 | #print(f"Function '{function}' calls: {', '.join(calls) if calls else 'No calls'}") 52 | print(f"Function '{function}' calls:") 53 | for func in calls: 54 | print(f"\t{func}") 55 | -------------------------------------------------------------------------------- /crossReference.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------ 2 | # 3 | # X-Reference a arduinoGlue.h file 4 | # 5 | # file name : crossReference.py 6 | # 7 | # by : Willem Aandewiel 8 | # 9 | # Version : v0.81 (04-10-2024) 10 | # 11 | # Usage: python3 crossReference.py 1: 162 | usage_str = ", ".join(usage) 163 | print(f"{var_type} {var_name} {usage_str}") 164 | 165 | except Exception as e: 166 | logging.error(f"An unexpected error occurred: {e}") 167 | 168 | if __name__ == "__main__": 169 | if len(sys.argv) != 2: 170 | print("Usage: python crossReference.py ") 171 | sys.exit(1) 172 | 173 | project_path = sys.argv[1] 174 | process_arduino_project(project_path) 175 | 176 | 177 | #******************************************************************************************* 178 | # MIT License 179 | # 180 | # Copyright (c) 2024 Willem Aandewiel 181 | # 182 | # Permission is hereby granted, free of charge, to any person obtaining a copy 183 | # of this software and associated documentation files (the "Software"), to deal 184 | # in the Software without restriction, including without limitation the rights 185 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 186 | # copies of the Software, and to permit persons to whom the Software is 187 | # furnished to do so, subject to the following conditions: 188 | # 189 | # The above copyright notice and this permission notice shall be included in all 190 | # copies or substantial portions of the Software. 191 | # 192 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 193 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 194 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 195 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 196 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 197 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 198 | # SOFTWARE. 199 | #************************************************************************************************ 200 | 201 | -------------------------------------------------------------------------------- /saveNext.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | NOW=$(date '+%Y-%m-%d_%H.%M') 4 | echo ${NOW} 5 | SOURCE="${HOME}/tmp/arduinoIDE2platformIO-convertor/next.py" 6 | #DEST="${HOME}/Documents/claudeProgress/next_${NOW}.py" 7 | DEST="${HOME}/Documents/claudeProgress/" 8 | echo "Copy ${SOURCE} to ${DEST}/next_${NOW}.py" 9 | # 10 | cp -v ${SOURCE} ${DEST}/next_${NOW}.py 11 | cp -v ${SOURCE} ../../changes/next_${NOW}.py 12 | 13 | echo "Done!" 14 | 15 | ls -l ${DEST} 16 | 17 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/Debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : Debug.h, part of ESP_tickerExtend 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** Met dank aan Erik 7 | ** 8 | ** TERMS OF USE: MIT License. See bottom of file. 9 | *************************************************************************** 10 | */ 11 | 12 | /*---- start macro's ------------------------------------------------------------------*/ 13 | 14 | #define Debug(...) ({ Serial.print(__VA_ARGS__); \ 15 | }) 16 | #define Debugln(...) ({ Serial.println(__VA_ARGS__); \ 17 | }) 18 | #define Debugf(...) ({ Serial.printf(__VA_ARGS__); \ 19 | }) 20 | 21 | #define DebugFlush() ({ Serial.flush(); \ 22 | }) 23 | 24 | 25 | #define DebugT(...) ({ Debug(__VA_ARGS__); \ 26 | }) 27 | #define DebugTln(...) ({ Debugln(__VA_ARGS__); \ 28 | }) 29 | #define DebugTf(...) ({ Debugf(__VA_ARGS__); \ 30 | }) 31 | 32 | /*---- einde macro's ------------------------------------------------------------------*/ 33 | 34 | 35 | /*************************************************************************** 36 | * 37 | * Permission is hereby granted, free of charge, to any person obtaining a 38 | * copy of this software and associated documentation files (the 39 | * "Software"), to deal in the Software without restriction, including 40 | * without limitation the rights to use, copy, modify, merge, publish, 41 | * distribute, sublicense, and/or sell copies of the Software, and to permit 42 | * persons to whom the Software is furnished to do so, subject to the 43 | * following conditions: 44 | * 45 | * The above copyright notice and this permission notice shall be included 46 | * in all copies or substantial portions of the Software. 47 | * 48 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 49 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 51 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 52 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 53 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 54 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 55 | * 56 | **************************************************************************** 57 | */ 58 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/ESP_ticker.h: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : ESP_ticker.h, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "TimeSyncClass.h" 17 | 18 | #include "Debug.h" 19 | //aaw#include "networkStuff.h" 20 | 21 | #include // @ 3.7.3 (was 3.5.5) 22 | #include 23 | #include "parola_Fonts_data.h" 24 | #include 25 | 26 | // Define the number of devices we have in the chain and the hardware interface 27 | // NOTE: These pin numbers are for ESP8266 hardware SPI and will probably not 28 | // work with your hardware and may need to be adapted 29 | //#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW 30 | #define HARDWARE_TYPE MD_MAX72XX::FC16_HW 31 | //#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW 32 | #define MAX_DEVICES 8 33 | #define MAX_SPEED 50 34 | 35 | //#define CLK_PIN 14 // or SCK 36 | //#define DATA_PIN 13 // or MOSI 37 | #define CS_PIN 15 // or SS 38 | 39 | #define SETTINGS_FILE "/settings.ini" 40 | #define LOCAL_SIZE 255 41 | #define NEWS_SIZE 512 42 | #define JSON_BUFF_MAX 255 43 | #define MAX_NO_NO_WORDS 20 44 | 45 | 46 | // HARDWARE SPI 47 | MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); 48 | 49 | // WiFi Server object and parameters 50 | ESP8266WebServer httpServer(80); 51 | 52 | #include // part of ESP8266 Core https://github.com/esp8266/Arduino 53 | 54 | // Scrolling parameters 55 | 56 | uint8_t inFX, outFX; 57 | textEffect_t effect[] = 58 | { 59 | PA_PRINT, 60 | //PA_SCAN_HORIZ, 61 | PA_SCROLL_LEFT, 62 | PA_WIPE, 63 | PA_SCROLL_UP_LEFT, 64 | PA_SCROLL_UP, 65 | PA_OPENING_CURSOR, 66 | PA_GROW_UP, 67 | PA_MESH, 68 | PA_SCROLL_UP_RIGHT, 69 | //PA_BLINDS, 70 | PA_CLOSING, 71 | PA_RANDOM, 72 | PA_GROW_DOWN, 73 | PA_SCAN_VERT, 74 | PA_SCROLL_DOWN_LEFT, 75 | PA_WIPE_CURSOR, 76 | //PA_DISSOLVE, 77 | PA_OPENING, 78 | PA_CLOSING_CURSOR, 79 | PA_SCROLL_DOWN_RIGHT, 80 | PA_SCROLL_RIGHT, 81 | //PA_SLICE, 82 | PA_SCROLL_DOWN, 83 | }; 84 | 85 | 86 | bool Verbose = false; 87 | char cDate[15], cTime[10]; 88 | uint32_t nrReboots; 89 | // Global message buffers shared by Wifi and Scrolling functions 90 | char cMsg[NEWS_SIZE]; 91 | char tempMessage[LOCAL_SIZE] = ""; 92 | uint8_t msgType; 93 | char actMessage[NEWS_SIZE], timeMsg[20]; 94 | char onTickerMessage[LOCAL_SIZE] = {}; 95 | char fileMessage[LOCAL_SIZE]; 96 | uint8_t newsMsgID = 0; 97 | uint8_t localMsgID = 0; 98 | int16_t valueLDR, valueIntensity; 99 | char fChar[10]; 100 | String lastReset = ""; 101 | uint32_t timeTimer = 0; 102 | uint32_t ntpTimer = millis() + 30000; 103 | uint32_t weerTimer = 0; 104 | uint32_t newsapiTimer = 0; 105 | uint32_t revisionTimer = 0; 106 | String noWords[MAX_NO_NO_WORDS+1]; 107 | char settingHostname[41]; 108 | char settingNewsNoWords[LOCAL_SIZE]; 109 | uint8_t settingLocalMaxMsg, settingTextSpeed, settingMaxIntensity; 110 | uint16_t settingLDRlowOffset, settingLDRhighOffset; 111 | char settingWeerLiveAUTH[51], settingWeerLiveLocation[51]; 112 | uint8_t settingWeerLiveInterval; 113 | char settingNewsAUTH[51]; 114 | uint8_t settingNewsInterval, settingNewsMaxMsg; 115 | bool LittleFSmounted = false; 116 | FSInfo LittleFSinfo; 117 | time_t now; 118 | struct tm timeinfo; 119 | bool timeSynced = false; 120 | 121 | 122 | TimeSync timeSync; 123 | 124 | const char *weekDayName[] { "Unknown", "Zondag", "Maandag", "Dinsdag", "Woensdag" 125 | , "Donderdag", "Vrijdag", "Zaterdag", "Unknown" }; 126 | const char *flashMode[] { "QIO", "QOUT", "DIO", "DOUT", "Unknown" }; 127 | 128 | 129 | /*************************************************************************** 130 | * 131 | * Permission is hereby granted, free of charge, to any person obtaining a 132 | * copy of this software and associated documentation files (the 133 | * "Software"), to deal in the Software without restriction, including 134 | * without limitation the rights to use, copy, modify, merge, publish, 135 | * distribute, sublicense, and/or sell copies of the Software, and to permit 136 | * persons to whom the Software is furnished to do so, subject to the 137 | * following conditions: 138 | * 139 | * The above copyright notice and this permission notice shall be included 140 | * in all copies or substantial portions of the Software. 141 | * 142 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 143 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 144 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 145 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 146 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 147 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 148 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 149 | * 150 | **************************************************************************** 151 | */ 152 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/ESP_ticker.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : ESP_ticker (lichtkrant) 4 | */ 5 | const char* FWversion = "v1.7.3 (04-05-2023)"; 6 | /* 7 | ** Copyright (c) 2021 .. 2023 Willem Aandewiel 8 | ** 9 | ** TERMS OF USE: MIT License. See bottom of file. 10 | *************************************************************************** 11 | 12 | Arduino-IDE settings for ESP-12E: 13 | 14 | - Board: "Generic ESP8266 Module" (ALLWAYS!!!!!) 15 | - Buildin Led: "2" 16 | - Upload Speed: "115200" 17 | - CPU Frequency: "80 MHz" (or if you need the speed: 160MHz) 18 | - Flash size: "4MB (FS:2M OTA~1019KB)" 19 | - Flash mode: "DIO" / "DOUT" 20 | - Flash Frequency: "40MHz" 21 | - Reset Method: "nodemcu" or something else 22 | - Debug port: "Disabled" 23 | - Debug Level: "None" 24 | - IwIP Variant: "v2 Lower Memory" 25 | - VTables: "Flash" 26 | - Exceptions: "Legacy (new can return nullptr)" 27 | - Erase Flash: "Only Sketch" 28 | - Espressif FW: "nonos-sdk 2.2.1+100 (190703)" 29 | - SSL Support: "All SSL ciphers (most compatible)" 30 | - Port: "ESPticker at <-- IP address -->" 31 | 32 | Arduino ESP8266 core v2.7.+ 33 | */ 34 | 35 | 36 | // Use the Parola library to scroll text on the display 37 | // IP address for the ESP8266 is displayed on the scrolling display 38 | // after startup initialisation and connected to the WiFi network. 39 | // 40 | // Connections for ESP8266 hardware SPI are: 41 | // Vcc 3v3 LED matrices seem to work at 3.3V 42 | // GND GND GND 43 | // DIN D7 HSPID or HMOSI 44 | // CS or LD D8 HSPICS or HCS 45 | // CLK D5 CLK or HCLK 46 | // 47 | // MD_MAX72XX library can be found at https://github.com/MajicDesigns/MD_MAX72XX 48 | // 49 | 50 | #define USE_UPDATE_SERVER 51 | 52 | #define _HOSTNAME "ESPticker" 53 | #include "ESP_ticker.h" 54 | 55 | 56 | //--------------------------------------------------------------------- 57 | int16_t calculateIntensity() 58 | { 59 | int a0In = 0; 60 | for (int l=0; l<2; l++) 61 | { 62 | //--- read analog A0 63 | a0In+= analogRead(A0); 64 | delay(200); 65 | } 66 | a0In = a0In / 2; //-- smooth things up a bit 67 | 68 | DebugTf("analogRead[%d], ", a0In); 69 | //---test if (a0In < settingLDRlowOffset) a0In = settingLDRlowOffset; 70 | Debugf(" LDRlowOffset[%d] LDRhighOffset[%d] ", settingLDRlowOffset, settingLDRhighOffset); 71 | valueLDR = (valueLDR + a0In) / 2; 72 | if (valueLDR < settingLDRlowOffset) valueLDR = settingLDRlowOffset; 73 | if (valueLDR > settingLDRhighOffset) valueLDR = settingLDRhighOffset; 74 | Debugf(" ==> valueLDR[%d]\r\n", valueLDR); 75 | 76 | //--- map LDR to offset..1024 -> 0..settingMax 77 | int intensity = map(valueLDR, settingLDRlowOffset, settingLDRhighOffset, 0, settingMaxIntensity); 78 | //DebugTf("map(%d, %d, %d, 0, %d) => [%d]\r\n", valueLDR, settingLDRlowOffset, settingLDRhighOffset 79 | // , 0 , settingMaxIntensity); 80 | 81 | return intensity; 82 | 83 | } // calculateIntensity() 84 | 85 | 86 | //--------------------------------------------------------------------- 87 | char *updateTime() 88 | { 89 | time(&now); 90 | snprintf(timeMsg, 20, "%02d : %02d", localtime(&now)->tm_hour, localtime(&now)->tm_min); 91 | return timeMsg; 92 | 93 | } // updateTime() 94 | 95 | 96 | //--------------------------------------------------------------------- 97 | bool getTheLocalTime(struct tm *info, uint32_t ms) 98 | { 99 | //-- getLocalTime() is not implemented in the ArduinoIDE 100 | //-- so this is a 'work around' function 101 | uint32_t start = millis(); 102 | time_t now; 103 | while((millis()-start) <= ms) 104 | { 105 | time(&now); 106 | localtime_r(&now, info); 107 | if(info->tm_year > (2016 - 1900)) 108 | { 109 | return true; 110 | } 111 | delay(10); 112 | } 113 | return false; 114 | 115 | } // getTheLocalTime() 116 | 117 | 118 | //--------------------------------------------------------------------- 119 | void splitNewsNoWords(const char *noNo) 120 | { 121 | DebugTln(noNo); 122 | int8_t wc = splitString(String(noNo), ' ', noWords, MAX_NO_NO_WORDS); 123 | for(int8_t i=0; i 1) 127 | { 128 | noWords[i].toLowerCase(); 129 | DebugTf("NoNoWord[%d] [%s]\r\n", i, noWords[i].c_str()); 130 | } 131 | } 132 | 133 | } // splitNewsNoWords() 134 | 135 | //--------------------------------------------------------------------- 136 | bool hasNoNoWord(const char *cIn) 137 | { 138 | for(int8_t i=0; i -1) && (noWords[i].length() > 1)) // yes! it's in there somewhere 144 | { 145 | DebugTf("found [%s]\r\n", noWords[i].c_str()); 146 | return true; 147 | } 148 | } 149 | //DebugTln("no NoNo words found!"); 150 | return false; 151 | 152 | } // hasNoNoWord() 153 | 154 | 155 | //--------------------------------------------------------------------- 156 | void nextNieuwsBericht() 157 | { 158 | bool breakOut = false; 159 | newsMsgID++; 160 | if (newsMsgID >= settingNewsMaxMsg) newsMsgID = 0; 161 | while (!readFileById("NWS", newsMsgID)) 162 | { 163 | DebugTln("File not found!"); 164 | newsMsgID++; 165 | if (newsMsgID > settingNewsMaxMsg) 166 | { 167 | newsMsgID = 0; 168 | breakOut = true; 169 | break; 170 | } 171 | } 172 | if (!breakOut) 173 | { 174 | snprintf(actMessage, NEWS_SIZE, "** %s **", fileMessage); 175 | //DebugTf("newsMsgID[%d] %s\r\n", newsMsgID, actMessage); 176 | utf8Ascii(actMessage); 177 | P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed)); 178 | } 179 | 180 | } // nextNieuwsBericht() 181 | 182 | 183 | //--------------------------------------------------------------------- 184 | void nextLocalBericht() 185 | { 186 | bool nothingThere = false; 187 | 188 | localMsgID++; 189 | if (localMsgID > settingLocalMaxMsg) 190 | { 191 | localMsgID = 0; 192 | nothingThere = true; 193 | } 194 | while (!readFileById("LCL", localMsgID)) 195 | { 196 | DebugTf("File [/newsFiles/LCL-%03d] not found!\r\n", localMsgID); 197 | localMsgID++; 198 | if (localMsgID > settingLocalMaxMsg) 199 | { 200 | DebugTln("Back to LCL-000, exit while-loop"); 201 | localMsgID = 0; 202 | continue; 203 | } 204 | } 205 | if (nothingThere && (localMsgID == 0)) 206 | { 207 | nothingThere = true; 208 | getRevisionData(); 209 | } 210 | else nothingThere = false; 211 | 212 | snprintf(actMessage, LOCAL_SIZE, "** %s **", fileMessage); 213 | //DebugTf("localMsgID[%d] %s\r\n", localMsgID, actMessage); 214 | utf8Ascii(actMessage); 215 | P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed)); 216 | 217 | if ((millis() - revisionTimer) > 900000) 218 | { 219 | revisionTimer = millis(); 220 | getRevisionData(); 221 | } 222 | 223 | } // nextLocalBericht() 224 | 225 | 226 | //===================================================================== 227 | void setup() 228 | { 229 | Serial.begin(115200); 230 | while(!Serial) { /* wait a bit */ } 231 | 232 | lastReset = ESP.getResetReason(); 233 | 234 | DebugTln("\r\n[MD_Parola WiFi Message Display]\r\n"); 235 | DebugTf("Booting....[%s]\r\n\r\n", String(FWversion).c_str()); 236 | 237 | P.begin(); 238 | P.displayClear(); 239 | P.displaySuspend(false); 240 | P.setIntensity(2); 241 | P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, 20); 242 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 243 | do 244 | { 245 | yield(); 246 | } while( !P.displayAnimate() ); 247 | 248 | actMessage[0] = '\0'; 249 | 250 | //================ LittleFS =========================================== 251 | if (LittleFS.begin()) 252 | { 253 | DebugTln(F("LittleFS Mount succesfull\r")); 254 | LittleFSmounted = true; 255 | 256 | readSettings(true); 257 | splitNewsNoWords(settingNewsNoWords); 258 | 259 | if (settingNewsInterval == 0) 260 | { 261 | removeNewsData(); 262 | } 263 | else 264 | { 265 | if (!LittleFS.exists("/newsFiles/LCL-000")) 266 | { 267 | char LCL000[100]; 268 | sprintf(LCL000, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str()); 269 | writeFileById("LCL", 0, LCL000); 270 | } 271 | if (!LittleFS.exists("/newsFiles/LCL-001")) 272 | { 273 | char LCL001[100]; 274 | sprintf(LCL001, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str()); 275 | writeFileById("LCL", 1, LCL001); 276 | } 277 | writeFileById("NWS", 1, "(c) 2021 Willem Aandewiel"); 278 | } 279 | } 280 | else 281 | { 282 | DebugTln(F("LittleFS Mount failed\r")); // Serious problem with LittleFS 283 | LittleFSmounted = false; 284 | } 285 | //==========================================================// 286 | // writeLastStatus(); // only for firsttime initialization // 287 | //==========================================================// 288 | readLastStatus(); // place it in actTimestamp 289 | 290 | // attempt to connect to Wifi network: 291 | int t = 0; 292 | while ((WiFi.status() != WL_CONNECTED) && (t < 25)) 293 | { 294 | delay(500); 295 | Serial.print("."); 296 | t++; 297 | } 298 | if ( WiFi.status() != WL_CONNECTED) { 299 | DebugTln("Attempting to connect to WiFi network\r"); 300 | sprintf(actMessage, "Connect to AP '%s' and configure WiFi on 192.168.4.1 ", _HOSTNAME); 301 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 302 | do { yield(); } while( !P.displayAnimate() ); 303 | //P.print(" 192.168.4.1"); 304 | } 305 | // Connect to and initialise WiFi network 306 | digitalWrite(LED_BUILTIN, HIGH); 307 | startWiFi(_HOSTNAME, 240); // timeout 4 minuten 308 | digitalWrite(LED_BUILTIN, LOW); 309 | 310 | startMDNS(settingHostname); 311 | 312 | DebugTln("Get time from NTP"); 313 | timeSync.setup(); 314 | timeSync.sync(300); 315 | time(&now); 316 | if (localtime(&now)->tm_year > 120) 317 | { 318 | timeSynced = true; 319 | Serial.println("Time synchronized with NTP Service"); 320 | } 321 | else 322 | { 323 | timeSynced = false; 324 | Serial.println("Could not synchronize time with NTP Service"); 325 | } 326 | 327 | time(&now); 328 | Serial.println("-------------------------------------------------------------------------------"); 329 | if (!getTheLocalTime(&timeinfo, 10000)) 330 | { 331 | Debugln("Time : Failed to obtain time!"); 332 | } 333 | else 334 | { 335 | Debugf( "Time : %04d-%02d-%02d %02d:%02d:%02d\r\n", localtime(&now)->tm_year+1900 336 | , localtime(&now)->tm_mon+1 337 | , localtime(&now)->tm_mday 338 | , localtime(&now)->tm_hour 339 | , localtime(&now)->tm_min 340 | , localtime(&now)->tm_sec); 341 | } 342 | 343 | nrReboots++; 344 | writeLastStatus(); 345 | //writeToLog("=========REBOOT=========================="); 346 | 347 | snprintf(cMsg, sizeof(cMsg), "Last reset reason: [%s]", ESP.getResetReason().c_str()); 348 | DebugTln(cMsg); 349 | //writeToLog(cMsg); 350 | 351 | Serial.print("\nGebruik 'telnet "); 352 | Serial.print (WiFi.localIP()); 353 | Serial.println("' voor verdere debugging\r\n"); 354 | 355 | //================ Start HTTP Server ================================ 356 | setupFSexplorer(); 357 | httpServer.serveStatic("/FSexplorer.png", LittleFS, "/FSexplorer.png"); 358 | httpServer.on("/", sendIndexPage); 359 | httpServer.on("/index", sendIndexPage); 360 | httpServer.on("/index.html",sendIndexPage); 361 | httpServer.serveStatic("/index.css", LittleFS, "/index.css"); 362 | httpServer.serveStatic("/index.js", LittleFS, "/index.js"); 363 | // all other api calls are catched in FSexplorer onNotFounD! 364 | httpServer.on("/api", HTTP_GET, processAPI); 365 | 366 | 367 | httpServer.begin(); 368 | DebugTln("\nServer started\r"); 369 | 370 | // Set up first message as the IP address 371 | sprintf(actMessage, "%03d.%03d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); 372 | DebugTf("\nAssigned IP[%s]\r\n", actMessage); 373 | P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, (MAX_SPEED - settingTextSpeed)); 374 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 375 | 376 | valueIntensity = calculateIntensity(); // read analog input pin 0 377 | 378 | P.setIntensity(valueIntensity); 379 | newsMsgID = 0; 380 | do { yield(); } while( !P.displayAnimate() ); 381 | 382 | P.setFont(ExtASCII); 383 | 384 | inFX = 0; 385 | outFX= 0; 386 | 387 | for (int i=0; i<=settingNewsMaxMsg; i++) 388 | { 389 | writeFileById("NWS", i, ""); 390 | //DebugTf("readFileById(NWS, %d)\r\n", i); 391 | //readFileById("NWS", i); 392 | } 393 | 394 | } // setup() 395 | 396 | 397 | //===================================================================== 398 | void loop() 399 | { 400 | httpServer.handleClient(); 401 | MDNS.update(); 402 | yield(); 403 | 404 | if ((millis() > weerTimer) && (strlen(settingWeerLiveAUTH) > 5)) 405 | { 406 | weerTimer = millis() + (settingWeerLiveInterval * (300 * 1000)); // Interval in Minutes! 407 | if (settingWeerLiveInterval > 0) getWeerLiveData(); 408 | } 409 | 410 | if ((millis() > newsapiTimer) && (strlen(settingNewsAUTH) > 5)) 411 | { 412 | newsapiTimer = millis() + (settingNewsInterval * (300 * 1000)); // Interval in Minutes! 413 | if (settingNewsInterval > 0) 414 | { 415 | if (!getNewsapiData()) //-- first try ... 416 | { 417 | delay(100); 418 | if (!getNewsapiData()) //-- second try ... 419 | { 420 | //-- try again in two(2) minutes ... 421 | newsapiTimer = millis() + (2 * (60 * 1000)); // Interval in Minutes! 422 | } 423 | } 424 | } 425 | } 426 | 427 | if (P.displayAnimate()) // done with animation, ready for next message 428 | { 429 | yield(); 430 | msgType++; 431 | DebugTf("msgType[%d]\r\n", msgType); 432 | time(&now); 433 | if (localtime(&now)->tm_year > 120) timeSynced = true; 434 | 435 | 436 | switch(msgType) 437 | { 438 | case 1: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display weekday"); return; } 439 | if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; } 440 | inFX = random(0, ARRAY_SIZE(effect)); 441 | outFX = random(0, ARRAY_SIZE(effect)); 442 | snprintf(actMessage, LOCAL_SIZE, weekDayName[localtime(&now)->tm_wday+1]); 443 | snprintf(onTickerMessage, 120, "%s", actMessage); 444 | DebugT(" ["); Debug(onTickerMessage); Debugln("]"); 445 | P.displayClear(); 446 | P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 1000, effect[inFX], effect[outFX]); 447 | DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage); 448 | break; 449 | case 2: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display the time"); return; } 450 | if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; } 451 | timeTimer = millis() + 60000; 452 | inFX = random(0, ARRAY_SIZE(effect)); 453 | outFX = random(0, ARRAY_SIZE(effect)); 454 | sprintf(actMessage, "%s", updateTime()); 455 | snprintf(onTickerMessage, 120, "%s", actMessage); 456 | DebugT(" ["); Debug(onTickerMessage); Debugln("]"); 457 | P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 2000, effect[inFX], effect[outFX]); 458 | DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage); 459 | break; 460 | case 3: nextLocalBericht(); 461 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 462 | break; 463 | case 6: nextLocalBericht(); 464 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 465 | break; 466 | case 4: 467 | case 5: 468 | case 7: 469 | case 8: if (settingNewsInterval > 0) 470 | nextNieuwsBericht(); 471 | else nextLocalBericht(); 472 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 473 | break; 474 | case 9: if (settingWeerLiveInterval > 0) 475 | { 476 | snprintf(actMessage, LOCAL_SIZE, "** %s **", tempMessage); 477 | Debugf("\t[%s]\r\n", actMessage); 478 | utf8Ascii(actMessage); 479 | } 480 | else nextLocalBericht(); 481 | P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); 482 | break; 483 | case 10: if (settingNewsInterval > 0) 484 | nextNieuwsBericht(); 485 | else nextLocalBericht(); 486 | break; 487 | default: msgType = 0; 488 | return; 489 | 490 | } // switch() 491 | 492 | //DebugTln(actMessage); 493 | valueIntensity = calculateIntensity(); // read analog input pin 0 494 | DebugTf("Intensity set to [%d]\r\n", valueIntensity); 495 | P.setIntensity(valueIntensity); 496 | // Tell Parola we have a new animation 497 | P.displayReset(); 498 | DebugTln("End of displayAnimate().."); 499 | 500 | } // dislayAnimate() 501 | 502 | 503 | } // loop() 504 | 505 | 506 | /*************************************************************************** 507 | * 508 | * Permission is hereby granted, free of charge, to any person obtaining a 509 | * copy of this software and associated documentation files (the 510 | * "Software"), to deal in the Software without restriction, including 511 | * without limitation the rights to use, copy, modify, merge, publish, 512 | * distribute, sublicense, and/or sell copies of the Software, and to permit 513 | * persons to whom the Software is furnished to do so, subject to the 514 | * following conditions: 515 | * 516 | * The above copyright notice and this permission notice shall be included 517 | * in all copies or substantial portions of the Software. 518 | * 519 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 520 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 521 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 522 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 523 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 524 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 525 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 526 | * 527 | **************************************************************************** 528 | */ 529 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/FSexplorer.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : FSexplorer 4 | ** Version : 2.0 10-05-202 5 | ** 6 | ** Mostly stolen from https://www.arduinoforum.de/User-Fips 7 | ** For more information visit: https://fipsok.de 8 | ** See also https://www.arduinoforum.de/arduino-Thread-LittleFS-DOWNLOAD-UPLOAD-DELETE-Esp8266-NodeMCU 9 | ** 10 | *************************************************************************** 11 | Copyright (c) 2018 Jens Fleischer. All rights reserved. 12 | 13 | This file is free software; you can redistribute it and/or 14 | modify it under the terms of the GNU Lesser General Public 15 | License as published by the Free Software Foundation; either 16 | version 2.1 of the License, or (at your option) any later version. 17 | This file is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | ******************************************************************* 22 | ** Usage: 23 | ** 24 | ** setup() 25 | ** { 26 | ** setupFSexplorer(); 27 | ** httpServer.serveStatic("/FSexplorer.png", LittleFS, "/FSexplorer.png"); 28 | ** httpServer.on("/", sendIndexPage); 29 | ** httpServer.on("/index", sendIndexPage); 30 | ** httpServer.on("/index.html",sendIndexPage); 31 | ** httpServer.begin(); 32 | ** } 33 | ** 34 | ** loop() 35 | ** { 36 | ** httpServer.handleClient(); 37 | ** . 38 | ** . 39 | ** } 40 | */ 41 | 42 | #define MAX_FILES_IN_LIST 25 43 | 44 | const char Helper[] = R"( 45 |
You first need to upload these two files: 46 |
    47 |
  • FSexplorer.html
  • 48 |
  • FSexplorer.css
  • 49 |
50 |
51 | 52 | 53 |
54 |
or you can use the Flash Utility to flash firmware or LittleFS! 55 |
56 | 57 |
58 | )"; 59 | const char Header[] = "HTTP/1.1 303 OK\r\nLocation:FSexplorer.html\r\nCache-Control: no-cache\r\n"; 60 | 61 | //===================================================================================== 62 | void setupFSexplorer() // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden 63 | { 64 | LittleFS.begin(); 65 | 66 | if (LittleFS.exists("/FSexplorer.html")) 67 | { 68 | httpServer.serveStatic("/FSexplorer.html", LittleFS, "/FSexplorer.html"); 69 | httpServer.serveStatic("/FSexplorer", LittleFS, "/FSexplorer.html"); 70 | } 71 | else 72 | { 73 | httpServer.send(200, "text/html", Helper); //Upload the FSexplorer.html 74 | } 75 | httpServer.on("/api/listfiles", APIlistFiles); 76 | httpServer.on("/LittleFSformat", formatLittleFS); 77 | httpServer.on("/upload", HTTP_POST, []() {}, handleFileUpload); 78 | httpServer.on("/ReBoot", reBootESP); 79 | httpServer.on("/update", updateFirmware); 80 | httpServer.onNotFound([]() 81 | { 82 | if (Verbose) DebugTf("in 'onNotFound()'!! [%s] => \r\n", String(httpServer.uri()).c_str()); 83 | if (httpServer.uri().indexOf("/api/") == 0) 84 | { 85 | if (Verbose) DebugTf("next: processAPI(%s)\r\n", String(httpServer.uri()).c_str()); 86 | processAPI(); 87 | } 88 | else if (httpServer.uri() == "/") 89 | { 90 | DebugTln("index requested.."); 91 | sendIndexPage(); 92 | } 93 | else 94 | { 95 | DebugTf("next: handleFile(%s)\r\n" 96 | , String(httpServer.urlDecode(httpServer.uri())).c_str()); 97 | if (!handleFile(httpServer.urlDecode(httpServer.uri()))) 98 | { 99 | httpServer.send(404, "text/plain", "FileNotFound\r\n"); 100 | } 101 | } 102 | }); 103 | 104 | } // setupFSexplorer() 105 | 106 | 107 | //===================================================================================== 108 | void APIlistFiles() // Senden aller Daten an den Client 109 | { 110 | FSInfo LittleFSinfo; 111 | 112 | typedef struct _fileMeta { 113 | char Name[30]; 114 | int32_t Size; 115 | } fileMeta; 116 | 117 | _fileMeta dirMap[MAX_FILES_IN_LIST+1]; 118 | int fileNr = 0; 119 | 120 | Dir dir = LittleFS.openDir("/"); // List files on LittleFS 121 | while (dir.next() && (fileNr < MAX_FILES_IN_LIST)) 122 | { 123 | dirMap[fileNr].Name[0] = '\0'; 124 | //strncat(dirMap[fileNr].Name, dir.fileName().substring(1).c_str(), 29); // remove leading '/' 125 | strncat(dirMap[fileNr].Name, dir.fileName().c_str(), 29); 126 | dirMap[fileNr].Size = dir.fileSize(); 127 | fileNr++; 128 | } 129 | DebugTf("fileNr[%d], Max[%d]\r\n", fileNr, MAX_FILES_IN_LIST); 130 | 131 | // -- bubble sort dirMap op .Name-- 132 | for (int8_t y = 0; y < fileNr; y++) { 133 | yield(); 134 | for (int8_t x = y + 1; x < fileNr; x++) { 135 | //DebugTf("y[%d], x[%d] => seq[y][%s] / seq[x][%s] ", y, x, dirMap[y].Name, dirMap[x].Name); 136 | if (compare(String(dirMap[x].Name), String(dirMap[y].Name))) 137 | { 138 | //Debug(" !switch!"); 139 | fileMeta temp = dirMap[y]; 140 | dirMap[y] = dirMap[x]; 141 | dirMap[x] = temp; 142 | } /* end if */ 143 | //Debugln(); 144 | } /* end for */ 145 | } /* end for */ 146 | 147 | if (fileNr >= MAX_FILES_IN_LIST) 148 | { 149 | fileNr = MAX_FILES_IN_LIST; 150 | dirMap[fileNr].Name[0] = '\0'; 151 | //--- if you change this message you also have to 152 | //--- change FSexplorer.html 153 | strncat(dirMap[fileNr].Name, "More files not listed ..", 29); 154 | dirMap[fileNr].Size = 0; 155 | fileNr++; 156 | } 157 | 158 | String temp = "["; 159 | for (int f=0; f < fileNr; f++) 160 | { 161 | DebugTf("[%3d] >> [%s]\r\n", f, dirMap[f].Name); 162 | if (temp != "[") temp += ","; 163 | temp += R"({"name":")" + String(dirMap[f].Name) + R"(","size":")" + formatBytes(dirMap[f].Size) + R"("})"; 164 | } 165 | 166 | LittleFS.info(LittleFSinfo); 167 | temp += R"(,{"usedBytes":")" + formatBytes(LittleFSinfo.usedBytes * 1.05) + R"(",)" + // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag 168 | R"("totalBytes":")" + formatBytes(LittleFSinfo.totalBytes) + R"(","freeBytes":")" + // Zeigt die Größe des Speichers 169 | (LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05)) + R"("}])"; // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag 170 | 171 | httpServer.send(200, "application/json", temp); 172 | 173 | } // APIlistFiles() 174 | 175 | 176 | //===================================================================================== 177 | bool handleFile(String&& path) 178 | { 179 | if (httpServer.hasArg("delete")) 180 | { 181 | DebugTf("Delete -> [%s]\n\r", httpServer.arg("delete").c_str()); 182 | LittleFS.remove(httpServer.arg("delete")); // Datei löschen 183 | httpServer.sendContent(Header); 184 | return true; 185 | } 186 | if (!LittleFS.exists("/FSexplorer.html")) httpServer.send(200, "text/html", Helper); //Upload the FSexplorer.html 187 | if (path.endsWith("/")) path += "index.html"; 188 | return LittleFS.exists(path) ? ({File f = LittleFS.open(path, "r"); httpServer.streamFile(f, contentType(path)); f.close(); true;}) : false; 189 | 190 | } // handleFile() 191 | 192 | 193 | //===================================================================================== 194 | void handleFileUpload() 195 | { 196 | static File fsUploadFile; 197 | HTTPUpload& upload = httpServer.upload(); 198 | if (upload.status == UPLOAD_FILE_START) 199 | { 200 | if (upload.filename.length() > 30) 201 | { 202 | upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen 203 | } 204 | Debugln("FileUpload Name: " + upload.filename); 205 | fsUploadFile = LittleFS.open("/" + httpServer.urlDecode(upload.filename), "w"); 206 | } 207 | else if (upload.status == UPLOAD_FILE_WRITE) 208 | { 209 | Debugln("FileUpload Data: " + (String)upload.currentSize); 210 | if (fsUploadFile) 211 | fsUploadFile.write(upload.buf, upload.currentSize); 212 | } 213 | else if (upload.status == UPLOAD_FILE_END) 214 | { 215 | if (fsUploadFile) 216 | fsUploadFile.close(); 217 | Debugln("FileUpload Size: " + (String)upload.totalSize); 218 | httpServer.sendContent(Header); 219 | } 220 | 221 | } // handleFileUpload() 222 | 223 | 224 | //===================================================================================== 225 | void formatLittleFS() 226 | { //Formatiert den Speicher 227 | if (!LittleFS.exists("/!format")) return; 228 | DebugTln(F("Format LittleFS")); 229 | LittleFS.format(); 230 | httpServer.sendContent(Header); 231 | 232 | } // formatLittleFS() 233 | 234 | //===================================================================================== 235 | const String formatBytes(size_t const& bytes) 236 | { 237 | return (bytes < 1024) ? String(bytes) + " Byte" : (bytes < (1024 * 1024)) ? String(bytes / 1024.0) + " KB" : String(bytes / 1024.0 / 1024.0) + " MB"; 238 | 239 | } //formatBytes() 240 | 241 | //===================================================================================== 242 | const String &contentType(String& filename) 243 | { 244 | if (filename.endsWith(".htm") || filename.endsWith(".html")) filename = "text/html"; 245 | else if (filename.endsWith(".css")) filename = "text/css"; 246 | else if (filename.endsWith(".js")) filename = "application/javascript"; 247 | else if (filename.endsWith(".json")) filename = "application/json"; 248 | else if (filename.endsWith(".png")) filename = "image/png"; 249 | else if (filename.endsWith(".gif")) filename = "image/gif"; 250 | else if (filename.endsWith(".jpg")) filename = "image/jpeg"; 251 | else if (filename.endsWith(".ico")) filename = "image/x-icon"; 252 | else if (filename.endsWith(".xml")) filename = "text/xml"; 253 | else if (filename.endsWith(".pdf")) filename = "application/x-pdf"; 254 | else if (filename.endsWith(".zip")) filename = "application/x-zip"; 255 | else if (filename.endsWith(".gz")) filename = "application/x-gzip"; 256 | else filename = "text/plain"; 257 | return filename; 258 | 259 | } // &contentType() 260 | 261 | //===================================================================================== 262 | bool freeSpace(uint16_t const& printsize) 263 | { 264 | FSInfo LittleFSinfo; 265 | LittleFS.info(LittleFSinfo); 266 | Debugln(formatBytes(LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05)) + " im Spiffs frei"); 267 | return (LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05) > printsize) ? true : false; 268 | 269 | } // freeSpace() 270 | 271 | 272 | //===================================================================================== 273 | void updateFirmware() 274 | { 275 | DebugTln(F("Redirect to updateIndex ..")); 276 | doRedirect("wait ... ", 1, "/updateIndex", false); 277 | 278 | } // updateFirmware() 279 | 280 | //===================================================================================== 281 | void reBootESP() 282 | { 283 | DebugTln(F("Redirect and ReBoot ..")); 284 | doRedirect("Reboot ESP - lichtKrant ..", 60, "/", true); 285 | 286 | } // reBootESP() 287 | 288 | //===================================================================================== 289 | void doRedirect(String msg, int wait, const char* URL, bool reboot) 290 | { 291 | String redirectHTML = 292 | "" 293 | "" 294 | "" 295 | "" 298 | "Redirect to Main Program" 299 | "" 300 | "

FSexplorer

" 301 | "

"+msg+"

" 302 | "
" 303 | "
Redirect over  
" 304 | "
"+String(wait)+"
" 305 | "
  seconden ...
" 306 | "
 
" 307 | "
" 308 | "" 309 | "


If you are not redirected automatically, click this Main Program." 310 | " " 320 | "\r\n"; 321 | 322 | DebugTln(msg); 323 | httpServer.send(200, "text/html", redirectHTML); 324 | if (reboot) 325 | { 326 | delay(5000); 327 | ESP.restart(); 328 | delay(5000); 329 | } 330 | 331 | } // doRedirect() 332 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/TimeSyncClass.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** With thanks to Michael 3 | */ 4 | 5 | #include "TimeSyncClass.h" 6 | 7 | TimeSync::TimeSync() {} 8 | 9 | /** 10 | * Sets up the time synchronization with the specified timezone. 11 | * 12 | * @param timeZone The timezone to set. 13 | * 14 | * @return void 15 | * 16 | * @throws None 17 | */ 18 | void TimeSync::setup(const char *timeZone) 19 | { 20 | configTime(0, 0, "pool.ntp.org", "time.nist.gov", "time.google.com"); 21 | setenv("TZ", timeZone, 1); 22 | tzset(); 23 | 24 | Serial.printf("Timezone set to: %s\r\n", timeZone); 25 | } 26 | 27 | /** 28 | * Checks if the system time is synchronized. 29 | * 30 | * @return True if the system time is synchronized, false otherwise. 31 | * 32 | * @throws None 33 | */ 34 | bool TimeSync::isSynced() 35 | { 36 | time_t now; 37 | time(&now); 38 | 39 | return (bool)(localtime(&now)->tm_year > 120); 40 | } 41 | 42 | /** 43 | * Synchronizes the system time with the NTP service. 44 | * 45 | * @param maxTry The maximum number of attempts to synchronize the time. 46 | * 47 | * @return True if the system time is synchronized with the NTP service, 48 | * False otherwise. 49 | * 50 | * @throws None 51 | */ 52 | bool TimeSync::sync(uint16_t maxTry) 53 | { 54 | time_t now; 55 | time(&now); 56 | uint8_t tries = 0; 57 | 58 | Serial.print("Get time from NTP service ."); 59 | do 60 | { 61 | time(&now); 62 | delay(20); 63 | if ((tries%10)==0) 64 | { 65 | Serial.print(". "); 66 | } 67 | tries++; 68 | } while ((localtime(&now)->tm_year <= 120) && (tries < maxTry)); 69 | 70 | if (localtime(&now)->tm_year > 120) 71 | Serial.println(" Synchronized with NTP Service!"); 72 | else Serial.println(" NOT (YET) SYNCED!"); 73 | 74 | return localtime(&now)->tm_year > 120; 75 | 76 | } 77 | 78 | /** 79 | * Logs the current time. 80 | * 81 | * @return void 82 | * 83 | * @throws None 84 | */ 85 | void TimeSync::logTime() 86 | { 87 | time_t now; 88 | time(&now); 89 | struct tm timeinfo; 90 | 91 | if (localtime(&now)->tm_year <= 120) 92 | { 93 | Serial.println("Failed to obtain time"); 94 | return; 95 | } 96 | Serial.printf("%s", asctime(&timeinfo)); 97 | } 98 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/TimeSyncClass.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** See MIT license at the end of this file 3 | */ 4 | 5 | #include 6 | 7 | class TimeSync 8 | { 9 | public: 10 | TimeSync(); 11 | void setup(const char *timeZone = "CET-1CEST,M3.5.0,M10.5.0/3"); 12 | bool sync(uint16_t maxTry); 13 | bool isSynced(); 14 | void logTime(); 15 | 16 | }; 17 | 18 | /********************************************************************************** 19 | * MIT License 20 | * 21 | * Copyright (c) 2024 Willem Aandewiel (mrWheel) 22 | * 23 | * Permission is hereby granted, free of charge, to any person obtaining a copy 24 | * of this software and associated documentation files (the "Software"), to deal 25 | * in the Software without restriction, including without limitation the rights 26 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | * copies of the Software, and to permit persons to whom the Software is 28 | * furnished to do so, subject to the following conditions: 29 | * 30 | * The above copyright notice and this permission notice shall be included in all 31 | * copies or substantial portions of the Software. 32 | * 33 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | * SOFTWARE. 40 | ********************************************************************************** 41 | */ 42 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/FSexplorer.css: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : FSexplorer.html, part of ESP_ticker 4 | ** Version : v1.3.1 5 | ** For more information visit: https://fipsok.de 6 | *************************************************************************** 7 | */ 8 | body { 9 | font-family: sans-serif; 10 | //background-color: #a9a9a9; 11 | background-color: lightblue; 12 | display: flex; 13 | flex-flow: column; 14 | align-items: left; 15 | } 16 | h1,h2 { 17 | color: #e1e1e1; 18 | text-shadow: 2px 2px 2px black; 19 | } 20 | a:link { 21 | text-decoration: none; 22 | } 23 | input { 24 | height: 35px; 25 | font-size: 13px; 26 | } 27 | h1+main { 28 | display: flex; 29 | } 30 | section { 31 | display: flex; 32 | flex-direction: column; 33 | padding: 0.2em; 34 | } 35 | hr { 36 | border: 0; 37 | clear:both; 38 | display:block; 39 | width: 99%; 40 | background-color:darkblue; 41 | height: 5px; 42 | } 43 | 44 | #left { 45 | align-items: flex-end; 46 | text-shadow: 1px 1px 2px #757474; 47 | } 48 | .note { 49 | background-color: salmon; 50 | padding: 0.5em; 51 | margin-top: 1em; 52 | text-align: center; 53 | max-width: 320px; 54 | border-radius: 0.5em; 55 | } 56 | .button { 57 | width: 150px; 58 | height: 40px; 59 | font-size: 14px; 60 | margin-top: 1em; 61 | cursor: pointer; 62 | background-color: lightgray; 63 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7); 64 | } 65 | [type=submit] { 66 | height: 40px; 67 | min-width: 70px; 68 | } 69 | [value^=Format] { 70 | background-color: #ddd; 71 | } 72 | [title] { 73 | background-color: silver; 74 | font-size: 16px; 75 | width: 125px; 76 | } 77 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/FSexplorer.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | FSexplorer ESP 19 | 96 | 97 | 98 |

FSexplorer ESP

99 |
100 |
101 |
102 |
103 | 104 |
105 | 106 | 107 |
108 |
109 | 110 |
111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 |
119 |
120 | 121 |
122 | 123 |
124 | 125 |
126 |
127 | 128 |
129 | 130 |
131 |
132 |
133 | 134 | 135 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/FSexplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrWheel/arduinoIDE2platformIO-convertor/64f81c8c4c4a3f3d5080859960acf18450902978/testProject/ESP_ticker/data/FSexplorer.png -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/Uno_reset-en_snippet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrWheel/arduinoIDE2platformIO-convertor/64f81c8c4c4a3f3d5080859960acf18450902978/testProject/ESP_ticker/data/Uno_reset-en_snippet.jpg -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrWheel/arduinoIDE2platformIO-convertor/64f81c8c4c4a3f3d5080859960acf18450902978/testProject/ESP_ticker/data/favicon.ico -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : index.css, part of ESP_ticker 4 | ** Version : v1.3.1 5 | ** 6 | ** Copyright (c) 2020 Willem Aandewiel 7 | ** 8 | ** TERMS OF USE: MIT License. See bottom of file. 9 | *************************************************************************** 10 | */ 11 | * { 12 | font-family: Arial, Helvetica, sans-serif; 13 | } 14 | 15 | html { 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | font-family: 'Dosis', sans-serif; 19 | line-height: 1.6; 20 | color: black; 21 | background: #e6ffff; 22 | } 23 | 24 | table { 25 | color: black; 26 | border-collapse: collapse; 27 | width: 90%; 28 | background: lightblue; 29 | } 30 | 31 | th { 32 | padding-left: 15px; 33 | padding-right: 15px; 34 | white-space: nowrap; 35 | border-right: 1px solid #ddd; 36 | border-left: 1px solid #ddd; 37 | vertical-align: bottom; 38 | } 39 | 40 | td { 41 | padding-left: 15px; 42 | padding-right: 15px; 43 | white-space: nowrap; 44 | border-bottom: 1px solid #ddd; 45 | border-right: 1px solid #ddd; 46 | border-left: 1px solid #ddd; 47 | vertical-align: bottom; 48 | } 49 | 50 | tfoot { 51 | font-size: 12px; 52 | color: darkblue; 53 | } 54 | 55 | input { 56 | padding-top: 3px; 57 | padding-bottom: 2px; 58 | font-size: 12pt; 59 | } 60 | 61 | .settingDiv { 62 | background: lightblue; 63 | white-space: nowrap; 64 | } 65 | 66 | .outer-div { 67 | /*width: 90%; */ 68 | text-align: center; 69 | background-color: #e6ffff 70 | } 71 | .inner-div { 72 | display: inline-block; 73 | /* 74 | margin: 0 auto; 75 | padding: 3px; 76 | background-color: lightblue 77 | */ 78 | } 79 | 80 | .container-card { 81 | padding: 50; 82 | margin: 50; 83 | list-style: none; 84 | flex-direction: row; /*row | row-reverse | column | column-reverse*/ 85 | 86 | display: -webkit-box; 87 | display: -moz-box; 88 | display: -ms-flexbox; 89 | display: -webkit-flex; 90 | display: flex; 91 | 92 | -webkit-flex-flow: row wrap; 93 | justify-content: space-around; /* flex-start | flex-end | center | space-between | space-around | space-evenly */ 94 | 95 | } 96 | 97 | .container-box { 98 | padding: 0; 99 | margin: 0; 100 | list-style: none; 101 | flex-direction: column; /*row | row-reverse | column | column-reverse*/ 102 | 103 | display: -webkit-box; 104 | display: -moz-box; 105 | display: -ms-flexbox; 106 | display: -webkit-flex; 107 | display: flex; 108 | 109 | -webkit-flex-flow: column wrap; 110 | justify-content: flex-start; 111 | margin-top: 10px; 112 | 113 | } 114 | 115 | /*--------------------- N A V - B A R -----------------*/ 116 | .nav-container { 117 | list-style: none; 118 | margin: 0; 119 | 120 | background: #00bffe; 121 | 122 | display: -webkit-box; 123 | display: -moz-box; 124 | display: -ms-flexbox; 125 | display: -webkit-flex; 126 | display: flex-start; 127 | -webkit-flex-flow: row wrap; 128 | min-height: 24px; 129 | 130 | } 131 | 132 | .nav-img { 133 | top: 1px; 134 | display: inline-block; 135 | width: 40px; 136 | height: 40px; 137 | } 138 | 139 | .nav-item { 140 | display: inline-block; 141 | font-size: 16px; 142 | padding: 10px 0; 143 | height: 20px; 144 | width: 110px; 145 | border: none; 146 | color: white; 147 | 148 | } 149 | 150 | .nav-left { 151 | flex: 2 1 0; 152 | text-align: center; 153 | } 154 | 155 | .nav-right { 156 | flex: 1 1 0; 157 | text-align: right; 158 | width: 200px; 159 | } 160 | 161 | .nav-container a:hover { 162 | color: black; 163 | background: skyblue; /* only for FSexplorer - Rest does not work */ 164 | } 165 | 166 | .nav-clock { 167 | //top: 1px; 168 | color: black; 169 | float: right; 170 | font-size: small; 171 | font-weight:bold; 172 | text-align: right; 173 | background: white; 174 | width: 200px; 175 | padding-right: 10px; 176 | background: #e6ffff; 177 | } 178 | 179 | /*-------------------------*/ 180 | /*** 181 | @media all and (max-width: 600px) { 182 | .nav-container { 183 | justify-content: space-around; 184 | } 185 | } 186 | 187 | @media all and (max-width: 400px) { 188 | .nav-container { 189 | -webkit-flex-flow: column wrap; 190 | flex-flow: column wrap; 191 | padding: 0; 192 | } 193 | } 194 | ***/ 195 | 196 | .tabButton { 197 | background-color: white; 198 | } 199 | 200 | .tabButton a:hover { 201 | background-color: gray; 202 | } 203 | 204 | 205 | .header h1 span { 206 | position: relative; 207 | top: 1px; 208 | left: 10px; 209 | } 210 | 211 | .bottom { 212 | position: fixed; 213 | font-size: small; 214 | color: gray; 215 | bottom:0; 216 | } 217 | .right-0 {right: 0; padding-right: 10px; } 218 | .left-0 {left: 0; padding-left: 10px; } 219 | 220 | //--- next 4 are for the API doc page -------- 221 | .div1 { 222 | float: left; 223 | margin-left: 20px; 224 | margin-top: 10px; 225 | } 226 | .div1 a:link, a:visited { 227 | background-color: deepSkyBlue; 228 | color: white; 229 | padding: 5px 15px; 230 | width: 210px; 231 | text-align: right; 232 | text-decoration: none; 233 | display: inline-block; 234 | } 235 | .div1 a:hover, a:active { 236 | background-color: deepSkyBlue; 237 | color: black; 238 | } 239 | 240 | .div2 { 241 | margin-left: 280px; 242 | margin-top: -52px; 243 | } 244 | 245 | 246 | /* define tag selectors () -------------------------------------------------- */ 247 | 248 | button { width: 100px; } 249 | 250 | /* 251 | *************************************************************************** 252 | * 253 | * Permission is hereby granted, free of charge, to any person obtaining a 254 | * copy of this software and associated documentation files (the 255 | * "Software"), to deal in the Software without restriction, including 256 | * without limitation the rights to use, copy, modify, merge, publish, 257 | * distribute, sublicense, and/or sell copies of the Software, and to permit 258 | * persons to whom the Software is furnished to do so, subject to the 259 | * following conditions: 260 | * 261 | * The above copyright notice and this permission notice shall be included 262 | * in all copies or substantial portions of the Software. 263 | * 264 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 265 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 266 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 267 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 268 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 269 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 270 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 271 | * 272 | *************************************************************************** 273 | */ 274 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : index.js, part of ESP_ticker 4 | ** Version : v1.6.0 (02-01-2021) 5 | ** 6 | ** Copyright (c) 2021 Willem Aandewiel 7 | ** 8 | ** TERMS OF USE: MIT License. See bottom of file. 9 | *************************************************************************** 10 | */ 11 | const APIGW='http://'+window.location.host+'/api/'; 12 | 13 | "use strict"; 14 | 15 | let needReload = true; 16 | 17 | window.onload=bootsTrapMain; 18 | window.onfocus = function() { 19 | if (needReload) { 20 | window.location.reload(true); 21 | } 22 | }; 23 | 24 | //============================================================================ 25 | function bootsTrapMain() { 26 | console.log("bootsTrapMain()"); 27 | document.getElementById('saveMsg').addEventListener('click',function() 28 | {saveMessages();}); 29 | document.getElementById('M_FSexplorer').addEventListener('click',function() 30 | { console.log("newTab: goFSexplorer"); 31 | location.href = "/FSexplorer"; 32 | }); 33 | document.getElementById('S_FSexplorer').addEventListener('click',function() 34 | { console.log("newTab: goFSexplorer"); 35 | location.href = "/FSexplorer"; 36 | }); 37 | document.getElementById('back').addEventListener('click',function() 38 | { console.log("newTab: goBack"); 39 | location.href = "/"; 40 | }); 41 | document.getElementById('Settings').addEventListener('click',function() 42 | {settingsPage();}); 43 | document.getElementById('saveSettings').addEventListener('click',function() 44 | {saveSettings();}); 45 | needReload = false; 46 | refreshDevTime(); 47 | refreshDevInfo(); 48 | refreshMessages(); 49 | 50 | document.getElementById("displayMainPage").style.display = "block"; 51 | document.getElementById("displaySettingsPage").style.display = "none"; 52 | 53 | } // bootsTrapMain() 54 | 55 | function settingsPage() 56 | { 57 | document.getElementById("displayMainPage").style.display = "none"; 58 | 59 | var settingsPage = document.getElementById("settingsPage"); 60 | refreshSettings(); 61 | document.getElementById("displaySettingsPage").style.display = "block"; 62 | 63 | } // settingsPage() 64 | 65 | 66 | //============================================================================ 67 | function refreshDevTime() 68 | { 69 | //console.log("Refresh api/v0/devtime .."); 70 | fetch(APIGW+"v0/devtime") 71 | .then(response => response.json()) 72 | .then(json => { 73 | console.log("parsed .., data is ["+ JSON.stringify(json)+"]"); 74 | for( let i in json.devtime ){ 75 | if (json.devtime[i].name == "dateTime") 76 | { 77 | //console.log("Got new time ["+json.devtime[i].value+"]"); 78 | document.getElementById('theTime').innerHTML = json.devtime[i].value; 79 | } 80 | } 81 | }) 82 | .catch(function(error) { 83 | var p = document.createElement('p'); 84 | p.appendChild( 85 | document.createTextNode('Error: ' + error.message) 86 | ); 87 | }); 88 | 89 | } // refreshDevTime() 90 | 91 | 92 | //============================================================================ 93 | function refreshDevInfo() 94 | { 95 | document.getElementById('devName').innerHTML = ""; 96 | fetch(APIGW+"v0/devinfo") 97 | .then(response => response.json()) 98 | .then(json => { 99 | //console.log("parsed .., data is ["+ JSON.stringify(json)+"]"); 100 | data = json.devinfo; 101 | for( let i in data ) 102 | { 103 | if (data[i].name == "fwversion") 104 | { 105 | document.getElementById('devVersion').innerHTML = json.devinfo[i].value; 106 | 107 | } else if (data[i].name == 'hostname') 108 | { 109 | document.getElementById('devName').innerHTML += data[i].value+" "; 110 | 111 | } else if (data[i].name == 'ipaddress') 112 | { 113 | document.getElementById('devName').innerHTML += " ("+data[i].value+") "; 114 | } 115 | } 116 | }) 117 | .catch(function(error) { 118 | var p = document.createElement('p'); 119 | p.appendChild( 120 | document.createTextNode('Error: ' + error.message) 121 | ); 122 | }); 123 | } // refreshDevInfo() 124 | 125 | //============================================================================ 126 | function refreshMessages() 127 | { 128 | console.log("refreshMessages() .."); 129 | data = {}; 130 | fetch(APIGW+"v0/messages") 131 | .then(response => response.json()) 132 | .then(json => { 133 | console.log("then(json => ..)"); 134 | msg = json.messages; 135 | for( let i in msg ) 136 | { 137 | msg[i].value = msg[i].value.replaceAll("@1@", ":"); 138 | msg[i].value = msg[i].value.replaceAll("@2@", "{"); 139 | msg[i].value = msg[i].value.replaceAll("@3@", "}"); 140 | msg[i].value = msg[i].value.replaceAll("@4@", ","); 141 | msg[i].value = msg[i].value.replaceAll("@5@", "\\"); 142 | msg[i].value = msg[i].value.replaceAll("@6@", "\%"); 143 | 144 | console.log("["+msg[i].name+"]=>["+msg[i].value+"]"); 145 | var messages = document.getElementById('mainPage'); 146 | if( ( document.getElementById("msgR_"+msg[i].name)) == null ) 147 | { 148 | var rowDiv = document.createElement("div"); 149 | rowDiv.setAttribute("class", "msgDiv"); 150 | rowDiv.setAttribute("id", "msgR_"+msg[i].name); 151 | rowDiv.setAttribute("style", "text-align: right;"); 152 | rowDiv.style.marginLeft = "10px"; 153 | rowDiv.style.marginRight = "10px"; 154 | rowDiv.style.width = "850px"; 155 | rowDiv.style.border = "thick solid lightblue"; 156 | rowDiv.style.background = "lightblue"; 157 | //--- field Name --- 158 | var fldDiv = document.createElement("div"); 159 | fldDiv.setAttribute("style", "margin-right: 10px;"); 160 | fldDiv.style.width = "30px"; 161 | fldDiv.style.float = 'left'; 162 | fldDiv.textContent = msg[i].name; 163 | rowDiv.appendChild(fldDiv); 164 | //--- input --- 165 | var inputDiv = document.createElement("div"); 166 | inputDiv.setAttribute("style", "text-align: left;"); 167 | 168 | var sInput = document.createElement("INPUT"); 169 | sInput.setAttribute("id", "M_"+msg[i].name); 170 | sInput.setAttribute("style", "background: white"); 171 | 172 | if (msg[i].type == "s") 173 | { 174 | sInput.setAttribute("type", "text"); 175 | sInput.setAttribute("maxlength", msg[i].maxlen); 176 | sInput.setAttribute("size", 100); 177 | 178 | sInput.setAttribute("value", msg[i].value); 179 | //console.log("addEventListener(M_"+msg[i].name+")"); 180 | sInput.addEventListener('change', 181 | function() { setBackGround("M_"+msg[i].name, "lightgray"); }, 182 | false 183 | ); 184 | } 185 | inputDiv.appendChild(sInput); 186 | 187 | rowDiv.appendChild(inputDiv); 188 | messages.appendChild(rowDiv); 189 | } 190 | else 191 | { 192 | document.getElementById("M_"+msg[i].name).style.background = "white"; 193 | document.getElementById("M_"+msg[i].name).value = data[i].value; 194 | } 195 | } 196 | document.getElementById("waiting").innerHTML = ""; 197 | }) 198 | .catch(function(error) { 199 | var p = document.createElement('p'); 200 | p.appendChild( 201 | document.createTextNode('Error: ' + error.message) 202 | ); 203 | }); 204 | 205 | } // refreshMessages() 206 | 207 | 208 | //============================================================================ 209 | function refreshSettings() 210 | { 211 | console.log("refreshSettings() .."); 212 | data = {}; 213 | fetch(APIGW+"v0/settings") 214 | .then(response => response.json()) 215 | .then(json => { 216 | console.log("then(json => ..)"); 217 | data = json.settings; 218 | for( let i in data ) 219 | { 220 | console.log("["+data[i].name+"]=>["+data[i].value+"]"); 221 | var settings = document.getElementById('settingsPage'); 222 | if( ( document.getElementById("D_"+data[i].name)) == null ) 223 | { 224 | var rowDiv = document.createElement("div"); 225 | rowDiv.setAttribute("class", "settingDiv"); 226 | //----rowDiv.setAttribute("id", "settingR_"+data[i].name); 227 | rowDiv.setAttribute("id", "D_"+data[i].name); 228 | rowDiv.setAttribute("style", "text-align: right;"); 229 | rowDiv.style.marginLeft = "10px"; 230 | rowDiv.style.marginRight = "10px"; 231 | rowDiv.style.width = "800px"; 232 | rowDiv.style.border = "thick solid lightblue"; 233 | rowDiv.style.background = "lightblue"; 234 | //--- field Name --- 235 | var fldDiv = document.createElement("div"); 236 | fldDiv.setAttribute("style", "margin-right: 10px;"); 237 | fldDiv.style.width = "270px"; 238 | fldDiv.style.float = 'left'; 239 | fldDiv.textContent = translateToHuman(data[i].name); 240 | rowDiv.appendChild(fldDiv); 241 | //--- input --- 242 | var inputDiv = document.createElement("div"); 243 | inputDiv.setAttribute("style", "text-align: left;"); 244 | 245 | var sInput = document.createElement("INPUT"); 246 | //----sInput.setAttribute("id", "setFld_"+data[i].name); 247 | sInput.setAttribute("id", data[i].name); 248 | 249 | if (data[i].type == "s") 250 | { 251 | sInput.setAttribute("type", "text"); 252 | sInput.setAttribute("maxlength", data[i].maxlen); 253 | sInput.setAttribute("size", 50); 254 | } 255 | else if (data[i].type == "f") 256 | { 257 | sInput.setAttribute("type", "number"); 258 | sInput.max = data[i].max; 259 | sInput.min = data[i].min; 260 | sInput.step = (data[i].min + data[i].max) / 1000; 261 | } 262 | else if (data[i].type == "i") 263 | { 264 | sInput.setAttribute("type", "number"); 265 | sInput.setAttribute("size", 10); 266 | sInput.max = data[i].max; 267 | sInput.min = data[i].min; 268 | //sInput.step = (data[i].min + data[i].max) / 1000; 269 | sInput.step = 1; 270 | } 271 | sInput.setAttribute("value", data[i].value); 272 | sInput.addEventListener('change', 273 | function() { setBackGround(data[i].name, "lightgray"); }, 274 | false 275 | ); 276 | inputDiv.appendChild(sInput); 277 | 278 | rowDiv.appendChild(inputDiv); 279 | settings.appendChild(rowDiv); 280 | } 281 | else 282 | { 283 | //----document.getElementById("setFld_"+data[i].name).style.background = "white"; 284 | document.getElementById(data[i].name).style.background = "white"; 285 | //----document.getElementById("setFld_"+data[i].name).value = data[i].value; 286 | document.getElementById(data[i].name).value = data[i].value; 287 | } 288 | } 289 | //console.log("-->done.."); 290 | }) 291 | .catch(function(error) { 292 | var p = document.createElement('p'); 293 | p.appendChild( 294 | document.createTextNode('Error: ' + error.message) 295 | ); 296 | }); 297 | 298 | } // refreshSettings() 299 | 300 | 301 | //============================================================================ 302 | function saveMessages() 303 | { 304 | console.log("Saving messages .."); 305 | let changes = false; 306 | 307 | //--- has anything changed? 308 | var page = document.getElementById("mainPage"); 309 | var mRow = page.getElementsByTagName("input"); 310 | //var mRow = document.getElementById("mainPage").getElementsByTagName('div'); 311 | for(var i = 0; i < mRow.length; i++) 312 | { 313 | //do something to each div like 314 | var msgId = mRow[i].getAttribute("id"); 315 | var field = msgId.substr(2); 316 | //console.log("msgId["+msgId+", msgNr["+field+"]"); 317 | value = document.getElementById(msgId).value; 318 | //console.log("==> name["+field+"], value["+value+"]"); 319 | 320 | changes = false; 321 | 322 | if (getBackGround("M_"+field) == "lightgray") 323 | { 324 | setBackGround("M_"+field, "white"); 325 | changes = true; 326 | } 327 | if (changes) { 328 | console.log("Changes where made in ["+field+"]["+value+"]"); 329 | //processWithTimeout([(data.length -1), 0], 2, data, sendPostReading); 330 | sendPostMessages(field, value); 331 | } 332 | } 333 | 334 | } // saveMessages() 335 | 336 | 337 | //============================================================================ 338 | function saveSettings() 339 | { 340 | console.log("saveSettings() ..."); 341 | let changes = false; 342 | 343 | //--- has anything changed? 344 | var page = document.getElementById("settingsPage"); 345 | var mRow = page.getElementsByTagName("input"); 346 | //var mRow = document.getElementById("mainPage").getElementsByTagName('div'); 347 | for(var i = 0; i < mRow.length; i++) 348 | { 349 | //do something to each div like 350 | var msgId = mRow[i].getAttribute("id"); 351 | var field = msgId; 352 | //console.log("msgId["+msgId+", msgNr["+field+"]"); 353 | value = document.getElementById(msgId).value; 354 | //console.log("==> name["+field+"], value["+value+"]"); 355 | 356 | changes = false; 357 | 358 | if (getBackGround(field) == "lightgray") 359 | { 360 | setBackGround(field, "white"); 361 | changes = true; 362 | } 363 | if (changes) { 364 | console.log("Changes where made in ["+field+"]["+value+"]"); 365 | var value = value.replace(/,/g, " "); 366 | value = value.replace(/ /g, " "); 367 | value = value.replace(/ /g, " "); 368 | value = value.replace(/ /g, " "); 369 | //processWithTimeout([(data.length -1), 0], 2, data, sendPostReading); 370 | sendPostSetting(field, value); 371 | } 372 | } 373 | 374 | } // saveSettings() 375 | 376 | 377 | //============================================================================ 378 | function sendPostMessages(field, value) 379 | { 380 | //console.log("sendPostMessages(value): "+value+"]"); 381 | value = value.replaceAll("\"", "'"); 382 | value = value.replaceAll(":", "@1@"); 383 | value = value.replaceAll("{", "@2@"); 384 | value = value.replaceAll("}", "@3@"); 385 | value = value.replaceAll(",", "@4@"); 386 | value = value.replaceAll("\\", "@5@"); 387 | value = value.replaceAll("\%", "@6@"); 388 | console.log("sendPostMessages(value): "+value+"]"); 389 | 390 | const jsonString = {"name" : field, "value" : value}; 391 | console.log("sendPostMessages(): "+JSON.stringify(jsonString)); 392 | const other_params = { 393 | headers : { "content-type" : "application/json; charset=UTF-8"}, 394 | body : JSON.stringify(jsonString), 395 | method : "POST", 396 | mode : "cors" 397 | }; 398 | 399 | fetch(APIGW+"v0/messages", other_params) 400 | .then(function(response) { 401 | }, function(error) { 402 | console.log("Error["+error.message+"]"); //=> String 403 | }); 404 | 405 | } // sendPostMessages() 406 | 407 | 408 | //============================================================================ 409 | function sendPostSetting(field, value) 410 | { 411 | const jsonString = {"name" : field, "value" : value}; 412 | console.log("sending: "+JSON.stringify(jsonString)); 413 | const other_params = { 414 | headers : { "content-type" : "application/json; charset=UTF-8"}, 415 | body : JSON.stringify(jsonString), 416 | method : "POST", 417 | mode : "cors" 418 | }; 419 | 420 | fetch(APIGW+"v0/settings", other_params) 421 | .then(function(response) { 422 | //console.log(response.status ); //=> number 100–599 423 | //console.log(response.statusText); //=> String 424 | //console.log(response.headers); //=> Headers 425 | //console.log(response.url); //=> String 426 | //console.log(response.text()); 427 | //return response.text() 428 | }, function(error) { 429 | console.log("Error["+error.message+"]"); //=> String 430 | }); 431 | 432 | } // sendPostSetting() 433 | 434 | 435 | //============================================================================ 436 | function translateToHuman(longName) { 437 | //for(var index = 0; index < (translateFields.length -1); index++) 438 | for(var index = 0; index < translateFields.length; index++) 439 | { 440 | if (translateFields[index][0] == longName) 441 | { 442 | return translateFields[index][1]; 443 | } 444 | }; 445 | return longName; 446 | 447 | } // translateToHuman() 448 | 449 | 450 | 451 | //============================================================================ 452 | function setBackGround(field, newColor) { 453 | //console.log("setBackground("+field+", "+newColor+")"); 454 | document.getElementById(field).style.background = newColor; 455 | 456 | } // setBackGround() 457 | 458 | 459 | //============================================================================ 460 | function getBackGround(field) { 461 | //console.log("getBackground("+field+")"); 462 | return document.getElementById(field).style.background; 463 | 464 | } // getBackGround() 465 | 466 | 467 | //============================================================================ 468 | function round(value, precision) { 469 | var multiplier = Math.pow(10, precision || 0); 470 | return Math.round(value * multiplier) / multiplier; 471 | } 472 | 473 | 474 | //============================================================================ 475 | function printAllVals(obj) { 476 | for (let k in obj) { 477 | if (typeof obj[k] === "object") { 478 | printAllVals(obj[k]) 479 | } else { 480 | // base case, stop recurring 481 | console.log(obj[k]); 482 | } 483 | } 484 | } // printAllVals() 485 | 486 | 487 | var translateFields = [ 488 | [ "author", "Auteur" ] 489 | ,[ "localMaxMsg", "max. aantal boodschappen" ] 490 | ,[ "textSpeed", "scroll snelheid" ] 491 | ,[ "maxIntensity", "max. Intensiteit leds" ] 492 | ,[ "LDRlowOffset", "LDR min. waarde" ] 493 | ,[ "LDRhighOffset", "LDR max. waarde" ] 494 | ,[ "weerliveAUTH", "weerlive.nl auth. token" ] 495 | ,[ "weerliveLocation", "weerlive.nl locatie" ] 496 | ,[ "weerliveInterval", "weerlive.nl refresh interval in minuten" ] 497 | ,[ "newsapiAUTH", "newsapi.org auth. token" ] 498 | ,[ "newsapiMaxMsg", "newsapi.nl max. items" ] 499 | ,[ "newsapiInterval", "newapi.nl refresh interval in minuten" ] 500 | ,[ "newsNoWords", "skip items met deze woorden" ] 501 | ]; 502 | 503 | //============================================================================ 504 | String.prototype.replaceAll = function(str1, str2, ignore) 505 | { 506 | return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2); 507 | //return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2); 508 | } 509 | 510 | /* 511 | *************************************************************************** 512 | * 513 | * Permission is hereby granted, free of charge, to any person obtaining a 514 | * copy of this software and associated documentation files (the 515 | * "Software"), to deal in the Software without restriction, including 516 | * without limitation the rights to use, copy, modify, merge, publish, 517 | * distribute, sublicense, and/or sell copies of the Software, and to permit 518 | * persons to whom the Software is furnished to do so, subject to the 519 | * following conditions: 520 | * 521 | * The above copyright notice and this permission notice shall be included 522 | * in all copies or substantial portions of the Software. 523 | * 524 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 525 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 526 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 527 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 528 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 529 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 530 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 531 | * 532 | *************************************************************************** 533 | */ 534 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/newsFiles/LCL-001: -------------------------------------------------------------------------------- 1 | Dit is een dummy local news file! -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/newsFiles/NWS-001: -------------------------------------------------------------------------------- 1 | Dit is een dummy news file! -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/settings.ini: -------------------------------------------------------------------------------- 1 | Hostname = ESPticker 2 | localMaxMsg = 5 3 | textSpeed = 25 4 | maxIntensity = 6 5 | LDRlowOffset = 70 6 | LDRhighOffset = 700 7 | weerLiveAUTH = 8 | weerLiveLocatie = Amsterdam 9 | weerLiveInterval = 15 10 | newsAUTH = 11 | newsNoWords = Voetbal show UEFA KNVB vannederland boulevard voetbalzone 12 | newsMaxMsg = 4 13 | newsInterval = 15 14 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/data/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrWheel/arduinoIDE2platformIO-convertor/64f81c8c4c4a3f3d5080859960acf18450902978/testProject/ESP_ticker/data/settings.png -------------------------------------------------------------------------------- /testProject/ESP_ticker/helperStuff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : helperStuff, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | #include 12 | 13 | //=========================================================================================== 14 | bool compare(String x, String y) 15 | { 16 | for (int i = 0; i < min(x.length(), y.length()); i++) { 17 | if (x[i] != y[i]) 18 | { 19 | return (bool)(x[i] < y[i]); 20 | } 21 | } 22 | return x.length() < y.length(); 23 | 24 | } // compare() 25 | 26 | 27 | //=========================================================================================== 28 | boolean isValidIP(IPAddress ip) 29 | { 30 | /* Works as follows: 31 | * example: 32 | * 127.0.0.1 33 | * 1 => 127||0||0||1 = 128>0 = true 34 | * 2 => !(false || false) = true 35 | * 3 => !(false || false || false || false ) = true 36 | * 4 => !(true && true && true && true) = false 37 | * 5 => !(false) = true 38 | * true && true & true && false && true = false ==> correct, this is an invalid addres 39 | * 40 | * 0.0.0.0 41 | * 1 => 0||0||0||0 = 0>0 = false 42 | * 2 => !(true || true) = false 43 | * 3 => !(false || false || false || false) = true 44 | * 4 => !(true && true && true && tfalse) = true 45 | * 5 => !(false) = true 46 | * false && false && true && true && true = false ==> correct, this is an invalid addres 47 | * 48 | * 192.168.0.1 49 | * 1 => 192||168||0||1 =233>0 = true 50 | * 2 => !(false || false) = true 51 | * 3 +> !(false || false || false || false) = true 52 | * 4 => !(false && false && true && true) = true 53 | * 5 => !(false) = true 54 | * true & true & true && true && true = true ==> correct, this is a valid address 55 | * 56 | * 255.255.255.255 57 | * 1 => 255||255||255||255 =255>0 = true 58 | * 2 => !(false || false ) = true 59 | * 3 +> !(true || true || true || true) = false 60 | * 4 => !(false && false && false && false) = true 61 | * 5 => !(true) = false 62 | * true && true && false && true && false = false ==> correct, this is an invalid address 63 | * 64 | * 0.123.12.1 => true && false && true && true && true = false ==> correct, this is an invalid address 65 | * 10.0.0.0 => true && false && true && true && true = false ==> correct, this is an invalid address 66 | * 10.255.0.1 => true && true && false && true && true = false ==> correct, this is an invalid address 67 | * 150.150.255.150 => true && true && false && true && true = false ==> correct, this is an invalid address 68 | * 69 | * 123.21.1.99 => true && true && true && true && true = true ==> correct, this is annvalid address 70 | * 1.1.1.1 => true && true && true && true && true = true ==> correct, this is annvalid address 71 | * 72 | * Some references on valid ip addresses: 73 | * - https://www.quora.com/How-do-you-identify-an-invalid-IP-address 74 | * 75 | */ 76 | boolean _isValidIP = false; 77 | _isValidIP = ((ip[0] || ip[1] || ip[2] || ip[3])>0); // if any bits are set, then it is not 0.0.0.0 78 | _isValidIP &= !((ip[0]==0) || (ip[3]==0)); // if either the first or last is a 0, then it is invalid 79 | _isValidIP &= !((ip[0]==255) || (ip[1]==255) || (ip[2]==255) || (ip[3]==255)) ; // if any of the octets is 255, then it is invalid 80 | _isValidIP &= !(ip[0]==127 && ip[1]==0 && ip[2]==0 && ip[3]==1); // if not 127.0.0.0 then it might be valid 81 | _isValidIP &= !(ip[0]>=224); // if ip[0] >=224 then reserved space 82 | 83 | DebugTf( "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); 84 | if (_isValidIP) 85 | Debugln(F(" = Valid IP")); 86 | else 87 | Debugln(F(" = Invalid IP!")); 88 | 89 | return _isValidIP; 90 | 91 | } // isValidIP() 92 | 93 | 94 | //=========================================================================================== 95 | bool isNumericp(const char *timeStamp, int8_t len) 96 | { 97 | for (int i=0; (i '9') 100 | { 101 | return false; 102 | } 103 | } 104 | return true; 105 | 106 | } // isNumericp() 107 | 108 | 109 | //=========================================================================================== 110 | int8_t splitString(String inStrng, char delimiter, String wOut[], uint8_t maxWords) 111 | { 112 | int16_t inxS = 0, inxE = 0, wordCount = 0; 113 | 114 | inStrng.trim(); 115 | while(inxE < inStrng.length() && wordCount < maxWords) 116 | { 117 | inxE = inStrng.indexOf(delimiter, inxS); //finds location of first , 118 | wOut[wordCount] = inStrng.substring(inxS, inxE); //captures first data String 119 | wOut[wordCount].trim(); 120 | //DebugTf("[%d] => [%c] @[%d] found[%s]\r\n", wordCount, delimiter, inxE, wOut[wordCount].c_str()); 121 | inxS = inxE; 122 | inxS++; 123 | wordCount++; 124 | } 125 | // zero rest of the words 126 | for(int i=wordCount; i< maxWords; i++) 127 | { 128 | wOut[wordCount][0] = 0; 129 | } 130 | // if not whole string processed place rest in last word 131 | if (inxS < inStrng.length()) 132 | { 133 | wOut[maxWords-1] = inStrng.substring(inxS, inStrng.length()); // store rest of String 134 | } 135 | 136 | return wordCount; 137 | 138 | } // splitString() 139 | 140 | 141 | 142 | //=========================================================================================== 143 | void strConcat(char *dest, int maxLen, const char *src) 144 | { 145 | if (strlen(dest) + strlen(src) < maxLen) 146 | { 147 | strcat(dest, src); 148 | } 149 | else 150 | { 151 | DebugTf("Combined string > %d chars\r\n", maxLen); 152 | } 153 | 154 | } // strConcat() 155 | 156 | 157 | //=========================================================================================== 158 | void strConcat(char *dest, int maxLen, float v, int dec) 159 | { 160 | static char buff[25]; 161 | if (dec == 0) sprintf(buff,"%.0f", v); 162 | else if (dec == 1) sprintf(buff,"%.1f", v); 163 | else if (dec == 2) sprintf(buff,"%.2f", v); 164 | else if (dec == 3) sprintf(buff,"%.3f", v); 165 | else if (dec == 4) sprintf(buff,"%.4f", v); 166 | else if (dec == 5) sprintf(buff,"%.5f", v); 167 | else sprintf(buff,"%f", v); 168 | 169 | if (strlen(dest) + strlen(buff) < maxLen) 170 | { 171 | strcat(dest, buff); 172 | } 173 | else 174 | { 175 | DebugTf("Combined string > %d chars\r\n", maxLen); 176 | } 177 | 178 | } // strConcat() 179 | 180 | 181 | //=========================================================================================== 182 | void strConcat(char *dest, int maxLen, int v) 183 | { 184 | static char buff[25]; 185 | sprintf(buff,"%d", v); 186 | 187 | if (strlen(dest) + strlen(buff) < maxLen) 188 | { 189 | strcat(dest, buff); 190 | } 191 | else 192 | { 193 | DebugTf("Combined string > %d chars\r\n", maxLen); 194 | } 195 | 196 | } // strConcat() 197 | 198 | 199 | //=========================================================================================== 200 | void strToLower(char *src) 201 | { 202 | for (int i = 0; i < strlen(src); i++) 203 | { 204 | if (src[i] == '\0') return; 205 | if (src[i] >= 'A' && src[i] <= 'Z') 206 | src[i] += 32; 207 | } 208 | } // strToLower() 209 | 210 | //=========================================================================================== 211 | // a 'save' string copy 212 | void strCopy(char *dest, int maxLen, const char *src, int frm, int to) 213 | { 214 | int d=0; 215 | //DebugTf("dest[%s], src[%s] max[%d], frm[%d], to[%d] =>\r\n", dest, src, maxLen, frm, to); 216 | dest[0] = '\0'; 217 | for (int i=0; i<=frm; i++) 218 | { 219 | if (src[i] == 0) return; 220 | } 221 | for (int i=frm; (src[i] != 0 && i<=to && d= maxLen! 232 | void strCopy(char *dest, int maxLen, const char *src) 233 | { 234 | dest[0] = '\0'; 235 | strcat(dest, src); 236 | 237 | } // strCopy() 238 | 239 | //=========================================================================================== 240 | // 'tttABCtDEtFGHttt' => 'ABCtDEtFGHttt' 241 | void strLTrim(char *dest, int maxLen, const char tChar ) 242 | { 243 | char tmp[maxLen]; 244 | int tPos = 0; 245 | bool done = false; 246 | 247 | tmp[0] = '\0'; 248 | 249 | for (int dPos=0; (dPos 'tttABCtDEtFGH' 264 | void strRTrim(char *dest, int maxLen, const char tChar ) 265 | { 266 | char tmp[maxLen]; 267 | int dPos, tPos, dMax; 268 | bool done = false; 269 | 270 | for(dMax=0; dMax=0 && !done); dPos--) 274 | { 275 | if (dest[dPos] == tChar) 276 | { 277 | tPos = dPos; 278 | tmp[tPos] = '\0'; 279 | } 280 | else done = true; 281 | } 282 | dPos++; 283 | for(dMax = 0; dMax <= dPos; dMax++) 284 | { 285 | tmp[dMax] = dest[dMax]; 286 | } 287 | tmp[dMax+1] = '\0'; 288 | strCopy(dest, maxLen, tmp); 289 | 290 | } // strRTrim() 291 | 292 | //=========================================================================================== 293 | // 'tttABCtDEtFGHttt' => 'ABCtDEtFGH' 294 | void strTrim(char *dest, int maxLen, const char tChar ) 295 | { 296 | char sTmp[maxLen]; 297 | 298 | strCopy(sTmp, maxLen, dest); 299 | strLTrim(sTmp, maxLen, tChar); 300 | strRTrim(sTmp, maxLen, tChar); 301 | strCopy(dest, maxLen, sTmp); 302 | 303 | } // strTrim() 304 | 305 | //=========================================================================================== 306 | void strRemoveAll(char *dest, int maxLen, const char tChar) 307 | { 308 | char tmp[maxLen]; 309 | int tPos = 0; 310 | tmp[0] = '\0'; 311 | for (int dPos=0; (dPos= ' ' && dest[dPos] <= '~') // space = 32, '~' = 127 333 | { 334 | tmp[tPos++] = dest[dPos]; 335 | } 336 | } 337 | tmp[tPos] = '\0'; 338 | strCopy(dest, maxLen, tmp); 339 | 340 | } // strTrimCntr() 341 | 342 | //=========================================================================================== 343 | int strIndex(const char *haystack, const char *needle, int start) 344 | { 345 | // strindex(hay, needle) ???? 346 | char *p = strstr(haystack+start, needle); 347 | if (p) { 348 | //DebugTf("found [%s] at position [%d]\r\n", needle, (p - haystack)); 349 | return (p - haystack); 350 | } 351 | return -1; 352 | 353 | } // strIndex() 354 | 355 | //=========================================================================================== 356 | int strIndex(const char *haystack, const char *needle) 357 | { 358 | return strIndex(haystack, needle, 0); 359 | 360 | } // strIndex() 361 | 362 | //=========================================================================================== 363 | int stricmp(const char *a, const char *b) 364 | { 365 | for (;; a++, b++) { 366 | int d = tolower((unsigned char)*a) - tolower((unsigned char)*b); 367 | if (d != 0 || !*a) 368 | return d; 369 | } 370 | 371 | } // stricmp() 372 | 373 | //=========================================================================================== 374 | char *intToStr(int32_t v) 375 | { 376 | static char buff[25]; 377 | sprintf(buff,"%d", v); 378 | return buff; 379 | 380 | } // intToStr() 381 | 382 | //=========================================================================================== 383 | char *floatToStr(float v, int dec) 384 | { 385 | static char buff[25]; 386 | if (dec == 0) sprintf(buff,"%.0f", v); 387 | else if (dec == 1) sprintf(buff,"%.1f", v); 388 | else if (dec == 2) sprintf(buff,"%.2f", v); 389 | else if (dec == 3) sprintf(buff,"%.3f", v); 390 | else if (dec == 4) sprintf(buff,"%.4f", v); 391 | else if (dec == 5) sprintf(buff,"%.5f", v); 392 | else sprintf(buff,"%f", v); 393 | return buff; 394 | 395 | } // floattToStr() 396 | 397 | //=========================================================================================== 398 | float formatFloat(float v, int dec) 399 | { 400 | return (String(v, dec).toFloat()); 401 | 402 | } // formatFloat() 403 | 404 | //=========================================================================================== 405 | float strToFloat(const char *s, int dec) 406 | { 407 | float r = 0.0; 408 | int p = 0; 409 | int d = -1; 410 | 411 | r = strtof(s, NULL); 412 | p = (int)(r*pow(10, dec)); 413 | r = p / pow(10, dec); 414 | //DebugTf("[%s][%d] => p[%d] -> r[%f]\r\n", s, dec, p, r); 415 | return r; 416 | 417 | } // strToFloat() 418 | 419 | //=========================================================================================== 420 | void parseJsonKey(const char *sIn, const char *key, char *val, int valLen) 421 | { 422 | // json key-value pair looks like: 423 | // "samenv": "Zwaar bewolkt", 424 | // or "samenv": "Zwaar bewolkt"} 425 | int keyStart = strIndex(sIn, key); 426 | int sepStart = strIndex(sIn, ",", keyStart); 427 | if (sepStart == -1) 428 | { 429 | sepStart = strIndex(sIn, "}", keyStart); 430 | } 431 | strCopy(val, valLen, sIn, keyStart+strlen(key), sepStart); 432 | strRemoveAll(val, valLen, ':'); 433 | strRemoveAll(val, valLen, ','); 434 | strRemoveAll(val, valLen, '}'); 435 | strRemoveAll(val, valLen, '"'); 436 | strTrim(val, valLen, ' '); 437 | 438 | } // parseJsonKey() 439 | 440 | //=========================================================================================== 441 | uint8_t utf8Ascii(uint8_t ascii) 442 | // Convert a single Character from UTF8 to Extended ASCII according to ISO 8859-1, 443 | // also called ISO Latin-1. Codes 128-159 contain the Microsoft Windows Latin-1 444 | // extended characters: 445 | // - codes 0..127 are identical in ASCII and UTF-8 446 | // - codes 160..191 in ISO-8859-1 and Windows-1252 are two-byte characters in UTF-8 447 | // + 0xC2 then second byte identical to the extended ASCII code. 448 | // - codes 192..255 in ISO-8859-1 and Windows-1252 are two-byte characters in UTF-8 449 | // + 0xC3 then second byte differs only in the first two bits to extended ASCII code. 450 | // - codes 128..159 in Windows-1252 are different, but usually only the €-symbol will be needed from this range. 451 | // + The euro symbol is 0x80 in Windows-1252, 0xa4 in ISO-8859-15, and 0xe2 0x82 0xac in UTF-8. 452 | // 453 | // Modified from original code at http://playground.arduino.cc/Main/Utf8ascii 454 | // Extended ASCII encoding should match the characters at http://www.ascii-code.com/ 455 | // 456 | // Return "0" if a byte has to be ignored. 457 | { 458 | static uint8_t cPrev; 459 | uint8_t c = '\0'; 460 | 461 | if (ascii < 0x7f) // Standard ASCII-set 0..0x7F, no conversion 462 | { 463 | cPrev = '\0'; 464 | c = ascii; 465 | } 466 | else 467 | { 468 | switch (cPrev) // Conversion depending on preceding UTF8-character 469 | { 470 | case 0xC2: c = ascii; break; 471 | case 0xC3: c = ascii | 0xC0; break; 472 | case 0x82: if (ascii==0xAC) c = 0x80; // Euro symbol special case 473 | } 474 | cPrev = ascii; // save last char 475 | } 476 | //Debugf("\nConverted 0x%02x", ascii); 477 | //Debugf(" to 0x%02x", c); 478 | 479 | return(c); 480 | 481 | } // utf8Ascii(uint8_t) 482 | 483 | //=========================================================================================== 484 | void utf8Ascii(char* s) 485 | // In place conversion UTF-8 string to Extended ASCII 486 | // The extended ASCII string is always shorter. 487 | { 488 | uint8_t c; 489 | char *cp = s; 490 | 491 | //DebugTf("\nConverting: %c", s); 492 | 493 | while (*s != '\0') 494 | { 495 | c = utf8Ascii(*s++); 496 | if (c != '\0') 497 | *cp++ = c; 498 | } 499 | *cp = '\0'; // terminate the new string 500 | 501 | } // utf8Ascii(char) 502 | 503 | 504 | void getRevisionData() 505 | { 506 | if (!LittleFS.exists("/newsFiles/LCL-000")) 507 | { 508 | char LCL000[100]; 509 | sprintf(LCL000, "ESP_ticker %s (c) by Willem Aandewiel", String(FWversion).c_str()); 510 | writeFileById("LCL", 0, LCL000); 511 | } 512 | 513 | } // getRevisionData() 514 | 515 | /*************************************************************************** 516 | * 517 | * Permission is hereby granted, free of charge, to any person obtaining a 518 | * copy of this software and associated documentation files (the 519 | * "Software"), to deal in the Software without restriction, including 520 | * without limitation the rights to use, copy, modify, merge, publish, 521 | * distribute, sublicense, and/or sell copies of the Software, and to permit 522 | * persons to whom the Software is furnished to do so, subject to the 523 | * following conditions: 524 | * 525 | * The above copyright notice and this permission notice shall be included 526 | * in all copies or substantial portions of the Software. 527 | * 528 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 529 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 530 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 531 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 532 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 533 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 534 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 535 | * 536 | **************************************************************************** 537 | */ 538 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/jsonStuff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : jsonStuff, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | //aaw//WiFiServer httpServer(80); 12 | static char objSprtr[10] = ""; 13 | 14 | //======================================================================= 15 | void sendStartJsonObj(const char *objName) 16 | { 17 | char sBuff[50] = ""; 18 | objSprtr[0] = '\0'; 19 | 20 | snprintf(sBuff, sizeof(sBuff), "{\"%s\":[\r\n", objName); 21 | httpServer.sendHeader("Access-Control-Allow-Origin", "*"); 22 | httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); 23 | httpServer.send(200, "application/json", sBuff); 24 | 25 | } // sendStartJsonObj() 26 | 27 | 28 | //======================================================================= 29 | void sendEndJsonObj() 30 | { 31 | httpServer.sendContent("\r\n]}\r\n"); 32 | 33 | //httpServer.sendHeader( "Content-Length", "0"); 34 | //httpServer.send ( 200, "application/json", ""); 35 | 36 | } // sendEndJsonObj() 37 | 38 | 39 | //======================================================================= 40 | void sendNestedJsonObj(const char *cName, const char *cValue) 41 | { 42 | char jsonBuff[JSON_BUFF_MAX] = ""; 43 | 44 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": \"%s\"}" 45 | , objSprtr, cName, cValue); 46 | 47 | httpServer.sendContent(jsonBuff); 48 | sprintf(objSprtr, ",\r\n"); 49 | 50 | } // sendNestedJsonObj(*char, *char) 51 | 52 | 53 | //======================================================================= 54 | void sendNestedJsonObj(const char *cName, String sValue) 55 | { 56 | char jsonBuff[JSON_BUFF_MAX] = ""; 57 | 58 | if (sValue.length() > (JSON_BUFF_MAX - 65) ) 59 | { 60 | DebugTf("[2] sValue.length() [%d]\r\n", sValue.length()); 61 | } 62 | 63 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": \"%s\"}" 64 | , objSprtr, cName, sValue.c_str()); 65 | 66 | httpServer.sendContent(jsonBuff); 67 | sprintf(objSprtr, ",\r\n"); 68 | 69 | } // sendNestedJsonObj(*char, String) 70 | 71 | 72 | //======================================================================= 73 | void sendNestedJsonObj(const char *cName, int32_t iValue) 74 | { 75 | char jsonBuff[200] = ""; 76 | 77 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %d}" 78 | , objSprtr, cName, iValue); 79 | 80 | httpServer.sendContent(jsonBuff); 81 | sprintf(objSprtr, ",\r\n"); 82 | 83 | } // sendNestedJsonObj(*char, int) 84 | 85 | //======================================================================= 86 | void sendNestedJsonObj(const char *cName, uint32_t uValue) 87 | { 88 | char jsonBuff[200] = ""; 89 | 90 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %u }" 91 | , objSprtr, cName, uValue); 92 | 93 | httpServer.sendContent(jsonBuff); 94 | sprintf(objSprtr, ",\r\n"); 95 | 96 | } // sendNestedJsonObj(*char, uint) 97 | 98 | 99 | //======================================================================= 100 | void sendNestedJsonObj(const char *cName, float fValue) 101 | { 102 | char jsonBuff[200] = ""; 103 | 104 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.3f }" 105 | , objSprtr, cName, fValue); 106 | 107 | httpServer.sendContent(jsonBuff); 108 | sprintf(objSprtr, ",\r\n"); 109 | 110 | } // sendNestedJsonObj(*char, float) 111 | 112 | 113 | //======================================================================= 114 | // ************ function to build Json Settings string ****************** 115 | //======================================================================= 116 | void sendJsonSettingObj(const char *cName, float fValue, const char *fType, int minValue, int maxValue) 117 | { 118 | char jsonBuff[200] = ""; 119 | 120 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.3f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 121 | , objSprtr, cName, fValue, fType, minValue, maxValue); 122 | 123 | httpServer.sendContent(jsonBuff); 124 | sprintf(objSprtr, ",\r\n"); 125 | 126 | } // sendJsonSettingObj(*char, float, *char, int, int) 127 | 128 | 129 | //======================================================================= 130 | void sendJsonSettingObj(const char *cName, float fValue, const char *fType, int minValue, int maxValue, int decPlaces) 131 | { 132 | char jsonBuff[200] = ""; 133 | 134 | switch(decPlaces) { 135 | case 0: 136 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.0f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 137 | , objSprtr, cName, fValue, fType, minValue, maxValue); 138 | break; 139 | case 2: 140 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.2f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 141 | , objSprtr, cName, fValue, fType, minValue, maxValue); 142 | break; 143 | case 5: 144 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.5f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 145 | , objSprtr, cName, fValue, fType, minValue, maxValue); 146 | break; 147 | default: 148 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 149 | , objSprtr, cName, fValue, fType, minValue, maxValue); 150 | 151 | } 152 | 153 | httpServer.sendContent(jsonBuff); 154 | sprintf(objSprtr, ",\r\n"); 155 | 156 | } // sendJsonSettingObj(*char, float, *char, int, int, int) 157 | 158 | 159 | //======================================================================= 160 | void sendJsonSettingObj(const char *cName, int iValue, const char *iType, int minValue, int maxValue) 161 | { 162 | char jsonBuff[200] = ""; 163 | 164 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %d, \"type\": \"%s\", \"min\": %d, \"max\": %d}" 165 | , objSprtr, cName, iValue, iType, minValue, maxValue); 166 | 167 | httpServer.sendContent(jsonBuff); 168 | sprintf(objSprtr, ",\r\n"); 169 | 170 | } // sendJsonSettingObj(*char, int, *char, int, int) 171 | 172 | 173 | //======================================================================= 174 | void sendJsonSettingObj(const char *cName, const char *cValue, const char *sType, int maxLen) 175 | { 176 | char jsonBuff[200] = ""; 177 | 178 | snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\":\"%s\", \"type\": \"%s\", \"maxlen\": %d}" 179 | , objSprtr, cName, cValue, sType, maxLen); 180 | 181 | httpServer.sendContent(jsonBuff); 182 | sprintf(objSprtr, ",\r\n"); 183 | 184 | } // sendJsonSettingObj(*char, *char, *char, int, int) 185 | 186 | 187 | 188 | 189 | /*************************************************************************** 190 | * 191 | * Permission is hereby granted, free of charge, to any person obtaining a 192 | * copy of this software and associated documentation files (the 193 | * "Software"), to deal in the Software without restriction, including 194 | * without limitation the rights to use, copy, modify, merge, publish, 195 | * distribute, sublicense, and/or sell copies of the Software, and to permit 196 | * persons to whom the Software is furnished to do so, subject to the 197 | * following conditions: 198 | * 199 | * The above copyright notice and this permission notice shall be included 200 | * in all copies or substantial portions of the Software. 201 | * 202 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 203 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 204 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 205 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 206 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 207 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 208 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 209 | * 210 | **************************************************************************** 211 | */ 212 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/littlefsStuff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : littlefsStuff, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | //==================================================================== 12 | void readLastStatus() 13 | { 14 | char buffer[50] = ""; 15 | char dummy[50] = ""; 16 | 17 | File _file = LittleFS.open("/sysStatus.csv", "r"); 18 | if (!_file) 19 | { 20 | DebugTln("read(): No /sysStatus.csv found .."); 21 | } 22 | if(_file.available()) { 23 | int l = _file.readBytesUntil('\n', buffer, sizeof(buffer)); 24 | buffer[l] = 0; 25 | DebugTf("read lastUpdate[%s]\r\n", buffer); 26 | sscanf(buffer, "%[^;]; %[^;]; %u; %[^;]", cDate, cTime, &nrReboots, dummy); 27 | DebugTf("values timestamp[%s %s], nrReboots[%u], dummy[%s]\r\n" 28 | , cDate 29 | , cTime 30 | , nrReboots 31 | , dummy); 32 | yield(); 33 | } 34 | _file.close(); 35 | 36 | } // readLastStatus() 37 | 38 | 39 | //==================================================================== 40 | void writeLastStatus() 41 | { 42 | if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! 43 | { 44 | DebugTf("Bailout due to low heap (%d bytes)\r\n", ESP.getFreeHeap()); 45 | return; 46 | } 47 | char buffer[50] = ""; 48 | snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d; %02d:%02d:%02d; %010u; %s;\n" 49 | , localtime(&now)->tm_year+1900, localtime(&now)->tm_mon+1, localtime(&now)->tm_mday 50 | , localtime(&now)->tm_hour, localtime(&now)->tm_min, localtime(&now)->tm_sec 51 | , nrReboots 52 | , "meta data"); 53 | DebugTf("writeLastStatus() => %s\r\n", buffer); 54 | 55 | File _file = LittleFS.open("/sysStatus.csv", "w"); 56 | if (!_file) 57 | { 58 | DebugTln("write(): No /sysStatus.csv found .."); 59 | } 60 | _file.print(buffer); 61 | _file.flush(); 62 | _file.close(); 63 | 64 | } // writeLastStatus() 65 | 66 | 67 | //------------------------------------------------------------------------ 68 | bool readFileById(const char* fType, uint8_t mId) 69 | { 70 | String percChar = "%%"; 71 | String backSlash = "\\"; 72 | String rTmp; 73 | char fName[50] = ""; 74 | 75 | sprintf(fName, "/newsFiles/%s-%03d", fType, mId); 76 | 77 | DebugTf("read [%s] ", fName); 78 | 79 | if (!LittleFS.exists(fName)) 80 | { 81 | Debugln("Does not exist!"); 82 | return false; 83 | } 84 | 85 | File f = LittleFS.open(fName, "r"); 86 | 87 | while(f.available()) 88 | { 89 | rTmp = f.readStringUntil('\n'); 90 | //Debugf("rTmp(in) [%s]\r\n", rTmp.c_str()); 91 | rTmp.replace("\r", ""); 92 | } 93 | f.close(); 94 | 95 | rTmp.replace("@1@", ":"); 96 | rTmp.replace("@2@", "{"); 97 | rTmp.replace("@3@", "}"); 98 | rTmp.replace("@4@", ","); 99 | rTmp.replace("@5@", backSlash); 100 | rTmp.replace("@6@", percChar); 101 | //DebugTf("rTmp(out) [%s]\r\n", rTmp.c_str()); 102 | 103 | snprintf(fileMessage, LOCAL_SIZE, rTmp.c_str()); 104 | if (strlen(fileMessage) == 0) 105 | { 106 | Debugln("file is zero bytes long"); 107 | return false; 108 | } 109 | Debugf("OK! \r\n\t[%s]\r\n", fileMessage); 110 | 111 | if (mId == 0) 112 | { 113 | LittleFS.remove("/newsFiles/LCL-000"); 114 | DebugTln("Remove LCL-000 .."); 115 | } 116 | 117 | return true; 118 | 119 | } // readFileById() 120 | 121 | //------------------------------------------------------------------------ 122 | bool writeFileById(const char* fType, uint8_t mId, const char *msg) 123 | { 124 | String rTmp; 125 | char fName[50] = ""; 126 | sprintf(fName, "/newsFiles/%s-%03d", fType, mId); 127 | 128 | DebugTf("write [%s]-> [%s]\r\n", fName, msg); 129 | 130 | if (strlen(msg) < 3) 131 | { 132 | LittleFS.remove(fName); 133 | Debugln("Empty message, file removed!"); 134 | return true; 135 | } 136 | 137 | DebugTln("LittleFS.open()..."); 138 | File file = LittleFS.open(fName, "w"); 139 | if (!file) 140 | { 141 | Debugf("open(%s, 'w') FAILED!!! --> Bailout\r\n", fName); 142 | return false; 143 | } 144 | yield(); 145 | 146 | Debugln(F("Start writing data .. \r")); 147 | DebugFlush(); 148 | Debugln(msg); 149 | file.println(msg); 150 | file.close(); 151 | 152 | DebugTln("Exit writeFileById()!"); 153 | return true; 154 | 155 | } // writeFileById() 156 | 157 | 158 | //======================================================================= 159 | void updateMessage(const char *field, const char *newValue) 160 | { 161 | int8_t msgId = String(field).toInt(); 162 | 163 | DebugTf("-> field[%s], newValue[%s]\r\n", field, newValue); 164 | 165 | if (msgId < 0 || msgId > settingLocalMaxMsg) 166 | { 167 | DebugTf("msgId[%d] is out of scope! Bailing out!\r\n", msgId); 168 | return; 169 | } 170 | 171 | writeFileById("LCL", msgId, newValue); 172 | 173 | } // updateMessage() 174 | 175 | 176 | //==================================================================== 177 | void writeToLog(const char *logLine) 178 | { 179 | if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! 180 | { 181 | DebugTf("Bailout due to low heap (%d bytes)\r\n", ESP.getFreeHeap()); 182 | return; 183 | } 184 | char buffer[150] = ""; 185 | snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d; %02d:%02d:%02d; %s;\n" 186 | , localtime(&now)->tm_year+1900, localtime(&now)->tm_mon+1, localtime(&now)->tm_mday 187 | , localtime(&now)->tm_hour, localtime(&now)->tm_min, localtime(&now)->tm_sec 188 | , logLine); 189 | DebugTf("writeToLogs() => %s\r\n", buffer); 190 | File _file = LittleFS.open("/sysLog.csv", "a"); 191 | if (!_file) 192 | { 193 | DebugTln("write(): No /sysLog.csv found .."); 194 | } 195 | _file.print(buffer); 196 | _file.flush(); 197 | _file.close(); 198 | 199 | } // writeLastStatus() 200 | 201 | 202 | 203 | /*************************************************************************** 204 | * 205 | * Permission is hereby granted, free of charge, to any person obtaining a 206 | * copy of this software and associated documentation files (the 207 | * "Software"), to deal in the Software without restriction, including 208 | * without limitation the rights to use, copy, modify, merge, publish, 209 | * distribute, sublicense, and/or sell copies of the Software, and to permit 210 | * persons to whom the Software is furnished to do so, subject to the 211 | * following conditions: 212 | * 213 | * The above copyright notice and this permission notice shall be included 214 | * in all copies or substantial portions of the Software. 215 | * 216 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 217 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 218 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 219 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 220 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 221 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 222 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 223 | * 224 | **************************************************************************** 225 | */ 226 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/networkStuff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : networkStuff.ino 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | ** Usage: 10 | ** 11 | ** #define HOSTNAME thisProject 12 | ** 13 | ** setup() 14 | ** { 15 | ** startWiFi(_HOSTNAME, 240); // timeout 4 minuten 16 | ** startMDNS(_HOSTNAME); 17 | ** httpServer.on("/index", ); 18 | ** httpServer.on("/index.html",); 19 | ** httpServer.begin(); 20 | ** } 21 | ** 22 | ** loop() 23 | ** { 24 | ** handleWiFi(); 25 | ** MDNS.update(); 26 | ** httpServer.handleClient(); 27 | ** . 28 | ** . 29 | ** } 30 | */ 31 | 32 | 33 | #include //ESP8266 Core WiFi Library 34 | #include // Version 1.0.0 - part of ESP8266 Core https://github.com/esp8266/Arduino 35 | #include // part of ESP8266 Core https://github.com/esp8266/Arduino 36 | 37 | #include // part of ESP8266 Core https://github.com/esp8266/Arduino 38 | #include // https://github.com/mrWheel/ModUpdateServer 39 | #include "updateServerHtml.h" 40 | #include // part of ESP8266 Core 41 | 42 | ESP8266HTTPUpdateServer httpUpdater(true); 43 | 44 | bool isConnected = false; 45 | 46 | /** 47 | void configModeCallback (WiFiManager *myWiFiManager) 48 | { 49 | Debugln(F("Entered config mode\r")); 50 | Debugln(WiFi.softAPIP().toString()); 51 | Debugln(" ----> brows to 192.168.4.1"); 52 | Debugln(myWiFiManager->getConfigPortalSSID()); 53 | 54 | } // configModeCallback() 55 | **/ 56 | 57 | //=========================================================================================== 58 | void startWiFi(const char* hostname, int timeOut) 59 | { 60 | WiFiManager manageWiFi; 61 | uint32_t lTime = millis(); 62 | String thisAP = String(hostname) + "-" + WiFi.macAddress(); 63 | 64 | DebugT("start WiFi ..."); 65 | 66 | manageWiFi.setDebugOutput(true); 67 | 68 | //-- reset settings - wipe stored credentials for testing 69 | //-- these are stored by the esp library 70 | //manageWiFi.resetSettings(); 71 | 72 | 73 | //-- set callback that gets called when connecting to previous 74 | //-- WiFi fails, and enters Access Point mode 75 | //manageWiFi.setAPCallback(configModeCallback); 76 | 77 | //-- sets timeout until configuration portal gets turned off 78 | //-- useful to make it all retry or go to sleep in seconds 79 | manageWiFi.setTimeout(timeOut); // in seconden ... 80 | 81 | //-- fetches ssid and pass and tries to connect 82 | //-- if it does not connect it starts an access point with the specified name 83 | //-- here "lichtKrant-" 84 | //-- and goes into a blocking loop awaiting configuration 85 | if (!manageWiFi.autoConnect(thisAP.c_str())) 86 | { 87 | DebugTln(F("failed to connect and hit timeout")); 88 | 89 | //reset and try again, or maybe put it to deep sleep 90 | //delay(3000); 91 | //ESP.reset(); 92 | //delay(2000); 93 | DebugTf(" took [%d] seconds ==> ERROR!\r\n", (millis() - lTime) / 1000); 94 | return; 95 | } 96 | 97 | Debugln(); 98 | DebugT(F("Connected to " )); Debugln (WiFi.SSID()); 99 | DebugT(F("IP address: " )); Debugln (WiFi.localIP()); 100 | DebugT(F("IP gateway: " )); Debugln (WiFi.gatewayIP()); 101 | Debugln(); 102 | 103 | httpUpdater.setup(&httpServer); 104 | httpUpdater.setIndexPage(UpdateServerIndex); 105 | httpUpdater.setSuccessPage(UpdateServerSuccess); 106 | DebugTf(" took [%d] seconds => OK!\r\n", (millis() - lTime) / 1000); 107 | 108 | } // startWiFi() 109 | 110 | 111 | //======================================================================= 112 | void startMDNS(const char *Hostname) 113 | { 114 | DebugTf("[1] mDNS setup as [%s.local]\r\n", Hostname); 115 | if (MDNS.begin(Hostname)) // Start the mDNS responder for Hostname.local 116 | { 117 | DebugTf("[2] mDNS responder started as [%s.local]\r\n", Hostname); 118 | } 119 | else 120 | { 121 | DebugTln(F("[3] Error setting up MDNS responder!\r\n")); 122 | } 123 | MDNS.addService("http", "tcp", 80); 124 | 125 | } // startMDNS() 126 | 127 | /*************************************************************************** 128 | * 129 | * Permission is hereby granted, free of charge, to any person obtaining a 130 | * copy of this software and associated documentation files (the 131 | * "Software"), to deal in the Software without restriction, including 132 | * without limitation the rights to use, copy, modify, merge, publish, 133 | * distribute, sublicense, and/or sell copies of the Software, and to permit 134 | * persons to whom the Software is furnished to do so, subject to the 135 | * following conditions: 136 | * 137 | * The above copyright notice and this permission notice shall be included 138 | * in all copies or substantial portions of the Software. 139 | * 140 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 141 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 142 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 143 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 144 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 145 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 146 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 147 | * 148 | **************************************************************************** 149 | */ 150 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/newsapi_org.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : newsapi_org 4 | ** 5 | ** Copyright (c) 2021 .. 2023 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | //-- http://newsapi.org/v2/top-headlines?country=nl&apiKey=API_KEY 12 | 13 | //---------------------------------------------------------------------- 14 | bool getNewsapiData() 15 | { 16 | const char* newsapiHost = "newsapi.org"; 17 | const int httpPort = 80; 18 | int newsapiStatus = 0; 19 | char newsMessage[NEWS_SIZE] = {}; 20 | int startPos, endPos; 21 | int32_t maxWait; 22 | char jsonResponse[1024]; 23 | char val[51] = ""; 24 | 25 | WiFiClient newsapiClient; 26 | 27 | Debugln(); 28 | DebugTf("getNewsapiData(%s)\r\n", newsapiHost); 29 | 30 | // We now create a URI for the request 31 | String url = "/v2/top-headlines?country=nl&apiKey="; 32 | url += settingNewsAUTH; 33 | 34 | DebugTf("Requesting URL: %s/v2/top-headlines?country=nl&apiKey=secret\r\n", newsapiHost); 35 | DebugFlush(); 36 | //Debugln("\r\n======================================="); 37 | //DebugFlush(); 38 | //Debug(newsapiHost); 39 | //Debugln(url); 40 | //Debugln("======================================="); 41 | 42 | if (!newsapiClient.connect(newsapiHost, httpPort)) 43 | { 44 | DebugTln("connection failed"); 45 | sprintf(tempMessage, "connection to %s failed", newsapiHost); 46 | //-- empty newsMessage store -- 47 | for(int i=0; i<=settingNewsMaxMsg; i++) 48 | { 49 | //sprintf(newsMessage, ""); 50 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 51 | else writeFileById("NWS", i, ""); 52 | } 53 | 54 | newsapiClient.flush(); 55 | newsapiClient.stop(); 56 | return false; 57 | } 58 | 59 | // This will send the request to the server 60 | newsapiClient.print(String("GET ") + url + " HTTP/1.1\r\n" + 61 | "Host: " + newsapiHost + "\r\n" + 62 | "User-Agent: ESP-ticker\r\n" + 63 | "Connection: close\r\n\r\n"); 64 | delay(10); 65 | 66 | newsapiClient.setTimeout(5000); 67 | while (newsapiClient.connected() || newsapiClient.available()) 68 | { 69 | yield(); 70 | while(newsapiClient.available()) 71 | { 72 | Debugln(); 73 | //--- skip to find HTTP/1.1 74 | //--- then parse response code 75 | if (newsapiClient.find("HTTP/1.1")) 76 | { 77 | newsapiStatus = newsapiClient.parseInt(); // parse status code 78 | DebugTf("Statuscode: [%d] ", newsapiStatus); 79 | if (newsapiStatus != 200) 80 | { 81 | Debugln(" ERROR!"); 82 | while(newsapiClient.available()) 83 | { 84 | char nC = newsapiClient.read(); 85 | Debug(nC); 86 | } 87 | Debugln(); 88 | newsapiClient.flush(); 89 | newsapiClient.stop(); 90 | for(int i=0; i<=settingNewsMaxMsg; i++) 91 | { 92 | //sprintf(newsMessage, ""); 93 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 94 | else writeFileById("NWS", i, ""); 95 | } 96 | newsapiClient.flush(); 97 | newsapiClient.stop(); 98 | return false; 99 | } 100 | Debugln(" OK!"); 101 | } 102 | else 103 | { 104 | DebugTln("Error reading newsapi.org.. -> bailout!"); 105 | for(int i=0; i<=settingNewsMaxMsg; i++) 106 | { 107 | //sprintf(newsMessage, ""); 108 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 109 | else writeFileById("NWS", i, ""); 110 | } 111 | newsapiClient.flush(); 112 | newsapiClient.stop(); 113 | return false; 114 | } 115 | //--- skip headers 116 | uint16_t msgIdx = 0; 117 | int msgNr = 0; 118 | while (newsapiClient.find("\"title\":\"")) 119 | { 120 | for (int i=0; i 30) newsMessage[msgIdx - 16] = 0; 128 | Debugf("\t[%2d] %s\r\n", msgNr, newsMessage); 129 | if (!hasNoNoWord(newsMessage) && strlen(newsMessage) > 15) 130 | { 131 | if (msgNr <= settingNewsMaxMsg) 132 | { 133 | writeFileById("NWS", msgNr, newsMessage); 134 | } 135 | msgNr++; 136 | } 137 | } // while find(title) 138 | } // while available .. 139 | 140 | } // connected .. 141 | 142 | newsapiClient.flush(); 143 | newsapiClient.stop(); 144 | updateMessage("0", "News brought to you by 'newsapi.org'"); 145 | 146 | return true; 147 | 148 | } // getNewsapiData() 149 | 150 | 151 | //---------------------------------------------------------------------- 152 | void removeNewsData() 153 | { 154 | char nwsName[15]; 155 | 156 | for(int n=0; n<=settingNewsMaxMsg; n++) 157 | { 158 | sprintf(nwsName, "/newsFiles/NWS-%03d", n); 159 | DebugTf("Remove [%s] from LittleFS ..\r\n", nwsName); 160 | LittleFS.remove(nwsName); 161 | } 162 | 163 | } // removeNewsData() 164 | 165 | /*************************************************************************** 166 | * 167 | * Permission is hereby granted, free of charge, to any person obtaining a 168 | * copy of this software and associated documentation files (the 169 | * "Software"), to deal in the Software without restriction, including 170 | * without limitation the rights to use, copy, modify, merge, publish, 171 | * distribute, sublicense, and/or sell copies of the Software, and to permit 172 | * persons to whom the Software is furnished to do so, subject to the 173 | * following conditions: 174 | * 175 | * The above copyright notice and this permission notice shall be included 176 | * in all copies or substantial portions of the Software. 177 | * 178 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 179 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 180 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 181 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 182 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 183 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 184 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 185 | * 186 | **************************************************************************** 187 | */ 188 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/newsapi_org.txt: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : newsapi_org 4 | ** 5 | ** Copyright (c) 2021 .. 2023 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | //-- http://newsapi.org/v2/top-headlines?country=nl&apiKey=API_KEY 12 | 13 | //---------------------------------------------------------------------- 14 | bool getNewsapiData() 15 | { 16 | const char* newsapiHost = "newsapi.org"; 17 | const int httpPort = 80; 18 | int newsapiStatus = 0; 19 | char newsMessage[NEWS_SIZE] = {}; 20 | int startPos, endPos; 21 | int32_t maxWait; 22 | char jsonResponse[1024]; 23 | char val[51] = ""; 24 | 25 | WiFiClient newsapiClient; 26 | 27 | Debugln(); 28 | DebugTf("getNewsapiData(%s)\r\n", newsapiHost); 29 | 30 | // We now create a URI for the request 31 | String url = "/v2/top-headlines?country=nl&apiKey="; 32 | url += settingNewsAUTH; 33 | 34 | DebugTf("Requesting URL: %s/v2/top-headlines?country=nl&apiKey=secret\r\n", newsapiHost); 35 | DebugFlush(); 36 | //Debugln("\r\n======================================="); 37 | //DebugFlush(); 38 | //Debug(newsapiHost); 39 | //Debugln(url); 40 | //Debugln("======================================="); 41 | 42 | if (!newsapiClient.connect(newsapiHost, httpPort)) 43 | { 44 | DebugTln("connection failed"); 45 | sprintf(tempMessage, "connection to %s failed", newsapiHost); 46 | //-- empty newsMessage store -- 47 | for(int i=0; i<=settingNewsMaxMsg; i++) 48 | { 49 | //sprintf(newsMessage, ""); 50 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 51 | else writeFileById("NWS", i, ""); 52 | } 53 | 54 | newsapiClient.flush(); 55 | newsapiClient.stop(); 56 | return false; 57 | } 58 | 59 | // This will send the request to the server 60 | newsapiClient.print(String("GET ") + url + " HTTP/1.1\r\n" + 61 | "Host: " + newsapiHost + "\r\n" + 62 | "User-Agent: ESP-ticker\r\n" + 63 | "Connection: close\r\n\r\n"); 64 | delay(10); 65 | 66 | newsapiClient.setTimeout(5000); 67 | while (newsapiClient.connected() || newsapiClient.available()) 68 | { 69 | yield(); 70 | while(newsapiClient.available()) 71 | { 72 | Debugln(); 73 | //--- skip to find HTTP/1.1 74 | //--- then parse response code 75 | if (newsapiClient.find("HTTP/1.1")) 76 | { 77 | newsapiStatus = newsapiClient.parseInt(); // parse status code 78 | DebugTf("Statuscode: [%d] ", newsapiStatus); 79 | if (newsapiStatus != 200) 80 | { 81 | Debugln(" ERROR!"); 82 | while(newsapiClient.available()) 83 | { 84 | char nC = newsapiClient.read(); 85 | Debug(nC); 86 | } 87 | Debugln(); 88 | newsapiClient.flush(); 89 | newsapiClient.stop(); 90 | for(int i=0; i<=settingNewsMaxMsg; i++) 91 | { 92 | //sprintf(newsMessage, ""); 93 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 94 | else writeFileById("NWS", i, ""); 95 | } 96 | newsapiClient.flush(); 97 | newsapiClient.stop(); 98 | return false; 99 | } 100 | Debugln(" OK!"); 101 | } 102 | else 103 | { 104 | DebugTln("Error reading newsapi.org.. -> bailout!"); 105 | for(int i=0; i<=settingNewsMaxMsg; i++) 106 | { 107 | //sprintf(newsMessage, ""); 108 | if (i==1) writeFileById("NWS", i, "There is No News ...."); 109 | else writeFileById("NWS", i, ""); 110 | } 111 | newsapiClient.flush(); 112 | newsapiClient.stop(); 113 | return false; 114 | } 115 | //--- skip headers 116 | uint16_t msgIdx = 0; 117 | int msgNr = 0; 118 | while (newsapiClient.find("\"title\":\"")) 119 | { 120 | for (int i=0; i 30) newsMessage[msgIdx - 16] = 0; 128 | Debugf("\t[%2d] %s\r\n", msgNr, newsMessage); 129 | if (!hasNoNoWord(newsMessage) && strlen(newsMessage) > 15) 130 | { 131 | if (msgNr <= settingNewsMaxMsg) 132 | { 133 | writeFileById("NWS", msgNr, newsMessage); 134 | } 135 | msgNr++; 136 | } 137 | } // while find(title) 138 | } // while available .. 139 | 140 | } // connected .. 141 | 142 | newsapiClient.flush(); 143 | newsapiClient.stop(); 144 | updateMessage("0", "News brought to you by 'newsapi.org'"); 145 | 146 | return true; 147 | 148 | } // getNewsapiData() 149 | 150 | 151 | //---------------------------------------------------------------------- 152 | void removeNewsData() 153 | { 154 | char nwsName[15]; 155 | 156 | for(int n=0; n<=settingNewsMaxMsg; n++) 157 | { 158 | sprintf(nwsName, "/newsFiles/NWS-%03d", n); 159 | DebugTf("Remove [%s] from LittleFS ..\r\n", nwsName); 160 | LittleFS.remove(nwsName); 161 | } 162 | 163 | } // removeNewsData() 164 | 165 | /*************************************************************************** 166 | * 167 | * Permission is hereby granted, free of charge, to any person obtaining a 168 | * copy of this software and associated documentation files (the 169 | * "Software"), to deal in the Software without restriction, including 170 | * without limitation the rights to use, copy, modify, merge, publish, 171 | * distribute, sublicense, and/or sell copies of the Software, and to permit 172 | * persons to whom the Software is furnished to do so, subject to the 173 | * following conditions: 174 | * 175 | * The above copyright notice and this permission notice shall be included 176 | * in all copies or substantial portions of the Software. 177 | * 178 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 179 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 180 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 181 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 182 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 183 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 184 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 185 | * 186 | **************************************************************************** 187 | */ 188 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/parola_Fonts_data.h: -------------------------------------------------------------------------------- 1 | // Data file for UTF-8 example user defined fonts 2 | #pragma once 3 | 4 | MD_MAX72XX::fontType_t ExtASCII[] PROGMEM = 5 | { 6 | 0, // 0 - 'Unused' 7 | 0, // 1 - 'Unused' 8 | 0, // 2 - 'Unused' 9 | 0, // 3 - 'Unused' 10 | 0, // 4 - 'Unused' 11 | 0, // 5 - 'Unused' 12 | 0, // 6 - 'Unused' 13 | 0, // 7 - 'Unused' 14 | 0, // 8 - 'Unused' 15 | 0, // 9 - 'Unused' 16 | 0, // 10 - 'Unused' 17 | 0, // 11 - 'Unused' 18 | 0, // 12 - 'Unused' 19 | 0, // 13 - 'Unused' 20 | 0, // 14 - 'Unused' 21 | 0, // 15 - 'Unused' 22 | 0, // 16 - 'Unused' 23 | 0, // 17 - 'Unused' 24 | 0, // 18 - 'Unused' 25 | 0, // 19 - 'Unused' 26 | 0, // 20 - 'Unused' 27 | 0, // 21 - 'Unused' 28 | 0, // 22 - 'Unused' 29 | 0, // 23 - 'Unused' 30 | 0, // 24 - 'Unused' 31 | 0, // 25 - 'Unused' 32 | 0, // 26 - 'Unused' 33 | 0, // 27 - 'Unused' 34 | 0, // 28 - 'Unused' 35 | 0, // 29 - 'Unused' 36 | 0, // 30 - 'Unused' 37 | 0, // 31 - 'Unused' 38 | 2, 0, 0, // 32 - 'Space' 39 | 1, 95, // 33 - '!' 40 | 3, 7, 0, 7, // 34 - '"' 41 | 5, 20, 127, 20, 127, 20, // 35 - '#' 42 | 5, 36, 42, 127, 42, 18, // 36 - '$' 43 | 5, 35, 19, 8, 100, 98, // 37 - '%' 44 | 5, 54, 73, 86, 32, 80, // 38 - '&' 45 | 2, 4, 3, // 39 46 | 3, 28, 34, 65, // 40 - '(' 47 | 3, 65, 34, 28, // 41 - ')' 48 | 5, 42, 28, 127, 28, 42, // 42 - '*' 49 | 5, 8, 8, 62, 8, 8, // 43 - '+' 50 | 2, 128, 96, // 44 - ',' 51 | 5, 8, 8, 8, 8, 8, // 45 - '-' 52 | 2, 96, 96, // 46 - '.' 53 | 5, 32, 16, 8, 4, 2, // 47 - '/' 54 | 5, 62, 81, 73, 69, 62, // 48 - '0' 55 | 3, 66, 127, 64, // 49 - '1' 56 | 5, 114, 73, 73, 73, 70, // 50 - '2' 57 | 5, 33, 65, 73, 77, 51, // 51 - '3' 58 | 5, 24, 20, 18, 127, 16, // 52 - '4' 59 | 5, 39, 69, 69, 69, 57, // 53 - '5' 60 | 5, 60, 74, 73, 73, 49, // 54 - '6' 61 | 5, 65, 33, 17, 9, 7, // 55 - '7' 62 | 5, 54, 73, 73, 73, 54, // 56 - '8' 63 | 5, 70, 73, 73, 41, 30, // 57 - '9' 64 | 1, 20, // 58 - ':' 65 | 2, 128, 104, // 59 - ';' 66 | 4, 8, 20, 34, 65, // 60 - '<' 67 | 5, 20, 20, 20, 20, 20, // 61 - '=' 68 | 4, 65, 34, 20, 8, // 62 - '>' 69 | 5, 2, 1, 89, 9, 6, // 63 - '?' 70 | 5, 62, 65, 93, 89, 78, // 64 - '@' 71 | 5, 124, 18, 17, 18, 124, // 65 - 'A' 72 | 5, 127, 73, 73, 73, 54, // 66 - 'B' 73 | 5, 62, 65, 65, 65, 34, // 67 - 'C' 74 | 5, 127, 65, 65, 65, 62, // 68 - 'D' 75 | 5, 127, 73, 73, 73, 65, // 69 - 'E' 76 | 5, 127, 9, 9, 9, 1, // 70 - 'F' 77 | 5, 62, 65, 65, 81, 115, // 71 - 'G' 78 | 5, 127, 8, 8, 8, 127, // 72 - 'H' 79 | 3, 65, 127, 65, // 73 - 'I' 80 | 5, 32, 64, 65, 63, 1, // 74 - 'J' 81 | 5, 127, 8, 20, 34, 65, // 75 - 'K' 82 | 5, 127, 64, 64, 64, 64, // 76 - 'L' 83 | 5, 127, 2, 28, 2, 127, // 77 - 'M' 84 | 5, 127, 4, 8, 16, 127, // 78 - 'N' 85 | 5, 62, 65, 65, 65, 62, // 79 - 'O' 86 | 5, 127, 9, 9, 9, 6, // 80 - 'P' 87 | 5, 62, 65, 81, 33, 94, // 81 - 'Q' 88 | 5, 127, 9, 25, 41, 70, // 82 - 'R' 89 | 5, 38, 73, 73, 73, 50, // 83 - 'S' 90 | 5, 3, 1, 127, 1, 3, // 84 - 'T' 91 | 5, 63, 64, 64, 64, 63, // 85 - 'U' 92 | 5, 31, 32, 64, 32, 31, // 86 - 'V' 93 | 5, 63, 64, 56, 64, 63, // 87 - 'W' 94 | 5, 99, 20, 8, 20, 99, // 88 - 'X' 95 | 5, 3, 4, 120, 4, 3, // 89 - 'Y' 96 | 5, 97, 89, 73, 77, 67, // 90 - 'Z' 97 | 3, 127, 65, 65, // 91 - '[' 98 | 5, 2, 4, 8, 16, 32, // 92 - '\' 99 | 3, 65, 65, 127, // 93 - ']' 100 | 5, 4, 2, 1, 2, 4, // 94 - '^' 101 | 5, 64, 64, 64, 64, 64, // 95 - '_' 102 | 2, 3, 4, // 96 - '`' 103 | 5, 32, 84, 84, 120, 64, // 97 - 'a' 104 | 5, 127, 40, 68, 68, 56, // 98 - 'b' 105 | 5, 56, 68, 68, 68, 40, // 99 - 'c' 106 | 5, 56, 68, 68, 40, 127, // 100 - 'd' 107 | 5, 56, 84, 84, 84, 24, // 101 - 'e' 108 | 4, 8, 126, 9, 2, // 102 - 'f' 109 | 5, 24, 164, 164, 156, 120, // 103 - 'g' 110 | 5, 127, 8, 4, 4, 120, // 104 - 'h' 111 | 3, 68, 125, 64, // 105 - 'i' 112 | 4, 64, 128, 128, 122, // 106 - 'j' 113 | 4, 127, 16, 40, 68, // 107 - 'k' 114 | 3, 65, 127, 64, // 108 - 'l' 115 | 5, 124, 4, 120, 4, 120, // 109 - 'm' 116 | 5, 124, 8, 4, 4, 120, // 110 - 'n' 117 | 5, 56, 68, 68, 68, 56, // 111 - 'o' 118 | 5, 252, 24, 36, 36, 24, // 112 - 'p' 119 | 5, 24, 36, 36, 24, 252, // 113 - 'q' 120 | 5, 124, 8, 4, 4, 8, // 114 - 'r' 121 | 5, 72, 84, 84, 84, 36, // 115 - 's' 122 | 4, 4, 63, 68, 36, // 116 - 't' 123 | 5, 60, 64, 64, 32, 124, // 117 - 'u' 124 | 5, 28, 32, 64, 32, 28, // 118 - 'v' 125 | 5, 60, 64, 48, 64, 60, // 119 - 'w' 126 | 5, 68, 40, 16, 40, 68, // 120 - 'x' 127 | 5, 76, 144, 144, 144, 124, // 121 - 'y' 128 | 5, 68, 100, 84, 76, 68, // 122 - 'z' 129 | 3, 8, 54, 65, // 123 - '{' 130 | 1, 119, // 124 - '|' 131 | 3, 65, 54, 8, // 125 - '}' 132 | 5, 2, 1, 2, 4, 2, // 126 - '~' 133 | 0, // 127 - 'Unused' 134 | 6, 20, 62, 85, 85, 65, 34, // 128 - 'Euro sign' 135 | 0, // 129 - 'Not used' 136 | 2, 128, 96, // 130 - 'Single low 9 quotation mark' 137 | 5, 192, 136, 126, 9, 3, // 131 - 'f with hook' 138 | 4, 128, 96, 128, 96, // 132 - 'Single low 9 quotation mark' 139 | 8, 96, 96, 0, 96, 96, 0, 96, 96, // 133 - 'Horizontal ellipsis' 140 | 3, 4, 126, 4, // 134 - 'Dagger' 141 | 3, 20, 126, 20, // 135 - 'Double dagger' 142 | 4, 2, 1, 1, 2, // 136 - 'Modifier circumflex' 143 | 7, 35, 19, 104, 100, 2, 97, 96, // 137 - 'Per mille sign' 144 | 5, 72, 85, 86, 85, 36, // 138 - 'S with caron' 145 | 3, 8, 20, 34, // 139 - '< quotation' 146 | 6, 62, 65, 65, 127, 73, 73, // 140 - 'OE' 147 | 0, // 141 - 'Not used' 148 | 5, 68, 101, 86, 77, 68, // 142 - 'z with caron' 149 | 0, // 143 - 'Not used' 150 | 0, // 144 - 'Not used' 151 | 2, 3, 4, // 145 - 'Left single quote mark' 152 | 2, 4, 3, // 146 - 'Right single quote mark' 153 | 4, 3, 4, 3, 4, // 147 - 'Left double quote marks' 154 | 4, 4, 3, 4, 3, // 148 - 'Right double quote marks' 155 | 4, 0, 24, 60, 24, // 149 - 'Bullet Point' 156 | 3, 8, 8, 8, // 150 - 'En dash' 157 | 5, 8, 8, 8, 8, 8, // 151 - 'Em dash' 158 | 4, 2, 1, 2, 1, // 152 - 'Small ~' 159 | 7, 1, 15, 1, 0, 15, 2, 15, // 153 - 'TM' 160 | 5, 72, 85, 86, 85, 36, // 154 - 's with caron' 161 | 3, 34, 20, 8, // 155 - '> quotation' 162 | 7, 56, 68, 68, 124, 84, 84, 8, // 156 - 'oe' 163 | 0, // 157 - 'Not used' 164 | 5, 68, 101, 86, 77, 68, // 158 - 'z with caron' 165 | 5, 12, 17, 96, 17, 12, // 159 - 'Y diaresis' 166 | 2, 0, 0, // 160 - 'Non-breaking space' 167 | 1, 125, // 161 - 'Inverted !' 168 | 5, 60, 36, 126, 36, 36, // 162 - 'Cent sign' 169 | 5, 72, 126, 73, 65, 102, // 163 - 'Pound sign' 170 | 5, 34, 28, 20, 28, 34, // 164 - 'Currency sign' 171 | 5, 43, 47, 252, 47, 43, // 165 - 'Yen' 172 | 1, 119, // 166 - '|' 173 | 4, 102, 137, 149, 106, // 167 - 'Section sign' 174 | 3, 1, 0, 1, // 168 - 'Spacing diaresis' 175 | 7, 62, 65, 93, 85, 85, 65, 62, // 169 - 'Copyright' 176 | 3, 13, 13, 15, // 170 - 'Feminine Ordinal Ind.' 177 | 5, 8, 20, 42, 20, 34, // 171 - '<<' 178 | 5, 8, 8, 8, 8, 56, // 172 - 'Not sign' 179 | 0, // 173 - 'Soft Hyphen' 180 | 7, 62, 65, 127, 75, 117, 65, 62, // 174 - 'Registered Trademark' 181 | 5, 1, 1, 1, 1, 1, // 175 - 'Spacing Macron Overline' 182 | 3, 2, 5, 2, // 176 - 'Degree' 183 | 5, 68, 68, 95, 68, 68, // 177 - '+/-' 184 | 3, 25, 21, 19, // 178 - 'Superscript 2' 185 | 3, 17, 21, 31, // 179 - 'Superscript 3' 186 | 2, 2, 1, // 180 - 'Acute accent' 187 | 4, 252, 64, 64, 60, // 181 - 'micro (mu)' 188 | 5, 6, 9, 127, 1, 127, // 182 - 'Paragraph Mark' 189 | 2, 24, 24, // 183 - 'Middle Dot' 190 | 3, 128, 128, 96, // 184 - 'Spacing sedilla' 191 | 2, 2, 31, // 185 - 'Superscript 1' 192 | 4, 6, 9, 9, 6, // 186 - 'Masculine Ordinal Ind.' 193 | 5, 34, 20, 42, 20, 8, // 187 - '>>' 194 | 6, 64, 47, 16, 40, 52, 250, // 188 - '1/4' 195 | 6, 64, 47, 16, 200, 172, 186, // 189 - '1/2' 196 | 6, 85, 53, 31, 40, 52, 250, // 190 - '3/4' 197 | 5, 48, 72, 77, 64, 32, // 191 - 'Inverted ?' 198 | 5, 120, 20, 21, 22, 120, // 192 - 'A grave' 199 | 5, 120, 22, 21, 20, 120, // 193 - 'A acute' 200 | 5, 122, 21, 21, 21, 122, // 194 - 'A circumflex' 201 | 5, 120, 22, 21, 22, 121, // 195 - 'A tilde' 202 | 5, 120, 21, 20, 21, 120, // 196 - 'A diaresis' 203 | 5, 120, 20, 21, 20, 120, // 197 - 'A ring above' 204 | 6, 124, 10, 9, 127, 73, 73, // 198 - 'AE' 205 | 5, 30, 161, 161, 97, 18, // 199 - 'C sedilla' 206 | 4, 124, 85, 86, 68, // 200 - 'E grave' 207 | 4, 124, 86, 85, 68, // 201 - 'E acute' 208 | 4, 126, 85, 85, 70, // 202 - 'E circumflex' 209 | 4, 124, 85, 84, 69, // 203 - 'E diaresis' 210 | 3, 68, 125, 70, // 204 - 'I grave' 211 | 3, 68, 126, 69, // 205 - 'I acute' 212 | 3, 70, 125, 70, // 206 - 'I circumplex' 213 | 3, 69, 124, 69, // 207 - 'I diaresis' 214 | 6, 4, 127, 69, 65, 65, 62, // 208 - 'Capital Eth' 215 | 5, 124, 10, 17, 34, 125, // 209 - 'N tilde' 216 | 5, 56, 68, 69, 70, 56, // 210 - 'O grave' 217 | 5, 56, 70, 69, 68, 56, // 211 - 'O acute' 218 | 5, 58, 69, 69, 69, 58, // 212 - 'O circumflex' 219 | 5, 56, 70, 69, 70, 57, // 213 - 'O tilde' 220 | 5, 56, 69, 68, 69, 56, // 214 - 'O diaresis' 221 | 5, 34, 20, 8, 20, 34, // 215 - 'Multiplication sign' 222 | 7, 64, 62, 81, 73, 69, 62, 1, // 216 - 'O slashed' 223 | 5, 60, 65, 66, 64, 60, // 217 - 'U grave' 224 | 5, 60, 64, 66, 65, 60, // 218 - 'U acute' 225 | 5, 58, 65, 65, 65, 58, // 219 - 'U circumflex' 226 | 5, 60, 65, 64, 65, 60, // 220 - 'U diaresis' 227 | 5, 12, 16, 98, 17, 12, // 221 - 'Y acute' 228 | 4, 127, 18, 18, 12, // 222 - 'Capital thorn' 229 | 4, 254, 37, 37, 26, // 223 - 'Small letter sharp S' 230 | 5, 32, 84, 85, 122, 64, // 224 - 'a grave' 231 | 5, 32, 84, 86, 121, 64, // 225 - 'a acute' 232 | 5, 34, 85, 85, 121, 66, // 226 - 'a circumflex' 233 | 5, 32, 86, 85, 122, 65, // 227 - 'a tilde' 234 | 5, 32, 85, 84, 121, 64, // 228 - 'a diaresis' 235 | 5, 32, 84, 85, 120, 64, // 229 - 'a ring above' 236 | 7, 32, 84, 84, 124, 84, 84, 8, // 230 - 'ae' 237 | 5, 24, 36, 164, 228, 40, // 231 - 'c sedilla' 238 | 5, 56, 84, 85, 86, 88, // 232 - 'e grave' 239 | 5, 56, 84, 86, 85, 88, // 233 - 'e acute' 240 | 5, 58, 85, 85, 85, 90, // 234 - 'e circumflex' 241 | 5, 56, 85, 84, 85, 88, // 235 - 'e diaresis' 242 | 3, 68, 125, 66, // 236 - 'i grave' 243 | 3, 68, 126, 65, // 237 - 'i acute' 244 | 3, 70, 125, 66, // 238 - 'i circumflex' 245 | 3, 69, 124, 65, // 239 - 'i diaresis' 246 | 4, 48, 75, 74, 61, // 240 - 'Small eth' 247 | 4, 122, 9, 10, 113, // 241 - 'n tilde' 248 | 5, 56, 68, 69, 70, 56, // 242 - 'o grave' 249 | 5, 56, 70, 69, 68, 56, // 243 - 'o acute' 250 | 5, 58, 69, 69, 69, 58, // 244 - 'o circumflex' 251 | 5, 56, 70, 69, 70, 57, // 245 - 'o tilde' 252 | 5, 56, 69, 68, 69, 56, // 246 - 'o diaresis' 253 | 5, 8, 8, 42, 8, 8, // 247 - 'Division sign' 254 | 6, 64, 56, 84, 76, 68, 58, // 248 - 'o slashed' 255 | 5, 60, 65, 66, 32, 124, // 249 - 'u grave' 256 | 5, 60, 64, 66, 33, 124, // 250 - 'u acute' 257 | 5, 58, 65, 65, 33, 122, // 251 - 'u circumflex' 258 | 5, 60, 65, 64, 33, 124, // 252 - 'u diaresis' 259 | 4, 156, 162, 161, 124, // 253 - 'y acute' 260 | 4, 252, 72, 72, 48, // 254 - 'small thorn' 261 | 4, 157, 160, 160, 125, // 255 - 'y diaresis' 262 | }; 263 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/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 | [platformio] 12 | workspace_dir = .pio.nosync 13 | default_envs = esp12eDev 14 | 15 | [env:esp12eDev] 16 | platform = espressif8266 17 | board = esp12e 18 | framework = arduino 19 | board_build.filesystem = littlefs 20 | monitor_speed = 115200 21 | upload_speed = 115200 22 | #--- upload_port only needed for FileSys upload 23 | upload_port = /dev/cu.usbserial-3224144 24 | build_flags = -DDEBUG 25 | lib_ldf_mode = deep+ 26 | lib_deps = 27 | bblanchon/ArduinoJson@6.19.4 28 | https://github.com/PaulStoffregen/Time 29 | jandrassy/TelnetStream@^1.3.0 30 | majicdesigns/MD_Parola@^3.7.3 31 | tzapu/WiFiManager@^0.16.0 32 | https://github.com/mrWheel/ModUpdateServer 33 | 34 | monitor_filters = 35 | esp8266_exception_decoder 36 | 37 | [env:esp12eProd] 38 | platform = espressif8266 39 | board = esp12e 40 | framework = arduino 41 | board_build.filesystem = littlefs 42 | monitor_speed = 115200 43 | upload_speed = 115200 44 | #--- upload_port only needed for FileSys upload 45 | upload_port = /dev/cu.usbserial-3224144 46 | lib_ldf_mode = deep+ 47 | lib_deps = 48 | bblanchon/ArduinoJson@6.19.4 49 | bblanchon/StreamUtils@^1.9.0 50 | https://github.com/PaulStoffregen/Time 51 | jandrassy/TelnetStream@0.1.0 52 | majicdesigns/MD_Parola@^3.7.3 53 | tzapu/WiFiManager@^0.16.0 54 | https://github.com/mrWheel/ModUpdateServer 55 | 56 | monitor_filters = 57 | esp8266_exception_decoder 58 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/restAPI.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : restAPI, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | //======================================================================= 15 | void processAPI() 16 | { 17 | char fName[40] = ""; 18 | char URI[50] = ""; 19 | String words[10]; 20 | 21 | strncpy( URI, httpServer.uri().c_str(), sizeof(URI) ); 22 | 23 | if (httpServer.method() == HTTP_GET) 24 | DebugTf("from[%s] URI[%s] method[GET] \r\n" 25 | , httpServer.client().remoteIP().toString().c_str() 26 | , URI); 27 | else DebugTf("from[%s] URI[%s] method[PUT] \r\n" 28 | , httpServer.client().remoteIP().toString().c_str() 29 | , URI); 30 | 31 | if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! 32 | { 33 | DebugTf("==> Bailout due to low heap (%d bytes))\r\n", ESP.getFreeHeap() ); 34 | httpServer.send(500, "text/plain", "500: internal server error (low heap)\r\n"); 35 | return; 36 | } 37 | 38 | int8_t wc = splitString(URI, '/', words, 10); 39 | 40 | if (Verbose) 41 | { 42 | DebugT(">>"); 43 | for (int w=0; w [%s], ", w, words[w].c_str()); 46 | } 47 | Debugln(" "); 48 | } 49 | 50 | if (words[1] != "api") 51 | { 52 | sendApiNotFound(URI); 53 | return; 54 | } 55 | 56 | if (words[2] != "v0") 57 | { 58 | sendApiNotFound(URI); 59 | return; 60 | } 61 | 62 | if (words[3] == "devinfo") 63 | { 64 | sendDeviceInfo(); 65 | } 66 | else if (words[3] == "devtime") 67 | { 68 | sendDeviceTime(); 69 | } 70 | else if (words[3] == "settings") 71 | { 72 | if (httpServer.method() == HTTP_PUT || httpServer.method() == HTTP_POST) 73 | { 74 | postSettings(); 75 | } 76 | else 77 | { 78 | sendDeviceSettings(); 79 | } 80 | } 81 | else if (words[3] == "messages") 82 | { 83 | if (httpServer.method() == HTTP_PUT || httpServer.method() == HTTP_POST) 84 | { 85 | postMessages(); 86 | } 87 | else 88 | { 89 | sendLocalMessages(); 90 | } 91 | } 92 | else if (words[3] == "news") 93 | { 94 | sendNewsMessages(); 95 | } 96 | else sendApiNotFound(URI); 97 | 98 | } // processAPI() 99 | 100 | 101 | //======================================================================= 102 | void sendDeviceInfo() 103 | { 104 | FSInfo LittleFSinfo; 105 | 106 | sendStartJsonObj("devinfo"); 107 | 108 | sendNestedJsonObj("author", "Willem Aandewiel (www.aandewiel.nl)"); 109 | sendNestedJsonObj("fwversion", FWversion); 110 | 111 | snprintf(cMsg, sizeof(cMsg), "%s %s", __DATE__, __TIME__); 112 | sendNestedJsonObj("compiled", cMsg); 113 | sendNestedJsonObj("hostname", settingHostname); 114 | sendNestedJsonObj("ipaddress", WiFi.localIP().toString().c_str()); 115 | sendNestedJsonObj("macaddress", WiFi.macAddress().c_str()); 116 | sendNestedJsonObj("freeheap", ESP.getFreeHeap()); 117 | sendNestedJsonObj("maxfreeblock", ESP.getMaxFreeBlockSize()); 118 | sendNestedJsonObj("chipid", String( ESP.getChipId(), HEX ).c_str()); 119 | sendNestedJsonObj("coreversion", String( ESP.getCoreVersion() ).c_str() ); 120 | sendNestedJsonObj("sdkversion", String( ESP.getSdkVersion() ).c_str()); 121 | sendNestedJsonObj("cpufreq", ESP.getCpuFreqMHz()); 122 | sendNestedJsonObj("sketchsize", formatFloat( (ESP.getSketchSize() / 1024.0), 3)); 123 | sendNestedJsonObj("freesketchspace", formatFloat( (ESP.getFreeSketchSpace() / 1024.0), 3)); 124 | 125 | snprintf(cMsg, sizeof(cMsg), "%08X", ESP.getFlashChipId()); 126 | sendNestedJsonObj("flashchipid", cMsg); // flashChipId 127 | sendNestedJsonObj("flashchipsize", formatFloat((ESP.getFlashChipSize() / 1024.0 / 1024.0), 3)); 128 | sendNestedJsonObj("flashchiprealsize", formatFloat((ESP.getFlashChipRealSize() / 1024.0 / 1024.0), 3)); 129 | 130 | LittleFS.info(LittleFSinfo); 131 | sendNestedJsonObj("spiffssize", formatFloat( (LittleFSinfo.totalBytes / (1024.0 * 1024.0)), 0)); 132 | 133 | sendNestedJsonObj("flashchipspeed", formatFloat((ESP.getFlashChipSpeed() / 1000.0 / 1000.0), 0)); 134 | 135 | FlashMode_t ideMode = ESP.getFlashChipMode(); 136 | sendNestedJsonObj("flashchipmode", flashMode[ideMode]); 137 | sendNestedJsonObj("boardtype", 138 | #ifdef ARDUINO_ESP8266_NODEMCU 139 | "ESP8266_NODEMCU" 140 | #endif 141 | #ifdef ARDUINO_ESP8266_GENERIC 142 | "ESP8266_GENERIC" 143 | #endif 144 | #ifdef ESP8266_ESP01 145 | "ESP8266_ESP01" 146 | #endif 147 | #ifdef ESP8266_ESP12 148 | "ESP8266_ESP12" 149 | #endif 150 | ); 151 | sendNestedJsonObj("ssid", WiFi.SSID().c_str()); 152 | sendNestedJsonObj("wifirssi", WiFi.RSSI()); 153 | 154 | sendNestedJsonObj("lastreset", lastReset); 155 | 156 | httpServer.sendContent("\r\n]}\r\n"); 157 | 158 | } // sendDeviceInfo() 159 | 160 | 161 | //======================================================================= 162 | void sendDeviceTime() 163 | { 164 | struct tm* timeinfo; 165 | char actTime[50]; 166 | 167 | sendStartJsonObj("devtime"); 168 | // Get current time 169 | time(&now); 170 | timeinfo = localtime(&now); // This is now correct 171 | 172 | snprintf(actTime, 49, "%02d-%02d-%04d %02d:%02d" 173 | , timeinfo->tm_mday, timeinfo->tm_mon+1, timeinfo->tm_year+1900 174 | , timeinfo->tm_hour, timeinfo->tm_min); 175 | sendNestedJsonObj("dateTime", actTime); 176 | sendNestedJsonObj("epoch", (uint32_t)now); 177 | 178 | sendEndJsonObj(); 179 | 180 | } // sendDeviceTime() 181 | 182 | 183 | //======================================================================= 184 | void sendDeviceSettings() 185 | { 186 | DebugTln("sending device settings ...\r"); 187 | 188 | sendStartJsonObj("settings"); 189 | 190 | sendJsonSettingObj("hostname", settingHostname, "s", sizeof(settingHostname) -1); 191 | sendJsonSettingObj("localMaxMsg", settingLocalMaxMsg, "i", 1, 20); 192 | sendJsonSettingObj("textSpeed", settingTextSpeed, "i", 10, MAX_SPEED); 193 | sendJsonSettingObj("LDRlowOffset", settingLDRlowOffset, "i", 0, 500); 194 | sendJsonSettingObj("LDRhighOffset", settingLDRhighOffset, "i", 500, 1024); 195 | sendJsonSettingObj("maxIntensity", settingMaxIntensity, "i", 0, 15); 196 | sendJsonSettingObj("weerliveAUTH", settingWeerLiveAUTH, "s", sizeof(settingWeerLiveAUTH) -1); 197 | sendJsonSettingObj("weerliveLocation", settingWeerLiveLocation, "s", sizeof(settingWeerLiveLocation) -1); 198 | sendJsonSettingObj("weerliveInterval", settingWeerLiveInterval, "i", 15, 120); 199 | sendJsonSettingObj("newsapiAUTH", settingNewsAUTH, "s", sizeof(settingNewsAUTH) -1); 200 | sendJsonSettingObj("newsapiMaxMsg", settingNewsMaxMsg, "i", 1, 20); 201 | sendJsonSettingObj("newsapiInterval", settingNewsInterval, "i", 15, 120); 202 | sendJsonSettingObj("newsNoWords", settingNewsNoWords, "s", sizeof(settingNewsNoWords) -1); 203 | 204 | sendEndJsonObj(); 205 | 206 | } // sendDeviceSettings() 207 | 208 | 209 | //======================================================================= 210 | void sendLocalMessages() 211 | { 212 | int mID; 213 | 214 | DebugTln("sending local Messages ...\r"); 215 | 216 | sendStartJsonObj("messages"); 217 | 218 | for (mID=1; mID <= settingLocalMaxMsg; mID++) 219 | { 220 | if (readFileById("LCL", mID)) 221 | { 222 | //--- next 5 lines are realy dirty ... 223 | char newMsg[LOCAL_SIZE] = ""; 224 | String tmp = String(fileMessage); 225 | tmp.replace("\\", "\\\\"); 226 | sprintf(newMsg, "%s", tmp.c_str()); 227 | //sendJsonSettingObj(intToStr(mID), fileMessage, "s", sizeof(fileMessage) -1); 228 | sendJsonSettingObj(intToStr(mID), newMsg, "s", sizeof(newMsg) -1); 229 | } 230 | else 231 | { 232 | sendJsonSettingObj(intToStr(mID), "", "s", sizeof(fileMessage) -1); 233 | } 234 | } 235 | 236 | sendEndJsonObj(); 237 | 238 | } // sendlocalMessages() 239 | 240 | 241 | //======================================================================= 242 | void sendNewsMessages() 243 | { 244 | int nID; 245 | 246 | DebugTln("sending news Messages ...\r"); 247 | 248 | sendStartJsonObj("newsapi"); 249 | 250 | for (nID=0; nID <= settingNewsMaxMsg; nID++) 251 | { 252 | if (readFileById("NWS", nID)) 253 | { 254 | sendJsonSettingObj(intToStr(nID), fileMessage, "s", sizeof(fileMessage) -1); 255 | } 256 | } 257 | 258 | sendEndJsonObj(); 259 | 260 | } // sendNewsMessages() 261 | 262 | 263 | //======================================================================= 264 | void postMessages() 265 | { 266 | //------------------------------------------------------------ 267 | // json string: {"name":"4","value":"Bericht tekst"} 268 | //------------------------------------------------------------ 269 | // so, why not use ArduinoJSON library? 270 | // I say: try it yourself ;-) It won't be easy 271 | String wOut[5]; 272 | String wPair[5]; 273 | String jsonIn = httpServer.arg(0).c_str(); 274 | char field[25] = ""; 275 | char newValue[101]=""; 276 | jsonIn.replace("{", ""); 277 | jsonIn.replace("}", ""); 278 | jsonIn.replace("\"", ""); 279 | int8_t wp = splitString(jsonIn.c_str(), ',', wPair, 5) ; 280 | for (int i=0; i pair[%s]\r\n", i, wPair[i].c_str()); 283 | int8_t wc = splitString(wPair[i].c_str(), ':', wOut, 5) ; 284 | //DebugTf("==> [%s] -> field[%s]->val[%s]\r\n", wPair[i].c_str(), wOut[0].c_str(), wOut[1].c_str()); 285 | if (wOut[0].equalsIgnoreCase("name")) strCopy(field, sizeof(field), wOut[1].c_str()); 286 | if (wOut[0].equalsIgnoreCase("value")) strCopy(newValue, sizeof(newValue), wOut[1].c_str()); 287 | } 288 | DebugTf("--> field[%s] => newValue[%s]\r\n", field, newValue); 289 | updateMessage(field, newValue); 290 | httpServer.send(200, "application/json", httpServer.arg(0)); 291 | 292 | } // postMessages() 293 | 294 | 295 | //======================================================================= 296 | void postSettings() 297 | { 298 | //------------------------------------------------------------ 299 | // json string: {"name":"settingInterval","value":9} 300 | // json string: {"name":"settingHostname","value":"abc"} 301 | //------------------------------------------------------------ 302 | // so, why not use ArduinoJSON library? 303 | // I say: try it yourself ;-) It won't be easy 304 | String wOut[5]; 305 | String wPair[5]; 306 | String jsonIn = httpServer.arg(0).c_str(); 307 | char field[25] = ""; 308 | char newValue[101]=""; 309 | jsonIn.replace("{", ""); 310 | jsonIn.replace("}", ""); 311 | jsonIn.replace("\"", ""); 312 | int8_t wp = splitString(jsonIn.c_str(), ',', wPair, 5) ; 313 | for (int i=0; i pair[%s]\r\n", i, wPair[i].c_str()); 316 | int8_t wc = splitString(wPair[i].c_str(), ':', wOut, 5) ; 317 | //DebugTf("==> [%s] -> field[%s]->val[%s]\r\n", wPair[i].c_str(), wOut[0].c_str(), wOut[1].c_str()); 318 | if (wOut[0].equalsIgnoreCase("name")) strCopy(field, sizeof(field), wOut[1].c_str()); 319 | if (wOut[0].equalsIgnoreCase("value")) strCopy(newValue, sizeof(newValue), wOut[1].c_str()); 320 | } 321 | DebugTf("--> field[%s] => newValue[%s]\r\n", field, newValue); 322 | updateSetting(field, newValue); 323 | httpServer.send(200, "application/json", httpServer.arg(0)); 324 | 325 | } // postSettings() 326 | 327 | 328 | //==================================================== 329 | void sendApiNotFound(const char *URI) 330 | { 331 | httpServer.sendHeader("Access-Control-Allow-Origin", "*"); 332 | httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); 333 | httpServer.send ( 404, "text/html", ""); 334 | 335 | strCopy(cMsg, sizeof(cMsg), ""); 337 | httpServer.sendContent(cMsg); 338 | 339 | strCopy(cMsg, sizeof(cMsg), "

ESP - lichtKrant

"); 340 | httpServer.sendContent(cMsg); 341 | 342 | strCopy(cMsg, sizeof(cMsg), "
["); 343 | strConcat(cMsg, sizeof(cMsg), URI); 344 | strConcat(cMsg, sizeof(cMsg), "] is not a valid "); 345 | httpServer.sendContent(cMsg); 346 | 347 | strCopy(cMsg, sizeof(cMsg), "\r\n"); 348 | httpServer.sendContent(cMsg); 349 | 350 | } // sendApiNotFound() 351 | 352 | 353 | /*************************************************************************** 354 | * 355 | * Permission is hereby granted, free of charge, to any person obtaining a 356 | * copy of this software and associated documentation files (the 357 | * "Software"), to deal in the Software without restriction, including 358 | * without limitation the rights to use, copy, modify, merge, publish, 359 | * distribute, sublicense, and/or sell copies of the Software, and to permit 360 | * persons to whom the Software is furnished to do so, subject to the 361 | * following conditions: 362 | * 363 | * The above copyright notice and this permission notice shall be included 364 | * in all copies or substantial portions of the Software. 365 | * 366 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 367 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 368 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 369 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 370 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 371 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 372 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 373 | * 374 | **************************************************************************** 375 | */ 376 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/sendIndexPage.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : sendIndexPage 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | static const char indexPage[] = 12 | R"( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ESP Lichtkrant 21 | 22 | 23 | 24 | 25 |
26 |

27 | ESP - lichtKrant       28 | -   29 | [version] 30 | 00:00 31 |

32 |
33 |
34 | 49 | 50 | 62 | 63 | 64 | 65 | 66 |
2021 © Willem Aandewiel
67 | 68 | 69 |
-
70 | 71 | 74 | 75 | 76 | 77 | 78 | )"; 79 | 80 | void sendIndexPage() 81 | { 82 | httpServer.send(200, "text/html", indexPage); 83 | 84 | } // sendIndexPage() 85 | 86 | 87 | 88 | /*************************************************************************** 89 | * 90 | * Permission is hereby granted, free of charge, to any person obtaining a 91 | * copy of this software and associated documentation files (the 92 | * "Software"), to deal in the Software without restriction, including 93 | * without limitation the rights to use, copy, modify, merge, publish, 94 | * distribute, sublicense, and/or sell copies of the Software, and to permit 95 | * persons to whom the Software is furnished to do so, subject to the 96 | * following conditions: 97 | * 98 | * The above copyright notice and this permission notice shall be included 99 | * in all copies or substantial portions of the Software. 100 | * 101 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 102 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 103 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 104 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 105 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 106 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 107 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 108 | * 109 | **************************************************************************** 110 | */ 111 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/settingStuff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : settingsStuff, part of ESP_ticker 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | //======================================================================= 12 | void writeSettings(bool show) 13 | { 14 | DebugTf("Writing to [%s] ..\r\n", SETTINGS_FILE); 15 | File file = LittleFS.open(SETTINGS_FILE, "w"); // open for reading and writing 16 | if (!file) 17 | { 18 | DebugTf("open(%s, 'w') FAILED!!! --> Bailout\r\n", SETTINGS_FILE); 19 | return; 20 | } 21 | yield(); 22 | 23 | DebugT(F("Start writing setting data ")); 24 | 25 | file.print("Hostname = "); file.println(settingHostname); Debug(F(".")); 26 | file.print("localMaxMsg = "); file.println(settingLocalMaxMsg); Debug(F(".")); 27 | file.print("textSpeed = "); file.println(settingTextSpeed); Debug(F(".")); 28 | file.print("maxIntensity = "); file.println(settingMaxIntensity); Debug(F(".")); 29 | file.print("LDRlowOffset = "); file.println(settingLDRlowOffset); Debug(F(".")); 30 | file.print("LDRhighOffset = "); file.println(settingLDRhighOffset); Debug(F(".")); 31 | file.print("weerLiveAUTH = "); file.println(settingWeerLiveAUTH); Debug(F(".")); 32 | file.print("weerLiveLocatie = "); file.println(settingWeerLiveLocation); Debug(F(".")); 33 | file.print("weerLiveInterval = "); file.println(settingWeerLiveInterval); Debug(F(".")); 34 | file.print("newsAUTH = "); file.println(settingNewsAUTH); Debug(F(".")); 35 | file.print("newsNoWords = "); file.println(settingNewsNoWords); Debug(F(".")); 36 | file.print("newsMaxMsg = "); file.println(settingNewsMaxMsg); Debug(F(".")); 37 | file.print("newsInterval = "); file.println(settingNewsInterval); Debug(F(".")); 38 | 39 | file.close(); 40 | 41 | Debugln(F(" done")); 42 | 43 | if (show) 44 | { 45 | DebugTln(F("Wrote this:")); 46 | DebugT(F(" Hostname = ")); Debugln(settingHostname); 47 | DebugT(F(" newsNoWords = ")); Debugln(settingNewsNoWords); 48 | DebugT(F(" localMaxMsg = ")); Debugln(settingLocalMaxMsg); 49 | DebugT(F(" textSpeed = ")); Debugln(settingTextSpeed); 50 | DebugT(F(" LDRlowOffset = ")); Debugln(settingLDRlowOffset); 51 | DebugT(F(" LDRhighOffset = ")); Debugln(settingLDRhighOffset); 52 | DebugT(F(" maxIntensity = ")); Debugln(settingMaxIntensity); 53 | DebugT(F(" weerLiveAUTH = ")); Debugln(settingWeerLiveAUTH); 54 | DebugT(F(" weerLiveLocatie = ")); Debugln(settingWeerLiveLocation); 55 | DebugT(F("weerLiveInterval = ")); Debugln(settingWeerLiveInterval); 56 | DebugT(F(" newsAUTH = ")); Debugln(settingNewsAUTH); 57 | DebugT(F(" newsMaxMsg = ")); Debugln(settingNewsMaxMsg); 58 | DebugT(F(" newsInterval = ")); Debugln(settingNewsInterval); 59 | 60 | } // Verbose 61 | 62 | } // writeSettings() 63 | 64 | 65 | //======================================================================= 66 | void readSettings(bool show) 67 | { 68 | String sTmp; 69 | String words[10]; 70 | char cTmp[LOCAL_SIZE], cVal[101], cKey[101]; 71 | 72 | File file; 73 | 74 | DebugTf(" %s ..\r\n", SETTINGS_FILE); 75 | 76 | snprintf(settingHostname, sizeof(settingHostname), "%s", _HOSTNAME); 77 | snprintf(settingNewsNoWords, sizeof(settingNewsNoWords),"Voetbal, show, UEFA, KNVB"); 78 | settingLocalMaxMsg = 5; 79 | settingTextSpeed = 25; 80 | settingLDRlowOffset = 70; 81 | settingLDRhighOffset = 700; 82 | settingMaxIntensity = 6; 83 | snprintf(settingWeerLiveAUTH, 50, ""); 84 | snprintf(settingWeerLiveLocation, 50, ""); 85 | settingWeerLiveInterval = 0; 86 | snprintf(settingNewsAUTH, 50, ""); 87 | settingNewsMaxMsg = 4; 88 | settingNewsInterval = 0; 89 | 90 | if (!LittleFS.exists(SETTINGS_FILE)) 91 | { 92 | DebugTln(F(" .. file not found! --> created file!")); 93 | writeSettings(show); 94 | } 95 | 96 | for (int T = 0; T < 2; T++) 97 | { 98 | file = LittleFS.open(SETTINGS_FILE, "r"); 99 | if (!file) 100 | { 101 | if (T == 0) DebugTf(" .. something went wrong opening [%s]\r\n", SETTINGS_FILE); 102 | else DebugT(T); 103 | delay(100); 104 | } 105 | } // try T times .. 106 | 107 | DebugTln(F("Reading settings:\r")); 108 | while(file.available()) 109 | { 110 | sTmp = file.readStringUntil('\n'); 111 | snprintf(cTmp, sizeof(cTmp), "%s", sTmp.c_str()); 112 | //strTrim(cTmp, sizeof(cTmp), '\r'); 113 | strTrimCntr(cTmp, sizeof(cTmp)); 114 | //DebugTf("cTmp[%s] (%d)\r\n", cTmp, strlen(cTmp)); 115 | int sEq = strIndex(cTmp, "="); 116 | strCopy(cKey, 100, cTmp, 0, sEq -1); 117 | strCopy(cVal, 100, cTmp, sEq +1, strlen(cTmp)); 118 | //DebugTf("cKey[%s], cVal[%s]\r\n", cKey, cVal); 119 | strTrim(cKey, sizeof(cKey), ' '); 120 | strTrim(cVal, sizeof(cVal), ' '); 121 | //DebugTf("cKey[%s], cVal[%s]\r\n", cKey, cVal); 122 | 123 | //strToLower(cKey); 124 | if (stricmp(cKey, "hostname") == 0) strCopy(settingHostname, sizeof(settingHostname), cVal); 125 | if (stricmp(cKey, "localMaxMsg") == 0) settingLocalMaxMsg = atoi(cVal); 126 | if (stricmp(cKey, "textSpeed") == 0) settingTextSpeed = atoi(cVal); 127 | if (stricmp(cKey, "LDRlowOffset") == 0) settingLDRlowOffset = atoi(cVal); 128 | if (stricmp(cKey, "LDRhighOffset") == 0) settingLDRhighOffset = atoi(cVal); 129 | if (stricmp(cKey, "maxIntensity") == 0) settingMaxIntensity = atoi(cVal); 130 | if (stricmp(cKey, "weerLiveAUTH") == 0) strCopy(settingWeerLiveAUTH, sizeof(settingWeerLiveAUTH), cVal); 131 | if (stricmp(cKey, "weerlivelocatie") == 0) strCopy(settingWeerLiveLocation, sizeof(settingWeerLiveLocation), cVal); 132 | if (stricmp(cKey, "weerLiveInterval") == 0) settingWeerLiveInterval = atoi(cVal); 133 | if (stricmp(cKey, "newsAUTH") == 0) strCopy(settingNewsAUTH, sizeof(settingNewsAUTH), cVal); 134 | if (stricmp(cKey, "newsNoWords") == 0) strCopy(settingNewsNoWords, sizeof(settingNewsNoWords), cVal); 135 | if (stricmp(cKey, "newsMaxMsg") == 0) settingNewsMaxMsg = atoi(cVal); 136 | if (stricmp(cKey, "newsInterval") == 0) settingNewsInterval = atoi(cVal); 137 | 138 | } // while available() 139 | 140 | file.close(); 141 | 142 | //--- this will take some time to settle in 143 | //--- probably need a reboot before that to happen :-( 144 | MDNS.setHostname(settingHostname); // start advertising with new(?) settingHostname 145 | if (settingLocalMaxMsg > 20) settingLocalMaxMsg = 20; 146 | if (settingLocalMaxMsg < 1) settingLocalMaxMsg = 1; 147 | if (settingTextSpeed > MAX_SPEED) settingTextSpeed = MAX_SPEED; 148 | if (settingTextSpeed < 10) settingTextSpeed = 10; 149 | if (settingLDRlowOffset > 500) settingLDRlowOffset = 500; 150 | if (settingLDRlowOffset < 1) settingLDRlowOffset = 0; 151 | if (settingLDRhighOffset < 500) settingLDRhighOffset = 500; 152 | if (settingLDRhighOffset > 1024) settingLDRhighOffset = 1024; 153 | if (settingMaxIntensity > 15) settingMaxIntensity = 15; 154 | if (settingMaxIntensity < 1) settingMaxIntensity = 1; 155 | if (strlen(settingWeerLiveLocation) < 1) sprintf(settingWeerLiveLocation, "Amsterdam"); 156 | if (settingWeerLiveInterval == 0) 157 | { 158 | settingWeerLiveInterval = 0; // geen weerberichten 159 | } 160 | else 161 | { 162 | if (settingWeerLiveInterval > 120) settingWeerLiveInterval = 120; // minuten! 163 | if (settingWeerLiveInterval < 15) settingWeerLiveInterval = 15; 164 | } 165 | if (settingNewsMaxMsg > 20) settingNewsMaxMsg = 20; 166 | if (settingNewsMaxMsg < 1) settingNewsMaxMsg = 1; 167 | if (settingNewsInterval > 120) settingNewsInterval = 120; 168 | if (settingNewsInterval == 0) 169 | { 170 | settingNewsInterval = 0; // geen nieuwsberichten 171 | } 172 | else 173 | { 174 | if (settingNewsInterval < 15) settingNewsInterval = 15; 175 | } 176 | 177 | DebugTln(F(" .. done\r")); 178 | 179 | if (!show) return; 180 | 181 | Debugln(F("\r\n==== read Settings ===================================================\r")); 182 | Debugf(" Hostname : %s\r\n", settingHostname); 183 | Debugf(" local Max. Msg : %d\r\n", settingLocalMaxMsg); 184 | Debugf(" text Speed : %d\r\n", settingTextSpeed); 185 | Debugf(" LDR low offset : %d\r\n", settingLDRlowOffset); 186 | Debugf(" LDR high offset : %d\r\n", settingLDRhighOffset); 187 | Debugf(" max Intensity : %d\r\n", settingMaxIntensity); 188 | Debugf(" WeerLive.nl AUTH : %s\r\n", settingWeerLiveAUTH); 189 | Debugf(" WeerLive.nl Locatie : %s\r\n", settingWeerLiveLocation); 190 | Debugf(" WeerLive.nl Interval : %d\r\n", settingWeerLiveInterval); 191 | Debugf(" newsapi.org AUTH : %s\r\n", settingNewsAUTH); 192 | Debugf(" newsapi.org NoWords : %s\r\n", settingNewsNoWords); 193 | Debugf(" newsapi.org Max. Msg : %d\r\n", settingNewsMaxMsg); 194 | Debugf(" newsapi.org Interval : %d\r\n", settingNewsInterval); 195 | 196 | Debugln(F("-\r")); 197 | 198 | } // readSettings() 199 | 200 | 201 | //======================================================================= 202 | void updateSetting(const char *field, const char *newValue) 203 | { 204 | DebugTf("-> field[%s], newValue[%s]\r\n", field, newValue); 205 | 206 | if (!stricmp(field, "Hostname")) { 207 | strCopy(settingHostname, sizeof(settingHostname), newValue); 208 | if (strlen(settingHostname) < 1) strCopy(settingHostname, sizeof(settingHostname), _HOSTNAME); 209 | char *dotPntr = strchr(settingHostname, '.') ; 210 | if (dotPntr != NULL) 211 | { 212 | byte dotPos = (dotPntr-settingHostname); 213 | if (dotPos > 0) settingHostname[dotPos] = '\0'; 214 | } 215 | Debugln(); 216 | DebugTf("Need reboot before new %s.local will be available!\r\n\n", settingHostname); 217 | } 218 | if (!stricmp(field, "localMaxMsg")) settingLocalMaxMsg = String(newValue).toInt(); 219 | if (!stricmp(field, "textSpeed")) settingTextSpeed = String(newValue).toInt(); 220 | if (!stricmp(field, "LDRlowOffset")) settingLDRlowOffset = String(newValue).toInt(); 221 | if (!stricmp(field, "LDRhighOffset")) settingLDRhighOffset = String(newValue).toInt(); 222 | if (!stricmp(field, "maxIntensity")) settingMaxIntensity = String(newValue).toInt(); 223 | 224 | if (!stricmp(field, "weerLiveAUTH")) strCopy(settingWeerLiveAUTH, sizeof(settingWeerLiveAUTH), newValue); 225 | if (!stricmp(field, "weerLiveLocation")) strCopy(settingWeerLiveLocation, sizeof(settingWeerLiveLocation), newValue); 226 | if (!stricmp(field, "weerLiveInterval")) settingWeerLiveInterval = String(newValue).toInt(); 227 | 228 | if (!stricmp(field, "newsapiAUTH")) strCopy(settingNewsAUTH, sizeof(settingNewsAUTH), newValue); 229 | if (!stricmp(field, "newsNoWords")) strCopy(settingNewsNoWords, sizeof(settingNewsNoWords), newValue); 230 | if (!stricmp(field, "newsapiMaxMsg")) settingNewsMaxMsg = String(newValue).toInt(); 231 | if (!stricmp(field, "newsapiInterval")) settingNewsInterval = String(newValue).toInt(); 232 | 233 | writeSettings(false); 234 | 235 | if (settingWeerLiveInterval == 0) memset(tempMessage, 0, sizeof(tempMessage)); 236 | else if (settingWeerLiveInterval < 15) settingWeerLiveInterval = 15; 237 | if (settingNewsInterval == 0) removeNewsData(); 238 | else if (settingNewsInterval < 15) settingNewsInterval = 15; 239 | //--- rebuild noWords array -- 240 | splitNewsNoWords(settingNewsNoWords); 241 | 242 | } // updateSetting() 243 | 244 | 245 | /*************************************************************************** 246 | * 247 | * Permission is hereby granted, free of charge, to any person obtaining a 248 | * copy of this software and associated documentation files (the 249 | * "Software"), to deal in the Software without restriction, including 250 | * without limitation the rights to use, copy, modify, merge, publish, 251 | * distribute, sublicense, and/or sell copies of the Software, and to permit 252 | * persons to whom the Software is furnished to do so, subject to the 253 | * following conditions: 254 | * 255 | * The above copyright notice and this permission notice shall be included 256 | * in all copies or substantial portions of the Software. 257 | * 258 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 259 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 260 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 261 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 262 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 263 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 264 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 265 | * 266 | **************************************************************************** 267 | */ 268 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/updateServerHtml.h: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : updateServerHtml.h 4 | ** 5 | ** Copyright (c) 2021 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | static const char UpdateServerIndex[] PROGMEM = 12 | R"( 13 | 16 | 17 |

ESP - lichtKrant Flash utility

18 |
19 | Selecteer een ".ino.bin" bestand
20 | 21 | 22 |
23 |
24 | Selecteer een ".mklittlefs.bin" bestand
25 | 26 | 27 |
28 |
29 |
Let op!!! 30 |
Bij het flashen van LittleFS raakt u alle bestanden op LittleFS kwijt. 31 |
Maak daarom eerst een kopie van deze bestanden (met de FSexplorer) 32 | en zet deze na het flashen van LittleFS weer terug. 33 |
34 |
35 | Klik hier 36 | om terug te keren naar het hoofdscherm! 37 | 38 | )"; 39 | 40 | static const char UpdateServerSuccess[] PROGMEM = 41 | R"( 42 | 45 | 46 |

lichtKrant Flash utility

47 |
48 |

Update successful!

49 |
50 |
Wait for the lichtKrant to reboot and start the HTTP server 51 |
52 |
53 |
Wacht nog 30 seconden .. 54 |
Als het lijkt of er niets gebeurd, wacht dan tot de teller 55 | op 'nul' staat en klik daarna hier! 56 | 57 | 68 | )"; 69 | 70 | 71 | 72 | /*************************************************************************** 73 | * 74 | * Permission is hereby granted, free of charge, to any person obtaining a 75 | * copy of this software and associated documentation files (the 76 | * "Software"), to deal in the Software without restriction, including 77 | * without limitation the rights to use, copy, modify, merge, publish, 78 | * distribute, sublicense, and/or sell copies of the Software, and to permit 79 | * persons to whom the Software is furnished to do so, subject to the 80 | * following conditions: 81 | * 82 | * The above copyright notice and this permission notice shall be included 83 | * in all copies or substantial portions of the Software. 84 | * 85 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 86 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 87 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 88 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 89 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 90 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 91 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 92 | * 93 | **************************************************************************** 94 | */ 95 | -------------------------------------------------------------------------------- /testProject/ESP_ticker/weerlive_nl.ino: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | ** Program : weerlive_nl 4 | ** 5 | ** Copyright (c) 2021 .. 2023 Willem Aandewiel 6 | ** 7 | ** TERMS OF USE: MIT License. See bottom of file. 8 | *************************************************************************** 9 | */ 10 | 11 | void getWeerLiveData() 12 | { 13 | const char* weerliveHost = "weerlive.nl"; 14 | const int httpPort = 80; 15 | int weerliveStatus = 0; 16 | String tempString; 17 | int startPos, endPos; 18 | int32_t maxWait; 19 | char jsonResponse[1536]; 20 | char val[51] = ""; 21 | bool gotData = false; 22 | 23 | WiFiClient weerliveClient; 24 | 25 | DebugTf("getWeerLiveData(%s)\r\n", weerliveHost); 26 | 27 | // We now create a URI for the request 28 | String url = "/api/json-data-10min.php?key="; 29 | url += settingWeerLiveAUTH; 30 | url += "&locatie="; 31 | url += settingWeerLiveLocation; 32 | 33 | DebugTf("Requesting URL: %s/api/json-data-10min.php?key=secret&locatie=%s\r\n", weerliveHost, settingWeerLiveLocation); 34 | Debugln(url); 35 | if (!weerliveClient.connect(weerliveHost, httpPort)) 36 | { 37 | DebugTln("connection failed"); 38 | sprintf(tempMessage, "connection to %s failed", weerliveHost); 39 | weerliveClient.flush(); 40 | weerliveClient.stop(); 41 | return; 42 | } 43 | 44 | // This will send the request to the server 45 | weerliveClient.print(String("GET ") + url + " HTTP/1.1\r\n" + 46 | "Host: " + weerliveHost + "\r\n" + 47 | "Connection: close\r\n\r\n"); 48 | delay(10); 49 | 50 | weerliveClient.setTimeout(5000); 51 | 52 | while ((weerliveClient.connected() || weerliveClient.available()) && !gotData) 53 | { 54 | yield(); 55 | while(weerliveClient.available() && !gotData) 56 | { 57 | //--- skip to find HTTP/1.1 58 | //--- then parse response code 59 | if (weerliveClient.find("HTTP/1.1")) 60 | { 61 | weerliveStatus = weerliveClient.parseInt(); // parse status code 62 | DebugTf("Statuscode: [%d] ", weerliveStatus); 63 | if (weerliveStatus != 200) 64 | { 65 | Debugln(" ERROR!"); 66 | weerliveClient.flush(); 67 | weerliveClient.stop(); 68 | return; 69 | } 70 | Debugln(" OK!"); 71 | } 72 | else 73 | { 74 | DebugTln("Error reading weerLive.. -> bailout!"); 75 | weerliveClient.flush(); 76 | weerliveClient.stop(); 77 | return; 78 | } 79 | //--- skip headers 80 | if (weerliveClient.find("\r\n\r\n")) 81 | { 82 | int charsRead = 0; 83 | charsRead = weerliveClient.readBytesUntil('\0', jsonResponse, sizeof(jsonResponse)); 84 | jsonResponse[(charsRead -1)] = '\0'; 85 | gotData = true; 86 | DebugTln("Got weer data!"); 87 | } 88 | } // while available .. 89 | 90 | } // connected .. 91 | 92 | weerliveClient.flush(); 93 | weerliveClient.stop(); 94 | //-- jsonResponse looks like: 95 | //-- { "liveweer": [{"plaats": "Baarn", "timestamp": "1683105785", "time": "03-05-2023 11:23", "temp": "10.4", "gtemp": "8.8", "samenv": "Licht bewolkt", "lv": "56", "windr": "NO", "windrgr": "44", "windms": "3", "winds": "2", "windk": "5.8", "windkmh": "10.8", "luchtd": "1029.4", "ldmmhg": "772", "dauwp": "2", "zicht": "35", "verw": "Zonnig en droog, donderdag warmer", "sup": "06:03", "sunder": "21:08", "image": "lichtbewolkt", "d0weer": "halfbewolkt", "d0tmax": "15", "d0tmin": "3", "d0windk": "2", "d0windknp": "6", "d0windms": "3", "d0windkmh": "11", "d0windr": "NO", "d0windrgr": "44", "d0neerslag": "0", "d0zon": "35", "d1weer": "halfbewolkt", "d1tmax": "20", "d1tmin": "5", "d1windk": "2", "d1windknp": "6", "d1windms": "3", "d1windkmh": "11", "d1windr": "O", "d1windrgr": "90", "d1neerslag": "20", "d1zon": "60", "d2weer": "regen", "d2tmax": "19", "d2tmin": "12", "d2windk": "2", "d2windknp": "6", "d2windms": "3", "d2windkmh": "11", "d2windr": "ZW", "d2windrgr": "225", "d2neerslag": "80", "d2zon": "30", "alarm": "0", "alarmtxt": ""}]} 96 | 97 | int prevLength = strlen(jsonResponse); 98 | strTrimCntr(jsonResponse, 1534); 99 | DebugTf("jsonResponse now [%d]chars (before trim [%d]chars)\r\n", strlen(jsonResponse), prevLength); 100 | DebugTf("jsonResponse is [%s]\r\n\n", jsonResponse); 101 | 102 | parseJsonKey(jsonResponse, "plaats", val, 50); 103 | snprintf(tempMessage, LOCAL_SIZE, val); 104 | parseJsonKey(jsonResponse, "samenv", val, 50); 105 | snprintf(cMsg, LOCAL_SIZE, " %s %s ", tempMessage, val); 106 | parseJsonKey(jsonResponse, "d0tmin", val, 50); 107 | snprintf(tempMessage, LOCAL_SIZE, "%s min %s°C ", cMsg, val); 108 | parseJsonKey(jsonResponse, "d0tmax", val, 50); 109 | snprintf(cMsg, LOCAL_SIZE, "%s max %s°C", tempMessage, val); 110 | parseJsonKey(jsonResponse, "luchtd", val, 50); 111 | snprintf(tempMessage, LOCAL_SIZE, "%s - luchtdruk %s hPa ", cMsg, val); 112 | 113 | parseJsonKey(jsonResponse, "d1weer", val, 50); 114 | snprintf(cMsg, LOCAL_SIZE, "%s - morgen %s ", tempMessage, val); 115 | parseJsonKey(jsonResponse, "d1tmin", val, 50); 116 | snprintf(tempMessage, LOCAL_SIZE, "%s min %s°C ", cMsg, val); 117 | parseJsonKey(jsonResponse, "d1tmax", val, 50); 118 | snprintf(cMsg, LOCAL_SIZE, "%s max %s°C", tempMessage, val); 119 | 120 | snprintf(tempMessage, LOCAL_SIZE, "%s", cMsg); 121 | Debugln("\r\n"); 122 | Debugf("\tWeer[%s]\r\n", tempMessage); 123 | 124 | } // getWeerLiveData() 125 | 126 | 127 | /*************************************************************************** 128 | * 129 | * Permission is hereby granted, free of charge, to any person obtaining a 130 | * copy of this software and associated documentation files (the 131 | * "Software"), to deal in the Software without restriction, including 132 | * without limitation the rights to use, copy, modify, merge, publish, 133 | * distribute, sublicense, and/or sell copies of the Software, and to permit 134 | * persons to whom the Software is furnished to do so, subject to the 135 | * following conditions: 136 | * 137 | * The above copyright notice and this permission notice shall be included 138 | * in all copies or substantial portions of the Software. 139 | * 140 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 141 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 142 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 143 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 144 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 145 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 146 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 147 | * 148 | **************************************************************************** 149 | */ 150 | --------------------------------------------------------------------------------