Major update/rewrite/recombine to get SSDP, and GrowController UI and application shell.

This commit is contained in:
David
2021-10-25 23:46:03 +00:00
parent c47c37a891
commit 4597350845
52 changed files with 4981 additions and 4990 deletions

416
Firmware/CustomHandlers.cpp Normal file
View File

@@ -0,0 +1,416 @@
//
//
#include "CustomHandlers.h"
#include "WiFiStateHandler.h"
// General Purpose helper stuff
// Handlers
void HandleNotFound() {
bool found = false;
const DirEntry *de = Directory;
Serial.printf("No Custom Handler for '%s'\n", server.uri().c_str()+1);
while (de->Filename) {
Serial.printf("test for '%s' with '%s'\n", de->Filename, server.uri().c_str()+1);
#if 0
if (0 == strcmp(server.uri().c_str()+1, de->Filename)) {
Serial.printf("send '%s' (%i B) as '%s'\n", de->Filename, de->Filesize, de->Filetype);
server.send_P(200, de->Filetype, (const char *)de->Filedata, de->Filesize);
found = true;
break;
}
#endif
de++;
}
if (!found) {
Serial.printf("!found '%s' - redirect to home\n", server.uri().c_str());
server.sendHeader("Location", "/", true);
server.send(302, "text/plain", "Not supported. Heading for home.");
}
}
void HandleMyIP_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
String message = "var mySite = 'http://";
message += String(WiFi.localIP()[0]);
message += "." + String(WiFi.localIP()[1]);
message += "." + String(WiFi.localIP()[2]);
message += "." + String(WiFi.localIP()[3]);
message += "';\n";
server.send(200, "text/javascript", message);
}
void HandleRootPage() {
if (server.hasArg("SW")) {
String newState = server.arg("SW");
if (newState == "1") {
// @todo SetCircuit(CMD_On);
Serial.printf("SW 1\n");
} else if (newState == "0") {
// @todo SetCircuit(CMD_Off);
Serial.printf("SW 0\n");
} else if (newState == "2") {
// @todo SetCircuit(CMD_Toggle);
Serial.printf("SW 2\n");
} else {
Serial.printf("SW %s ???\n", newState.c_str());
; // no action
}
}
// @todo String modeText = (isStationMode) ? "Station mode<br/>" : "Access Point mode<br/>";
// @todo String status = GetCircuitStatus() ? "On<br/>" : "Off<br/>";
String myIP = String(WiFi.localIP()[0]);
myIP += "." + String(WiFi.localIP()[1]);
myIP += "." + String(WiFi.localIP()[2]);
myIP += "." + String(WiFi.localIP()[3]);
server.send_P(200, "text/html", (const char *)index_htm);
}
static String EncType_str(uint8_t encType) {
switch (encType) {
case ENC_TYPE_NONE:
return "open";
break;
case ENC_TYPE_WEP:
return "wep";
break;
case ENC_TYPE_TKIP:
return "wpa psk";
break;
case ENC_TYPE_CCMP:
return "wpa2 psk";
break;
case ENC_TYPE_AUTO:
return "wpa wpa2 psk";
break;
default:
return "unknown";
break;
}
}
void HandleAPScan() {
// WiFi.scanNetworks will return the number of networks found
Serial.println("Scan ...");
int n = WiFi.scanNetworks(false, true); // blocking call and show hidden.
Serial.println("scan done.");
if (n == 0) {
Serial.println("no networks found");
} else {
Serial.print(n);
Serial.println(" networks found");
server.sendContent_P("HTTP/1.1 200 OK\r\n");
server.sendContent_P("Content-Type: text/html\r\n");
server.sendContent_P("\r\n");
server.sendContent_P(
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Grow Controller - Scan</title></head>\n"
"<body>\n"
"<h1>Grow Controller - Scan</h1>"
);
server.sendContent_P(
" <form action=\"/config\">\n"
" <blockquote>\n"
" <table width='800' border='0'>\n"
" <tr bgcolor='#E0E0E0'><td colspan='2'>Net</td><td>RSSI</td><td>SSID</td><td>BSSID</td><td>Encryption</td><td>Channel</td><td>Hidden</td></tr>\n"
);
for (int i = 0; i < n; ++i) {
// Select, Network #, RSSI, SSID, BSSID, Channel, Hidden
String bgcolor = (i & 1) ? "#E0E0E0" : "#FFFFFF";
String record =
" <tr bgcolor='" + bgcolor + "'><td><input type='radio' name='ssid' value='" + WiFi.SSID(i) + "'></td>\n"
+ " <td>" + (i + 1) + "</td>\n"
+ " <td>" + WiFi.RSSI(i) + "</td>\n"
+ " <td>" + WiFi.SSID(i) + "<br/>"
+ " <img src='/green1x1.png' width='" + String(5 * (100 + WiFi.RSSI(i))) + "' height='3'></td>\n"
+ " <td>" + WiFi.BSSIDstr(i) + "</td>\n";
record += " <td>" + EncType_str(WiFi.encryptionType(i)) + "</td>\n";
record += " <td>" + String(WiFi.channel(i));
record += "</td>\n";
record += WiFi.isHidden(i)
? " <td>Yes</td>\n"
: " <td>No</td>\n";
record += " </tr>\n";
server.sendContent(record);
//
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
}
server.sendContent_P(
" <tr><td>&nbsp;</td>\n"
" <td colspan='7'>\n"
" <input type=\"submit\" value=\"Select\" />\n"
" <input type=\"reset\"/>\n"
" </td>\n"
" </tr>\n"
" </table>\n"
" </blockquote>\n"
" </form>\n"
"NAV:&nbsp;&nbsp;"
" <a href='/'>Home</a> | "
" <a href='/config'>Config</a> | "
" <a href='/scan'>Scan</a> | "
" <a href='/rssi'>RSSI</a> | "
" <a href='/curr'>Current</a> | "
" <a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> | "
" <a href='/about'>About</a>\n"
" <br/>\n"
"</body>\n"
"</html>\n"
);
}
}
void HandleAPConfigPage() {
String name = wifiConfig.getName();
String ssid = wifiConfig.getSSID();
String pass = wifiConfig.getPassword();
String url = wifiConfig.getURL();
//String ntp = wifiConfig.getNTPServerName();
char _onref[6], _offref[6], _autoOff[6];
snprintf(_onref, sizeof(_onref), "%d", wifiConfig.getOnRef());
snprintf(_offref, sizeof(_offref), "%d", wifiConfig.getOffRef());
snprintf(_autoOff, sizeof(_autoOff), "%d", wifiConfig.getAutoOff());
String onref = String(_onref);
String offref = String(_offref);
String autooff = String(_autoOff);
if (server.hasArg("ssid") && server.hasArg("pass")) {
String _ssid = server.hasArg("_ssid") ? server.arg("_ssid") : "";
String _pass = server.hasArg("_pass") ? server.arg("_pass") : "";
name = server.arg("name");
ssid = server.arg("ssid");
pass = server.arg("pass");
url = server.hasArg("url") ? server.arg("url") : "";
onref = server.hasArg("onref") ? server.arg("onref") : "";
offref = server.hasArg("offref") ? server.arg("offref") : "";
autooff = server.hasArg("autooff") ? server.arg("autooff") : "";
//ntp = server.hasArg("ntp") ? server.arg("ntp") : "";
if (ssid == "reset" && pass == "reset") {
FactoryReset(true);
}
wifiConfig.setName(name);
wifiConfig.setSSID(ssid);
wifiConfig.setPassword(pass);
wifiConfig.setURL(url);
wifiConfig.setOnRef(onref.toInt());
wifiConfig.setOffRef(offref.toInt());
wifiConfig.setAutoOff(autooff.toInt());
//wifiConfig.setNTPServerName(ntp);
wifiConfig.save();
Serial.println("Settings saved.");
if (ssid != _ssid || pass != _pass) {
// They changed stuff that requires a restart.
server.send(200, "text/html", "Configuration Saved! Restarting in 3 sec...");
Serial.printf("Config saved. Restarting in 3 sec . . .");
delay(3000);
ESP.restart();
return;
}
}
String modeText = (WiFiStateGet() == WFC_JoinedAP) ? "Station mode<br/>" : "Access Point mode<br/>";
if (server.hasArg("ssid") && !server.hasArg("pass")) {
ssid = server.arg("ssid");
pass = "";
Serial.println("ssid: " + ssid + ", pass: " + pass);
}
if (server.hasArg("url"))
url = server.arg("url");
//if (server.hasArg("ntp"))
// ntp = server.arg("ntp");
server.send(200, "text/html",
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Grow Controller - Config</title></head>\n"
"<body>\n"
"<h1>Grow Controller - Configuration</h1>"
" <form action=\"\">\n"
" <blockquote>\n"
" <table border='0'>\n"
" <tr>\n"
" <td>Node Name</td>\n"
" <td><input type=\"text\" size=\"32\" name=\"name\" value=\"" + name + "\" /></td>\n"
" </tr>\n"
" <tr>\n"
" <td>SSID</td>\n"
" <td><input type=\"text\" size=\"32\" name=\"ssid\" value=\"" + ssid + "\" />&nbsp;**\n"
" <input type=\"hidden\" name=\"_ssid\" value=\"" + ssid + "\" /></td>\n"
" </tr>\n"
" <tr nowrap='nowrap'>\n"
" <td>Password</td>\n"
" <td nowrap='nowrap'><input type=\"password\" size=\"64\" name=\"pass\" value=\"" + pass + "\" />&nbsp;**\n"
" <input type=\"hidden\" name=\"_pass\" value=\"" + pass + "\" /></td>\n"
" </tr>\n"
" <tr>\n"
" <td>Update URL</td>\n"
" <td><input type=\"text\" size=\"64\" name=\"url\" value=\"" + url + "\" /></td>\n"
" </tr>\n"
// " <tr>\n"
// " <td>NTP Server</td>\n"
// " <td><input type=\"text\" size=\"64\" name=\"ntp\" value=\"" + ntp + "\" /></td>\n"
// " </tr>\n"
" <tr>\n"
" <td>Auto-Off</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"autooff\" value=\"" + autooff + "\" /> (time in seconds when non-zero)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>On Reference</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"onref\" value=\"" + onref + "\" /> (sense above this indicates power is on)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>Off Reference</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"offref\" value=\"" + offref + "\" /> (sense below this indicates power is off)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;</td>\n"
" <td>\n"
" <input type=\"submit\" value=\"Save\" />\n"
" <input type=\"reset\"/> ** Changes to these will trigger a module restart.\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td colspan='2'><hr/></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;</td>\n"
" <td><a href='/swupdatecheck'>Check for SW update (module will restart if updated)</a></td/>\n"
" </tr>\n"
" </table>\n"
" </blockquote>\n"
" </form>\n"
"NAV:&nbsp;&nbsp;"
" <a href='/'>Home</a> | "
" <a href='/config'>Config</a> | "
" <a href='/scan'>Scan</a> | "
" <a href='/rssi'>RSSI</a> | "
" <a href='/curr'>Current</a> | "
" <a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> | "
" <a href='/about'>About</a>\n"
" <br/>\n"
"</body>\n"
"</html>\n"
);
}
// ###################################################################
//
// Old handlers, for 'static' information that needs to be send to
// build a web page.
//
// This is to be obsoleted by the new method at the top of this file.
//
// ###################################################################
void HandlePlantModel() {
Serial.printf("PlantModel\n");
server.send_P(200, "image/png", plantmodel_png, sizeof(plantmodel_png));
}
void HandlePlantModel_css() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/css", plantmodel_css);
}
void HandleButton_css() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/css", button_css);
}
void HandleIndex_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/javascript", index_js);
}
void HandleAbout_htm() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/html", about_htm);
}
void HandleAbout_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/javascript", about_js);
}
void HandleFavIcon() {
Serial.printf("favicon.ico\n");
server.send_P(200, "image/x-icon", favicon_ico, sizeof(favicon_ico));
}
void HandleIcon() {
Serial.printf("icon.png\n");
server.send_P(200, "image/png", icon_png, sizeof(icon_png));
}
void HandleOpenDoor() {
Serial.printf("open.png\n");
server.send_P(200, "image/png", open_png, sizeof(open_png));
}
void HandleGreen1x1() {
Serial.printf("green1x1.png\n");
server.send_P(200, "image/png", green1x1_png, sizeof(green1x1_png));
}
void HandleHeaterOn() {
Serial.printf("soilheater_on.png\n");
server.send_P(200, "image/png", soilheater_on_png, sizeof(soilheater_on_png));
}
void HandleHeaterOff() {
Serial.printf("soilheater_off.png\n");
server.send_P(200, "image/png", soilheater_off_png, sizeof(soilheater_off_png));
}
void HandleThermometer() {
Serial.printf("thermometer.png\n");
server.send_P(200, "image/png", thermometer_png, sizeof(thermometer_png));
}
void HandleSoilMoisture() {
Serial.printf("soilmoisture.png\n");
server.send_P(200, "image/png", soilmoisture_png, sizeof(soilmoisture_png));
}
void HandlePointer() {
Serial.printf("pointer.png\n");
server.send_P(200, "image/png", pointer_png, sizeof(pointer_png));
}
void HandleWater() {
Serial.printf("water.png\n");
server.send_P(200, "image/png", water_png, sizeof(water_png));
}
void HandleRSSIPage() {
server.send_P(200, "text/html", rssi_htm);
}
void HandleRSSI_js() {
server.send_P(200, "text/javascript", rssi_js);
}
void HandleCurrPage() {
server.send_P(200, "text/html", curr_htm);
}
void HandleCurr_js() {
server.send_P(200, "text/javascript", curr_js);
}
void HandleNav_js() {
server.send_P(200, "text/javascript", nav_js);
}
void HandleSWUpdateCheck() {
updateCheck = true;
HandleAPConfigPage();
}
#if 0
void HandleCircuitOn() {
//SetCircuit(CMD_On);
HandleGetState(); // Reply with current state
}
void HandleCircuitOff() {
//SetCircuit(CMD_Off);
HandleGetState(); // Reply with current state
}
void HandleCircuitToggle() {
//SetCircuit(CMD_Toggle);
HandleGetState(); // Reply with current state
}
#endif

59
Firmware/CustomHandlers.h Normal file
View File

@@ -0,0 +1,59 @@
//
//
// Custom Web Page Handlers
//
// For accepting and processing arguments, or for custom page
// generation.
//
#ifndef CUSTOMHANDERS_H
#define CUSTOMHANDERS_H
#include "ProjGlobals.h"
#include "Web_Resources.h"
/// If a URI does not have a custom handler hook, it will scan the file system for the target resource.
/// Only if it is then not found will it try to redirect the user to the main page.
///
void HandleNotFound();
///
/// For all of the following, there is actively generated content on the page,
/// so it must have a custom handler.
///
void HandleRootPage();
void HandleMyIP_js();
void HandleAPConfigPage();
void HandlePlantModel();
void HandleAPScan();
void HandlePlantModel();
void HandlePlantModel_css();
void HandleButton_css();
void HandleIndex_js();
void HandleAbout_htm();
void HandleAbout_js();
void HandleCircuitOn();
void HandleCircuitOff();
void HandleCircuitToggle();
void HandleSWUpdateCheck();
void HandleFavIcon();
void HandleIcon();
void HandleOpenDoor();
void HandleGreen1x1();
void HandleFavIcon();
void HandleHeaterOn();
void HandleWater();
void HandleRSSIPage();
void HandleRSSI_js();
void HandleCurrPage();
void HandleCurr_js();
void HandleNav_js();
void HandleHeaterOn();
void HandleHeaterOff();
void HandleThermometer();
void HandleSoilMoisture();
void HandlePointer();
#endif // CUSTOMHANDERS_H

View File

@@ -1,143 +0,0 @@
#include "./DNSServer.h"
#include <lwip/def.h>
#include <Arduino.h>
#define DEBUG
#define DEBUG_OUTPUT Serial
DNSServer::DNSServer() {
_ttl = htonl(60);
_errorReplyCode = DNSReplyCode::NonExistentDomain;
}
bool DNSServer::start(const uint16_t &port, const String &domainName,
const IPAddress &resolvedIP) {
_port = port;
_domainName = domainName;
_resolvedIP[0] = resolvedIP[0];
_resolvedIP[1] = resolvedIP[1];
_resolvedIP[2] = resolvedIP[2];
_resolvedIP[3] = resolvedIP[3];
downcaseAndRemoveWwwPrefix(_domainName);
return _udp.begin(_port) == 1;
}
void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode) {
_errorReplyCode = replyCode;
}
void DNSServer::setTTL(const uint32_t &ttl) {
_ttl = htonl(ttl);
}
void DNSServer::stop() {
_udp.stop();
}
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) {
domainName.toLowerCase();
domainName.replace("www.", "");
}
void DNSServer::processNextRequest() {
_currentPacketSize = _udp.parsePacket();
if (_currentPacketSize) {
_buffer = (unsigned char*) malloc(_currentPacketSize * sizeof(char));
_udp.read(_buffer, _currentPacketSize);
_dnsHeader = (DNSHeader*) _buffer;
if (_dnsHeader->QR == DNS_QR_QUERY &&
_dnsHeader->OPCode == DNS_OPCODE_QUERY &&
requestIncludesOnlyOneQuestion() &&
(_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
) {
replyWithIP();
} else if (_dnsHeader->QR == DNS_QR_QUERY) {
replyWithCustomCode();
}
free(_buffer);
}
}
bool DNSServer::requestIncludesOnlyOneQuestion() {
return ntohs(_dnsHeader->QDCount) == 1 &&
_dnsHeader->ANCount == 0 &&
_dnsHeader->NSCount == 0 &&
_dnsHeader->ARCount == 0;
}
String DNSServer::getDomainNameWithoutWwwPrefix() {
String parsedDomainName = "";
unsigned char *start = _buffer + 12;
if (*start == 0) {
return parsedDomainName;
}
int pos = 0;
while (true) {
unsigned char labelLength = *(start + pos);
for (int i = 0; i < labelLength; i++) {
pos++;
parsedDomainName += (char)*(start + pos);
}
pos++;
if (*(start + pos) == 0) {
downcaseAndRemoveWwwPrefix(parsedDomainName);
return parsedDomainName;
} else {
parsedDomainName += ".";
}
}
}
void DNSServer::replyWithIP() {
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->ANCount = _dnsHeader->QDCount;
_dnsHeader->QDCount = _dnsHeader->QDCount;
//_dnsHeader->RA = 1;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, _currentPacketSize);
_udp.write((uint8_t) 192); // answer name is a pointer
_udp.write((uint8_t) 12); // pointer to offset at 0x00c
_udp.write((uint8_t) 0); // 0x0001 answer is type A query (host address)
_udp.write((uint8_t) 1);
_udp.write((uint8_t) 0); //0x0001 answer is class IN (internet address)
_udp.write((uint8_t) 1);
_udp.write((unsigned char*) &_ttl, 4);
// Length of RData is 4 bytes (because, in this case, RData is IPv4)
_udp.write((uint8_t) 0);
_udp.write((uint8_t) 4);
_udp.write(_resolvedIP, sizeof(_resolvedIP));
_udp.endPacket();
#ifdef DEBUG
DEBUG_OUTPUT.print("DNS responds: ");
DEBUG_OUTPUT.print(_resolvedIP[0]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[1]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[2]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[3]);
DEBUG_OUTPUT.print(" for ");
DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix());
#endif
}
void DNSServer::replyWithCustomCode() {
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->RCode = (unsigned char) _errorReplyCode;
_dnsHeader->QDCount = 0;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, sizeof(DNSHeader));
_udp.endPacket();
}

View File

@@ -1,68 +0,0 @@
#ifndef DNSServer_h
#define DNSServer_h
#include <WiFiUdp.h>
#define DNS_QR_QUERY 0
#define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0
enum class DNSReplyCode {
NoError = 0,
FormError = 1,
ServerFailure = 2,
NonExistentDomain = 3,
NotImplemented = 4,
Refused = 5,
YXDomain = 6,
YXRRSet = 7,
NXRRSet = 8
};
struct DNSHeader {
uint16_t ID; // identification number
unsigned char RD : 1; // recursion desired
unsigned char TC : 1; // truncated message
unsigned char AA : 1; // authoritive answer
unsigned char OPCode : 4; // message_type
unsigned char QR : 1; // query/response flag
unsigned char RCode : 4; // response code
unsigned char Z : 3; // its z! reserved
unsigned char RA : 1; // recursion available
uint16_t QDCount; // number of question entries
uint16_t ANCount; // number of answer entries
uint16_t NSCount; // number of authority entries
uint16_t ARCount; // number of resource entries
};
class DNSServer {
public:
DNSServer();
void processNextRequest();
void setErrorReplyCode(const DNSReplyCode &replyCode);
void setTTL(const uint32_t &ttl);
// Returns true if successful, false if there are no sockets available
bool start(const uint16_t &port,
const String &domainName,
const IPAddress &resolvedIP);
// stops the DNS server
void stop();
private:
WiFiUDP _udp;
uint16_t _port;
String _domainName;
unsigned char _resolvedIP[4];
int _currentPacketSize;
unsigned char* _buffer;
DNSHeader* _dnsHeader;
uint32_t _ttl;
DNSReplyCode _errorReplyCode;
void downcaseAndRemoveWwwPrefix(String &domainName);
String getDomainNameWithoutWwwPrefix();
bool requestIncludesOnlyOneQuestion();
void replyWithIP();
void replyWithCustomCode();
};
#endif

View File

@@ -1,847 +1,144 @@
///
///
/// Grow Controller
///
///
/// The beginnings of a WiFi accessible switch.
///
#include <dummy.h>
#include <tcp_axtls.h>
#include <SyncClient.h>
#include <ESPAsyncTCPbuffer.h>
#include <ESPAsyncTCP.h>
#include <async_config.h>
#include <AsyncPrinter.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266httpUpdate.h>
//#include <ESP8266mDNS.h>
#include <ESP8266SSDP.h>
#include "ProjGlobals.h" // Some global settings
#include "WiFiConfiguration.h" //
#include "WiFiStateHandler.h" // Startup as AP or client and transition
#include "CustomHandlers.h" // Handlers for the web pages
#include "PlantModel.h" // The Plant Model controller/simulator/status
const String AP_NAME = "GrowController"; ///< Network name when in Access Point mode
const String AP_VERS = "v1.00.11"; ///< Compared to the server version for SW Updates
ESP8266WebServer server(80);
ConfigManager wifiConfig; ///< Track various configuration items
//ESP8266HTTPUpdate Updater;
bool updateCheck = false; ///< Set when a SW update check is pending
// Application includes
#include "./DNSServer.h" // Patched lib
#include "WiFiConfiguration.h"
#include "Web_RootPage.h"
#include "Web_RSSIGraph.h"
#include "Web_FavIcon.h"
#include "Web_APConfig.h"
#include "ProjGlobals.h"
#include "Web_Resources.h"
#include "GrowController.h" // APIs available in this (.ino) file
#include "fauxmoESP.h"
// If there is no hardware, such as with an ESP-01 module only,
// set this. Then it won't loop through reset constantly, and
// other behaviors that will be determined.
#define HW_SIMULATE
// SW Update Deployment instructions:
// Register the urls with custom handlers
// Register the SSDP interface
//
// 1) If any resources changed, rebuild Web_Resources.h
// 2) Update this version number
// 3) Build the binary
// 4) Rename the .bin to match the version (e.g. GrowController v1.02.12.bin)
// 5) Move the product (.bin) to the server location
// 6) Update the PHP script on the server to match this version (if it does not auto-adapt)
// 7) Visit the node's web page and initiate an update, or let it check on its own cycle
//
const String MyVer = "GrowController v2.00.11"; ///< Compared to the server version for SW Updates
#define UPDATE_INTERVAL (12 * 60 * 60 * 1000) ///< Check once every 12 hours
bool swUpdateInProcess = false; ///< Status of an update to keep the relays from running
// ESP12-E pinout Version 2
//
// +------------------+
// | __ __ _____ |
// | | |_| |_| |
// | |__________ |
// |__________________|
// Reset | !Reset Tx | Tx
// Current | ADC Rx | Rx
// Prog En | CHPD-EN GPIO5 | FAULT
// SW_RESET | GPIO16 GPIO4 | SW_TOGGLE
// OLED SCL | GPIO14 GPIO0 |
// RLY Set2 | GPIO12 GPIO2 | OLED SDA
// RLY Set1 | GPIO13 GPIO15 | Pull-down
// | Vcc Gnd |
// +------------------+
// ESP12-E pinout Version 1
//
// +------------------+
// | __ __ _____ |
// | | |_| |_| |
// | |__________ |
// |__________________|
// Reset | !Reset Tx | Tx
// Current | ADC Rx | Rx
// Prog En | CHPD-EN GPIO5 |
// SW_RESET | GPIO16 GPIO4 | SW_TOGGLE
// RLY Set2 | GPIO14 GPIO0 |
// LED On | GPIO12 GPIO2 |
// RLY Set1 | GPIO13 GPIO15 | LED WiFi
// | Vcc Gnd |
// +------------------+
// +-------------+
// SW_RESET ---+----|(----------->| WiFiConfig |-----> LED_WIFI
// | | |
// | | |-----> isStationMode
// | |-------------|
// +----|>o---------->| Timer |
// | |-----> Factory Reset
// RESET_MS --------------------->| |
// +-------------+
//
#define SW_RESET 16 ///< The local reset switch
#define RESET_MS 8000 ///< Hold Reset for this long to factory reset
#define LED_WIFI 15 ///< The Pin controlling the WiFi on LED
bool isStationMode = false; ///< Tracks if the WiFi is in station mode
// WiFI LED Duty Cycle
//
// |******* | AP Mode
// |************************* | Joining AP
// |************************************************ | Joined
// | | | | | |
// 0 100 200 300 400 500ms
//
#define LED_APMODE__MS 80
#define LED_JOINING_MS 250
#define LED_JOINED__MS 490
#define LED_PERIOD__MS 500
// __ //
// | \ //
// onRef ------------------------------|- \ +-----------+ //
// | +-----| S Q |--+--> CurrStatus //
// +--------|+ / | | | //
// +---------+ | |__/ +--| R !Q | +--> LED_ON //
// ANA_IN --+--->| average |----+-->iRawSum/X | +-----------+ //
// | +---------+ | __ | //
// | | | \ | //
// +--> iRaw +--------|- \ | //
// | +--+ //
// offRef ------------------------------|+ / //
// |__/ //
//
#define ANA_IN A0 ///< The Analog input signal
#define SENSE_SAMPLE_MS 20 ///< The Sample rate for the analog input
#define AVG_RATIO 64 ///< Average Ratio (new is 1/AVG_RATIO of old)
#define LED_ON 12 ///< The load-on indicating LED
int32_t iRaw; ///< The raw sampled value
int32_t iRawSum; ///< The Averaged Sum of Raw Samples
bool CurrStatus = false; ///< Track the current sense indication of power on/off
void StartWebServer();
// _ +------------------+
// Turn On -------------| \____ | +-----------+ |
// +--|>o--|_/ | _ +-->| D !Q |--+
// CurrStatus --| and +-------->) \ | |
// | _ +-------->) +--->| ck Q |--------> RelayState
// +-------| \____| +---->)_/ +-----------+
// Turn Off-------------|_/ | or
// and |
// SW_TOGGLE ----|>o---------------+
//
#define SW_TOGGLE 4 ///< The local toggle switch
bool RelayState = false; ///< The current tracked relay drive state
// +-----------+
// | _ _ |
// | _/ _| |_ |--------> Relay Set
// RelayState ---------------------------------->| _ _ |
// | \_ _| |_ |--------> Relay Reset
// | |
// +-----------+
//
#define RELAY_PULSE_MS 15 ///< Duration of a pulse
#define RLY_SET1 13 ///< The set-pin for the relay
#define RLY_SET2 14 ///< The reset-pin for the relay
// Timing:
//
// | | | | |
// _________________________
// 3WaySwitch ____________________/ \__________________
// | | |b->| | |b->|
// ______________________
// RelayState __________/ \______________________
// | |a->| | |a->| |
// | | | | |
// +++++++ ++++++++++
// +++ +++ +++ +++
// AnalogIn ++++++++++++ ++++++++++ +++++++++++++++
// | | | | |
// CurrStatus |Off |On |Off |On |Off
//
// a) Locally applied change in load to filtered AnalogIn delay
// b) Externally applied change in load to filtered AnalogIn delay
//
bool DebugValue; ///< Makes it chatty while running
const byte DNS_PORT = 53; ///< Capture DNS requests on port 53
IPAddress apIP(192,168,4,1); ///< Private network for server
DNSServer dnsServer; ///< Create the DNS object
ESP8266WebServer server(80); ///< For the user experience
fauxmoESP fauxmo; ///< For the WEMO emulator
ConfigManager wifiConfig; ///< Track various configuration items
ESP8266HTTPUpdate Updater;
bool updateCheck = false; ///< Set when a SW update check is pending
uint16_t autoOffTimer; ///< When the output is turned on, this is set to timeout in seconds
// local functions
void WiFiStateHandler(); ///< WiFi interface manager
void StartWebServer(); ///< Activate the web server
void HandleNotFound(); ///< Webpage for not-found URL
void ProcessAutoOff(bool init = false); ///< Process the auto-off timer
String AP_SSID = "GrowController"; ///< When in Access Point, this is the prefix
void GetMac();
String macToStr(const uint8_t* mac); ///< Create C++ String with the mac
String clientMac = ""; ///< Hosts the MAC to easily identify this node
// ===========================================================================
typedef enum {
WFC_ConnectToAP,
WFC_JoiningAP,
WFC_JoinedAP,
WFC_CreateAP,
WFC_HostingAP,
} WiFiConnectState;
WiFiConnectState wifiConState; ///< Track the current WiFi state
void SetLED(int pin, int state) {
digitalWrite(pin, state);
if (pin == LED_WIFI)
; // Serial.printf("SET LED_WIFI to %d\n", state);
else if (pin == LED_ON)
Serial.printf("SET LED_ON to %d\n", state);
else
Serial.printf("***** ERROR setting LED pin %d to %d\n", pin, state);
}
void ResetMonitor(bool init = false) {
static unsigned long timeAtReset;
static bool monitorActive;
if (init) {
timeAtReset = millis();
monitorActive = true;
} else if (monitorActive) {
int sw_state = digitalRead(SW_RESET);
#ifdef HW_SIMULATE
sw_state = 1;
#endif
if (sw_state == 1) {
// not pressed
monitorActive = false;
} else if ((millis() - timeAtReset) > RESET_MS) {
// reset has been held long enough
FactoryReset(true);
}
}
}
//
// Average the new sample by summation with 7/8 of the running Sum.
//
void ProcessCurrentSense() {
static unsigned long last = millis();
unsigned long now = millis();
if (now - last > SENSE_SAMPLE_MS) {
last = now;
iRaw = analogRead(ANA_IN); // 1023 = 1.0v
iRawSum = iRawSum - (iRawSum / AVG_RATIO) + iRaw; // Now have running avg.
if (DebugValue)
Serial.printf("Sample: %d, rawSum: %d, relay: %d (%d%d), \n",
iRaw, iRawSum, RelayState, digitalRead(RLY_SET1), digitalRead(RLY_SET2));
if (iRawSum / AVG_RATIO > wifiConfig.getOnRef() && !CurrStatus) {
CurrStatus = true;
SetLED(LED_ON, 1);
ProcessAutoOff(true);
autoOffTimer = wifiConfig.getAutoOff(); // if zero, we do nothing...
Serial.printf("Auto Off Timer set to %d\n", autoOffTimer);
} else if (iRawSum / AVG_RATIO < wifiConfig.getOffRef() && CurrStatus) {
CurrStatus = false;
SetLED(LED_ON, 0);
}
}
}
// _______ ___ __ _______
// Press \_________________________/ \___/ \___/
// | |
// v v
// _ _
// Toggle _______/ \____________.............../ \______________....
//
// Sample | | | | | ...
// Block |---- 80 ms ---| |---- 80 ms -----|
#define Block_ms 800
#define SAMPL_ms 100
void ProcessToggleSw() {
static unsigned long last = millis();
static bool lastSWState = false;
static uint8_t blockTics;
unsigned long now = millis();
if (now - last > SAMPL_ms) {
last = now;
if (blockTics) {
blockTics--;
return;
}
bool swState = !digitalRead(SW_TOGGLE); // active low is pressed.
if (DebugValue)
Serial.printf("Sample: %d, rawSum: %d, relay: %d (%d%d), sw: %d\n",
iRaw, iRawSum, RelayState, digitalRead(RLY_SET1), digitalRead(RLY_SET2), swState);
if (swState != lastSWState && swState == true) {
Serial.printf("Toggle pushed\n");
blockTics = Block_ms / SAMPL_ms;
SetCircuit(CMD_Toggle);
}
lastSWState = swState;
}
}
// Drive Relay controls pulsing either the set or the reset pin
//
// If the control signal changes, then it will generate a pulse on the
// appropriate relay driver pin, ensuring that the alternate pin is
// first off.
//
// This API is also called periodically, which permits it to then
// time and auto-off any active control pin.
//
// @param i can be -1 for timing, 0 to drive one relay pin, 1 to drive the other.
//
void DriveRelay(int i = -1) {
static int currentSignal = -1;
static bool isActive = false;
static unsigned long timeSet = 0;
if (swUpdateInProcess)
i = 2;
switch (i) {
case -1:
// Any active timing to process
if (isActive && (millis() - timeSet > RELAY_PULSE_MS)) {
digitalWrite(RLY_SET1, LOW);
digitalWrite(RLY_SET2, LOW);
isActive = false;
}
break;
case 0:
if (i != currentSignal) {
currentSignal = i;
digitalWrite(RLY_SET2, LOW); // Turn off the alternate
digitalWrite(RLY_SET1, HIGH);
timeSet = millis();
isActive = true;
}
break;
case 1:
if (i != currentSignal) {
currentSignal = i;
digitalWrite(RLY_SET1, LOW); // Turn off the alternate
digitalWrite(RLY_SET2, HIGH);
timeSet = millis();
isActive = true;
}
break;
case 2:
default:
// Error, or the force-off state
digitalWrite(RLY_SET1, LOW); // Turn off the elements
digitalWrite(RLY_SET2, LOW);
isActive = false;
break;
}
}
void ProcessNameChange() {
char xName[CFG_NAMESIZE];
fauxmo.getDeviceName(0, xName, CFG_NAMESIZE);
if (0 != strcmp(wifiConfig.getName().c_str(), xName)) {
fauxmo.renameDevice(0, wifiConfig.getName().c_str());
}
}
void ProcessSWUpdate() {
static unsigned long lastCheck;
unsigned long nowCheck = millis();
if ((nowCheck - lastCheck) > (UPDATE_INTERVAL)) {
lastCheck = nowCheck;
updateCheck = true;
}
if (updateCheck) {
WiFiClient wifiClient;
swUpdateInProcess = true;
Serial.printf("SW Update start '%s'...\n", wifiConfig.getURL().c_str());
t_httpUpdate_return swRet = Updater.update(wifiClient, wifiConfig.getURL(), MyVer);
switch (swRet) {
default:
case HTTP_UPDATE_FAILED:
Serial.printf("SW Update - failed.\n");
Serial.printf("Error: %s\n", Updater.getLastErrorString().c_str());
delay(100);
ESP.restart();
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.printf("SW Update - no updates.\n");
break;
case HTTP_UPDATE_OK:
Serial.printf("SW Update - update ok.\n");
break;
}
swUpdateInProcess = false;
updateCheck = false;
Serial.printf("SW Update - process end.\n");
}
}
void ProcessConsole() {
if (Serial.available()) {
int ch = Serial.read();
Serial.printf("Serial.available() => %d\n", ch);
switch (ch) {
case '0':
iRawSum = 10;
break;
case '1':
iRawSum = 1000;
break;
case 'd':
DebugValue = !DebugValue;
break;
case 'u':
updateCheck = true;
break;
case '\x0D': // Ignore <cr>
break;
case '?':
default:
DebugValue = false;
Serial.printf("Commands:\n");
Serial.printf(" 0|1 Off or On (by forcing rawSum\n");
Serial.printf(" d Debug toggle\n");
Serial.printf(" u update check\n");
break;
}
}
static unsigned long last = millis();
if (millis() - last > 5000) {
static uint32_t refFreeHeap;
uint32_t freeHeap = ESP.getFreeHeap();
last = millis();
if (freeHeap != refFreeHeap)
Serial.printf("[MAIN] Free heap: %d bytes\n", ESP.getFreeHeap());
refFreeHeap = freeHeap;
}
}
void ProcessAutoOff(bool init) {
static unsigned long lastCheck;
unsigned long nowCheck = millis();
if (init)
lastCheck = nowCheck;
if (autoOffTimer && ((nowCheck - lastCheck) >= 1000)) {
lastCheck += 1000;
--autoOffTimer;
SetLED(LED_ON, autoOffTimer & 1); // During auto-Off, blink slowly...
if (autoOffTimer == 0) {
Serial.printf("AutoOff time remaining 0 sec. Off Now.\n");
SetCircuit(CMD_Off);
}
else {
Serial.printf("AutoOff time remaining %d sec\n", autoOffTimer);
}
}
}
void HWIOInit() {
// User push-buttons
pinMode(SW_TOGGLE, INPUT);
pinMode(SW_RESET, INPUT);
// Turn off relay drivers
pinMode(RLY_SET1, OUTPUT);
digitalWrite(RLY_SET1, 0);
pinMode(RLY_SET2, OUTPUT);
digitalWrite(RLY_SET2, 0);
// Turn off user LEDs
pinMode(LED_ON, OUTPUT);
digitalWrite(LED_ON, 0);
pinMode(LED_WIFI, OUTPUT);
digitalWrite(LED_WIFI, 0);
void setup() {
Serial.begin(115200);
Serial.printf("\n");
Serial.printf("\n******************************************************************\n");
Serial.printf(" %s Web Server - Build " __DATE__ " " __TIME__ "\n", AP_NAME.c_str());
Serial.printf(" Version %s\n", AP_VERS.c_str());
Serial.printf(" Copyright (c) 2021 by Smartware Computing, all rights reserved.\n");
Serial.printf("******************************************************************\n");
wifiConfig.load();
}
void setup(void) {
HWIOInit();
ResetMonitor(true); /// Initialize the reset switch monitor for long press
Serial.begin(115200);
Serial.printf("\n******************************************************************\n");
Serial.printf(" GrowController Web Server - Build " __DATE__ " " __TIME__ "\n");
Serial.printf(" Version %s\n", MyVer.c_str());
Serial.printf(" Copyright (c) 2018 by Smartware Computing, all rights reserved.\n");
Serial.printf("******************************************************************\n");
void loop() {
//
// Process WiFi State Changes
//
static WiFiConnectState lastState = WFC_Idle;
WiFiConnectState currState = WiFiStateHandler();
if (lastState != currState) {
lastState = currState;
switch (currState) { // On change to this state
default:
break;
case WFC_HostingAP:
case WFC_JoinedAP:
StartWebServer();
break;
}
} else {
switch (currState) {
default:
break;
case WFC_HostingAP:
case WFC_JoinedAP:
server.handleClient();
break;
}
}
GetMac();
AP_SSID += "-";
AP_SSID += clientMac;
wifiConfig.load();
String name = wifiConfig.getName();
String ssid = wifiConfig.getSSID();
String pass = wifiConfig.getPassword();
if (ssid == "" || pass == "")
wifiConState = WFC_CreateAP;
else
wifiConState = WFC_ConnectToAP;
#if 0
if (MDNS.begin("esp")) { // clientMac.c_str()
//MDNS.addService("switch", "tcp", 80);
//MDNS.addServiceTxt("switch", "tcp", "switchkey", "SWITCHVALUE");
Serial.printf("MDNS responder started.\n");
}
#endif
StartWebServer();
#if 1
fauxmo.addDevice(name.c_str());
fauxmo.enable(true);
fauxmo.onSetState([](unsigned char device_id, const char * device_name, bool state) {
Serial.printf("[MAIN] Device #%d (%s) state command: %s\n", device_id, device_name, state ? "ON" : "OFF");
if ((state && !CurrStatus)
|| (!state && CurrStatus)) {
SetCircuit(RelayState ? CMD_Off : CMD_On);
}
});
fauxmo.onGetState([](unsigned char device_id, const char * device_name) {
(void) device_id;
(void) device_name;
return CurrStatus; // GetCircuitStatus(); // whatever the state of the device is
});
#endif
//
//
//
simulation();
// @todo delay(1);
}
void loop(void) {
ResetMonitor(); // Monitor for long press to factory reset
WiFiStateHandler(); // start/connect as AP or Station
ProcessSWUpdate(); // query the server for fresh SW
fauxmo.handle(); // WEMO interface
ProcessNameChange(); // Update fauxmo if the name changes
ProcessCurrentSense(); // Get status by measuring current
ProcessToggleSw(); // Monitor for user pressing toggle
DriveRelay(); // Do what the relays need, if anything
ProcessConsole();
ProcessAutoOff(); // If auto-off is enabled...
}
// ###########################################################################
void WiFiStateHandler() {
static unsigned long timeStateChange = 0;
static unsigned long timeLEDControl;
unsigned long timeLEDCycle;
String ssid = wifiConfig.getSSID();
String pass = wifiConfig.getPassword();
IPAddress myIP;
timeLEDCycle = millis() - timeLEDControl;
if (timeLEDCycle >= LED_PERIOD__MS) {
timeLEDControl = millis();
timeLEDCycle = 0;
}
switch (wifiConState) {
case WFC_ConnectToAP:
isStationMode = true;
WiFi.mode(WIFI_STA);
ssid = wifiConfig.getSSID();
pass = wifiConfig.getPassword();
WiFi.begin(ssid.c_str(), pass.c_str());
timeStateChange = millis();
timeLEDControl = timeStateChange;
wifiConState = WFC_JoiningAP;
break;
case WFC_JoiningAP:
if (timeLEDCycle <= LED_JOINING_MS)
SetLED(LED_WIFI, 1);
else
SetLED(LED_WIFI, 0);
if (WiFi.status() == WL_CONNECTED) {
myIP = WiFi.localIP();
Serial.printf("IP Address: %s\n", myIP.toString().c_str());
timeStateChange = millis();
StartWebServer();
wifiConState = WFC_JoinedAP;
} else if (millis() - timeStateChange > 30000) {
timeStateChange = millis();
wifiConState = WFC_CreateAP; // failed for 30 sec, now what. retry or CreateAP?
}
break;
case WFC_CreateAP:
if (timeLEDCycle <= LED_APMODE__MS)
SetLED(LED_WIFI, 1);
else
SetLED(LED_WIFI, 0);
isStationMode = false;
Serial.printf("Starting Access Point %s\n", AP_SSID.c_str());
WiFi.softAP(AP_SSID.c_str());
myIP = WiFi.softAPIP();
Serial.printf("IP Address: %s\n", myIP.toString().c_str());
dnsServer.start(DNS_PORT, "*", apIP);
timeStateChange = millis();
StartWebServer();
wifiConState = WFC_HostingAP;
break;
case WFC_JoinedAP:
if (timeLEDCycle <= LED_JOINED__MS)
SetLED(LED_WIFI, 1);
else
SetLED(LED_WIFI, 0);
server.handleClient();
break;
case WFC_HostingAP:
if (timeLEDCycle <= LED_APMODE__MS)
SetLED(LED_WIFI, 1);
else
SetLED(LED_WIFI, 0);
server.handleClient();
dnsServer.processNextRequest();
break;
}
}
bool GetCircuitStatus() {
return CurrStatus;
}
void SetCircuit(CircuitCmd_T newState) {
if (swUpdateInProcess)
return;
switch (newState) {
case CMD_Off:
if (GetCircuitStatus()) {
RelayState = !RelayState;
DriveRelay(RelayState);
}
break;
case CMD_On:
if (!GetCircuitStatus()) {
RelayState = !RelayState;
DriveRelay(RelayState);
}
break;
case CMD_Toggle:
RelayState = !RelayState;
DriveRelay(RelayState);
break;
default:
break;
}
}
void HandleCircuitOn() {
SetCircuit(CMD_On);
HandleGetState(); // Reply with current state
}
void HandleCircuitOff() {
SetCircuit(CMD_Off);
HandleGetState(); // Reply with current state
}
void HandleCircuitToggle() {
SetCircuit(CMD_Toggle);
HandleGetState(); // Reply with current state
}
// {
// "id": "5c:cf:7f:c0:52:82",
// "name": "3-Way Switch",
// "state": 0,
// "sense": 56,
// "ip": "192.168.1.23",
// "rssi": -63,
// "countdown": "3:45",
// "uptime": "0:11:45",
// "wifimode": "station"|"ap",
// }
//
void HandleGetState() {
int day, hr, min, sec;
char _upTime[15]; // 0000.00:00:00\0 + 1 spare
char _timeout[10]; // 00:00:00\0 + 1 spare
sec = millis() / 1000;
min = sec / 60;
hr = min / 60;
day = hr / 24;
if (day)
snprintf(_upTime, sizeof(_upTime), "%dd %d:%02d:%02d", day, hr % 24, min % 60, sec % 60);
else if (hr)
snprintf(_upTime, sizeof(_upTime), "%d:%02d:%02d", hr, min % 60, sec % 60);
else
snprintf(_upTime, sizeof(_upTime), "%02d:%02d", min % 60, sec % 60);
sec = autoOffTimer;
min = sec / 60;
hr = min / 60;
if (hr)
snprintf(_timeout, sizeof(_timeout), "%d:%02d:%02d", hr, min % 60, sec % 60);
else
snprintf(_timeout, sizeof(_timeout), "%d:%02d", min, sec % 60);
GetMac();
String modeText = (isStationMode) ? "Station" : "Access Point";
String message = "{ \"id\": \"" + String(clientMac) + "\", ";
message += "\"name\": \"" + wifiConfig.getName() + "\", ";
message += "\"version\": \"" + MyVer + "\", ";
message += "\"state\": " + String(CurrStatus) + ", ";
message += "\"raw\": " + String(iRaw) + ", ";
message += "\"sense\": " + String(iRawSum / AVG_RATIO) + ", ";
message += "\"ip\": \""
+ String(WiFi.localIP()[0])
+ "." + String(WiFi.localIP()[1])
+ "." + String(WiFi.localIP()[2])
+ "." + String(WiFi.localIP()[3])
+ "\", ";
message += "\"rssi\": " + String(WiFi.RSSI()) + ", ";
message += "\"countdown\": \"" + String(_timeout) + "\", ";
message += "\"uptime\": \"" + String(_upTime) + "\", ";
message += "\"wifimode\": \"" + modeText + "\", ";
message += "\"toggle\": " + String(!digitalRead(SW_TOGGLE)) + ", ";
message += "\"reset\": " + String(!digitalRead(SW_RESET));
message += " }";
if (DebugValue)
Serial.println(message + "\0");
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send(200, "text/plain", message);
}
// Prototypes:
// const char Button_css[]
// const char favicon_ico[]
// const char Green1x1_png[]
// const char index_htm[]
// const char index_js[]
// const char rssi_htm[]
// const char rssi_js[]
void HandleSWUpdateCheck() {
updateCheck = true;
HandleAPConfigPage();
}
void HandleButton_css() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/plain", Button_css);
}
void HandleIndex_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/plain", index_js);
}
void HandleAbout_htm() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/html", about_htm);
}
void HandleAbout_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send_P(200, "text/plain", about_js);
}
void HandleMyIP_js() {
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
String message = "var mySite = 'http://";
message += String(WiFi.localIP()[0]);
message += "." + String(WiFi.localIP()[1]);
message += "." + String(WiFi.localIP()[2]);
message += "." + String(WiFi.localIP()[3]);
message += "';\n";
server.send(200, "text/plain", message);
}
void StartWebServer() {
// path /firmware
//httpUpdater.setup(&server, update_path, update_username, update_password);
server.on("/", HandleRootPage);
server.on("/myip.js", HandleMyIP_js);
server.on("/button.css", HandleButton_css);
server.on("/index.js", HandleIndex_js);
server.on("/about", HandleAbout_htm);
server.on("/about.js", HandleAbout_js);
server.on("/favicon.ico", HandleFavIcon);
server.on("/PlantModel.png", HandlePlantModel);
server.on("/Heater.png", HandleHeater);
server.on("/Water.png", HandleWater);
server.on("/config", HandleAPConfigPage);
server.on("/swupdatecheck", HandleSWUpdateCheck);
server.on("/scan", HandleAPScan);
server.on("/green1x1.png", HandleGreen1x1);
server.on("/rssi", HandleRSSIPage);
server.on("/rssi.js", HandleRSSI_JS);
server.on("/curr", HandleCurrPage);
server.on("/curr.js", HandleCurr_JS);
server.on("/on", HandleCircuitOn);
server.on("/off", HandleCircuitOff);
server.on("/toggle", HandleCircuitToggle);
server.on("/state", HandleGetState);
server.on("/icon.png", HandleIcon);
server.on("/Open.png", HandleOpenDoor);
server.onNotFound(HandleNotFound);
server.begin();
Serial.println("HTTP server started");
// path /firmware
Serial.println("HTTP server starting ...");
//httpUpdater.setup(&server, update_path, update_username, update_password);
server.on("/", HandleRootPage);
server.on("/config", HandleAPConfigPage);
server.on("/myip.js", HandleMyIP_js);
server.on("/nav.js", HandleNav_js);
server.on("/plantmodel.png", HandlePlantModel);
server.on("/plantmodel.css", HandlePlantModel_css);
server.on("/button.css", HandleButton_css);
server.on("/index.js", HandleIndex_js);
server.on("/about.htm", HandleAbout_htm);
server.on("/about.js", HandleAbout_js);
server.on("/favicon.ico", HandleFavIcon);
server.on("/soilheater_on.png", HandleHeaterOn);
server.on("/soilheater_off.png", HandleHeaterOff);
server.on("/thermometer.png", HandleThermometer);
server.on("/soilmoisture.png", HandleSoilMoisture);
server.on("/pointer.png", HandlePointer);
server.on("/water.png", HandleWater);
server.on("/green1x1.png", HandleGreen1x1);
server.on("/rssi.htm", HandleRSSIPage);
server.on("/rssi.js", HandleRSSI_js);
server.on("/curr.htm", HandleCurrPage);
server.on("/curr.js", HandleCurr_js);
server.on("/icon.png", HandleIcon);
server.on("/open.png", HandleOpenDoor);
server.on("/swupdatecheck", HandleSWUpdateCheck);
server.on("/scan", HandleAPScan);
//server.on("/on", HandleCircuitOn);
//server.on("/off", HandleCircuitOff);
//server.on("/toggle", HandleCircuitToggle);
server.on("/state", HandleGetState);
server.on("/description.xml", HTTP_GET, []() {
Serial.printf("description.xml handler.\n");
SSDP.schema(server.client());
});
server.onNotFound(HandleNotFound);
server.begin();
Serial.println("HTTP server started.");
//
// SSDP Startup
//
Serial.printf("SSDP server starting ...\n");
SSDP.setSchemaURL("description.xml");
SSDP.setHTTPPort(80);
SSDP.setName(wifiConfig.getName().c_str()); // "Philips hue clone");
SSDP.setSerialNumber("001788102201");
SSDP.setURL("/"); // Root Page
SSDP.setModelName(AP_NAME.c_str()); // "Philips hue bridge 2012");
SSDP.setModelNumber("929000226503");
SSDP.setModelURL("https://www.smart-family.net/GrowController");
SSDP.setManufacturer("Smartware Computing");
SSDP.setManufacturerURL("https://www.smart-family.net");
SSDP.setDeviceType("upnp:rootdevice");
SSDP.begin();
Serial.println("SSDP server started.");
}
void HandleNotFound() {
Serial.printf("not found: %s\n", server.uri().c_str());
const DirEntry *de = Directory;
boolean found = false;
while (*de->Filename) {
if (0 == strcmp(de->Filename, server.uri().c_str())) {
Serial.printf("send %s\n", de->Filename);
server.send_P(200, de->Filetype, de->Filedata, de->Filesize);
found = true;
break;
} else {
de++;
}
}
if (!found) {
Serial.printf("!found - redirect to home\n");
server.sendHeader("Location", String("/"), true);
server.send(302, "text/plain", "Not supported. Heading for home.");
}
}
void GetMac() {
unsigned char mac[6];
WiFi.macAddress(mac);
clientMac = macToStr(mac);
}
String macToStr(const uint8_t * mac) {
String result;
for (int i = 0; i < 6; ++i) {
result += String(mac[i], 16);
if (i < 5)
result += ':';
}
return result;
void FactoryReset(bool forceRestart) {
Serial.println("Factory Reset Configuration...\n");
wifiConfig.factoryReset();
wifiConfig.save();
if (forceRestart) {
Serial.println("Restart in 3 sec ...");
delay(3000);
ESP.restart();
}
}

View File

@@ -1,23 +0,0 @@
#ifndef GROWCONTROLLER_H
#define GROWCONTROLLER_H
bool GetCircuitStatus();
/// Command for the circuit
typedef enum {
CMD_Off, ///< Command it off
CMD_On, ///< Command it on
CMD_Toggle ///< Command a toggle
} CircuitCmd_T;
/// Set the state
/// @param[in] newState
/// - 0 = Off
/// - 1 = On
/// - 2 = Toggle
///
void SetCircuit(CircuitCmd_T newState);
#endif // GROWCONTROLLER_H

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<NotepadPlus>
<Session activeView="0">
<mainView activeIndex="16">
<File firstVisibleLine="0" xOffset="0" scrollWidth="1152" startPos="66" endPos="135" selMode="0" offset="0" wrapCount="1" lang="HTML" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\about.htm" backupFilePath="" originalFileLastModifTimestamp="-1755984831" originalFileLastModifTimestampHigh="30918723" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7864439" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1152" startPos="135" endPos="135" selMode="0" offset="0" wrapCount="1" lang="HTML" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\curr.htm" backupFilePath="" originalFileLastModifTimestamp="-1755304900" originalFileLastModifTimestampHigh="30918723" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="8699361" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="15" xOffset="0" scrollWidth="1161" startPos="1008" endPos="1008" selMode="0" offset="0" wrapCount="1" lang="HTML" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\index.htm" backupFilePath="" originalFileLastModifTimestamp="582500597" originalFileLastModifTimestampHigh="30918741" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="576" startPos="399" endPos="412" selMode="0" offset="0" wrapCount="1" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\about.js" backupFilePath="" originalFileLastModifTimestamp="304003083" originalFileLastModifTimestampHigh="30772255" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="369" startPos="54" endPos="54" selMode="0" offset="0" wrapCount="1" lang="CSS" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\button.css" backupFilePath="" originalFileLastModifTimestamp="-809889817" originalFileLastModifTimestampHigh="30918529" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="-285211648" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="272" startPos="92" endPos="92" selMode="0" offset="0" wrapCount="1" lang="CSS" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\plantmodel.css" backupFilePath="" originalFileLastModifTimestamp="1181250598" originalFileLastModifTimestampHigh="30918741" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\curr.js" backupFilePath="" originalFileLastModifTimestamp="-1210165259" originalFileLastModifTimestampHigh="30918511" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7733353" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="756" startPos="727" endPos="734" selMode="0" offset="0" wrapCount="1" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\index.js" backupFilePath="" originalFileLastModifTimestamp="1069068571" originalFileLastModifTimestampHigh="30918535" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="1139900416" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\rssi.js" backupFilePath="" originalFileLastModifTimestamp="304113078" originalFileLastModifTimestampHigh="30772255" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="1139900416" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1451" startPos="231" endPos="287" selMode="0" offset="0" wrapCount="1" lang="HTML" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\rssi.htm" backupFilePath="" originalFileLastModifTimestamp="-1754114876" originalFileLastModifTimestampHigh="30918723" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="131072" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1071" startPos="100" endPos="100" selMode="0" offset="0" wrapCount="1" lang="HTML" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\navigation.htm" backupFilePath="" originalFileLastModifTimestamp="-1753474825" originalFileLastModifTimestampHigh="30918723" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1089" startPos="212" endPos="212" selMode="0" offset="0" wrapCount="1" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\nav.js" backupFilePath="" originalFileLastModifTimestamp="2129232066" originalFileLastModifTimestampHigh="30918721" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="594" startPos="152" endPos="152" selMode="0" offset="0" wrapCount="1" lang="JavaScript" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\myip.js" backupFilePath="" originalFileLastModifTimestamp="1696718592" originalFileLastModifTimestampHigh="30918535" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="257" xOffset="0" scrollWidth="994" startPos="11252" endPos="11252" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\CustomHandlers.cpp" backupFilePath="" originalFileLastModifTimestamp="-1709816653" originalFileLastModifTimestampHigh="30918741" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7733353" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="721" startPos="661" endPos="661" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\CustomHandlers.h" backupFilePath="" originalFileLastModifTimestamp="-931836632" originalFileLastModifTimestampHigh="30918741" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="131072" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="71" xOffset="0" scrollWidth="749" startPos="3433" endPos="3443" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\gcfw.ino" backupFilePath="" originalFileLastModifTimestamp="-347279647" originalFileLastModifTimestampHigh="30918751" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7733353" mapWrapIndentMode="-1" mapIsWrap="no">
<Mark line="110" />
</File>
<File firstVisibleLine="10" xOffset="0" scrollWidth="567" startPos="487" endPos="487" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\ProjGlobals.h" backupFilePath="" originalFileLastModifTimestamp="320190381" originalFileLastModifTimestampHigh="30918752" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7733353" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Web_Resources.h" backupFilePath="" originalFileLastModifTimestamp="-1355064912" originalFileLastModifTimestampHigh="30918723" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="131072" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\WiFiConfiguration.cpp" backupFilePath="" originalFileLastModifTimestamp="755248262" originalFileLastModifTimestampHigh="30917526" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="-285211648" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\WiFiConfiguration.h" backupFilePath="" originalFileLastModifTimestamp="755308276" originalFileLastModifTimestampHigh="30917526" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7274563" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="1" startPos="0" endPos="0" selMode="0" offset="0" wrapCount="0" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\WiFiStateHandler.cpp" backupFilePath="" originalFileLastModifTimestamp="-1855225827" originalFileLastModifTimestampHigh="30917528" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="5439531" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="0" xOffset="0" scrollWidth="490" startPos="70" endPos="70" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\WiFiStateHandler.h" backupFilePath="" originalFileLastModifTimestamp="-1855157359" originalFileLastModifTimestampHigh="30917528" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="6553711" mapWrapIndentMode="-1" mapIsWrap="no" />
</mainView>
<subView activeIndex="1">
<File firstVisibleLine="4" xOffset="0" scrollWidth="648" startPos="278" endPos="278" selMode="0" offset="0" wrapCount="1" lang="C++" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\ProjGlobals.h" backupFilePath="" originalFileLastModifTimestamp="320190381" originalFileLastModifTimestampHigh="30918752" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="7733353" mapWrapIndentMode="-1" mapIsWrap="no" />
<File firstVisibleLine="36" xOffset="0" scrollWidth="272" startPos="98" endPos="107" selMode="0" offset="0" wrapCount="1" lang="CSS" encoding="-1" userReadOnly="no" filename="C:\Projects\GrowController\gcfw\Resources\plantmodel.css" backupFilePath="" originalFileLastModifTimestamp="1181250598" originalFileLastModifTimestampHigh="30918741" mapFirstVisibleDisplayLine="-1" mapFirstVisibleDocLine="-1" mapLastVisibleDocLine="-1" mapNbLine="-1" mapHigherPos="-1" mapWidth="-1" mapHeight="-1" mapKByteInDoc="0" mapWrapIndentMode="-1" mapIsWrap="no" />
</subView>
</Session>
</NotepadPlus>

246
Firmware/PlantModel.cpp Normal file
View File

@@ -0,0 +1,246 @@
//
//
// PlantModel
//
//
#include "ProjGlobals.h"
#include "PlantModel.h"
#include "WiFiStateHandler.h"
#include "Utility.h"
PlantData_T plant; /// This keeps track of everything relevant to this system
String OnOffText(bool isOn) {
if (isOn)
return String("On");
else
return String("Off");
}
String DrumStateText(DrumState_E state) {
switch (state) {
case DrumIsOpen:
return String("Open");
case DrumIsClosed:
return String("Closed");
case DrumIsMoving:
return String("Moving");
default:
return String("Error");
}
}
String DrumMotorText(DrumMotor_E state) {
switch (state) {
case DrumOff:
return String("Stopped");
case DrumCW:
return String("CW Rotation");
case DrumCCW:
return String("CCW Rotation");
default:
return String("Error");
}
}
String DrumOpenText(bool isOpen) {
if (isOpen)
return String("Open");
else
return String("not open");
}
String DrumClosedText(bool isClosed) {
if (isClosed)
return String("Closed");
else
return String("not closed");
}
String WaterInTankText(bool waterInTank) {
if (waterInTank)
return String("OK");
else
return String("Empty");
}
// {
// "id": "5c:cf:7f:c0:52:82",
// "name": "3-Way Switch",
// "state": 0,
// "sense": 56,
// "ip": "192.168.1.23",
// "rssi": "-63",
// "countdown": "3:45",
// "uptime": "0:11:45",
// "wifimode": "station"|"ap"
// }
//
void HandleGetState() {
int day, hr, minute, sec;
char _upTime[15]; // 0000.00:00:00\0 + 1 spare
sec = millis() / 1000;
minute = sec / 60;
hr = minute / 60;
day = hr / 24;
if (day)
snprintf(_upTime, sizeof(_upTime), "%dd %d:%02d:%02d", day, hr % 24, minute % 60, sec % 60);
else if (hr)
snprintf(_upTime, sizeof(_upTime), "%d:%02d:%02d", hr, minute % 60, sec % 60);
else
snprintf(_upTime, sizeof(_upTime), "%02d:%02d", minute % 60, sec % 60);
String modeText = (WiFiStateGet() == WFC_JoinedAP) ? "Station" : "Access Point";
String message = "{\n";
message += "\t\"id\": \"" + GetMacString() + "\",\n";
message += "\t\"name\": \"" + wifiConfig.getName() + "\",\n";
message += "\t\"version\": \"" + AP_VERS + "\",\n";
//message += "\t\"state\": " + String(CurrStatus) + ",\n";
//message += "\t\"raw\": " + String(iRaw) + ",\n";
//message += "\t\"sense\": " + String(iRawSum / AVG_RATIO) + ",\n";
message += "\t\"ip\": \""
+ String(WiFi.localIP()[0])
+ "." + String(WiFi.localIP()[1])
+ "." + String(WiFi.localIP()[2])
+ "." + String(WiFi.localIP()[3])
+ "\",\n";
message += "\t\"rssi\": \"" + String(WiFi.RSSI()) + "\",\n";
message += "\t\"uptime\": \"" + String(_upTime) + "\",\n";
message += "\t\"wifimode\": \"" + modeText + "\",\n";
snprintf(_upTime, sizeof(_upTime), "%02u:%02u", plant.SecondsToday/3660, (plant.SecondsToday/60)%60);
message += "\t\"TimeOfDay\": \"" + String(_upTime) + "\",\n";
message += "\t\"Fahrenheit\": \"" + String(plant.Fahrenheit) + "\",\n";
message += "\t\"ChamberTemp_C\": \"" + String(plant.ChamberTemp_C) + "\",\n";
message += "\t\"ChamberHumi\": \"" + String(plant.ChamberHumi) + " % \",\n";
message += "\t\"SoilHeater\": \"" + OnOffText(plant.SoilHeater) + "\",\n";
message += "\t\"SoilTemp_C\": \"" + String(plant.SoilTemp_C) + "\",\n";
message += "\t\"AmbientTemp_C\": \"" + String(plant.AmbientTemp_C) + "\",\n";
message += "\t\"DrumDoorIcon\": \"" + DrumStateText(plant.DrumDoorIcon) + "\",\n";
message += "\t\"DrumOpenSensor\": \"" + DrumOpenText(plant.DrumOpenSensor) + "\",\n";
message += "\t\"DrumClosedSensor\": \"" + DrumClosedText(plant.DrumClosedSensor) + "\",\n";
message += "\t\"DrumMotorStatus\": \"" + DrumMotorText(plant.DrumMotorStatus) + "\",\n";
message += "\t\"DrumMotor_V\": \"" + String(plant.DrumMotor_V) + "\",\n";
message += "\t\"DrumMotor_I\": \"" + String(plant.DrumMotor_I) + "\",\n";
snprintf(_upTime, sizeof(_upTime), "%02u:%02u", plant.DrumMotorOn_sec/60, plant.DrumMotorOn_sec%60);
message += "\t\"DrumMotorOn_sec\": \"" + String(_upTime) + "\",\n";
message += "\t\"WaterInTank\": \"" + String(plant.WaterInTank) + "\",\n";
message += "\t\"WaterMotor_V\": \"" + String(plant.WaterPumpStatus) + "\",\n";
message += "\t\"SoilMoisture_X\": \"" + String(plant.SoilMoisture_X) + "\",\n";
snprintf(_upTime, sizeof(_upTime), "%02u:%02u", plant.WaterCyclePeriod_sec/60, plant.WaterCyclePeriod_sec%60);
message += "\t\"WaterCyclePeriod_sec\": \"" + String(_upTime) + "\",\n";
snprintf(_upTime, sizeof(_upTime), "%02u:%02u", plant.WaterCycleOn_sec/60, plant.WaterCycleOn_sec%60);
message += "\t\"WaterCycleOn_sec\": \"" + String(_upTime) + "\",\n";
snprintf(_upTime, sizeof(_upTime), "%02u:%02u", plant.WaterCycle_sec/60, plant.WaterCycle_sec%60);
message += "\t\"waterCycle\": \"" + String(_upTime) + "\"\n";
//message += "\t\"toggle\": " + String(!digitalRead(SW_TOGGLE)) + ",\n";
//message += "\t\"reset\": " + String(!digitalRead(SW_RESET));
message += "}\n";
server.sendHeader("Access-Control-Allow-Origin", String("*"), true);
server.send(200, "application/json", message);
}
void simulation() {
if (++plant.SecondsToday >= (24*3600)) { // Seconds are advancing really fast for the simulation
plant.SecondsToday -= (24 * 3600);
}
static int lastMinutesToday = -1;
int hoursToday = plant.SecondsToday / 3600;
int minutesToday = plant.SecondsToday / 60;
// Simulate Ambient temp rising and falling
if (lastMinutesToday != minutesToday) {
lastMinutesToday = minutesToday;
Serial.printf("%02u:%02u, Ambient %3.1f\n", hoursToday, minutesToday % 60, plant.AmbientTemp_C);
if (hoursToday < 15) {
// warming part of the day
if (plant.AmbientTemp_C < 45.0) {
plant.AmbientTemp_C += (float)(rand() % 10)/1000.0;
}
} else {
// cooling part of the day
if (plant.AmbientTemp_C > 10.0) {
plant.AmbientTemp_C -= (float)(rand() % 10)/1000.0;
}
}
if (plant.SoilTemp_C > 40.0) {
plant.SoilHeater = false;
} else if (plant.SoilTemp_C < 20.0) {
plant.SoilHeater = true;
}
if (plant.SoilHeater) {
if (plant.SoilTemp_C < 50.0) {
plant.SoilTemp_C += (float)(rand() % 10)/1000.0;
}
} else {
if (plant.SoilTemp_C < plant.ChamberTemp_C) {
plant.SoilTemp_C += (float)(rand() % 10)/1000.0;
} else {
plant.SoilTemp_C -= (float)(rand() % 10)/1000.0;
}
}
if (plant.DrumMotorStatus == DrumCW) {
plant.DrumMotorOn_sec++;
} else if (plant.DrumMotorStatus == DrumCCW) {
plant.DrumMotorOn_sec++;
} else {
plant.DrumMotorOn_sec = 0;
}
if (plant.ChamberTemp_C > 50.0) {
if (!plant.DrumOpenSensor) {
plant.DrumMotorStatus = DrumCW;
if (plant.DrumMotorOn_sec > 50) {
plant.DrumOpenSensor = true;
plant.DrumClosedSensor = false;
}
}
} else if (plant.ChamberTemp_C < 40.0) {
if (!plant.DrumClosedSensor) {
plant.DrumMotorStatus = DrumCW;
if (plant.DrumMotorOn_sec > 50) {
plant.DrumOpenSensor = false;
plant.DrumClosedSensor = true;
}
}
}
if (plant.DrumDoorIcon == DrumIsOpen) {
if (plant.ChamberTemp_C > plant.AmbientTemp_C) {
plant.ChamberTemp_C -= (float)(rand() % 10)/1000.0;
} else if (plant.ChamberTemp_C < plant.AmbientTemp_C) {
plant.ChamberTemp_C += (float)(rand() % 10)/1000.0;
}
} else {
if (plant.ChamberTemp_C > plant.AmbientTemp_C) {
plant.ChamberTemp_C -= (float)(rand() % 5)/1000.0;
} else if (plant.ChamberTemp_C < plant.AmbientTemp_C) {
plant.ChamberTemp_C += (float)(rand() % 20)/1000.0;
}
}
plant.ChamberHumi = (float)(rand() % 73) + 25;
if (++plant.WaterCycle_sec >= plant.WaterCyclePeriod_sec) {
plant.WaterCycle_sec -= plant.WaterCyclePeriod_sec;
}
if (plant.WaterCycle_sec >= plant.WaterCycleOn_sec) {
plant.WaterPumpStatus = false;
}
if (plant.SoilMoisture_X < 60.0 && plant.WaterCycle_sec < plant.WaterCycleOn_sec) {
plant.WaterPumpStatus = true;
}
if (plant.WaterPumpStatus) {
plant.SoilMoisture_X += (float)(rand() % 40)/100.0;
} else {
plant.SoilMoisture_X -= (float)(rand() % 5)/100.0;
}
}
}

9
Firmware/PlantModel.h Normal file
View File

@@ -0,0 +1,9 @@
//
//
//
#include "ProjGlobals.h"
void HandleGetState();
void simulation();

View File

@@ -1,5 +1,5 @@
//
// Some settings
// Project Global Settings
//
#ifndef PROJGLOBALS_H
@@ -7,10 +7,60 @@
#include <ESP8266WebServer.h>
// Manage the WiFi Configuration, AP, SSID, etc.
#include "WiFiConfiguration.h"
extern bool isStationMode;
typedef enum {
DrumIsOpen,
DrumIsClosed,
DrumIsMoving,
DrumIsStuck,
} DrumState_E;
typedef enum {
DrumOff,
DrumCW,
DrumCCW
} DrumMotor_E;
typedef struct {
uint32_t SecondsToday; // elapsed seconds from midnight (for simulation)
bool Fahrenheit; // Show temp in Fahrenheit.
float ChamberTemp_C; // Chamber Temp
float ChamberHumi; // Chamber Humidity - if installed
bool SoilHeater;
float SoilTemp_C; // Soil Temp - if installed
float SoilMoisture_X; // Soil Moisture - unitless number, if installed
float AmbientTemp_C; //
DrumState_E DrumDoorIcon; // Current State: open/close/moving
bool DrumOpenSensor;
bool DrumClosedSensor;
DrumMotor_E DrumMotorStatus; // Off, CW, CCW
uint32_t DrumMotorOn_sec;
float DrumMotor_V;
float DrumMotor_I;
bool WaterInTank; // controls image of the WaterLevelIcon
bool WaterPumpStatus; // WaterLevelIcon pump on
float WaterMotor_V;
uint32_t WaterCyclePeriod_sec; // Total Time (allows WaterLevelIcon to soak in)
uint32_t WaterCycleOn_sec; // WaterLevelIcon Pump on time
uint32_t WaterCycle_sec; // Running timer
} PlantData_T;
extern void FactoryReset(bool forceRestart);
extern const String AP_NAME;
extern const String AP_VERS;
// extern bool isStationMode;
extern ESP8266WebServer server;
extern ConfigManager wifiConfig;
#endif // PROJGLOBALS_H
extern PlantData_T plant; /// This keeps track of everything relevant to this system
extern bool updateCheck;
#endif // PROJGLOBALS_H

View File

@@ -1,8 +1,12 @@
input.BigButton {
width: 150px;
padding: 20px;
height: 100px;
padding: 10px;
white-space: normal;
cursor: pointer;
text-align: center;
font-weight: bold;
font-size: 120%;
background: #3366cc;
@@ -19,3 +23,26 @@ input.BigButton:hover {
background: #000;
border: 5px solid #fff;
}
input.SmallButton {
width: 150px;
height: 50px;
padding: 10px;
white-space: normal;
cursor: pointer;
font-weight: bold;
font-size: 100%;
background: #3366cc;
color: #fff;
border: 5px solid #112233;
border-radius: 10px;
-moz-box-shadow: 6px 6px 5px #999;
-webkit-box-shadow: 6px 6px 5px #999;
box-shadow: 6px 6px 5px #999;
}
input.SmallButton:hover {
color: #ffff00;
background: #000;
border: 5px solid #fff;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -2,25 +2,25 @@
<html>
<head>
<title>Grow Controller</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, width=device-width">
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='about.js'></script>
<script type='text/javascript' src='nav.js'></script>
</head>
<body>
<body onload="NavInit();">
<h1>Grow Controller</h1>
by Smartware Computing
<blockquote>
<table cellpadding="5">
<tr valign="top">
<td><img src="/icon.jpg" /></td>
<td><img src="icon.png" title="logo" /></td>
<td>
This Grow Controller is an IOT device based on an ESP8266-12 module. The software for it has been
derived from various sources and where copyrights have been identified, they are listed below.<br />
<ul>
<li>The composite work is Copyright &copy; 2018-2021 by Smartware Computing, all rights reserved.</li>
<li>Library for WEMO emulation, Copyright &copy; 2016 by Xose Pérez</li>
<li>Libraries; Copyright &copy; 2001-2013 Free Software Foundation, Inc.</li>
<li>Libraries; Copyright (c) 1997 Silicon Graphics Computer Systems, Inc.</li>
</ul>
<dl>
<dt>Toggle Button</dt>
@@ -36,20 +36,6 @@
Uptime:
</blockquote>
<table>
<tr valign="top">
<td>NAV:</td>
<td>
<a href='/'>Home</a> |
<a href='/config'>Config</a> |
<a href='/scan'>Scan</a> |
<a href='/rssi'>RSSI</a> |
<a href='/curr'>Current</a> |
<a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> |
<a href='/about'>About</a>
</td>
</tr>
</table>
<span id='nav'></span>
</body>
</html>

View File

@@ -2,11 +2,13 @@
<html>
<head>
<title>Grow Controller</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, width=device-width">
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='curr.js'></script>
<script type='text/javascript' src='nav.js'></script>
</head>
<body onload='CurrInit();'>
<body onload='CurrInit(); NavInit();'>
<h1>Grow Controller - Current Sense</h1>
<blockquote>
<table width='800' border='0'>
@@ -25,19 +27,6 @@
</tr>
</table>
</blockquote>
<table>
<tr valign="top">
<td>NAV:</td>
<td>
<a href='/'>Home</a> |
<a href='/config'>Config</a> |
<a href='/scan'>Scan</a> |
<a href='/rssi'>RSSI</a> |
<a href='/curr'>Current</a> |
<a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> |
<a href='/about'>About</a>
</td>
</tr>
</table>
<span id='nav'></span>
</body>
</html>

View File

@@ -10,7 +10,7 @@ var axes = {};
var totalSamples = 400; // Width of the graph
for (var i = 0; i < totalSamples; i++)
rawData[i] = avgData[i] = 0;
setInterval(RefreshStatus, 250);
setInterval(RefreshStatus, 500);
// -------------------------

View File

@@ -2,133 +2,16 @@
<html lang="en">
<head>
<title>Grow Controller</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<style>
input.SmallButton {
width: 150px;
height: 50px;
padding: 10px;
white-space: normal;
cursor: pointer;
font-weight: bold;
font-size: 100%;
background: #3366cc;
color: #fff;
border: 5px solid #112233;
border-radius: 10px;
-moz-box-shadow: 6px 6px 5px #999;
-webkit-box-shadow: 6px 6px 5px #999;
box-shadow: 6px 6px 5px #999;
}
input.SmallButton:hover {
color: #ffff00;
background: #000;
border: 5px solid #fff;
}
input.BigButton {
width: 150px;
height: 100px;
padding: 10px;
white-space: normal;
cursor: pointer;
font-weight: bold;
font-size: 120%;
background: #3366cc;
color: #fff;
border: 5px solid #112233;
border-radius: 10px;
-moz-box-shadow: 6px 6px 5px #999;
-webkit-box-shadow: 6px 6px 5px #999;
box-shadow: 6px 6px 5px #999;
}
input.BigButton:hover {
color: #ffff00;
background: #000;
border: 5px solid #fff;
}
table {
width: 100%;
}
.box {
position: relative;
display: inline-block;
}
div.airtemp {
position: absolute;
left: 170px;
top: 50px;
//border: 2px solid lightgray;
}
div.soilmoisture {
position: absolute;
left: 140px;
top: 75px;
//border: 2px solid lightgray;
}
div.soiltemp {
position: absolute;
left: 150px;
top: 120px;
color: white;
//border: 2px solid lightgray;
}
div.heater {
position: absolute;
left: 105px;
top: 156px;
color: white;
//border: 2px solid lightgray;
}
div.ambtemp {
position: absolute;
left: 205px;
top: 205px;
//border: 2px solid lightgray;
}
div.drumMotorV {
position: absolute;
left: 190px;
top: 290px;
//border: 2px solid lightgray;
}
div.drumMotorI {
position: absolute;
left: 180px;
top: 320px;
//border: 2px solid lightgray;
}
div.waterMotorV {
position: absolute;
left: 210px;
top: 390px;
//border: 2px solid lightgray;
}
div.waterLevel {
position: absolute;
left: 85px;
top: 395px;
//border: 2px solid lightgray;
}
div.heaterCoil {
position: absolute;
left: 78px;
top: 177px;
}
div.water {
position: absolute;
left: 52px;
top: 420px;
z-index: -55;
}
</style>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, width=device-width">
<link rel="stylesheet" type="text/css" href="button.css" media="all" />
<link rel="stylesheet" type="text/css" href="plantmodel.css" media="all" />
<script type='text/javascript' src='nav.js'></script>
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='about.js'></script>
<script type='text/javascript' src='index.js'></script>
</head>
<body onload="RefreshStatus();">
<body onload="NavInit();">
<h1><span id='name'>Grow Controller</span></h1>
<form>
<table border='0'>
@@ -137,19 +20,44 @@
<div class="box">
<div style="position: relative; z-index: -10;">
<!-- 243 x 488 -->
<img src='PlantModel.png' />
<img src='plantmodel.png' title='Picture of the System' />
</div>
<div class="airtemp">92&deg;F</div>
<div class="soilmoisture">8%</div>
<div class="soiltemp">74&deg;F</div>
<div class="heater">ON</div>
<div class="ambtemp">65&deg;F</div>
<div class="drumMotorV">12.3V</div>
<div class="drumMotorI">1.2A</div>
<div class="waterMotorV">0.0V</div>
<div class="waterLevel">OK</div>
<div class="heaterCoil"><img src="Heater.png" /></div>
<div class="water"><img src="Water.png" /></div>
<div class='TimeOfDay' id='TimeOfDay'>--:--</div>
<div class='AmbientTemp_C' id='AmbientTemp_C' >--.- &deg;F</div>
<div class='AmbientTempIcon' id='AmbientTempIcon' ><img src='thermometer.png' title='Ambient Temperature' /></div>
<div class='ChamberTemp_C' id='ChamberTemp_C' >--.- &deg;F</div>
<div class='ChamberHumi' id='ChamberHumi' >-- %</div>
<div class='ChamberTempIcon' id='ChamberTempIcon' ><img src='thermometer.png' title='Chamber Temperature' /></div>
<div class='SoilHeater' id='SoilHeater' >Heat</div>
<div class='SoilHeaterOnIcon' id='SoilHeaterOnIcon' ><img src='soilheater_on.png' title='Soil Heater' /></div>
<div class='SoilHeaterOffIcon' id='SoilHeaterOffIcon' ><img src='soilheater_off.png' title='Soil Heater' /></div>
<div class='SoilThermoIcon' id='SoilThermoIcon' ><img src='thermometer.png' title='Soil Temperature' /></div>
<div class='SoilHumiIcon' id='SoilHumiIcon' ><img src='soilmoisture.png' title='Soil Temperature' /></div>
<div class='SoilTemp_C' id='SoilTemp_C' >--.- &deg;F</div>
<div class='SoilMoisture_X' id='SoilMoisture_X' >-- %</div>
<div class='DrumDoorIcon' id='DrumDoorIcon' ><img src='open.png' title='Open' /></div>
<div class='DrumOpenSensor' id='DrumOpenSensor' >O</div>
<div class='DrumClosedSensor' id='DrumClosedSensor' >C</div>
<div class='DrumMotorLabel' id='DrumMotorLabel' >Motor</div>
<div class='DrumMotorStatus' id='DrumMotorStatus' >Off</div>
<div class='DrumMotor_V' id='DrumMotor_V' >12.5 V</div>
<div class='DrumMotor_I' id='DrumMotor_I' >1.2 A</div>
<div class='DrumMotorOn_sec' id='DrumMotorOn_sec' >10 s</div>
<div class='WaterInTank' id='WaterInTank'>Low Water</div>
<div class='WaterPumpLabel' id='WaterPumpLabel' >Pump</div>
<div class='WaterPumpStatus' id='WaterPumpStatus' >On</div>
<div class='WaterLevelIcon' id='WaterLevelIcon'><img src='water.png' title='Water in Tank' /></div>
<div class='WaterMotor_V' id='WaterMotor_V' >10.7 V</div>
<div class='WaterCyclePeriod_sec' id='WaterCyclePeriod_sec' >90 s</div>
<div class='WaterCycleOn_sec' id='WaterCycleOn_sec' >8 s</div>
<div class='WaterCycle_sec' id='WaterCycle_sec' >10 s</div>
<div class='WaterCycleOnBox' id='WaterCycleOnBox' ></div>
<div class='WaterCycleStatusIcon' id='WaterCycleStatusIcon' ><img src='pointer.png' title='Pump On Time' /></div>
</div>
</td>
<td width='10%' align='center'>
@@ -174,33 +82,19 @@
</table>
</form>
<p></p>
<table border='0'>
<tr valign="top">
<td>NAV:</td>
<td>
<a href='/'>Home</a> |
<a href='/config'>Config</a> |
<a href='/scan'>Scan</a> |
<a href='/rssi'>RSSI</a> |
<a href='/curr'>Current</a> |
<a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> |
<a href='/about'>About</a>
</td>
</tr>
<!-- tr valign="top">
<td>M2M:</td>
<td>
<a href='/on'>http://<span class='ip'></span>/on</a> |
<a href='/off'>http://<span class='ip'></span>/off</a> |
<a href='/toggle'>http://<span class='ip'></span>/toggle</a> |
<a href='/state'>http://<span class='ip'></span>/state</a>
</td>
</tr!-->
</table>
<span id='state'></span>
<span id='raw'></span>
<span id='sense'></span>
<span id='toggle'></span>
<span id='reset'></span>
<span id='countdown'></span>
<span id='uptime'></span>
<span id='wifimode'></span>
<span id='nav'></span>
<hr>
<table border='0'>
<tr>
<td>GrowController by Origin Technologies</td>
<td>GrowController by Smartware Computing</td>
<td align="right"><a href="/swupdatecheck">Firmware</a>: <span id='version'>v0.00.00</span></td>
</tr>
</table>

View File

@@ -1,9 +1,9 @@
//
// rssi script
// Main Page period status update
//
// var mySite = 'http://192.168.1.23' from myip.js
// var mySite = 'http://192.168.1.23'; // from myip.js
var url = mySite + '/state';
setInterval(RefreshStatus, 250);
setInterval(RefreshStatus, 2500);
getIt = function (aUrl, callback) {
var xhr = new XMLHttpRequest();
@@ -17,6 +17,14 @@ getIt = function (aUrl, callback) {
xhr.send();
};
function ConvertTempToString(tempC, showF) {
var tempF = tempC * 9 / 5 + 32;
if (showF)
return tempF.toFixed(1) + "&deg;F";
else
return tempC.toFixed(1) + "&deg;C";
}
function RefreshStatus() {
getIt(url,
function(data) {
@@ -27,18 +35,72 @@ function RefreshStatus() {
state = "Off";
document.getElementById('version').innerHTML = obj.version;
document.getElementById('name').innerHTML = obj.name;
document.getElementById('state').innerHTML = 'Output: ' + state;
document.getElementById('raw').innerHTML = 'raw: ' + obj.raw;
document.getElementById('sense').innerHTML = 'Sense: ' + obj.sense;
document.getElementById('toggle').innerHTML = 'Toggle: ' + obj.toggle;
document.getElementById('reset').innerHTML = 'Reset: ' + obj.reset;
document.getElementById('countdown').innerHTML = obj.countdown;
//document.getElementById('state').innerHTML = 'Output: ' + state;
//document.getElementById('raw').innerHTML = 'raw: ' + obj.raw;
//document.getElementById('sense').innerHTML = 'Sense: ' + obj.sense;
//document.getElementById('toggle').innerHTML = 'Toggle: ' + obj.toggle;
//document.getElementById('reset').innerHTML = 'Reset: ' + obj.reset;
//document.getElementById('countdown').innerHTML = 'Countdown: ' + obj.countdown;
document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime;
document.getElementById('wifimode').innerHTML = 'Mode: ' + obj.wifimode;
document.getElementById('wifimode').innerHTML = 'WiFi Mode: ' + obj.wifimode;
// IP Address
var elms = document.querySelectorAll('.' + 'ip'), i;
for (i = 0; i < elms.length; ++i) {
elms[i].textContent = obj.ip;
}
// Plant status
document.getElementById('TimeOfDay').innerHTML = obj.TimeOfDay;
var userF = obj.Fahrenheit;
if (typeof obj.AmbientTemp_C != "undefined") {
document.getElementById('AmbientTemp_C').innerHTML = ConvertTempToString(obj.AmbientTemp_C, userF);
} else {
document.getElementById('AmbientTemp_C').innerHTML = "--";
}
if (typeof obj.ChamberTemp_C != "undefined") {
document.getElementById('ChamberTemp_C').innerHTML = ConvertTempToString(obj.ChamberTemp_C, userF);
document.getElementById('ChamberHumi').innerHTML = parseInt(obj.ChamberHumi) + "%";
} else {
document.getElementById('ChamberTemp_C').innerHTML = "--";
document.getElementById('ChamberHumi').innerHTML = "--"
}
if (typeof obj.SoilHeater != "undefined") {
document.getElementById('SoilHeater').innerHTML = obj.SoilHeater;
} else {
document.getElementById('SoilHeater').innerHTML = "--";
}
if (typeof obj.SoilTemp_C != "undefined") {
document.getElementById('SoilTemp_C').innerHTML = ConvertTempToString(obj.SoilTemp_C, userF);
} else {
document.getElementById('SoilTemp_C').innerHTML = "--";
}
if (typeof obj.SoilTemp_C != "undefined") {
document.getElementById('SoilMoisture_X').innerHTML = obj.SoilMoisture_X;
} else {
document.getElementById('SoilMoisture_X').innerHTML = "--";
}
if (obj.DrumDoorIcon == 1) {
document.getElementById('DrumDoorIcon').style.display === "none";
} else {
document.getElementById('DrumDoorIcon').style.display === "block";
}
document.getElementById('DrumOpenSensor').innerHTML = obj.DrumOpenSensor;
document.getElementById('DrumClosedSensor').innerHTML = obj.DrumClosedSensor;
document.getElementById('DrumMotorStatus').innerHTML = obj.DrumMotorStatus;
document.getElementById('DrumMotorOn_sec').innerHTML = obj.DrumMotorOn_sec + "s";
document.getElementById('DrumMotor_V').innerHTML = parseFloat(obj.DrumMotor_V).toFixed(1) + "V";
document.getElementById('DrumMotor_I').innerHTML = parseFloat(obj.DrumMotor_I).toFixed(1) + "A";
document.getElementById('WaterInTank').innerHTML = obj.WaterInTank;
document.getElementById('WaterPumpStatus').innerHTML = obj.WaterPumpStatus;
document.getElementById('WaterMotor_V').innerHTML = parseFloat(obj.WaterMotor_V).toFixed(1) + "V";
document.getElementById('WaterCyclePeriod_sec').innerHTML = obj.WaterCyclePeriod_sec + "s";
document.getElementById('WaterCycleOn_sec').innerHTML = obj.WaterCycleOn_sec + "s";
document.getElementById('WaterCycle_sec').innerHTML = obj.WaterCycle_sec + "s";
}
);
}

View File

@@ -0,0 +1,5 @@
// Normally provided by the application intercepting this request,
// but for debug it can be handy to fix it.
//
var mySite = 'http://192.168.1.10'; // from myip.js

20
Firmware/Resources/nav.js Normal file
View File

@@ -0,0 +1,20 @@
//
//
function NavInit() {
nav = document.getElementById('nav');
var txt = "<table>";
txt += "<tr valign='top'>";
txt += " <td>NAV:</td>";
txt += " <td>";
txt += " <a href='index.htm'>Home</a> | ";
txt += " <a href='config'>Config</a> | ";
txt += " <a href='scan'>Scan</a> | ";
txt += " <a href='rssi.htm'>RSSI</a> | ";
txt += " <a href='curr.htm'>Current</a> | ";
txt += " <a href='config?ssid=reset&pass=reset' onclick=\\\"return confirm('Are you sure?')\\\">Factory Reset</a> | ";
txt += " <a href='about.htm'>About</a>";
txt += " </td>";
txt += "</tr>";
txt += "</table>";
nav.innerHTML = txt;
}

View File

@@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<table>
<tr valign="top">
<td>NAV:</td>
<td>
<a href='/'>Home</a> |
<a href='config'>Config</a> |
<a href='scan'>Scan</a> |
<a href='rssi'>RSSI</a> |
<a href='curr'>Current</a> |
<a href='config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> |
<a href='about'>About</a>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,249 @@
<style>
table {
width: 100%;
}
.box {
position: relative;
display: inline-block;
border-color: black;
border-style: solid;
border-width: 1px;
}
div.TimeOfDay {
position: absolute;
right: 0px;
top: 2px;
width: 40px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.AmbientTemp_C {
position: absolute;
left: 15px;
top: 2px;
width: 40px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.AmbientTempIcon {
position: absolute;
left: 0px;
top: 0px;
}
div.ChamberTempIcon {
position: absolute;
left: 142px;
top: 40px;
}
div.ChamberTemp_C {
position: absolute;
left: 160px;
top: 40px;
}
div.ChamberHumi {
position: absolute;
left: 160px;
top: 55px;
}
div.SoilMoisture_X {
position: absolute;
left: 130px;
top: 135px;
color: white;
}
div.SoilHumiIcon {
position: absolute;
left: 140px;
top: 95px;
}
div.SoilThermoIcon {
position: absolute;
left: 170px;
top: 100px;
}
div.SoilTemp_C {
position: absolute;
left: 165px;
top: 135px;
color: white;
}
div.SoilHeater {
position: absolute;
left: 105px;
top: 156px;
color: white;
}
div.SoilHeaterOnIcon {
position: absolute;
left: 76px;
top: 167px;
}
div.SoilHeaterOffIcon {
position: absolute;
left: 89px;
top: 181px;
}
div.DrumDoorIcon {
position: absolute;
transform: rotate(180deg);
left: 14px;
top: 0px;
}
div.DrumOpenSensor {
position: absolute;
left: 20px;
top: 96px;
}
div.DrumClosedSensor {
position: absolute;
right: 10px;
top: 96px;
}
div.DrumMotorLabel {
position: absolute;
left: 95px;
top: 270px;
width: 50px;
text-align: center;
color: white;
}
div.DrumMotorStatus {
position: absolute;
left: 95px;
top: 290px;
width: 50px;
text-align: center;
color: white;
}
div.DrumMotor_V {
position: absolute;
left: 180px;
top: 225px;
width: 50px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
//color: Red;
}
div.DrumMotor_I {
position: absolute;
left: 180px;
top: 245px;
width: 50px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.DrumMotorOn_sec {
position: absolute;
left: 180px;
top: 315px;
width: 50px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.WaterInTank {
position: absolute;
left: 55px;
top: 365px;
width: 80px;
text-align: center;
}
div.WaterPumpLabel {
position: absolute;
left: 124px;
top: 390px;
width: 80px;
text-align: center;
color: white;
}
div.WaterPumpStatus {
position: absolute;
left: 124px;
top: 410px;
width: 80px;
text-align: center;
color: white;
}
div.WaterLevelIcon {
position: absolute;
left: 42px;
top: 393px;
z-index: -55;
}
div.WaterMotor_V {
position: absolute;
left: 180px;
top: 350px;
width: 50px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.WaterCyclePeriod_sec {
position: absolute;
left: 182px;
top: 467px;
width: 50px;
text-align: right;
border-color: green;
//border-style: solid;
border-width: 1px;
}
div.WaterCycleOn_sec {
position: absolute;
left: 23px;
top: 467px;
width: 50px;
text-align: right;
border-color: green;
//border-style: solid;
border-width: 1px;
}
div.WaterCycle_sec {
position: absolute;
left: 180px;
top: 440px;
width: 50px;
text-align: right;
border-color: green;
border-style: solid;
border-width: 1px;
}
div.WaterCycleOnBox {
position: absolute;
left: 22px;
top: 464px;
width: 50px;
height: 20px;
background-color: #BDD7EE;
z-index: -35;
//border-color: red;
border-style: solid;
border-width: 1px;
}
div.WaterCycleStatusIcon {
position: absolute;
left: 80px;
top: 469px;
width: 25px;
text-align: center;
z-index: -25;
border-color: green;
//border-style: solid;
border-width: 1px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -2,11 +2,13 @@
<html>
<head>
<title>Grow Controller RSSI</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, width=device-width">
<script type='text/javascript' src='nav.js'></script>
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='rssi.js'></script>
</head>
<body onload='RSSIInit();'>
<body onload='RSSIInit(); NavInit();'>
<h1>Grow Controller - RSSI</h1>
<blockquote>
<table width='800' border='0'>
@@ -25,19 +27,6 @@
</tr>
</table>
</blockquote>
<table>
<tr valign="top">
<td>NAV:</td>
<td>
<a href='/'>Home</a> |
<a href='/config'>Config</a> |
<a href='/scan'>Scan</a> |
<a href='/rssi'>RSSI</a> |
<a href='/curr'>Current</a> |
<a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> |
<a href='/about'>About</a>
</td>
</tr>
</table>
<span id='nav'></span>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

View File

@@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2020
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SmartSwitch", "SmartSwitch.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2245D551-F0E5-4A91-B978-826DFEF0D6BF}
EndGlobalSection
EndGlobal

File diff suppressed because one or more lines are too long

29
Firmware/Utility.cpp Normal file
View File

@@ -0,0 +1,29 @@
//
//
// Utility functions
//
//
#include <ESP8266WiFi.h>
#include "Utility.h"
static String macToStr(const uint8_t * mac);
String GetMacString() {
unsigned char mac[6];
WiFi.macAddress(mac);
String clientMac(macToStr(mac));
return clientMac;
}
String macToStr(const uint8_t * mac) {
String result;
for (int i = 0; i < 6; ++i) {
result += String(mac[i], 16);
if (i < 5)
result += ':';
}
return result;
}

11
Firmware/Utility.h Normal file
View File

@@ -0,0 +1,11 @@
//
//
// Utility.h
//
//
#include <ESP8266WiFi.h>
// Returns the MAC Address as a string object
String GetMacString();

View File

@@ -1,262 +0,0 @@
/*
FAUXMO ESP 2.4.0
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
const char UDP_TEMPLATE[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"CACHE-CONTROL: max-age=86400\r\n"
"DATE: Mon, 22 Jun 2015 17:24:01 GMT\r\n"
"EXT:\r\n"
"LOCATION: http://%s:%d/setup.xml\r\n"
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
"01-NLS: %s\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"ST: %s\r\n"
"USN: uuid:Socket-1_0-%s::"
"%s\r\n" // %s = urn:Belkin:device:** (Echo Dot 2Gen, Echo 1Gen) or %s = upnp:rootdevice (Echo 2Gen, Echo Plus)
"X-User-Agent: redsonic\r\n"
"\r\n";
const char SETUP_TEMPLATE[] PROGMEM =
"<?xml version=\"1.0\"?>\n"
"<root xmlns=\"urn:Belkin:device-1-0\">\n"
" <specVersion>\n"
" <major>1</major>\n"
" <minor>0</minor>\n"
" </specVersion>\n"
" <device>\n"
" <deviceType>urn:Belkin:device:controllee:1</deviceType>\n"
" <friendlyName>%s</friendlyName>\n"
" <manufacturer>Belkin International Inc.</manufacturer>\n" ///< must be "Belkin..." for Alexa discovery
" <manufacturerURL>http://www.smart-family.net</manufacturerURL>\n" ///<
" <modelDescription>Smartware Grow Controller</modelDescription>\n" ///<
" <modelName>Grow Controller</modelName>\n" ///<
" <modelNumber>1.0.0</modelNumber>\n"
#if 0
" <modelURL>http://www.smart-family.net</modelURL>\n" ///< This causes it to fail to send completely
#endif
" <UDN>uuid:Socket-1_0-%s</UDN>\n"
" <serialNumber>%s</serialNumber>\n"
" <iconList>\n"
" <mimetype>jpg</mimetype>\n"
" <width>100</width>\n"
" <height>100</height>\n"
" <depth>100</depth>\n"
" <url>icon.jpg</url>\n"
" </iconList>\n"
" <presentationURL>http://%s:%d</presentationURL>\n"
#if 0
" <serviceList>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:WiFiSetup:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:WiFiSetup1</serviceId>\n"
" <controlURL>/upnp/control/WiFiSetup1</controlURL>\n"
" <eventSubURL>/upnp/event/WiFiSetup1</eventSubURL>\n"
" <SCPDURL>/setupservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:timesync:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:timesync1</serviceId>\n"
" <controlURL>/upnp/control/timesync1</controlURL>\n"
" <eventSubURL>/upnp/event/timesync1</eventSubURL>\n"
" <SCPDURL>/timesyncservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:basicevent:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:basicevent1</serviceId>\n"
" <controlURL>/upnp/control/basicevent1</controlURL>\n"
" <eventSubURL>/upnp/event/basicevent1</eventSubURL>\n"
" <SCPDURL>/eventservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:firmwareupdate:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:firmwareupdate1</serviceId>\n"
" <controlURL>/upnp/control/firmwareupdate1</controlURL>\n"
" <eventSubURL>/upnp/event/firmwareupdate1</eventSubURL>\n"
" <SCPDURL>/firmwareupdate.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:rules:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:rules1</serviceId>\n"
" <controlURL>/upnp/control/rules1</controlURL>\n"
" <eventSubURL>/upnp/event/rules1</eventSubURL>\n"
" <SCPDURL>/rulesservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:metainfo:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:metainfo1</serviceId>\n"
" <controlURL>/upnp/control/metainfo1</controlURL>\n"
" <eventSubURL>/upnp/event/metainfo1</eventSubURL>\n"
" <SCPDURL>/metainfoservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:remoteaccess:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:remoteaccess1</serviceId>\n"
" <controlURL>/upnp/control/remoteaccess1</controlURL>\n"
" <eventSubURL>/upnp/event/remoteaccess1</eventSubURL>\n"
" <SCPDURL>/remoteaccess.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:deviceinfo:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:deviceinfo1</serviceId>\n"
" <controlURL>/upnp/control/deviceinfo1</controlURL>\n"
" <eventSubURL>/upnp/event/deviceinfo1</eventSubURL>\n"
" <SCPDURL>/deviceinfoservice.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:smartsetup:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:smartsetup1</serviceId>\n"
" <controlURL>/upnp/control/smartsetup1</controlURL>\n"
" <eventSubURL>/upnp/event/smartsetup1</eventSubURL>\n"
" <SCPDURL>/smartsetup.xml</SCPDURL>\n"
" </service>\n"
" <service>\n"
" <serviceType>urn:Belkin:service:manufacture:1</serviceType>\n"
" <serviceId>urn:Belkin:serviceId:manufacture1</serviceId>\n"
" <controlURL>/upnp/control/manufacture1</controlURL>\n"
" <eventSubURL>/upnp/event/manufacture1</eventSubURL>\n"
" <SCPDURL>/manufacture.xml</SCPDURL>\n"
" </service>\n"
" </serviceList>\n"
#endif
" </device>\n"
"</root>\n";
const char EVENTSERVICE_TEMPLATE[] PROGMEM =
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
"<actionList>"
"<action>"
"<name>SetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval />"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>in</direction>"
"</argument>"
"</argumentList>"
"</action>"
"<action>"
"<name>GetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>out</direction>"
"</argument>"
"</argumentList>"
"</action>"
"</actionList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>BinaryState</name>"
"<dataType>Boolean</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"<stateVariable sendEvents=\"yes\">"
"<name>level</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</scpd>\r\n"
"\r\n";
const char METAINFO_TEMPLATE[] PROGMEM =
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
"<specVersion><major>1</major><minor>0</minor></specVersion>"
"<actionList>"
"<action>"
"<name>GetMetaInfo</name>"
"<argumentList>"
"<retval/>"
"<name>GetMetaInfo</name>"
"<relatedStateVariable>MetaInfo</relatedStateVariable>"
"<direction>in</direction>"
"</argumentList>"
"</action>"
"</actionList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>MetaInfo</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</scpd>\r\n"
"\r\n";
const char SETSTATE_TEMPLATE[] PROGMEM =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>"
"<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">"
"<BinaryState>%d</BinaryState>"
"</u:SetBinaryState>"
"</s:Body>"
"</s:Envelope>\r\n";
const char GETSTATE_TEMPLATE[] PROGMEM =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>"
"<u:GetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">"
"<BinaryState>%d</BinaryState>"
"</u:GetBinaryStateResponse>"
"</s:Body>"
"</s:Envelope>\r\n";
const char HEADERS[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"CONTENT-LENGTH: %d\r\n"
"CONTENT-TYPE: text/xml\r\n"
"DATE: Sun, 01 Jan 2017 00:00:00 GMT\r\n"
"LAST-MODIFIED: Sat, 01 Jan 2017 00:00:00 GMT\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"X-USER-AGENT: redsonic\r\n"
"CONNECTION: close\r\n\r\n";
const char HEADERJPG[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"CONTENT-LENGTH: %d\r\n"
"CONTENT-TYPE: image/jpg\r\n"
"DATE: Sun, 01 Jan 2017 00:00:00 GMT\r\n"
"LAST-MODIFIED: Sat, 01 Jan 2017 00:00:00 GMT\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"X-USER-AGENT: redsonic\r\n"
"CONNECTION: close\r\n\r\n";
const char HEADERPNG[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"CONTENT-LENGTH: %d\r\n"
"CONTENT-TYPE: image/png\r\n"
"DATE: Sun, 01 Jan 2017 00:00:00 GMT\r\n"
"LAST-MODIFIED: Sat, 01 Jan 2017 00:00:00 GMT\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"X-USER-AGENT: redsonic\r\n"
"CONNECTION: close\r\n\r\n";

View File

@@ -1,253 +0,0 @@
#include <ESP8266WiFi.h>
#include "ProjGlobals.h"
#include "Web_APConfig.h"
static String EncType_str(uint8_t encType) {
switch (encType) {
case ENC_TYPE_NONE:
return "open";
break;
case ENC_TYPE_WEP:
return "wep";
break;
case ENC_TYPE_TKIP:
return "wpa psk";
break;
case ENC_TYPE_CCMP:
return "wpa2 psk";
break;
case ENC_TYPE_AUTO:
return "wpa wpa2 psk";
break;
default:
return "unknown";
break;
}
}
void HandleAPScan() {
// WiFi.scanNetworks will return the number of networks found
Serial.println("Scan ...");
int n = WiFi.scanNetworks(false, true); // blocking call and show hidden.
Serial.println("scan done.");
if (n == 0) {
Serial.println("no networks found");
} else {
Serial.print(n);
Serial.println(" networks found");
server.sendContent_P("HTTP/1.1 200 OK\r\n");
server.sendContent_P("Content-Type: text/html\r\n");
server.sendContent_P("\r\n");
server.sendContent_P(
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Grow Controller - Scan</title></head>\n"
"<body>\n"
"<h1>Grow Controller - Scan</h1>"
);
server.sendContent_P(
" <form action=\"/config\">\n"
" <blockquote>\n"
" <table width='800' border='0'>\n"
" <tr bgcolor='#E0E0E0'><td colspan='2'>Net</td><td>RSSI</td><td>SSID</td><td>BSSID</td><td>Encryption</td><td>Channel</td><td>Hidden</td></tr>\n"
);
for (int i = 0; i < n; ++i) {
// Select, Network #, RSSI, SSID, BSSID, Channel, Hidden
String bgcolor = (i & 1) ? "#E0E0E0" : "#FFFFFF";
String record =
" <tr bgcolor='" + bgcolor + "'><td><input type='radio' name='ssid' value='" + WiFi.SSID(i) + "'></td>\n"
+ " <td>" + (i + 1) + "</td>\n"
+ " <td>" + WiFi.RSSI(i) + "</td>\n"
+ " <td>" + WiFi.SSID(i) + "<br/>"
+ " <img src='/green1x1.png' width='" + String(5 * (100 + WiFi.RSSI(i))) + "' height='3'></td>\n"
+ " <td>" + WiFi.BSSIDstr(i) + "</td>\n";
record += " <td>" + EncType_str(WiFi.encryptionType(i)) + "</td>\n";
record += " <td>" + String(WiFi.channel(i));
record += "</td>\n";
record += WiFi.isHidden(i)
? " <td>Yes</td>\n"
: " <td>No</td>\n";
record += " </tr>\n";
server.sendContent(record);
//
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
}
server.sendContent_P(
" <tr><td>&nbsp;</td>\n"
" <td colspan='7'>\n"
" <input type=\"submit\" value=\"Select\" />\n"
" <input type=\"reset\"/>\n"
" </td>\n"
" </tr>\n"
" </table>\n"
" </blockquote>\n"
" </form>\n"
"NAV:&nbsp;&nbsp;"
" <a href='/'>Home</a> | "
" <a href='/config'>Config</a> | "
" <a href='/scan'>Scan</a> | "
" <a href='/rssi'>RSSI</a> | "
" <a href='/curr'>Current</a> | "
" <a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> | "
" <a href='/about'>About</a>\n"
" <br/>\n"
"</body>\n"
"</html>\n"
);
}
}
void FactoryReset(bool forceRestart) {
Serial.println("Factory Reset Configuration...\n");
wifiConfig.factoryReset();
wifiConfig.save();
if (forceRestart) {
Serial.println("Restart in 3 sec ...");
delay(3000);
ESP.restart();
}
}
void HandleAPConfigPage() {
String name = wifiConfig.getName();
String ssid = wifiConfig.getSSID();
String pass = wifiConfig.getPassword();
String url = wifiConfig.getURL();
//String ntp = wifiConfig.getNTPServerName();
char _onref[6], _offref[6], _autoOff[6];
snprintf(_onref, sizeof(_onref), "%d", wifiConfig.getOnRef());
snprintf(_offref, sizeof(_offref), "%d", wifiConfig.getOffRef());
snprintf(_autoOff, sizeof(_autoOff), "%d", wifiConfig.getAutoOff());
String onref = String(_onref);
String offref = String(_offref);
String autooff = String(_autoOff);
if (server.hasArg("ssid") && server.hasArg("pass")) {
String _ssid = server.hasArg("_ssid") ? server.arg("_ssid") : "";
String _pass = server.hasArg("_pass") ? server.arg("_pass") : "";
name = server.arg("name");
ssid = server.arg("ssid");
pass = server.arg("pass");
url = server.hasArg("url") ? server.arg("url") : "";
onref = server.hasArg("onref") ? server.arg("onref") : "";
offref = server.hasArg("offref") ? server.arg("offref") : "";
autooff = server.hasArg("autooff") ? server.arg("autooff") : "";
//ntp = server.hasArg("ntp") ? server.arg("ntp") : "";
if (ssid == "reset" && pass == "reset") {
FactoryReset(true);
}
wifiConfig.setName(name);
wifiConfig.setSSID(ssid);
wifiConfig.setPassword(pass);
wifiConfig.setURL(url);
wifiConfig.setOnRef(onref.toInt());
wifiConfig.setOffRef(offref.toInt());
wifiConfig.setAutoOff(autooff.toInt());
//wifiConfig.setNTPServerName(ntp);
wifiConfig.save();
Serial.println("Settings saved.");
if (ssid != _ssid || pass != _pass) {
// They changed stuff that requires a restart.
server.send(200, "text/html", "Configuration Saved! Restarting in 3 sec...");
Serial.printf("Config saved. Restarting in 3 sec . . .");
delay(3000);
ESP.restart();
return;
}
}
String modeText = (isStationMode) ? "Station mode<br/>" : "Access Point mode<br/>";
if (server.hasArg("ssid") && !server.hasArg("pass")) {
ssid = server.arg("ssid");
pass = "";
Serial.println("ssid: " + ssid + ", pass: " + pass);
}
if (server.hasArg("url"))
url = server.arg("url");
//if (server.hasArg("ntp"))
// ntp = server.arg("ntp");
server.send(200, "text/html",
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Grow Controller - Config</title></head>\n"
"<body>\n"
"<h1>Grow Controller - Configuration</h1>"
" <form action=\"\">\n"
" <blockquote>\n"
" <table border='0'>\n"
" <tr>\n"
" <td>Node Name</td>\n"
" <td><input type=\"text\" size=\"32\" name=\"name\" value=\"" + name + "\" /></td>\n"
" </tr>\n"
" <tr>\n"
" <td>SSID</td>\n"
" <td><input type=\"text\" size=\"32\" name=\"ssid\" value=\"" + ssid + "\" />&nbsp;**\n"
" <input type=\"hidden\" name=\"_ssid\" value=\"" + ssid + "\" /></td>\n"
" </tr>\n"
" <tr nowrap='nowrap'>\n"
" <td>Password</td>\n"
" <td nowrap='nowrap'><input type=\"password\" size=\"64\" name=\"pass\" value=\"" + pass + "\" />&nbsp;**\n"
" <input type=\"hidden\" name=\"_pass\" value=\"" + pass + "\" /></td>\n"
" </tr>\n"
" <tr>\n"
" <td>Update URL</td>\n"
" <td><input type=\"text\" size=\"64\" name=\"url\" value=\"" + url + "\" /></td>\n"
" </tr>\n"
// " <tr>\n"
// " <td>NTP Server</td>\n"
// " <td><input type=\"text\" size=\"64\" name=\"ntp\" value=\"" + ntp + "\" /></td>\n"
// " </tr>\n"
" <tr>\n"
" <td>Auto-Off</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"autooff\" value=\"" + autooff + "\" /> (time in seconds when non-zero)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>On Reference</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"onref\" value=\"" + onref + "\" /> (sense above this indicates power is on)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>Off Reference</td>\n"
" <td><input type=\"text\" size=\"10\" name=\"offref\" value=\"" + offref + "\" /> (sense below this indicates power is off)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;</td>\n"
" <td>\n"
" <input type=\"submit\" value=\"Save\" />\n"
" <input type=\"reset\"/> ** Changes to these will trigger a module restart.\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td colspan='2'><hr/></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;</td>\n"
" <td><a href='/swupdatecheck'>Check for SW update (module will restart if updated)</a></td/>\n"
" </tr>\n"
" </table>\n"
" </blockquote>\n"
" </form>\n"
"NAV:&nbsp;&nbsp;"
" <a href='/'>Home</a> | "
" <a href='/config'>Config</a> | "
" <a href='/scan'>Scan</a> | "
" <a href='/rssi'>RSSI</a> | "
" <a href='/curr'>Current</a> | "
" <a href='/config?ssid=reset&pass=reset' onclick=\"return confirm('Are you sure?')\">Factory Reset</a> | "
" <a href='/about'>About</a>\n"
" <br/>\n"
"</body>\n"
"</html>\n"
);
}

View File

@@ -1,12 +0,0 @@
#ifndef WEB_APCONFIG_H
#define WEB_APCONFIG_H
void FactoryReset(bool forceRestart = false);
void HandleAPScan();
void HandleAPConfigPage();
#endif // WEB_APCONFIG_H

View File

@@ -1,39 +0,0 @@
#include <ESP8266WiFi.h>
#include "ProjGlobals.h"
#include "Web_FavIcon.h"
#include "Web_Resources.h"
void HandleGreen1x1() {
Serial.printf("Green1x1.png\n");
server.send_P(200, "image/png", Green1x1_png, sizeof(Green1x1_png));
}
void HandleFavIcon() {
Serial.printf("favicon.ico\n");
server.send_P(200, "image/x-icon", favicon_ico, sizeof(favicon_ico));
}
void HandleIcon() {
Serial.printf("icon.png\n");
server.send_P(200, "image/png", icon_png, sizeof(icon_png));
}
void HandleOpenDoor() {
Serial.printf("Open.png\n");
server.send_P(200, "image/png", Open_png, sizeof(Open_png));
}
void HandlePlantModel() {
Serial.printf("PlantModel\n");
server.send_P(200, "image/png", PlantModel_png, sizeof(PlantModel_png));
}
void HandleHeater() {
Serial.printf("Heater\n");
server.send_P(200, "image/png", Heater_png, sizeof(Heater_png));
}
void HandleWater() {
Serial.printf("Water\n");
server.send_P(200, "image/png", Water_png, sizeof(Water_png));
}

View File

@@ -1,13 +0,0 @@
#ifndef WEB_FAVICON_H
#define WEB_FAVICON_H
void HandleGreen1x1();
void HandleFavIcon();
void HandlePlantModel();
void HandleHeater();
void HandleWater();
void HandleIcon();
void HandleOpenDoor();
#endif // WEB_FAVICON_H

View File

@@ -1,22 +0,0 @@
#include <ESP8266WiFi.h>
#include "ProjGlobals.h"
#include "Web_RSSIGraph.h"
#include "Web_Resources.h"
void HandleRSSIPage() {
server.send_P(200, "text/html", rssi_htm);
}
void HandleRSSI_JS() {
server.send_P(200, "text/html", rssi_js);
}
void HandleCurrPage() {
server.send_P(200, "text/html", curr_htm);
}
void HandleCurr_JS() {
server.send_P(200, "text/html", curr_js);
}

View File

@@ -1,10 +0,0 @@
#ifndef WEB_RSSIGRAPH_H
#define WEB_RSSIGRAPH_H
void HandleRSSIPage();
void HandleRSSI_JS();
void HandleCurrPage();
void HandleCurr_JS();
#endif // WEB_RSSIGRAPH_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
#include <ESP8266WiFi.h>
#include "ProjGlobals.h"
#include "Web_RootPage.h"
#include "GrowController.h"
#include "Web_Resources.h"
void HandleRootPage() {
if (server.hasArg("SW")) {
String newState = server.arg("SW");
if (newState == "1") {
SetCircuit(CMD_On);
Serial.printf("SW 1\n");
} else if (newState == "0") {
SetCircuit(CMD_Off);
Serial.printf("SW 0\n");
} else if (newState == "2") {
SetCircuit(CMD_Toggle);
Serial.printf("SW 2\n");
} else {
Serial.printf("SW %s ???\n", newState.c_str());
; // no action
}
}
String modeText = (isStationMode) ? "Station mode<br/>" : "Access Point mode<br/>";
String status = GetCircuitStatus() ? "On<br/>" : "Off<br/>";
String myIP = String(WiFi.localIP()[0]);
myIP += "." + String(WiFi.localIP()[1]);
myIP += "." + String(WiFi.localIP()[2]);
myIP += "." + String(WiFi.localIP()[3]);
server.send_P(200, "text/html", index_htm);
}

View File

@@ -1,7 +0,0 @@
#ifndef WEB_ROOTPAGE_H
#define WEB_ROOTPAGE_H
void HandleRootPage();
#endif // WEB_ROOTPAGE_H

View File

@@ -26,30 +26,29 @@
///
class ConfigManager {
public:
ConfigManager();
void load();
void save();
void factoryReset();
String getName();
void setName(String _name);
String getSSID();
void setSSID(String _ssid);
String getPassword();
void setPassword(String _password);
String getURL();
void setURL(String _url);
uint16_t getOnRef();
void setOnRef(uint16 onR);
uint16_t getOffRef();
void setOffRef(uint16 offR);
uint16_t getAutoOff();
void setAutoOff(uint16 autoOff);
#if 0
String getNTPServerName();
void setNTPServerName(String _name);
#endif
ConfigManager();
void load();
void save();
void factoryReset();
String getName();
void setName(String _name);
String getSSID();
void setSSID(String _ssid);
String getPassword();
void setPassword(String _password);
String getURL();
void setURL(String _url);
uint16_t getOnRef();
void setOnRef(uint16 onR);
uint16_t getOffRef();
void setOffRef(uint16 offR);
uint16_t getAutoOff();
void setAutoOff(uint16 autoOff);
#if 0
String getNTPServerName();
void setNTPServerName(String _name);
#endif
private:
};
#endif // !WIFICONFIGURATION_H

View File

@@ -0,0 +1,103 @@
//
//
//
#include "ProjGlobals.h"
#include "WiFiStateHandler.h"
WiFiConnectState wifiConState; ///< Track the current WiFi state
void WiFiStateSet(WiFiConnectState newState) {
wifiConState = newState;
}
WiFiConnectState WiFiStateGet() {
return wifiConState;
}
WiFiConnectState WiFiStateHandler() {
static unsigned long timeStateChange = 0;
// @todo static unsigned long timeLEDControl;
// @todo unsigned long timeLEDCycle;
String ssid = wifiConfig.getSSID();
String pass = wifiConfig.getPassword();
IPAddress myIP;
// @todo
#if 0
timeLEDCycle = millis() - timeLEDControl;
if (timeLEDCycle >= LED_PERIOD__MS) {
timeLEDControl = millis();
timeLEDCycle = 0;
}
#endif
switch (wifiConState) {
case WFC_ConnectToAP:
// @todo isStationMode = true;
WiFi.mode(WIFI_STA);
wifi_set_sleep_type(NONE_SLEEP_T); // rumor is that this fixes multicast receive
//ssid = wifiConfig.getSSID();
//pass = wifiConfig.getPassword();
WiFi.begin(ssid.c_str(), pass.c_str());
timeStateChange = millis();
// @todo timeLEDControl = timeStateChange;
wifiConState = WFC_JoiningAP;
break;
case WFC_JoiningAP:
// @todo if (timeLEDCycle <= LED_JOINING_MS) {
// @todo SetLED(LED_WIFI, 1);
// @todo } else {
// @todo SetLED(LED_WIFI, 0);
// @todo }
if (WiFi.status() == WL_CONNECTED) {
myIP = WiFi.localIP();
Serial.printf("IP Address: %s\n", myIP.toString().c_str());
timeStateChange = millis();
//StartWebServer();
wifiConState = WFC_JoinedAP;
} else if (millis() - timeStateChange > 30000) {
timeStateChange = millis();
wifiConState = WFC_CreateAP; // failed for 30 sec, now what. retry or CreateAP?
}
break;
case WFC_CreateAP:
// @todo if (timeLEDCycle <= LED_APMODE__MS) {
// @todo SetLED(LED_WIFI, 1);
// @todo } else {
// @todo SetLED(LED_WIFI, 0);
// @todo }
// @todo isStationMode = false;
Serial.printf("Starting Access Point %s\n", AP_NAME.c_str());
WiFi.softAP(AP_NAME.c_str());
myIP = WiFi.softAPIP();
Serial.printf("IP Address: %s\n", myIP.toString().c_str());
// @todo dnsServer.start(DNS_PORT, "*", apIP);
timeStateChange = millis();
//StartWebServer();
wifiConState = WFC_HostingAP;
break;
case WFC_JoinedAP:
// @todo if (timeLEDCycle <= LED_JOINED__MS) {
// @todo SetLED(LED_WIFI, 1);
// @todo } else {
// @todo SetLED(LED_WIFI, 0);
// @todo }
// @todo server.handleClient();
break;
case WFC_HostingAP:
// @todo if (timeLEDCycle <= LED_APMODE__MS) {
// @todo SetLED(LED_WIFI, 1);
// @todo } else {
// @todo SetLED(LED_WIFI, 0);
// @todo }
// @todo server.handleClient();
// @todo dnsServer.processNextRequest();
break;
default:
case WFC_Idle:
break;
}
return wifiConState;
}

View File

@@ -0,0 +1,58 @@
//
//
// WiFi Connection - handles the state - AP or Client
//
//
#ifndef WIFISTATEHANDLER_H
#define WIFISTATEHANDLER_H
#include <Arduino.h>
/// WiFi Connection States of connection
typedef enum {
WFC_ConnectToAP, ///< Connect to an Access Point
WFC_JoiningAP, ///< Joining the Access Point
WFC_JoinedAP, ///< Joined to the Access Point
WFC_CreateAP, ///< Create an Access Point
WFC_HostingAP, ///< Hosting an Access Point
WFC_Idle, ///< Idle take no action, or invalid
} WiFiConnectState;
/// Get the current state
///
/// @returns the current state
///
WiFiConnectState WiFiStateGet();
/// Called from the main loop to manage the WiFi connection state
///
/// @returns current connection state
///
/// @code
/// void loop() {
/// ...
/// WiFiStateHandler();
/// ...
/// }
/// @endcode
WiFiConnectState WiFiStateHandler(); ///< WiFi interface manager
/// Used to force a specific state
///
/// @param[in] newState is the state to set the WiFi connection to
///
/// @code
/// void setup() {
/// ...
/// if (ssid == "" || pass == "")
/// WiFiStateSet(WFC_CreateAP);
/// else
/// WiFiStateSet(WFC_ConnectToAP);
/// ...
/// }
/// @endcode
///
void WiFiStateSet(WiFiConnectState newState);
#endif // WIFISTATEHANDLER_H

View File

@@ -8,31 +8,31 @@
// Change this when it is required to force a reinitialization
//
#define EXPECTED_FMT 0x03
#define EXPECTED_FMT 0x03
typedef struct {
uint8_t format[2]; // Format of this data structure, and ~format
struct info {
char name[CFG_NAMESIZE]; // Name of this node
char ssid[CFG_SSIDSIZE]; // SSID of the saved network [max length defined by standard]
char pass[CFG_PASSSIZE]; // PASScode of the saved network
char updateURL[CFG_UURLSIZE]; // URL of the trusted server with code updates
uint16_t onRef; // A/D reference above which the output is considered 'On'
uint16_t offRef; // A/D reference below which the output is considered 'Off'
uint16_t autoOff; // When non-zero, this is the # minutes until auto-turn off
//char NTPIP[64]; // Name or IP of the NTP Server
char Padding[10];
} info;
uint8_t format[2]; // Format of this data structure, and ~format
struct info {
char name[CFG_NAMESIZE]; // Network Discoverable Name of this node
char ssid[CFG_SSIDSIZE]; // SSID of the saved network [max length defined by standard]
char pass[CFG_PASSSIZE]; // PASScode of the saved network
char updateURL[CFG_UURLSIZE]; // URL of the trusted server with code updates
uint16_t onRef; // A/D reference above which the output is considered 'On'
uint16_t offRef; // A/D reference below which the output is considered 'Off'
uint16_t autoOff; // When non-zero, this is the # minutes until auto-turn off
//char NTPIP[64]; // Name or IP of the NTP Server
char Padding[10];
} info;
} Config_t;
#define OFS_FORMAT (0)
#define OFS_NAME (OFS_FORMAT + 2)
#define OFS_SSID (OFS_NAME + CFG_NAMESIZE)
#define OFS_PASS (OFS_SSID + CFG_SSIDSIZE)
#define OFS_URL (OFS_PASS + CFG_PASSSIZE)
#define OFS_ONREF (OFS_URL + CFG_UURLSIZE)
#define OFS_OFFREF (OFS_ONREF + sizeof(uint16_t))
#define OFS_FORMAT (0)
#define OFS_NAME (OFS_FORMAT + 2)
#define OFS_SSID (OFS_NAME + CFG_NAMESIZE)
#define OFS_PASS (OFS_SSID + CFG_SSIDSIZE)
#define OFS_URL (OFS_PASS + CFG_PASSSIZE)
#define OFS_ONREF (OFS_URL + CFG_UURLSIZE)
#define OFS_OFFREF (OFS_ONREF + sizeof(uint16_t))
#define OFS_AUTOOFF (OFS_OFFREF + sizeof(uint16_t))
//#define OFS_NTPIP (OFS_OFFREF + 2)
//#define OFS_NTPIP (OFS_OFFREF + 2)
Config_t Configuration;
@@ -41,117 +41,116 @@ ConfigManager::ConfigManager() {
}
void ConfigManager::factoryReset() {
Configuration.format[0] = EXPECTED_FMT;
Configuration.format[1] = ~EXPECTED_FMT;
strcpy(Configuration.info.name, "3-Way Switch");
Configuration.info.ssid[0] = '\0';
Configuration.info.pass[0] = '\0';
//Configuration.info.updateURL[0] = '\0';
strcpy(Configuration.info.updateURL, "https://192.168.1.201/mbed/ESP.php");
Configuration.info.onRef = 150;
Configuration.info.offRef = 100;
Configuration.info.autoOff = 0;
//Configuration.info.NTPIP[0] = '\0';
Configuration.format[0] = EXPECTED_FMT;
Configuration.format[1] = ~EXPECTED_FMT;
strcpy(Configuration.info.name, "3-Way Switch");
Configuration.info.ssid[0] = '\0';
Configuration.info.pass[0] = '\0';
//Configuration.info.updateURL[0] = '\0';
strcpy(Configuration.info.updateURL, "https://192.168.1.201/mbed/ESP.php");
Configuration.info.onRef = 150;
Configuration.info.offRef = 100;
Configuration.info.autoOff = 0;
//Configuration.info.NTPIP[0] = '\0';
}
void ConfigManager::load() {
EEPROM.begin(512);
uint8_t * ptr = (uint8_t *) &Configuration;
uint8_t i = 0;
do {
*ptr++ = EEPROM.read(OFS_FORMAT + i++);
} while (i < sizeof(Configuration));
if ((Configuration.format[0] & 0xFF) != (~Configuration.format[1] & 0xFF)) {
// Factory default
Serial.printf("Bad Configuration - reset to factory defaults\n");
factoryReset();
return;
}
Serial.printf("Loading from EEROM\n");
Serial.printf("Node Name: %s\n", Configuration.info.name);
Serial.printf("SSID: %s\n", Configuration.info.ssid);
Serial.printf("PASS: %s\n", Configuration.info.pass);
Serial.printf("URL : %s\n", Configuration.info.updateURL);
Serial.printf("On Ref: %d\n", Configuration.info.onRef);
Serial.printf("Off Ref: %d\n", Configuration.info.offRef);
Serial.printf("Auto Off: %d\n", Configuration.info.autoOff);
//Serial.printf("NTP Svr: %s\n", Configuration.info.NTPIP);
delay(500);
EEPROM.begin(512);
uint8_t * ptr = (uint8_t *) &Configuration;
uint8_t i = 0;
do {
*ptr++ = EEPROM.read(OFS_FORMAT + i++);
} while (i < sizeof(Configuration));
if ((Configuration.format[0] & 0xFF) != (~Configuration.format[1] & 0xFF)) {
// Factory default
Serial.printf("Bad Configuration - reset to factory defaults\n");
factoryReset();
return;
}
Serial.printf("Loading from EEROM\n");
Serial.printf("Node Name: %s\n", Configuration.info.name);
Serial.printf("SSID: %s\n", Configuration.info.ssid);
Serial.printf("PASS: %s\n", Configuration.info.pass);
Serial.printf("URL : %s\n", Configuration.info.updateURL);
Serial.printf("On Ref: %d\n", Configuration.info.onRef);
Serial.printf("Off Ref: %d\n", Configuration.info.offRef);
Serial.printf("Auto Off: %d\n", Configuration.info.autoOff);
//Serial.printf("NTP Svr: %s\n", Configuration.info.NTPIP);
delay(500);
}
void ConfigManager::save(void) {
EEPROM.begin(512);
uint8_t * ptr = (uint8_t *) &Configuration;
uint8_t i = 0;
do {
EEPROM.write(OFS_FORMAT + i++, *ptr++);
} while (i < sizeof(Configuration));
EEPROM.commit();
delay(500);
EEPROM.begin(512);
uint8_t * ptr = (uint8_t *) &Configuration;
uint8_t i = 0;
do {
EEPROM.write(OFS_FORMAT + i++, *ptr++);
} while (i < sizeof(Configuration));
EEPROM.commit();
delay(500);
}
#if 0
String ConfigManager::getNTPServerName() {
return String(Configuration.info.NTPIP);
return String(Configuration.info.NTPIP);
}
void ConfigManager::setNTPServerName(String _name) {
strncpy(Configuration.info.NTPIP, _name.c_str(), sizeof(Configuration.info.NTPIP));
Serial.printf("setNTPServerName(%s)\n", Configuration.info.NTPIP);
strncpy(Configuration.info.NTPIP, _name.c_str(), sizeof(Configuration.info.NTPIP));
Serial.printf("setNTPServerName(%s)\n", Configuration.info.NTPIP);
}
#endif
String ConfigManager::getName() {
return String(Configuration.info.name);
return String(Configuration.info.name);
}
void ConfigManager::setName(String _name) {
strncpy(Configuration.info.name, _name.c_str(), sizeof(Configuration.info.name));
Serial.printf("setName(%s)\n", Configuration.info.name);
strncpy(Configuration.info.name, _name.c_str(), sizeof(Configuration.info.name));
Serial.printf("setName(%s)\n", Configuration.info.name);
}
String ConfigManager::getSSID() {
return String(Configuration.info.ssid);
return String(Configuration.info.ssid);
}
void ConfigManager::setSSID(String _ssid) {
strncpy(Configuration.info.ssid, _ssid.c_str(), sizeof(Configuration.info.ssid));
Serial.printf("setSSID(%s)\n", Configuration.info.ssid);
strncpy(Configuration.info.ssid, _ssid.c_str(), sizeof(Configuration.info.ssid));
Serial.printf("setSSID(%s)\n", Configuration.info.ssid);
}
String ConfigManager::getPassword() {
return String(Configuration.info.pass);
return String(Configuration.info.pass);
}
void ConfigManager::setPassword(String _password) {
strncpy(Configuration.info.pass, _password.c_str(), sizeof(Configuration.info.pass));
Serial.printf("setPass(%s)\n", Configuration.info.pass);
strncpy(Configuration.info.pass, _password.c_str(), sizeof(Configuration.info.pass));
Serial.printf("setPass(%s)\n", Configuration.info.pass);
}
String ConfigManager::getURL() {
return String(Configuration.info.updateURL);
return String(Configuration.info.updateURL);
}
void ConfigManager::setURL(String _url) {
strncpy(Configuration.info.updateURL, _url.c_str(), sizeof(Configuration.info.updateURL));
Serial.printf("setURL (%s)\n", Configuration.info.updateURL);
strncpy(Configuration.info.updateURL, _url.c_str(), sizeof(Configuration.info.updateURL));
Serial.printf("setURL (%s)\n", Configuration.info.updateURL);
}
uint16_t ConfigManager::getOnRef() {
return Configuration.info.onRef;
return Configuration.info.onRef;
}
void ConfigManager::setOnRef(uint16 onR) {
Configuration.info.onRef = onR;
Configuration.info.onRef = onR;
}
uint16_t ConfigManager::getOffRef() {
return Configuration.info.offRef;
return Configuration.info.offRef;
}
void ConfigManager::setOffRef(uint16 offR) {
Configuration.info.offRef = offR;
Configuration.info.offRef = offR;
}
uint16_t ConfigManager::getAutoOff() {
return Configuration.info.autoOff;
return Configuration.info.autoOff;
}
void ConfigManager::setAutoOff(uint16 autoOff) {
Configuration.info.autoOff = autoOff;
Configuration.info.autoOff = autoOff;
}

View File

@@ -1,484 +0,0 @@
/*
FAUXMO ESP 2.4.2
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <Arduino.h>
#if defined(ESP32)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#error Platform not supported
#endif
#include "fauxmoESP.h"
#include "Web_Resources.h"
// -----------------------------------------------------------------------------
// UDP
// -----------------------------------------------------------------------------
void fauxmoESP::_sendUDPResponse(unsigned int device_id) {
fauxmoesp_device_t device = _devices[device_id];
DEBUG_MSG_FAUXMO("[FAUXMO] UDP response for device #%d (%s)\n", _current, device.name);
char buffer[16];
IPAddress ip = WiFi.localIP();
snprintf_P(buffer, sizeof(buffer), PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
char response[strlen(UDP_TEMPLATE) + 128];
snprintf_P(response, sizeof(response), UDP_TEMPLATE,
buffer,
_base_port + _current,
device.uuid,
_udpPattern == 1 ? UDP_DEVICE_PATTERN_1 : _udpPattern == 2 ? UDP_DEVICE_PATTERN_2 : _udpPattern == 3 ? UDP_DEVICE_PATTERN_3 : _udpPattern == 4 ? UDP_DEVICE_PATTERN_4 : _udpPattern == 5 ? UDP_DEVICE_PATTERN_5 : UDP_ROOT_DEVICE,
device.uuid,
_udpPattern == 1 ? UDP_DEVICE_PATTERN_1 : _udpPattern == 2 ? UDP_ROOT_DEVICE : _udpPattern == 3 ? UDP_ROOT_DEVICE : _udpPattern == 4 ? UDP_ROOT_DEVICE : _udpPattern == 5 ? UDP_ROOT_DEVICE : UDP_ROOT_DEVICE
);
_udp.beginPacket(_remoteIP, _remotePort);
#if defined(ESP32)
_udp.printf(response);
#else
_udp.write(response);
#endif
_udp.endPacket();
DEBUG_MSG_FAUXMO("[FAUXMO] response:\n%s\n", response);
}
void fauxmoESP::_nextUDPResponse() {
while (_roundsLeft) {
if (_devices[_current].hit == false) break;
if (++_current == _devices.size()) {
--_roundsLeft;
_current = 0;
}
}
if (_roundsLeft > 0) {
_sendUDPResponse(_current);
if (++_current == _devices.size()) {
--_roundsLeft;
_current = 0;
}
}
}
void fauxmoESP::_onUDPData(IPAddress remoteIP, unsigned int remotePort, void *data, size_t len) {
if (!_enabled) {
DEBUG_MSG_FAUXMO("onUDPData\n");
return;
}
char * p = (char *) data;
p[len] = 0;
if (strstr(p, UDP_SEARCH_PATTERN) == (char *) data) {
Serial.printf("onUDP: %s\n", p);
_udpPattern = 0;
if (strstr(p, UDP_DEVICE_PATTERN_1) != NULL) _udpPattern = 1;
if (strstr(p, UDP_DEVICE_PATTERN_2) != NULL) _udpPattern = 2;
if (strstr(p, UDP_DEVICE_PATTERN_3) != NULL) _udpPattern = 3;
if (strstr(p, UDP_DEVICE_PATTERN_4) != NULL) _udpPattern = 4; // ssdp:all
if (strstr(p, UDP_DEVICE_PATTERN_5) != NULL) _udpPattern = 5; // ssdpsearch:all
if (strstr(p, UDP_ROOT_DEVICE) != NULL) _udpPattern = 6; // upnp:rootdevice
if (_udpPattern) {
#ifdef DEBUG_FAUXMO
char buffer[16];
snprintf_P(buffer, sizeof(buffer), PSTR("%d.%d.%d.%d"), remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3]);
DEBUG_MSG_FAUXMO("[FAUXMO] Search request from %s\n", buffer);
#endif
// Set hits to false
for (unsigned int i = 0; i < _devices.size(); i++) {
_devices[i].hit = false;
}
// Send responses
_remoteIP = remoteIP;
_remotePort = remotePort;
_current = random(0, _devices.size());
_roundsLeft = UDP_RESPONSES_TRIES;
}
}
}
// -----------------------------------------------------------------------------
// TCP
// -----------------------------------------------------------------------------
void fauxmoESP::_handleSetup(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
(void) data;
(void) len;
DEBUG_MSG_FAUXMO("[FAUXMO] Device #%d /setup.xml\n", device_id);
_devices[device_id].hit = true;
fauxmoesp_device_t device = _devices[device_id];
IPAddress myIP;
myIP = WiFi.localIP();
char response[80 + strlen_P(SETUP_TEMPLATE) + strlen(device.name) + strlen(device.uuid) + strlen(device.serial) + 25];
snprintf_P(response, sizeof(response), SETUP_TEMPLATE,
device.name, device.uuid, device.serial,
myIP.toString().c_str(), 80);
char headers[strlen_P(HEADERS) + 10];
snprintf_P(headers, sizeof(headers), HEADERS, strlen(response));
client->write(headers, strlen(headers));
client->write(response, strlen(response));
DEBUG_MSG_FAUXMO("[FAUXMO] end /setup.xml: %d, %d\n", strlen(headers), strlen(response));
}
void fauxmoESP::_handleEventService(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
(void) device_id;
(void) data;
(void) len;
DEBUG_MSG_FAUXMO("[FAUXMO] Device #%d /eventservice.xml\n", device_id);
char response[strlen_P(EVENTSERVICE_TEMPLATE)];
snprintf_P(response, sizeof(response), EVENTSERVICE_TEMPLATE);
char headers[strlen_P(HEADERS) + 10];
snprintf_P(headers, sizeof(headers), HEADERS, strlen(response));
client->write(headers, strlen(headers));
client->write(response, strlen(response));
DEBUG_MSG_FAUXMO("[FAUXMO] end /eventservice.xml\n");
}
void fauxmoESP::_handleMetaInfoService(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
(void) device_id;
(void) data;
(void) len;
DEBUG_MSG_FAUXMO("[FAUXMO] Device #%d /metainfoservice.xml\n", device_id);
char response[strlen_P(METAINFO_TEMPLATE)];
snprintf_P(response, sizeof(response), METAINFO_TEMPLATE);
char headers[strlen_P(HEADERS) + 10];
snprintf_P(headers, sizeof(headers), HEADERS, strlen(response));
client->write(headers, strlen(headers));
client->write(response, strlen(response));
DEBUG_MSG_FAUXMO("[FAUXMO] end /metainfoservice.xml\n");
}
void fauxmoESP::_handleControl(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
DEBUG_MSG_FAUXMO("[FAUXMO] Device #%d /upnp/control/basicevent1\n", device_id);
char content[len+1];
memcpy(content, data, len);
fauxmoesp_device_t device = _devices[device_id];
// The default template is the one for GetBinaryState queries
const char * response_template = GETSTATE_TEMPLATE;
if (strstr(content, "SetBinaryState") != NULL) {
if (strstr(content, "<BinaryState>0</BinaryState>") != NULL) {
if (_setCallback) _setCallback(device_id, device.name, false);
}
if (strstr(content, "<BinaryState>1</BinaryState>") != NULL) {
if (_setCallback) _setCallback(device_id, device.name, true);
}
// Use a specific response template for SetBinaryState action
response_template = SETSTATE_TEMPLATE;
}
// Update current state
if (_getCallback) device.state = _getCallback(device_id, device.name);
// Send response
char response[strlen_P(response_template) + 10];
snprintf_P(response, sizeof(response), response_template, device.state ? 1 : 0);
char headers[strlen_P(HEADERS) + 10];
snprintf_P(headers, sizeof(headers), HEADERS, strlen(response));
client->write(headers, strlen(headers));
client->write(response, strlen(response));
DEBUG_MSG_FAUXMO("[FAUXMO] end /upnp/control/basicevent1\n");
}
void fauxmoESP::_handleIcon(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
(void) device_id;
(void) data;
(void) len;
DEBUG_MSG_FAUXMO("[FAUXMO] Icon /icon.png\n");
char *buf = (char *) malloc(strlen(HEADERJPG) + 5);
if (buf) {
snprintf_P(buf, strlen_P(HEADERJPG) + 5, HEADERJPG, sizeof(icon_png));
client->write(buf, strlen(buf));
client->write(icon_png, sizeof(icon_png));
DEBUG_MSG_FAUXMO("[FAUXMO] sent icon.png [%d bytes]\n", sizeof(icon_png));
delay(100);
free(buf);
}
DEBUG_MSG_FAUXMO("[FAUXMO] end /icon.png\n");
}
void fauxmoESP::_onTCPData(AsyncClient *client, unsigned int device_id, void *data, size_t len) {
if (!_enabled) {
DEBUG_MSG_FAUXMO("onTCPData ! _enabled\n");
return;
}
#ifdef DEBUG_FAUXMO
char * p = (char *) data;
p[len] = 0;
Serial.printf("[FAUXMO] onTCPData(dev:%d,\n%s\n", device_id, p);
#endif
{
char match[] = {"GET /setup.xml HTTP/1.1"};
if (memcmp(data, match, strlen(match)-1) == 0) {
_handleSetup(client, device_id, data, len);
return;
}
}
{
char match[] = {"GET /eventservice.xml HTTP/1.1"};
if (memcmp(data, match, strlen(match)-1) == 0) {
_handleEventService(client, device_id, data, len);
return;
}
}
{
char match[] = {"GET /metainfoservice.xml HTTP/1.1"};
if (memcmp(data, match, strlen(match)-1) == 0) {
_handleMetaInfoService(client, device_id, data, len);
return;
}
}
{
char match[] = {"POST /upnp/control/basicevent1 HTTP/1.1"};
if (memcmp(data, match, strlen(match)-1) == 0) {
_handleControl(client, device_id, data, len);
return;
}
}
{
char match[] = {"GET /icon.png HTTP/1.1"};
if (memcmp(data, match, strlen(match)-1) == 0) {
_handleIcon(client, device_id, data, len);
return;
}
}
}
void fauxmoESP::_onTCPClient(AsyncClient *client, unsigned int device_id) {
for (unsigned char i = 0; i < TCP_MAX_CLIENTS; i++) {
if (!_tcpClients[i] || !_tcpClients[i]->connected()) {
_tcpClients[i] = client;
client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) {
(void) s;
(void) c;
(void) len;
(void) time;
}, 0);
client->onData([this, i, device_id](void *s, AsyncClient *c, void *data, size_t len) {
(void) s;
_onTCPData(c, device_id, data, len);
}, 0);
client->onDisconnect([this, i](void *s, AsyncClient *c) {
(void) s;
_tcpClients[i]->free();
_tcpClients[i] = NULL;
delete c;
DEBUG_MSG_FAUXMO("[FAUXMO] Client #%d disconnected\n", i);
}, 0);
client->onError([i](void *s, AsyncClient *c, int8_t error) {
(void) s;
(void) c;
(void) error;
DEBUG_MSG_FAUXMO("[FAUXMO] Error %s (%d) on client #%d\n", c->errorToString(error), error, i);
}, 0);
client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) {
(void) s;
(void) time;
DEBUG_MSG_FAUXMO("[FAUXMO] Timeout on client #%d at %i\n", i, time);
c->close();
}, 0);
DEBUG_MSG_FAUXMO("[FAUXMO] Client #%d connected\n", i);
return;
}
}
DEBUG_MSG_FAUXMO("[FAUXMO] Rejecting - Too many connections\n");
client->onDisconnect([](void *s, AsyncClient *c) {
(void) s;
c->free();
delete c;
});
client->close(true);
}
// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------
unsigned char fauxmoESP::addDevice(const char * device_name) {
fauxmoesp_device_t new_device;
unsigned int device_id = _devices.size();
// Copy name
new_device.name = strdup(device_name);
// Chip ID
#if defined(ESP32)
unsigned long chip_id = (uint32_t) ESP.getEfuseMac();
#else
unsigned long chip_id = ESP.getChipId();
#endif
// Create UUID
char uuid[15];
snprintf_P(uuid, sizeof(uuid), PSTR("444776%06X%02X\0"), chip_id, device_id); // "DEV" + CHIPID + DEV_ID
new_device.uuid = strdup(uuid);
// Create Serialnumber
char serial[15];
sprintf(serial, "221793K0%06X", (uint32)chip_id); // "221703K0" + CHIPID
new_device.serial = strdup(serial);
// TCP Server
new_device.server = new AsyncServer(_base_port + device_id);
new_device.server->onClient([this, device_id](void *s, AsyncClient* c) {
(void) s;
_onTCPClient(c, device_id);
}, 0);
new_device.server->begin();
// Attach
_devices.push_back(new_device);
DEBUG_MSG_FAUXMO("[FAUXMO] Device '%s' added as #%d\n", device_name, device_id);
return device_id;
}
bool fauxmoESP::renameDevice(unsigned char id, const char * device_name) {
if (id <= _devices.size()) {
free(_devices[id].name);
_devices[id].name = strdup(device_name);
DEBUG_MSG_FAUXMO("[FAUXMO] Device #%d renamed to '%s'\n", id, device_name);
return true;
}
return false;
}
char * fauxmoESP::getDeviceName(unsigned char id, char * device_name, size_t len) {
if (id <= _devices.size()) {
strncpy(device_name, _devices[id].name, len);
}
return device_name;
}
void fauxmoESP::setState(unsigned char id, bool state) {
if (id <= _devices.size()) {
_devices[id].state = state;
}
}
void fauxmoESP::handle() {
int len = _udp.parsePacket();
if (len > 0) {
DEBUG_MSG_FAUXMO("[FAUXMO] handle %d bytes\n", len);
IPAddress remoteIP = _udp.remoteIP();
unsigned int remotePort = _udp.remotePort();
uint8_t data[len];
_udp.read(data, len);
_onUDPData(remoteIP, remotePort, data, len);
}
if (_roundsLeft > 0) {
if (millis() - _lastTick > UDP_RESPONSES_INTERVAL) {
_lastTick = millis();
_nextUDPResponse();
}
}
}
void fauxmoESP::enable(bool enable) {
DEBUG_MSG_FAUXMO("[FAUXMO] %s\n", enable ? "Enabled" : "Disabled");
_enabled = enable;
#ifdef ESP32
_udp.beginMulticast(UDP_MULTICAST_IP, UDP_MULTICAST_PORT);
#endif
}
fauxmoESP::fauxmoESP(unsigned int port) {
_base_port = port;
#ifdef ESP8266
// Start UDP server on STA connection
_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) {
(void) ipInfo;
_udp.beginMulticast(WiFi.localIP(), UDP_MULTICAST_IP, UDP_MULTICAST_PORT);
DEBUG_MSG_FAUXMO("[FAUXMO] UDP server started\n");
});
#endif
}

View File

@@ -1,135 +0,0 @@
/*
FAUXMO ESP 2.4.2
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#define DEFAULT_TCP_BASE_PORT 49152
#define UDP_MULTICAST_IP IPAddress(239,255,255,250)
#define UDP_MULTICAST_PORT 1900
#define TCP_MAX_CLIENTS 10
#define UDP_SEARCH_PATTERN "M-SEARCH"
#define UDP_DEVICE_PATTERN_1 "urn:Belkin:device:**"
#define UDP_DEVICE_PATTERN_2 "urn:Belkin:device:controllee:1"
#define UDP_DEVICE_PATTERN_3 "urn:Belkin:service:basicevent:1"
#define UDP_DEVICE_PATTERN_4 "ssdp:all"
#define UDP_DEVICE_PATTERN_5 "ssdpsearch:all"
#define UDP_ROOT_DEVICE "upnp:rootdevice"
#define UDP_RESPONSES_INTERVAL 250
#define UDP_RESPONSES_TRIES 5
// Here, or in the compiler configuration
#define DEBUG_FAUXMO Serial
#ifdef DEBUG_FAUXMO
#define DEBUG_MSG_FAUXMO(...) DEBUG_FAUXMO.printf( __VA_ARGS__ )
#else
#define DEBUG_MSG_FAUXMO(...)
#endif
#include <Arduino.h>
#if defined(ESP32)
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#else
#error Platform not supported
#endif
#include <WiFiUdp.h>
#include <functional>
#include <vector>
#include "WeMo.h"
typedef std::function<void(unsigned char, const char *, bool)> TSetStateCallback;
typedef std::function<bool(unsigned char, const char *)> TGetStateCallback;
typedef struct {
char * name;
char * uuid;
char * serial;
bool hit;
bool state;
AsyncServer * server;
} fauxmoesp_device_t;
class fauxmoESP {
public:
fauxmoESP(unsigned int port = DEFAULT_TCP_BASE_PORT);
unsigned char addDevice(const char * device_name);
bool renameDevice(unsigned char id, const char * device_name);
char * getDeviceName(unsigned char id, char * buffer, size_t len);
void onSetState(TSetStateCallback fn) { _setCallback = fn; }
void onGetState(TGetStateCallback fn) { _getCallback = fn; }
void enable(bool enable);
void handle();
// backwards compatibility DEPRECATED
void onMessage(TSetStateCallback fn) { onSetState(fn); }
void setState(unsigned char id, bool state);
private:
bool _enabled = true;
unsigned int _base_port = DEFAULT_TCP_BASE_PORT;
std::vector<fauxmoesp_device_t> _devices;
#ifdef ESP8266
WiFiEventHandler _handler;
#endif
WiFiUDP _udp;
AsyncClient * _tcpClients[TCP_MAX_CLIENTS];
TSetStateCallback _setCallback = NULL;
TGetStateCallback _getCallback = NULL;
unsigned int _roundsLeft = 0;
unsigned int _current = 0;
unsigned long _lastTick;
IPAddress _remoteIP;
unsigned int _remotePort;
unsigned int _udpPattern;
void _sendUDPResponse(unsigned int device_id);
void _nextUDPResponse();
void _handleSetup(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _handleMetaInfoService(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _handleEventService(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _handleControl(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _handleIcon(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _onUDPData(const IPAddress remoteIP, unsigned int remotePort, void *data, size_t len);
void _onTCPData(AsyncClient *client, unsigned int device_id, void *data, size_t len);
void _onTCPClient(AsyncClient *client, unsigned int device_id);
};

View File

@@ -9,6 +9,8 @@ my $maxCols = 16;
my $maxLines = 0;
my $ESP = 0;
my $DIR = 0;
my $ROOT = 0;
my $UINT8 = 0;
my $file = "";
my $ctrlC = 0; # User induced termination
my $lineT = 0; # Linecount induced termination
@@ -34,7 +36,7 @@ my %FileType = (
"ico" => "image/x-icon",
"png" => "image/png",
"jpg" => "image/jpeg",
"js" => "application/javascript",
"js" => "text/javascript",
);
@@ -45,21 +47,26 @@ if (!@ARGV)
$prg [file|<wildcard> [...]] [-n=##] [-l=##] [-ESP] [-DIR] [-o=outfile]
version $VERSION
Dumps out the contents of the [file] as a c array.
Dumps out the contents of the [file(s)] as a c array.
If the file is binary, it emits a hex char-array using
the -n and -l settings.
If the file is text, it emits as an ascii char-array
where each source line is one ascii quoted line.
-ESP adds the PROGMEM attribte to the byte array data.
-ESP adds the PROGMEM attribute to the byte array data.
-DIR adds a 'directory' at the end of the output, providing
a reference to each input file. Most useful for a set
of files.
-ROOT prefixes each entry in the 'directory' with '/',
so "index.htm" becomes
-o=outfile Writes the output to outfile.
-u overrides the default 'char' array to be 'uint8_t'
NOTE that the filename and type info remains 'char'.
-n=## sets the byte count for each line, defaults 32.
-l=## sets a termination after ## lines have been printed.
@@ -83,8 +90,12 @@ foreach (@ARGV)
{ $outfile = $1; }
elsif (/-ESP/)
{ $ESP = 1; }
elsif (/-ROOT/)
{ $ROOT = 1; }
elsif (/-DIR/)
{ $DIR = 1; }
elsif (/-u/)
{ $UINT8 = 1; }
elsif (-e $_ )
{ push @files, $_; }
elsif (/[\*\?]/)
@@ -114,19 +125,32 @@ if ($outfile ne "") {
printf("No outfile, emitting to console.\n");
}
printf("//\n");
printf("// Command : %s %s\n", $prg, join(" ", @ARGV));
printf("// Command: %s %s\n", $prg, join(" ", @ARGV));
printf("// version: %2.1f\n", $VERSION);
printf("// From cwd : %s\n", cwd);
printf("// From cwd: %s\n", cwd);
printf("// Generated: %s\n", scalar localtime());
printf("// Run script in command shell, not PowerShell\n");
printf("// NOTE: Run script in command shell, not PowerShell\n");
printf("//\n");
if ($ESP) {
print <<EOM;
// PROGMEM is not recognized by Win32 tools...
#ifdef WIN32
#define PROGMEM
#endif
EOM
}
my @summary;
foreach my $file (@files) {
push @summary, Process($file);
last if ($ctrlC);
}
print "\n// Prototypes:\n// " . join("\n// ", @summary) . "\n";
if ($DIR) {
my $type = "char "; #($UINT8) ? "uint8_t" : "char ";
print <<EOM;
// Directory of Files
@@ -135,10 +159,10 @@ if ($DIR) {
// an empty "" Filename is reached.
//
typedef struct {
const char * Filename;
const char * Filedata;
const char * Filetype;
uint16_t Filesize;
const char * Filename;
const $type * Filedata;
const char * Filetype;
uint16_t Filesize;
} DirEntry;
EOM
@@ -147,14 +171,14 @@ EOM
foreach my $file (sort keys %DirInfo) {
#printf(STDERR "file %s, title %s, size %d\n", $file, $DirInfo{$file}{'reference'}, $DirInfo{$file}{'size'});
my $title = $DirInfo{$file}{'reference'};
my $fn = sprintf("\"%s\"", $file);
my $fn = sprintf("\"%s%s\"", $ROOT ? "/" : "", $file);
my $ext = "";
$ext = $1 if ($file =~ /.*\.(.*)/);
my $ftype = sprintf("\"%s\"", $FileType{$ext});
#printf(STDERR "ext: '%s' => '%s'\n", $ext, $FileType{lc($ext)});
printf("\t{ %-20s, %-20s, %24s, %6d },\n", $fn, $title, $ftype, $DirInfo{$file}{'size'});
}
printf("\t{ %-20s, %-20s, %24s, %6d }\n", "\"\"", "NULL", "NULL", 0);
printf("\t{ %-20s, %-20s, %24s, %6d }\n", "NULL", "NULL", "NULL", 0);
printf("};\n\n");
}
@@ -185,7 +209,7 @@ sub Process {
printf("\n");
printf("// File: %s\n", $file);
printf("//\n");
$prototype = sprintf("const char %s[]%s", $title, ($ESP) ? " PROGMEM" : "");
$prototype = sprintf("const %s %s[]%s", ($UINT8) ? "uint8_t" : "char", $title, ($ESP) ? " PROGMEM" : "");
printf("%s = {\n", $prototype);
my $size = 0;
@@ -198,7 +222,7 @@ sub Process {
$line =~ s/\r//g;
$line =~ s/"/\\"/g;
printf("\t\"%s\\n\"\n", $line);
$size += length($line);
$size += length($line) + 1; # +1 for the \n line termination.
}
} else {
printf("\t");

View File

@@ -2,7 +2,7 @@
REM
REM "Compile" the web resources folder into code modules.
REM
REM Command : MakeByteArray -ESP *.* -o=..\Web_Resources.h
REM Command : MakeByteArray -ESP -ROOT -DIR *.* -o=..\Web_Resources.h
REM From cwd : C:/Projects/SmartSwitch/Firmware/Resources
REM Run script in command shell, not PowerShell
REM
@@ -46,7 +46,7 @@ dir /b
echo.
echo.Compiling the web resources into code modules...
MakeByteArray -ESP -DIR *.* -o=%TARG%
MakeByteArray -ESP -ROOT -DIR *.* -o=%TARG%
echo.
echo.Done. %TARG% has been updated and returning to prior folder.