@ -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_VERSION "0.9.6" | |||
#define APP_VERSION "0.9.7" | |||
#define APP_AUTHOR "xose.perez@gmail.com" | |||
#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(); | |||
} | |||
} | |||
} |