Many changes - not yet "controlled" updates.

This commit is contained in:
David
2021-10-04 13:45:39 +00:00
parent 8c1a403008
commit 74543a99f8
42 changed files with 7718 additions and 0 deletions

143
Firmware/DNSServer.cpp Normal file
View File

@@ -0,0 +1,143 @@
#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();
}

68
Firmware/DNSServer.h Normal file
View File

@@ -0,0 +1,68 @@
#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

847
Firmware/Firmware.ino Normal file
View File

@@ -0,0 +1,847 @@
///
///
/// 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>
// 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:
//
// 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
// _ +------------------+
// 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(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");
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
}
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");
}
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;
}

23
Firmware/GrowController.h Normal file
View File

@@ -0,0 +1,23 @@
#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

16
Firmware/ProjGlobals.h Normal file
View File

@@ -0,0 +1,16 @@
//
// Some settings
//
#ifndef PROJGLOBALS_H
#define PROJGLOBALS_H
#include <ESP8266WebServer.h>
#include "WiFiConfiguration.h"
extern bool isStationMode;
extern ESP8266WebServer server;
extern ConfigManager wifiConfig;
#endif // PROJGLOBALS_H

View File

@@ -0,0 +1,21 @@
input.BigButton {
width: 150px;
padding: 20px;
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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

BIN
Firmware/Resources/Open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<title>Grow Controller</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='about.js'></script>
</head>
<body>
<h1>Grow Controller</h1>
by Smartware Computing
<blockquote>
<table cellpadding="5">
<tr valign="top">
<td><img src="/icon.jpg" /></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>
<dd>Each press will toggle the circuit between on and off.</dd>
<dt>Reset Button</dt>
<dd>Momentary press resets unit. Very Long press causes Factory reset back to Access Point mode.</dd>
</dl>
</td>
</tr>
</table>
Mode:
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>
</body>
</html>

View File

@@ -0,0 +1,30 @@
//
// rssi script
//
// var mySite = 'http://192.168.1.23' from myip.js
var url = mySite + '/state';
setInterval(RefreshStatus, 5000);
getIt = function(aUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', aUrl, true);
xhr.responseType = 'text';
xhr.onload = function(e) {
if (this.status === 200) {
callback(this.response);
}
};
xhr.send();
}
function RefreshStatus() {
getIt(url,
function(data) {
obj = JSON.parse(data);
var elms = document.querySelectorAll('.' + 'ip'), i;
for (i = 0; i < elms.length; ++i) {
elms[i].textContent = obj.ip;
}
}
);
}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Grow Controller</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='curr.js'></script>
</head>
<body onload='CurrInit();'>
<h1>Grow Controller - Current Sense</h1>
<blockquote>
<table width='800' border='0'>
<tr>
<td colspan='3' align='center'>
<canvas id='curr' width='790' height='400'></canvas>
</td>
</tr>
<tr>
<td width='*' align='left'>
<div id='uptime'>x:xx:xx</div>
</td>
<td width='20%' align='right'>
<div id='currText'>xx</div>
</td>
</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>
</body>
</html>

141
Firmware/Resources/curr.js Normal file
View File

@@ -0,0 +1,141 @@
//
// curr script
//
// var mySite = 'http://192.168.1.23' from myip.js
var url = mySite + '/state';
var rawData = [];
var avgData = [];
var currIndex = 0;
var axes = {};
var totalSamples = 400; // Width of the graph
for (var i = 0; i < totalSamples; i++)
rawData[i] = avgData[i] = 0;
setInterval(RefreshStatus, 250);
// -------------------------
getIt = function (aUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', aUrl, true);
xhr.responseType = 'text';
xhr.onload = function (e) {
if (this.status === 200) {
callback(this.response);
}
};
xhr.send();
};
function RefreshStatus() {
getIt(url,
function (data) {
obj = JSON.parse(data);
UpdateGraph(obj.raw, obj.sense); // raw and averaged
document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime;
document.getElementById('currText').innerHTML = 'raw: ' + obj.raw + ', avg: ' + obj.sense;
}
);
}
function CurrInit() {
var canvas = document.getElementById('curr');
if (null === canvas || !canvas.getContext)
return;
ctx = canvas.getContext('2d');
axes.w = 0.9 * canvas.width;
axes.h = 0.8 * canvas.height;
axes.x0 = 0.07 * canvas.width; // x0 pixels from left to x=0
axes.y0 = 0.10 * canvas.height; // y0 pixels from top to y=0
axes.scaleX = totalSamples; // # pixels from x=0 to x=1
axes.scaleY = 1024;
axes.doNegativeX = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.rect(0, 0, canvas.width - 1, canvas.height - 1);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
showAxes(ctx, axes);
}
function UpdateGraph(rawCurr, avgCurr) {
var xx, yy;
var i;
if (rawCurr > axes.scaleY) // safety limit
rawCurr = axes.scaleY;
else if (rawCurr < 0)
rawCurr = 0;
if (avgCurr > axes.scaleY) // safety limit
avgCurr = axes.scaleY;
else if (avgCurr < 0)
avgCurr = 0;
rawData[currIndex] = rawCurr;
avgData[currIndex] = avgCurr;
console.log('UpdateGraph(-' + rawCurr + ')');
CurrInit();
// Draw Raw
x0 = axes.x0;
y0 = axes.y0;
ctx.beginPath();
ctx.strokeStyle = '#FF8080';
ctx.lineWidth = 3;
for (i = 0; i < totalSamples; i++) {
pi = (currIndex + 1 + i) % totalSamples;
xx = x0 + i / axes.scaleX * axes.w;
yy = y0 + axes.h - rawData[pi] / axes.scaleY * axes.h;
ctx.lineTo(xx, yy);
}
ctx.stroke();
// Circle
ctx.beginPath();
ctx.arc(xx, yy, 7, 0, 2 * Math.PI, false);
ctx.fillstyle = '#FF8080';
ctx.fill();
ctx.strokeStyle = '#FF0000';
ctx.stroke();
// Draw Average
x0 = axes.x0;
y0 = axes.y0;
ctx.beginPath();
ctx.strokeStyle = '#8080FF';
ctx.lineWidth = 3;
for (i = 0; i < totalSamples; i++) {
pi = (currIndex + 1 + i) % totalSamples;
xx = x0 + i / axes.scaleX * axes.w;
yy = y0 + axes.h - avgData[pi] / axes.scaleY * axes.h;
ctx.lineTo(xx, yy);
}
ctx.stroke();
// Circle
ctx.beginPath();
ctx.arc(xx, yy, 7, 0, 2 * Math.PI, false);
ctx.fillstyle = '#8080FF';
ctx.fill();
ctx.strokeStyle = '#0000FF';
ctx.stroke();
currIndex++;
currIndex %= totalSamples;
}
function showAxes(ctx, axes) {
var x0 = axes.x0, w = axes.w;
var y0 = axes.y0, h = axes.h;
var xmin = axes.doNegativeX ? 0 : x0;
ctx.lineWidth = 1;
ctx.font = '12px Arial';
ctx.textAlign = 'end';
ctx.beginPath();
ctx.strokeStyle = 'rgb(128,128,128)';
ctx.moveTo(x0, y0); ctx.lineTo(x0, y0 + h); // Y axis
for (var y = 0; y <= axes.scaleY; y += 100) {
yy = axes.y0 + axes.h - y / axes.scaleY * axes.h;
ctx.moveTo(xmin, yy); ctx.lineTo(x0 + w, yy); // X axis
ctx.fillText(y, x0 - 3, yy + 3);
}
ctx.font = '18px Arial';
ctx.textAlign = 'start';
ctx.fillText('Current Graph', axes.x0, axes.y0 - 6);
ctx.stroke();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
Firmware/Resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,208 @@
<!DOCTYPE html>
<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>
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='index.js'></script>
</head>
<body onload="RefreshStatus();">
<h1><span id='name'>Grow Controller</span></h1>
<form>
<table border='0'>
<tr>
<td width='45%' align='center'>
<div class="box">
<div style="position: relative; z-index: -10;">
<!-- 243 x 488 -->
<img src='PlantModel.png' />
</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>
</td>
<td width='10%' align='center'>
</td>
<td width='45%' align='center'>
<input class='BigButton' type='button' value='Automatic' onclick="window.location.href='/?SW=1'" />
<br />
<input class='BigButton' type='button' value='Manual [Open|Close]' onclick="window.location.href='/?SW=0'" />
<br />
<br />
<input class='BigButton' type='button' value='Grow Settings' onclick="window.location.href='/?SW=2'" />
<br />
<input class='SmallButton' type='button' value='Load Monitor' onclick="window.location.href='/curr'" />
<br />
<input class='SmallButton' type='button' value='Wi-Fi Config.' onclick="window.location.href='/config'" />
<br />
<input class='SmallButton' type='button' value='RSSI Monitor' onclick="window.location.href='/rssi'" />
<br />
<input class='SmallButton' type='button' value='Scan for APs' onclick="window.location.href='/scan'" />
</td>
</tr>
</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>
<hr>
<table border='0'>
<tr>
<td>GrowController by Origin Technologies</td>
<td align="right"><a href="/swupdatecheck">Firmware</a>: <span id='version'>v0.00.00</span></td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,44 @@
//
// rssi script
//
// var mySite = 'http://192.168.1.23' from myip.js
var url = mySite + '/state';
setInterval(RefreshStatus, 250);
getIt = function (aUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', aUrl, true);
xhr.responseType = 'text';
xhr.onload = function (e) {
if (this.status === 200) {
callback(this.response);
}
};
xhr.send();
};
function RefreshStatus() {
getIt(url,
function(data) {
obj = JSON.parse(data);
if (obj.state === 1)
state = "On";
else
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('uptime').innerHTML = 'Uptime: ' + obj.uptime;
document.getElementById('wifimode').innerHTML = 'Mode: ' + obj.wifimode;
var elms = document.querySelectorAll('.' + 'ip'), i;
for (i = 0; i < elms.length; ++i) {
elms[i].textContent = obj.ip;
}
}
);
}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<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">
<script type='text/javascript' src='myip.js'></script>
<script type='text/javascript' src='rssi.js'></script>
</head>
<body onload='RSSIInit();'>
<h1>Grow Controller - RSSI</h1>
<blockquote>
<table width='800' border='0'>
<tr>
<td colspan='3' align='center'>
<canvas id='RSSIGraph' width='790' height='400'></canvas>
</td>
</tr>
<tr>
<td width='*' align='left'>
<div id='uptime'>x:xx:xx</div>
</td>
<td width='20%' align='right'>
<div id='rssi'>-xx</div>
</td>
</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>
</body>
</html>

109
Firmware/Resources/rssi.js Normal file
View File

@@ -0,0 +1,109 @@
//
// rssi script
//
// var mySite = 'http://192.168.1.23' from myip.js
var url = mySite + '/state';
var rssiData = [];
var rssiIndex = 0;
var axes = {};
for (var i = 0; i<100; i++)
rssiData[i] = 0;
setInterval(RefreshStatus, 250);
// -------------------------
getIt = function (aUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', aUrl, true);
xhr.responseType = 'text';
xhr.onload = function (e) {
if (this.status === 200) {
callback(this.response);
}
};
xhr.send();
};
function RefreshStatus() {
getIt(url,
function(data) {
obj = JSON.parse(data);
UpdateGraph(obj.rssi);
document.getElementById('rssi').innerHTML = 'RSSI: ' + obj.rssi;
document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime;
}
);
}
function RSSIInit() {
var canvas = document.getElementById('RSSIGraph');
if (null === canvas || !canvas.getContext)
return;
ctx = canvas.getContext('2d');
axes.w = 0.9 * canvas.width;
axes.h = 0.8 * canvas.height;
axes.x0 = 0.07 * canvas.width; // x0 pixels from left to x=0
axes.y0 = 0.10 * canvas.height; // y0 pixels from top to y=0
axes.scaleX = 100; // # pixels from x=0 to x=1
axes.scaleY = 100;
axes.doNegativeX = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.rect(0, 0, canvas.width - 1, canvas.height - 1);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
showAxes(ctx, axes);
}
function UpdateGraph(rssi) {
var xx, yy;
rssi = -rssi; // invert for convenience
if (rssi > axes.scaleY) // safety limit
rssi = axes.scaleY;
else if (rssi < 0)
rssi = 0;
rssiData[rssiIndex] = rssi;
console.log('UpdateGraph(-' + rssi + ')');
RSSIInit();
x0 = axes.x0;
y0 = axes.y0;
ctx.beginPath();
ctx.strokeStyle = 'rgb(250,128,128)';
ctx.lineWidth = 5;
for (var i = 0; i<100; i++) {
pi = (rssiIndex + 1 + i) % 100;
xx = x0 + i / axes.scaleX * axes.w;
yy = y0 + rssiData[pi] / axes.scaleY * axes.h;
ctx.lineTo(xx, yy);
}
ctx.stroke();
ctx.beginPath();
ctx.arc(xx, yy, 7, 0, 2 * Math.PI, false);
ctx.fillstyle = 'green';
ctx.fill();
ctx.strokeStyle = '#003300';
ctx.stroke();
rssiIndex++;
rssiIndex %= 100;
}
function showAxes(ctx, axes) {
var x0 = axes.x0, w = axes.w;
var y0 = axes.y0, h = axes.h;
var xmin = axes.doNegativeX ? 0 : x0;
ctx.lineWidth = 1;
ctx.font = '12px Arial';
ctx.textAlign = 'end';
ctx.beginPath();
ctx.strokeStyle = 'rgb(128,128,128)';
ctx.moveTo(x0, y0); ctx.lineTo(x0, y0 + h); // Y axis
for (var y = 0; y <= axes.scaleY; y += 10) {
yy = axes.y0 + y / axes.scaleY * axes.h;
ctx.moveTo(xmin, yy); ctx.lineTo(x0 + w, yy); // X axis
ctx.fillText(-y, x0 - 3, yy + 3);
}
ctx.font = '18px Arial';
ctx.textAlign = 'start';
ctx.fillText('RSSI Graph', axes.x0, axes.y0 - 6);
ctx.stroke();
}

25
Firmware/SmartSwitch.sln Normal file
View File

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

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

262
Firmware/WeMo.h Normal file
View File

@@ -0,0 +1,262 @@
/*
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";

253
Firmware/Web_APConfig.cpp Normal file
View File

@@ -0,0 +1,253 @@
#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"
);
}

12
Firmware/Web_APConfig.h Normal file
View File

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

39
Firmware/Web_FavIcon.cpp Normal file
View File

@@ -0,0 +1,39 @@
#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));
}

13
Firmware/Web_FavIcon.h Normal file
View File

@@ -0,0 +1,13 @@
#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

@@ -0,0 +1,22 @@
#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);
}

10
Firmware/Web_RSSIGraph.h Normal file
View File

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

3549
Firmware/Web_Resources.h Normal file

File diff suppressed because it is too large Load Diff

33
Firmware/Web_RootPage.cpp Normal file
View File

@@ -0,0 +1,33 @@
#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);
}

7
Firmware/Web_RootPage.h Normal file
View File

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

View File

@@ -0,0 +1,55 @@
///
/// Simple WiFi Configuration features
///
#ifndef WIFICONFIGURATION_H
#define WIFICONFIGURATION_H
#include <ESP8266WiFi.h>
#define CFG_NAMESIZE 32
#define CFG_SSIDSIZE 32
#define CFG_PASSSIZE 64
#define CFG_UURLSIZE 64
/// Provides a handy interface for WiFi configuration.
///
/// @code
/// WiFiConfiguration config;
/// ...
/// config.load();
/// ssid = config.getSSID();
/// pass = config.getPassword();
/// ...
/// WiFi.begin(ssid.c_str(), pass.c_str());
///
/// @endcode
///
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
private:
};
#endif // !WIFICONFIGURATION_H

View File

@@ -0,0 +1,157 @@
//
// This file hosts all the non-volatile configuration options for this node
//
#include "WiFiConfiguration.h"
#include <EEPROM.h>
#define EEROM_SIZE 512
// Change this when it is required to force a reinitialization
//
#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;
} 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_AUTOOFF (OFS_OFFREF + sizeof(uint16_t))
//#define OFS_NTPIP (OFS_OFFREF + 2)
Config_t Configuration;
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';
}
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);
}
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);
}
#if 0
String ConfigManager::getNTPServerName() {
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);
}
#endif
String ConfigManager::getName() {
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);
}
String ConfigManager::getSSID() {
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);
}
String ConfigManager::getPassword() {
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);
}
String ConfigManager::getURL() {
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);
}
uint16_t ConfigManager::getOnRef() {
return Configuration.info.onRef;
}
void ConfigManager::setOnRef(uint16 onR) {
Configuration.info.onRef = onR;
}
uint16_t ConfigManager::getOffRef() {
return Configuration.info.offRef;
}
void ConfigManager::setOffRef(uint16 offR) {
Configuration.info.offRef = offR;
}
uint16_t ConfigManager::getAutoOff() {
return Configuration.info.autoOff;
}
void ConfigManager::setAutoOff(uint16 autoOff) {
Configuration.info.autoOff = autoOff;
}

479
Firmware/fauxmoESP.cpp Normal file
View File

@@ -0,0 +1,479 @@
/*
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) 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) 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("444556%06X%02X\0"), chip_id, device_id); // "DEV" + CHIPID + DEV_ID
new_device.uuid = strdup(uuid);
// Create Serialnumber
char serial[15];
sprintf(serial, "221703K0%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) {
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
}

135
Firmware/fauxmoESP.h Normal file
View File

@@ -0,0 +1,135 @@
/*
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);
};

290
Hardware/GrowController.sch Normal file
View File

@@ -0,0 +1,290 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE eagle SYSTEM "eagle.dtd">
<eagle version="7.7.0">
<drawing>
<settings>
<setting alwaysvectorfont="no"/>
<setting verticaltext="up"/>
</settings>
<grid distance="0.1" unitdist="inch" unit="inch" style="lines" multiple="1" display="no" altdistance="0.01" altunitdist="inch" altunit="inch"/>
<layers>
<layer number="1" name="Top" color="4" fill="1" visible="no" active="no"/>
<layer number="2" name="Route2" color="1" fill="3" visible="no" active="no"/>
<layer number="3" name="Route3" color="4" fill="3" visible="no" active="no"/>
<layer number="4" name="Route4" color="1" fill="4" visible="no" active="no"/>
<layer number="5" name="Route5" color="4" fill="4" visible="no" active="no"/>
<layer number="6" name="Route6" color="1" fill="8" visible="no" active="no"/>
<layer number="7" name="Route7" color="4" fill="8" visible="no" active="no"/>
<layer number="8" name="Route8" color="1" fill="2" visible="no" active="no"/>
<layer number="9" name="Route9" color="4" fill="2" visible="no" active="no"/>
<layer number="10" name="Route10" color="1" fill="7" visible="no" active="no"/>
<layer number="11" name="Route11" color="4" fill="7" visible="no" active="no"/>
<layer number="12" name="Route12" color="1" fill="5" visible="no" active="no"/>
<layer number="13" name="Route13" color="4" fill="5" visible="no" active="no"/>
<layer number="14" name="Route14" color="1" fill="6" visible="no" active="no"/>
<layer number="15" name="Route15" color="4" fill="6" visible="no" active="no"/>
<layer number="16" name="Bottom" color="1" fill="1" visible="no" active="no"/>
<layer number="17" name="Pads" color="2" fill="1" visible="no" active="no"/>
<layer number="18" name="Vias" color="2" fill="1" visible="no" active="no"/>
<layer number="19" name="Unrouted" color="6" fill="1" visible="no" active="no"/>
<layer number="20" name="Dimension" color="15" fill="1" visible="no" active="no"/>
<layer number="21" name="tPlace" color="7" fill="1" visible="no" active="no"/>
<layer number="22" name="bPlace" color="7" fill="1" visible="no" active="no"/>
<layer number="23" name="tOrigins" color="15" fill="1" visible="no" active="no"/>
<layer number="24" name="bOrigins" color="15" fill="1" visible="no" active="no"/>
<layer number="25" name="tNames" color="7" fill="1" visible="no" active="no"/>
<layer number="26" name="bNames" color="7" fill="1" visible="no" active="no"/>
<layer number="27" name="tValues" color="7" fill="1" visible="no" active="no"/>
<layer number="28" name="bValues" color="7" fill="1" visible="no" active="no"/>
<layer number="29" name="tStop" color="7" fill="3" visible="no" active="no"/>
<layer number="30" name="bStop" color="7" fill="6" visible="no" active="no"/>
<layer number="31" name="tCream" color="7" fill="4" visible="no" active="no"/>
<layer number="32" name="bCream" color="7" fill="5" visible="no" active="no"/>
<layer number="33" name="tFinish" color="6" fill="3" visible="no" active="no"/>
<layer number="34" name="bFinish" color="6" fill="6" visible="no" active="no"/>
<layer number="35" name="tGlue" color="7" fill="4" visible="no" active="no"/>
<layer number="36" name="bGlue" color="7" fill="5" visible="no" active="no"/>
<layer number="37" name="tTest" color="7" fill="1" visible="no" active="no"/>
<layer number="38" name="bTest" color="7" fill="1" visible="no" active="no"/>
<layer number="39" name="tKeepout" color="4" fill="11" visible="no" active="no"/>
<layer number="40" name="bKeepout" color="1" fill="11" visible="no" active="no"/>
<layer number="41" name="tRestrict" color="4" fill="10" visible="no" active="no"/>
<layer number="42" name="bRestrict" color="1" fill="10" visible="no" active="no"/>
<layer number="43" name="vRestrict" color="2" fill="10" visible="no" active="no"/>
<layer number="44" name="Drills" color="7" fill="1" visible="no" active="no"/>
<layer number="45" name="Holes" color="7" fill="1" visible="no" active="no"/>
<layer number="46" name="Milling" color="3" fill="1" visible="no" active="no"/>
<layer number="47" name="Measures" color="7" fill="1" visible="no" active="no"/>
<layer number="48" name="Document" color="7" fill="1" visible="no" active="no"/>
<layer number="49" name="Reference" color="7" fill="1" visible="no" active="no"/>
<layer number="50" name="dxf" color="7" fill="1" visible="no" active="no"/>
<layer number="51" name="tDocu" color="7" fill="1" visible="no" active="no"/>
<layer number="52" name="bDocu" color="7" fill="1" visible="no" active="no"/>
<layer number="53" name="tGND_GNDA" color="7" fill="9" visible="no" active="no"/>
<layer number="54" name="bGND_GNDA" color="1" fill="9" visible="no" active="no"/>
<layer number="56" name="wert" color="7" fill="1" visible="no" active="no"/>
<layer number="57" name="tCAD" color="7" fill="1" visible="no" active="no"/>
<layer number="59" name="tCarbon" color="7" fill="1" visible="no" active="no"/>
<layer number="60" name="bCarbon" color="7" fill="1" visible="no" active="no"/>
<layer number="90" name="Modules" color="5" fill="1" visible="yes" active="yes"/>
<layer number="91" name="Nets" color="2" fill="1" visible="yes" active="yes"/>
<layer number="92" name="Busses" color="1" fill="1" visible="yes" active="yes"/>
<layer number="93" name="Pins" color="2" fill="1" visible="no" active="yes"/>
<layer number="94" name="Symbols" color="4" fill="1" visible="yes" active="yes"/>
<layer number="95" name="Names" color="7" fill="1" visible="yes" active="yes"/>
<layer number="96" name="Values" color="7" fill="1" visible="yes" active="yes"/>
<layer number="97" name="Info" color="7" fill="1" visible="yes" active="yes"/>
<layer number="98" name="Guide" color="6" fill="1" visible="yes" active="yes"/>
<layer number="99" name="SpiceOrder" color="7" fill="1" visible="yes" active="yes"/>
<layer number="100" name="Muster" color="7" fill="1" visible="no" active="no"/>
<layer number="101" name="Patch_Top" color="12" fill="4" visible="yes" active="yes"/>
<layer number="102" name="Vscore" color="7" fill="1" visible="yes" active="yes"/>
<layer number="103" name="tMap" color="7" fill="1" visible="yes" active="yes"/>
<layer number="104" name="Name" color="16" fill="1" visible="yes" active="yes"/>
<layer number="105" name="tPlate" color="7" fill="1" visible="yes" active="yes"/>
<layer number="106" name="bPlate" color="7" fill="1" visible="yes" active="yes"/>
<layer number="107" name="Crop" color="7" fill="1" visible="yes" active="yes"/>
<layer number="108" name="tplace-old" color="10" fill="1" visible="yes" active="yes"/>
<layer number="109" name="ref-old" color="11" fill="1" visible="yes" active="yes"/>
<layer number="110" name="fp0" color="7" fill="1" visible="yes" active="yes"/>
<layer number="111" name="LPC17xx" color="7" fill="1" visible="yes" active="yes"/>
<layer number="112" name="tSilk" color="7" fill="1" visible="yes" active="yes"/>
<layer number="113" name="IDFDebug" color="4" fill="1" visible="yes" active="yes"/>
<layer number="114" name="Route14" color="7" fill="1" visible="no" active="no"/>
<layer number="115" name="Route15" color="7" fill="1" visible="no" active="no"/>
<layer number="116" name="Patch_BOT" color="9" fill="4" visible="yes" active="yes"/>
<layer number="117" name="mPads" color="7" fill="1" visible="yes" active="yes"/>
<layer number="118" name="mVias" color="7" fill="1" visible="yes" active="yes"/>
<layer number="119" name="mUnrouted" color="7" fill="1" visible="yes" active="yes"/>
<layer number="120" name="mDimension" color="7" fill="1" visible="yes" active="yes"/>
<layer number="121" name="_tsilk" color="7" fill="1" visible="yes" active="yes"/>
<layer number="122" name="_bsilk" color="7" fill="1" visible="yes" active="yes"/>
<layer number="123" name="mtOrigins" color="7" fill="1" visible="yes" active="yes"/>
<layer number="124" name="mbOrigins" color="7" fill="1" visible="yes" active="yes"/>
<layer number="125" name="mtNames" color="7" fill="1" visible="yes" active="yes"/>
<layer number="126" name="mbNames" color="7" fill="1" visible="yes" active="yes"/>
<layer number="127" name="mtValues" color="7" fill="1" visible="yes" active="yes"/>
<layer number="128" name="mbValues" color="7" fill="1" visible="yes" active="yes"/>
<layer number="129" name="mtStop" color="7" fill="1" visible="yes" active="yes"/>
<layer number="130" name="mbStop" color="7" fill="1" visible="yes" active="yes"/>
<layer number="131" name="mtCream" color="7" fill="1" visible="yes" active="yes"/>
<layer number="132" name="mbCream" color="7" fill="1" visible="yes" active="yes"/>
<layer number="133" name="mtFinish" color="7" fill="1" visible="yes" active="yes"/>
<layer number="134" name="mbFinish" color="7" fill="1" visible="yes" active="yes"/>
<layer number="135" name="mtGlue" color="7" fill="1" visible="yes" active="yes"/>
<layer number="136" name="mbGlue" color="7" fill="1" visible="yes" active="yes"/>
<layer number="137" name="mtTest" color="7" fill="1" visible="yes" active="yes"/>
<layer number="138" name="mbTest" color="7" fill="1" visible="yes" active="yes"/>
<layer number="139" name="mtKeepout" color="7" fill="1" visible="yes" active="yes"/>
<layer number="140" name="mbKeepout" color="7" fill="1" visible="yes" active="yes"/>
<layer number="141" name="mtRestrict" color="7" fill="1" visible="yes" active="yes"/>
<layer number="142" name="mbRestrict" color="7" fill="1" visible="yes" active="yes"/>
<layer number="143" name="mvRestrict" color="7" fill="1" visible="yes" active="yes"/>
<layer number="144" name="Drill_legend" color="7" fill="1" visible="yes" active="yes"/>
<layer number="145" name="mHoles" color="7" fill="1" visible="yes" active="yes"/>
<layer number="146" name="mMilling" color="7" fill="1" visible="yes" active="yes"/>
<layer number="147" name="mMeasures" color="7" fill="1" visible="yes" active="yes"/>
<layer number="148" name="mDocument" color="7" fill="1" visible="yes" active="yes"/>
<layer number="149" name="mReference" color="7" fill="1" visible="yes" active="yes"/>
<layer number="150" name="Notes" color="7" fill="1" visible="yes" active="yes"/>
<layer number="151" name="HeatSink" color="7" fill="1" visible="yes" active="yes"/>
<layer number="152" name="mbDocu" color="7" fill="1" visible="yes" active="yes"/>
<layer number="153" name="FabDoc1" color="7" fill="1" visible="yes" active="yes"/>
<layer number="154" name="FabDoc2" color="7" fill="1" visible="yes" active="yes"/>
<layer number="155" name="FabDoc3" color="7" fill="1" visible="yes" active="yes"/>
<layer number="160" name="Outline" color="14" fill="1" visible="yes" active="yes"/>
<layer number="188" name="Graphics" color="7" fill="1" visible="yes" active="yes"/>
<layer number="191" name="mNets" color="7" fill="1" visible="yes" active="yes"/>
<layer number="192" name="mBusses" color="7" fill="1" visible="yes" active="yes"/>
<layer number="193" name="mPins" color="7" fill="1" visible="yes" active="yes"/>
<layer number="194" name="mSymbols" color="7" fill="1" visible="yes" active="yes"/>
<layer number="195" name="mNames" color="7" fill="1" visible="yes" active="yes"/>
<layer number="196" name="mValues" color="7" fill="1" visible="yes" active="yes"/>
<layer number="199" name="Contour" color="7" fill="1" visible="yes" active="yes"/>
<layer number="200" name="200bmp" color="1" fill="10" visible="yes" active="yes"/>
<layer number="201" name="201bmp" color="2" fill="10" visible="yes" active="yes"/>
<layer number="202" name="202bmp" color="3" fill="10" visible="yes" active="yes"/>
<layer number="203" name="203bmp" color="4" fill="10" visible="yes" active="yes"/>
<layer number="204" name="204bmp" color="5" fill="10" visible="yes" active="yes"/>
<layer number="205" name="205bmp" color="6" fill="10" visible="yes" active="yes"/>
<layer number="206" name="206bmp" color="7" fill="10" visible="yes" active="yes"/>
<layer number="207" name="207bmp" color="8" fill="10" visible="yes" active="yes"/>
<layer number="208" name="208bmp" color="9" fill="10" visible="yes" active="yes"/>
<layer number="209" name="209bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="210" name="210bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="211" name="211bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="212" name="212bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="213" name="213bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="214" name="214bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="215" name="215bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="216" name="SMD16" color="7" fill="1" visible="yes" active="yes"/>
<layer number="217" name="217bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="218" name="218bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="219" name="219bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="220" name="220bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="221" name="221bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="222" name="222bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="223" name="223bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="224" name="224bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="225" name="225bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="226" name="226bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="227" name="227bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="228" name="228bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="229" name="229bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="230" name="230bmp" color="7" fill="1" visible="yes" active="yes"/>
<layer number="231" name="Eagle3D_PG1" color="7" fill="1" visible="yes" active="yes"/>
<layer number="232" name="Eagle3D_PG2" color="7" fill="1" visible="yes" active="yes"/>
<layer number="233" name="Eagle3D_PG3" color="7" fill="1" visible="yes" active="yes"/>
<layer number="248" name="Housing" color="7" fill="1" visible="yes" active="yes"/>
<layer number="249" name="Edge" color="7" fill="1" visible="yes" active="yes"/>
<layer number="250" name="Descript" color="3" fill="1" visible="no" active="no"/>
<layer number="251" name="SMDround" color="12" fill="11" visible="no" active="no"/>
<layer number="254" name="cooling" color="7" fill="1" visible="yes" active="yes"/>
<layer number="255" name="Accent" color="7" fill="1" visible="yes" active="yes"/>
</layers>
<schematic xreflabel="%F%N/%S.%C%R" xrefpart="/%S.%C%R">
<libraries>
<library name="Smartware">
<description>&lt;b&gt;Parts&lt;/b&gt;</description>
<packages>
</packages>
<symbols>
<symbol name="FRAME_A_L">
<frame x1="0" y1="0" x2="254" y2="196.85" columns="6" rows="5" layer="94" border-bottom="no"/>
</symbol>
<symbol name="DOCFIELD">
<wire x1="0" y1="0" x2="71.12" y2="0" width="0.1016" layer="94"/>
<wire x1="101.6" y1="10.16" x2="80.01" y2="10.16" width="0.1016" layer="94"/>
<wire x1="0" y1="0" x2="0" y2="5.08" width="0.1016" layer="94"/>
<wire x1="0" y1="5.08" x2="71.12" y2="5.08" width="0.1016" layer="94"/>
<wire x1="0" y1="5.08" x2="0" y2="10.16" width="0.1016" layer="94"/>
<wire x1="101.6" y1="10.16" x2="101.6" y2="5.08" width="0.1016" layer="94"/>
<wire x1="71.12" y1="5.08" x2="71.12" y2="0" width="0.1016" layer="94"/>
<wire x1="71.12" y1="5.08" x2="80.01" y2="5.08" width="0.1016" layer="94"/>
<wire x1="71.12" y1="0" x2="101.6" y2="0" width="0.1016" layer="94"/>
<wire x1="80.01" y1="10.16" x2="80.01" y2="5.08" width="0.1016" layer="94"/>
<wire x1="80.01" y1="10.16" x2="0" y2="10.16" width="0.1016" layer="94"/>
<wire x1="80.01" y1="5.08" x2="101.6" y2="5.08" width="0.1016" layer="94"/>
<wire x1="101.6" y1="5.08" x2="101.6" y2="0" width="0.1016" layer="94"/>
<wire x1="0" y1="10.16" x2="0" y2="15.24" width="0.1016" layer="94"/>
<wire x1="0" y1="15.24" x2="0" y2="24.13" width="0.1016" layer="94"/>
<wire x1="101.6" y1="34.29" x2="0" y2="34.29" width="0.1016" layer="94"/>
<wire x1="101.6" y1="34.29" x2="101.6" y2="29.21" width="0.1016" layer="94"/>
<wire x1="101.6" y1="29.21" x2="101.6" y2="24.13" width="0.1016" layer="94"/>
<wire x1="0" y1="24.13" x2="101.6" y2="24.13" width="0.1016" layer="94"/>
<wire x1="0" y1="24.13" x2="0" y2="29.21" width="0.1016" layer="94"/>
<wire x1="0" y1="29.21" x2="0" y2="34.29" width="0.1016" layer="94"/>
<wire x1="101.6" y1="24.13" x2="101.6" y2="15.24" width="0.1016" layer="94"/>
<wire x1="101.6" y1="15.24" x2="101.6" y2="10.16" width="0.1016" layer="94"/>
<wire x1="0" y1="29.21" x2="101.6" y2="29.21" width="0.1016" layer="94"/>
<wire x1="0" y1="15.24" x2="101.6" y2="15.24" width="0.1016" layer="94"/>
<text x="1.27" y="1.27" size="2.54" layer="94" font="vector">Date:</text>
<text x="12.7" y="1.27" size="2.54" layer="94" font="vector">&gt;LAST_DATE_TIME</text>
<text x="72.39" y="1.27" size="2.54" layer="94" font="vector">Sheet:</text>
<text x="86.36" y="1.27" size="2.54" layer="94" font="vector">&gt;SHEET</text>
<text x="81.28" y="6.35" size="2.54" layer="94" font="vector">Rev:</text>
<text x="1.27" y="30.48" size="2.54" layer="94" font="vector">Document Title:</text>
<text x="1.27" y="6.35" size="2.54" layer="94" font="vector">Part Number:</text>
<text x="35.56" y="30.48" size="2.54" layer="94" font="vector">&gt;DRAWING_NAME</text>
<text x="1.27" y="25.4" size="2.54" layer="94" font="vector">Sheet Function:</text>
<text x="30.48" y="6.35" size="2.54" layer="94" font="vector">&gt;PART_NUMBER</text>
<text x="91.44" y="6.35" size="2.54" layer="94" font="vector">&gt;PART_REV</text>
<text x="5.08" y="20.32" size="2.54" layer="94" font="vector">&gt;COMPANY_NAME</text>
<text x="5.08" y="16.51" size="2.54" layer="94" font="vector">&gt;COMPANY_ADDR</text>
<text x="1.27" y="11.43" size="2.54" layer="94" font="vector">Drawn:</text>
<text x="30.48" y="11.43" size="2.54" layer="94" font="vector">&gt;ENGINEER</text>
<text x="35.56" y="25.4" size="2.54" layer="94" font="vector">&gt;MODULE</text>
</symbol>
</symbols>
<devicesets>
<deviceset name="FRAME_A_L" prefix="FRAME" uservalue="yes">
<description>&lt;b&gt;FRAME - A Landscape&lt;/b&gt;&lt;br/&gt;
Smartware Computing</description>
<gates>
<gate name="G$1" symbol="FRAME_A_L" x="0" y="0" addlevel="always"/>
<gate name="G$2" symbol="DOCFIELD" x="148.59" y="0" addlevel="always"/>
</gates>
<devices>
<device name="">
<technologies>
<technology name=""/>
</technologies>
</device>
</devices>
</deviceset>
</devicesets>
</library>
</libraries>
<attributes>
<attribute name="COMPANY_ADDR" value="https://smart-family.net/Smartware"/>
<attribute name="COMPANY_NAME" value="Smartware Computing"/>
<attribute name="ENGINEER" value="D.Smart"/>
<attribute name="PART_NUMBER" value="GrowController"/>
<attribute name="PART_REV" value="0.0"/>
</attributes>
<variantdefs>
</variantdefs>
<classes>
<class number="0" name="default" width="0" drill="0">
</class>
</classes>
<parts>
<part name="FRAME1" library="Smartware" deviceset="FRAME_A_L" device=""/>
</parts>
<sheets>
<sheet>
<plain>
</plain>
<instances>
<instance part="FRAME1" gate="G$1" x="0" y="0"/>
<instance part="FRAME1" gate="G$2" x="148.59" y="0"/>
</instances>
<busses>
</busses>
<nets>
</nets>
</sheet>
</sheets>
</schematic>
</drawing>
</eagle>

244
Tools/MakeByteArray.pl Normal file
View File

@@ -0,0 +1,244 @@
# MakeByteArray
#
#
use strict;
use warnings;
use Cwd qw(cwd);
my $maxCols = 16;
my $maxLines = 0;
my $ESP = 0;
my $DIR = 0;
my $file = "";
my $ctrlC = 0; # User induced termination
my $lineT = 0; # Linecount induced termination
$SIG{INT} = \&CtrlCHandler;
$SIG{QUIT} = \&CtrlCHandler;
my %DirInfo; # Name and FileSize
my $prg = $0;
$prg =~ s/^.*[\\\/](.*)/$1/;
$prg =~ s/(.*)\..*/$1/;
my $VERSION = 1.0;
# Extension in this list must be lower case.
my %FileType = (
"htm" => "text/html",
"html" => "text/html",
"gif" => "image/gif",
"svg" => "image/svg+xml",
"css" => "text/css",
"ico" => "image/x-icon",
"png" => "image/png",
"jpg" => "image/jpeg",
"js" => "application/javascript",
);
if (!@ARGV)
{
print <<EOM;
$prg [file|<wildcard> [...]] [-n=##] [-l=##] [-ESP] [-DIR] [-o=outfile]
version $VERSION
Dumps out the contents of the [file] 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.
-DIR adds a 'directory' at the end of the output, providing
a reference to each input file. Most useful for a set
of files.
-o=outfile Writes the output to outfile.
-n=## sets the byte count for each line, defaults 32.
-l=## sets a termination after ## lines have been printed.
Example: MakeByteArray -ESP srcpath\\*.* -o=dstpath\\Web_Resources.h
EOM
exit;
}
my @files;
my $outfile = "";
#printf("ARGS: '%s'\n", join("|", @ARGV));
foreach (@ARGV)
{
if (/-n=(\d+)/)
{ $maxCols = $1; }
elsif (/-l=(\d+)/)
{ $maxLines = $1; }
elsif (/-o=(.*)/)
{ $outfile = $1; }
elsif (/-ESP/)
{ $ESP = 1; }
elsif (/-DIR/)
{ $DIR = 1; }
elsif (-e $_ )
{ push @files, $_; }
elsif (/[\*\?]/)
{
my @gFiles = glob($_);
foreach (@gFiles)
{
push @files, $_ if (-e $_);
}
}
else
{ print "unrecognized command.\n"; exit; }
}
if (!@files)
{
print "No files to process.\n";
exit;
}
my $oldHandle;
if ($outfile ne "") {
#printf("Open for write '%s'\n", $outfile);
open(FO, ">$outfile") || die("Can't write to $outfile");
$oldHandle = select FO;
} else {
printf("No outfile, emitting to console.\n");
}
printf("//\n");
printf("// Command : %s %s\n", $prg, join(" ", @ARGV));
printf("// version: %2.1f\n", $VERSION);
printf("// From cwd : %s\n", cwd);
printf("// Generated: %s\n", scalar localtime());
printf("// Run script in command shell, not PowerShell\n");
my @summary;
foreach my $file (@files) {
push @summary, Process($file);
last if ($ctrlC);
}
print "\n// Prototypes:\n// " . join("\n// ", @summary) . "\n";
if ($DIR) {
print <<EOM;
// Directory of Files
//
// This typedef is used to iterate through the directory listing, until
// an empty "" Filename is reached.
//
typedef struct {
const char * Filename;
const char * Filedata;
const char * Filetype;
uint16_t Filesize;
} DirEntry;
EOM
printf("const DirEntry Directory[] %s= {\n", ($ESP) ? "PROGMEM " : "");
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 $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("};\n\n");
}
if ($outfile) {
close FO;
select($oldHandle);
}
print "\nTerminated by operator\n" if ($ctrlC == 1);
exit;
# ####################################
sub Process {
my $file = shift;
my $title = $file;
my $isText = 0;
my $prototype;
$title =~ s/\./_/g;
$isText = 1 if (-T $file);
open(FH, "<$file") || die("Can't read $file.\n");
binmode FH if ($isText);
my $cols = 0;
my $c;
my $ascii = "";
my $address = 0;
my $lineCount = 0;
printf("\n");
printf("// File: %s\n", $file);
printf("//\n");
$prototype = sprintf("const char %s[]%s", $title, ($ESP) ? " PROGMEM" : "");
printf("%s = {\n", $prototype);
my $size = 0;
if ($isText) {
while (<FH>) {
my $line = $_;
chomp $line;
$line =~ s/\t/ /g;
$line =~ s/\\"/"/g;
$line =~ s/\r//g;
$line =~ s/"/\\"/g;
printf("\t\"%s\\n\"\n", $line);
$size += length($line);
}
} else {
printf("\t");
while (sysread(FH, $c, 1) && !$ctrlC && !$lineT) {
my $b = ord($c);
printf("0x%02X,", $b);
if ($b >= 0x20 && $b <= 0x7F)
{ $ascii .= chr($b); }
else
{ $ascii .= "."; }
++$cols;
++$address;
$cols %= $maxCols;
if ($cols == 0) {
#print " // $ascii";
print "\n";
$ascii = "";
$lineCount++;
$lineT = 1 if ($maxLines && $lineCount >= $maxLines);
if (!$lineT) {
printf("\t");
}
}
$size += 1;
}
if ($ascii ne "") {
print " " x ($maxCols - $cols);
}
printf("\n");
}
printf("}; // %s, %d bytes\n", $title, $size);
close FH;
$DirInfo{$file}{'reference'} = $title;
$DirInfo{$file}{'size'} = $size;
return $prototype;
}
# If the user presses Control-C, signal it.
#
sub CtrlCHandler {
$ctrlC = 1;
}

View File

@@ -0,0 +1,55 @@
@echo off
REM
REM "Compile" the web resources folder into code modules.
REM
REM Command : MakeByteArray -ESP *.* -o=..\Web_Resources.h
REM From cwd : C:/Projects/SmartSwitch/Firmware/Resources
REM Run script in command shell, not PowerShell
REM
REM External Dependency:
REM MakeByteArray - Converts 1 or more files into a .h file byte array.
REM
REM +------------------------------------------------+
REM | MakeResourceFiles.cmd |
REM +------------------------------------------------+
REM ||
REM +----------+ +-----------+ +-----------+
REM +----------+ | |Make | | |
REM |source | | |Byte | |Resources.h|
REM |.htm | | ==> |Array.pl | ==> | |
REM |.js | | | | | |
REM |.jpg | | | | | |
REM |etc... |-+ | | | |
REM +----------+ +-----------+ +-----------+
setlocal
REM ######################################################################
REM
REM Configuration Options
REM
set SRC=..\Firmware\Resources
set TARG=..\Web_Resources.h
REM
REM End of Configuration
REM
REM ######################################################################
echo.
echo.Make Resource Files
echo.Change to %SRC% ...
pushd %SRC%
echo.Show web resources
dir /b
echo.
echo.Compiling the web resources into code modules...
MakeByteArray -ESP -DIR *.* -o=%TARG%
echo.
echo.Done. %TARG% has been updated and returning to prior folder.
dir %TARG%
popd
endlocal

124
Tools/MoveToServer.pl Normal file
View File

@@ -0,0 +1,124 @@
#
# Move to Server
#
# Monitor the debug and release folders for a new binary.
# If found,
# 1 Read the .ino file to find the current version string.
# 2 Rename the .bin to include the version string.
# 3 Move it to the server.
#
# Searches .ino file for: const String MyVer = "SmartSwitch v1.02.24";
#
use strict;
use warnings;
###################### Configuration ###########################
#
# Target Server Path
#
my $ServerPath = "\\\\server1\\web\\mbed\\ESPbin";
# Sleep Time between checks
#
my $SleepTime = 5;
# Verbose mode
# 0 = off
# 1 = highlights
# 2 = detailed
#
my $Verbose = 1;
#####################
my $continue = 1;
$SIG{INT} = sub { $continue = 0; };
$SIG{TERM} = sub { $continue = 0; };
do {
printf("\n") if ($Verbose > 1);
printf("MoveToServer check at %s\n", scalar localtime()) if ($Verbose == 1);
chdir "../Firmware";
Process();
Pause($SleepTime);
} while ($continue);
exit;
####################################################################
sub Process {
my $slnFile = "";
my @files = glob("*.sln");
foreach (@files) {
$slnFile = $_;
printf(" Found: %s\n", $slnFile) if ($Verbose > 1);
}
if ($slnFile eq "") {
printf(" *** No Solution Files found ...\n") if ($Verbose == 1);
return;
}
my $inoFile = $slnFile;
$inoFile =~ s/(.*)\.sln/$1\.ino/;
if (!-e $inoFile) {
printf(" *** %s not found ...\n", $inoFile) if ($Verbose == 1);
return;
}
printf(" Search %s\n", $inoFile) if ($Verbose > 1);
my $verString = GetVerString($inoFile);
if ($verString eq "") {
printf(" *** No Version string in %s ...\n", $inoFile) if ($Verbose == 1);
return;
}
printf(" Version '%s'\n", $verString) if ($Verbose > 1);
my $targBin = $slnFile;
$targBin =~ s/(.*)\.sln/$1\.bin/;
printf(" Target Bin file is %s\n", $targBin) if ($Verbose > 1);
my @Folders = qw(Debug Release);
foreach my $f (@Folders) {
printf(" Scanning %s\n", $f) if ($Verbose > 1);
my $tF = "$f\\$targBin";
if (-e $tF) {
printf(" Processing %s\n", $tF) if ($Verbose > 1);
my $srvrFile = "$ServerPath\\$verString.bin";
my $cmd = sprintf("move /Y \"%s\" \"%s\"", $tF, $srvrFile);
printf(" > %s\n", $cmd);
`$cmd`;
}
}
}
sub Pause {
my $dly = shift;
my $count = 0;
while ($count++ < $dly && $continue) {
sleep(1);
}
}
sub GetVerString {
my $iF = shift;
my $ver = "";
open(IF, "<$iF") || return $ver;
#const String MyVer = "SmartSwitch v1.02.24";
while (<IF>) {
if (/String MyVer = \"(.*)\"/) {
$ver = $1;
last;
#close IF;
#return $ver;
}
}
close IF;
return $ver;
}