@ -0,0 +1,28 @@ | |||||
/* | |||||
ESPurna | |||||
BUTTON MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include <DebounceEvent.h> | |||||
DebounceEvent button1 = false; | |||||
// ----------------------------------------------------------------------------- | |||||
// BUTTON | |||||
// ----------------------------------------------------------------------------- | |||||
void buttonSetup() { | |||||
button1 = DebounceEvent(BUTTON_PIN); | |||||
} | |||||
void buttonLoop() { | |||||
if (button1.loop()) { | |||||
if (button1.getEvent() == EVENT_SINGLE_CLICK) toggleRelay(); | |||||
if (button1.getEvent() == EVENT_LONG_CLICK) wifiAP(); | |||||
if (button1.getEvent() == EVENT_DOUBLE_CLICK) ESP.reset(); | |||||
} | |||||
} |
@ -0,0 +1,84 @@ | |||||
// Managed from platformio.ini | |||||
//#define DEBUG | |||||
//#define ESPURNA | |||||
//#define SONOFF | |||||
//#define SLAMPHER | |||||
//#define S20 | |||||
//#define NODEMCUV2 | |||||
//#define ENABLE_NOFUSS 1 | |||||
//#define ENABLE_EMON 1 | |||||
//#define ENABLE_RF 1 | |||||
//#define ENABLE_DHT 1 | |||||
#define BUTTON_PIN 0 | |||||
#define RELAY_PIN 12 | |||||
#ifdef ESPURNA | |||||
#define MANUFACTURER "TINKERMAN" | |||||
#define DEVICE "ESPURNA" | |||||
#define LED_PIN 13 | |||||
#endif | |||||
#ifdef SONOFF | |||||
#define MANUFACTURER "ITEAD" | |||||
#define DEVICE "SONOFF" | |||||
#define LED_PIN 13 | |||||
#endif | |||||
#ifdef SLAMPHER | |||||
#define MANUFACTURER "ITEAD" | |||||
#define DEVICE "SLAMPHER" | |||||
#define LED_PIN 13 | |||||
#endif | |||||
#ifdef S20 | |||||
#define MANUFACTURER "ITEAD" | |||||
#define DEVICE "S20" | |||||
#define LED_PIN 13 | |||||
#endif | |||||
#ifdef NODEMCUV2 | |||||
#define MANUFACTURER "NODEMCU" | |||||
#define DEVICE "LOLIN" | |||||
#define LED_PIN 16 | |||||
#endif | |||||
#define AP_PASS "fibonacci" | |||||
#define OTA_PASS "fibonacci" | |||||
#define OTA_PORT 8266 | |||||
#define BUFFER_SIZE 1024 | |||||
#define STATUS_UPDATE_INTERVAL 30000 | |||||
#define HEARTBEAT_INTERVAL 60000 | |||||
#define FS_VERSION_FILE "/fsversion" | |||||
#define WIFI_RECONNECT_INTERVAL 300000 | |||||
#define RF_PIN 14 | |||||
#define DHT_PIN 14 | |||||
#define DHT_UPDATE_INTERVAL 300000 | |||||
#define DHT_TYPE DHT22 | |||||
#define DHT_TIMING 11 | |||||
#define MQTT_RECONNECT_DELAY 10000 | |||||
#define MQTT_RETAIN true | |||||
#define MQTT_STATUS_TOPIC "" | |||||
#define MQTT_IP_TOPIC "/ip" | |||||
#define MQTT_VERSION_TOPIC "/version" | |||||
#define MQTT_FSVERSION_TOPIC "/fsversion" | |||||
#define MQTT_HEARTBEAT_TOPIC "/heartbeat" | |||||
#define MQTT_POWER_TOPIC "/power" | |||||
#define MQTT_TEMPERATURE_TOPIC "/temperature" | |||||
#define MQTT_HUMIDITY_TOPIC "/humidity" | |||||
#define EMON_CURRENT_PIN 0 | |||||
#define EMON_SAMPLES 1000 | |||||
#define EMON_INTERVAL 10000 | |||||
#define EMON_MEASUREMENTS 6 | |||||
#define EMON_ADC_BITS 10 | |||||
#define EMON_REFERENCE_VOLTAGE 1.0 | |||||
#define EMON_CURRENT_PRECISION 1 | |||||
#define EMON_CURRENT_OFFSET 0.25 |
@ -0,0 +1,73 @@ | |||||
/* | |||||
ESPurna | |||||
DHT MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if ENABLE_DHT | |||||
#include "DHT.h" | |||||
DHT dht(DHT_PIN, DHT_TYPE, DHT_TIMING); | |||||
double temperature; | |||||
double humidity; | |||||
// ----------------------------------------------------------------------------- | |||||
// DHT | |||||
// ----------------------------------------------------------------------------- | |||||
double getTemperature() { | |||||
return temperature; | |||||
} | |||||
double getHumidity() { | |||||
return humidity; | |||||
} | |||||
void dhtSetup() { | |||||
dht.begin(); | |||||
} | |||||
void dhtLoop() { | |||||
static unsigned long last_check = 0; | |||||
if (!mqttConnected()) return; | |||||
if ((last_check > 0) && ((millis() - last_check) < DHT_UPDATE_INTERVAL)) return; | |||||
last_check = millis(); | |||||
char buffer[10]; | |||||
temperature = dht.readTemperature(); | |||||
if (isnan(temperature)) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[DHT] Error reading temperature")); | |||||
#endif | |||||
} else { | |||||
dtostrf(temperature, 4, 1, buffer); | |||||
mqttSend((char *) MQTT_TEMPERATURE_TOPIC, buffer); | |||||
#ifdef DEBUG | |||||
Serial.print(F("[DHT] Temperature: ")); | |||||
Serial.println(temperature); | |||||
#endif | |||||
} | |||||
humidity = dht.readHumidity(); | |||||
if (isnan(humidity)) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[DHT] Error reading humidity")); | |||||
#endif | |||||
} else { | |||||
dtostrf(humidity, 4, 1, buffer); | |||||
mqttSend((char *) MQTT_HUMIDITY_TOPIC, buffer); | |||||
#ifdef DEBUG | |||||
Serial.print(F("[DHT] Humidity: ")); | |||||
Serial.println(humidity); | |||||
#endif | |||||
} | |||||
} | |||||
#endif |
@ -0,0 +1,85 @@ | |||||
/* | |||||
ESPurna | |||||
EMON MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if ENABLE_EMON | |||||
#include <EmonLiteESP.h> | |||||
EmonLiteESP power; | |||||
double current; | |||||
// ----------------------------------------------------------------------------- | |||||
// EMON | |||||
// ----------------------------------------------------------------------------- | |||||
double getCurrent() { | |||||
return current; | |||||
} | |||||
unsigned int currentCallback() { | |||||
return analogRead(EMON_CURRENT_PIN); | |||||
} | |||||
void powerMonitorSetup() { | |||||
power.initCurrent(currentCallback, EMON_ADC_BITS, EMON_REFERENCE_VOLTAGE, config.pwCurrentRatio.toFloat()); | |||||
power.setPrecision(EMON_CURRENT_PRECISION); | |||||
} | |||||
void powerMonitorLoop() { | |||||
static unsigned long next_measurement = millis(); | |||||
static byte measurements = 0; | |||||
static double max = 0; | |||||
static double min = 0; | |||||
static double sum = 0; | |||||
if (!mqttConnected()) return; | |||||
if (millis() > next_measurement) { | |||||
// Safety check: do not read current if relay is OFF | |||||
if (!digitalRead(RELAY_PIN)) { | |||||
current = 0; | |||||
} else { | |||||
current = power.getCurrent(EMON_SAMPLES); | |||||
current -= EMON_CURRENT_OFFSET; | |||||
if (current < 0) current = 0; | |||||
} | |||||
if (measurements == 0) { | |||||
max = min = current; | |||||
} else { | |||||
if (current > max) max = current; | |||||
if (current < min) min = current; | |||||
} | |||||
sum += current; | |||||
++measurements; | |||||
#ifdef DEBUG | |||||
Serial.print(F("[ENERGY] Power now: ")); | |||||
Serial.print(int(current * config.pwMainsVoltage.toFloat())); | |||||
Serial.println(F("W")); | |||||
#endif | |||||
if (measurements == EMON_MEASUREMENTS) { | |||||
char buffer[8]; | |||||
double power = (sum - max - min) * config.pwMainsVoltage.toFloat() / (measurements - 2); | |||||
sprintf(buffer, "%d", int(power)); | |||||
mqttSend((char *) MQTT_POWER_TOPIC, buffer); | |||||
sum = 0; | |||||
measurements = 0; | |||||
} | |||||
next_measurement += EMON_INTERVAL; | |||||
} | |||||
} | |||||
#endif |
@ -0,0 +1,228 @@ | |||||
/* | |||||
ESPurna | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
This program is free software: you can redistribute it and/or modify | |||||
it under the terms of the GNU General Public License as published by | |||||
the Free Software Foundation, either version 3 of the License, or | |||||
(at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
GNU General Public License for more details. | |||||
You should have received a copy of the GNU General Public License | |||||
along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
extern "C" { | |||||
#include "user_interface.h" | |||||
} | |||||
#include <Arduino.h> | |||||
#include "version.h" | |||||
#include "defaults.h" | |||||
#include "FS.h" | |||||
#include "Config.h" | |||||
// ----------------------------------------------------------------------------- | |||||
// Methods | |||||
// ----------------------------------------------------------------------------- | |||||
void getCompileTime(char * buffer) { | |||||
int day, month, year, hour, minute, second; | |||||
// parse date | |||||
String tmp = String(__DATE__); | |||||
day = tmp.substring(4,6).toInt(); | |||||
year = tmp.substring(7).toInt(); | |||||
tmp = tmp.substring(0,3); | |||||
if (tmp.equals("Jan")) month = 1; | |||||
if (tmp.equals("Feb")) month = 2; | |||||
if (tmp.equals("Mar")) month = 3; | |||||
if (tmp.equals("Apr")) month = 4; | |||||
if (tmp.equals("May")) month = 5; | |||||
if (tmp.equals("Jun")) month = 6; | |||||
if (tmp.equals("Jul")) month = 7; | |||||
if (tmp.equals("Aug")) month = 8; | |||||
if (tmp.equals("Sep")) month = 9; | |||||
if (tmp.equals("Oct")) month = 10; | |||||
if (tmp.equals("Nov")) month = 11; | |||||
if (tmp.equals("Dec")) month = 12; | |||||
// parse time | |||||
tmp = String(__TIME__); | |||||
hour = tmp.substring(0,2).toInt(); | |||||
minute = tmp.substring(3,5).toInt(); | |||||
second = tmp.substring(6,8).toInt(); | |||||
sprintf(buffer, "%d%02d%02d%02d%02d%02d", year, month, day, hour, minute, second); | |||||
buffer[14] = 0; | |||||
} | |||||
String getIdentifier() { | |||||
char identifier[20]; | |||||
sprintf(identifier, "%s_%06X", DEVICE, ESP.getChipId()); | |||||
return String(identifier); | |||||
} | |||||
void blink(unsigned long delayOff, unsigned long delayOn) { | |||||
static unsigned long next = millis(); | |||||
static bool status = HIGH; | |||||
if (next < millis()) { | |||||
status = !status; | |||||
digitalWrite(LED_PIN, status); | |||||
next += ((status) ? delayOff : delayOn); | |||||
} | |||||
} | |||||
void showStatus() { | |||||
if (wifiConnected()) { | |||||
blink(5000, 500); | |||||
} else { | |||||
blink(500, 500); | |||||
} | |||||
} | |||||
void hardwareSetup() { | |||||
Serial.begin(115200); | |||||
pinMode(LED_PIN, OUTPUT); | |||||
SPIFFS.begin(); | |||||
} | |||||
void getFSVersion(char * buffer) { | |||||
File h = SPIFFS.open(FS_VERSION_FILE, "r"); | |||||
if (!h) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[SPIFFS] Could not open file system version file.")); | |||||
#endif | |||||
strcpy(buffer, APP_VERSION); | |||||
return; | |||||
} | |||||
size_t size = h.size(); | |||||
h.readBytes(buffer, size - 1); | |||||
h.close(); | |||||
} | |||||
void hardwareLoop() { | |||||
showStatus(); | |||||
// Heartbeat | |||||
static unsigned long last_heartbeat = 0; | |||||
if (millis() - last_heartbeat > HEARTBEAT_INTERVAL) { | |||||
last_heartbeat = millis(); | |||||
mqttSend((char *) MQTT_HEARTBEAT_TOPIC, (char *) "1"); | |||||
#ifdef DEBUG | |||||
Serial.print(F("[BEAT] Free heap: ")); | |||||
Serial.println(ESP.getFreeHeap()); | |||||
#endif | |||||
} | |||||
} | |||||
// ----------------------------------------------------------------------------- | |||||
// Booting | |||||
// ----------------------------------------------------------------------------- | |||||
void welcome() { | |||||
char buffer[BUFFER_SIZE]; | |||||
getCompileTime(buffer); | |||||
Serial.println(); | |||||
Serial.println(); | |||||
Serial.print(APP_NAME); | |||||
Serial.print(F(" ")); | |||||
Serial.print(APP_VERSION); | |||||
Serial.print(F(" built ")); | |||||
Serial.println(buffer); | |||||
Serial.println(APP_AUTHOR); | |||||
Serial.println(APP_WEBSITE); | |||||
Serial.println(); | |||||
Serial.print(F("Device: ")); | |||||
Serial.println(getIdentifier()); | |||||
Serial.print(F("Last reset reason: ")); | |||||
Serial.println(ESP.getResetReason()); | |||||
Serial.print(F("Memory size: ")); | |||||
Serial.print(ESP.getFlashChipSize()); | |||||
Serial.println(F(" bytes")); | |||||
Serial.print(F("Free heap: ")); | |||||
Serial.print(ESP.getFreeHeap()); | |||||
Serial.println(F(" bytes")); | |||||
FSInfo fs_info; | |||||
if (SPIFFS.info(fs_info)) { | |||||
Serial.print(F("File system total size: ")); | |||||
Serial.print(fs_info.totalBytes); | |||||
Serial.println(F(" bytes")); | |||||
Serial.print(F("File system used size : ")); | |||||
Serial.print(fs_info.usedBytes); | |||||
Serial.println(F(" bytes")); | |||||
} | |||||
Serial.println(); | |||||
} | |||||
void setup() { | |||||
hardwareSetup(); | |||||
relaySetup(); | |||||
buttonSetup(); | |||||
delay(1000); | |||||
welcome(); | |||||
config.load(); | |||||
// At the moment I am overriding any possible hostname stored in EEPROM | |||||
// with the generated one until I have a way to change them from the | |||||
// configuration interface | |||||
config.hostname = getIdentifier(); | |||||
wifi_station_set_hostname((char *) config.hostname.c_str()); | |||||
wifiSetup(); | |||||
otaSetup(); | |||||
mqttSetup(); | |||||
webServerSetup(); | |||||
#if ENABLE_NOFUSS | |||||
nofussSetup(); | |||||
#endif | |||||
#if ENABLE_RF | |||||
rfSetup(); | |||||
#endif | |||||
#if ENABLE_DHT | |||||
dhtSetup(); | |||||
#endif | |||||
#if ENABLE_EMON | |||||
powerMonitorSetup(); | |||||
#endif | |||||
} | |||||
void loop() { | |||||
wifiLoop(); | |||||
hardwareLoop(); | |||||
buttonLoop(); | |||||
otaLoop(); | |||||
mqttLoop(); | |||||
webServerLoop(); | |||||
#if ENABLE_NOFUSS | |||||
nofussLoop(); | |||||
#endif | |||||
#if ENABLE_RF | |||||
rfLoop(); | |||||
#endif | |||||
#if ENABLE_DHT | |||||
dhtLoop(); | |||||
#endif | |||||
#if ENABLE_EMON | |||||
powerMonitorLoop(); | |||||
#endif | |||||
delay(1); | |||||
} |
@ -0,0 +1,161 @@ | |||||
/* | |||||
ESPurna | |||||
MQTT MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include <WiFiClient.h> | |||||
#include <PubSubClient.h> | |||||
WiFiClient client; | |||||
PubSubClient mqtt(client); | |||||
String mqttTopic; | |||||
bool isCallbackMessage = false; | |||||
// ----------------------------------------------------------------------------- | |||||
// MQTT | |||||
// ----------------------------------------------------------------------------- | |||||
bool mqttConnected() { | |||||
return mqtt.connected(); | |||||
} | |||||
void buildTopics() { | |||||
// Replace identifier | |||||
mqttTopic = config.mqttTopic; | |||||
mqttTopic.replace("{identifier}", config.hostname); | |||||
} | |||||
void mqttSend(char * topic, char * message) { | |||||
if (!mqtt.connected()) return; | |||||
if (isCallbackMessage) return; | |||||
String path = mqttTopic + String(topic); | |||||
#ifdef DEBUG | |||||
Serial.print(F("[MQTT] Sending ")); | |||||
Serial.print(path); | |||||
Serial.print(F(" ")); | |||||
Serial.println(message); | |||||
#endif | |||||
mqtt.publish(path.c_str(), message, MQTT_RETAIN); | |||||
} | |||||
void mqttCallback(char* topic, byte* payload, unsigned int length) { | |||||
#ifdef DEBUG | |||||
Serial.print(F("[MQTT] Received ")); | |||||
Serial.print(topic); | |||||
Serial.print(F(" ")); | |||||
for (int i = 0; i < length; i++) { | |||||
Serial.print((char)payload[i]); | |||||
} | |||||
Serial.println(); | |||||
#endif | |||||
// Action to perform | |||||
if ((char)payload[0] == '0') { | |||||
isCallbackMessage = true; | |||||
switchRelayOff(); | |||||
} | |||||
if ((char)payload[0] == '1') { | |||||
isCallbackMessage = true; | |||||
switchRelayOn(); | |||||
} | |||||
if ((char)payload[0] == '2') { | |||||
toggleRelay(); | |||||
} | |||||
isCallbackMessage = false; | |||||
} | |||||
void mqttConnect() { | |||||
if (!mqtt.connected() && (config.mqttServer.length()>0)) { | |||||
mqtt.setServer((const char *) config.mqttServer.c_str(), config.mqttPort.toInt()); | |||||
#ifdef DEBUG | |||||
Serial.print(F("[MQTT] Connecting to broker at ")); | |||||
Serial.print(config.mqttServer); | |||||
#endif | |||||
if (config.mqttUser.length() > 0) { | |||||
#ifdef DEBUG | |||||
Serial.print(F(" as user ")); | |||||
Serial.print(config.mqttUser); | |||||
Serial.print(F(": ")); | |||||
#endif | |||||
mqtt.connect( | |||||
config.hostname.c_str(), | |||||
(const char *) config.mqttUser.c_str(), | |||||
(const char *) config.mqttPassword.c_str() | |||||
); | |||||
} else { | |||||
#ifdef DEBUG | |||||
Serial.print(F(" anonymously: ")); | |||||
#endif | |||||
mqtt.connect(config.hostname.c_str()); | |||||
} | |||||
if (mqtt.connected()) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("connected!")); | |||||
#endif | |||||
buildTopics(); | |||||
// Say hello and report our IP and VERSION | |||||
mqttSend((char *) MQTT_IP_TOPIC, (char *) getIP().c_str()); | |||||
mqttSend((char *) MQTT_VERSION_TOPIC, (char *) APP_VERSION); | |||||
char buffer[10]; | |||||
getFSVersion(buffer); | |||||
mqttSend((char *) MQTT_FSVERSION_TOPIC, buffer); | |||||
// Publish current relay status | |||||
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) (digitalRead(RELAY_PIN) ? "1" : "0")); | |||||
// Subscribe to topic | |||||
#ifdef DEBUG | |||||
Serial.print(F("[MQTT] Subscribing to ")); | |||||
Serial.println(mqttTopic); | |||||
#endif | |||||
mqtt.subscribe(mqttTopic.c_str()); | |||||
} else { | |||||
#ifdef DEBUG | |||||
Serial.print(F("failed, rc=")); | |||||
Serial.println(mqtt.state()); | |||||
#endif | |||||
} | |||||
} | |||||
} | |||||
void mqttSetup() { | |||||
mqtt.setCallback(mqttCallback); | |||||
} | |||||
void mqttLoop() { | |||||
static unsigned long timeout = millis(); | |||||
if (wifiConnected()) { | |||||
if (!mqtt.connected()) { | |||||
if (timeout < millis()) { | |||||
mqttConnect(); | |||||
timeout = millis() + MQTT_RECONNECT_DELAY; | |||||
} | |||||
} | |||||
if (mqtt.connected()) mqtt.loop(); | |||||
} | |||||
} |
@ -0,0 +1,88 @@ | |||||
/* | |||||
ESPurna | |||||
NOFUSS MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if ENABLE_NOFUSS | |||||
#include "NoFUSSClient.h" | |||||
// ----------------------------------------------------------------------------- | |||||
// NOFUSS | |||||
// ----------------------------------------------------------------------------- | |||||
void nofussSetup() { | |||||
NoFUSSClient.setServer(config.nofussServer); | |||||
NoFUSSClient.setDevice(DEVICE); | |||||
NoFUSSClient.setVersion(APP_VERSION); | |||||
NoFUSSClient.onMessage([](nofuss_t code) { | |||||
if (code == NOFUSS_START) { | |||||
Serial.println(F("[NoFUSS] Start")); | |||||
} | |||||
if (code == NOFUSS_UPTODATE) { | |||||
Serial.println(F("[NoFUSS] Already in the last version")); | |||||
} | |||||
if (code == NOFUSS_PARSE_ERROR) { | |||||
Serial.println(F("[NoFUSS] Error parsing server response")); | |||||
} | |||||
if (code == NOFUSS_UPDATING) { | |||||
Serial.println(F("[NoFUSS] Updating")); | |||||
Serial.print( F(" New version: ")); | |||||
Serial.println(NoFUSSClient.getNewVersion()); | |||||
Serial.print( F(" Firmware: ")); | |||||
Serial.println(NoFUSSClient.getNewFirmware()); | |||||
Serial.print( F(" File System: ")); | |||||
Serial.println(NoFUSSClient.getNewFileSystem()); | |||||
} | |||||
if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) { | |||||
Serial.print(F("[NoFUSS] File System Update Error: ")); | |||||
Serial.println(NoFUSSClient.getErrorString()); | |||||
} | |||||
if (code == NOFUSS_FILESYSTEM_UPDATED) { | |||||
Serial.println(F("[NoFUSS] File System Updated")); | |||||
} | |||||
if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) { | |||||
Serial.print(F("[NoFUSS] Firmware Update Error: ")); | |||||
Serial.println(NoFUSSClient.getErrorString()); | |||||
} | |||||
if (code == NOFUSS_FIRMWARE_UPDATED) { | |||||
Serial.println(F("[NoFUSS] Firmware Updated")); | |||||
} | |||||
if (code == NOFUSS_RESET) { | |||||
Serial.println(F("[NoFUSS] Resetting board")); | |||||
} | |||||
if (code == NOFUSS_END) { | |||||
Serial.println(F("[NoFUSS] End")); | |||||
} | |||||
}); | |||||
} | |||||
void nofussLoop() { | |||||
static unsigned long last_check = 0; | |||||
if (!wifiConnected()) return; | |||||
if ((last_check > 0) && ((millis() - last_check) < config.nofussInterval.toInt())) return; | |||||
last_check = millis(); | |||||
NoFUSSClient.handle(); | |||||
} | |||||
#endif |
@ -0,0 +1,64 @@ | |||||
/* | |||||
ESPurna | |||||
OTA MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include <ArduinoJson.h> | |||||
#include "ArduinoOTA.h" | |||||
// ----------------------------------------------------------------------------- | |||||
// OTA | |||||
// ----------------------------------------------------------------------------- | |||||
void otaSetup() { | |||||
ArduinoOTA.setPort(OTA_PORT); | |||||
ArduinoOTA.setHostname(config.hostname.c_str()); | |||||
ArduinoOTA.setPassword((const char *) OTA_PASS); | |||||
ArduinoOTA.onStart([]() { | |||||
#if ENABLE_RF | |||||
rfEnable(false); | |||||
#endif | |||||
#if DEBUG | |||||
Serial.println(F("[OTA] Start")); | |||||
#endif | |||||
}); | |||||
ArduinoOTA.onEnd([]() { | |||||
#if DEBUG | |||||
Serial.println(F("[OTA] End")); | |||||
#endif | |||||
#if ENABLE_RF | |||||
rfEnable(true); | |||||
#endif | |||||
}); | |||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |||||
#if DEBUG | |||||
Serial.printf("[OTA] Progress: %u%%\r", (progress / (total / 100))); | |||||
#endif | |||||
}); | |||||
ArduinoOTA.onError([](ota_error_t error) { | |||||
#if DEBUG | |||||
Serial.printf("[OTA] Error[%u]: ", error); | |||||
if (error == OTA_AUTH_ERROR) Serial.println(F("[OTA] Auth Failed")); | |||||
else if (error == OTA_BEGIN_ERROR) Serial.println(F("[OTA] Begin Failed")); | |||||
else if (error == OTA_CONNECT_ERROR) Serial.println(F("[OTA] Connect Failed")); | |||||
else if (error == OTA_RECEIVE_ERROR) Serial.println(F("[OTA] Receive Failed")); | |||||
else if (error == OTA_END_ERROR) Serial.println(F("[OTA] End Failed")); | |||||
#endif | |||||
}); | |||||
ArduinoOTA.begin(); | |||||
} | |||||
void otaLoop() { | |||||
ArduinoOTA.handle(); | |||||
} |
@ -0,0 +1,62 @@ | |||||
/* | |||||
ESPurna | |||||
RELAY MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include <EEPROM.h> | |||||
// ----------------------------------------------------------------------------- | |||||
// RELAY | |||||
// ----------------------------------------------------------------------------- | |||||
void switchRelayOn() { | |||||
if (!digitalRead(RELAY_PIN)) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[RELAY] ON")); | |||||
#endif | |||||
digitalWrite(RELAY_PIN, HIGH); | |||||
EEPROM.write(0, 1); | |||||
EEPROM.commit(); | |||||
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "1"); | |||||
} | |||||
} | |||||
void switchRelayOff() { | |||||
if (digitalRead(RELAY_PIN)) { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[RELAY] OFF")); | |||||
#endif | |||||
digitalWrite(RELAY_PIN, LOW); | |||||
EEPROM.write(0, 0); | |||||
EEPROM.commit(); | |||||
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "0"); | |||||
} | |||||
} | |||||
void toggleRelay() { | |||||
if (digitalRead(RELAY_PIN)) { | |||||
switchRelayOff(); | |||||
} else { | |||||
switchRelayOn(); | |||||
} | |||||
} | |||||
void relaySetup() { | |||||
pinMode(RELAY_PIN, OUTPUT); | |||||
EEPROM.begin(4096); | |||||
EEPROM.read(0) == 1 ? switchRelayOn() : switchRelayOff(); | |||||
} |
@ -0,0 +1,85 @@ | |||||
/* | |||||
ESPurna | |||||
RF MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if ENABLE_RF | |||||
#include <RemoteReceiver.h> | |||||
unsigned long rfCode = 0; | |||||
unsigned long rfCodeON = 0; | |||||
unsigned long rfCodeOFF = 0; | |||||
// ----------------------------------------------------------------------------- | |||||
// RF | |||||
// ----------------------------------------------------------------------------- | |||||
void rfEnable(bool enable) { | |||||
if (enable) { | |||||
RemoteReceiver::enable(); | |||||
} else { | |||||
RemoteReceiver::disable(); | |||||
} | |||||
} | |||||
void rfLoop() { | |||||
if (rfCode == 0) return; | |||||
#ifdef DEBUG | |||||
Serial.print(F("[RF] Received code: ")); | |||||
Serial.println(rfCode); | |||||
#endif | |||||
if (rfCode == rfCodeON) switchRelayOn(); | |||||
if (rfCode == rfCodeOFF) switchRelayOff(); | |||||
rfCode = 0; | |||||
} | |||||
void rfBuildCodes() { | |||||
unsigned long code = 0; | |||||
// channel | |||||
unsigned int channel = config.rfChannel.toInt(); | |||||
for (byte i = 0; i < 5; i++) { | |||||
code *= 3; | |||||
if (channel & 1) code += 1; | |||||
channel >>= 1; | |||||
} | |||||
// device | |||||
unsigned int device = config.rfDevice.toInt(); | |||||
for (byte i = 0; i < 5; i++) { | |||||
code *= 3; | |||||
if (device != i) code += 2; | |||||
} | |||||
// status | |||||
code *= 9; | |||||
rfCodeOFF = code + 2; | |||||
rfCodeON = code + 6; | |||||
#ifdef DEBUG | |||||
Serial.print(F("[RF] Code ON: ")); | |||||
Serial.println(rfCodeON); | |||||
Serial.print(F("[RF] Code OFF: ")); | |||||
Serial.println(rfCodeOFF); | |||||
#endif | |||||
} | |||||
void rfCallback(unsigned long code, unsigned int period) { | |||||
rfCode = code; | |||||
} | |||||
void rfSetup() { | |||||
pinMode(RF_PIN, INPUT_PULLUP); | |||||
rfBuildCodes(); | |||||
RemoteReceiver::init(RF_PIN, 3, rfCallback); | |||||
RemoteReceiver::enable(); | |||||
} | |||||
#endif |
@ -1,4 +1,4 @@ | |||||
#define APP_NAME "Espurna" | #define APP_NAME "Espurna" | ||||
#define APP_VERSION "0.9.6" | |||||
#define APP_VERSION "0.9.7" | |||||
#define APP_AUTHOR "xose.perez@gmail.com" | #define APP_AUTHOR "xose.perez@gmail.com" | ||||
#define APP_WEBSITE "http://tinkerman.cat" | #define APP_WEBSITE "http://tinkerman.cat" |
@ -0,0 +1,243 @@ | |||||
/* | |||||
ESPurna | |||||
WEBSERVER MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include <ESP8266WebServer.h> | |||||
#include <ESP8266mDNS.h> | |||||
#include "FS.h" | |||||
ESP8266WebServer server(80); | |||||
// ----------------------------------------------------------------------------- | |||||
// WEBSERVER | |||||
// ----------------------------------------------------------------------------- | |||||
String getContentType(String filename) { | |||||
if (server.hasArg("download")) return "application/octet-stream"; | |||||
else if (filename.endsWith(".htm")) return "text/html"; | |||||
else if (filename.endsWith(".html")) return "text/html"; | |||||
else if (filename.endsWith(".css")) return "text/css"; | |||||
else if (filename.endsWith(".js")) return "application/javascript"; | |||||
else if (filename.endsWith(".png")) return "image/png"; | |||||
else if (filename.endsWith(".gif")) return "image/gif"; | |||||
else if (filename.endsWith(".jpg")) return "image/jpeg"; | |||||
else if (filename.endsWith(".ico")) return "image/x-icon"; | |||||
else if (filename.endsWith(".xml")) return "text/xml"; | |||||
else if (filename.endsWith(".pdf")) return "application/x-pdf"; | |||||
else if (filename.endsWith(".zip")) return "application/x-zip"; | |||||
else if (filename.endsWith(".gz")) return "application/x-gzip"; | |||||
return "text/plain"; | |||||
} | |||||
void handleRelayOn() { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[WEBSERVER] Request: /relay/on")); | |||||
#endif | |||||
switchRelayOn(); | |||||
server.send(200, "text/plain", "ON"); | |||||
} | |||||
void handleRelayOff() { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[WEBSERVER] Request: /relay/off")); | |||||
#endif | |||||
switchRelayOff(); | |||||
server.send(200, "text/plain", "OFF"); | |||||
} | |||||
bool handleFileRead(String path) { | |||||
#ifdef DEBUG | |||||
Serial.print(F("[WEBSERVER] Request: ")); | |||||
Serial.println(path); | |||||
#endif | |||||
if (path.endsWith("/")) path += "index.html"; | |||||
String contentType = getContentType(path); | |||||
String pathWithGz = path + ".gz"; | |||||
if (SPIFFS.exists(pathWithGz)) path = pathWithGz; | |||||
if (SPIFFS.exists(path)) { | |||||
File file = SPIFFS.open(path, "r"); | |||||
size_t sent = server.streamFile(file, contentType); | |||||
size_t contentLength = file.size(); | |||||
file.close(); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
void handleInit() { | |||||
#ifdef DEBUG | |||||
Serial.println("[WEBSERVER] Request: /init"); | |||||
#endif | |||||
char buffer[64]; | |||||
char built[16]; | |||||
getCompileTime(built); | |||||
sprintf(buffer, "%s %s built %s", APP_NAME, APP_VERSION, built); | |||||
StaticJsonBuffer<1024> jsonBuffer; | |||||
JsonObject& root = jsonBuffer.createObject(); | |||||
root["appname"] = String(buffer); | |||||
root["manufacturer"] = String(MANUFACTURER); | |||||
root["device"] = String(DEVICE); | |||||
root["hostname"] = config.hostname; | |||||
root["network"] = (WiFi.status() == WL_CONNECTED) ? WiFi.SSID() : "ACCESS POINT"; | |||||
root["ip"] = (WiFi.status() == WL_CONNECTED) ? WiFi.localIP().toString() : WiFi.softAPIP().toString(); | |||||
root["updateInterval"] = STATUS_UPDATE_INTERVAL; | |||||
root["ssid0"] = config.ssid[0]; | |||||
root["pass0"] = config.pass[0]; | |||||
root["ssid1"] = config.ssid[1]; | |||||
root["pass1"] = config.pass[1]; | |||||
root["ssid2"] = config.ssid[2]; | |||||
root["pass2"] = config.pass[2]; | |||||
root["mqttServer"] = config.mqttServer; | |||||
root["mqttPort"] = config.mqttPort; | |||||
root["mqttUser"] = config.mqttUser; | |||||
root["mqttPassword"] = config.mqttPassword; | |||||
root["mqttTopic"] = config.mqttTopic; | |||||
#if ENABLE_RF | |||||
root["rfChannel"] = config.rfChannel; | |||||
root["rfDevice"] = config.rfDevice; | |||||
#endif | |||||
#if ENABLE_EMON | |||||
root["pwMainsVoltage"] = config.pwMainsVoltage; | |||||
root["pwCurrentRatio"] = config.pwCurrentRatio; | |||||
#endif | |||||
String output; | |||||
root.printTo(output); | |||||
server.send(200, "text/json", output); | |||||
} | |||||
void handleStatus() { | |||||
// Update reconnection timeout to avoid disconnecting the web client | |||||
resetConnectionTimeout(); | |||||
#ifdef DEBUG | |||||
//Serial.println("[WEBSERVER] Request: /status"); | |||||
#endif | |||||
StaticJsonBuffer<256> jsonBuffer; | |||||
JsonObject& root = jsonBuffer.createObject(); | |||||
root["relay"] = digitalRead(RELAY_PIN) ? 1: 0; | |||||
root["mqtt"] = mqtt.connected() ? 1: 0; | |||||
#if ENABLE_EMON | |||||
root["power"] = getCurrent() * config.pwMainsVoltage.toFloat(); | |||||
#endif | |||||
#if ENABLE_DHT | |||||
root["temperature"] = getTemperature(); | |||||
root["humidity"] = getHumidity(); | |||||
#endif | |||||
String output; | |||||
root.printTo(output); | |||||
server.send(200, "text/json", output); | |||||
} | |||||
void handleSave() { | |||||
#ifdef DEBUG | |||||
Serial.println(F("[WEBSERVER] Request: /save")); | |||||
#endif | |||||
if (server.hasArg("status")) { | |||||
if (server.arg("status") == "1") { | |||||
switchRelayOn(); | |||||
} else { | |||||
switchRelayOff(); | |||||
} | |||||
} | |||||
if (server.hasArg("ssid0")) config.ssid[0] = server.arg("ssid0"); | |||||
if (server.hasArg("pass0")) config.pass[0] = server.arg("pass0"); | |||||
if (server.hasArg("ssid1")) config.ssid[1] = server.arg("ssid1"); | |||||
if (server.hasArg("pass1")) config.pass[1] = server.arg("pass1"); | |||||
if (server.hasArg("ssid2")) config.ssid[2] = server.arg("ssid2"); | |||||
if (server.hasArg("pass2")) config.pass[2] = server.arg("pass2"); | |||||
if (server.hasArg("mqttServer")) config.mqttServer = server.arg("mqttServer"); | |||||
if (server.hasArg("mqttPort")) config.mqttPort = server.arg("mqttPort"); | |||||
if (server.hasArg("mqttUser")) config.mqttUser = server.arg("mqttUser"); | |||||
if (server.hasArg("mqttPassword")) config.mqttPassword = server.arg("mqttPassword"); | |||||
if (server.hasArg("mqttTopic")) config.mqttTopic = server.arg("mqttTopic"); | |||||
#if ENABLE_RF | |||||
if (server.hasArg("rfChannel")) config.rfChannel = server.arg("rfChannel"); | |||||
if (server.hasArg("rfDevice")) config.rfDevice = server.arg("rfDevice"); | |||||
#endif | |||||
#if ENABLE_EMON | |||||
if (server.hasArg("pwMainsVoltage")) config.pwMainsVoltage = server.arg("pwMainsVoltage"); | |||||
if (server.hasArg("pwCurrentRatio")) config.pwCurrentRatio = server.arg("pwCurrentRatio"); | |||||
#endif | |||||
server.send(202, "text/json", "{}"); | |||||
config.save(); | |||||
#if ENABLE_RF | |||||
rfBuildCodes(); | |||||
#endif | |||||
#if ENABLE_EMON | |||||
power.setCurrentRatio(config.pwCurrentRatio.toFloat()); | |||||
#endif | |||||
// Disconnect from current WIFI network, wifiLoop will take care of the reconnection | |||||
wifiDisconnect(); | |||||
} | |||||
void webServerSetup() { | |||||
// Relay control | |||||
server.on("/relay/on", HTTP_GET, handleRelayOn); | |||||
server.on("/relay/off", HTTP_GET, handleRelayOff); | |||||
// Configuration page | |||||
server.on("/init", HTTP_GET, handleInit); | |||||
server.on("/status", HTTP_GET, handleStatus); | |||||
server.on("/save", HTTP_POST, handleSave); | |||||
// Anything else | |||||
server.onNotFound([]() { | |||||
// Hidden files | |||||
#ifndef DEBUG | |||||
if (server.uri().startsWith("/.")) { | |||||
server.send(403, "text/plain", "Forbidden"); | |||||
return; | |||||
} | |||||
#endif | |||||
// Existing files in SPIFFS | |||||
if (!handleFileRead(server.uri())) { | |||||
server.send(404, "text/plain", "NotFound"); | |||||
return; | |||||
} | |||||
}); | |||||
// Run server | |||||
server.begin(); | |||||
} | |||||
void webServerLoop() { | |||||
server.handleClient(); | |||||
} |
@ -0,0 +1,162 @@ | |||||
/* | |||||
ESPurna | |||||
WIFI MODULE | |||||
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#include "JustWifi.h" | |||||
JustWifi jw; | |||||
unsigned long wifiLastConnectionTime = 0; | |||||
// ----------------------------------------------------------------------------- | |||||
// WIFI | |||||
// ----------------------------------------------------------------------------- | |||||
String getIP() { | |||||
return jw.getIP(); | |||||
} | |||||
String getNetwork() { | |||||
return jw.getNetwork(); | |||||
} | |||||
void wifiDisconnect() { | |||||
jw.disconnect(); | |||||
} | |||||
void resetConnectionTimeout() { | |||||
wifiLastConnectionTime = millis(); | |||||
} | |||||
bool wifiConnected() { | |||||
return jw.connected(); | |||||
} | |||||
void wifiSetup() { | |||||
// Message callbacks | |||||
jw.onMessage([](justwifi_messages_t code, char * parameter) { | |||||
// Disconnect from MQTT server if no WIFI | |||||
if (code != MESSAGE_CONNECTED) { | |||||
if (mqtt.connected()) mqtt.disconnect(); | |||||
} | |||||
#if DEBUG | |||||
if (code == MESSAGE_AUTO_NOSSID) { | |||||
Serial.println("[WIFI] No information about the last successful network"); | |||||
} | |||||
if (code == MESSAGE_AUTO_CONNECTING) { | |||||
Serial.print("[WIFI] Connecting to last successful network: "); | |||||
Serial.println(parameter); | |||||
} | |||||
if (code == MESSAGE_AUTO_FAILED) { | |||||
Serial.println("[WIFI] Could not connect to last successful network"); | |||||
} | |||||
if (code == MESSAGE_CONNECTING) { | |||||
Serial.print("[WIFI] Connecting to "); | |||||
Serial.println(parameter); | |||||
} | |||||
if (code == MESSAGE_CONNECT_WAITING) { | |||||
// | |||||
} | |||||
if (code == MESSAGE_CONNECT_FAILED) { | |||||
Serial.print("[WIFI] Could not connect to "); | |||||
Serial.println(parameter); | |||||
} | |||||
if (code == MESSAGE_CONNECTED) { | |||||
Serial.print("[WIFI] Connected to "); | |||||
Serial.print(jw.getNetwork()); | |||||
Serial.print(" with IP "); | |||||
Serial.println(jw.getIP()); | |||||
} | |||||
if (code == MESSAGE_DISCONNECTED) { | |||||
Serial.println("[WIFI] Disconnected"); | |||||
} | |||||
if (code == MESSAGE_ACCESSPOINT_CREATING) { | |||||
Serial.println("[WIFI] Creating access point"); | |||||
} | |||||
if (code == MESSAGE_ACCESSPOINT_CREATED) { | |||||
Serial.print("[WIFI] Access point created with SSID "); | |||||
Serial.print(jw.getNetwork()); | |||||
Serial.print(" and IP "); | |||||
Serial.println(jw.getIP()); | |||||
} | |||||
if (code == MESSAGE_ACCESSPOINT_FAILED) { | |||||
Serial.println("[WIFI] Could not create access point"); | |||||
} | |||||
#endif | |||||
}); | |||||
} | |||||
bool wifiAP() { | |||||
//jw.disconnect(); | |||||
return jw.startAP((char *) config.hostname.c_str(), (char *) AP_PASS); | |||||
} | |||||
void wifiConnect() { | |||||
resetConnectionTimeout(); | |||||
//WiFi.printDiag(Serial); | |||||
// Set networks | |||||
jw.cleanNetworks(); | |||||
jw.addNetwork((char *) config.ssid[0].c_str(), (char *) config.pass[0].c_str()); | |||||
jw.addNetwork((char *) config.ssid[1].c_str(), (char *) config.pass[1].c_str()); | |||||
jw.addNetwork((char *) config.ssid[2].c_str(), (char *) config.pass[2].c_str()); | |||||
// Connecting | |||||
if (!jw.autoConnect()) { | |||||
if (!jw.connect()) { | |||||
if (!wifiAP()) { | |||||
#if DEBUG | |||||
Serial.println("[WIFI] Could not start any wifi interface!"); | |||||
#endif | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void wifiLoop() { | |||||
jw.loop(); | |||||
// Check disconnection | |||||
if (!jw.connected()) { | |||||
// If we are in AP mode try to reconnect every WIFI_RECONNECT_INTERVAL | |||||
// wifiLastConnectionTime gets updated upon every connect try or when | |||||
// the webserver is hit by a request to avoid web clients to be | |||||
// disconnected while configuring the board | |||||
if (jw.getMode() == MODE_ACCESS_POINT) { | |||||
if (millis() - wifiLastConnectionTime > WIFI_RECONNECT_INTERVAL) { | |||||
wifiConnect(); | |||||
} | |||||
// else reconnect right away | |||||
} else { | |||||
wifiConnect(); | |||||
} | |||||
} | |||||
} |