Add source files

This commit is contained in:
fvanroie 2020-01-24 18:14:52 +01:00
parent 1100360eb3
commit 025ac7b35f
18 changed files with 5594 additions and 0 deletions

1371
src/hasp.cpp Normal file

File diff suppressed because it is too large Load Diff

145
src/hasp_config.cpp Normal file
View File

@ -0,0 +1,145 @@
#include "Arduino.h"
#include "ArduinoJson.h"
#ifdef ESP32
#include "SPIFFS.h"
#endif
#include <FS.h> // Include the SPIFFS library
#include "hasp_config.h"
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_http.h"
#include "hasp_mqtt.h"
#include "hasp_wifi.h"
#include "hasp_mdns.h"
#include "hasp_gui.h"
#include "hasp_tft.h"
#include "hasp_ota.h"
#include "hasp.h"
#define HASP_CONFIG_FILE F("/config.json")
void spiffsList()
{
#if defined(ARDUINO_ARCH_ESP32)
debugPrintln(F("FILE: Listing files on the internal flash:"));
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file) {
char msg[64];
sprintf_P(msg, PSTR("FILE: * %s (%u bytes)"), file.name(), (uint32_t)file.size());
debugPrintln(msg);
file = root.openNextFile();
}
#endif
#if defined(ARDUINO_ARCH_ESP8266)
debugPrintln(F("FILE: Listing files on the internal flash:"));
Dir dir = SPIFFS.openDir("/");
while(dir.next()) {
char msg[64];
sprintf_P(msg, PSTR("FILE: * %s (%u bytes)"), dir.fileName().c_str(), (uint32_t)dir.fileSize());
debugPrintln(msg);
}
#endif
}
bool configChanged()
{
return false;
}
void configLoop()
{
if(configChanged()) {
// configSetConfig();
}
}
void configGetConfig(JsonDocument & settings, bool setupdebug = false)
{
File file = SPIFFS.open(HASP_CONFIG_FILE, "r");
if(file) {
size_t size = file.size();
if(size > 1024) {
errorPrintln(F("CONF: %sConfig file size is too large"));
return;
}
DeserializationError error = deserializeJson(settings, file);
if(!error) {
file.close();
if(setupdebug) {
debugSetup(); // Debug started, now we can use it; HASP header sent
debugPrintln(F("FILE: [SUCCESS] SPI flash FS mounted"));
spiffsList();
}
debugPrintln(String(F("CONF: Loading ")) + String(HASP_CONFIG_FILE));
// show settings in log
String output;
serializeJson(settings, output);
debugPrintln(String(F("CONF: ")) + output);
debugPrintln(String(F("CONF: [SUCCESS] Loaded ")) + String(HASP_CONFIG_FILE));
return;
}
}
if(setupdebug) {
// setup debugging defaults
debugSetup(); // Debug started, now we can use it; HASP header sent
debugPrintln(F("FILE: [SUCCESS] SPI flash FS mounted"));
spiffsList();
}
debugPrintln(String(F("CONF: Loading ")) + String(HASP_CONFIG_FILE));
errorPrintln(String(F("CONF: %sFailed to load ")) + String(HASP_CONFIG_FILE));
}
void configWriteConfig()
{
/* Read Config File */
DynamicJsonDocument settings(1024);
debugPrintln(String(F("CONF: Config LOADING first")) + String(HASP_CONFIG_FILE));
configGetConfig(settings, false);
debugPrintln(String(F("CONF: Config LOADED first")) + String(HASP_CONFIG_FILE));
bool changed = true;
// changed |= debugGetConfig(settings[F("debug")].to<JsonObject>());
// changed |= guiGetConfig(settings[F("gui")].to<JsonObject>());
changed |= haspGetConfig(settings[F("hasp")].to<JsonObject>());
// changed |= httpGetConfig(settings[F("http")].to<JsonObject>());
// changed |= mdnsGetConfig(settings[F("mdns")].to<JsonObject>());
// changed |= mqttGetConfig(settings[F("mqtt")].to<JsonObject>());
// changed |= otaGetConfig(settings[F("ota")].to<JsonObject>());
// changed |= tftGetConfig(settings[F("tft")].to<JsonObject>());
// changed |= wifiGetConfig(settings[F("wifi")].to<JsonObject>());
if(changed) {
File file = SPIFFS.open(HASP_CONFIG_FILE, "w");
if(file) {
debugPrintln(F("CONF: Writing /config.json"));
size_t size = serializeJson(settings, file);
file.close();
if(size > 0) {
debugPrintln(F("CONF: [SUCCESS] /config.json saved"));
return;
}
}
errorPrintln(F("CONF: %sFailed to write /config.json"));
} else {
debugPrintln(F("CONF: Configuration was not changed"));
}
}
void configSetup(JsonDocument & settings)
{
if(!SPIFFS.begin()) {
errorPrintln(F("FILE: %sSPI flash init failed. Unable to mount FS."));
} else {
configGetConfig(settings, true);
}
}

74
src/hasp_debug.cpp Normal file
View File

@ -0,0 +1,74 @@
#include <Arduino.h>
#include "ArduinoJson.h"
#ifdef ESP8266
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#else
#include <Wifi.h>
#endif
#include <WiFiUdp.h>
#include <Syslog.h>
#include "hasp_debug.h"
#include "hasp_config.h"
#include "user_config_override.h"
#ifndef SYSLOG_SERVER
#define SYSLOG_SERVER ""
#endif
#ifndef SYSLOG_PORT
#define SYSLOG_PORT 514
#endif
#ifndef APP_NAME
#define APP_NAME "HASP"
#endif
std::string debugAppName = APP_NAME;
std::string debugSyslogHost = SYSLOG_SERVER;
uint16_t debugSyslogPort = SYSLOG_PORT;
// A UDP instance to let us send and receive packets over UDP
WiFiUDP syslogClient;
// Create a new syslog instance with LOG_KERN facility
// Syslog syslog(syslogClient, SYSLOG_SERVER, SYSLOG_PORT, MQTT_CLIENT, APP_NAME, LOG_KERN);
// Create a new empty syslog instance
Syslog syslog(syslogClient, debugSyslogHost.c_str(), debugSyslogPort, debugAppName.c_str(), debugAppName.c_str(),
LOG_LOCAL0);
void debugSetup()
{
Serial.begin(115200); /* prepare for possible serial debug */
Serial.flush();
Serial.println();
Serial.println();
Serial.println(F("\n _____ _____ _____ _____\n | | | _ | __| _ |\n"
" | | |__ | __|\n |__|__|__|__|_____|__|\n"
" Home Automation Switch Plate\n Open Hardware edition\n\n"));
Serial.flush();
// prepare syslog configuration here (can be anywhere before first call of
// log/logf method)
syslog.server(debugSyslogHost.c_str(), debugSyslogPort);
syslog.deviceHostname(debugAppName.c_str());
syslog.appName(debugAppName.c_str());
syslog.defaultPriority(LOG_LOCAL0);
}
void debugLoop()
{}
void serialPrintln(String debugText)
{
String debugTimeText =
"[+" + String(float(millis()) / 1000, 3) + "s] " + String(ESP.getFreeHeap()) + " " + debugText;
Serial.println(debugTimeText);
}
void debugStop()
{
Serial.flush();
}

50
src/hasp_eeprom.cpp Normal file
View File

@ -0,0 +1,50 @@
#include <EEPROM.h>
#include <Arduino.h>
#include "hasp_debug.h"
void eepromWrite(char addr, std::string & data);
std::string eepromRead(char addr);
void eepromSetup()
{
EEPROM.begin(1024);
// debugPrintln("EEPROM: Started Eeprom");
}
void eepromLoop()
{}
void eepromUpdate(uint16_t addr, char ch)
{
if(EEPROM.read(addr) != ch) {
EEPROM.write(addr, ch);
}
}
void eepromWrite(uint16_t addr, std::string & data)
{
int count = data.length();
for(int i = 0; i < count; i++) {
eepromUpdate(addr + i, data[i]);
}
eepromUpdate(addr + count, '\0');
EEPROM.commit();
}
std::string eepromRead(uint16_t addr)
{
int i;
char data[1024]; // Max 1024 Bytes
int len = 0;
unsigned char k;
k = EEPROM.read(addr);
while(k != '\0' && len < 1023) // Read until null character
{
k = EEPROM.read(addr + len);
if((uint8_t(k) < 32) || (uint8_t(k) > 127)) break; // check for printable ascii, includes '\0'
data[len] = k;
len++;
}
return std::string(data);
}

233
src/hasp_gui.cpp Normal file
View File

@ -0,0 +1,233 @@
#include <Ticker.h>
#include "lvgl.h"
#include "lv_conf.h"
#include "TFT_eSPI.h"
#ifdef ESP32
#include "png_decoder.h"
#endif
#include "lv_zifont.h"
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_config.h"
#include "hasp_gui.h"
#define LVGL_TICK_PERIOD 30 // 30
uint16_t guiSleepTime = 150; // 0.1 second resolution
bool guiSleeping = false;
uint8_t guiTickPeriod = 50;
Ticker tick; /* timer for interrupt handler */
TFT_eSPI tft = TFT_eSPI(); /* TFT instance */
bool IRAM_ATTR guiCheckSleep()
{
bool shouldSleep = lv_disp_get_inactive_time(NULL) > guiSleepTime * 100;
if(shouldSleep && !guiSleeping) {
debugPrintln(F("GUI: Going to sleep now..."));
guiSleeping = true;
}
return shouldSleep;
}
#if LV_USE_LOG != 0
/* Serial debugging */
void debugLvgl(lv_log_level_t level, const char * file, uint32_t line, const char * dsc)
{
char msg[128];
sprintf(msg, PSTR("LVGL: %s@%d->%s"), file, line, dsc);
debugPrintln(msg);
}
#endif
/* Display flushing */
void tft_espi_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
uint16_t c;
tft.startWrite(); /* Start new TFT transaction */
tft.setAddrWindow(area->x1, area->y1, (area->x2 - area->x1 + 1),
(area->y2 - area->y1 + 1)); /* set the working window */
for(int y = area->y1; y <= area->y2; y++) {
for(int x = area->x1; x <= area->x2; x++) {
c = color_p->full;
tft.writeColor(c, 1);
color_p++;
}
}
tft.endWrite(); /* terminate TFT transaction */
lv_disp_flush_ready(disp); /* tell lvgl that flushing is done */
}
/* Interrupt driven periodic handler */
static void IRAM_ATTR lv_tick_handler(void)
{
lv_tick_inc(guiTickPeriod);
}
/* Reading input device (simulated encoder here) */
bool read_encoder(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
static int32_t last_diff = 0;
int32_t diff = 0; /* Dummy - no movement */
int btn_state = LV_INDEV_STATE_REL; /* Dummy - no press */
data->enc_diff = diff - last_diff;
data->state = btn_state;
last_diff = diff;
return false;
}
bool my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
{
uint16_t touchX, touchY;
bool touched = tft.getTouch(&touchX, &touchY, 600);
if(!touched) return false;
bool shouldSleep = guiCheckSleep();
if(!shouldSleep && guiSleeping) {
debugPrintln(F("GUI: Waking up!"));
guiSleeping = false;
}
// Ignore first press?
if(touchX > tft.width() || touchY > tft.height()) {
Serial.print(F("Y or y outside of expected parameters.. x: "));
Serial.print(touchX);
Serial.print(F(" / y: "));
Serial.println(touchY);
} else {
/*Save the state and save the pressed coordinate*/
data->state = touched ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
data->point.x = touchX;
data->point.y = touchY;
/*
Serial.print("Data x");
Serial.println(touchX);
Serial.print("Data y");
Serial.println(touchY);*/
}
return false; /*Return `false` because we are not buffering and no more data to read*/
}
void guiSetup(TFT_eSPI & screen, JsonObject settings)
{
size_t buffer_size;
tft = screen;
lv_init();
#if ESP32
/* allocate on iram (or psram ?) */
buffer_size = 1024 * 8;
static lv_color_t * guiVdbBuffer = (lv_color_t *)malloc(sizeof(lv_color_t) * buffer_size);
static lv_disp_buf_t disp_buf;
lv_disp_buf_init(&disp_buf, guiVdbBuffer, NULL, buffer_size);
#else
/* allocate on heap */
static lv_color_t guiVdbBuffer[1024 * 4];
buffer_size = sizeof(guiVdbBuffer) / sizeof(guiVdbBuffer[0]);
static lv_disp_buf_t disp_buf;
lv_disp_buf_init(&disp_buf, guiVdbBuffer, NULL, buffer_size);
#endif
debugPrintln(String(F("LVGL: VDB size : ")) + String(buffer_size));
#if LV_USE_LOG != 0
debugPrintln(F("LVGL: Registering lvgl logging handler"));
lv_log_register_print_cb(debugLvgl); /* register print function for debugging */
#endif
/* Initialize PNG decoder */
// png_decoder_init();
/* Initialize the display driver */
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.flush_cb = tft_espi_flush;
disp_drv.buffer = &disp_buf;
#if(TFT_ROTATION == 0 || TFT_ROTATION == 2 || TFT_ROTATION == 4 || TFT_ROTATION == 6)
/* 1/3=Landscape or 0/2=Portrait orientation */
// Normal width & height
disp_drv.hor_res = TFT_WIDTH; // From User_Setup.h
disp_drv.ver_res = TFT_HEIGHT; // From User_Setup.h
#else
// Swapped width & height
disp_drv.hor_res = TFT_HEIGHT; // From User_Setup.h
disp_drv.ver_res = TFT_WIDTH; // From User_Setup.h
#endif
lv_disp_drv_register(&disp_drv);
/*Initialize the touch pad*/
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_ENCODER;
// indev_drv.read_cb = read_encoder;
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_t * mouse_indev = lv_indev_drv_register(&indev_drv);
lv_obj_t * label = lv_label_create(lv_layer_sys(), NULL);
lv_label_set_text(label, "<");
lv_indev_set_cursor(mouse_indev, label); // connect the object to the driver
/*
lv_obj_t * cursor = lv_obj_create(lv_layer_sys(), NULL); // show on every page
lv_obj_set_size(cursor, 9, 9);
static lv_style_t style_cursor;
lv_style_copy(&style_cursor, &lv_style_pretty);
style_cursor.body.radius = LV_RADIUS_CIRCLE;
style_cursor.body.main_color = LV_COLOR_RED;
style_cursor.body.opa = LV_OPA_COVER;
lv_obj_set_style(cursor, &style_cursor);
// lv_obj_set_click(cursor, false);
lv_indev_set_cursor(mouse_indev, cursor); // connect the object to the driver
*/
/* Initialize mouse pointer */
/*// if(true) {
debugPrintln(PSTR("LVGL: Initialize Cursor"));
lv_obj_t * cursor;
lv_obj_t * mouse_layer = lv_disp_get_layer_sys(NULL); // default display
// cursor = lv_obj_create(lv_scr_act(), NULL);
cursor = lv_obj_create(mouse_layer, NULL); // show on every page
lv_obj_set_size(cursor, 9, 9);
static lv_style_t style_round;
lv_style_copy(&style_round, &lv_style_plain);
style_round.body.radius = LV_RADIUS_CIRCLE;
style_round.body.main_color = LV_COLOR_RED;
style_round.body.opa = LV_OPA_COVER;
lv_obj_set_style(cursor, &style_round);
lv_obj_set_click(cursor, false); // don't click on the cursor
lv_indev_set_cursor(mouse_indev, cursor);
// }*/
/*Initialize the graphics library's tick*/
tick.attach_ms(guiTickPeriod, lv_tick_handler);
// guiLoop();
}
void IRAM_ATTR guiLoop()
{
lv_task_handler(); /* let the GUI do its work */
guiCheckSleep();
}
void guiStop()
{}
bool guiGetConfig(const JsonObject & settings)
{
if(!settings.isNull() && settings[F_GUI_TICKPERIOD] == guiTickPeriod) return false;
settings[F_GUI_TICKPERIOD] = guiTickPeriod;
size_t size = serializeJson(settings, Serial);
Serial.println();
return true;
}

1066
src/hasp_http.cpp Normal file

File diff suppressed because it is too large Load Diff

863
src/hasp_http.old Normal file
View File

@ -0,0 +1,863 @@
//#include "webServer.h"
#include <Arduino.h>
#include "ArduinoJson.h"
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_http.h"
#include "hasp_mqtt.h"
#include "hasp_wifi.h"
#include "hasp_config.h"
#include "hasp.h"
#if defined(ARDUINO_ARCH_ESP32)
#include "SPIFFS.h"
#endif
#include <FS.h>
#include <ESP.h>
bool httpEnable = true;
bool webServerStarted = false;
uint16_t httpPort = 80;
FS * filesystem = &SPIFFS;
File fsUploadFile;
String httpUser = "admin";
String httpPassword = "";
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WebServer.h>
ESP8266WebServer webServer(80);
#endif
#if defined(ARDUINO_ARCH_ESP32)
#include <rom/rtc.h> // needed to get the ResetInfo
#include <WebServer.h>
WebServer webServer(80);
// Compatibility function for ESP8266 getRestInfo
String esp32ResetReason(uint8_t cpuid)
{
if(cpuid > 1) {
return F("Invalid CPU id");
}
RESET_REASON reason = rtc_get_reset_reason(cpuid);
switch(reason) {
case 1:
return F("POWERON_RESET");
break; /**<1, Vbat power on reset*/
case 3:
return F("SW_RESET");
break; /**<3, Software reset digital core*/
case 4:
return F("OWDT_RESET");
break; /**<4, Legacy watch dog reset digital core*/
case 5:
return F("DEEPSLEEP_RESET");
break; /**<5, Deep Sleep reset digital core*/
case 6:
return F("SDIO_RESET");
break; /**<6, Reset by SLC module, reset digital core*/
case 7:
return F("TG0WDT_SYS_RESET");
break; /**<7, Timer Group0 Watch dog reset digital core*/
case 8:
return F("TG1WDT_SYS_RESET");
break; /**<8, Timer Group1 Watch dog reset digital core*/
case 9:
return F("RTCWDT_SYS_RESET");
break; /**<9, RTC Watch dog Reset digital core*/
case 10:
return F("INTRUSION_RESET");
break; /**<10, Instrusion tested to reset CPU*/
case 11:
return F("TGWDT_CPU_RESET");
break; /**<11, Time Group reset CPU*/
case 12:
return F("SW_CPU_RESET");
break; /**<12, Software reset CPU*/
case 13:
return F("RTCWDT_CPU_RESET");
break; /**<13, RTC Watch dog Reset CPU*/
case 14:
return F("EXT_CPU_RESET");
break; /**<14, for APP CPU, reseted by PRO CPU*/
case 15:
return F("RTCWDT_BROWN_OUT_RESET");
break; /**<15, Reset when the vdd voltage is not stable*/
case 16:
return F("RTCWDT_RTC_RESET");
break; /**<16, RTC Watch dog reset digital core and rtc module*/
default:
return F("NO_MEAN");
}
}
// these need to be removed
const uint8_t D0 = 0;
const uint8_t D1 = 1;
const uint8_t D2 = 2;
#endif // ESP32
static const char HTTP_DOCTYPE[] PROGMEM =
"<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width,initial-scale=1,"
"user-scalable=no\"/>";
static const char HTTP_META_GO_BACK[] PROGMEM = "<meta http-equiv='refresh' content='10;url=/'/>";
static const char HTTP_HEADER[] PROGMEM = "<title>%s</title>";
static const char HTTP_STYLE[] PROGMEM =
"<style>.c{text-align:center;}"
"div,input{padding:5px;font-size:1em;}"
"input{width:90%;}"
"body{text-align:center;font-family:verdana;}"
"button{border:0;border-radius:0.6rem;background-color:#1fb3ec;color:#eee;line-height:2.4rem;font-size:1.2rem;"
"width:100%;}"
".q{float:right;width:64px;text-align:right;}"
".red{background-color:#f33;}"
".button3{background-color:#f44336;}"
".button4{background-color:#e7e7e7;color:black;}"
".button5{background-color:#555555;}"
".button6{background-color:#4CAF50;}</style>";
static const char HTTP_SCRIPT[] PROGMEM = "<script>function "
"c(l){document.getElementById('s').value=l.innerText||l.textContent;document."
"getElementById('p').focus();}</script>";
static const char HTTP_HEADER_END[] PROGMEM =
"</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
static const char HTTP_END[] PROGMEM = "<div style='text-align:right;font-size:11px;'><hr/><a href='/about' "
"style='color:#aaa;'>HASP 0.0.0 by Francis Van Roie</div></body></html>";
// Additional CSS style to match Hass theme
static const char HASP_STYLE[] PROGMEM =
"<style>button{background-color:#03A9F4;}body{width:60%;margin:auto;}input:invalid{border:"
"1px solid red;}input[type=checkbox]{width:20px;}</style>";
// these need to be removed
uint8_t motionPin = 0; // GPIO input pin for motion sensor if connected and enabled
bool debugSerialEnabled = true; // Enable USB serial debug output
bool debugTelnetEnabled = false; // Enable telnet debug output
////////////////////////////////////////////////////////////////////////////////////////////////////
// These defaults may be overwritten with values saved by the web interface
char motionPinConfig[3] = "0";
////////////////////////////////////////////////////////////////////////////////////////////////////
// URL for auto-update "version.json"
const char UPDATE_URL[] = "http://haswitchplate.com/update/version.json";
// Default link to compiled Arduino firmware image
String espFirmwareUrl = "http://haswitchplate.com/update/HASwitchPlate.ino.d1_mini.bin";
// Default link to compiled Nextion firmware images
String lcdFirmwareUrl = "http://haswitchplate.com/update/HASwitchPlate.tft";
////////////////////////////////////////////////////////////////////////////////////////////////////
String formatBytes(size_t bytes);
////////////////////////////////////////////////////////////////////////////////////////////////////
bool httpIsAuthenticated(const String & page)
{
if(httpPassword[0] != '\0') { // Request HTTP auth if httpPassword is set
if(!webServer.authenticate(httpUser.c_str(), httpPassword.c_str())) {
webServer.requestAuthentication();
return false;
}
}
char buffer[128];
sprintf(buffer, PSTR("HTTP: Sending %s page to client connected from: %s"), page.c_str(),
webServer.client().remoteIP().toString().c_str());
debugPrintln(buffer);
return true;
}
void webSendPage(String & nodename, uint32_t httpdatalength, bool gohome = false)
{
char buffer[64];
/* Calculate Content Length upfront */
uint16_t contentLength = 0;
contentLength += sizeof(HTTP_DOCTYPE) - 1;
contentLength += sizeof(HTTP_HEADER) - 1 - 2 + nodename.length();
contentLength += sizeof(HTTP_SCRIPT) - 1;
contentLength += sizeof(HTTP_STYLE) - 1;
contentLength += sizeof(HASP_STYLE) - 1;
if(gohome) contentLength += sizeof(HTTP_META_GO_BACK) - 1;
contentLength += sizeof(HTTP_HEADER_END) - 1;
contentLength += sizeof(HTTP_END) - 1;
webServer.setContentLength(contentLength + httpdatalength);
webServer.send_P(200, PSTR("text/html"), HTTP_DOCTYPE); // 122
sprintf_P(buffer, HTTP_HEADER, nodename.c_str());
webServer.sendContent(buffer); // 17-2+len
webServer.sendContent_P(HTTP_SCRIPT); // 131
webServer.sendContent_P(HTTP_STYLE); // 487
webServer.sendContent_P(HASP_STYLE); // 145
if(gohome) webServer.sendContent_P(HTTP_META_GO_BACK); // 47
webServer.sendContent_P(HTTP_HEADER_END); // 80
}
void webHandleRoot()
{
if(!httpIsAuthenticated(F("root"))) return;
char buffer[64];
String nodename = haspGetNodename();
String httpMessage((char *)0);
httpMessage.reserve(1024);
httpMessage += String(F("<h1>"));
httpMessage += String(nodename);
httpMessage += String(F("</h1>"));
httpMessage += F("<p><form method='get' action='info'><button type='submit'>Information</button></form></p>");
httpMessage += F("<p><form method='get' action='config'><button type='submit'>Configuration</button></form></p>");
httpMessage +=
F("<p><form method='get' action='firmware'><button type='submit'>Firmware Upgrade</button></form></p>");
httpMessage +=
F("<p><form method='get' action='reboot'><button class='red' type='submit'>Restart</button></form></p>");
webSendPage(nodename, httpMessage.length(), false);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleAbout()
{ // http://plate01/about
if(!httpIsAuthenticated(F("/about"))) return;
String nodename = haspGetNodename();
String httpMessage((char *)0);
httpMessage.reserve(1250);
httpMessage += F("<p><h3>HASP OpenHardware edition</h3>Copyright&copy; 2020 Francis Van Roie "
"</br>MIT License</p>");
httpMessage += F("<p>Based on the previous work of the following open source developers.</p><hr>");
httpMessage +=
F("<p><h3>HASwitchPlate</h3>Copyright&copy; 2019 Allen Derusha allen@derusha.org</b></br>MIT License</p>");
httpMessage +=
F("<p><h3>LittlevGL</h3>Copyright&copy; 2016 G&aacute;bor Kiss-V&aacute;mosi</br>Copyright&copy; 2019 "
"LittlevGL</br>MIT License</p>");
httpMessage += F("<p><h3>Lvgl ziFont Font Engine</h3>Copyright&copy; 2020 Francis Van Roie</br>MIT License</p>");
httpMessage += F("<p><h3>TFT_eSPI Library</h3>Copyright&copy; 2017 Bodmer (https://github.com/Bodmer) All "
"rights reserved.</br>FreeBSD License</br>");
httpMessage +=
F("<i>includes parts from the Adafruit_GFX library - Copyright&copy; 2012 Adafruit Industries. All rights "
"reserved. BSD License</i></p>");
httpMessage += F("<p><h3>ArduinoJson</h3>Copyright&copy; 2014-2019 Benoit BLANCHON</br>MIT License</p>");
httpMessage += F("<p><h3>PubSubClient</h3>Copyright&copy; 2008-2015 Nicholas O'Leary</br>MIT License</p>");
httpMessage += F("<p><h3>Syslog</h3>Copyright&copy; 2016 Martin Sloup</br>MIT License</p>");
httpMessage += F("</p><p><form method='get' action='/'><button type='submit'>Main Menu</button></form>");
webSendPage(nodename, httpMessage.length(), false);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleInfo()
{ // http://plate01/
if(!httpIsAuthenticated(F("/info"))) return;
char buffer[64];
String nodename = haspGetNodename();
String httpMessage((char *)0);
httpMessage.reserve(1024);
httpMessage += F("<hr><b>MQTT Status: </b>");
if(mqttIsConnected()) { // Check MQTT connection
httpMessage += String(F("Connected"));
} else {
httpMessage += String(F("<font color='red'><b>Disconnected</b></font>, return code: "));
// +String(mqttClient.returnCode());
}
httpMessage += String(F("<br/><b>MQTT ClientID: </b>"));
// +String(mqttClientId);
httpMessage += F("<br/><b>HASP Version: </b>");
httpMessage += String(haspGetVersion());
httpMessage += F("<br/><b>Uptime: </b>");
httpMessage += String(long(millis() / 1000));
// httpMessage += String(F("<br/><b>LCD Model: </b>")) + String(LV_HASP_HOR_RES_MAX) + " x " +
// String(LV_HASP_VER_RES_MAX); httpMessage += String(F("<br/><b>LCD Version: </b>")) + String(lcdVersion);
httpMessage += F("</p/><p><b>LCD Active Page: </b>");
httpMessage += String(haspGetPage());
httpMessage += F("<br/><b>CPU Frequency: </b>");
httpMessage += String(ESP.getCpuFreqMHz());
httpMessage += F("MHz</p/><p><b>SSID: </b>");
httpMessage += String(WiFi.SSID());
httpMessage += F("</br><b>Signal Strength: </b>");
httpMessage += String(WiFi.RSSI());
httpMessage += F("</br><b>IP Address: </b>");
httpMessage += String(WiFi.localIP().toString());
httpMessage += F("</br><b>Gateway: </b>");
httpMessage += String(WiFi.gatewayIP().toString());
httpMessage += F("</br><b>DNS Server: </b>");
httpMessage += String(WiFi.dnsIP().toString());
httpMessage += F("</br><b>MAC Aress: </b>");
httpMessage += String(WiFi.macAddress());
httpMessage += F("</p/><p><b>ESP Chip Id: </b>");
#if defined(ARDUINO_ARCH_ESP32)
httpMessage += String(ESP.getChipRevision());
#else
httpMessage += String(ESP.getChipId());
#endif
httpMessage += F("<br/><b>Flash Chip Size: </b>");
httpMessage += formatBytes(ESP.getFlashChipSize());
httpMessage += F("</br><b>Program Size: </b>");
httpMessage += formatBytes(ESP.getSketchSize());
httpMessage += F(" bytes<br/><b>Free Program Space: </b>");
httpMessage += formatBytes(ESP.getFreeSketchSpace());
httpMessage += F(" bytes<br/><b>Free Memory: </b>");
httpMessage += formatBytes(ESP.getFreeHeap());
#if defined(ARDUINO_ARCH_ESP32)
// httpMessage += F("<br/><b>Heap Max Alloc: </b>");
// httpMessage += String(ESP.getMaxAllocHeap());
httpMessage += F("<br/><b>Memory Fragmentation: </b>");
httpMessage += String((int16_t)(100.00f - (float)ESP.getMaxAllocHeap() / (float)ESP.getFreeHeap() * 100.00f));
httpMessage += F("<br/><b>ESP SDK version: </b>");
httpMessage += String(ESP.getSdkVersion());
httpMessage += F("<br/><b>Last Reset: </b> CPU0: ");
httpMessage += String(esp32ResetReason(0));
httpMessage += F(" / CPU1: ");
httpMessage += String(esp32ResetReason(1));
#else
httpMessage += F("<br/><b>Memory Fragmentation: </b>");
httpMessage += String(ESP.getHeapFragmentation());
httpMessage += F("<br/><b>ESP Core version: </b>");
httpMessage += String(ESP.getCoreVersion());
httpMessage += F("<br/><b>Last Reset: </b>");
httpMessage += String(ESP.getResetInfo());
#endif
httpMessage += F("</p><p><form method='get' action='/'><button type='submit'>Main Menu</button></form>");
webSendPage(nodename, httpMessage.length(), false);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
}
String formatBytes(size_t bytes)
{
if(bytes < 1024) {
return String(bytes) + "B";
} else if(bytes < (1024 * 1024)) {
return String(bytes / 1024.0) + "KB";
} else if(bytes < (1024 * 1024 * 1024)) {
return String(bytes / 1024.0 / 1024.0) + "MB";
} else {
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
}
}
String getContentType(String filename)
{
if(webServer.hasArg(F("download"))) {
return F("application/octet-stream");
} else if(filename.endsWith(F(".htm"))) {
return F("text/html");
} else if(filename.endsWith(F(".html"))) {
return F("text/html");
} else if(filename.endsWith(F(".css"))) {
return F("text/css");
} else if(filename.endsWith(F(".js"))) {
return F("application/javascript");
} else if(filename.endsWith(F(".png"))) {
return F("image/png");
} else if(filename.endsWith(F(".gif"))) {
return F("image/gif");
} else if(filename.endsWith(F(".jpg"))) {
return F("image/jpeg");
} else if(filename.endsWith(F(".ico"))) {
return F("image/x-icon");
} else if(filename.endsWith(F(".xml"))) {
return F("text/xml");
} else if(filename.endsWith(F(".pdf"))) {
return F("application/x-pdf");
} else if(filename.endsWith(F(".zip"))) {
return F("application/x-zip");
} else if(filename.endsWith(F(".gz"))) {
return F("application/x-gzip");
}
return F("text/plain");
}
String urldecode(String str)
{
String encodedString = "";
char c;
char code0;
char code1;
for(int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if(c == '+') {
encodedString += ' ';
} else if(c == '%') {
char buffer[3];
i++;
buffer[0] = str.charAt(i);
i++;
buffer[1] = str.charAt(i);
buffer[2] = '\0';
c = (char)strtol((const char *)&buffer, NULL, 16);
encodedString += c;
} else {
encodedString += c;
}
yield();
}
return encodedString;
}
bool handleFileRead(String path)
{
path = urldecode(path).substring(0, 31);
if(!httpIsAuthenticated(path)) return false;
if(path.endsWith("/")) {
path += F("index.htm");
}
String pathWithGz = path + F(".gz");
if(filesystem->exists(pathWithGz) || filesystem->exists(path)) {
if(filesystem->exists(pathWithGz)) path += F(".gz");
File file = filesystem->open(path, "r");
String contentType = getContentType(path);
if(path == F("/edit.htm.gz")) {
contentType = F("text/html");
}
webServer.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void handleFileUpload()
{
if(webServer.uri() != "/edit") {
return;
}
HTTPUpload & upload = webServer.upload();
if(upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if(!filename.startsWith("/")) {
filename = "/" + filename;
}
debugPrintln(String(F("handleFileUpload Name: ")) + filename);
fsUploadFile = filesystem->open(filename, "w");
filename.clear();
} else if(upload.status == UPLOAD_FILE_WRITE) {
// DBG_OUTPUT_PORT.print("handleFileUpload Data: "); debugPrintln(upload.currentSize);
if(fsUploadFile) {
fsUploadFile.write(upload.buf, upload.currentSize);
}
} else if(upload.status == UPLOAD_FILE_END) {
if(fsUploadFile) {
fsUploadFile.close();
}
debugPrintln(String(F("handleFileUpload Size: ")) + String(upload.totalSize));
}
}
void handleFileDelete()
{
if(webServer.args() == 0) {
return webServer.send(500, PSTR("text/plain"), PSTR("BAD ARGS"));
}
String path = webServer.arg(0);
debugPrintln(String(F("handleFileDelete: ")) + path);
if(path == "/") {
return webServer.send(500, PSTR("text/plain"), PSTR("BAD PATH"));
}
if(!filesystem->exists(path)) {
return webServer.send(404, PSTR("text/plain"), PSTR("FileNotFound"));
}
filesystem->remove(path);
webServer.send(200, PSTR("text/plain"), "");
path.clear();
}
void handleFileCreate()
{
if(webServer.args() == 0) {
return webServer.send(500, PSTR("text/plain"), PSTR("BAD ARGS"));
}
String path = webServer.arg(0);
debugPrintln(String(F("handleFileCreate: ")) + path);
if(path == "/") {
return webServer.send(500, PSTR("text/plain"), PSTR("BAD PATH"));
}
if(filesystem->exists(path)) {
return webServer.send(500, PSTR("text/plain"), PSTR("FILE EXISTS"));
}
File file = filesystem->open(path, "w");
if(file) {
file.close();
} else {
return webServer.send(500, PSTR("text/plain"), PSTR("CREATE FAILED"));
}
webServer.send(200, PSTR("text/plain"), "");
path.clear();
}
void handleFileList()
{
if(!webServer.hasArg(F("dir"))) {
webServer.send(500, PSTR("text/plain"), PSTR("BAD ARGS"));
return;
}
String path = webServer.arg(F("dir"));
debugPrintln(String(F("handleFileList: ")) + path);
path.clear();
#if defined(ARDUINO_ARCH_ESP32)
debugPrintln(PSTR("HTTP: Listing files on the internal flash:"));
File root = SPIFFS.open("/");
File file = root.openNextFile();
String output = "[";
while(file) {
if(output != "[") {
output += ',';
}
bool isDir = false;
output += F("{\"type\":\"");
output += (isDir) ? F("dir") : F("file");
output += F("\",\"name\":\"");
if(file.name()[0] == '/') {
output += &(file.name()[1]);
} else {
output += file.name();
}
output += F("\"}");
char msg[64];
sprintf(msg, PSTR("HTTP: * %s (%u bytes)"), file.name(), (uint32_t)file.size());
debugPrintln(msg);
// file.close();
file = root.openNextFile();
}
output += "]";
#else
Dir dir = filesystem->openDir(path);
String output = "[";
while(dir.next()) {
File entry = dir.openFile("r");
if(output != "[") {
output += ',';
}
bool isDir = false;
output += F("{\"type\":\"");
output += (isDir) ? F("dir") : F("file");
output += F("\",\"name\":\"");
if(entry.name()[0] == '/') {
output += &(entry.name()[1]);
} else {
output += entry.name();
}
output += F("\"}");
entry.close();
}
output += "]";
#endif
webServer.send(200, PSTR("text/json"), output);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleConfig()
{ // http://plate01/
if(!httpIsAuthenticated(F("/config"))) return;
char buffer[64];
String nodename = haspGetNodename();
String httpMessage((char *)0);
httpMessage.reserve(1024);
httpMessage += String(F("<form method='POST' action='saveConfig'>"));
httpMessage += String(F("<b>WiFi SSID</b> <i><small>(required)</small></i><input id='wifiSSID' required "
"name='wifiSSID' maxlength=32 placeholder='WiFi SSID' value='")) +
String(WiFi.SSID()) + "'>";
httpMessage += String(F("<br/><b>WiFi Password</b> <i><small>(required)</small></i><input id='wifiPass' required "
"name='wifiPass' type='password' maxlength=64 placeholder='WiFi Password' value='")) +
String("********") + "'>";
httpMessage +=
F("<br/><br/><b>HASP Node Name</b> <i><small>(required. lowercase letters, numbers, and _ only)</small>"
"</i><input id='haspGetNodename()' required name='haspGetNodename()' maxlength=15 "
"placeholder='HASP Node Name' pattern='[a-z0-9_]*' value='");
httpMessage += nodename + "'>";
httpMessage += F("<br/><br/><b>Group Name</b> <i><small>(required)</small></i><input id='groupName' required "
"name='groupName' maxlength=15 placeholder='Group Name' value='");
httpMessage += mqttGetGroup() + "'>";
httpMessage += F("<br/><br/><b>MQTT Broker</b> <i><small>(required)</small></i><input id='mqttServer' required "
"name='mqttServer' maxlength=63 placeholder='mqttServer' value='");
httpMessage += mqttGetServer() + "'>";
httpMessage += F("<br/><b>MQTT Port</b> <i><small>(required)</small></i><input id='mqttPort' required "
"name='mqttPort' type='number' maxlength=5 placeholder='mqttPort' value='");
httpMessage += String(mqttGetPort()) + "'>";
httpMessage += F("<br/><b>MQTT User</b> <i><small>(optional)</small></i><input id='mqttUser' name='mqttUser' "
"maxlength=31 placeholder='mqttUser' value='");
httpMessage += mqttGetUser() + "'>";
httpMessage += F("<br/><b>MQTT Password</b> <i><small>(optional)</small></i><input id='mqttPassword' "
"name='mqttPassword' type='password' maxlength=31 placeholder='mqttPassword' value='");
if(mqttGetPassword() != "") httpMessage += String("********");
httpMessage += String(F("'><br/><br/><b>HASP Admin Username</b> <i><small>(optional)</small></i><input "
"id='httpUser' name='httpUser' maxlength=31 placeholder='Admin User' value='")) +
String(httpUser) + "'>";
httpMessage +=
String(F("<br/><b>HASP Admin Password</b> <i><small>(optional)</small></i><input id='httpPassword' "
"name='httpPassword' type='password' maxlength=31 placeholder='Admin User Password' value='"));
if(httpPassword.length() != 0) {
httpMessage += String("********");
}
httpMessage +=
String(F("'><br/><hr><b>Motion Sensor Pin:&nbsp;</b><select id='motionPinConfig' name='motionPinConfig'>"));
httpMessage += String(F("<option value='0'"));
if(!motionPin) {
httpMessage += String(F(" selected"));
}
httpMessage += String(F(">disabled/not installed</option><option value='D0'"));
if(motionPin == D0) {
httpMessage += String(F(" selected"));
}
httpMessage += String(F(">D0</option><option value='D1'"));
if(motionPin == D1) {
httpMessage += String(F(" selected"));
}
httpMessage += String(F(">D1</option><option value='D2'"));
if(motionPin == D2) {
httpMessage += String(F(" selected"));
}
httpMessage += String(F(">D2</option></select>"));
httpMessage += String(F("<br/><b>Serial debug output enabled:</b><input id='debugSerialEnabled' "
"name='debugSerialEnabled' type='checkbox'"));
if(debugSerialEnabled) {
httpMessage += String(F(" checked='checked'"));
}
httpMessage += String(F("><br/><b>Telnet debug output enabled:</b><input id='debugTelnetEnabled' "
"name='debugTelnetEnabled' type='checkbox'"));
if(debugTelnetEnabled) {
httpMessage += String(F(" checked='checked'"));
}
httpMessage += String(F("><br/><b>mDNS enabled:</b><input id='mdnsEnabled' name='mdnsEnabled' type='checkbox'"));
/*if (mdnsEnabled)
{
httpMessage += String(F(" checked='checked'"));
}
httpMessage += String(F("><br/><hr><button type='submit'>save settings</button></form>"));
if (updateEspAvailable)
{
httpMessage += String(F("<br/><hr><font color='green'><center><h3>HASP Update
available!</h3></center></font>")); httpMessage += String(F("<form method='get' action='espfirmware'>"));
httpMessage += String(F("<input id='espFirmwareURL' type='hidden' name='espFirmware' value='")) +
espFirmwareUrl
+ "'>"; httpMessage += String(F("<button type='submit'>update HASP to v")) + String(updateEspAvailableVersion) +
String(F("</button></form>"));
}*/
httpMessage += F("<hr><button type='submit'>Save Configuration</button></form>");
httpMessage += F("<p><form method='get' action='resetConfig'><button class='red' type='submit'>Factory Reset "
"All Settings</button></form></p>");
httpMessage += F("<hr><form method='get' action='/'><button type='submit'>Main Menu</button></form>");
webSendPage(nodename, httpMessage.length(), false);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpHandleNotFound()
{ // webServer 404
if(handleFileRead(webServer.uri())) return;
debugPrintln(String(F("HTTP: Sending 404 to client connected from: ")) + webServer.client().remoteIP().toString());
String httpMessage((char *)0);
httpMessage.reserve(128);
httpMessage += F("File Not Found\n\nURI: ");
httpMessage += webServer.uri();
httpMessage += F("\nMethod: ");
httpMessage += (webServer.method() == HTTP_GET) ? F("GET") : F("POST");
httpMessage += F("\nArguments: ");
httpMessage += webServer.args();
httpMessage += "\n";
for(uint8_t i = 0; i < webServer.args(); i++) {
httpMessage += " " + webServer.argName(i) + ": " + webServer.arg(i) + "\n";
}
webServer.send(404, PSTR("text/plain"), httpMessage.c_str());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleSaveConfig()
{
if(!httpIsAuthenticated(F("/saveconfig"))) return;
configWriteConfig();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleFirmware()
{
if(!httpIsAuthenticated(F("/firmware"))) return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpHandleEspFirmware()
{ // http://plate01/espfirmware
if(!httpIsAuthenticated(F("/espfirmware"))) return;
String nodename = haspGetNodename();
char buffer[64];
String httpMessage((char *)0);
httpMessage.reserve(128);
httpMessage += String(F("<h1>"));
httpMessage += String(haspGetNodename());
httpMessage += String(F(" ESP update</h1><br/>Updating ESP firmware from: "));
httpMessage += String(webServer.arg("espFirmware"));
webSendPage(nodename, httpMessage.length(), true);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
debugPrintln(String(F("HTTP: Attempting ESP firmware update from: ")) + String(webServer.arg("espFirmware")));
// espStartOta(webServer.arg("espFirmware"));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpHandleReboot()
{ // http://plate01/reboot
if(!httpIsAuthenticated(F("/reboot"))) return;
String nodename = haspGetNodename();
String httpMessage = F("Rebooting Device");
webSendPage(nodename, httpMessage.length(), true);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
delay(500);
debugPrintln(PSTR("HTTP: Reboot device"));
haspSetPage(0);
haspSetAttr(F("p[0].b[1].txt"), F("\"Rebooting...\""));
delay(500);
haspReset();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpHandleResetConfig()
{ // http://plate01/resetConfig
if(!httpIsAuthenticated(F("/espfirmware"))) return;
bool resetConfirmed = webServer.arg(F("confirm")) == F("yes");
String nodename = haspGetNodename();
char buffer[64];
String httpMessage((char *)0);
httpMessage.reserve(128);
if(resetConfirmed) { // User has confirmed, so reset everything
httpMessage += F("<h1>");
httpMessage += haspGetNodename();
httpMessage += F("</h1><b>Resetting all saved settings and restarting device into WiFi AP mode</b>");
} else {
httpMessage += F("<h1>Warning</h1><b>This process will reset all settings to the default values and "
"restart the device. You may need to connect to the WiFi AP displayed on the panel to "
"re-configure the device before accessing it again."
"<br/><hr><br/><form method='get' action='resetConfig'>"
"<br/><br/><button type='submit' name='confirm' value='yes'>reset all settings</button></form>"
"<br/><hr><br/><form method='get' action='/'>"
"<button type='submit'>return home</button></form>");
}
webSendPage(nodename, httpMessage.length(), resetConfirmed);
webServer.sendContent(httpMessage); // len
webServer.sendContent_P(HTTP_END); // 20
if(resetConfirmed) {
delay(1000);
// configClearSaved();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpSetup(const JsonObject & settings)
{
webServer.on(F("/page/"), []() {
String pageid = webServer.arg(F("page"));
webServer.send(200, PSTR("text/plain"), "Page: '" + pageid + "'");
haspSetPage(pageid.toInt());
});
webServer.on("/list", HTTP_GET, handleFileList);
// load editor
webServer.on("/edit", HTTP_GET, []() {
if(!handleFileRead("/edit.htm")) {
webServer.send(404, "text/plain", "FileNotFound");
}
});
webServer.on("/edit", HTTP_PUT, handleFileCreate);
webServer.on("/edit", HTTP_DELETE, handleFileDelete);
// first callback is called after the request has ended with all parsed arguments
// second callback handles file uploads at that location
webServer.on("/edit", HTTP_POST, []() { webServer.send(200, "text/plain", ""); }, handleFileUpload);
// get heap status, analog input value and all GPIO statuses in one json call
webServer.on("/all", HTTP_GET, []() {
String json('{');
json += "\"heap\":" + String(ESP.getFreeHeap());
json += ", \"analog\":" + String(analogRead(A0));
json += "}";
webServer.send(200, "text/json", json);
json.clear();
});
webServer.on(F("/"), webHandleRoot);
webServer.on(F("/about"), webHandleAbout);
webServer.on(F("/info"), webHandleInfo);
webServer.on(F("/config"), webHandleConfig);
webServer.on(F("/saveConfig"), webHandleSaveConfig);
webServer.on(F("/resetConfig"), httpHandleResetConfig);
webServer.on(F("/firmware"), webHandleFirmware);
webServer.on(F("/espfirmware"), httpHandleEspFirmware);
webServer.on(F("/reboot"), httpHandleReboot);
webServer.onNotFound(httpHandleNotFound);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpReconnect()
{
if(!httpEnable) return;
webServer.stop();
webServer.begin();
debugPrintln(String(F("HTTP: Server started @ http://")) + WiFi.localIP().toString());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void httpLoop(bool wifiIsConnected)
{
if(httpEnable) webServer.handleClient();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool httpGetConfig(const JsonObject & settings)
{
if(!settings.isNull() && settings[F_CONFIG_ENABLE] == httpEnable && settings[F_CONFIG_PORT] == httpPort &&
settings[F_CONFIG_USER] == httpUser && settings[F_CONFIG_PASS] == httpPassword)
return false;
settings[F_CONFIG_ENABLE] = httpEnable;
settings[F_CONFIG_PORT] = httpPort;
settings[F_CONFIG_USER] = httpUser;
settings[F_CONFIG_PASS] = httpPassword;
size_t size = serializeJson(settings, Serial);
Serial.println();
return true;
}

43
src/hasp_log.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <Arduino.h>
#ifdef ESP8266
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#else
#include <Wifi.h>
#endif
#include <WiFiUdp.h>
#include <Syslog.h>
#include "hasp_log.h"
#include "hasp_debug.h"
void debugPrintln(String debugText)
{
serialPrintln(debugText);
// if(WiFi.isConnected()) syslog.log(LOG_INFO, debugText);
}
void errorPrintln(String debugText)
{
char buffer[256];
sprintf_P(buffer, debugText.c_str(), PSTR("[ERROR] "));
serialPrintln(buffer);
if(WiFi.isConnected()) {
char buffer[256];
sprintf_P(buffer, debugText.c_str(), "");
// syslog.log(LOG_ERR, buffer);
}
}
void warningPrintln(String debugText)
{
char buffer[256];
sprintf_P(buffer, debugText.c_str(), PSTR("[WARNING] "));
serialPrintln(buffer);
if(WiFi.isConnected()) {
char buffer[256];
sprintf_P(buffer, debugText.c_str(), "");
// syslog.log(LOG_WARNING, buffer);
}
}

55
src/hasp_mdns.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "Arduino.h"
#include "ArduinoJson.h"
#ifdef ESP32
#include <ESPmDNS.h>
#else
#include <ESP8266mDNS.h>
MDNSResponder::hMDNSService hMDNSService;
#endif
#include "hasp_mdns.h"
const char F_CONFIG_ENABLE[] PROGMEM = "enable";
bool mdnsEnabled = true;
String hasp2Node = "plate01";
const float haspVersion = 0.38;
void mdnsSetup(const JsonObject & settings)
{
if(mdnsEnabled) {
// Setup mDNS service discovery if enabled
// MDNS.addService(String(hasp2Node), String("tcp"), 80);
/*if(debugTelnetEnabled) {
MDNS.addService(haspNode, "telnet", "tcp", 23);
}*/
// MDNS.addServiceTxt(hasp2Node, "tcp", "app_name", "HASwitchPlate");
// MDNS.addServiceTxt(hasp2Node, "tcp", "app_version", String(haspVersion).c_str());
MDNS.begin(hasp2Node.c_str());
}
}
void mdnsLoop(bool wifiIsConnected)
{
// if(mdnsEnabled) {
// MDNS();
// }s
}
void mdnsStop()
{
MDNS.end();
}
bool mdnsGetConfig(const JsonObject & settings)
{
if(!settings.isNull() && settings[F_CONFIG_ENABLE] == mdnsEnabled) return false;
settings[F_CONFIG_ENABLE] = mdnsEnabled;
size_t size = serializeJson(settings, Serial);
Serial.println();
return true;
}

422
src/hasp_mqtt.cpp Normal file
View File

@ -0,0 +1,422 @@
#include <Arduino.h>
#include "ArduinoJson.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <Wifi.h>
#else
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <ESP.h>
#include <DNSServer.h>
#endif
#include <PubSubClient.h>
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_config.h"
#include "hasp_mqtt.h"
#include "hasp_wifi.h"
#include "hasp.h"
#include "user_config_override.h"
// Size of buffer for incoming MQTT message
#define mqttMaxPacketSize 2u * 1024u
String mqttClientId; // Auto-generated MQTT ClientID
/*
String mqttGetSubtopic; // MQTT subtopic for incoming commands requesting .val
String mqttGetSubtopicJSON; // MQTT object buffer for JSON status when requesting .val
String mqttStateTopic; // MQTT topic for outgoing panel interactions
String mqttStateJSONTopic; // MQTT topic for outgoing panel interactions in JSON format
String mqttCommandTopic; // MQTT topic for incoming panel commands
String mqttGroupCommandTopic; // MQTT topic for incoming group panel commands
String mqttStatusTopic; // MQTT topic for publishing device connectivity state
String mqttSensorTopic; // MQTT topic for publishing device information in JSON format
*/
String mqttLightCommandTopic; // MQTT topic for incoming panel backlight on/off commands
String mqttLightStateTopic; // MQTT topic for outgoing panel backlight on/off state
String mqttLightBrightCommandTopic; // MQTT topic for incoming panel backlight dimmer commands
String mqttLightBrightStateTopic; // MQTT topic for outgoing panel backlight dimmer state
// String mqttMotionStateTopic; // MQTT topic for outgoing motion sensor state
String mqttNodeTopic;
String mqttGroupTopic;
////////////////////////////////////////////////////////////////////////////////////////////////////
// These defaults may be overwritten with values saved by the web interface
char mqttServer[64] = MQTT_HOST;
uint16_t mqttPort = MQTT_PORT;
char mqttUser[32] = MQTT_USER;
char mqttPassword[32] = MQTT_PASSW;
// char haspNode[16] = "plate01";
String mqttGroupName = "plates";
/*
const String mqttCommandSubscription = mqttCommandTopic + "/#";
const String mqttGroupCommandSubscription = mqttGroupCommandTopic + "/#";
const String mqttLightSubscription = "hasp/" + String(haspGetNodename()) + "/light/#";
const String mqttLightBrightSubscription = "hasp/" + String(haspGetNodename()) + "/brightness/#";
*/
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Send changed values OUT
void mqttSendNewEvent(uint8_t pageid, uint8_t btnid, int32_t val)
{
char topic[72];
sprintf_P(topic, PSTR("hasp/%s/state/p[%u].b[%u].event"), haspGetNodename().c_str(), pageid, btnid);
char value[32];
itoa(val, value, 10);
mqttClient.publish(topic, value);
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + String(value));
// as json
sprintf_P(topic, PSTR("hasp/%s/state/json"), haspGetNodename().c_str(), pageid, btnid);
sprintf_P(value, PSTR("{\"event\":\"p[%u]].b[%u].event\", \"value\":%u}"), pageid, btnid, val);
mqttClient.publish(topic, value);
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + String(value));
}
void mqttSendNewValue(uint8_t pageid, uint8_t btnid, int32_t val)
{
char topic[72];
sprintf_P(topic, PSTR("hasp/%s/state/p[%u].b[%u].val"), haspGetNodename().c_str(), pageid, btnid);
char value[32];
itoa(val, value, 10);
mqttClient.publish(topic, value);
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + String(value));
// as json
sprintf_P(topic, PSTR("hasp/%s/state/json"), haspGetNodename().c_str(), pageid, btnid);
sprintf_P(value, PSTR("{\"event\":\"p[%u]].b[%u].val\", \"value\":%u}"), pageid, btnid, val);
mqttClient.publish(topic, value);
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + String(value));
}
void mqttSendNewValue(uint8_t pageid, uint8_t btnid, String txt)
{
char topic[72];
sprintf_P(topic, PSTR("hasp/%s/state/p[%u].b[%u].txt"), haspGetNodename().c_str(), pageid, btnid);
mqttClient.publish(topic, txt.c_str());
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + txt);
// as json
char value[64];
sprintf_P(topic, PSTR("hasp/%s/state/json"), haspGetNodename().c_str(), pageid, btnid);
sprintf_P(value, PSTR("{\"event\":\"p[%u]].b[%u].txt\", \"value\":\"%s\"}"), pageid, btnid, txt.c_str());
mqttClient.publish(topic, value);
debugPrintln(String(F("MQTT OUT: ")) + String(topic) + " = " + String(value));
}
void mqttHandlePage(String strPageid)
{
if(strPageid.length() == 0) {
String strPayload = String(haspGetPage());
String topic = mqttNodeTopic + F("state/page");
char buffer[64];
sprintf_P(buffer, PSTR("MQTT OUT: %s = %s"), topic.c_str(), strPayload.c_str());
debugPrintln(buffer);
mqttClient.publish(topic.c_str(), strPayload.c_str());
} else {
if(strPageid.toInt() <= 250) haspSetPage(strPageid.toInt());
}
}
void mqttHandleJson(String & strPayload)
{ // Parse an incoming JSON array into individual Nextion commands
if(strPayload.endsWith(
",]")) { // Trailing null array elements are an artifact of older Home Assistant automations and need to
// be removed before parsing by ArduinoJSON 6+
strPayload.remove(strPayload.length() - 2, 2);
strPayload.concat("]");
}
DynamicJsonDocument nextionCommands(mqttMaxPacketSize + 1024);
DeserializationError jsonError = deserializeJson(nextionCommands, strPayload);
if(jsonError) { // Couldn't parse incoming JSON command
debugPrintln(String(F("MQTT: [ERROR] Failed to parse incoming JSON command with error: ")) +
String(jsonError.c_str()));
return;
}
for(uint8_t i = 0; i < nextionCommands.size(); i++) {
debugPrintln(nextionCommands[i]);
// nextionSendCmd(nextionCommands[i]);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Receive incoming messages
void mqttCallback(char * topic, byte * payload, unsigned int length)
{ // Handle incoming commands from MQTT
payload[length] = '\0';
String strTopic = topic;
String strPayload = (char *)payload;
// strTopic: homeassistant/haswitchplate/devicename/command/p[1].b[4].txt
// strPayload: "Lights On"
// subTopic: p[1].b[4].txt
// Incoming Namespace (replace /device/ with /group/ for group commands)
// '[...]/device/command' -m '' = No command requested, respond with mqttStatusUpdate()
// '[...]/device/command' -m 'dim=50' = nextionSendCmd("dim=50")
// '[...]/device/command/json' -m '["dim=5", "page 1"]' = nextionSendCmd("dim=50"), nextionSendCmd("page 1")
// '[...]/device/command/page' -m '1' = nextionSendCmd("page 1")
// '[...]/device/command/statusupdate' -m '' = mqttStatusUpdate()
// '[...]/device/command/lcdupdate' -m 'http://192.168.0.10/local/HASwitchPlate.tft' =
// nextionStartOtaDownload("http://192.168.0.10/local/HASwitchPlate.tft")
// '[...]/device/command/lcdupdate' -m '' = nextionStartOtaDownload("lcdFirmwareUrl")
// '[...]/device/command/espupdate' -m 'http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin' =
// espStartOta("http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin")
// '[...]/device/command/espupdate' -m '' = espStartOta("espFirmwareUrl")
// '[...]/device/command/p[1].b[4].txt' -m '' = nextionGetAttr("p[1].b[4].txt")
// '[...]/device/command/p[1].b[4].txt' -m '"Lights On"' = nextionSetAttr("p[1].b[4].txt", "\"Lights On\"")
debugPrintln(String(F("MQTT IN: '")) + strTopic + "' : '" + strPayload + "'");
if(strTopic.startsWith(mqttNodeTopic)) {
strTopic = strTopic.substring(mqttNodeTopic.length(), strTopic.length());
} else if(strTopic.startsWith(mqttGroupTopic)) {
strTopic = strTopic.substring(mqttGroupTopic.length(), strTopic.length());
} else {
return;
}
// debugPrintln(String(F("MQTT Short Topic : '")) + strTopic + "'");
if(strTopic == F("command")) {
if(strPayload == "") { // '[...]/device/command' -m '' = No command requested, respond with mqttStatusUpdate()
// mqttStatusUpdate(); // return status JSON via MQTT
} else { // '[...]/device/command' -m 'dim=50' == nextionSendCmd("dim=50")
// nextionSendCmd(strPayload);
}
return;
}
if(strTopic.startsWith(F("command/"))) {
strTopic = strTopic.substring(8u, strTopic.length());
// debugPrintln(String(F("MQTT Shorter Command Topic : '")) + strTopic + "'");
if(strTopic == F("page")) { // '[...]/device/command/page' -m '1' == nextionSendCmd("page 1")
mqttHandlePage(strPayload);
} else if(strTopic == F("dim")) { // '[...]/device/command/page' -m '1' == nextionSendCmd("page 1")
#if defined(ARDUINO_ARCH_ESP32)
ledcWrite(0, map(strPayload.toInt(), 0, 100, 0, 1023)); // ledChannel and value
#else
analogWrite(D1, map(strPayload.toInt(), 0, 100, 0, 1023));
#endif
} else if(strTopic == F("json")) { // '[...]/device/command/json' -m '["dim=5", "page 1"]' =
// nextionSendCmd("dim=50"), nextionSendCmd("page 1")
mqttHandleJson(strPayload); // Send to nextionParseJson()
} else if(strTopic == F("statusupdate")) { // '[...]/device/command/statusupdate' == mqttStatusUpdate()
// mqttStatusUpdate(); // return status JSON via MQTT
} else if(strTopic == F("espupdate")) { // '[...]/device/command/espupdate' -m
// 'http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin' ==
// espStartOta("http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin")
if(strPayload == "") {
// espStartOta(espFirmwareUrl);
} else {
// espStartOta(strPayload);
}
} else if(strTopic == F("reboot")) { // '[...]/device/command/reboot' == reboot microcontroller)
debugPrintln(F("MQTT: Rebooting device"));
haspReset();
} else if(strTopic == F("lcdreboot")) { // '[...]/device/command/lcdreboot' == reboot LCD panel)
debugPrintln(F("MQTT: Rebooting LCD"));
haspReset();
} else if(strTopic == F("factoryreset")) { // '[...]/device/command/factoryreset' == clear all saved settings)
// configClearSaved();
} else if(strPayload == "") { // '[...]/device/command/p[1].b[4].txt' -m '' == nextionGetAttr("p[1].b[4].txt")
haspProcessAttribute(strTopic, "");
} else { // '[...]/device/command/p[1].b[4].txt' -m '"Lights On"' ==
// nextionSetAttr("p[1].b[4].txt", "\"Lights On\"")
haspProcessAttribute(strTopic, strPayload);
}
return;
}
if(strTopic == mqttLightBrightCommandTopic) { // change the brightness from the light topic
int panelDim = map(strPayload.toInt(), 0, 255, 0, 100);
// nextionSetAttr("dim", String(panelDim));
// nextionSendCmd("dims=dim");
// mqttClient.publish(mqttLightBrightStateTopic, strPayload);
} else if(strTopic == mqttLightCommandTopic &&
strPayload == F("OFF")) { // set the panel dim OFF from the light topic, saving current dim level first
// nextionSendCmd("dims=dim");
// nextionSetAttr("dim", "0");
mqttClient.publish(mqttLightStateTopic.c_str(), PSTR("OFF"));
} else if(strTopic == mqttLightCommandTopic &&
strPayload == F("ON")) { // set the panel dim ON from the light topic, restoring saved dim level
// nextionSendCmd("dim=dims");
mqttClient.publish(mqttLightStateTopic.c_str(), PSTR("ON"));
}
if(strTopic == F("status") &&
strPayload == F("OFF")) { // catch a dangling LWT from a previous connection if it appears
char topicBuffer[64];
sprintf_P(topicBuffer, PSTR("%sstatus"), mqttNodeTopic.c_str());
debugPrintln(String(F("MQTT: binary_sensor state: [")) + topicBuffer + "] : ON");
mqttClient.publish(topicBuffer, "ON", true);
return;
}
}
void mqttReconnect()
{
static uint8_t mqttReconnectCount = 0;
bool mqttFirstConnect = true;
String nodeName = haspGetNodename();
// Generate an MQTT client ID as haspNode + our MAC address
mqttClientId = nodeName + "-" + WiFi.macAddress();
char topicBuffer[64];
sprintf_P(topicBuffer, PSTR("hasp/%s/"), nodeName.c_str());
mqttNodeTopic = topicBuffer;
sprintf_P(topicBuffer, PSTR("hasp/%s/"), mqttGroupName.c_str());
mqttGroupTopic = topicBuffer;
// haspSetPage(0);
debugPrintln(String(F("MQTT: Attempting connection to broker ")) + String(mqttServer) + String(F(" as clientID ")) +
mqttClientId);
// Attempt to connect and set LWT and Clean Session
sprintf_P(topicBuffer, PSTR("%sstatus"), mqttNodeTopic.c_str());
if(!mqttClient.connect(mqttClientId.c_str(), mqttUser, mqttPassword, topicBuffer, 0, false, "OFF", true)) {
// Retry until we give up and restart after connectTimeout seconds
mqttReconnectCount++;
Serial.print(String(F("failed, rc=")));
Serial.print(mqttClient.state());
// Wait 5 seconds before retrying
// delay(50);
return;
}
debugPrintln(F("MQTT: [SUCCESS] MQTT Client is Connected"));
haspReconnect();
/*
// MQTT topic string definitions
mqttStateTopic = prefix + F("/state");
mqttStateJSONTopic = prefix + F("/state/json");
mqttCommandTopic = prefix + F("/page");
mqttGroupCommandTopic = "hasp/" + mqttGroupName + "/page";
mqttCommandTopic = prefix + F("/command");
mqttGroupCommandTopic = "hasp/" + mqttGroupName + "/command";
mqttSensorTopic = prefix + F("/sensor");
mqttLightCommandTopic = prefix + F("/light/switch");
mqttLightStateTopic = prefix + F("/light/state");
mqttLightBrightCommandTopic = prefix + F("/brightness/set");
mqttLightBrightStateTopic = prefix + F("/brightness/state");
mqttMotionStateTopic = prefix + F("/motion/state");
*/
// Set keepAlive, cleanSession, timeout
// mqttClient.setOptions(30, true, 5000);
// declare LWT
// mqttClient.setWill(mqttStatusTopic.c_str(), "OFF");
// Attempt to connect to broker, setting last will and testament
// Subscribe to our incoming topics
sprintf_P(topicBuffer, PSTR("%scommand/#"), mqttGroupTopic.c_str());
if(mqttClient.subscribe(topicBuffer)) {
debugPrintln(String(F("MQTT: * Subscribed to ")) + topicBuffer);
}
sprintf_P(topicBuffer, PSTR("%scommand/#"), mqttNodeTopic.c_str());
if(mqttClient.subscribe(topicBuffer)) {
debugPrintln(String(F("MQTT: * Subscribed to ")) + topicBuffer);
}
sprintf_P(topicBuffer, PSTR("%slight/#"), mqttNodeTopic.c_str());
if(mqttClient.subscribe(topicBuffer)) {
debugPrintln(String(F("MQTT: * Subscribed to ")) + topicBuffer);
}
sprintf_P(topicBuffer, PSTR("%sbrightness/#"), mqttNodeTopic.c_str());
if(mqttClient.subscribe(topicBuffer)) {
debugPrintln(String(F("MQTT: * Subscribed to ")) + topicBuffer);
}
sprintf_P(topicBuffer, PSTR("%sstatus"), mqttNodeTopic.c_str());
if(mqttClient.subscribe(topicBuffer)) {
debugPrintln(String(F("MQTT: * Subscribed to ")) + topicBuffer);
}
// Force any subscribed clients to toggle OFF/ON when we first connect to
// make sure we get a full panel refresh at power on. Sending OFF,
// "ON" will be sent by the mqttStatusTopic subscription action.
debugPrintln(String(F("MQTT: binary_sensor state: [")) + topicBuffer + "] : " + (mqttFirstConnect ? "OFF" : "ON"));
mqttClient.publish(topicBuffer, mqttFirstConnect ? "OFF" : "ON", true); //, 1);
mqttFirstConnect = false;
mqttReconnectCount = 0;
}
void mqttSetup(const JsonObject & settings)
{
if(!settings[F_CONFIG_HOST].isNull()) {
strcpy(mqttServer, settings[F_CONFIG_HOST]);
}
if(!settings[F_CONFIG_PORT].isNull()) {
mqttPort = settings[F_CONFIG_PORT];
}
if(!settings[F_CONFIG_USER].isNull()) {
strcpy(mqttUser, settings[F_CONFIG_USER]);
}
if(!settings[F_CONFIG_PASS].isNull()) {
strcpy(mqttPassword, settings[F_CONFIG_PASS]);
}
if(!settings[F_CONFIG_GROUP].isNull()) {
mqttGroupName = settings[F_CONFIG_GROUP].as<String>();
}
mqttClient.setServer(mqttServer, 1883);
mqttClient.setCallback(mqttCallback);
}
void mqttLoop(bool wifiIsConnected)
{
if(wifiIsConnected && !mqttClient.connected())
mqttReconnect();
else
mqttClient.loop();
}
bool mqttIsConnected()
{
return mqttClient.connected();
}
void mqttStop()
{
if(mqttClient.connected()) {
char topicBuffer[64];
sprintf_P(topicBuffer, PSTR("%sstatus"), mqttNodeTopic.c_str());
mqttClient.publish(topicBuffer, "OFF");
sprintf_P(topicBuffer, PSTR("%ssensor"), mqttNodeTopic.c_str());
mqttClient.publish(topicBuffer, "{\"status\": \"unavailable\"}");
mqttClient.disconnect();
debugPrintln(String(F("MQTT: Disconnected from broker")));
}
}
bool mqttGetConfig(const JsonObject & settings)
{
if(!settings.isNull() && settings[F_CONFIG_HOST] == mqttServer && settings[F_CONFIG_PORT] == mqttPort &&
settings[F_CONFIG_USER] == mqttUser && settings[F_CONFIG_PASS] == mqttPassword &&
settings[F_CONFIG_GROUP] == mqttGroupName)
return false;
settings[F_CONFIG_GROUP] = mqttGroupName;
settings[F_CONFIG_HOST] = mqttServer;
settings[F_CONFIG_PORT] = mqttPort;
settings[F_CONFIG_USER] = mqttUser;
settings[F_CONFIG_PASS] = mqttPassword;
size_t size = serializeJson(settings, Serial);
Serial.println();
return true;
}

64
src/hasp_ota.cpp Normal file
View File

@ -0,0 +1,64 @@
#include <Arduino.h>
#include "ArduinoJson.h"
#include <ArduinoOTA.h>
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_ota.h"
#include "hasp.h"
#define F_OTA_URL F("otaurl")
std::string otaUrl = "http://ota.local";
void otaSetup(JsonObject settings)
{
char buffer[256];
if(!settings[F_OTA_URL].isNull()) {
otaUrl = settings[F_OTA_URL].as<String>().c_str();
sprintf_P(buffer, PSTR("ORA url: %s"), otaUrl.c_str());
debugPrintln(buffer);
}
ArduinoOTA.setHostname(String(haspGetNodename()).c_str());
// ArduinoOTA.setPassword(configPassword);
ArduinoOTA.onStart([]() {
debugPrintln(F("OTA: update start"));
haspSendCmd("page 0");
haspSetAttr("p[0].b[1].txt", "\"ESP OTA Update\"");
});
ArduinoOTA.onEnd([]() {
haspSendCmd("page 0");
debugPrintln(F("OTA: update complete"));
haspSetAttr("p[0].b[1].txt", "\"ESP OTA Update\\rComplete!\"");
haspReset();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
haspSetAttr("p[0].b[1].txt", "\"ESP OTA Update\\rProgress: " + String(progress / (total / 100)) + "%\"");
});
ArduinoOTA.onError([](ota_error_t error) {
debugPrintln(String(F("OTA: ERROR code ")) + String(error));
if(error == OTA_AUTH_ERROR)
debugPrintln(F("OTA: ERROR - Auth Failed"));
else if(error == OTA_BEGIN_ERROR)
debugPrintln(F("OTA: ERROR - Begin Failed"));
else if(error == OTA_CONNECT_ERROR)
debugPrintln(F("OTA: ERROR - Connect Failed"));
else if(error == OTA_RECEIVE_ERROR)
debugPrintln(F("OTA: ERROR - Receive Failed"));
else if(error == OTA_END_ERROR)
debugPrintln(F("OTA: ERROR - End Failed"));
haspSetAttr("p[0].b[1].txt", "\"ESP OTA FAILED\"");
delay(5000);
// haspSendCmd("page " + String(nextionActivePage));
});
ArduinoOTA.begin();
debugPrintln(F("OTA: Over the Air firmware update ready"));
}
void otaLoop()
{
ArduinoOTA.handle(); // Arduino OTA loop
}

57
src/hasp_spiffs.cpp Normal file
View File

@ -0,0 +1,57 @@
#include <Arduino.h>
#include "ArduinoJson.h"
#include "hasp_conf.h"
#include "hasp_log.h"
#include "hasp_spiffs.h"
#if LV_USE_HASP_SPIFFS
#if defined(ARDUINO_ARCH_ESP32)
#include "SPIFFS.h"
#endif
#include <FS.h> // Include the SPIFFS library
#endif
/*
void spiffsList()
{
#if defined(ARDUINO_ARCH_ESP32)
debugPrintln(PSTR("FILE: Listing files on the internal flash:"));
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file) {
char msg[64];
sprintf(msg, PSTR("FILE: * %s (%u bytes)"), file.name(), (uint32_t)file.size());
debugPrintln(msg);
file = root.openNextFile();
}
#endif
#if defined(ARDUINO_ARCH_ESP8266)
debugPrintln(PSTR("FILE: Listing files on the internal flash:"));
Dir dir = SPIFFS.openDir("/");
while(dir.next()) {
char msg[64];
sprintf(msg, PSTR("FILE: * %s (%u bytes)"), dir.fileName().c_str(), (uint32_t)dir.fileSize());
debugPrintln(msg);
}
#endif
}
*/
void spiffsSetup()
{
// no spiffs settings
#if LV_USE_HASP_SPIFFS
char msg[64];
if(!SPIFFS.begin()) {
sprintf(msg, PSTR("FILE: %sSPI flash init failed. Unable to mount FS."));
errorPrintln(msg);
} else {
sprintf(msg, PSTR("FILE: [SUCCESS] SPI flash FS mounted"));
debugPrintln(msg);
// spiffsList();
}
#endif
}
void spiffsLoop()
{}

229
src/hasp_tft.cpp Normal file
View File

@ -0,0 +1,229 @@
#include "TFT_eSPI.h" // Graphics and font library for ST7735 driver chip
#include "ArduinoJson.h"
#ifdef ESP8266
ADC_MODE(ADC_VCC); // tftShowConfig measures the voltage on the pin
#endif
#include "hasp_log.h"
#include "hasp_tft.h"
#define F_TFT_ROTATION F("rotation")
#define F_TFT_FREQUENCY F("frequency")
int8_t getPinName(int8_t pin);
void tftSetup(TFT_eSPI & tft, JsonObject settings)
{
uint8_t rotation = TFT_ROTATION;
if(settings[F_TFT_ROTATION]) rotation = settings[F_TFT_ROTATION];
uint32_t frequency = SPI_FREQUENCY;
if(settings[F_TFT_FREQUENCY]) frequency = settings[F_TFT_FREQUENCY];
char buffer[64];
sprintf_P(buffer, PSTR("TFT: %d rotation / %d frequency"), rotation, frequency);
debugPrintln(buffer);
tft.begin(); /* TFT init */
tft.setRotation(rotation); /* 1/3=Landscape or 0/2=Portrait orientation */
tftShowConfig(tft);
/* Load Calibration data */
#ifdef ESP8266
uint16_t calData[5] = {238, 3529, 369, 3532, 6};
#else
uint16_t calData[5] = {294, 3410, 383, 3491, 4};
#endif
// uint16_t calData[5] = {0, 0, 0, 0, 0};
uint8_t calDataOK = 0;
// Calibrate
if(0) {
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 0);
// tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println(PSTR("Touch corners as indicated"));
tft.setTextFont(1);
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
for(uint8_t i = 0; i < 5; i++) {
Serial.print(calData[i]);
if(i < 4) Serial.print(", ");
}
}
tft.setTouch(calData);
}
void tftLoop()
{
// Nothing to do here
}
void tftStop()
{}
void tftOffsetInfo(uint8_t pin, uint8_t x_offset, uint8_t y_offset)
{
char buffer[64];
if(x_offset != 0) {
sprintf_P(buffer, PSTR("TFT: R%u x offset = %i"), pin, x_offset);
debugPrintln(buffer);
}
if(y_offset != 0) {
sprintf_P(buffer, PSTR("TFT: R%u y offset = %i"), pin, y_offset);
debugPrintln(buffer);
}
}
void tftPinInfo(String pinfunction, int8_t pin)
{
if(pin != -1) {
char buffer[64];
sprintf_P(buffer, PSTR("TFT: %s = D%i (GPIO %i)"), pinfunction.c_str(), getPinName(pin), pin);
debugPrintln(buffer);
}
}
void tftCalibrate()
{}
void tftShowConfig(TFT_eSPI & tft)
{
setup_t tftSetup;
char buffer[128];
tft.getSetup(tftSetup);
sprintf_P(buffer, PSTR("TFT: TFT_eSPI ver = %s"), tftSetup.version.c_str());
debugPrintln(buffer);
sprintf_P(buffer, PSTR("TFT: Processor = ESP%i"), tftSetup.esp, HEX);
debugPrintln(buffer);
sprintf_P(buffer, PSTR("TFT: Frequency = %i MHz"), ESP.getCpuFreqMHz());
debugPrintln(buffer);
#ifdef ESP8266
sprintf_P(buffer, PSTR("TFT: Voltage = %2.2f V"), ESP.getVcc() / 918.0);
debugPrintln(buffer); // 918 empirically determined
#endif
sprintf_P(buffer, PSTR("TFT: Transactions = %s"), (tftSetup.trans == 1) ? PSTR("Yes") : PSTR("No"));
debugPrintln(buffer);
sprintf_P(buffer, PSTR("TFT: Interface = %s"), (tftSetup.serial == 1) ? PSTR("SPI") : PSTR("Parallel"));
debugPrintln(buffer);
#ifdef ESP8266
sprintf_P(buffer, PSTR("TFT: SPI overlap = %s"), (tftSetup.overlap == 1) ? PSTR("Yes") : PSTR("No"));
debugPrintln(buffer);
#endif
if(tftSetup.tft_driver != 0xE9D) // For ePaper displays the size is defined in the sketch
{
sprintf_P(buffer, PSTR("TFT: Display driver = %i"), tftSetup.tft_driver);
debugPrintln(buffer);
sprintf_P(buffer, PSTR("TFT: Display width = %i"), tftSetup.tft_width);
debugPrintln(buffer);
sprintf_P(buffer, PSTR("TFT: Display height = %i"), tftSetup.tft_height);
debugPrintln(buffer);
} else if(tftSetup.tft_driver == 0xE9D)
debugPrintln(F("Display driver = ePaper"));
// Offsets, not all used yet
tftOffsetInfo(0, tftSetup.r0_x_offset, tftSetup.r0_y_offset);
tftOffsetInfo(1, tftSetup.r1_x_offset, tftSetup.r1_y_offset);
tftOffsetInfo(2, tftSetup.r2_x_offset, tftSetup.r2_y_offset);
tftOffsetInfo(3, tftSetup.r3_x_offset, tftSetup.r3_y_offset);
/* replaced by tftOffsetInfo
if(tftSetup.r1_x_offset != 0) Serial.printf("R1 x offset = %i \n", tftSetup.r1_x_offset);
if(tftSetup.r1_y_offset != 0) Serial.printf("R1 y offset = %i \n", tftSetup.r1_y_offset);
if(tftSetup.r2_x_offset != 0) Serial.printf("R2 x offset = %i \n", tftSetup.r2_x_offset);
if(tftSetup.r2_y_offset != 0) Serial.printf("R2 y offset = %i \n", tftSetup.r2_y_offset);
if(tftSetup.r3_x_offset != 0) Serial.printf("R3 x offset = %i \n", tftSetup.r3_x_offset);
if(tftSetup.r3_y_offset != 0) Serial.printf("R3 y offset = %i \n", tftSetup.r3_y_offset);
*/
tftPinInfo(F("MOSI "), tftSetup.pin_tft_mosi);
tftPinInfo(F("MISO "), tftSetup.pin_tft_miso);
tftPinInfo(F("SCLK "), tftSetup.pin_tft_clk);
#ifdef ESP8266
if(tftSetup.overlap == true) {
debugPrintln(F("Overlap selected, following pins MUST be used:\n"));
debugPrintln(F("MOSI = SD1 (GPIO 8)\n"));
debugPrintln(F("MISO = SD0 (GPIO 7)\n"));
debugPrintln(F("SCK = CLK (GPIO 6)\n"));
debugPrintln(F("TFT_CS = D3 (GPIO 0)\n\n"));
debugPrintln(F("TFT_DC and TFT_RST pins can be tftSetup defined\n"));
}
#endif
tftPinInfo(F("TFT_CS "), tftSetup.pin_tft_cs);
tftPinInfo(F("TFT_DC "), tftSetup.pin_tft_dc);
tftPinInfo(F("TFT_RST"), tftSetup.pin_tft_rst);
tftPinInfo(F("TOUCH_RST"), tftSetup.pin_tch_cs);
tftPinInfo(F("TFT_WR "), tftSetup.pin_tft_wr);
tftPinInfo(F("TFT_RD "), tftSetup.pin_tft_rd);
tftPinInfo(F("TFT_D0 "), tftSetup.pin_tft_d0);
tftPinInfo(F("TFT_D1 "), tftSetup.pin_tft_d1);
tftPinInfo(F("TFT_D2 "), tftSetup.pin_tft_d2);
tftPinInfo(F("TFT_D3 "), tftSetup.pin_tft_d3);
tftPinInfo(F("TFT_D4 "), tftSetup.pin_tft_d4);
tftPinInfo(F("TFT_D5 "), tftSetup.pin_tft_d5);
tftPinInfo(F("TFT_D6 "), tftSetup.pin_tft_d6);
tftPinInfo(F("TFT_D7 "), tftSetup.pin_tft_d7);
uint16_t fonts = tft.fontsLoaded();
if(fonts & (1 << 1)) debugPrintln(F("Font GLCD loaded\n"));
if(fonts & (1 << 2)) debugPrintln(F("Font 2 loaded\n"));
if(fonts & (1 << 4)) debugPrintln(F("Font 4 loaded\n"));
if(fonts & (1 << 6)) debugPrintln(F("Font 6 loaded\n"));
if(fonts & (1 << 7)) debugPrintln(F("Font 7 loaded\n"));
if(fonts & (1 << 9))
debugPrintln(F("Font 8N loaded\n"));
else if(fonts & (1 << 8))
debugPrintln(F("Font 8 loaded\n"));
if(fonts & (1 << 15)) debugPrintln(F("Smooth font enabled\n"));
if(tftSetup.serial == 1) {
sprintf_P(buffer, PSTR("TFT: Display SPI frequency = %2.1f MHz"), tftSetup.tft_spi_freq / 10.0);
debugPrintln(buffer);
}
if(tftSetup.pin_tch_cs != -1) {
sprintf_P(buffer, PSTR("TFT: Touch SPI frequency = %2.1f MHz"), tftSetup.tch_spi_freq / 10.0);
debugPrintln(buffer);
}
}
// Get pin name for ESP8266
int8_t getPinName(int8_t pin)
{
// For ESP32 pin labels on boards use the GPIO number
#ifdef ESP32
return pin;
#endif
// For ESP8266 the pin labels are not the same as the GPIO number
// These are for the NodeMCU pin definitions:
// GPIO Dxx
if(pin == 16) return 0;
if(pin == 5) return 1;
if(pin == 4) return 2;
if(pin == 0) return 3;
if(pin == 2) return 4;
if(pin == 14) return 5;
if(pin == 12) return 6;
if(pin == 13) return 7;
if(pin == 15) return 8;
if(pin == 3) return 9;
if(pin == 1) return 10;
if(pin == 9) return 11;
if(pin == 10) return 12;
return -1; // Invalid pin
}

179
src/hasp_wifi.cpp Normal file
View File

@ -0,0 +1,179 @@
#include <Arduino.h>
#include "ArduinoJson.h"
#include "hasp_conf.h"
#include "hasp_wifi.h"
#include "hasp_mqtt.h"
#include "hasp_http.h"
#include "hasp_log.h"
#include "hasp_debug.h"
#include "hasp_config.h"
#include "hasp_gui.h"
#include "hasp.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <Wifi.h>
#else
#include <ESP8266WiFi.h>
static WiFiEventHandler wifiEventHandler[3];
#endif
#include "user_config_override.h"
std::string wifiSsid = WIFI_SSID;
std::string wifiPassword = WIFI_PASSW;
// long wifiPrevMillis = 0;
// bool wifiWasConnected = false;
// int8_t wifiReconnectAttempt = -20;
void wifiConnected(IPAddress ipaddress)
{
char buffer[64];
sprintf_P(buffer, PSTR("WIFI: Received IP address %s"), ipaddress.toString().c_str());
debugPrintln(buffer);
sprintf_P(buffer, PSTR("WIFI: Connected = %s"), WiFi.status() == WL_CONNECTED ? PSTR("yes") : PSTR("no"));
debugPrintln(buffer);
httpReconnect();
// mqttReconnect();
haspReconnect();
}
void wifiDisconnected(const char * ssid, uint8_t reason)
{
char buffer[64];
sprintf_P(buffer, PSTR("WIFI: Disconnected from %s (Reason: %d)"), ssid, reason);
debugPrintln(buffer);
WiFi.reconnect();
}
void wifiSsidConnected(const char * ssid)
{
char buffer[64];
sprintf_P(buffer, PSTR("WIFI: Connected to SSID %s. Requesting IP..."), ssid);
debugPrintln(buffer);
}
#if defined(ARDUINO_ARCH_ESP32)
void wifi_callback(system_event_id_t event, system_event_info_t info)
{
switch(event) {
case SYSTEM_EVENT_STA_CONNECTED:
wifiSsidConnected((const char *)info.connected.ssid);
break;
case SYSTEM_EVENT_STA_GOT_IP:
wifiConnected(IPAddress(info.got_ip.ip_info.ip.addr));
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
wifiDisconnected((const char *)info.disconnected.ssid, info.disconnected.reason);
// NTP.stop(); // NTP sync can be disabled to avoid sync errors
break;
default:
break;
}
}
#endif
#if defined(ARDUINO_ARCH_ESP8266)
void wifiSTAConnected(WiFiEventStationModeConnected info)
{
wifiSsidConnected(info.ssid.c_str());
}
// Start NTP only after IP network is connected
void wifiSTAGotIP(WiFiEventStationModeGotIP info)
{
wifiConnected(IPAddress(info.ip));
}
// Manage network disconnection
void wifiSTADisconnected(WiFiEventStationModeDisconnected info)
{
wifiDisconnected(info.ssid.c_str(), info.reason);
}
#endif
void wifiSetup(JsonObject settings)
{
char buffer[64];
if(!settings[F_CONFIG_SSID].isNull()) {
wifiSsid = settings[F_CONFIG_SSID].as<String>().c_str();
// sprintf_P(buffer, PSTR("Wifi Ssid: %s"), wifiSsid.c_str());
// debugPrintln(buffer);
}
if(!settings[F_CONFIG_PASS].isNull()) {
wifiPassword = settings[F_CONFIG_PASS].as<String>().c_str();
// sprintf_P(buffer, PSTR("Wifi Password: %s"), wifiPassword.c_str());
// debugPrintln(buffer);
}
sprintf_P(buffer, PSTR("WIFI: Connecting to : %s"), wifiSsid.c_str());
debugPrintln(buffer);
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSsid.c_str(), wifiPassword.c_str());
#if defined(ARDUINO_ARCH_ESP32)
WiFi.onEvent(wifi_callback);
#endif
#if defined(ARDUINO_ARCH_ESP8266)
wifiEventHandler[0] = WiFi.onStationModeGotIP(wifiSTAGotIP); // As soon WiFi is connected, start NTP Client
wifiEventHandler[1] = WiFi.onStationModeDisconnected(wifiSTADisconnected);
wifiEventHandler[2] = WiFi.onStationModeConnected(wifiSTAConnected);
#endif
}
bool wifiLoop()
{
return WiFi.status() == WL_CONNECTED;
/*
if(WiFi.status() == WL_CONNECTED) {
if(wifiWasConnected) return true;
debugPrintln(F("WIFI: Reconnected"));
wifiWasConnected = true;
wifiReconnectAttempt = 1;
wifiPrevMillis = millis();
haspOnline();
return true;
} else if(millis() - wifiPrevMillis > 1000) {
if(wifiReconnectAttempt < 20) {
if(wifiReconnectAttempt == 1) { // <0 means we were never connected yet
// haspOffline();
warningPrintln(String(F("WIFI: %sConnection lost. Reconnecting... #")) +
String(wifiReconnectAttempt)); WiFi.reconnect(); } else { debugPrintln(F("WIFI: Waiting for connection..."));
}
} else {
// haspOffline();
debugPrintln(F("WIFI: Connection lost. Reconnecting..."));
WiFi.reconnect();
}
wifiReconnectAttempt++;
wifiPrevMillis = millis();
}
return false;*/
}
bool wifiGetConfig(const JsonObject & settings)
{
if(!settings.isNull() && settings[F_CONFIG_SSID] == String(wifiSsid.c_str()) &&
settings[F_CONFIG_PASS] == String(wifiPassword.c_str()))
return false;
settings[F_CONFIG_SSID] = String(wifiSsid.c_str());
settings[F_CONFIG_PASS] = String(wifiPassword.c_str());
size_t size = serializeJson(settings, Serial);
Serial.println();
return true;
}

519
src/lv_theme_hasp.c Normal file
View File

@ -0,0 +1,519 @@
/**
* @file lv_theme_hasp.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../lib/lvgl/src/lv_themes/lv_theme.h"
#if LV_USE_THEME_HASP
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
static lv_theme_t theme;
static lv_style_t def;
static lv_style_t scr;
/*Static style definitions*/
static lv_style_t sb;
static lv_style_t plain_bordered;
static lv_style_t label_prim;
static lv_style_t label_sec;
static lv_style_t label_hint;
static lv_style_t btn_rel, btn_pr, btn_trel, btn_tpr, btn_ina;
/*Saved input parameters*/
static uint16_t _hue;
static lv_font_t * _font;
/**********************
* MACROS
**********************/
/**********************
* STATIC FUNCTIONS
**********************/
static void basic_init(void)
{
lv_style_plain.text.font = _font;
lv_style_pretty.text.font = _font;
lv_style_pretty_color.text.font = _font;
lv_style_btn_rel.text.font = _font;
lv_style_btn_pr.text.font = _font;
lv_style_btn_tgl_rel.text.font = _font;
lv_style_btn_tgl_pr.text.font = _font;
lv_style_btn_ina.text.font = _font;
if(_hue <= 360) {
lv_style_pretty_color.body.main_color = lv_color_hsv_to_rgb(_hue, 10, 70);
lv_style_pretty_color.body.grad_color = lv_color_hsv_to_rgb(_hue, 80, 80);
lv_style_plain_color.body.main_color = lv_color_hsv_to_rgb(_hue, 50, 75);
lv_style_plain_color.body.grad_color = lv_style_plain_color.body.main_color;
lv_style_btn_rel.body.main_color = lv_color_hsv_to_rgb(_hue, 10, 70);
lv_style_btn_rel.body.grad_color = lv_color_hsv_to_rgb(_hue, 80, 80);
lv_style_btn_pr.body.main_color = lv_color_hsv_to_rgb(_hue, 80, 70);
lv_style_btn_pr.body.grad_color = lv_color_hsv_to_rgb(_hue, 10, 80);
lv_style_btn_tgl_rel.body.main_color = lv_color_hsv_to_rgb(_hue, 10, 70);
lv_style_btn_tgl_rel.body.grad_color = lv_color_hsv_to_rgb(_hue, 80, 80);
lv_style_btn_tgl_pr.body.main_color = lv_color_hsv_to_rgb(_hue, 80, 70);
lv_style_btn_tgl_pr.body.grad_color = lv_color_hsv_to_rgb(_hue, 10, 80);
}
lv_style_copy(&def, &lv_style_pretty); /*Initialize the default style*/
def.text.font = _font;
lv_style_copy(&scr, &def);
scr.body.padding.bottom = 0;
scr.body.padding.top = 0;
scr.body.padding.left = 0;
scr.body.padding.right = 0;
lv_style_copy(&sb, &lv_style_pretty_color);
sb.body.grad_color = sb.body.main_color;
sb.body.padding.right = sb.body.padding.right / 2; /*Make closer to the edges*/
sb.body.padding.bottom = sb.body.padding.bottom / 2;
lv_style_copy(&plain_bordered, &lv_style_plain);
plain_bordered.body.border.width = 2;
plain_bordered.body.border.color = lv_color_hex3(0xbbb);
theme.style.bg = &lv_style_plain;
theme.style.scr = &scr;
theme.style.panel = &lv_style_pretty;
}
static void btn_init(void)
{
#if LV_USE_BTN != 0
theme.style.btn.rel = &lv_style_btn_rel;
theme.style.btn.pr = &lv_style_btn_pr;
theme.style.btn.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.btn.tgl_pr = &lv_style_btn_tgl_pr;
theme.style.btn.ina = &lv_style_btn_ina;
#endif
}
static void label_init(void)
{
#if LV_USE_LABEL != 0
lv_style_copy(&label_prim, &lv_style_plain);
lv_style_copy(&label_sec, &lv_style_plain);
lv_style_copy(&label_hint, &lv_style_plain);
label_prim.text.color = lv_color_hex3(0x111);
label_sec.text.color = lv_color_hex3(0x888);
label_hint.text.color = lv_color_hex3(0xaaa);
theme.style.label.prim = &label_prim;
theme.style.label.sec = &label_sec;
theme.style.label.hint = &label_hint;
#endif
}
static void img_init(void)
{
#if LV_USE_IMG != 0
theme.style.img.light = &def;
theme.style.img.dark = &def;
#endif
}
static void line_init(void)
{
#if LV_USE_LINE != 0
theme.style.line.decor = &def;
#endif
}
static void led_init(void)
{
#if LV_USE_LED != 0
static lv_style_t led;
lv_style_copy(&led, &lv_style_pretty_color);
led.body.shadow.width = LV_DPI / 10;
led.body.radius = LV_RADIUS_CIRCLE;
led.body.border.width = LV_DPI / 30;
led.body.border.opa = LV_OPA_30;
led.body.shadow.color = led.body.main_color;
theme.style.led = &led;
#endif
}
static void bar_init(void)
{
#if LV_USE_BAR
theme.style.bar.bg = &lv_style_pretty;
theme.style.bar.indic = &lv_style_pretty_color;
#endif
}
static void slider_init(void)
{
#if LV_USE_SLIDER != 0
static lv_style_t slider_bg;
lv_style_copy(&slider_bg, &lv_style_pretty);
slider_bg.body.padding.left = LV_DPI / 20;
slider_bg.body.padding.right = LV_DPI / 20;
slider_bg.body.padding.top = LV_DPI / 20;
slider_bg.body.padding.bottom = LV_DPI / 20;
theme.style.slider.bg = &slider_bg;
theme.style.slider.indic = &lv_style_pretty_color;
theme.style.slider.knob = &lv_style_pretty;
#endif
}
static void sw_init(void)
{
#if LV_USE_SW != 0
static lv_style_t sw_indic, sw_bg;
lv_style_copy(&sw_indic, &lv_style_pretty_color);
sw_indic.body.padding.left = -0;
sw_indic.body.padding.right = -0;
sw_indic.body.padding.top = -0;
sw_indic.body.padding.bottom = -0;
sw_indic.body.padding.inner = -0;
lv_style_copy(&sw_bg, &lv_style_pretty);
sw_bg.body.padding.left = -0;
sw_bg.body.padding.right = -0;
sw_bg.body.padding.top = -0;
sw_bg.body.padding.bottom = -0;
sw_bg.body.padding.inner = -0;
theme.style.sw.bg = &sw_bg;
theme.style.sw.indic = &sw_indic;
theme.style.sw.knob_off = &lv_style_pretty;
theme.style.sw.knob_on = &lv_style_pretty;
#endif
}
static void lmeter_init(void)
{
#if LV_USE_LMETER != 0
static lv_style_t lmeter;
lv_style_copy(&lmeter, &lv_style_pretty_color);
lmeter.line.color = lv_color_hex3(0xddd);
lmeter.line.width = 2;
lmeter.body.main_color = lv_color_mix(lmeter.body.main_color, LV_COLOR_WHITE, LV_OPA_50);
lmeter.body.grad_color = lv_color_mix(lmeter.body.grad_color, LV_COLOR_BLACK, LV_OPA_50);
theme.style.lmeter = &lmeter;
#endif
}
static void gauge_init(void)
{
#if LV_USE_GAUGE != 0
static lv_style_t gauge;
lv_style_copy(&gauge, theme.style.lmeter);
gauge.line.color = theme.style.lmeter->body.grad_color;
gauge.line.width = 2;
gauge.body.main_color = lv_color_hex3(0x888);
gauge.body.grad_color = theme.style.lmeter->body.main_color;
gauge.text.color = lv_color_hex3(0x888);
gauge.text.font = _font;
theme.style.gauge = &gauge;
#endif
}
static void chart_init(void)
{
#if LV_USE_CHART
theme.style.chart = &lv_style_pretty;
#endif
}
static void cb_init(void)
{
#if LV_USE_CB != 0
theme.style.cb.bg = &lv_style_transp;
theme.style.cb.box.rel = &lv_style_pretty;
theme.style.cb.box.pr = &lv_style_btn_pr;
theme.style.cb.box.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.cb.box.tgl_pr = &lv_style_btn_tgl_pr;
theme.style.cb.box.ina = &lv_style_btn_ina;
#endif
}
static void btnm_init(void)
{
#if LV_USE_BTNM
theme.style.btnm.bg = &lv_style_pretty;
theme.style.btnm.btn.rel = &lv_style_btn_rel;
theme.style.btnm.btn.pr = &lv_style_btn_pr;
theme.style.btnm.btn.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.btnm.btn.tgl_pr = &lv_style_btn_tgl_pr;
theme.style.btnm.btn.ina = &lv_style_btn_ina;
#endif
}
static void kb_init(void)
{
#if LV_USE_KB
theme.style.kb.bg = &lv_style_pretty;
theme.style.kb.btn.rel = &lv_style_btn_rel;
theme.style.kb.btn.pr = &lv_style_btn_pr;
theme.style.kb.btn.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.kb.btn.tgl_pr = &lv_style_btn_tgl_pr;
theme.style.kb.btn.ina = &lv_style_btn_ina;
#endif
}
static void mbox_init(void)
{
#if LV_USE_MBOX
theme.style.mbox.bg = &lv_style_pretty;
theme.style.mbox.btn.bg = &lv_style_transp;
theme.style.mbox.btn.rel = &lv_style_btn_rel;
theme.style.mbox.btn.pr = &lv_style_btn_tgl_pr;
#endif
}
static void page_init(void)
{
#if LV_USE_PAGE
theme.style.page.bg = &lv_style_pretty;
theme.style.page.scrl = &lv_style_transp_tight;
theme.style.page.sb = &sb;
#endif
}
static void ta_init(void)
{
#if LV_USE_TA
theme.style.ta.area = &lv_style_pretty;
theme.style.ta.oneline = &lv_style_pretty;
theme.style.ta.cursor = NULL;
theme.style.ta.sb = &sb;
#endif
}
static void list_init(void)
{
#if LV_USE_LIST != 0
theme.style.list.bg = &lv_style_pretty;
theme.style.list.scrl = &lv_style_transp_fit;
theme.style.list.sb = &sb;
theme.style.list.btn.rel = &lv_style_btn_rel;
theme.style.list.btn.pr = &lv_style_btn_pr;
theme.style.list.btn.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.list.btn.tgl_pr = &lv_style_btn_tgl_pr;
theme.style.list.btn.ina = &lv_style_btn_ina;
#endif
}
static void ddlist_init(void)
{
#if LV_USE_DDLIST != 0
theme.style.ddlist.bg = &lv_style_pretty;
theme.style.ddlist.sel = &lv_style_plain_color;
theme.style.ddlist.sb = &sb;
#endif
}
static void roller_init(void)
{
#if LV_USE_ROLLER != 0
theme.style.roller.bg = &lv_style_pretty;
theme.style.roller.sel = &lv_style_plain_color;
#endif
}
static void tabview_init(void)
{
#if LV_USE_TABVIEW != 0
theme.style.tabview.bg = &plain_bordered;
theme.style.tabview.indic = &lv_style_plain_color;
theme.style.tabview.btn.bg = &lv_style_transp;
theme.style.tabview.btn.rel = &lv_style_btn_rel;
theme.style.tabview.btn.pr = &lv_style_btn_pr;
theme.style.tabview.btn.tgl_rel = &lv_style_btn_tgl_rel;
theme.style.tabview.btn.tgl_pr = &lv_style_btn_tgl_pr;
#endif
}
static void table_init(void)
{
#if LV_USE_TABLE != 0
theme.style.table.bg = &lv_style_transp_tight;
theme.style.table.cell = &lv_style_plain;
#endif
}
static void win_init(void)
{
#if LV_USE_WIN != 0
theme.style.win.bg = &plain_bordered;
theme.style.win.sb = &sb;
theme.style.win.header = &lv_style_plain_color;
theme.style.win.content = &lv_style_transp;
theme.style.win.btn.rel = &lv_style_btn_rel;
theme.style.win.btn.pr = &lv_style_btn_pr;
#endif
}
#if LV_USE_GROUP
static void style_mod(lv_group_t * group, lv_style_t * style)
{
(void)group; /*Unused*/
#if LV_COLOR_DEPTH != 1
/*Make the style to be a little bit orange*/
style->body.border.opa = LV_OPA_COVER;
style->body.border.color = LV_COLOR_ORANGE;
/*If not empty or has border then emphasis the border*/
if(style->body.opa != LV_OPA_TRANSP || style->body.border.width != 0) style->body.border.width = LV_DPI / 20;
style->body.main_color = lv_color_mix(style->body.main_color, LV_COLOR_ORANGE, LV_OPA_70);
style->body.grad_color = lv_color_mix(style->body.grad_color, LV_COLOR_ORANGE, LV_OPA_70);
style->body.shadow.color = lv_color_mix(style->body.shadow.color, LV_COLOR_ORANGE, LV_OPA_60);
style->text.color = lv_color_mix(style->text.color, LV_COLOR_ORANGE, LV_OPA_70);
#else
style->body.border.opa = LV_OPA_COVER;
style->body.border.color = LV_COLOR_BLACK;
style->body.border.width = 2;
#endif
}
static void style_mod_edit(lv_group_t * group, lv_style_t * style)
{
(void)group; /*Unused*/
#if LV_COLOR_DEPTH != 1
/*Make the style to be a little bit orange*/
style->body.border.opa = LV_OPA_COVER;
style->body.border.color = LV_COLOR_GREEN;
/*If not empty or has border then emphasis the border*/
if(style->body.opa != LV_OPA_TRANSP || style->body.border.width != 0) style->body.border.width = LV_DPI / 20;
style->body.main_color = lv_color_mix(style->body.main_color, LV_COLOR_GREEN, LV_OPA_70);
style->body.grad_color = lv_color_mix(style->body.grad_color, LV_COLOR_GREEN, LV_OPA_70);
style->body.shadow.color = lv_color_mix(style->body.shadow.color, LV_COLOR_GREEN, LV_OPA_60);
style->text.color = lv_color_mix(style->text.color, LV_COLOR_GREEN, LV_OPA_70);
#else
style->body.border.opa = LV_OPA_COVER;
style->body.border.color = LV_COLOR_BLACK;
style->body.border.width = 3;
#endif
}
#endif /*LV_USE_GROUP*/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Initialize the default theme
* @param hue [0..360] hue value from HSV color space to define the theme's base color
* @param font pointer to a font (NULL to use the default)
* @return pointer to the initialized theme
*/
lv_theme_t * lv_theme_hasp_init(uint16_t hue, lv_font_t * font)
{
if(font == NULL) font = LV_FONT_DEFAULT;
_hue = hue;
_font = font;
/*For backward compatibility initialize all theme elements with a default style */
uint16_t i;
lv_style_t ** style_p = (lv_style_t **)&theme.style;
for(i = 0; i < LV_THEME_STYLE_COUNT; i++) {
*style_p = &def;
style_p++;
}
basic_init();
btn_init();
label_init();
img_init();
line_init();
led_init();
bar_init();
slider_init();
sw_init();
lmeter_init();
gauge_init();
chart_init();
cb_init();
btnm_init();
kb_init();
mbox_init();
page_init();
ta_init();
list_init();
ddlist_init();
roller_init();
tabview_init();
table_init();
win_init();
#if LV_USE_GROUP
theme.group.style_mod_xcb = style_mod;
theme.group.style_mod_edit_xcb = style_mod_edit;
#endif
return &theme;
}
/**
* Get a pointer to the theme
* @return pointer to the theme
*/
lv_theme_t * lv_theme_get_hasp(void)
{
return &theme;
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif

57
src/lv_theme_hasp.h Normal file
View File

@ -0,0 +1,57 @@
/**
* @file lv_theme_default.h
*
*/
#ifndef LV_THEME_HASP_H
#define LV_THEME_HASP_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lvgl.h"
#include "../lib/lvgl/src/lv_conf_internal.h"
#if LV_USE_THEME_HASP
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize the default theme
* @param hue [0..360] hue value from HSV color space to define the theme's base color
* @param font pointer to a font (NULL to use the default)
* @return pointer to the initialized theme
*/
lv_theme_t * lv_theme_hasp_init(uint16_t hue, lv_font_t * font);
/**
* Get a pointer to the theme
* @return pointer to the theme
*/
lv_theme_t * lv_theme_get_hasp(void);
/**********************
* MACROS
**********************/
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_THEME_TEMPL_H*/

134
src/main.cpp Normal file
View File

@ -0,0 +1,134 @@
#include "ArduinoJson.h"
#include "TFT_eSPI.h"
#include "hasp_conf.h"
#include "hasp_debug.h"
#include "hasp_eeprom.h"
#include "hasp_spiffs.h"
#include "hasp_config.h"
#include "hasp_tft.h"
#include "hasp_gui.h"
//#include "hasp_ota.h"
#include "hasp.h"
#if LV_USE_HASP_SPIFFS
#if defined(ARDUINO_ARCH_ESP32)
#include "SPIFFS.h"
#endif
#include <FS.h> // Include the SPIFFS library
#endif
#if LV_USE_HASP_WIFI
#include "hasp_wifi.h"
#endif
#if LV_USE_HASP_MQTT
#include "hasp_mqtt.h"
#endif
#if LV_USE_HASP_HTTP
#include "hasp_http.h"
#endif
bool isConnected;
void setup()
{
/* Init Storage */
eepromSetup();
#if LV_USE_HASP_SPIFFS
// spiffsSetup();
#endif
/* Read Config File */
DynamicJsonDocument settings(1024);
// configGetConfig(doc);
// JsonObject settings = doc.as<JsonObject>();
configSetup(settings);
if(!settings[F("pins")][F("TFT_BCKL")].isNull()) {
int8_t pin = settings[F("pins")][F("TFT_BCKL")].as<int8_t>();
#if defined(ARDUINO_ARCH_ESP32)
if(pin >= 0)
// configure LED PWM functionalitites
ledcSetup(0, 5000, 10);
// attach the channel to the GPIO to be controlled
ledcAttachPin(pin, 0);
#else
pinMode(pin, OUTPUT);
#endif
}
#if LV_USE_HASP_SDCARD
sdcardSetup();
#endif
/* Init Graphics */
TFT_eSPI screen = TFT_eSPI();
tftSetup(screen, settings[F("tft")]);
guiSetup(screen, settings[F("gui")]);
/* Init GUI Application */
haspSetup(settings[F("hasp")]);
/* Init Network Services */
#if LV_USE_HASP_WIFI
wifiSetup(settings[F("wifi")]);
#if LV_USE_HASP_MQTT
mqttSetup(settings[F("mqtt")]);
#endif
#if LV_USE_HASP_MDNS
mdnsSetup(settings[F("mdns")]);
#endif
#if LV_USE_HASP_HTTP
httpSetup(settings[F("http")]);
#endif
// otaSetup(settings[F("ota")]);
#endif
}
void loop()
{
/* Storage Loops */
// eepromLoop();
// spiffsLoop();
#if LV_USE_HASP_SDCARD
// sdcardLoop();
#endif
configLoop();
/* Graphics Loops */
// tftLoop();
guiLoop();
/* Application Loops */
// haspLoop();
/* Network Services Loops */
#if LV_USE_HASP_WIFI > 0
isConnected = wifiLoop();
#if LV_USE_HASP_MQTT > 0
mqttLoop(isConnected);
#endif
#if LV_USE_HASP_HTTP > 0
httpLoop(isConnected);
#endif
#if LV_USE_HASP_MDNS > 0
mdnsLoop(wifiIsConnected);
#endif
// otaLoop();
#endif
delay(5);
}

View File

@ -0,0 +1,33 @@
/***************************************************
WiFi Settings
**************************************************/
#define WIFI_SSID ""
#define WIFI_PASSW ""
/***************************************************
MQTT Settings
**************************************************/
#define MQTT_HOST ""
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASSW ""
#define MQTT_TOPIC "plates"
#define MQTT_CLIENTID "plate01"
#define MQTT_TELEPERIOD 60000
#define MQTT_STATEPERIOD 300000
/***************************************************
* Server Settings
**************************************************/
#define OTA_HOSTNAME ""
#define OTA_SERVER ""
#define OTA_PORT 80
#define OTA_URL ""
/***************************************************
* Syslog Settings
**************************************************/
#define SYSLOG_SERVER ""
#define SYSLOG_PORT 514
#define APP_NAME "HASP"