initial commit

Author:    gilex-dev <gilex-dev@proton.me>

Date:      Thu Oct 14 22:12:12 2021 +0200
This commit is contained in:
gilex-dev 2023-08-19 18:34:10 +02:00
commit 98e0d95e74
9 changed files with 694 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.cache/
.pio/
.vscode/
compile_commands.json

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# ESP8266-IOT-TIMER
An Arduino program for the ESP8266 NodeMCU to switch the GPIOs based on a timer.
## Web Interface
Currently, the web interface uses zero JavaScript.
This will however change in the future.
## Convert to classic Arduino project
open `src/` and rename `.cpp` files to `.ino`. Rename `main.ino` to `src.ino` and open all files in `src` in the Arduino Legacy IDE and compile as usual.

39
include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

20
platformio.ini Normal file
View File

@ -0,0 +1,20 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
upload_speed = 921600
monitor_speed = 74880
lib_deps =
ropg/ezTime@^0.8.3
paulstoffregen/OneWire@^2.3.7
milesburton/DallasTemperature@^3.11.0

29
src/globalvars.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef GLOBALVARS
#define GLOBALVARS
/* class-types from header files */
#include <Arduino.h>
#include <ESP8266WebServer.h>
#include <ezTime.h>
#define DEBUG 1 // 1 for development, 0 for production
#if DEBUG == 1
#define debug(debugMSG...) Serial.print(debugMSG)
#define debugln(debugMSG...) Serial.println(debugMSG)
#else
#define debug(debugMSG...)
#define debugln(debugMSG...)
#endif
/* global vars */
extern ESP8266WebServer server;
extern Timezone myTime;
extern char time_all[8][8][3];
extern char stat[8][4];
extern char settings_html[];
extern char home_html[];
extern bool override[];
extern const unsigned int pins[];
#endif

228
src/html.cpp Normal file
View File

@ -0,0 +1,228 @@
#include "globalvars.h"
#include <Arduino.h>
void reload_home() {
char time_current[3][3] = {"", "", ""};
if (myTime.hour() < 10) {
sprintf(time_current[0], "0%d", myTime.hour());
} else {
sprintf(time_current[0], "%d", myTime.hour());
}
if (myTime.minute() < 10) {
sprintf(time_current[1], "0%d", myTime.minute());
} else {
sprintf(time_current[1], "%d", myTime.minute());
}
if (myTime.second() < 10) {
sprintf(time_current[2], "0%d", myTime.second());
} else {
sprintf(time_current[2], "%d", myTime.second());
}
sprintf(home_html, "<!DOCTYPE html>\
<html lang='de'>\
<head>\
<meta charset='UTF-8'>\
<meta http-equiv='X-UA-Compatible' content='IE=edge'>\
<meta name='viewport' content='width=device-width, initial-scale=1.0'>\
<title>Home</title>\
<style>\
* {\
font-family: Arial, Helvetica, sans-serif;\
font-size: 40px;\
user-select: none;\
text-decoration: none;\
}\
h2 {\
font-size: 28px;\
}\
h2 span{\
font-size: inherit;\
font-family: monospace;\
}\
.wrapper_main {\
margin: 0 auto;\
width: fit-content;\
width: -moz-fit-content;\
text-align: center;\
background-color: rgb(202, 202, 202);\
padding: 1px 8px 8px 8px;\
border-radius: 4px;\
}\
.wrapper_form div a {\
width: 100px;\
}\
.wrapper_form div a, .animated_a {\
background-color: rgb(252, 255, 104);\
border: none;\
border-radius: 4px;\
box-shadow: rgb(180, 172, 59) 0 4px;\
position: relative;\
margin: 8px 0;\
padding: 2px;\
}\
.wrapper_form div a, .animated_a {\
display: inline-block;\
}\
a {\
color: black;\
}\
.wrapper_form div a, .animated_a {\
cursor: pointer;\
}\
.animated_a:active, .wrapper_form div a:active {\
box-shadow: rgb(180, 172, 59) 0 2px;\
top: 2px;\
}\
</style>\
</head>\
<body>\
<div class='wrapper_main'>\
<h1>Übersicht</h1>\
<h2>Uhrzeit: <span>%s:%s:%s</span></h2>\
<div class='wrapper_form'>\
<div>\
<span>Ventil 1: </span>\
<a href='/toggle?number=0'>%s</a>\
</div>\
<div>\
<span>Ventil 2:</span>\
<a href='/toggle?number=1'>%s</a>\
</div>\
<div>\
<span>Ventil 3:</span>\
<a href='/toggle?number=2'>%s</a>\
</div>\
<div>\
<span>Ventil 4:</span>\
<a href='/toggle?number=3'>%s</a>\
</div>\
<div>\
<span>Ventil 5:</span>\
<a href='/toggle?number=4'>%s</a>\
</div>\
<div>\
<span>Ventil 6:</span>\
<a href='/toggle?number=5'>%s</a>\
</div>\
<div>\
<span>Ventil 7:</span>\
<a href='/toggle?number=6'>%s</a>\
</div>\
<div style='display: none;'>\
<span>Ventil 8:</span>\
<a href='/toggle?number=7'>%s</a>\
</div>\
<a class='animated_a' href='/settings'>Einstellungen</a>\
<br>\
<a class='animated_a' href='#'>Kellerheizung</a>\
</div>\
</div>\
</body>\
</html>",
time_current[0], time_current[1], time_current[2], stat[0], stat[1],
stat[2], stat[3], stat[4], stat[5], stat[6], stat[7]);
}
void reload_settings() {
sprintf(settings_html, "<tr>\
<td>1</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>2</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>3</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>4</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>5</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>6</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr>\
<td>7</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
<tr style='display: none'>\
<td>8</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
<td>%s:%s</td>\
</tr>\
</table>\
</div>\
<a class='animated_a' href='/'>Übersicht</a>\
</div>\
</body>\
</html>",
time_all[0][0], time_all[1][0], time_all[2][0], time_all[3][0],
time_all[4][0], time_all[5][0], time_all[6][0], time_all[7][0],
time_all[0][1], time_all[1][1], time_all[2][1], time_all[3][1],
time_all[4][1], time_all[5][1], time_all[6][1], time_all[7][1],
time_all[0][2], time_all[1][2], time_all[2][2], time_all[3][2],
time_all[4][2], time_all[5][2], time_all[6][2], time_all[7][2],
time_all[0][3], time_all[1][3], time_all[2][3], time_all[3][3],
time_all[4][3], time_all[5][3], time_all[6][3], time_all[7][3],
time_all[0][4], time_all[1][4], time_all[2][4], time_all[3][4],
time_all[4][4], time_all[5][4], time_all[6][4], time_all[7][4],
time_all[0][5], time_all[1][5], time_all[2][5], time_all[3][5],
time_all[4][5], time_all[5][5], time_all[6][5], time_all[7][5],
time_all[0][6], time_all[1][6], time_all[2][6], time_all[3][6],
time_all[4][6], time_all[5][6], time_all[6][6], time_all[7][6],
time_all[0][7], time_all[1][7], time_all[2][7], time_all[3][7],
time_all[4][7], time_all[5][7], time_all[6][7], time_all[7][7]);
}
void handleToggle() {
int i = server.arg("number")
.toInt(); // converts GET argument to int and does stuff xD
override[i] = !override[i];
debugln(F("---------------------------"));
debug(F("[RECIVED] for: "));
debugln(i);
digitalWrite(pins[i], !digitalRead(pins[i]));
if (digitalRead(pins[i]) == LOW) {
sprintf(stat[i], "AN");
} else {
sprintf(stat[i], "AUS");
}
debug(F("Status: "));
debugln(stat[i]);
reload_home();
server.sendHeader(F("Location"), F("/"), true);
server.send(302, F("text/html"), home_html);
debugln(override[i]);
debugln(F("---------------------------"));
}

305
src/main.cpp Normal file
View File

@ -0,0 +1,305 @@
const char SSID[] = "";
const char PASSWORD[] = "";
const char version[] = " 0.8";
#include "globalvars.h"
#include <Arduino.h>
#include <EEPROM.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ezTime.h>
#include <stdint.h>
ESP8266WebServer server;
IPAddress staticIP(192, 168, 2, 5); // static IP address
IPAddress gateway(192, 168, 2, 1); // Router's IP address
IPAddress subnet(255, 255, 255, 0);
IPAddress dns(192, 168, 2, 1);
Timezone myTime;
char time_all[8][8][3] = {
{}, {}, {}, {},
{}, {}, {}, {}}; // 3D array for load_EEPROM() and handleTime()
const unsigned int pins[8] = {5, 4, 14, 12,
13, 10, 9}; // TODO: only 7 of 8 pins defined
int r = 0;
char stat[8][4] = {"AUS", "AUS", "AUS", "AUS",
"AUS", "AUS", "AUS", "AUS"}; // text on button
char t_stat[8][4] = {"", "", "", "",
"", "", "", ""}; // for "status of timer changed"
bool override[8] = {false, false, false, false,
false, false, false, false}; // for manual override
char home_html[3000]; // be careful when changing these values (might cause
// overflow)!
char settings_html[2014];
const char settings_html_pre[] PROGMEM = {"<!DOCTYPE html>\
<html lang='de'>\
<head>\
<meta charset='UTF-8'>\
<meta http-equiv='X-UA-Compatible' content='IE=edge'>\
<meta name='viewport' content='width=device-width, initial-scale=1.0'>\
<title>Einstellungen</title>\
<style>\
* {\
font-family: Arial, Helvetica, sans-serif;\
font-size: 26px;\
user-select: none;\
text-decoration: none;\
}\
a {\
color: black;\
}\
.wrapper_main {\
margin: 0 auto;\
max-width: fit-content;\
max-width: -moz-fit-content;\
text-align: center;\
background-color: rgb(214, 214, 214);\
padding: 1px 8px 8px 8px;\
border-radius: 4px;\
}\
#rule, [type='time'] {\
height: 36px;\
border: none;\
padding: 0 4px;\
margin: 4px 0;\
background-color: white;\
width: fit-content;\
width: -moz-fit-content;\
border-radius: 4px;\
}\
.animated_a, [type='submit']{\
background-color: rgb(135, 252, 119);\
border: none;\
border-radius: 4px;\
box-shadow: rgb(25, 167, 49) 0 4px;\
position: relative;\
margin: 8px 0;\
padding: 2px 8px;\
}\
[type='submit'] {\
position: relative;\
top: -2px;\
display: inline-block;\
}\
.animated_a {\
text-decoration: none;\
display: inline-block;\
margin: 8px 0;\
padding-top: 4px;\
height: 28px;\
}\
.wrapper_table {\
overflow: auto;\
}\
table {\
margin: 8px auto;\
}\
table, th, td {\
border: 1px solid black;\
border-collapse: collapse;\
user-select: text;\
padding: 0 8px;\
}\
.animated_a, [type='submit']{\
cursor: pointer;\
}\
.animated_a:active {\
box-shadow: rgb(25, 167, 49) 0 2px;\
top: 2px;\
}\
[type='submit']:active {\
box-shadow: rgb(25, 167, 49) 0 2px;\
top: 0px;\
}\
*:focus {\
outline: none;\
}\
</style>\
</head>\
<body>\
<div class='wrapper_main'>\
<form action='settings' method='POST'>\
<select name='rule' id='rule' required>\
<option value='' selected disabled>Ventiel Nr.</option>\
<option value='0'>1</option>\
<option value='1'>2</option>\
<option value='2'>3</option>\
<option value='3'>4</option>\
<option value='4'>5</option>\
<option value='5'>6</option>\
<option value='6'>7</option>\
<option value='7' disabled>8</option>\
</select>\
<br>\
<input type='time' name='time_o'>\
- <input type='time' name='time_f'>\
<br>\
<input type='time' name='time_o_1'>\
- <input type='time' name='time_f_1'>\
<br>\
<input type='submit'>\
</form>\
<div class='wrapper_table'>\
<table>\
<tr>\
<th style='background-color: gray;'></th>\
<th>von:</th>\
<th>bis:</th>\
<th>von:</th>\
<th>bis:</th>\
</tr>"};
void reload_home();
void reload_settings();
void handleToggle();
void load_EEPROM() {
unsigned int EEPROM_Addr = 0;
for (unsigned int i0 = 0; i0 < 8; i0++) {
for (unsigned int i = 0; i < 8; i++) {
if (EEPROM.read(EEPROM_Addr) < 10) {
sprintf(time_all[i0][i], "0%d", EEPROM.read(EEPROM_Addr));
} else {
sprintf(time_all[i0][i], "%d", EEPROM.read(EEPROM_Addr));
}
EEPROM_Addr += 4;
yield();
}
}
}
void handleHome() {
reload_home();
server.send(200, "text/html", home_html);
debugln(ESP.getFreeHeap(), DEC);
}
void handleSettings() {
reload_settings();
server.setContentLength(strlen(settings_html_pre) + strlen(settings_html));
server.send(200, "text/html", settings_html_pre);
server.sendContent(settings_html);
debugln(ESP.getFreeHeap(), DEC);
}
void handleTime() {
const char args[4][9] = {"time_o", "time_f", "time_o_1", "time_f_1"};
for (unsigned int arg = 0; arg < 4; arg++) {
if (server.arg(args[arg]) != "") {
char buff_h[2][2] = {server.arg(args[arg])[0], server.arg(args[arg])[1]};
char buff_m[2][2] = {server.arg(args[arg])[3], server.arg(args[arg])[4]};
sprintf(time_all[arg * 2][server.arg("rule").toInt()], "%s", buff_h);
sprintf(time_all[arg * 2 + 1][server.arg("rule").toInt()], "%s", buff_m);
EEPROM.put(server.arg("rule").toInt() * 4 + arg * 64,
atoi(time_all[arg * 2][server.arg("rule").toInt()]));
EEPROM.commit();
EEPROM.put(server.arg("rule").toInt() * 4 + arg * 64 + 32,
atoi(time_all[arg * 2 + 1][server.arg("rule").toInt()]));
EEPROM.commit();
}
}
reload_settings();
server.setContentLength(strlen(settings_html_pre) + strlen(settings_html));
server.send(301, "text/html", settings_html_pre);
server.sendContent(settings_html);
}
void timer() {
if (((myTime.hour() * 60 + myTime.minute() >=
atoi(time_all[0][r]) * 60 + atoi(time_all[1][r])) and
(myTime.hour() * 60 + myTime.minute() <
atoi(time_all[2][r]) * 60 + atoi(time_all[3][r]))) or
((myTime.hour() * 60 + myTime.minute() >=
atoi(time_all[4][r]) * 60 + atoi(time_all[5][r])) and
(myTime.hour() * 60 + myTime.minute() <
atoi(time_all[6][r]) * 60 + atoi(time_all[7][r])))) {
if ((override[r]) and (strcmp(t_stat[r], "off") == 0)) {
override[r] = false;
debugln(F("debug#1"));
}
sprintf(t_stat[r], "on");
if (!override[r]) {
digitalWrite(pins[r], LOW);
if (strcmp(stat[r], "AUS") == 0) {
debug(r);
debugln(F(" is on"));
sprintf(stat[r], "AN");
}
}
} else {
if ((override[r]) and (strcmp(t_stat[r], "on") == 0)) {
override[r] = false;
}
sprintf(t_stat[r], "off");
if (!override[r]) {
digitalWrite(pins[r], HIGH);
if (strcmp(stat[r], "AN") == 0) {
debug(r);
debugln(F(" is off"));
sprintf(stat[r], "AUS");
}
}
}
}
void setup() {
int p = 0;
while (p <= 7) {
pinMode(pins[p], OUTPUT);
digitalWrite(pins[p], HIGH);
p++;
}
Serial.begin(74880);
EEPROM.begin(256); // 4 bits for EEPROM, address 0-255, value 0-255
debug(F("\nver."));
debugln(version);
debugln(F("check for updates at "
"https://somepi.ddns.net/gitea/gilex-dev/ESP8266-IOT-timer/\n"));
load_EEPROM();
// WLAN-config
// ---------------------------------------------------------------------------------------------------------------------
WiFi.mode(WIFI_STA);
WiFi.config(staticIP, gateway, subnet, dns);
WiFi.hostname("ESP8266 IOT development");
WiFi.begin(SSID, PASSWORD);
Serial.println(F("Connecting ..."));
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print('.');
}
Serial.print(F("\nConnected to "));
Serial.println(WiFi.SSID());
Serial.print(F("IP address:\t"));
Serial.println(WiFi.localIP());
//----------------------------------------------------------------------------------------------------------------------------------
myTime.setLocation("de");
waitForSync();
debug(F("Local time: "));
debugln(myTime.dateTime("H:i:s"));
setInterval();
server.on("/", HTTP_GET, handleHome);
server.on("/settings", HTTP_GET, handleSettings);
server.on("/settings", HTTP_POST, handleTime);
server.on("/toggle", HTTP_GET, handleToggle);
server.on("/version", HTTP_GET,
[] { server.send(200, "text/html", version); });
server.begin();
debugln(F("Webserver started"));
}
void loop() {
server.handleClient();
timer();
r++;
if (r == 7) {
r = 0;
}
yield();
delay(50);
}

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html