This commit is contained in:
Manuel Weiser 2018-06-06 15:28:17 +02:00
commit aece7ca1b0
124 changed files with 39029 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json

BIN
.pioenvs Alias Normal file

Binary file not shown.

67
.travis.yml Normal file
View File

@ -0,0 +1,67 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < http://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < http://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < http://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choice one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to by used as a library with examples
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

1
Firmware Symbolic link
View File

@ -0,0 +1 @@
.pioenvs

18
astyle.conf Executable file
View File

@ -0,0 +1,18 @@
style=attach
indent=spaces=4
indent-classes
indent-switches
indent-namespaces
indent-continuation=4
#indent-preproc-define
#indent-preproc-cond
#indent-preproc-block
indent-col1-comments
pad-oper
pad-comma
pad-header
align-pointer=type
align-reference=type
add-brackets
convert-tabs
suffix=none

72
build.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
# Welcome
echo "--------------------------------------------------------------"
echo "ESPURNA FIRMWARE BUILDER"
# Available environments
travis=$(grep env: platformio.ini | grep travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
available=$(grep env: platformio.ini | grep -v ota | grep -v ssl | grep -v travis | sed 's/\[env://' | sed 's/\]/ /' | sort)
# Parameters
environments=$@
if [ "$environments" == "list" ]; then
echo "--------------------------------------------------------------"
echo "Available environments:"
for environment in $available; do
echo "* $environment"
done
exit
fi
# Environments to build
if [ $# -eq 0 ]; then
environments=$available
# Hook to build travis test envs
if [[ "${TRAVIS_BRANCH}" != "" ]]; then
if [[ ${TRAVIS_BRANCH} != "master" ]]; then
environments=$travis
fi
fi
fi
# Get current version
version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g')
echo "Building for version $version"
# Create output folder
mkdir -p firmware
if [ ! -e node_modules/gulp/bin/gulp.js ]; then
echo "--------------------------------------------------------------"
echo "Installing dependencies..."
npm install --only=dev
fi
echo "--------------------------------------------------------------"
echo "Get revision..."
revision=$(git rev-parse HEAD)
revision=${revision:0:7}
cp espurna/config/version.h espurna/config/version.h.original
sed -i -e "s/APP_REVISION \".*\"/APP_REVISION \"$revision\"/g" espurna/config/version.h
# Recreate web interface
echo "--------------------------------------------------------------"
echo "Building web interface..."
node node_modules/gulp/bin/gulp.js || exit
# Build all the required firmware images
echo "--------------------------------------------------------------"
echo "Building firmware images..."
mkdir -p ../firmware/espurna-$version
for environment in $environments; do
echo "* espurna-$version-$environment.bin"
platformio run --silent --environment $environment || break
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
done
echo "--------------------------------------------------------------"
mv espurna/config/version.h.original espurna/config/version.h

75
debug.sh Executable file
View File

@ -0,0 +1,75 @@
#!/bin/bash
# ------------------------------------------------------------------------------
# CONFIGURATION
# ------------------------------------------------------------------------------
ENVIRONMENT="nodemcu-lolin"
ADDR2LINE=$HOME/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-addr2line
DECODER=utils/EspStackTraceDecoder.jar
DECODER_ORIGIN=https://github.com/littleyoda/EspStackTraceDecoder/releases/download/untagged-83b6db3208da17a0f1fd/EspStackTraceDecoder.jar
FILE="/tmp/.trace"
# ------------------------------------------------------------------------------
# END CONFIGURATION - DO NOT EDIT FURTHER
# ------------------------------------------------------------------------------
# remove default trace file
rm -rf $FILE
function help {
echo
echo "Syntax: $0 [-e <environment>] [-d <dumpfile>]"
echo
}
# get environment from command line
while [[ $# -gt 1 ]]; do
key="$1"
case $key in
-e)
ENVIRONMENT="$2"
shift
;;
-d)
FILE="$2"
shift
;;
esac
shift # past argument or value
done
# check environment folder
if [ $ENVIRONMENT == "" ]; then
echo "No environment defined"
help
exit 1
fi
ELF=.pioenvs/$ENVIRONMENT/firmware.elf
if [ ! -f $ELF ]; then
echo "Could not find ELF file for the selected environment: $ELF"
exit 2
fi
# get decode
if [ ! -f $DECODER ]; then
folder=$(dirname "$DECODER")
if [ $folder != "." ]; then
mkdir -p $folder
fi
echo "Downloading decoder..."
wget -q $DECODER_ORIGIN -O "$DECODER"
fi
# get trace interactively
if [ ! -f $FILE ]; then
echo "Paste stack trace and end with a blank line:"
trace=$(sed '/^$/q')
echo $trace > $FILE
fi
java -jar $DECODER $ADDR2LINE $ELF $FILE

18
esp8266.flash.1m0.ld Executable file
View File

@ -0,0 +1,18 @@
/* Flash Split for 1M chips, no SPIFFS */
/* sketch 999KB */
/* eeprom 20KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xf9ff0
}
PROVIDE ( _SPIFFS_start = 0x402FB000 );
PROVIDE ( _SPIFFS_end = 0x402FB000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
INCLUDE "esp8266.flash.common.ld"

95
espurna/alexa.ino Executable file
View File

@ -0,0 +1,95 @@
/*
ALEXA MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if ALEXA_SUPPORT
#include <fauxmoESP.h>
fauxmoESP alexa;
struct AlexaDevChange {
AlexaDevChange(unsigned char device_id, bool state) : device_id(device_id), state(state) {};
unsigned char device_id = 0;
bool state = false;
};
#include <queue>
static std::queue<AlexaDevChange> _alexa_dev_changes;
// -----------------------------------------------------------------------------
// ALEXA
// -----------------------------------------------------------------------------
bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "alexa", 5) == 0);
}
void _alexaWebSocketOnSend(JsonObject& root) {
root["alexaVisible"] = 1;
root["alexaEnabled"] = getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1;
}
void _alexaConfigure() {
alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
}
// -----------------------------------------------------------------------------
void alexaSetup() {
// Backwards compatibility
moveSetting("fauxmoEnabled", "alexaEnabled");
// Load & cache settings
_alexaConfigure();
#if WEB_SUPPORT
// Websockets
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnAfterParseRegister(_alexaConfigure);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
#endif
unsigned int relays = relayCount();
String hostname = getSetting("hostname");
if (relays == 1) {
alexa.addDevice(hostname.c_str());
} else {
for (unsigned int i=0; i<relays; i++) {
alexa.addDevice((hostname + "_" + i).c_str());
}
}
alexa.onSetState([&](unsigned char device_id, const char * name, bool state) {
AlexaDevChange change(device_id, state);
_alexa_dev_changes.push(change);
});
alexa.onGetState([](unsigned char device_id, const char * name) {
return relayStatus(device_id);
});
// Register loop
espurnaRegisterLoop(alexaLoop);
}
void alexaLoop() {
alexa.handle();
while (!_alexa_dev_changes.empty()) {
AlexaDevChange& change = _alexa_dev_changes.front();
DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s\n"), change.device_id, change.state ? "ON" : "OFF");
relayStatus(change.device_id, change.state);
_alexa_dev_changes.pop();
}
}
#endif

197
espurna/api.ino Executable file
View File

@ -0,0 +1,197 @@
/*
API MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if WEB_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <vector>
typedef struct {
char * key;
api_get_callback_f getFn = NULL;
api_put_callback_f putFn = NULL;
} web_api_t;
std::vector<web_api_t> _apis;
// -----------------------------------------------------------------------------
bool _apiWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "api", 3) == 0);
}
void _apiWebSocketOnSend(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
}
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
bool _authAPI(AsyncWebServerRequest *request) {
if (getSetting("apiEnabled", API_ENABLED).toInt() == 0) {
DEBUG_MSG_P(PSTR("[WEBSERVER] HTTP API is not enabled\n"));
request->send(403);
return false;
}
if (!request->hasParam("apikey", (request->method() == HTTP_PUT))) {
DEBUG_MSG_P(PSTR("[WEBSERVER] Missing apikey parameter\n"));
request->send(403);
return false;
}
AsyncWebParameter* p = request->getParam("apikey", (request->method() == HTTP_PUT));
if (!p->value().equals(getSetting("apiKey"))) {
DEBUG_MSG_P(PSTR("[WEBSERVER] Wrong apikey parameter\n"));
request->send(403);
return false;
}
return true;
}
bool _asJson(AsyncWebServerRequest *request) {
bool asJson = false;
if (request->hasHeader("Accept")) {
AsyncWebHeader* h = request->getHeader("Accept");
asJson = h->value().equals("application/json");
}
return asJson;
}
ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
return [apiID](AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
web_api_t api = _apis[apiID];
// Check if its a PUT
if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
}
}
// Get response from callback
char value[API_BUFFER_SIZE];
(api.getFn)(value, API_BUFFER_SIZE);
// The response will be a 404 NOT FOUND if the resource is not available
if (!value) {
DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
request->send(404);
return;
}
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value);
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64];
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", value);
}
};
}
void _onAPIs(AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
bool asJson = _asJson(request);
char buffer[40];
String output;
if (asJson) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
for (unsigned int i=0; i < _apis.size(); i++) {
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key);
root[_apis[i].key] = String(buffer);
}
root.printTo(output);
request->send(200, "application/json", output);
} else {
for (unsigned int i=0; i < _apis.size(); i++) {
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key);
output += _apis[i].key + String(" -> ") + String(buffer) + String("\n");
}
request->send(200, "text/plain", output);
}
}
void _onRPC(AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
//bool asJson = _asJson(request);
int response = 404;
if (request->hasParam("action")) {
AsyncWebParameter* p = request->getParam("action");
String action = p->value();
DEBUG_MSG_P(PSTR("[RPC] Action: %s\n"), action.c_str());
if (action.equals("reboot")) {
response = 200;
deferredReset(100, CUSTOM_RESET_RPC);
}
}
request->send(response);
}
// -----------------------------------------------------------------------------
void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn) {
// Store it
web_api_t api;
char buffer[40];
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), key);
api.key = strdup(key);
api.getFn = getFn;
api.putFn = putFn;
_apis.push_back(api);
// Bind call
unsigned int methods = HTTP_GET;
if (putFn != NULL) methods += HTTP_PUT;
webServer()->on(buffer, methods, _bindAPI(_apis.size() - 1));
}
void apiSetup() {
webServer()->on("/apis", HTTP_GET, _onAPIs);
webServer()->on("/rpc", HTTP_GET, _onRPC);
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
}
#endif // WEB_SUPPORT

32
espurna/broker.ino Executable file
View File

@ -0,0 +1,32 @@
/*
BROKER MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if BROKER_SUPPORT
#include <vector>
std::vector<void (*)(const char *, unsigned char, const char *)> _broker_callbacks;
// -----------------------------------------------------------------------------
void brokerRegister(void (*callback)(const char *, unsigned char, const char *)) {
_broker_callbacks.push_back(callback);
}
void brokerPublish(const char * topic, unsigned char id, const char * message) {
//DEBUG_MSG_P(PSTR("[BROKER] Message %s[%u] => %s\n"), topic, id, message);
for (unsigned char i=0; i<_broker_callbacks.size(); i++) {
(_broker_callbacks[i])(topic, id, message);
}
}
void brokerPublish(const char * topic, const char * message) {
brokerPublish(topic, 0, message);
}
#endif // BROKER_SUPPORT

258
espurna/button.ino Executable file
View File

@ -0,0 +1,258 @@
/*
BUTTON MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
// -----------------------------------------------------------------------------
// BUTTON
// -----------------------------------------------------------------------------
#include <DebounceEvent.h>
#include <vector>
typedef struct {
DebounceEvent * button;
unsigned long actions;
unsigned int relayID;
} button_t;
std::vector<button_t> _buttons;
#if MQTT_SUPPORT
void buttonMQTT(unsigned char id, uint8_t event) {
if (id >= _buttons.size()) return;
char payload[2];
itoa(event, payload, 10);
mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain
}
#endif
#if WEB_SUPPORT
bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "btn", 3) == 0);
}
#endif
int buttonFromRelay(unsigned int relayID) {
for (unsigned int i=0; i < _buttons.size(); i++) {
if (_buttons[i].relayID == relayID) return i;
}
return -1;
}
bool buttonState(unsigned char id) {
if (id >= _buttons.size()) return false;
return _buttons[id].button->pressed();
}
unsigned char buttonAction(unsigned char id, unsigned char event) {
if (id >= _buttons.size()) return BUTTON_MODE_NONE;
unsigned long actions = _buttons[id].actions;
if (event == BUTTON_EVENT_PRESSED) return (actions) & 0x0F;
if (event == BUTTON_EVENT_CLICK) return (actions >> 4) & 0x0F;
if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F;
if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F;
if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F;
return BUTTON_MODE_NONE;
}
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) {
unsigned int value;
value = pressed;
value += click << 4;
value += dblclick << 8;
value += lngclick << 12;
value += lnglngclick << 16;
return value;
}
uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED;
if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK;
if (event == EVENT_RELEASED) {
if (count == 1) {
if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK;
if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK;
return BUTTON_EVENT_CLICK;
}
if (count == 2) return BUTTON_EVENT_DBLCLICK;
}
}
void buttonEvent(unsigned int id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
if (event == 0) return;
#if MQTT_SUPPORT
buttonMQTT(id, event);
#endif
unsigned char action = buttonAction(id, event);
if (action == BUTTON_MODE_TOGGLE) {
if (_buttons[id].relayID > 0) {
relayToggle(_buttons[id].relayID - 1);
}
}
if (action == BUTTON_MODE_ON) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true);
}
}
if (action == BUTTON_MODE_OFF) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, false);
}
}
if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_RESET) {
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
if (action == BUTTON_MODE_FACTORY) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
}
}
void buttonSetup() {
#ifdef ITEAD_SONOFF_DUAL
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
#else
unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
#if BUTTON1_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
}
#endif
#if BUTTON2_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
}
#endif
#if BUTTON3_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
}
#endif
#if BUTTON4_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
}
#endif
#if BUTTON5_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
}
#endif
#if BUTTON6_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
}
#endif
#if BUTTON7_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
}
#endif
#if BUTTON8_PIN != GPIO_NONE
{
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});
}
#endif
#endif
DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
// Websocket Callbacks
#if WEB_SUPPORT
wsOnReceiveRegister(_buttonWebSocketOnReceive);
#endif
// Register loop
espurnaRegisterLoop(buttonLoop);
}
void buttonLoop() {
#ifdef ITEAD_SONOFF_DUAL
if (Serial.available() >= 4) {
if (Serial.read() == 0xA0) {
if (Serial.read() == 0x04) {
unsigned char value = Serial.read();
if (Serial.read() == 0xA1) {
// RELAYs and BUTTONs are synchonized in the SIL F330
// The on-board BUTTON2 should toggle RELAY0 value
// Since we are not passing back RELAY2 value
// (in the relayStatus method) it will only be present
// here if it has actually been pressed
if ((value & 4) == 4) {
buttonEvent(2, BUTTON_EVENT_CLICK);
return;
}
// Otherwise check if any of the other two BUTTONs
// (in the header) has been pressed, but we should
// ensure that we only toggle one of them to avoid
// the synchronization going mad
// This loop is generic for any PSB-04 module
for (unsigned int i=0; i<relayCount(); i++) {
bool status = (value & (1 << i)) > 0;
// Check if the status for that relay has changed
if (relayStatus(i) != status) {
buttonEvent(i, BUTTON_EVENT_CLICK);
break;
}
}
}
}
}
}
#else
for (unsigned int i=0; i < _buttons.size(); i++) {
if (unsigned char event = _buttons[i].button->loop()) {
unsigned char count = _buttons[i].button->getEventCount();
unsigned long length = _buttons[i].button->getEventLength();
unsigned char mapped = mapEvent(event, count, length);
buttonEvent(i, mapped);
}
}
#endif
}

36
espurna/config/all.h Executable file
View File

@ -0,0 +1,36 @@
/*
If you want to modify the stock configuration but you don't want to touch
the repo files you can define USE_CUSTOM_H in your build settings.
Arduino IDE:
define it in your boards.txt for the board of your choice.
For instance, for the "Generic ESP8266 Module" with prefix "generic" just add:
generic.build.extra_flags=-DESP8266 -DUSE_CUSTOM_H
PlatformIO:
add the setting to your environment or just define global PLATFORMIO_BUILD_FLAGS
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'"
Check https://github.com/xoseperez/espurna/issues/104
for an example on how to use this file.
*/
#ifdef USE_CUSTOM_H
#include "custom.h"
#endif
#include "version.h"
#include "arduino.h"
#include "hardware.h"
#include "defaults.h"
#include "general.h"
#include "prototypes.h"
#include "sensors.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif

133
espurna/config/arduino.h Executable file
View File

@ -0,0 +1,133 @@
//--------------------------------------------------------------------------------
// These settings are normally provided by PlatformIO
// Uncomment the appropiate line(s) to build from the Arduino IDE
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Hardware
//--------------------------------------------------------------------------------
//#define NODEMCU_LOLIN
//#define WEMOS_D1_MINI_RELAYSHIELD
//#define TINKERMAN_ESPURNA_H06
//#define TINKERMAN_ESPURNA_H08
//#define ITEAD_SONOFF_BASIC
//#define ITEAD_SONOFF_RF
//#define ITEAD_SONOFF_TH
//#define ITEAD_SONOFF_SV
//#define ITEAD_SLAMPHER
//#define ITEAD_S20
//#define ITEAD_SONOFF_TOUCH
//#define ITEAD_SONOFF_POW
//#define ITEAD_SONOFF_DUAL
//#define ITEAD_SONOFF_DUAL_R2
//#define ITEAD_SONOFF_4CH
//#define ITEAD_SONOFF_4CH_PRO
//#define ITEAD_1CH_INCHING
//#define ITEAD_MOTOR
//#define ITEAD_SONOFF_BNSZ01
//#define ITEAD_SONOFF_RFBRIDGE
//#define ITEAD_SONOFF_B1
//#define ITEAD_SONOFF_LED
//#define ITEAD_SONOFF_T1_1CH
//#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31
//#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
//#define AITHINKER_AI_LIGHT
//#define MAGICHOME_LED_CONTROLLER
//#define MAGICHOME_LED_CONTROLLER_20
//#define HUACANXING_H801
//#define HUACANXING_H802
//#define JANGOE_WIFI_RELAY_NC
//#define JANGOE_WIFI_RELAY_NO
//#define JORGEGARCIA_WIFI_RELAYS
//#define OPENENERGYMONITOR_MQTT_RELAY
//#define WION_50055
//#define EXS_WIFI_RELAY_V31
//#define GENERIC_V9261F
//#define GENERIC_ECH1560
//#define MANCAVEMADE_ESPLIVE
//#define INTERMITTECH_QUINLED
//#define ARILUX_AL_LC06
//#define ARILUX_E27
//#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266
//#define KMC_70011
//#define GENERIC_8CH
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD
//#define GIZWITS_WITTY_CLOUD
//#define EUROMATE_WIFI_STECKER_SCHUKO
//#define TONBUX_POWERSTRIP02
//#define LINGAN_SWA1
//#define HEYGO_HY02
//#define MAXCIO_WUS002S
//#define YIDIAN_XSSSA05
//#define TONBUX_XSSSA06
//#define GREEN_ESP8266RELAY
//#define IKE_ESPIKE
//#define ARNIEX_SWIFITCH
//#define GENERIC_ESP01SRELAY40
//#define GENERIC_ESP01SRGBLED10
//#define HELTEC_TOUCHRELAY
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
//--------------------------------------------------------------------------------
//#define ALEXA_SUPPORT 0
#define BROKER_SUPPORT 0
//#define DEBUG_SERIAL_SUPPORT 0
//#define DEBUG_TELNET_SUPPORT 0
//#define DEBUG_UDP_SUPPORT 1
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 1
//#define IR_SUPPORT 1
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1
//#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1
//#define NTP_SUPPORT 0
//#define RF_SUPPORT 1
#define SCHEDULER_SUPPORT 0
//#define SPIFFS_SUPPORT 1
//#define SSDP_SUPPORT 1
//#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0
#define THINGSPEAK_SUPPORT 0
//#define UART_MQTT_SUPPORT 1
//#define WEB_SUPPORT 0
//--------------------------------------------------------------------------------
// Sensors (values below are non-default values)
//--------------------------------------------------------------------------------
//#define AM2320_SUPPORT 1
//#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1
//#define BMX280_SUPPORT 1
//#define DALLAS_SUPPORT 1
//#define DHT_SUPPORT 1
//#define DIGITAL_SUPPORT 1
//#define ECH1560_SUPPORT 1
//#define EMON_ADC121_SUPPORT 1
//#define EMON_ADS1X15_SUPPORT 1
//#define EMON_ANALOG_SUPPORT 1
//#define EVENTS_SUPPORT 1
//#define GUVAS12SD_SUPPORT 1
//#define HLW8012_SUPPORT 1
//#define MHZ19_SUPPORT 1
//#define PMSX003_SUPPORT 1
//#define PZEM004T_SUPPORT 1
//#define SHT3X_I2C_SUPPORT 1
//#define SI7021_SUPPORT 1
//#define V9261F_SUPPORT 1

5
espurna/config/build.h Executable file
View File

@ -0,0 +1,5 @@
// DO NOT EDIT THIS FILE MANUALLY
// This file is modified by PlatformIO
// This file should not be pushed when modified, untrack changes with:
// git update-index --assume-unchanged code/espurna/config/build.h
#define APP_BUILD_FLAGS ""

423
espurna/config/defaults.h Executable file
View File

@ -0,0 +1,423 @@
// -----------------------------------------------------------------------------
// Hardware default values
// -----------------------------------------------------------------------------
#define GPIO_NONE 0x99
// -----------------------------------------------------------------------------
// Buttons
// -----------------------------------------------------------------------------
#ifndef BUTTON1_PIN
#define BUTTON1_PIN GPIO_NONE
#endif
#ifndef BUTTON2_PIN
#define BUTTON2_PIN GPIO_NONE
#endif
#ifndef BUTTON3_PIN
#define BUTTON3_PIN GPIO_NONE
#endif
#ifndef BUTTON4_PIN
#define BUTTON4_PIN GPIO_NONE
#endif
#ifndef BUTTON5_PIN
#define BUTTON5_PIN GPIO_NONE
#endif
#ifndef BUTTON6_PIN
#define BUTTON6_PIN GPIO_NONE
#endif
#ifndef BUTTON7_PIN
#define BUTTON7_PIN GPIO_NONE
#endif
#ifndef BUTTON8_PIN
#define BUTTON8_PIN GPIO_NONE
#endif
#ifndef BUTTON1_PRESS
#define BUTTON1_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON2_PRESS
#define BUTTON2_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_PRESS
#define BUTTON3_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_PRESS
#define BUTTON4_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_PRESS
#define BUTTON5_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_PRESS
#define BUTTON6_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_PRESS
#define BUTTON7_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_PRESS
#define BUTTON8_PRESS BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_CLICK
#define BUTTON1_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON2_CLICK
#define BUTTON2_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON3_CLICK
#define BUTTON3_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON4_CLICK
#define BUTTON4_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON5_CLICK
#define BUTTON5_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON6_CLICK
#define BUTTON6_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON7_CLICK
#define BUTTON7_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON8_CLICK
#define BUTTON8_CLICK BUTTON_MODE_TOGGLE
#endif
#ifndef BUTTON1_DBLCLICK
#define BUTTON1_DBLCLICK BUTTON_MODE_AP
#endif
#ifndef BUTTON2_DBLCLICK
#define BUTTON2_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_DBLCLICK
#define BUTTON3_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_DBLCLICK
#define BUTTON4_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_DBLCLICK
#define BUTTON5_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_DBLCLICK
#define BUTTON6_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_DBLCLICK
#define BUTTON7_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_DBLCLICK
#define BUTTON8_DBLCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_LNGCLICK
#define BUTTON1_LNGCLICK BUTTON_MODE_RESET
#endif
#ifndef BUTTON2_LNGCLICK
#define BUTTON2_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_LNGCLICK
#define BUTTON3_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_LNGCLICK
#define BUTTON4_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_LNGCLICK
#define BUTTON5_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_LNGCLICK
#define BUTTON6_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_LNGCLICK
#define BUTTON7_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_LNGCLICK
#define BUTTON8_LNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_LNGLNGCLICK
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_FACTORY
#endif
#ifndef BUTTON2_LNGLNGCLICK
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_LNGLNGCLICK
#define BUTTON3_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_LNGLNGCLICK
#define BUTTON4_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_LNGLNGCLICK
#define BUTTON5_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_LNGLNGCLICK
#define BUTTON6_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_LNGLNGCLICK
#define BUTTON7_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_LNGLNGCLICK
#define BUTTON8_LNGLNGCLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_RELAY
#define BUTTON1_RELAY 0
#endif
#ifndef BUTTON2_RELAY
#define BUTTON2_RELAY 0
#endif
#ifndef BUTTON3_RELAY
#define BUTTON3_RELAY 0
#endif
#ifndef BUTTON4_RELAY
#define BUTTON4_RELAY 0
#endif
#ifndef BUTTON5_RELAY
#define BUTTON5_RELAY 0
#endif
#ifndef BUTTON6_RELAY
#define BUTTON6_RELAY 0
#endif
#ifndef BUTTON7_RELAY
#define BUTTON7_RELAY 0
#endif
#ifndef BUTTON8_RELAY
#define BUTTON8_RELAY 0
#endif
// -----------------------------------------------------------------------------
// Relays
// -----------------------------------------------------------------------------
#ifndef DUMMY_RELAY_COUNT
#define DUMMY_RELAY_COUNT 0
#endif
#ifndef RELAY1_PIN
#define RELAY1_PIN GPIO_NONE
#endif
#ifndef RELAY2_PIN
#define RELAY2_PIN GPIO_NONE
#endif
#ifndef RELAY3_PIN
#define RELAY3_PIN GPIO_NONE
#endif
#ifndef RELAY4_PIN
#define RELAY4_PIN GPIO_NONE
#endif
#ifndef RELAY5_PIN
#define RELAY5_PIN GPIO_NONE
#endif
#ifndef RELAY6_PIN
#define RELAY6_PIN GPIO_NONE
#endif
#ifndef RELAY7_PIN
#define RELAY7_PIN GPIO_NONE
#endif
#ifndef RELAY8_PIN
#define RELAY8_PIN GPIO_NONE
#endif
#ifndef RELAY1_TYPE
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY2_TYPE
#define RELAY2_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY3_TYPE
#define RELAY3_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY4_TYPE
#define RELAY4_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY5_TYPE
#define RELAY5_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY6_TYPE
#define RELAY6_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY7_TYPE
#define RELAY7_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY8_TYPE
#define RELAY8_TYPE RELAY_TYPE_NORMAL
#endif
#ifndef RELAY1_RESET_PIN
#define RELAY1_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY2_RESET_PIN
#define RELAY2_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY3_RESET_PIN
#define RELAY3_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY4_RESET_PIN
#define RELAY4_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY5_RESET_PIN
#define RELAY5_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY6_RESET_PIN
#define RELAY6_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY7_RESET_PIN
#define RELAY7_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY8_RESET_PIN
#define RELAY8_RESET_PIN GPIO_NONE
#endif
#ifndef RELAY1_DELAY_ON
#define RELAY1_DELAY_ON 0
#endif
#ifndef RELAY2_DELAY_ON
#define RELAY2_DELAY_ON 0
#endif
#ifndef RELAY3_DELAY_ON
#define RELAY3_DELAY_ON 0
#endif
#ifndef RELAY4_DELAY_ON
#define RELAY4_DELAY_ON 0
#endif
#ifndef RELAY5_DELAY_ON
#define RELAY5_DELAY_ON 0
#endif
#ifndef RELAY6_DELAY_ON
#define RELAY6_DELAY_ON 0
#endif
#ifndef RELAY7_DELAY_ON
#define RELAY7_DELAY_ON 0
#endif
#ifndef RELAY8_DELAY_ON
#define RELAY8_DELAY_ON 0
#endif
#ifndef RELAY1_DELAY_OFF
#define RELAY1_DELAY_OFF 0
#endif
#ifndef RELAY2_DELAY_OFF
#define RELAY2_DELAY_OFF 0
#endif
#ifndef RELAY3_DELAY_OFF
#define RELAY3_DELAY_OFF 0
#endif
#ifndef RELAY4_DELAY_OFF
#define RELAY4_DELAY_OFF 0
#endif
#ifndef RELAY5_DELAY_OFF
#define RELAY5_DELAY_OFF 0
#endif
#ifndef RELAY6_DELAY_OFF
#define RELAY6_DELAY_OFF 0
#endif
#ifndef RELAY7_DELAY_OFF
#define RELAY7_DELAY_OFF 0
#endif
#ifndef RELAY8_DELAY_OFF
#define RELAY8_DELAY_OFF 0
#endif
// -----------------------------------------------------------------------------
// LEDs
// -----------------------------------------------------------------------------
#ifndef LED1_PIN
#define LED1_PIN GPIO_NONE
#endif
#ifndef LED2_PIN
#define LED2_PIN GPIO_NONE
#endif
#ifndef LED3_PIN
#define LED3_PIN GPIO_NONE
#endif
#ifndef LED4_PIN
#define LED4_PIN GPIO_NONE
#endif
#ifndef LED5_PIN
#define LED5_PIN GPIO_NONE
#endif
#ifndef LED6_PIN
#define LED6_PIN GPIO_NONE
#endif
#ifndef LED7_PIN
#define LED7_PIN GPIO_NONE
#endif
#ifndef LED8_PIN
#define LED8_PIN GPIO_NONE
#endif
#ifndef LED1_MODE
#define LED1_MODE LED_MODE_WIFI
#endif
#ifndef LED2_MODE
#define LED2_MODE LED_MODE_MQTT
#endif
#ifndef LED3_MODE
#define LED3_MODE LED_MODE_MQTT
#endif
#ifndef LED4_MODE
#define LED4_MODE LED_MODE_MQTT
#endif
#ifndef LED5_MODE
#define LED5_MODE LED_MODE_MQTT
#endif
#ifndef LED6_MODE
#define LED6_MODE LED_MODE_MQTT
#endif
#ifndef LED7_MODE
#define LED7_MODE LED_MODE_MQTT
#endif
#ifndef LED8_MODE
#define LED8_MODE LED_MODE_MQTT
#endif
#ifndef LED1_RELAY
#define LED1_RELAY 1
#endif
#ifndef LED2_RELAY
#define LED2_RELAY 2
#endif
#ifndef LED3_RELAY
#define LED3_RELAY 3
#endif
#ifndef LED4_RELAY
#define LED4_RELAY 4
#endif
#ifndef LED5_RELAY
#define LED5_RELAY 5
#endif
#ifndef LED6_RELAY
#define LED6_RELAY 6
#endif
#ifndef LED7_RELAY
#define LED7_RELAY 7
#endif
#ifndef LED8_RELAY
#define LED8_RELAY 8
#endif
// -----------------------------------------------------------------------------
// General
// -----------------------------------------------------------------------------
// Default hostname will be ESPURNA_XXXXXX, where XXXXXX is last 3 octets of chipID
#ifndef HOSTNAME
#define HOSTNAME ""
#endif
// Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON
#define BUTTON_PUSHBUTTON 0
#define BUTTON_SWITCH 1
#define BUTTON_DEFAULT_HIGH 2
#define BUTTON_SET_PULLUP 4
#endif
// Relay providers
#ifndef RELAY_PROVIDER
#define RELAY_PROVIDER RELAY_PROVIDER_RELAY
#endif
// Light provider
#ifndef LIGHT_PROVIDER
#define LIGHT_PROVIDER LIGHT_PROVIDER_NONE
#endif

1041
espurna/config/general.h Executable file

File diff suppressed because it is too large Load Diff

2023
espurna/config/hardware.h Executable file

File diff suppressed because it is too large Load Diff

118
espurna/config/prototypes.h Executable file
View File

@ -0,0 +1,118 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <functional>
#include <pgmspace.h>
extern "C" {
#include "user_interface.h"
}
// -----------------------------------------------------------------------------
// WebServer
// -----------------------------------------------------------------------------
#include <ESPAsyncWebServer.h>
AsyncWebServer * webServer();
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
typedef std::function<void(char *, size_t)> api_get_callback_f;
typedef std::function<void(const char *)> api_put_callback_f;
void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = NULL);
// -----------------------------------------------------------------------------
// WebSockets
// -----------------------------------------------------------------------------
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
// -----------------------------------------------------------------------------
// MQTT
// -----------------------------------------------------------------------------
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttMagnitude(char * topic);
// -----------------------------------------------------------------------------
// Broker
// -----------------------------------------------------------------------------
void brokerRegister(void (*)(const char *, unsigned char, const char *));
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
#include <Embedis.h>
template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
bool settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len);
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
void i2cScan();
void i2cClearBus();
bool i2cGetLock(unsigned char address);
bool i2cReleaseLock(unsigned char address);
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses);
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len);
uint8_t i2c_write_uint8(uint8_t address, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value);
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2);
uint8_t i2c_write_uint16(uint8_t address, uint16_t value);
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value);
uint8_t i2c_read_uint8(uint8_t address);
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg);
uint16_t i2c_read_uint16(uint8_t address);
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg);
uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg);
int16_t i2c_read_int16(uint8_t address, uint8_t reg);
int16_t i2c_read_int16_le(uint8_t address, uint8_t reg);
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len);
// -----------------------------------------------------------------------------
// GPIO
// -----------------------------------------------------------------------------
bool gpioValid(unsigned char gpio);
bool gpioGetLock(unsigned char gpio);
bool gpioReleaseLock(unsigned char gpio);
// -----------------------------------------------------------------------------
// Debug
// -----------------------------------------------------------------------------
void debugSend(const char * format, ...);
void debugSend_P(PGM_P format, ...);
// -----------------------------------------------------------------------------
// Domoticz
// -----------------------------------------------------------------------------
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
char * ltrim(char * s);
void nice_delay(unsigned long ms);

796
espurna/config/sensors.h Executable file
View File

@ -0,0 +1,796 @@
// =============================================================================
// SENSORS - General data
// =============================================================================
#define SENSOR_DEBUG 0 // Debug sensors
#define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds
#define SENSOR_READ_MIN_INTERVAL 6 // Minimum read interval
#define SENSOR_READ_MAX_INTERVAL 3600 // Maximum read interval
#define SENSOR_INIT_INTERVAL 10000 // Try to re-init non-ready sensors every 10s
#define SENSOR_REPORT_EVERY 10 // Report every this many readings
#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value
#define SENSOR_REPORT_MAX_EVERY 12 // Maximum
#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0)
// even if just one sensor (0 for backwards compatibility)
#ifndef SENSOR_POWER_CHECK_STATUS
#define SENSOR_POWER_CHECK_STATUS 1 // If set to 1 the reported power/current/energy will be 0 if the relay[0] is OFF
#endif
#ifndef SENSOR_TEMPERATURE_CORRECTION
#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction
#endif
#ifndef TEMPERATURE_MIN_CHANGE
#define TEMPERATURE_MIN_CHANGE 0.0 // Minimum temperature change to report
#endif
#ifndef SENSOR_HUMIDITY_CORRECTION
#define SENSOR_HUMIDITY_CORRECTION 0.0 // Offset correction
#endif
#ifndef HUMIDITY_MIN_CHANGE
#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report
#endif
// American Society of Heating, Refrigerating and Air-Conditioning Engineers suggests a range of 45% - 55% humidity to manage health effects and illnesses.
// Comfortable: 30% - 60%
// Recommended: 45% - 55%
// High : 55% - 80%
#define HUMIDITY_NORMAL 0 // > %30
#define HUMIDITY_COMFORTABLE 1 // > %45
#define HUMIDITY_DRY 2 // < %30
#define HUMIDITY_WET 3 // > %70
// United States Environmental Protection Agency - UV Index Scale
// One UV Index unit is equivalent to 25 milliWatts per square meter.
#define UV_INDEX_LOW 0 // 0 to 2 means low danger from the sun's UV rays for the average person.
#define UV_INDEX_MODERATE 1 // 3 to 5 means moderate risk of harm from unprotected sun exposure.
#define UV_INDEX_HIGH 2 // 6 to 7 means high risk of harm from unprotected sun exposure. Protection against skin and eye damage is needed.
#define UV_INDEX_VERY_HIGH 3 // 8 to 10 means very high risk of harm from unprotected sun exposure.
// Take extra precautions because unprotected skin and eyes will be damaged and can burn quickly.
#define UV_INDEX_EXTREME 4 // 11 or more means extreme risk of harm from unprotected sun exposure.
// Take all precautions because unprotected skin and eyes can burn in minutes.
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses
//------------------------------------------------------------------------------
// UNITS
//------------------------------------------------------------------------------
#define POWER_WATTS 0
#define POWER_KILOWATTS 1
#define ENERGY_JOULES 0
#define ENERGY_KWH 1
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
#ifndef SENSOR_TEMPERATURE_UNITS
#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT)
#endif
#ifndef SENSOR_ENERGY_UNITS
#define SENSOR_ENERGY_UNITS ENERGY_JOULES // Energy units (ENERGY_JOULES | ENERGY_KWH)
#endif
#ifndef SENSOR_POWER_UNITS
#define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS)
#endif
//--------------------------------------------------------------------------------
// Sensor ID
// These should remain over time, do not modify them, only add new ones at the end
//--------------------------------------------------------------------------------
#define SENSOR_DHTXX_ID 0x01
#define SENSOR_DALLAS_ID 0x02
#define SENSOR_EMON_ANALOG_ID 0x03
#define SENSOR_EMON_ADC121_ID 0x04
#define SENSOR_EMON_ADS1X15_ID 0x05
#define SENSOR_HLW8012_ID 0x06
#define SENSOR_V9261F_ID 0x07
#define SENSOR_ECH1560_ID 0x08
#define SENSOR_ANALOG_ID 0x09
#define SENSOR_DIGITAL_ID 0x10
#define SENSOR_EVENTS_ID 0x11
#define SENSOR_PMSX003_ID 0x12
#define SENSOR_BMX280_ID 0x13
#define SENSOR_MHZ19_ID 0x14
#define SENSOR_SI7021_ID 0x15
#define SENSOR_SHT3X_I2C_ID 0x16
#define SENSOR_BH1750_ID 0x17
#define SENSOR_PZEM004T_ID 0x18
#define SENSOR_AM2320_ID 0x19
#define SENSOR_GUVAS12SD_ID 0x20
#define SENSOR_CSE7766_ID 0x21
//--------------------------------------------------------------------------------
// Magnitudes
//--------------------------------------------------------------------------------
#define MAGNITUDE_NONE 0
#define MAGNITUDE_TEMPERATURE 1
#define MAGNITUDE_HUMIDITY 2
#define MAGNITUDE_PRESSURE 3
#define MAGNITUDE_CURRENT 4
#define MAGNITUDE_VOLTAGE 5
#define MAGNITUDE_POWER_ACTIVE 6
#define MAGNITUDE_POWER_APPARENT 7
#define MAGNITUDE_POWER_REACTIVE 8
#define MAGNITUDE_POWER_FACTOR 9
#define MAGNITUDE_ENERGY 10
#define MAGNITUDE_ENERGY_DELTA 11
#define MAGNITUDE_ANALOG 12
#define MAGNITUDE_DIGITAL 13
#define MAGNITUDE_EVENTS 14
#define MAGNITUDE_PM1dot0 15
#define MAGNITUDE_PM2dot5 16
#define MAGNITUDE_PM10 17
#define MAGNITUDE_CO2 18
#define MAGNITUDE_LUX 19
#define MAGNITUDE_UV 20
#define MAGNITUDE_MAX 21
// =============================================================================
// Specific data for each sensor
// =============================================================================
//------------------------------------------------------------------------------
// Analog sensor
// Enable support by passing ANALOG_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef ANALOG_SUPPORT
#define ANALOG_SUPPORT 0
#endif
#if ANALOG_SUPPORT
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0
#endif
//------------------------------------------------------------------------------
// BH1750
// Enable support by passing BH1750_SUPPORT=1 build flag
// http://www.elechouse.com/elechouse/images/product/Digital%20light%20Sensor/bh1750fvi-e.pdf
//------------------------------------------------------------------------------
#ifndef BH1750_SUPPORT
#define BH1750_SUPPORT 0
#endif
#ifndef BH1750_ADDRESS
#define BH1750_ADDRESS 0x00 // 0x00 means auto
#endif
#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE
#if BH1750_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// BME280/BMP280
// Enable support by passing BMX280_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef BMX280_SUPPORT
#define BMX280_SUPPORT 0
#endif
#ifndef BMX280_ADDRESS
#define BMX280_ADDRESS 0x00 // 0x00 means auto
#endif
#define BMX280_MODE 1 // 0 for sleep mode, 1 or 2 for forced mode, 3 for normal mode
#define BMX280_STANDBY 0 // 0 for 0.5ms, 1 for 62.5ms, 2 for 125ms
// 3 for 250ms, 4 for 500ms, 5 for 1000ms
// 6 for 10ms, 7 for 20ms
#define BMX280_FILTER 0 // 0 for OFF, 1 for 2 values, 2 for 4 values, 3 for 8 values and 4 for 16 values
#define BMX280_TEMPERATURE 1 // Oversampling for temperature (set to 0 to disable magnitude)
#define BMX280_HUMIDITY 1 // Oversampling for humidity (set to 0 to disable magnitude, only for BME280)
#define BMX280_PRESSURE 1 // Oversampling for pressure (set to 0 to disable magnitude)
#if BMX280_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Dallas OneWire temperature sensors
// Enable support by passing DALLAS_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef DALLAS_SUPPORT
#define DALLAS_SUPPORT 0
#endif
#ifndef DALLAS_PIN
#define DALLAS_PIN 14
#endif
#define DALLAS_RESOLUTION 9 // Not used atm
#define DALLAS_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
//------------------------------------------------------------------------------
// DHTXX temperature/humidity sensor
// Enable support by passing DHT_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef DHT_SUPPORT
#define DHT_SUPPORT 0
#endif
#ifndef DHT_PIN
#define DHT_PIN 13
#endif
#ifndef DHT_TYPE
#define DHT_TYPE DHT_CHIP_DHT22
#endif
//------------------------------------------------------------------------------
// CSE7766 based power sensor
// Enable support by passing CSE7766_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef CSE7766_SUPPORT
#define CSE7766_SUPPORT 0
#endif
#ifndef CSE7766_PIN
#define CSE7766_PIN 1 // TX pin from the CSE7766
#endif
#ifndef CSE7766_PIN_INVERSE
#define CSE7766_PIN_INVERSE 0 // Signal is inverted
#endif
#define CSE7766_SYNC_INTERVAL 300 // Safe time between transmissions (ms)
#define CSE7766_BAUDRATE 4800 // UART baudrate
#define CSE7766_V1R 1.0 // 1mR current resistor
#define CSE7766_V2R 1.0 // 1M voltage resistor
//------------------------------------------------------------------------------
// Digital sensor
// Enable support by passing DIGITAL_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef DIGITAL_SUPPORT
#define DIGITAL_SUPPORT 0
#endif
#ifndef DIGITAL_PIN
#define DIGITAL_PIN 2
#endif
#ifndef DIGITAL_PIN_MODE
#define DIGITAL_PIN_MODE INPUT_PULLUP
#endif
#ifndef DIGITAL_DEFAULT_STATE
#define DIGITAL_DEFAULT_STATE 1
#endif
//------------------------------------------------------------------------------
// ECH1560 based power sensor
// Enable support by passing ECH1560_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef ECH1560_SUPPORT
#define ECH1560_SUPPORT 0
#endif
#ifndef ECH1560_CLK_PIN
#define ECH1560_CLK_PIN 4 // CLK pin for the ECH1560
#endif
#ifndef ECH1560_MISO_PIN
#define ECH1560_MISO_PIN 5 // MISO pin for the ECH1560
#endif
#ifndef ECH1560_INVERTED
#define ECH1560_INVERTED 0 // Signal is inverted
#endif
//------------------------------------------------------------------------------
// Energy Monitor general settings
//------------------------------------------------------------------------------
#define EMON_MAX_SAMPLES 1000 // Max number of samples to get
#define EMON_MAX_TIME 250 // Max time in ms to sample
#define EMON_FILTER_SPEED 512 // Mobile average filter speed
#define EMON_MAINS_VOLTAGE 230 // Mains voltage
#define EMON_REFERENCE_VOLTAGE 3.3 // Reference voltage of the ADC
#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A)
#define EMON_REPORT_CURRENT 0 // Report current
#define EMON_REPORT_POWER 1 // Report power
#define EMON_REPORT_ENERGY 1 // Report energy
//------------------------------------------------------------------------------
// Energy Monitor based on ADC121
// Enable support by passing EMON_ADC121_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef EMON_ADC121_SUPPORT
#define EMON_ADC121_SUPPORT 0 // Do not build support by default
#endif
#define EMON_ADC121_I2C_ADDRESS 0x00 // 0x00 means auto
#if EMON_ADC121_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Energy Monitor based on ADS1X15
// Enable support by passing EMON_ADS1X15_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef EMON_ADS1X15_SUPPORT
#define EMON_ADS1X15_SUPPORT 0 // Do not build support by default
#endif
#define EMON_ADS1X15_I2C_ADDRESS 0x00 // 0x00 means auto
#define EMON_ADS1X15_TYPE ADS1X15_CHIP_ADS1115
#define EMON_ADS1X15_GAIN ADS1X15_REG_CONFIG_PGA_4_096V
#define EMON_ADS1X15_MASK 0x0F // A0=1 A1=2 A2=4 A3=8
#if EMON_ADS1X15_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// Energy Monitor based on interval analog GPIO
// Enable support by passing EMON_ANALOG_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef EMON_ANALOG_SUPPORT
#define EMON_ANALOG_SUPPORT 0 // Do not build support by default
#endif
#if EMON_ANALOG_SUPPORT
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0
#endif
//------------------------------------------------------------------------------
// Counter sensor
// Enable support by passing EVENTS_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef EVENTS_SUPPORT
#define EVENTS_SUPPORT 0 // Do not build with counter support by default
#endif
#ifndef EVENTS_PIN
#define EVENTS_PIN 2 // GPIO to monitor
#endif
#ifndef EVENTS_PIN_MODE
#define EVENTS_PIN_MODE INPUT // INPUT, INPUT_PULLUP
#endif
#ifndef EVENTS_INTERRUPT_MODE
#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#endif
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 10 millis
//------------------------------------------------------------------------------
// HLW8012 Energy monitor IC
// Enable support by passing HLW8012_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 0
#endif
#ifndef HLW8012_SEL_PIN
#define HLW8012_SEL_PIN 5
#endif
#ifndef HLW8012_CF1_PIN
#define HLW8012_CF1_PIN 13
#endif
#ifndef HLW8012_CF_PIN
#define HLW8012_CF_PIN 14
#endif
#ifndef HLW8012_SEL_CURRENT
#define HLW8012_SEL_CURRENT HIGH // SEL pin to HIGH to measure current
#endif
#ifndef HLW8012_CURRENT_R
#define HLW8012_CURRENT_R 0.001 // Current resistor
#endif
#ifndef HLW8012_VOLTAGE_R_UP
#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Upstream voltage resistor
#endif
#ifndef HLW8012_VOLTAGE_R_DOWN
#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Downstream voltage resistor
#endif
#define HLW8012_USE_INTERRUPTS 1 // Use interrupts to trap HLW8012 signals
//------------------------------------------------------------------------------
// MHZ19 CO2 sensor
// Enable support by passing MHZ19_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef MHZ19_SUPPORT
#define MHZ19_SUPPORT 0
#endif
#define MHZ19_RX_PIN 13
#define MHZ19_TX_PIN 15
//------------------------------------------------------------------------------
// Particle Monitor based on Plantower PMSX003
// Enable support by passing PMSX003_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef PMSX003_SUPPORT
#define PMSX003_SUPPORT 0
#endif
#define PMS_RX_PIN 13
#define PMS_TX_PIN 15
//------------------------------------------------------------------------------
// PZEM004T based power monitor
// Enable support by passing PZEM004T_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef PZEM004T_SUPPORT
#define PZEM004T_SUPPORT 0
#endif
#ifndef PZEM004T_USE_SOFT
#define PZEM004T_USE_SOFT 1 // Use software serial
#endif
#ifndef PZEM004T_RX_PIN
#define PZEM004T_RX_PIN 13 // Software serial RX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_TX_PIN
#define PZEM004T_TX_PIN 15 // Software serial TX GPIO (if PZEM004T_USE_SOFT == 1)
#endif
#ifndef PZEM004T_HW_PORT
#define PZEM004T_HW_PORT Serial1 // Hardware serial port (if PZEM004T_USE_SOFT == 0)
#endif
//------------------------------------------------------------------------------
// SHT3X I2C (Wemos) temperature & humidity sensor
// Enable support by passing SHT3X_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SHT3X_I2C_SUPPORT
#define SHT3X_I2C_SUPPORT 0
#endif
#ifndef SHT3X_I2C_ADDRESS
#define SHT3X_I2C_ADDRESS 0x00 // 0x00 means auto
#endif
#if SHT3X_I2C_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// SI7021 temperature & humidity sensor
// Enable support by passing SI7021_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef SI7021_SUPPORT
#define SI7021_SUPPORT 0
#endif
#ifndef SI7021_ADDRESS
#define SI7021_ADDRESS 0x00 // 0x00 means auto
#endif
#if SI7021_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// V9261F based power sensor
// Enable support by passing SI7021_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef V9261F_SUPPORT
#define V9261F_SUPPORT 0
#endif
#ifndef V9261F_PIN
#define V9261F_PIN 2 // TX pin from the V9261F
#endif
#ifndef V9261F_PIN_INVERSE
#define V9261F_PIN_INVERSE 1 // Signal is inverted
#endif
#define V9261F_SYNC_INTERVAL 600 // Sync signal length (ms)
#define V9261F_BAUDRATE 4800 // UART baudrate
// Default ratios
#define V9261F_CURRENT_FACTOR 79371434.0
#define V9261F_VOLTAGE_FACTOR 4160651.0
#define V9261F_POWER_FACTOR 153699.0
#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR
//------------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Enable support by passing AM2320_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef AM2320_SUPPORT
#define AM2320_SUPPORT 0
#endif
#ifndef AM2320_ADDRESS
#define AM2320_ADDRESS 0x00 // 0x00 means auto
#endif
#if AM2320_SUPPORT
#undef I2C_SUPPORT
#define I2C_SUPPORT 1
#endif
//------------------------------------------------------------------------------
// GUVAS12SD UV Sensor (analog)
// Enable support by passing GUVAS12SD_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef GUVAS12SD_SUPPORT
#define GUVAS12SD_SUPPORT 0
#endif
#ifndef GUVAS12SD_PIN
#define GUVAS12SD_PIN 14
#endif
// =============================================================================
// Sensor helpers configuration
// =============================================================================
#ifndef SENSOR_SUPPORT
#if ANALOG_SUPPORT || BH1750_SUPPORT || BMX280_SUPPORT || DALLAS_SUPPORT \
|| DHT_SUPPORT || DIGITAL_SUPPORT || ECH1560_SUPPORT \
|| EMON_ADC121_SUPPORT || EMON_ADS1X15_SUPPORT \
|| EMON_ANALOG_SUPPORT || EVENTS_SUPPORT || HLW8012_SUPPORT \
|| MHZ19_SUPPORT || PMSX003_SUPPORT || SHT3X_I2C_SUPPORT \
|| SI7021_SUPPORT || V9261F_SUPPORT || AM2320_SUPPORT \
|| GUVAS12SD_SUPPORT || CSE7766_SUPPORT
#define SENSOR_SUPPORT 1
#else
#define SENSOR_SUPPORT 0
#endif
#endif
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
#ifndef I2C_SUPPORT
#define I2C_SUPPORT 0 // I2C enabled (1.98Kb)
#endif
#define I2C_USE_BRZO 0 // Use brzo_i2c library or standard Wire
#ifndef I2C_SDA_PIN
#define I2C_SDA_PIN SDA // SDA GPIO (Sonoff => 4)
#endif
#ifndef I2C_SCL_PIN
#define I2C_SCL_PIN SCL // SCL GPIO (Sonoff => 14)
#endif
#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time
#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency
#define I2C_CLEAR_BUS 0 // Clear I2C bus on boot
#define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot
//--------------------------------------------------------------------------------
// Internal power monitor
// Enable support by passing ADC_VCC_ENABLED=1 build flag
// Do not enable this if using the analog GPIO for any other thing
//--------------------------------------------------------------------------------
#ifndef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 1
#endif
#if ADC_VCC_ENABLED
ADC_MODE(ADC_VCC);
#endif
//--------------------------------------------------------------------------------
// Class loading
//--------------------------------------------------------------------------------
#if SENSOR_SUPPORT
PROGMEM const unsigned char magnitude_decimals[] = {
0,
1, 0, 2,
3, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0
};
PROGMEM const char magnitude_unknown_topic[] = "unknown";
PROGMEM const char magnitude_temperature_topic[] = "temperature";
PROGMEM const char magnitude_humidity_topic[] = "humidity";
PROGMEM const char magnitude_pressure_topic[] = "pressure";
PROGMEM const char magnitude_current_topic[] = "current";
PROGMEM const char magnitude_voltage_topic[] = "voltage";
PROGMEM const char magnitude_active_power_topic[] = "power";
PROGMEM const char magnitude_apparent_power_topic[] = "apparent";
PROGMEM const char magnitude_reactive_power_topic[] = "reactive";
PROGMEM const char magnitude_power_factor_topic[] = "factor";
PROGMEM const char magnitude_energy_topic[] = "energy";
PROGMEM const char magnitude_energy_delta_topic[] = "energy_delta";
PROGMEM const char magnitude_analog_topic[] = "analog";
PROGMEM const char magnitude_digital_topic[] = "digital";
PROGMEM const char magnitude_events_topic[] = "events";
PROGMEM const char magnitude_pm1dot0_topic[] = "pm1dot0";
PROGMEM const char magnitude_pm2dot5_topic[] = "pm2dot5";
PROGMEM const char magnitude_pm10_topic[] = "pm10";
PROGMEM const char magnitude_co2_topic[] = "co2";
PROGMEM const char magnitude_lux_topic[] = "lux";
PROGMEM const char magnitude_uv_topic[] = "uv";
PROGMEM const char* const magnitude_topics[] = {
magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic,
magnitude_pressure_topic, magnitude_current_topic, magnitude_voltage_topic,
magnitude_active_power_topic, magnitude_apparent_power_topic, magnitude_reactive_power_topic,
magnitude_power_factor_topic, magnitude_energy_topic, magnitude_energy_delta_topic,
magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic,
magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic,
magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic
};
PROGMEM const char magnitude_empty[] = "";
PROGMEM const char magnitude_celsius[] = "C";
PROGMEM const char magnitude_fahrenheit[] = "F";
PROGMEM const char magnitude_percentage[] = "%";
PROGMEM const char magnitude_hectopascals[] = "hPa";
PROGMEM const char magnitude_amperes[] = "A";
PROGMEM const char magnitude_volts[] = "V";
PROGMEM const char magnitude_watts[] = "W";
PROGMEM const char magnitude_kw[] = "kW";
PROGMEM const char magnitude_joules[] = "J";
PROGMEM const char magnitude_kwh[] = "kWh";
PROGMEM const char magnitude_ugm3[] = "µg/m3";
PROGMEM const char magnitude_ppm[] = "ppm";
PROGMEM const char magnitude_lux[] = "lux";
PROGMEM const char magnitude_uv[] = "uv";
PROGMEM const char* const magnitude_units[] = {
magnitude_empty, magnitude_celsius, magnitude_percentage,
magnitude_hectopascals, magnitude_amperes, magnitude_volts,
magnitude_watts, magnitude_watts, magnitude_watts,
magnitude_percentage, magnitude_joules, magnitude_joules,
magnitude_empty, magnitude_empty, magnitude_empty,
magnitude_ugm3, magnitude_ugm3, magnitude_ugm3,
magnitude_ppm, magnitude_lux, magnitude_uv
};
#include "../sensors/BaseSensor.h"
#if ANALOG_SUPPORT
#include "../sensors/AnalogSensor.h"
#endif
#if BH1750_SUPPORT
#include "../sensors/BH1750Sensor.h"
#endif
#if BMX280_SUPPORT
#include "../sensors/BMX280Sensor.h"
#endif
#if CSE7766_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/CSE7766Sensor.h"
#endif
#if DALLAS_SUPPORT
#include <OneWire.h>
#include "../sensors/DallasSensor.h"
#endif
#if DHT_SUPPORT
#include "../sensors/DHTSensor.h"
#endif
#if DIGITAL_SUPPORT
#include "../sensors/DigitalSensor.h"
#endif
#if ECH1560_SUPPORT
#include "../sensors/ECH1560Sensor.h"
#endif
#if EMON_ADC121_SUPPORT
#include "../sensors/EmonADC121Sensor.h"
#endif
#if EMON_ADS1X15_SUPPORT
#include "../sensors/EmonADS1X15Sensor.h"
#endif
#if EMON_ANALOG_SUPPORT
#include "../sensors/EmonAnalogSensor.h"
#endif
#if EVENTS_SUPPORT
#include "../sensors/EventSensor.h"
#endif
#if HLW8012_SUPPORT
#include <HLW8012.h>
#include "../sensors/HLW8012Sensor.h"
#endif
#if MHZ19_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/MHZ19Sensor.h"
#endif
#if PMSX003_SUPPORT
#include <SoftwareSerial.h>
#include <PMS.h>
#include "../sensors/PMSX003Sensor.h"
#endif
#if PZEM004T_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/PZEM004TSensor.h"
#endif
#if SI7021_SUPPORT
#include "../sensors/SI7021Sensor.h"
#endif
#if SHT3X_I2C_SUPPORT
#include "../sensors/SHT3XI2CSensor.h"
#endif
#if V9261F_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/V9261FSensor.h"
#endif
#if AM2320_SUPPORT
#include "../sensors/AM2320Sensor.h"
#endif
#if GUVAS12SD_SUPPORT
#include "../sensors/GUVAS12SDSensor.h"
#endif
#endif // SENSOR_SUPPORT

6
espurna/config/version.h Executable file
View File

@ -0,0 +1,6 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.12.6a"
#define APP_REVISION ""
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3

BIN
espurna/data/index.html.gz Executable file

Binary file not shown.

BIN
espurna/data/server.cer Executable file

Binary file not shown.

BIN
espurna/data/server.key Executable file

Binary file not shown.

268
espurna/debug.ino Executable file
View File

@ -0,0 +1,268 @@
/*
DEBUG MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if DEBUG_SUPPORT
#include <stdio.h>
#include <stdarg.h>
#include <EEPROM.h>
extern "C" {
#include "user_interface.h"
}
#if DEBUG_UDP_SUPPORT
#include <WiFiUdp.h>
WiFiUDP _udp_debug;
#endif
void _debugSend(char * message) {
#if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true;
char timestamp[10] = {0};
if (add_timestamp) snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000);
add_timestamp = (message[strlen(message)-1] == 10) || (message[strlen(message)-1] == 13);
#endif
#if DEBUG_SERIAL_SUPPORT
#if DEBUG_ADD_TIMESTAMP
DEBUG_PORT.printf(timestamp);
#endif
DEBUG_PORT.printf(message);
#endif
#if DEBUG_UDP_SUPPORT
#if SYSTEM_CHECK_ENABLED
if (systemCheck()) {
#endif
_udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
#if DEBUG_ADD_TIMESTAMP
_udp_debug.write(timestamp);
#endif
_udp_debug.write(message);
_udp_debug.endPacket();
delay(1); // https://github.com/xoseperez/espurna/issues/438
#if SYSTEM_CHECK_ENABLED
}
#endif
#endif
#if DEBUG_TELNET_SUPPORT
#if DEBUG_ADD_TIMESTAMP
_telnetWrite(timestamp, strlen(timestamp));
#endif
_telnetWrite(message, strlen(message));
#endif
#if DEBUG_WEB_SUPPORT
if (wsConnected() && (getFreeHeap() > 10000)) {
String m = String(message);
m.replace("\"", "&quot;");
m.replace("{", "&#123");
m.replace("}", "&#125");
char buffer[m.length() + 24];
#if DEBUG_ADD_TIMESTAMP
snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s%s\"}"), timestamp, m.c_str());
#else
snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s\"}"), m.c_str());
#endif
wsSend(buffer);
}
#endif
}
// -----------------------------------------------------------------------------
void debugSend(const char * format, ...) {
va_list args;
va_start(args, format);
char test[1];
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, format, args);
va_end(args);
_debugSend(buffer);
delete[] buffer;
}
void debugSend_P(PGM_P format_P, ...) {
char format[strlen_P(format_P)+1];
memcpy_P(format, format_P, sizeof(format));
va_list args;
va_start(args, format_P);
char test[1];
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, format, args);
va_end(args);
_debugSend(buffer);
delete[] buffer;
}
#if DEBUG_WEB_SUPPORT
void debugSetup() {
wsOnSendRegister([](JsonObject& root) {
root["dbgVisible"] = 1;
});
wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "dbgcmd") == 0) {
const char* command = data.get<const char*>("command");
char buffer[strlen(command) + 2];
snprintf(buffer, sizeof(buffer), "%s\n", command);
settingsInject((void*) buffer, strlen(buffer));
}
});
}
#endif // DEBUG_WEB_SUPPORT
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. adress of stack start
* 10. adress of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
/**
* Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
// This method assumes EEPROM has already been initialized
// which is the first thing ESPurna does
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
// write epc1, epc2, epc3, excvaddr and depc to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
// write stack start and end address to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
// write stack trace to EEPROM
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) {
byte* byteValue = (byte*) i;
EEPROM.write(current_address++, *byteValue);
}
EEPROM.commit();
}
/**
* Clears crash info
*/
void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROM.commit();
}
/**
* Print out crash information that has been previusly saved in EEPROM
*/
void debugDumpCrashInfo() {
uint32_t crash_time;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
return;
}
DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
uint32_t epc1, epc2, epc3, excvaddr, depc;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3);
DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc);
uint32_t stack_start, stack_end;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start;
uint32_t stack_trace;
for (int16_t i = 0; i < stack_len; i += 0x10) {
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
for (byte j = 0; j < 4; j++) {
EEPROM.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
DEBUG_MSG_P(PSTR("\n[DEBUG] "));
}
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT

161
espurna/domoticz.ino Executable file
View File

@ -0,0 +1,161 @@
/*
DOMOTICZ MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if DOMOTICZ_SUPPORT
#include <ArduinoJson.h>
bool _dcz_enabled = false;
//------------------------------------------------------------------------------
// Private methods
//------------------------------------------------------------------------------
unsigned char _domoticzRelay(unsigned int idx) {
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
}
}
return -1;
}
void _domoticzMqttSubscribe(bool value) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (value) {
mqttSubscribeRaw(dczTopicOut.c_str());
} else {
mqttUnsubscribeRaw(dczTopicOut.c_str());
}
}
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
if (!_dcz_enabled) return;
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribeRaw(dczTopicOut.c_str());
}
if (type == MQTT_MESSAGE_EVENT) {
// Check topic
if (dczTopicOut.equals(topic)) {
// Parse response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[DOMOTICZ] Error parsing data\n"));
return;
}
// IDX
unsigned int idx = root["idx"];
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
}
};
#if WEB_SUPPORT
bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "dcz", 3) == 0);
}
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelays");
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
#if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("dczMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("dczMagnitude", i, 0).toInt();
}
#endif
}
#endif // WEB_SUPPORT
void _domoticzConfigure() {
bool enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled);
_dcz_enabled = enabled;
}
//------------------------------------------------------------------------------
// Public API
//------------------------------------------------------------------------------
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue) {
if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt();
if (idx > 0) {
char payload[128];
snprintf(payload, sizeof(payload), "{\"idx\": %u, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
}
}
template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, "");
}
void domoticzSendRelay(unsigned char relayID) {
if (!_dcz_enabled) return;
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
}
unsigned int domoticzIdx(unsigned char relayID) {
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);
return getSetting(buffer).toInt();
}
void domoticzSetup() {
_domoticzConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnAfterParseRegister(_domoticzConfigure);
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
#endif
mqttRegister(_domoticzMqtt);
}
bool domoticzEnabled() {
return _dcz_enabled;
}
#endif

177
espurna/espurna.ino Executable file
View File

@ -0,0 +1,177 @@
/*
ESPurna
Copyright (C) 2016-2018 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/>.
*/
#include "config/all.h"
#include <vector>
std::vector<void (*)()> _loop_callbacks;
// -----------------------------------------------------------------------------
// REGISTER
// -----------------------------------------------------------------------------
void espurnaRegisterLoop(void (*callback)()) {
_loop_callbacks.push_back(callback);
}
// -----------------------------------------------------------------------------
// BOOTING
// -----------------------------------------------------------------------------
void setup() {
// -------------------------------------------------------------------------
// Basic modules, will always run
// -------------------------------------------------------------------------
// Init EEPROM, Serial, SPIFFS and system check
systemSetup();
// Init persistance and terminal features
settingsSetup();
// Hostname & board name initialization
if (getSetting("hostname").length() == 0) {
setDefaultHostname();
}
setBoardName();
// Show welcome message and system configuration
info();
wifiSetup();
otaSetup();
#if TELNET_SUPPORT
telnetSetup();
#endif
// -------------------------------------------------------------------------
// Check if system is stable
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
#endif
// -------------------------------------------------------------------------
// Next modules will be only loaded if system is flagged as stable
// -------------------------------------------------------------------------
// Init webserver required before any module that uses API
#if WEB_SUPPORT
webSetup();
wsSetup();
apiSetup();
#if DEBUG_WEB_SUPPORT
debugSetup();
#endif
#endif
// lightSetup must be called before relaySetup
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightSetup();
#endif
relaySetup();
buttonSetup();
ledSetup();
#if MQTT_SUPPORT
mqttSetup();
#endif
#if MDNS_SERVER_SUPPORT
mdnsServerSetup();
#endif
#if MDNS_CLIENT_SUPPORT
mdnsClientSetup();
#endif
#if LLMNR_SUPPORT
llmnrSetup();
#endif
#if NETBIOS_SUPPORT
netbiosSetup();
#endif
#if SSDP_SUPPORT
ssdpSetup();
#endif
#if NTP_SUPPORT
ntpSetup();
#endif
#if I2C_SUPPORT
i2cSetup();
#endif
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbSetup();
#endif
#if ALEXA_SUPPORT
alexaSetup();
#endif
#if NOFUSS_SUPPORT
nofussSetup();
#endif
#if INFLUXDB_SUPPORT
idbSetup();
#endif
#if THINGSPEAK_SUPPORT
tspkSetup();
#endif
#if RF_SUPPORT
rfSetup();
#endif
#if IR_SUPPORT
irSetup();
#endif
#if DOMOTICZ_SUPPORT
domoticzSetup();
#endif
#if HOMEASSISTANT_SUPPORT
haSetup();
#endif
#if SENSOR_SUPPORT
sensorSetup();
#endif
#if SCHEDULER_SUPPORT
schSetup();
#endif
#if UART_MQTT_SUPPORT
uartmqttSetup();
#endif
// 3rd party code hook
#if USE_EXTRA
extraSetup();
#endif
// Prepare configuration for version 2.0
migrate();
saveSettings();
}
void loop() {
// Call registered loop callbacks
for (unsigned char i = 0; i < _loop_callbacks.size(); i++) {
(_loop_callbacks[i])();
}
}

25
espurna/filters/BaseFilter.h Executable file
View File

@ -0,0 +1,25 @@
// -----------------------------------------------------------------------------
// Base Filter (other filters inherit from this)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
class BaseFilter {
public:
virtual void add(double value);
virtual unsigned char count();
virtual void reset();
virtual double result();
virtual void resize(unsigned char size);
unsigned char size() { return _size; };
protected:
unsigned char _size;
};
#endif // SENSOR_SUPPORT

40
espurna/filters/MaxFilter.h Executable file
View File

@ -0,0 +1,40 @@
// -----------------------------------------------------------------------------
// Max Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "BaseFilter.h"
class MaxFilter : public BaseFilter {
public:
void add(double value) {
if (value > _max) _max = value;
}
unsigned char count() {
return 1;
}
void reset() {
_max = 0;
}
double result() {
return _max;
}
void resize(unsigned char size) {}
protected:
double _max = 0;
};
#endif // SENSOR_SUPPORT

92
espurna/filters/MedianFilter.h Executable file
View File

@ -0,0 +1,92 @@
// -----------------------------------------------------------------------------
// Median Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "BaseFilter.h"
class MedianFilter : public BaseFilter {
public:
~MedianFilter() {
if (_data) delete _data;
}
void add(double value) {
if (_pointer <= _size) {
_data[_pointer] = value;
_pointer++;
}
}
unsigned char count() {
return _pointer;
}
void reset() {
if (_pointer > 0) {
_data[0] = _data[_pointer-1];
_pointer = 1;
} else {
_pointer = 0;
}
}
double result() {
double sum = 0;
if (_pointer > 2) {
for (unsigned char i = 1; i <= _pointer - 2; i++) {
// For each position,
// we find the median with the previous and next value
// and use that for the sum
double previous = _data[i-1];
double current = _data[i];
double next = _data[i+1];
if (previous > current) std::swap(previous, current);
if (current > next) std::swap(current, next);
if (previous > current) std::swap(previous, current);
sum += current;
}
sum /= (_pointer - 2);
} else if (_pointer > 0) {
sum = _data[0];
}
return sum;
}
void resize(unsigned char size) {
if (_size == size) return;
_size = size;
if (_data) delete _data;
_data = new double[_size+1];
for (unsigned char i=0; i<=_size; i++) _data[i] = 0;
_pointer = 0;
}
protected:
unsigned char _pointer = 0;
double * _data = NULL;
};
#endif // SENSOR_SUPPORT

View File

@ -0,0 +1,51 @@
// -----------------------------------------------------------------------------
// Moving Average Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include <vector>
#include "BaseFilter.h"
class MovingAverageFilter : public BaseFilter {
public:
void add(double value) {
_sum = _sum + value - _data[_pointer];
_data[_pointer] = value;
_pointer = (_pointer + 1) % _size;
}
unsigned char count() {
return _pointer;
}
void reset() {}
double result() {
return _sum;
}
void resize(unsigned char size) {
if (_size == size) return;
_size = size;
if (_data) delete _data;
_data = new double[_size];
for (unsigned char i=0; i<_size; i++) _data[i] = 0;
_pointer = 0;
_sum = 0;
}
protected:
unsigned char _pointer = 0;
double _sum = 0;
double * _data = NULL;
};
#endif // SENSOR_SUPPORT

39
espurna/gpio.ino Executable file
View File

@ -0,0 +1,39 @@
/*
GPIO MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
unsigned int _gpio_locked = 0;
bool gpioValid(unsigned char gpio) {
if (gpio <= 5) return true;
if (12 <= gpio && gpio <= 15) return true;
return false;
}
bool gpioGetLock(unsigned char gpio) {
if (gpioValid(gpio)) {
unsigned int mask = 1 << gpio;
if ((_gpio_locked & mask) == 0) {
_gpio_locked |= mask;
DEBUG_MSG_P(PSTR("[GPIO] GPIO%u locked\n"), gpio);
return true;
}
}
DEBUG_MSG_P(PSTR("[GPIO] Failed getting lock for GPIO%u\n"), gpio);
return false;
}
bool gpioReleaseLock(unsigned char gpio) {
if (gpioValid(gpio)) {
unsigned int mask = 1 << gpio;
_gpio_locked &= ~mask;
DEBUG_MSG_P(PSTR("[GPIO] GPIO%u lock released\n"), gpio);
return true;
}
DEBUG_MSG_P(PSTR("[GPIO] Failed releasing lock for GPIO%u\n"), gpio);
return false;
}

297
espurna/homeassistant.ino Executable file
View File

@ -0,0 +1,297 @@
/*
HOME ASSISTANT MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>
bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
// SENSORS
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config.set("platform", "mqtt");
config.set("device_class", "sensor");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = String("\"") + magnitudeUnits(type) + String("\"");
}
void _haSendMagnitudes() {
for (unsigned char i=0; i<magnitudeCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/sensor/" +
getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
#endif // SENSOR_SUPPORT
// -----------------------------------------------------------------------------
// SWITCHES & LIGHTS
// -----------------------------------------------------------------------------
void _haSendSwitch(unsigned char i, JsonObject& config) {
String name = getSetting("hostname");
if (relayCount() > 1) {
name += String(" #") + String(i);
}
config.set("name", name);
config.set("platform", "mqtt");
if (relayCount()) {
config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
config["payload_on"] = String("1");
config["payload_off"] = String("0");
config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
config["payload_available"] = String("1");
config["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (i == 0) {
if (lightHasColor()) {
config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
}
if (lightChannels() > 3) {
config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
}
void _haSendSwitches() {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + type +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
}
// -----------------------------------------------------------------------------
String _haGetConfig() {
String output;
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
for (unsigned char i=0; i<relayCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
}
#if SENSOR_SUPPORT
for (unsigned char i=0; i<magnitudeCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
output += " - ";
first = false;
} else {
output += " ";
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
}
#endif
return output;
}
void _haSend() {
// Pending message to send?
if (!_haSendFlag) return;
// Are we connected?
if (!mqttConnected()) return;
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Send messages
_haSendSwitches();
#if SENSOR_SUPPORT
_haSendMagnitudes();
#endif
_haSendFlag = false;
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
_haSendFlag = (enabled != _haEnabled);
_haEnabled = enabled;
_haSend();
}
#if WEB_SUPPORT
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0);
}
void _haWebSocketOnSend(JsonObject& root) {
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "haconfig") == 0) {
String output = _haGetConfig();
output.replace(" ", "&nbsp;");
output.replace("\n", "<br />");
output = String("{\"haConfig\": \"") + output + String("\"}");
wsSend(client_id, output.c_str());
}
}
#endif
#if TERMINAL_SUPPORT
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
wsSend(_haWebSocketOnSend);
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
wsSend(_haWebSocketOnSend);
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
});
#if TERMINAL_SUPPORT
_haInitCommands();
#endif
}
#endif // HOMEASSISTANT_SUPPORT

379
espurna/i2c.ino Executable file
View File

@ -0,0 +1,379 @@
/*
I2C MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if I2C_SUPPORT
unsigned int _i2c_locked[16] = {0};
#if I2C_USE_BRZO
#include "brzo_i2c.h"
unsigned long _i2c_scl_frequency = 0;
#else
#include "Wire.h"
#endif
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
int _i2cClearbus(int sda, int scl) {
#if defined(TWCR) && defined(TWEN)
// Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
TWCR &= ~(_BV(TWEN));
#endif
// Make SDA (data) and SCL (clock) pins inputs with pullup
pinMode(sda, INPUT_PULLUP);
pinMode(scl, INPUT_PULLUP);
nice_delay(2500);
// Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
// IDE a chance to start uploaded the program
// before existing sketch confuses the IDE by sending Serial data.
// If it is held low the device cannot become the I2C master
// I2C bus error. Could not clear SCL clock line held low
boolean scl_low = (digitalRead(scl) == LOW);
if (scl_low) return 1;
boolean sda_low = (digitalRead(sda) == LOW);
int clockCount = 20; // > 2x9 clock
// While SDA is low for at most 20 cycles
while (sda_low && (clockCount > 0)) {
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high
pinMode(scl, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(scl, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(scl, INPUT); // release SCL LOW
pinMode(scl, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching
delayMicroseconds(10); // The >5uS is so that even the slowest I2C devices are handled
// loop waiting for SCL to become high only wait 2sec
scl_low = (digitalRead(scl) == LOW);
int counter = 20;
while (scl_low && (counter > 0)) {
counter--;
nice_delay(100);
scl_low = (digitalRead(scl) == LOW);
}
// If still low after 2 sec error
// I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
if (scl_low) return 2;
sda_low = (digitalRead(sda) == LOW); // and check SDA input again and loop
}
// If still low
// I2C bus error. Could not clear. SDA data line held low
if (sda_low) return 3;
// Pull SDA line low for "start" or "repeated start"
pinMode(sda, INPUT); // remove pullup
pinMode(sda, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control
// When there is only one I2C master a "start" or "repeat start" has the same function as a "stop" and clears the bus
// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(sda, INPUT); // remove output low
pinMode(sda, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // wait >5uS
pinMode(sda, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(scl, INPUT);
// Everything OK
return 0;
}
// ---------------------------------------------------------------------
// I2C API
// ---------------------------------------------------------------------
#if I2C_USE_BRZO
void i2c_wakeup(uint8_t address) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_end_transaction();
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
uint8_t buffer[1] = {value};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
return brzo_i2c_end_transaction();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, len, false);
return brzo_i2c_end_transaction();
}
uint8_t i2c_read_uint8(uint8_t address) {
uint8_t buffer[1] = {reg};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_read(buffer, 1, false);
brzo_i2c_end_transaction();
return buffer[0];
};
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
uint8_t buffer[1] = {reg};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
brzo_i2c_read(buffer, 1, false);
brzo_i2c_end_transaction();
return buffer[0];
};
uint16_t i2c_read_uint16(uint8_t address) {
uint8_t buffer[2] = {reg, 0};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
return (buffer[0] * 256) | buffer[1];
};
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
uint8_t buffer[2] = {reg, 0};
brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
brzo_i2c_write_uint8(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
return (buffer[0] * 256) | buffer[1];
};
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
brzo_i2c_start_transaction(address, _i2c_scl_frequency);
brzo_i2c_read(buffer, len, false);
brzo_i2c_end_transaction();
}
#else // not I2C_USE_BRZO
void i2c_wakeup(uint8_t address) {
Wire.beginTransmission((uint8_t) address);
Wire.endTransmission();
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) value);
return Wire.endTransmission();
}
uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
Wire.beginTransmission((uint8_t) address);
Wire.write(buffer, len);
return Wire.endTransmission();
}
uint8_t i2c_read_uint8(uint8_t address) {
uint8_t value;
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom((uint8_t) address, (uint8_t) 1);
value = Wire.read();
Wire.endTransmission();
return value;
};
uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
uint8_t value;
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t) address, (uint8_t) 1);
value = Wire.read();
Wire.endTransmission();
return value;
};
uint16_t i2c_read_uint16(uint8_t address) {
uint16_t value;
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom((uint8_t) address, (uint8_t) 2);
value = (Wire.read() * 256) | Wire.read();
Wire.endTransmission();
return value;
};
uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
uint16_t value;
Wire.beginTransmission((uint8_t) address);
Wire.write((uint8_t) reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t) address, (uint8_t) 2);
value = (Wire.read() * 256) | Wire.read();
Wire.endTransmission();
return value;
};
void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
Wire.beginTransmission((uint8_t) address);
Wire.requestFrom(address, (uint8_t) len);
for (int i=0; i<len; i++) buffer[i] = Wire.read();
Wire.endTransmission();
}
#endif // I2C_USE_BRZO
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
uint8_t buffer[2] = {reg, value};
return i2c_write_buffer(address, buffer, 2);
}
uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2) {
uint8_t buffer[3] = {reg, value1, value2};
return i2c_write_buffer(address, buffer, 3);
}
uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
uint8_t buffer[3];
buffer[0] = reg;
buffer[1] = (value >> 8) & 0xFF;
buffer[2] = (value >> 0) & 0xFF;
return i2c_write_buffer(address, buffer, 3);
}
uint8_t i2c_write_uint16(uint8_t address, uint16_t value) {
uint8_t buffer[2];
buffer[0] = (value >> 8) & 0xFF;
buffer[1] = (value >> 0) & 0xFF;
return i2c_write_buffer(address, buffer, 2);
}
uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) {
uint16_t temp = i2c_read_uint16(address, reg);
return (temp / 256) | (temp * 256);
};
int16_t i2c_read_int16(uint8_t address, uint8_t reg) {
return (int16_t) i2c_read_uint16(address, reg);
};
int16_t i2c_read_int16_le(uint8_t address, uint8_t reg) {
return (int16_t) i2c_read_uint16_le(address, reg);
};
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
void i2cClearBus() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
unsigned char scl = getSetting("i2cSCL", I2C_SCL_PIN).toInt();
DEBUG_MSG_P(PSTR("[I2C] Clear bus (response: %d)\n"), _i2cClearbus(sda, scl));
}
bool i2cCheck(unsigned char address) {
#if I2C_USE_BRZO
brzo_i2c_start_transaction(address, _i2c_scl_frequency);
brzo_i2c_ACK_polling(1000);
return brzo_i2c_end_transaction();
#else
Wire.beginTransmission(address);
return Wire.endTransmission();
#endif
}
bool i2cGetLock(unsigned char address) {
unsigned char index = address / 8;
unsigned char mask = 1 << (address % 8);
if (_i2c_locked[index] & mask) return false;
_i2c_locked[index] = _i2c_locked[index] | mask;
DEBUG_MSG_P(PSTR("[I2C] Address 0x%02X locked\n"), address);
return true;
}
bool i2cReleaseLock(unsigned char address) {
unsigned char index = address / 8;
unsigned char mask = 1 << (address % 8);
if (_i2c_locked[index] & mask) {
_i2c_locked[index] = _i2c_locked[index] & ~mask;
return true;
}
return false;
}
unsigned char i2cFind(size_t size, unsigned char * addresses, unsigned char &start) {
for (unsigned char i=start; i<size; i++) {
if (i2cCheck(addresses[i]) == 0) {
start = i;
return addresses[i];
}
}
return 0;
}
unsigned char i2cFind(size_t size, unsigned char * addresses) {
unsigned char start = 0;
return i2cFind(size, addresses, start);
}
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses) {
unsigned char start = 0;
unsigned char address = 0;
while (address = i2cFind(size, addresses, start)) {
if (i2cGetLock(address)) break;
start++;
}
return address;
}
void i2cScan() {
unsigned char nDevices = 0;
for (unsigned char address = 1; address < 127; address++) {
unsigned char error = i2cCheck(address);
if (error == 0) {
DEBUG_MSG_P(PSTR("[I2C] Device found at address 0x%02X\n"), address);
nDevices++;
}
}
if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n"));
}
void i2cSetup() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
unsigned char scl = getSetting("i2cSCL", I2C_SCL_PIN).toInt();
#if I2C_USE_BRZO
unsigned long cst = getSetting("i2cCST", I2C_CLOCK_STRETCH_TIME).toInt();
_i2c_scl_frequency = getSetting("i2cFreq", I2C_SCL_FREQUENCY).toInt();
brzo_i2c_setup(sda, scl, cst);
#else
Wire.begin(sda, scl);
#endif
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl);
#if I2C_CLEAR_BUS
i2cClearBus();
#endif
#if I2C_PERFORM_SCAN
i2cScan();
#endif
}
#endif

111
espurna/influxdb.ino Executable file
View File

@ -0,0 +1,111 @@
/*
INFLUXDB MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if INFLUXDB_SUPPORT
#include "ESPAsyncTCP.h"
#include "SyncClient.h"
bool _idb_enabled = false;
SyncClient _idb_client;
// -----------------------------------------------------------------------------
bool _idbWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "idb", 3) == 0);
}
void _idbWebSocketOnSend(JsonObject& root) {
root["idbVisible"] = 1;
root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
root["idbHost"] = getSetting("idbHost", INFLUXDB_HOST);
root["idbPort"] = getSetting("idbPort", INFLUXDB_PORT).toInt();
root["idbDatabase"] = getSetting("idbDatabase", INFLUXDB_DATABASE);
root["idbUsername"] = getSetting("idbUsername", INFLUXDB_USERNAME);
root["idbPassword"] = getSetting("idbPassword", INFLUXDB_PASSWORD);
}
void _idbConfigure() {
_idb_enabled = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
if (_idb_enabled && (getSetting("idbHost", INFLUXDB_HOST).length() == 0)) {
_idb_enabled = false;
setSetting("idbEnabled", 0);
}
}
// -----------------------------------------------------------------------------
bool idbSend(const char * topic, const char * payload) {
if (!_idb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true;
String h = getSetting("idbHost", INFLUXDB_HOST);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
unsigned int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
DEBUG_MSG("[INFLUXDB] Sending to %s:%u\n", host, port);
bool success = false;
_idb_client.setTimeout(2);
if (_idb_client.connect((const char *) host, port)) {
char data[128];
snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getSetting("hostname").c_str(), String(payload).c_str());
DEBUG_MSG("[INFLUXDB] Data: %s\n", data);
char request[256];
snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s",
getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(),
getSetting("idbUsername", INFLUXDB_USERNAME).c_str(), getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(),
host, port, strlen(data), data);
if (_idb_client.printf(request) > 0) {
while (_idb_client.connected() && _idb_client.available() == 0) delay(1);
while (_idb_client.available()) _idb_client.read();
if (_idb_client.connected()) _idb_client.stop();
success = true;
} else {
DEBUG_MSG("[INFLUXDB] Sent failed\n");
}
_idb_client.stop();
while (_idb_client.connected()) yield();
} else {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
}
free(host);
return success;
}
bool idbSend(const char * topic, unsigned char id, const char * payload) {
char measurement[64];
snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id);
return idbSend(topic, payload);
}
bool idbEnabled() {
return _idb_enabled;
}
void idbSetup() {
_idbConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend);
wsOnAfterParseRegister(_idbConfigure);
wsOnReceiveRegister(_idbWebSocketOnReceive);
#endif
}
#endif

112
espurna/ir.ino Executable file
View File

@ -0,0 +1,112 @@
/*
IR MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2017-2018 by François Déchery
*/
#if IR_SUPPORT
#include <IRremoteESP8266.h>
#include <IRrecv.h>
IRrecv * _ir_recv;
decode_results _ir_results;
// -----------------------------------------------------------------------------
// PRIVATE
// -----------------------------------------------------------------------------
void _irProcessCode(unsigned long code) {
static unsigned long last_code;
boolean found = false;
// Repeat last valid code
DEBUG_MSG_P(PSTR("[IR] Received 0x%06X\n"), code);
if (code == 0xFFFFFFFF) {
DEBUG_MSG_P(PSTR("[IR] Processing 0x%06X\n"), code);
code = last_code;
}
for (unsigned char i = 0; i < IR_BUTTON_COUNT ; i++) {
unsigned long button_code = pgm_read_dword(&IR_BUTTON[i][0]);
if (code == button_code) {
unsigned long button_mode = pgm_read_dword(&IR_BUTTON[i][1]);
unsigned long button_value = pgm_read_dword(&IR_BUTTON[i][2]);
if (button_mode == IR_BUTTON_MODE_STATE) {
relayStatus(0, button_value);
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (button_mode == IR_BUTTON_MODE_BRIGHTER) {
lightBrightnessStep(button_value ? 1 : -1);
nice_delay(150); //debounce
}
if (button_mode == IR_BUTTON_MODE_RGB) {
lightColor(button_value);
}
/*
#if LIGHT_PROVIDER == LIGHT_PROVIDER_FASTLED
if (button_mode == IR_BUTTON_MODE_EFFECT) {
_buttonAnimMode(button_value);
}
#endif
*/
/*
if (button_mode == IR_BUTTON_MODE_HSV) {
lightColor(button_value);
}
*/
lightUpdate(true, true);
#endif
found = true;
last_code = code;
break;
}
}
if (!found) {
DEBUG_MSG_P(PSTR("[IR] Ignoring code\n"));
}
}
// -----------------------------------------------------------------------------
// PUBLIC API
// -----------------------------------------------------------------------------
void irSetup() {
_ir_recv = new IRrecv(IR_PIN);
_ir_recv->enableIRIn();
// Register loop
espurnaRegisterLoop(irLoop);
}
void irLoop() {
if (_ir_recv->decode(&_ir_results)) {
unsigned long code = _ir_results.value;
_irProcessCode(code);
_ir_recv->resume(); // Receive the next value
}
}
#endif // IR_SUPPORT

290
espurna/led.ino Executable file
View File

@ -0,0 +1,290 @@
/*
LED MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
// -----------------------------------------------------------------------------
// LED
// -----------------------------------------------------------------------------
typedef struct {
unsigned char pin;
bool reverse;
unsigned char mode;
unsigned char relay;
} led_t;
std::vector<led_t> _leds;
bool _led_update = false; // For relay-based modes
// -----------------------------------------------------------------------------
bool _ledStatus(unsigned char id) {
if (id >= _ledCount()) return false;
bool status = digitalRead(_leds[id].pin);
return _leds[id].reverse ? !status : status;
}
bool _ledStatus(unsigned char id, bool status) {
if (id >=_ledCount()) return false;
digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status);
return status;
}
bool _ledToggle(unsigned char id) {
if (id >= _ledCount()) return false;
return _ledStatus(id, !_ledStatus(id));
}
unsigned char _ledMode(unsigned char id) {
if (id >= _ledCount()) return false;
return _leds[id].mode;
}
void _ledMode(unsigned char id, unsigned char mode) {
if (id >= _ledCount()) return;
_leds[id].mode = mode;
}
void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) {
if (id >= _ledCount()) return;
static unsigned long next = millis();
if (next < millis()) {
next += (_ledToggle(id) ? delayOn : delayOff);
}
}
#if WEB_SUPPORT
bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "led", 3) == 0);
}
void _ledWebSocketOnSend(JsonObject& root) {
if (_ledCount() == 0) return;
root["ledVisible"] = 1;
root["ledMode0"] = _ledMode(0);
}
#endif
#if MQTT_SUPPORT
void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_LED) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
mqttSubscribe(buffer);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (!t.startsWith(MQTT_TOPIC_LED)) return;
// Get led ID
unsigned int ledID = t.substring(strlen(MQTT_TOPIC_LED)+1).toInt();
if (ledID >= _ledCount()) {
DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
return;
}
// Check if LED is managed
if (_ledMode(ledID) != LED_MODE_MQTT) return;
// get value
unsigned char value = relayParsePayload(payload);
// Action to perform
if (value == 2) {
_ledToggle(ledID);
} else {
_ledStatus(ledID, value == 1);
}
}
}
#endif
unsigned char _ledCount() {
return _leds.size();
}
void _ledConfigure() {
for (unsigned int i=0; i < _leds.size(); i++) {
_ledMode(i, getSetting("ledMode", i, _ledMode(i)).toInt());
}
_led_update = true;
}
// -----------------------------------------------------------------------------
void ledUpdate(bool value) {
_led_update = value;
}
void ledSetup() {
#if LED1_PIN != GPIO_NONE
_leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY });
#endif
#if LED2_PIN != GPIO_NONE
_leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY });
#endif
#if LED3_PIN != GPIO_NONE
_leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY });
#endif
#if LED4_PIN != GPIO_NONE
_leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY });
#endif
#if LED5_PIN != GPIO_NONE
_leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY });
#endif
#if LED6_PIN != GPIO_NONE
_leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY });
#endif
#if LED7_PIN != GPIO_NONE
_leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY });
#endif
#if LED8_PIN != GPIO_NONE
_leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY });
#endif
for (unsigned int i=0; i < _leds.size(); i++) {
pinMode(_leds[i].pin, OUTPUT);
_ledStatus(i, false);
}
_ledConfigure();
#if MQTT_SUPPORT
mqttRegister(_ledMQTTCallback);
#endif
#if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend);
wsOnAfterParseRegister(_ledConfigure);
wsOnReceiveRegister(_ledWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
// Register loop
espurnaRegisterLoop(ledLoop);
}
void ledLoop() {
for (unsigned char i=0; i<_leds.size(); i++) {
if (_ledMode(i) == LED_MODE_WIFI) {
if (wifiConnected()) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
} else {
_ledBlink(i, 500, 500);
}
}
if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifiConnected()) {
if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
} else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
}
} else {
_ledBlink(i, 500, 500);
}
}
if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (wifiConnected()) {
if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
} else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
}
} else {
_ledBlink(i, 500, 500);
}
}
// Relay-based modes, update only if relays have been updated
if (!_led_update) continue;
if (_ledMode(i) == LED_MODE_FOLLOW) {
_ledStatus(i, relayStatus(_leds[i].relay-1));
}
if (_ledMode(i) == LED_MODE_FOLLOW_INVERSE) {
_ledStatus(i, !relayStatus(_leds[i].relay-1));
}
if (_ledMode(i) == LED_MODE_FINDME) {
bool status = true;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {
status = false;
break;
}
}
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_RELAY) {
bool status = false;
for (unsigned char k=0; k<relayCount(); k++) {
if (relayStatus(k)) {
status = true;
break;
}
}
_ledStatus(i, status);
}
if (_ledMode(i) == LED_MODE_ON) {
_ledStatus(i, true);
}
if (_ledMode(i) == LED_MODE_OFF) {
_ledStatus(i, false);
}
}
_led_update = false;
}

24
espurna/libs/EmbedisWrap.h Executable file
View File

@ -0,0 +1,24 @@
// -----------------------------------------------------------------------------
// Wrap class around Embedis (settings & terminal)
// -----------------------------------------------------------------------------
#pragma once
#include "Embedis.h"
class EmbedisWrap : public Embedis {
public:
EmbedisWrap(Stream& stream, size_t buflen = 128, size_t argvlen = 8): Embedis(stream, buflen, argvlen) {}
unsigned char getCommandCount() {
return commands.size();
}
String getCommandName(unsigned int i) {
if (i < commands.size()) return commands[i].name;
return String();
}
};

85
espurna/libs/StreamInjector.h Executable file
View File

@ -0,0 +1,85 @@
// -----------------------------------------------------------------------------
// Stream Injector
// -----------------------------------------------------------------------------
#pragma once
#include <Stream.h>
class StreamInjector : public Stream {
public:
typedef std::function<void(uint8_t ch)> writeCallback;
StreamInjector(Stream& serial, size_t buflen = 128) : _stream(serial), _buffer_size(buflen) {
_buffer = new char[buflen];
}
~StreamInjector() {
delete[] _buffer;
}
virtual void callback(writeCallback c) {
_callback = c;
}
virtual size_t write(uint8_t ch) {
if (_callback) _callback(ch);
return _stream.write(ch);
}
virtual int read() {
int ch = _stream.read();
if (ch == -1) {
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
_buffer_read = (_buffer_read + 1) % _buffer_size;
}
}
return ch;
}
virtual int available() {
unsigned int bytes = _stream.available();
if (_buffer_read > _buffer_write) {
bytes += (_buffer_write - _buffer_read + _buffer_size);
} else if (_buffer_read < _buffer_write) {
bytes += (_buffer_write - _buffer_read);
}
return bytes;
}
virtual int peek() {
int ch = _stream.peek();
if (ch == -1) {
if (_buffer_read != _buffer_write) {
ch = _buffer[_buffer_read];
}
}
return ch;
}
virtual void flush() {
_stream.flush();
_buffer_read = _buffer_write;
}
virtual void inject(char *data, size_t len) {
for (int i=0; i<len; i++) {
_buffer[_buffer_write] = data[i];
_buffer_write = (_buffer_write + 1) % _buffer_size;
}
}
private:
Stream& _stream;
char * _buffer;
unsigned char _buffer_size;
unsigned char _buffer_write = 0;
unsigned char _buffer_read = 0;
writeCallback _callback = NULL;
};

View File

@ -0,0 +1,87 @@
/*
WebSocketIncommingBuffer
Code by Hermann Kraus (https://bitbucket.org/hermr2d2/)
and slightly modified.
*/
#pragma once
#define MAX_WS_MSG_SIZE 4000
typedef std::function<void(AsyncWebSocketClient *client, uint8_t *data, size_t len)> AwsMessageHandler;
class WebSocketIncommingBuffer {
public:
WebSocketIncommingBuffer(AwsMessageHandler cb, bool terminate_string = true, bool cb_on_fragments = false) :
_cb(cb),
_terminate_string(terminate_string),
_cb_on_fragments(cb_on_fragments),
_buffer(0)
{}
~WebSocketIncommingBuffer() {
if (_buffer) delete _buffer;
}
void data_event(AsyncWebSocketClient *client, AwsFrameInfo *info, uint8_t *data, size_t len) {
if ((info->final || _cb_on_fragments)
&& !_terminate_string
&& info->index == 0
&& info->len == len) {
/* The whole message is in a single frame and we got all of it's
data therefore we can parse it without copying the data first.*/
_cb(client, data, len);
} else {
if (info->len > MAX_WS_MSG_SIZE) return;
/* Check if previous fragment was discarded because it was too long. */
//if (!_cb_on_fragments && info->num > 0 && !_buffer) return;
if (!_buffer) _buffer = new std::vector<uint8_t>();
if (info->index == 0) {
//New frame => preallocate memory
if (_cb_on_fragments) {
_buffer->reserve(info->len + 1);
} else {
/* The current fragment would lead to a message which is
too long. So discard everything received so far. */
if (info->len + _buffer->size() > MAX_WS_MSG_SIZE) {
delete _buffer;
_buffer = 0;
return;
} else {
_buffer->reserve(info->len + _buffer->size() + 1);
}
}
}
//assert(_buffer->size() == info->index);
_buffer->insert(_buffer->end(), data, data+len);
if (info->index + len == info->len
&& (info->final || _cb_on_fragments)) {
// Frame/message complete
if (_terminate_string) _buffer->push_back(0);
_cb(client, _buffer->data(), _buffer->size());
_buffer->clear();
}
}
}
private:
AwsMessageHandler _cb;
bool _cb_on_fragments;
bool _terminate_string;
std::vector<uint8_t> *_buffer;
};

33
espurna/libs/pwm.h Executable file
View File

@ -0,0 +1,33 @@
#ifndef __PWM_H__
#define __PWM_H__
/*pwm.h: function and macro definition of PWM API , driver level */
/*user_light.h: user interface for light API, user level*/
/*user_light_adj: API for color changing and lighting effects, user level*/
/*NOTE!! : DO NOT CHANGE THIS FILE*/
/*SUPPORT UP TO 8 PWM CHANNEL*/
//#define PWM_CHANNEL_NUM_MAX 8
struct pwm_param {
uint32 period;
uint32 freq;
uint32 duty[PWM_CHANNEL_NUM_MAX]; //PWM_CHANNEL<=8
};
/* pwm_init should be called only once, for now */
void pwm_init(uint32 period, uint32 *duty,uint32 pwm_channel_num,uint32 (*pin_info_list)[3]);
void pwm_start(void);
void pwm_set_duty(uint32 duty, uint8 channel);
uint32 pwm_get_duty(uint8 channel);
void pwm_set_period(uint32 period);
uint32 pwm_get_period(void);
uint32 get_pwm_version(void);
void set_pwm_debug_en(uint8 print_en);
#endif

1054
espurna/light.ino Executable file

File diff suppressed because it is too large Load Diff

18
espurna/llmnr.ino Executable file
View File

@ -0,0 +1,18 @@
/*
LLMNR MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if LLMNR_SUPPORT
#include <ESP8266LLMNR.h>
void llmnrSetup() {
LLMNR.begin(getSetting("hostname").c_str());
DEBUG_MSG_P(PSTR("[LLMNR] Configured\n"));
}
#endif // LLMNR_SUPPORT

132
espurna/mdns.ino Executable file
View File

@ -0,0 +1,132 @@
/*
MDNS MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
// -----------------------------------------------------------------------------
// mDNS Server
// -----------------------------------------------------------------------------
#if MDNS_SERVER_SUPPORT
#include <ESP8266mDNS.h>
#if MQTT_SUPPORT
void _mdnsFindMQTT() {
int count = MDNS.queryService("mqtt", "tcp");
DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count);
for (int i=0; i<count; i++) {
DEBUG_MSG_P(PSTR("[MQTT] Broker at %s:%d\n"), MDNS.IP(i).toString().c_str(), MDNS.port(i));
mqttSetBrokerIfNone(MDNS.IP(i), MDNS.port(i));
}
}
#endif
void _mdnsServerStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else {
DEBUG_MSG_P(PSTR("[MDNS] FAIL\n"));
}
}
// -----------------------------------------------------------------------------
void mdnsServerSetup() {
#if WEB_SUPPORT
MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt());
#endif
#if TELNET_SUPPORT
MDNS.addService("telnet", "tcp", TELNET_PORT);
#endif
// Public ESPurna related txt for OTA discovery
MDNS.addServiceTxt("arduino", "tcp", "app_name", APP_NAME);
MDNS.addServiceTxt("arduino", "tcp", "app_version", APP_VERSION);
MDNS.addServiceTxt("arduino", "tcp", "mac", WiFi.macAddress());
MDNS.addServiceTxt("arduino", "tcp", "target_board", getBoardName());
{
char buffer[6] = {0};
itoa(ESP.getFlashChipRealSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "mem_size", (const char *) buffer);
}
{
char buffer[6] = {0};
itoa(ESP.getFlashChipSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "sdk_size", (const char *) buffer);
}
{
char buffer[6] = {0};
itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
}
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) {
_mdnsServerStart();
#if MQTT_SUPPORT
_mdnsFindMQTT();
#endif // MQTT_SUPPORT
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
_mdnsServerStart();
}
});
}
#endif // MDNS_SERVER_SUPPORT
// -----------------------------------------------------------------------------
// mDNS Client
// -----------------------------------------------------------------------------
#if MDNS_CLIENT_SUPPORT
#include <WiFiUdp.h>
#include <mDNSResolver.h>
using namespace mDNSResolver;
WiFiUDP _mdns_udp;
Resolver _mdns_resolver(_mdns_udp);
String mdnsResolve(char * name) {
if (strlen(name) == 0) return String();
if (WiFi.status() != WL_CONNECTED) return String();
_mdns_resolver.setLocalIP(WiFi.localIP());
IPAddress ip = _mdns_resolver.search(name);
if (ip == INADDR_NONE) return String(name);
DEBUG_MSG_P(PSTR("[MDNS] '%s' resolved to '%s'\n"), name, ip.toString().c_str());
return ip.toString();
}
String mdnsResolve(String name) {
return mdnsResolve((char *) name.c_str());
}
void mdnsClientSetup() {
// Register loop
espurnaRegisterLoop(mdnsClientLoop);
}
void mdnsClientLoop() {
_mdns_resolver.loop();
}
#endif // MDNS_CLIENT_SUPPORT

894
espurna/migrate.ino Executable file
View File

@ -0,0 +1,894 @@
/*
MIGRATE MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
void _cmpMoveIndexDown(const char * key, int offset = 0) {
if (hasSetting(key, 0)) return;
for (unsigned char index = 1; index < SETTINGS_MAX_LIST_COUNT; index++) {
if (hasSetting(key, index)) {
setSetting(key, index - 1, getSetting(key, index).toInt() + offset);
} else {
delSetting(key, index - 1);
}
}
}
// Configuration versions
//
// 1: based on Embedis, no board definitions
// 2: based on Embedis, with board definitions 1-based
// 3: based on Embedis, with board definitions 0-based
void migrate() {
// Get config version
unsigned int board = getSetting("board", 0).toInt();
unsigned int config_version = getSetting("cfg", board > 0 ? 2 : 1).toInt();
// Update if not on latest version
if (config_version == CFG_VERSION) return;
setSetting("cfg", CFG_VERSION);
if (config_version == 2) {
_cmpMoveIndexDown("ledGPIO");
_cmpMoveIndexDown("ledLogic");
_cmpMoveIndexDown("btnGPIO");
_cmpMoveIndexDown("btnRelay", -1);
_cmpMoveIndexDown("relayGPIO");
_cmpMoveIndexDown("relayType");
}
if (config_version == 1) {
#if defined(NODEMCU_LOLIN)
setSetting("board", 2);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(WEMOS_D1_MINI_RELAYSHIELD)
setSetting("board", 3);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_BASIC)
setSetting("board", 4);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_TH)
setSetting("board", 5);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_SV)
setSetting("board", 6);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_TOUCH)
setSetting("board", 7);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_POW)
setSetting("board", 8);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 5);
setSetting("cf1GPIO", 13);
setSetting("cfGPIO", 14);
#elif defined(ITEAD_SONOFF_DUAL)
setSetting("board", 9);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnRelay", 0, 0xFF);
setSetting("btnRelay", 1, 0xFF);
setSetting("btnRelay", 2, 0);
setSetting("relayProvider", RELAY_PROVIDER_DUAL);
setSetting("relays", 2);
#elif defined(ITEAD_1CH_INCHING)
setSetting("board", 10);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_4CH)
setSetting("board", 11);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnGPIO", 3, 14);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 2);
setSetting("btnRelay", 3, 3);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SLAMPHER)
setSetting("board", 12);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_S20)
setSetting("board", 13);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ELECTRODRAGON_WIFI_IOT)
setSetting("board", 14);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 2);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 13);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(WORKCHOICE_ECOPLUG)
setSetting("board", 15);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(JANGOE_WIFI_RELAY_NC)
setSetting("board", 16);
setSetting("btnGPIO", 0, 12);
setSetting("btnGPIO", 1, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 2);
setSetting("relayGPIO", 1, 14);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("relayType", 1, RELAY_TYPE_INVERSE);
#elif defined(JANGOE_WIFI_RELAY_NO)
setSetting("board", 17);
setSetting("btnGPIO", 0, 12);
setSetting("btnGPIO", 1, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 2);
setSetting("relayGPIO", 1, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(OPENENERGYMONITOR_MQTT_RELAY)
setSetting("board", 18);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(JORGEGARCIA_WIFI_RELAYS)
setSetting("board", 19);
setSetting("relayGPIO", 0, 0);
setSetting("relayGPIO", 1, 2);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("relayType", 1, RELAY_TYPE_INVERSE);
#elif defined(AITHINKER_AI_LIGHT)
setSetting("board", 20);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 13);
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER)
setSetting("board", 21);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 14);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 2, 12);
setSetting("chGPIO", 3, 13);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER_IR)
setSetting("board", 21);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 5);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 14);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(ITEAD_MOTOR)
setSetting("board", 22);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TINKERMAN_ESPURNA_H06)
setSetting("board", 23);
setSetting("ledGPIO", 0, 5);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 4);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("selGPIO", 2);
setSetting("cf1GPIO", 13);
setSetting("cfGPIO", 14);
#elif defined(HUACANXING_H801)
setSetting("board", 24);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 5);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 15);
setSetting("chGPIO", 1, 13);
setSetting("chGPIO", 2, 12);
setSetting("chGPIO", 3, 14);
setSetting("chGPIO", 4, 4);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
#elif defined(ITEAD_BNSZ01)
setSetting("board", 25);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 12);
setSetting("chLogic", 0, 0);
setSetting("relays", 1);
#elif defined(ITEAD_SONOFF_RFBRIDGE)
setSetting("board", 26);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("relayProvider", RELAY_PROVIDER_RFBRIDGE);
setSetting("relays", 6);
#elif defined(ITEAD_SONOFF_4CH_PRO)
setSetting("board", 27);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnGPIO", 3, 14);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 2);
setSetting("btnRelay", 3, 3);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_B1)
setSetting("board", 28);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9231);
setSetting("myChips", 2);
setSetting("myDIGPIO", 12);
setSetting("myDCKIGPIO", 14);
setSetting("relays", 1);
#elif defined(ITEAD_SONOFF_LED)
setSetting("board", 29);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 12);
setSetting("chLogic", 0, 0);
setSetting("chGPIO", 1, 14);
setSetting("chLogic", 1, 0);
setSetting("relays", 1);
#elif defined(ITEAD_SONOFF_T1_1CH)
setSetting("board", 30);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 9);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_T1_2CH)
setSetting("board", 31);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 10);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_T1_3CH)
setSetting("board", 32);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 2);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayGPIO", 2, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_RF)
setSetting("board", 33);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(WION_50055)
setSetting("board", 34);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(EXS_WIFI_RELAY_V31)
setSetting("board", 35);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 13);
setSetting("relayResetGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(HUACANXING_H802)
setSetting("board", 36);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 14);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(GENERIC_V9261F)
setSetting("board", 37);
#elif defined(GENERIC_ECH1560)
setSetting("board", 38);
#elif defined(TINKERMAN_ESPURNA_H08)
setSetting("board", 39);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 4);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 5);
setSetting("cf1GPIO", 13);
setSetting("cfGPIO", 14);
#elif defined(MANCAVEMADE_ESPLIVE)
setSetting("board", 40);
setSetting("btnGPIO", 0, 4);
setSetting("btnGPIO", 1, 5);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 13);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(INTERMITTECH_QUINLED)
setSetting("board", 41);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 0, 1);
setSetting("ledLogic", 0, 1);
setSetting("chGPIO", 0, 0);
setSetting("chGPIO", 1, 2);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("relays", 1);
#elif defined(MAGICHOME_LED_CONTROLLER_20)
setSetting("board", 42);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 5);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(ARILUX_AL_LC06)
setSetting("board", 43);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 14);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chGPIO", 4, 5);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
#elif defined(XENON_SM_PW702U)
setSetting("board", 44);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(AUTHOMETION_LYT8266)
setSetting("board", 45);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 13);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 14);
setSetting("chGPIO", 3, 2);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
setSetting("enGPIO", 15);
#elif defined(ARILUX_E27)
setSetting("board", 46);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 13);
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);
#elif defined(YJZK_SWITCH_2CH)
setSetting("board", 47);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("ledWifi", 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ITEAD_SONOFF_DUAL_R2)
setSetting("board", 48);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnGPIO", 1, 9);
setSetting("btnGPIO", 2, 10);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_8CH)
setSetting("board", 49);
setSetting("relayGPIO", 0, 0);
setSetting("relayGPIO", 1, 2);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 5);
setSetting("relayGPIO", 4, 12);
setSetting("relayGPIO", 5, 13);
setSetting("relayGPIO", 6, 14);
setSetting("relayGPIO", 7, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
setSetting("relayType", 4, RELAY_TYPE_NORMAL);
setSetting("relayType", 5, RELAY_TYPE_NORMAL);
setSetting("relayType", 6, RELAY_TYPE_NORMAL);
setSetting("relayType", 7, RELAY_TYPE_NORMAL);
#elif defined(ARILUX_AL_LC01)
setSetting("board", 50);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 5);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 14);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(ARILUX_AL_LC11)
setSetting("board", 51);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 5);
setSetting("chGPIO", 1, 4);
setSetting("chGPIO", 2, 14);
setSetting("chGPIO", 3, 13);
setSetting("chGPIO", 4, 12);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
#elif defined(ARILUX_AL_LC02)
setSetting("board", 52);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 2, 13);
setSetting("chGPIO", 3, 15);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(KMC_70011)
setSetting("board", 53);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 14);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(GIZWITS_WITTY_CLOUD)
setSetting("board", 54);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 15);
setSetting("chGPIO", 1, 12);
setSetting("chGPIO", 2, 13);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("relays", 1);
#elif defined(EUROMATE_WIFI_STECKER_SCHUKO)
setSetting("board", 55);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_POWERSTRIP02)
setSetting("board", 56);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 12);
setSetting("relayGPIO", 3, 14);
setSetting("relayGPIO", 4, 16);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
setSetting("relayType", 1, RELAY_TYPE_INVERSE);
setSetting("relayType", 2, RELAY_TYPE_INVERSE);
setSetting("relayType", 3, RELAY_TYPE_INVERSE);
setSetting("relayType", 4, RELAY_TYPE_NORMAL); // Not a relay. USB ports on/off
setSetting("ledGPIO", 0, 0); // 1 blue led
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 3); // 3 red leds
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 1);
#elif defined(LINGAN_SWA1)
setSetting("board", 57);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(HEYGO_HY02)
setSetting("board", 58);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 3);
setSetting("cf1GPIO", 14);
setSetting("cfGPIO", 5);
#elif defined(MAXCIO_WUS002S)
setSetting("board", 59);
setSetting("ledGPIO", 0, 3);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 13);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
#elif defined(YIDIAN_XSSSA05)
setSetting("board", 60);
setSetting("ledGPIO", 0, 0);
setSetting("ledLogic", 0, 0);
setSetting("ledGPIO", 1, 5);
setSetting("ledLogic", 1, 0);
setSetting("ledGPIO", 2, 2);
setSetting("ledLogic", 2, 0);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(TONBUX_XSSSA06)
setSetting("board", 61);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GREEN_ESP8266RELAY)
setSetting("board", 62);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 5);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(IKE_ESPIKE)
setSetting("board", 63);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("btnGPIO", 1, 12);
setSetting("btnRelay", 1, 1);
setSetting("btnGPIO", 2, 13);
setSetting("btnRelay", 2, 2);
setSetting("relayGPIO", 0, 4);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 2, 16);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
#elif defined(ARNIEX_SWIFITCH)
setSetting("board", 64);
setSetting("ledGPIO", 0, 12);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 4);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_INVERSE);
#elif defined(GENERIC_ESP01SRELAY40)
setSetting("board", 65);
setSetting("ledGPIO", 0, 2);
setSetting("ledLogic", 0, 0);
setSetting("relayGPIO", 0, 0);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(GENERIC_ESP01SRGBLED10)
setSetting("board", 66);
setSetting("ledGPIO", 0, 2);
#elif defined(HELTEC_TOUCHRELAY)
setSetting("board", 67);
setSetting("btnGPIO", 0, 14);
setSetting("btnRelay", 0, 1);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#else
// Allow users to define new settings without migration config
//#error "UNSUPPORTED HARDWARE!"
#endif
}
saveSettings();
}

843
espurna/mqtt.ino Executable file
View File

@ -0,0 +1,843 @@
/*
MQTT MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if MQTT_SUPPORT
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoJson.h>
#include <vector>
#include <Ticker.h>
#if MQTT_USE_ASYNC // Using AsyncMqttClient
#include <AsyncMqttClient.h>
AsyncMqttClient _mqtt;
#else // Using PubSubClient
#include <PubSubClient.h>
PubSubClient _mqtt;
bool _mqtt_connected = false;
WiFiClient _mqtt_client;
#if ASYNC_TCP_SSL_ENABLED
WiFiClientSecure _mqtt_client_secure;
#endif // ASYNC_TCP_SSL_ENABLED
#endif // MQTT_USE_ASYNC
bool _mqtt_enabled = MQTT_ENABLED;
bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
unsigned char _mqtt_qos = MQTT_QOS;
bool _mqtt_retain = MQTT_RETAIN;
unsigned long _mqtt_keepalive = MQTT_KEEPALIVE;
String _mqtt_topic;
String _mqtt_topic_json;
String _mqtt_setter;
String _mqtt_getter;
bool _mqtt_forward;
char *_mqtt_user = 0;
char *_mqtt_pass = 0;
char *_mqtt_will;
char *_mqtt_clientid;
#if MQTT_SKIP_RETAINED
unsigned long _mqtt_connected_at = 0;
#endif
std::vector<mqtt_callback_f> _mqtt_callbacks;
typedef struct {
unsigned char parent = 255;
char * topic;
char * message = NULL;
} mqtt_message_t;
std::vector<mqtt_message_t> _mqtt_queue;
Ticker _mqtt_flush_ticker;
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
void _mqttConnect() {
// Do not connect if disabled
if (!_mqtt_enabled) return;
// Do not connect if already connected
if (_mqtt.connected()) return;
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
String h = getSetting("mqttServer", MQTT_SERVER);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
if (_mqtt_user) free(_mqtt_user);
if (_mqtt_pass) free(_mqtt_pass);
if (_mqtt_will) free(_mqtt_will);
if (_mqtt_clientid) free(_mqtt_clientid);
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
_mqtt_will = strdup(mqttTopic(MQTT_TOPIC_STATUS, false).c_str());
_mqtt_clientid = strdup(getSetting("mqttClientID", getIdentifier()).c_str());
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
#if MQTT_USE_ASYNC
_mqtt.setServer(host, port);
_mqtt.setClientId(_mqtt_clientid);
_mqtt.setKeepAlive(_mqtt_keepalive);
_mqtt.setCleanSession(false);
_mqtt.setWill(_mqtt_will, _mqtt_qos, _mqtt_retain, "0");
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
_mqtt.setCredentials(_mqtt_user, _mqtt_pass);
}
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
_mqtt.setSecure(secure);
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (sslFingerPrintArray(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
}
}
#endif // ASYNC_TCP_SSL_ENABLED
DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive);
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
_mqtt.connect();
#else // not MQTT_USE_ASYNC
bool response = true;
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (sslFingerPrintChar(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
response = false;
}
_mqtt_client_secure.stop();
yield();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
response = false;
}
} else {
DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
response = false;
}
} else {
_mqtt.setClient(_mqtt_client);
}
#else // not ASYNC_TCP_SSL_ENABLED
_mqtt.setClient(_mqtt_client);
#endif // ASYNC_TCP_SSL_ENABLED
if (response) {
_mqtt.setServer(host, port);
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
response = _mqtt.connect(_mqtt_clientid, _mqtt_user, _mqtt_pass, _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
} else {
response = _mqtt.connect(_mqtt_clientid, _mqtt_will, _mqtt_qos, _mqtt_retain, "0");
}
DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0);
DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive);
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
}
if (response) {
_mqttOnConnect();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
}
#endif // MQTT_USE_ASYNC
free(host);
}
void _mqttConfigure() {
// Get base topic
_mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders
_mqtt_topic.replace("{identifier}", getSetting("hostname"));
_mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#");
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
String mac = WiFi.macAddress();
mac.replace(":", "");
_mqtt_topic.replace("{mac}", mac);
// Getters and setters
_mqtt_setter = getSetting("mqttSetter", MQTT_SETTER);
_mqtt_getter = getSetting("mqttGetter", MQTT_GETTER);
_mqtt_forward = !_mqtt_getter.equals(_mqtt_setter);
// MQTT options
_mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt();
_mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1;
_mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt();
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
// Enable
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
mqttEnabled(false);
} else {
_mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1;
}
_mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1);
mqttQueueTopic(MQTT_TOPIC_JSON);
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
}
unsigned long _mqttNextMessageId() {
static unsigned long id = 0;
// just reboot, get last count from EEPROM
if (id == 0) {
// read id from EEPROM and shift it
id = EEPROM.read(EEPROM_MESSAGE_ID);
if (id == 0xFF) {
// There was nothing in EEPROM,
// next message is first message
id = 0;
} else {
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 3);
// Calculate next block and start from there
id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT));
}
}
// Save to EEPROM every MQTT_MESSAGE_ID_SHIFT
if (id % MQTT_MESSAGE_ID_SHIFT == 0) {
EEPROM.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROM.commit();
}
id++;
return id;
}
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _mqttWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "mqtt", 3) == 0);
}
void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttVisible"] = 1;
root["mqttStatus"] = mqttConnected();
root["mqttEnabled"] = mqttEnabled();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser", MQTT_USER);
root["mqttClientID"] = getSetting("mqttClientID");
root["mqttPassword"] = getSetting("mqttPassword", MQTT_PASS);
root["mqttKeep"] = _mqtt_keepalive;
root["mqttRetain"] = _mqtt_retain;
root["mqttQoS"] = _mqtt_qos;
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
root["mqttUseSSL"] = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
#endif
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
}
#endif
// -----------------------------------------------------------------------------
// SETTINGS
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _mqttInitCommands() {
settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) {
_mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
// MQTT Callbacks
// -----------------------------------------------------------------------------
void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
// Subscribe to internal action topics
mqttSubscribe(MQTT_TOPIC_ACTION);
// Flag system to send heartbeat
systemSendHeartbeat();
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
// Actions
if (t.equals(MQTT_TOPIC_ACTION)) {
if (strcmp(payload, MQTT_ACTION_RESET) == 0) {
deferredReset(100, CUSTOM_RESET_MQTT);
}
}
}
}
void _mqttOnConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#if MQTT_SKIP_RETAINED
_mqtt_connected_at = millis();
#endif
// Clean subscriptions
mqttUnsubscribeRaw("#");
// Send connect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL);
}
}
void _mqttOnDisconnect() {
DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
// Send disconnect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(_mqtt_callbacks[i])(MQTT_DISCONNECT_EVENT, NULL, NULL);
}
}
void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
if (len == 0) return;
char message[len + 1];
strlcpy(message, (char *) payload, len + 1);
#if MQTT_SKIP_RETAINED
if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) {
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message);
return;
}
#endif
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
// Send message event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message);
}
}
// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------
/**
Returns the magnitude part of a topic
@param topic the full MQTT topic
@return String object with the magnitude part.
*/
String mqttMagnitude(char * topic) {
String pattern = _mqtt_topic + _mqtt_setter;
int position = pattern.indexOf("#");
if (position == -1) return String();
String start = pattern.substring(0, position);
String end = pattern.substring(position + 1);
String magnitude = String(topic);
if (magnitude.startsWith(start) && magnitude.endsWith(end)) {
magnitude.replace(start, "");
magnitude.replace(end, "");
} else {
magnitude = String();
}
return magnitude;
}
/**
Returns a full MQTT topic from the magnitude
@param magnitude the magnitude part of the topic.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, bool is_set) {
String output = _mqtt_topic;
output.replace("#", magnitude);
output += is_set ? _mqtt_setter : _mqtt_getter;
return output;
}
/**
Returns a full MQTT topic from the magnitude
@param magnitude the magnitude part of the topic.
@param index index of the magnitude when more than one such magnitudes.
@param is_set whether to build a command topic (true)
or a state topic (false).
@return String full MQTT topic.
*/
String mqttTopic(const char * magnitude, unsigned int index, bool is_set) {
char buffer[strlen(magnitude)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), magnitude, index);
return mqttTopic(buffer, is_set);
}
// -----------------------------------------------------------------------------
void mqttSendRaw(const char * topic, const char * message, bool retain) {
if (_mqtt.connected()) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
#else
_mqtt.publish(topic, message, retain);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif
}
}
void mqttSendRaw(const char * topic, const char * message) {
mqttSendRaw (topic, message, _mqtt_retain);
}
void mqttSend(const char * topic, const char * message, bool force, bool retain) {
bool useJson = force ? false : _mqtt_use_json;
// Equeue message
if (useJson) {
// Set default queue topic
mqttQueueTopic(MQTT_TOPIC_JSON);
// Enqueue new message
mqttEnqueue(topic, message);
// Reset flush timer
_mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
// Send it right away
} else {
mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain);
}
}
void mqttSend(const char * topic, const char * message, bool force) {
mqttSend(topic, message, force, _mqtt_retain);
}
void mqttSend(const char * topic, const char * message) {
mqttSend(topic, message, false);
}
void mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) {
char buffer[strlen(topic)+5];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
mqttSend(buffer, message, force, retain);
}
void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
mqttSend(topic, index, message, force, _mqtt_retain);
}
void mqttSend(const char * topic, unsigned int index, const char * message) {
mqttSend(topic, index, message, false);
}
// -----------------------------------------------------------------------------
unsigned char _mqttBuildTree(JsonObject& root, char parent) {
unsigned char count = 0;
// Add enqueued messages
for (unsigned char i=0; i<_mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
if (element.parent == parent) {
++count;
JsonObject& elements = root.createNestedObject(element.topic);
unsigned char num = _mqttBuildTree(elements, i);
if (0 == num) {
root.set(element.topic, element.message);
}
}
}
return count;
}
void mqttFlush() {
if (!_mqtt.connected()) return;
if (_mqtt_queue.size() == 0) return;
// Build tree recursively
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
_mqttBuildTree(root, 255);
// Add extra propeties
#if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
if (ntpSynced()) root[MQTT_TOPIC_TIME] = ntpDateTime();
#endif
#if MQTT_ENQUEUE_MAC
root[MQTT_TOPIC_MAC] = WiFi.macAddress();
#endif
#if MQTT_ENQUEUE_HOSTNAME
root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname");
#endif
#if MQTT_ENQUEUE_IP
root[MQTT_TOPIC_IP] = getIP();
#endif
#if MQTT_ENQUEUE_MESSAGE_ID
root[MQTT_TOPIC_MESSAGE_ID] = _mqttNextMessageId();
#endif
// Send
String output;
root.printTo(output);
mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
// Clear queue
for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
mqtt_message_t element = _mqtt_queue[i];
free(element.topic);
if (element.message) {
free(element.message);
}
}
_mqtt_queue.clear();
}
void mqttQueueTopic(const char * topic) {
String t = mqttTopic(topic, false);
if (!t.equals(_mqtt_topic_json)) {
mqttFlush();
_mqtt_topic_json = t;
}
}
int8_t mqttEnqueue(const char * topic, const char * message, unsigned char parent) {
// Queue is not meant to send message "offline"
// We must prevent the queue does not get full while offline
if (!_mqtt.connected()) return -1;
// Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached
if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush();
int8_t index = _mqtt_queue.size();
// Enqueue new message
mqtt_message_t element;
element.parent = parent;
element.topic = strdup(topic);
if (NULL != message) {
element.message = strdup(message);
}
_mqtt_queue.push_back(element);
return index;
}
int8_t mqttEnqueue(const char * topic, const char * message) {
return mqttEnqueue(topic, message, 255);
}
// -----------------------------------------------------------------------------
void mqttSubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.subscribe(topic, _mqtt_qos);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
#endif
}
}
void mqttSubscribe(const char * topic) {
mqttSubscribeRaw(mqttTopic(topic, true).c_str());
}
void mqttUnsubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s\n"), topic);
#endif
}
}
void mqttUnsubscribe(const char * topic) {
mqttUnsubscribeRaw(mqttTopic(topic, true).c_str());
}
// -----------------------------------------------------------------------------
void mqttEnabled(bool status) {
_mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
}
bool mqttEnabled() {
return _mqtt_enabled;
}
bool mqttConnected() {
return _mqtt.connected();
}
void mqttDisconnect() {
if (_mqtt.connected()) {
DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
_mqtt.disconnect();
}
}
bool mqttForward() {
return _mqtt_forward;
}
void mqttRegister(mqtt_callback_f callback) {
_mqtt_callbacks.push_back(callback);
}
void mqttSetBroker(IPAddress ip, unsigned int port) {
setSetting("mqttServer", ip.toString());
setSetting("mqttPort", port);
mqttEnabled(MQTT_AUTOCONNECT);
}
void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) {
if (!hasSetting("mqttServer")) mqttSetBroker(ip, port);
}
void mqttReset() {
_mqttConfigure();
mqttDisconnect();
}
// -----------------------------------------------------------------------------
// Initialization
// -----------------------------------------------------------------------------
void mqttSetup() {
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",
MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED"
);
#if MQTT_USE_ASYNC
_mqtt.onConnect([](bool sessionPresent) {
_mqttOnConnect();
});
_mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) {
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[MQTT] TCP Disconnected\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
DEBUG_MSG_P(PSTR("[MQTT] Identifier Rejected\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
DEBUG_MSG_P(PSTR("[MQTT] Server unavailable\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
DEBUG_MSG_P(PSTR("[MQTT] Malformed credentials\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
DEBUG_MSG_P(PSTR("[MQTT] Not authorized\n"));
}
#if ASYNC_TCP_SSL_ENABLED
if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) {
DEBUG_MSG_P(PSTR("[MQTT] Bad fingerprint\n"));
}
#endif
_mqttOnDisconnect();
});
_mqtt.onMessage([](char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
_mqttOnMessage(topic, payload, len);
});
_mqtt.onSubscribe([](uint16_t packetId, uint8_t qos) {
DEBUG_MSG_P(PSTR("[MQTT] Subscribe ACK for PID %d\n"), packetId);
});
_mqtt.onPublish([](uint16_t packetId) {
DEBUG_MSG_P(PSTR("[MQTT] Publish ACK for PID %d\n"), packetId);
});
#else // not MQTT_USE_ASYNC
_mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
_mqttOnMessage(topic, (char *) payload, length);
});
#endif // MQTT_USE_ASYNC
_mqttConfigure();
mqttRegister(_mqttCallback);
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnReceiveRegister(_mqttWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT
_mqttInitCommands();
#endif
// Register loop
espurnaRegisterLoop(mqttLoop);
}
void mqttLoop() {
if (WiFi.status() != WL_CONNECTED) return;
#if MQTT_USE_ASYNC
_mqttConnect();
#else // not MQTT_USE_ASYNC
if (_mqtt.connected()) {
_mqtt.loop();
} else {
if (_mqtt_connected) {
_mqttOnDisconnect();
_mqtt_connected = false;
}
_mqttConnect();
}
#endif
}
#endif // MQTT_SUPPORT

20
espurna/netbios.ino Executable file
View File

@ -0,0 +1,20 @@
/*
NETBIOS MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if NETBIOS_SUPPORT
#include <ESP8266NetBIOS.h>
void netbiosSetup() {
static WiFiEventHandler _netbios_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
NBNS.begin(getSetting("hostname").c_str());
DEBUG_MSG_P(PSTR("[NETBIOS] Configured\n"));
});
}
#endif // NETBIOS_SUPPORT

180
espurna/nofuss.ino Executable file
View File

@ -0,0 +1,180 @@
/*
NOFUSS MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if NOFUSS_SUPPORT
#include "NoFUSSClient.h"
unsigned long _nofussLastCheck = 0;
unsigned long _nofussInterval = 0;
bool _nofussEnabled = false;
// -----------------------------------------------------------------------------
// NOFUSS
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _nofussWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "nofuss", 6) == 0);
}
void _nofussWebSocketOnSend(JsonObject& root) {
root["nofussVisible"] = 1;
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
}
#endif
void _nofussConfigure() {
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
#if MDNS_CLIENT_SUPPORT
nofussServer = mdnsResolve(nofussServer);
#endif
if (nofussServer.length() == 0) {
setSetting("nofussEnabled", 0);
_nofussEnabled = false;
} else {
_nofussEnabled = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
}
_nofussInterval = getSetting("nofussInterval", NOFUSS_INTERVAL).toInt();
_nofussLastCheck = 0;
if (!_nofussEnabled) {
DEBUG_MSG_P(PSTR("[NOFUSS] Disabled\n"));
} else {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%s"), APP_NAME, DEVICE);
NoFUSSClient.setServer(nofussServer);
NoFUSSClient.setDevice(buffer);
NoFUSSClient.setVersion(APP_VERSION);
DEBUG_MSG_P(PSTR("[NOFUSS] Server : %s\n"), nofussServer.c_str());
DEBUG_MSG_P(PSTR("[NOFUSS] Dervice: %s\n"), buffer);
DEBUG_MSG_P(PSTR("[NOFUSS] Version: %s\n"), APP_VERSION);
DEBUG_MSG_P(PSTR("[NOFUSS] Enabled\n"));
}
}
#if TERMINAL_SUPPORT
void _nofussInitCommands() {
settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
void nofussRun() {
NoFUSSClient.handle();
_nofussLastCheck = millis();
}
void nofussSetup() {
_nofussConfigure();
NoFUSSClient.onMessage([](nofuss_t code) {
if (code == NOFUSS_START) {
DEBUG_MSG_P(PSTR("[NoFUSS] Start\n"));
}
if (code == NOFUSS_UPTODATE) {
DEBUG_MSG_P(PSTR("[NoFUSS] Already in the last version\n"));
}
if (code == NOFUSS_NO_RESPONSE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Wrong server response: %d %s\n"), NoFUSSClient.getErrorNumber(), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_PARSE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Error parsing server response\n"));
}
if (code == NOFUSS_UPDATING) {
DEBUG_MSG_P(PSTR("[NoFUSS] Updating\n"));
DEBUG_MSG_P(PSTR(" New version: %s\n"), (char *) NoFUSSClient.getNewVersion().c_str());
DEBUG_MSG_P(PSTR(" Firmware: %s\n"), (char *) NoFUSSClient.getNewFirmware().c_str());
DEBUG_MSG_P(PSTR(" File System: %s\n"), (char *) NoFUSSClient.getNewFileSystem().c_str());
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 1}"));
#endif
}
if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_FILESYSTEM_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] File System Updated\n"));
}
if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
}
if (code == NOFUSS_FIRMWARE_UPDATED) {
DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Updated\n"));
}
if (code == NOFUSS_RESET) {
DEBUG_MSG_P(PSTR("[NoFUSS] Resetting board\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
nice_delay(100);
}
if (code == NOFUSS_END) {
DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
}
});
#if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend);
wsOnAfterParseRegister(_nofussConfigure);
wsOnReceiveRegister(_nofussWebSocketOnReceive);
#endif
#if TERMINAL_SUPPORT
_nofussInitCommands();
#endif
// Register loop
espurnaRegisterLoop(nofussLoop);
}
void nofussLoop() {
if (!_nofussEnabled) return;
if (!wifiConnected()) return;
if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return;
nofussRun();
}
#endif // NOFUSS_SUPPORT

179
espurna/ntp.ino Executable file
View File

@ -0,0 +1,179 @@
/*
NTP MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if NTP_SUPPORT
#include <TimeLib.h>
#include <NtpClientLib.h>
#include <WiFiClient.h>
#include <Ticker.h>
unsigned long _ntp_start = 0;
bool _ntp_update = false;
bool _ntp_configure = false;
// -----------------------------------------------------------------------------
// NTP
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "ntp", 3) == 0);
}
void _ntpWebSocketOnSend(JsonObject& root) {
root["ntpVisible"] = 1;
root["ntpStatus"] = (timeStatus() == timeSet);
root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
if (ntpSynced()) root["now"] = now();
}
#endif
void _ntpStart() {
_ntp_start = 0;
NTP.begin(getSetting("ntpServer", NTP_SERVER));
NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL);
NTP.setNTPTimeout(NTP_TIMEOUT);
_ntpConfigure();
}
void _ntpConfigure() {
_ntp_configure = false;
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
int sign = offset > 0 ? 1 : -1;
offset = abs(offset);
int tz_hours = sign * (offset / 60);
int tz_minutes = sign * (offset % 60);
if (NTP.getTimeZone() != tz_hours || NTP.getTimeZoneMinutes() != tz_minutes) {
NTP.setTimeZone(tz_hours, tz_minutes);
_ntp_update = true;
}
bool daylight = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
if (NTP.getDayLight() != daylight) {
NTP.setDayLight(daylight);
_ntp_update = true;
}
String server = getSetting("ntpServer", NTP_SERVER);
if (!NTP.getNtpServerName().equals(server)) {
NTP.setNtpServerName(server);
}
uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION).toInt();
NTP.setDSTZone(dst_region);
}
void _ntpUpdate() {
_ntp_update = false;
#if WEB_SUPPORT
wsSend(_ntpWebSocketOnSend);
#endif
if (strlen(ntpDateTime().c_str())) {
DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str());
}
}
void _ntpLoop() {
if (0 < _ntp_start && _ntp_start < millis()) _ntpStart();
if (_ntp_configure) _ntpConfigure();
if (_ntp_update) _ntpUpdate();
now();
#if BROKER_SUPPORT
static unsigned char last_minute = 60;
if (ntpSynced() && (minute() != last_minute)) {
last_minute = minute();
brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
}
#endif
}
void _ntpBackwards() {
moveSetting("ntpServer1", "ntpServer");
delSetting("ntpServer2");
delSetting("ntpServer3");
int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
if (-30 < offset && offset < 30) {
offset *= 60;
setSetting("ntpOffset", offset);
}
}
// -----------------------------------------------------------------------------
bool ntpSynced() {
return (year() > 2017);
}
String ntpDateTime() {
if (!ntpSynced()) return String();
char buffer[20];
time_t t = now();
snprintf_P(buffer, sizeof(buffer),
PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
year(t), month(t), day(t), hour(t), minute(t), second(t)
);
return String(buffer);
}
// -----------------------------------------------------------------------------
void ntpSetup() {
_ntpBackwards();
NTP.onNTPSyncEvent([](NTPSyncEvent_t error) {
if (error) {
#if WEB_SUPPORT
wsSend_P(PSTR("{\"ntpStatus\": false}"));
#endif
if (error == noResponse) {
DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
} else if (error == invalidAddress) {
DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
}
} else {
_ntp_update = true;
}
});
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) _ntp_start = millis() + NTP_START_DELAY;
});
#if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend);
wsOnReceiveRegister(_ntpWebSocketOnReceive);
wsOnAfterParseRegister([]() { _ntp_configure = true; });
#endif
// Register loop
espurnaRegisterLoop(_ntpLoop);
}
#endif // NTP_SUPPORT

249
espurna/ota.ino Executable file
View File

@ -0,0 +1,249 @@
/*
OTA MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "ArduinoOTA.h"
// -----------------------------------------------------------------------------
// Arduino OTA
// -----------------------------------------------------------------------------
void _otaConfigure() {
ArduinoOTA.setPort(OTA_PORT);
ArduinoOTA.setHostname(getSetting("hostname").c_str());
#if USE_PASSWORD
ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str());
#endif
}
void _otaLoop() {
ArduinoOTA.handle();
}
// -----------------------------------------------------------------------------
// Terminal OTA
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
#include <ESPAsyncTCP.h>
AsyncClient * _ota_client;
char * _ota_host;
char * _ota_url;
unsigned int _ota_port = 80;
unsigned long _ota_size = 0;
const char OTA_REQUEST_TEMPLATE[] PROGMEM =
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: ESPurna\r\n"
"Connection: close\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 0\r\n\r\n\r\n";
void _otaFrom(const char * host, unsigned int port, const char * url) {
if (_ota_host) free(_ota_host);
if (_ota_url) free(_ota_url);
_ota_host = strdup(host);
_ota_url = strdup(url);
_ota_port = port;
_ota_size = 0;
if (_ota_client == NULL) {
_ota_client = new AsyncClient();
}
_ota_client->onDisconnect([](void *s, AsyncClient *c) {
DEBUG_MSG_P(PSTR("\n"));
if (Update.end(true)){
DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size);
deferredReset(100, CUSTOM_RESET_OTA);
} else {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
_ota_client->free();
delete _ota_client;
_ota_client = NULL;
free(_ota_host);
_ota_host = NULL;
free(_ota_url);
_ota_url = NULL;
}, 0);
_ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
_ota_client->close(true);
}, 0);
_ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) {
char * p = (char *) data;
if (_ota_size == 0) {
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
p = strstr((char *)data, "\r\n\r\n") + 4;
len = len - (p - (char *) data);
}
if (!Update.hasError()) {
if (Update.write((uint8_t *) p, len) != len) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
}
_ota_size += len;
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size);
}, NULL);
_ota_client->onConnect([](void * arg, AsyncClient * client) {
#if ASYNC_TCP_SSL_ENABLED
if (443 == _ota_port) {
uint8_t fp[20] = {0};
sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp);
SSL * ssl = _ota_client->getSSL();
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n"));
}
}
#endif
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url);
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)];
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host);
client->write(buffer);
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _ota_client->connect(host, port, 443 == port);
#else
bool connected = _ota_client->connect(host, port);
#endif
if (!connected) {
DEBUG_MSG_P(PSTR("[OTA] Connection failed\n"));
_ota_client->close(true);
}
}
void _otaFrom(String url) {
// Port from protocol
unsigned int port = 80;
if (url.startsWith("https://")) port = 443;
url = url.substring(url.indexOf("/") + 2);
// Get host
String host = url.substring(0, url.indexOf("/"));
// Explicit port
int p = host.indexOf(":");
if (p > 0) {
port = host.substring(p + 1).toInt();
host = host.substring(0, p);
}
// Get URL
String uri = url.substring(url.indexOf("/"));
_otaFrom(host.c_str(), port, uri.c_str());
}
void _otaInitCommands() {
settingsRegisterCommand(F("OTA"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
} else {
DEBUG_MSG_P(PSTR("+OK\n"));
String url = String(e->argv[1]);
_otaFrom(url);
}
});
}
#endif // TERMINAL_SUPPORT
// -----------------------------------------------------------------------------
void otaSetup() {
_otaConfigure();
#if WEB_SUPPORT
wsOnAfterParseRegister(_otaConfigure);
#endif
#if TERMINAL_SUPPORT
_otaInitCommands();
#endif
// Register loop
espurnaRegisterLoop(_otaLoop);
// -------------------------------------------------------------------------
ArduinoOTA.onStart([]() {
DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 2}"));
#endif
});
ArduinoOTA.onEnd([]() {
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
deferredReset(100, CUSTOM_RESET_OTA);
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
#if DEBUG_SUPPORT
DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error);
if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n"));
else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n"));
else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n"));
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n"));
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n"));
#endif
});
ArduinoOTA.begin();
}

447
espurna/pwm.c Executable file
View File

@ -0,0 +1,447 @@
/*
* Copyright (C) 2016 Stefan Brüns <stefan.bruens@rwth-aachen.de>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* Set the following three defines to your needs */
#ifndef SDK_PWM_PERIOD_COMPAT_MODE
#define SDK_PWM_PERIOD_COMPAT_MODE 0
#endif
#ifndef PWM_MAX_CHANNELS
#define PWM_MAX_CHANNELS 8
#endif
#define PWM_DEBUG 0
#define PWM_USE_NMI 1
/* no user servicable parts beyond this point */
#define PWM_MAX_TICKS 0x7fffff
#if SDK_PWM_PERIOD_COMPAT_MODE
#define PWM_PERIOD_TO_TICKS(x) (x * 0.2)
#define PWM_DUTY_TO_TICKS(x) (x * 5)
#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2)
#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5)
#else
#define PWM_PERIOD_TO_TICKS(x) (x)
#define PWM_DUTY_TO_TICKS(x) (x)
#define PWM_MAX_DUTY PWM_MAX_TICKS
#define PWM_MAX_PERIOD PWM_MAX_TICKS
#endif
#include <c_types.h>
#include <pwm.h>
#include <eagle_soc.h>
#include <ets_sys.h>
// from SDK hw_timer.c
#define TIMER1_DIVIDE_BY_16 0x0004
#define TIMER1_ENABLE_TIMER 0x0080
struct pwm_phase {
uint32_t ticks; ///< delay until next phase, in 200ns units
uint16_t on_mask; ///< GPIO mask to switch on
uint16_t off_mask; ///< GPIO mask to switch off
};
/* Three sets of PWM phases, the active one, the one used
* starting with the next cycle, and the one updated
* by pwm_start. After the update pwm_next_set
* is set to the last updated set. pwm_current_set is set to
* pwm_next_set from the interrupt routine during the first
* pwm phase
*/
typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2];
static pwm_phase_array pwm_phases[3];
static struct {
struct pwm_phase* next_set;
struct pwm_phase* current_set;
uint8_t current_phase;
} pwm_state;
static uint32_t pwm_period;
static uint32_t pwm_period_ticks;
static uint32_t pwm_duty[PWM_MAX_CHANNELS];
static uint16_t gpio_mask[PWM_MAX_CHANNELS];
static uint8_t pwm_channels;
// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number
typedef uint32_t (pin_info_type)[3];
struct gpio_regs {
uint32_t out; /* 0x60000300 */
uint32_t out_w1ts; /* 0x60000304 */
uint32_t out_w1tc; /* 0x60000308 */
uint32_t enable; /* 0x6000030C */
uint32_t enable_w1ts; /* 0x60000310 */
uint32_t enable_w1tc; /* 0x60000314 */
uint32_t in; /* 0x60000318 */
uint32_t status; /* 0x6000031C */
uint32_t status_w1ts; /* 0x60000320 */
uint32_t status_w1tc; /* 0x60000324 */
};
static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300);
struct timer_regs {
uint32_t frc1_load; /* 0x60000600 */
uint32_t frc1_count; /* 0x60000604 */
uint32_t frc1_ctrl; /* 0x60000608 */
uint32_t frc1_int; /* 0x6000060C */
uint8_t pad[16];
uint32_t frc2_load; /* 0x60000620 */
uint32_t frc2_count; /* 0x60000624 */
uint32_t frc2_ctrl; /* 0x60000628 */
uint32_t frc2_int; /* 0x6000062C */
uint32_t frc2_alarm; /* 0x60000630 */
};
static struct timer_regs* timer = (struct timer_regs*)(0x60000600);
static void ICACHE_RAM_ATTR
pwm_intr_handler(void)
{
if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) &&
(pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) {
pwm_state.current_set = pwm_state.next_set;
pwm_state.current_phase = 0;
}
do {
// force write to GPIO registers on each loop
asm volatile ("" : : : "memory");
gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask);
gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask);
uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks;
pwm_state.current_phase++;
if (ticks) {
if (ticks >= 16) {
// constant interrupt overhead
ticks -= 9;
timer->frc1_int &= ~FRC1_INT_CLR_MASK;
WRITE_PERI_REG(&timer->frc1_load, ticks);
return;
}
ticks *= 4;
do {
ticks -= 1;
// stop compiler from optimizing delay loop to noop
asm volatile ("" : : : "memory");
} while (ticks > 0);
}
} while (1);
}
/**
* period: initial period (base unit 1us OR 200ns)
* duty: array of initial duty values, may be NULL, may be freed after pwm_init
* pwm_channel_num: number of channels to use
* pin_info_list: array of pin_info
*/
void ICACHE_FLASH_ATTR
pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num,
uint32_t (*pin_info_list)[3])
{
int i, j, n;
pwm_channels = pwm_channel_num;
if (pwm_channels > PWM_MAX_CHANNELS)
pwm_channels = PWM_MAX_CHANNELS;
for (i = 0; i < 3; i++) {
for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) {
pwm_phases[i][j].ticks = 0;
pwm_phases[i][j].on_mask = 0;
pwm_phases[i][j].off_mask = 0;
}
}
pwm_state.current_set = pwm_state.next_set = 0;
pwm_state.current_phase = 0;
uint32_t all = 0;
// PIN info: MUX-Register, Mux-Setting, PIN-Nr
for (n = 0; n < pwm_channels; n++) {
pin_info_type* pin_info = &pin_info_list[n];
PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]);
gpio_mask[n] = 1 << (*pin_info)[2];
all |= 1 << (*pin_info)[2];
if (duty)
pwm_set_duty(duty[n], n);
}
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all);
GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all);
pwm_set_period(period);
#if PWM_USE_NMI
ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler);
#else
ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL);
#endif
TM1_EDGE_INT_ENABLE();
timer->frc1_int &= ~FRC1_INT_CLR_MASK;
timer->frc1_ctrl = 0;
pwm_start();
}
__attribute__ ((noinline))
static uint8_t ICACHE_FLASH_ATTR
_pwm_phases_prep(struct pwm_phase* pwm)
{
uint8_t n, phases;
for (n = 0; n < pwm_channels + 2; n++) {
pwm[n].ticks = 0;
pwm[n].on_mask = 0;
pwm[n].off_mask = 0;
}
phases = 1;
for (n = 0; n < pwm_channels; n++) {
uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]);
if (ticks == 0) {
pwm[0].off_mask |= gpio_mask[n];
} else if (ticks >= pwm_period_ticks) {
pwm[0].on_mask |= gpio_mask[n];
} else {
if (ticks < (pwm_period_ticks/2)) {
pwm[phases].ticks = ticks;
pwm[0].on_mask |= gpio_mask[n];
pwm[phases].off_mask = gpio_mask[n];
} else {
pwm[phases].ticks = pwm_period_ticks - ticks;
pwm[phases].on_mask = gpio_mask[n];
pwm[0].off_mask |= gpio_mask[n];
}
phases++;
}
}
pwm[phases].ticks = pwm_period_ticks;
// bubble sort, lowest to hightest duty
n = 2;
while (n < phases) {
if (pwm[n].ticks < pwm[n - 1].ticks) {
struct pwm_phase t = pwm[n];
pwm[n] = pwm[n - 1];
pwm[n - 1] = t;
if (n > 2)
n--;
} else {
n++;
}
}
#if PWM_DEBUG
int t = 0;
for (t = 0; t <= phases; t++) {
ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
}
#endif
// shift left to align right edge;
uint8_t l = 0, r = 1;
while (r <= phases) {
uint32_t diff = pwm[r].ticks - pwm[l].ticks;
if (diff && (diff <= 16)) {
uint16_t mask = pwm[r].on_mask | pwm[r].off_mask;
pwm[l].off_mask ^= pwm[r].off_mask;
pwm[l].on_mask ^= pwm[r].on_mask;
pwm[0].off_mask ^= pwm[r].on_mask;
pwm[0].on_mask ^= pwm[r].off_mask;
pwm[r].ticks = pwm_period_ticks - diff;
pwm[r].on_mask ^= mask;
pwm[r].off_mask ^= mask;
} else {
l = r;
}
r++;
}
#if PWM_DEBUG
for (t = 0; t <= phases; t++) {
ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
}
#endif
// sort again
n = 2;
while (n <= phases) {
if (pwm[n].ticks < pwm[n - 1].ticks) {
struct pwm_phase t = pwm[n];
pwm[n] = pwm[n - 1];
pwm[n - 1] = t;
if (n > 2)
n--;
} else {
n++;
}
}
// merge same duty
l = 0, r = 1;
while (r <= phases) {
if (pwm[r].ticks == pwm[l].ticks) {
pwm[l].off_mask |= pwm[r].off_mask;
pwm[l].on_mask |= pwm[r].on_mask;
pwm[r].on_mask = 0;
pwm[r].off_mask = 0;
} else {
l++;
if (l != r) {
struct pwm_phase t = pwm[l];
pwm[l] = pwm[r];
pwm[r] = t;
}
}
r++;
}
phases = l;
#if PWM_DEBUG
for (t = 0; t <= phases; t++) {
ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
}
#endif
// transform absolute end time to phase durations
for (n = 0; n < phases; n++) {
pwm[n].ticks =
pwm[n + 1].ticks - pwm[n].ticks;
// subtract common overhead
pwm[n].ticks--;
}
pwm[phases].ticks = 0;
// do a cyclic shift if last phase is short
if (pwm[phases - 1].ticks < 16) {
for (n = 0; n < phases - 1; n++) {
struct pwm_phase t = pwm[n];
pwm[n] = pwm[n + 1];
pwm[n + 1] = t;
}
}
#if PWM_DEBUG
for (t = 0; t <= phases; t++) {
ets_printf("%d +%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
}
ets_printf("\n");
#endif
return phases;
}
void ICACHE_FLASH_ATTR
pwm_start(void)
{
pwm_phase_array* pwm = &pwm_phases[0];
if ((*pwm == pwm_state.next_set) ||
(*pwm == pwm_state.current_set))
pwm++;
if ((*pwm == pwm_state.next_set) ||
(*pwm == pwm_state.current_set))
pwm++;
uint8_t phases = _pwm_phases_prep(*pwm);
// all with 0% / 100% duty - stop timer
if (phases == 1) {
if (pwm_state.next_set) {
#if PWM_DEBUG
ets_printf("PWM stop\n");
#endif
timer->frc1_ctrl = 0;
ETS_FRC1_INTR_DISABLE();
}
pwm_state.next_set = NULL;
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask);
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask);
return;
}
// start if not running
if (!pwm_state.next_set) {
#if PWM_DEBUG
ets_printf("PWM start\n");
#endif
pwm_state.current_set = pwm_state.next_set = *pwm;
pwm_state.current_phase = phases - 1;
ETS_FRC1_INTR_ENABLE();
RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0);
timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER;
return;
}
pwm_state.next_set = *pwm;
}
void ICACHE_FLASH_ATTR
pwm_set_duty(uint32_t duty, uint8_t channel)
{
if (channel > PWM_MAX_CHANNELS)
return;
if (duty > PWM_MAX_DUTY)
duty = PWM_MAX_DUTY;
pwm_duty[channel] = duty;
}
uint32_t ICACHE_FLASH_ATTR
pwm_get_duty(uint8_t channel)
{
if (channel > PWM_MAX_CHANNELS)
return 0;
return pwm_duty[channel];
}
void ICACHE_FLASH_ATTR
pwm_set_period(uint32_t period)
{
pwm_period = period;
if (pwm_period > PWM_MAX_PERIOD)
pwm_period = PWM_MAX_PERIOD;
pwm_period_ticks = PWM_PERIOD_TO_TICKS(period);
}
uint32_t ICACHE_FLASH_ATTR
pwm_get_period(void)
{
return pwm_period;
}
uint32_t ICACHE_FLASH_ATTR
get_pwm_version(void)
{
return 1;
}
void ICACHE_FLASH_ATTR
set_pwm_debug_en(uint8_t print_en)
{
(void) print_en;
}

886
espurna/relay.ino Executable file
View File

@ -0,0 +1,886 @@
/*
RELAY MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <vector>
#include <functional>
typedef struct {
// Configuration variables
unsigned char pin; // GPIO pin for the relay
unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE
unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED
unsigned long delay_on; // Delay to turn relay ON
unsigned long delay_off; // Delay to turn relay OFF
unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
unsigned long pulse_ms; // Pulse length in millis
// Status variables
bool current_status; // Holds the current (physical) status of the relay
bool target_status; // Holds the target status
unsigned long fw_start; // Flood window start time
unsigned char fw_count; // Number of changes within the current flood window
unsigned long change_time; // Scheduled time to change
bool report; // Whether to report to own topic
bool group_report; // Whether to report to group topic
// Helping objects
Ticker pulseTicker; // Holds the pulse back timer
} relay_t;
std::vector<relay_t> _relays;
bool _relayRecursive = false;
Ticker _relaySaveTicker;
// -----------------------------------------------------------------------------
// RELAY PROVIDERS
// -----------------------------------------------------------------------------
void _relayProviderStatus(unsigned char id, bool status) {
// Check relay ID
if (id >= _relays.size()) return;
// Store new current status
_relays[id].current_status = status;
#if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
rfbStatus(id, status);
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
// Calculate mask
unsigned char mask=0;
for (unsigned char i=0; i<_relays.size(); i++) {
if (_relays[i].current_status) mask = mask + (1 << i);
}
// Send it to F330
Serial.flush();
Serial.write(0xA0);
Serial.write(0x04);
Serial.write(mask);
Serial.write(0xA1);
Serial.flush();
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
Serial.flush();
Serial.write(0xA0);
Serial.write(id + 1);
Serial.write(status);
Serial.write(0xA1 + status + id);
Serial.flush();
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
// If the number of relays matches the number of light channels
// assume each relay controls one channel.
// If the number of relays is the number of channels plus 1
// assume the first one controls all the channels and
// the rest one channel each.
// Otherwise every relay controls all channels.
// TODO: this won't work with a mixed of dummy and real relays
// but this option is not allowed atm (YANGNI)
if (_relays.size() == lightChannels()) {
lightState(id, status);
lightState(true);
} else if (_relays.size() == lightChannels() + 1) {
if (id == 0) {
lightState(status);
} else {
lightState(id-1, status);
}
} else {
lightState(status);
}
lightUpdate(true, true);
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
if (_relays[id].type == RELAY_TYPE_NORMAL) {
digitalWrite(_relays[id].pin, status);
} else if (_relays[id].type == RELAY_TYPE_INVERSE) {
digitalWrite(_relays[id].pin, !status);
} else if (_relays[id].type == RELAY_TYPE_LATCHED || _relays[id].type == RELAY_TYPE_LATCHED_INVERSE) {
bool pulse = RELAY_TYPE_LATCHED ? HIGH : LOW;
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
if (status) {
digitalWrite(_relays[id].pin, pulse);
} else {
digitalWrite(_relays[id].reset_pin, pulse);
}
nice_delay(RELAY_LATCHING_PULSE);
digitalWrite(_relays[id].pin, !pulse);
digitalWrite(_relays[id].reset_pin, !pulse);
}
#endif
}
/**
* Walks the relay vector processing only those relays
* that have to change to the requested mode
* @bool mode Requested mode
*/
void _relayProcess(bool mode) {
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
bool target = _relays[id].target_status;
// Only process the relays we have to change
if (target == _relays[id].current_status) continue;
// Only process the relays we have change to the requested mode
if (target != mode) continue;
// Only process if the change_time has arrived
if (current_time < _relays[id].change_time) continue;
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, target ? "ON" : "OFF");
// Call the provider to perform the action
_relayProviderStatus(id, target);
// Send to Broker
#if BROKER_SUPPORT
brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0");
#endif
// Send MQTT
#if MQTT_SUPPORT
relayMQTT(id);
#endif
if (!_relayRecursive) {
relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
domoticzSendRelay(id);
#endif
#if INFLUXDB_SUPPORT
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, target);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);
_relays[id].report = false;
_relays[id].group_report = false;
}
}
// -----------------------------------------------------------------------------
// RELAY
// -----------------------------------------------------------------------------
void relayPulse(unsigned char id) {
byte mode = _relays[id].pulse;
if (mode == RELAY_PULSE_NONE) return;
unsigned long ms = _relays[id].pulse_ms;
if (ms == 0) return;
bool status = relayStatus(id);
bool pulseStatus = (mode == RELAY_PULSE_ON);
if (pulseStatus == status) {
_relays[id].pulseTicker.detach();
} else {
_relays[id].pulseTicker.once_ms(ms, relayToggle, id);
}
}
bool relayStatus(unsigned char id, bool status, bool report, bool group_report) {
if (id >= _relays.size()) return false;
bool changed = false;
if (_relays[id].current_status == status) {
if (_relays[id].target_status != status) {
DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled change cancelled\n"), id);
_relays[id].target_status = status;
_relays[id].report = false;
_relays[id].group_report = false;
changed = true;
}
// For RFBridge, keep sending the message even if the status is already the required
#if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
rfbStatus(id, status);
#endif
// Update the pulse counter if the relay is already in the non-normal state (#454)
relayPulse(id);
} else {
unsigned int current_time = millis();
unsigned int fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW;
unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off;
_relays[id].fw_count++;
_relays[id].change_time = current_time + delay;
// If current_time is off-limits the floodWindow...
if (current_time < _relays[id].fw_start || fw_end <= current_time) {
// We reset the floodWindow
_relays[id].fw_start = current_time;
_relays[id].fw_count = 1;
// If current_time is in the floodWindow and there have been too many requests...
} else if (_relays[id].fw_count >= RELAY_FLOOD_CHANGES) {
// We schedule the changes to the end of the floodWindow
// unless it's already delayed beyond that point
if (fw_end - delay > current_time) {
_relays[id].change_time = fw_end;
}
}
_relays[id].target_status = status;
if (report) _relays[id].report = true;
if (group_report) _relays[id].group_report = true;
relaySync(id);
DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled %s in %u ms\n"),
id, status ? "ON" : "OFF",
(_relays[id].change_time - current_time));
changed = true;
}
return changed;
}
bool relayStatus(unsigned char id, bool status) {
return relayStatus(id, status, true, true);
}
bool relayStatus(unsigned char id) {
// Check relay ID
if (id >= _relays.size()) return false;
// Get status from storage
return _relays[id].current_status;
}
void relaySync(unsigned char id) {
// No sync if none or only one relay
if (_relays.size() < 2) return;
// Do not go on if we are comming from a previous sync
if (_relayRecursive) return;
// Flag sync mode
_relayRecursive = true;
byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt();
bool status = _relays[id].target_status;
// If RELAY_SYNC_SAME all relays should have the same state
if (relaySync == RELAY_SYNC_SAME) {
for (unsigned short i=0; i<_relays.size(); i++) {
if (i != id) relayStatus(i, status);
}
// If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
} else if (status) {
if (relaySync != RELAY_SYNC_ANY) {
for (unsigned short i=0; i<_relays.size(); i++) {
if (i != id) relayStatus(i, false);
}
}
// If ONLY_ONE and setting OFF we should set ON the other one
} else {
if (relaySync == RELAY_SYNC_ONE) {
unsigned char i = (id + 1) % _relays.size();
relayStatus(i, true);
}
}
// Unflag sync mode
_relayRecursive = false;
}
void relaySave() {
unsigned char bit = 1;
unsigned char mask = 0;
for (unsigned int i=0; i < _relays.size(); i++) {
if (relayStatus(i)) mask += bit;
bit += bit;
}
EEPROM.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROM.commit();
}
void relayToggle(unsigned char id, bool report, bool group_report) {
if (id >= _relays.size()) return;
relayStatus(id, !relayStatus(id), report, group_report);
}
void relayToggle(unsigned char id) {
relayToggle(id, true, true);
}
unsigned char relayCount() {
return _relays.size();
}
unsigned char relayParsePayload(const char * payload) {
// Payload could be "OFF", "ON", "TOGGLE"
// or its number equivalents: 0, 1 or 2
if (payload[0] == '0') return 0;
if (payload[0] == '1') return 1;
if (payload[0] == '2') return 2;
// trim payload
char * p = ltrim((char *)payload);
// to lower
unsigned int l = strlen(p);
if (l>6) l=6;
for (unsigned char i=0; i<l; i++) {
p[i] = tolower(p[i]);
}
unsigned int value = 0xFF;
if (strcmp(p, "off") == 0) {
value = 0;
} else if (strcmp(p, "on") == 0) {
value = 1;
} else if (strcmp(p, "toggle") == 0) {
value = 2;
} else if (strcmp(p, "query") == 0) {
value = 3;
}
return value;
}
// BACKWARDS COMPATIBILITY
void _relayBackwards() {
byte relayMode = getSetting("relayMode", RELAY_BOOT_MODE).toInt();
byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
float relayPulseTime = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
if (relayPulseMode == RELAY_PULSE_NONE) relayPulseTime = 0;
for (unsigned int i=0; i<_relays.size(); i++) {
if (!hasSetting("relayBoot", i)) setSetting("relayBoot", i, relayMode);
if (!hasSetting("relayPulse", i)) setSetting("relayPulse", i, relayPulseMode);
if (!hasSetting("relayTime", i)) setSetting("relayTime", i, relayPulseTime);
}
delSetting("relayMode");
delSetting("relayPulseMode");
delSetting("relayPulseTime");
}
void _relayBoot() {
_relayRecursive = true;
unsigned char bit = 1;
bool trigger_save = false;
// Get last statuses from EEPROM
unsigned char mask = EEPROM.read(EEPROM_RELAY_STATUS);
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays
bool status = false;
for (unsigned int i=0; i<_relays.size(); i++) {
unsigned char boot_mode = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode);
switch (boot_mode) {
case RELAY_BOOT_SAME:
status = ((mask & bit) == bit);
break;
case RELAY_BOOT_TOGGLE:
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
break;
case RELAY_BOOT_ON:
status = true;
break;
case RELAY_BOOT_OFF:
default:
status = false;
break;
}
_relays[i].current_status = !status;
_relays[i].target_status = status;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i;
#else
_relays[i].change_time = millis();
#endif
bit <<= 1;
}
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROM.commit();
}
_relayRecursive = false;
}
void _relayConfigure() {
for (unsigned int i=0; i<_relays.size(); i++) {
pinMode(_relays[i].pin, OUTPUT);
if (_relays[i].type == RELAY_TYPE_LATCHED || _relays[i].type == RELAY_TYPE_LATCHED_INVERSE) {
pinMode(_relays[i].reset_pin, OUTPUT);
}
_relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
_relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
}
}
//------------------------------------------------------------------------------
// WEBSOCKETS
//------------------------------------------------------------------------------
#if WEB_SUPPORT
bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "relay", 5) == 0);
}
void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
relay.add(_relays[i].target_status);
}
}
void _relayWebSocketOnStart(JsonObject& root) {
if (relayCount() == 0) return;
// Statuses
_relayWebSocketUpdate(root);
// Configuration
JsonArray& config = root.createNestedArray("relayConfig");
for (unsigned char i=0; i<relayCount(); i++) {
JsonObject& line = config.createNestedObject();
line["gpio"] = _relays[i].pin;
line["type"] = _relays[i].type;
line["reset"] = _relays[i].reset_pin;
line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
line["pulse"] = _relays[i].pulse;
line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
#if MQTT_SUPPORT
line["group"] = getSetting("mqttGroup", i, "");
line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
#endif
}
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
}
root["relayVisible"] = 1;
}
void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "relay") != 0) return;
if (data.containsKey("status")) {
unsigned char value = relayParsePayload(data["status"]);
if (value == 3) {
wsSend(_relayWebSocketUpdate);
} else if (value < 3) {
unsigned int relayID = 0;
if (data.containsKey("id")) {
String value = data["id"];
relayID = value.toInt();
}
// Action to perform
if (value == 0) {
relayStatus(relayID, false);
} else if (value == 1) {
relayStatus(relayID, true);
} else if (value == 2) {
relayToggle(relayID);
}
}
}
}
void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction);
wsOnAfterParseRegister(_relayConfigure);
wsOnReceiveRegister(_relayWebSocketOnReceive);
}
#endif // WEB_SUPPORT
//------------------------------------------------------------------------------
// REST API
//------------------------------------------------------------------------------
#if WEB_SUPPORT
void relaySetupAPI() {
// API entry points (protected with apikey)
for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
},
[relayID](const char * payload) {
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
return;
}
if (value == 0) {
relayStatus(relayID, false);
} else if (value == 1) {
relayStatus(relayID, true);
} else if (value == 2) {
relayToggle(relayID);
}
}
);
}
}
#endif // WEB_SUPPORT
//------------------------------------------------------------------------------
// MQTT
//------------------------------------------------------------------------------
#if MQTT_SUPPORT
void relayMQTT(unsigned char id) {
if (id >= _relays.size()) return;
// Send state topic
if (_relays[id].report) {
_relays[id].report = false;
mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
}
// Check group topic
if (_relays[id].group_report) {
_relays[id].group_report = false;
String t = getSetting("mqttGroup", id, "");
if (t.length() > 0) {
bool status = relayStatus(id);
if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status;
mqttSendRaw(t.c_str(), status ? "1" : "0");
}
}
}
void relayMQTT() {
for (unsigned int id=0; id < _relays.size(); id++) {
mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
}
}
void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) {
switch (value) {
case 0:
relayStatus(id, false, mqttForward(), !is_group_topic);
break;
case 1:
relayStatus(id, true, mqttForward(), !is_group_topic);
break;
case 2:
relayToggle(id, true, true);
break;
default:
_relays[id].report = true;
relayMQTT(id);
break;
}
}
void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
// Send status on connect
#if not HEARTBEAT_REPORT_RELAY
relayMQTT();
#endif
// Subscribe to own /set topic
char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(buffer);
// Subscribe to group topics
for (unsigned int i=0; i < _relays.size(); i++) {
String t = getSetting("mqttGroup", i, "");
if (t.length() > 0) mqttSubscribeRaw(t.c_str());
}
}
if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic
String t = mqttMagnitude((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
// Get relay ID
unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
} else {
relayStatusWrap(id, value, false);
}
return;
}
// Check group topics
for (unsigned int i=0; i < _relays.size(); i++) {
String t = getSetting("mqttGroup", i, "");
if ((t.length() > 0) && t.equals(topic)) {
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
if (value < 2) {
if (getSetting("mqttGroupInv", i, 0).toInt() == 1) {
value = 1 - value;
}
}
DEBUG_MSG_P(PSTR("[RELAY] Matched group topic for relayID %d\n"), i);
relayStatusWrap(i, value, true);
}
}
}
if (type == MQTT_DISCONNECT_EVENT) {
for (unsigned int i=0; i < _relays.size(); i++){
int reaction = getSetting("relayOnDisc", i, 0).toInt();
if (1 == reaction) { // switch relay OFF
DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, false, false);
} else if(2 == reaction) { // switch relay ON
DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i);
relayStatusWrap(i, true, false);
}
}
}
}
void relaySetupMQTT() {
mqttRegister(relayMQTTCallback);
}
#endif
//------------------------------------------------------------------------------
// InfluxDB
//------------------------------------------------------------------------------
#if INFLUXDB_SUPPORT
void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
}
#endif
//------------------------------------------------------------------------------
// Settings
//------------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
if (value == 2) {
relayToggle(id);
} else {
relayStatus(id, value == 1);
}
}
DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false");
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif // TERMINAL_SUPPORT
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
void _relayLoop() {
_relayProcess(false);
_relayProcess(true);
}
void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
}
#else
#if RELAY1_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif
#if RELAY2_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif
#if RELAY3_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif
#if RELAY4_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif
#if RELAY5_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
#endif
#if RELAY6_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
#endif
#if RELAY7_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
#endif
#if RELAY8_PIN != GPIO_NONE
_relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
#endif
#endif
_relayBackwards();
_relayConfigure();
_relayBoot();
_relayLoop();
espurnaRegisterLoop(_relayLoop);
#if WEB_SUPPORT
relaySetupAPI();
relaySetupWS();
#endif
#if MQTT_SUPPORT
relaySetupMQTT();
#endif
#if TERMINAL_SUPPORT
_relayInitCommands();
#endif
DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
}

192
espurna/rf.ino Executable file
View File

@ -0,0 +1,192 @@
/*
RF MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if RF_SUPPORT
#include <RCSwitch.h>
RCSwitch * _rfModem;
unsigned long _rf_learn_start = 0;
unsigned char _rf_learn_id = 0;
bool _rf_learn_status = true;
bool _rf_learn_active = false;
// -----------------------------------------------------------------------------
// RF
// -----------------------------------------------------------------------------
unsigned long _rfRetrieve(unsigned char id, bool status) {
String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0");
return strtoul(code.c_str(), 0, 16);
}
void _rfStore(unsigned char id, bool status, unsigned long code) {
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => %X\n"), id, status ? "ON" : "OFF", code);
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), code);
setSetting(status ? "rfbON" : "rfbOFF", id, buffer);
}
void _rfLearn(unsigned char id, bool status) {
_rf_learn_start = millis();
_rf_learn_id = id;
_rf_learn_status = status;
_rf_learn_active = true;
}
void _rfForget(unsigned char id, bool status) {
delSetting(status ? "rfbON" : "rfbOFF", id);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
#endif
}
bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) {
bool found = false;
DEBUG_MSG_P(PSTR("[RF] Trying to match code %X\n"), code);
for (unsigned char i=0; i<relayCount(); i++) {
unsigned long code_on = _rfRetrieve(i, true);
unsigned long code_off = _rfRetrieve(i, false);
if (code == code_on) {
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
value = 1;
found = true;
}
if (code == code_off) {
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
}
if (found) {
relayID = i;
return true;
}
}
return false;
}
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
void _rfWebSocketOnSend(JsonObject& root) {
char buffer[20];
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), _rfRetrieve(id, status == 1));
node["id"] = id;
node["status"] = status;
node["data"] = String(buffer);
}
}
}
void _rfWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "rfblearn") == 0) _rfLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) _rfForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) _rfStore(data["id"], data["status"], data["data"].as<long>());
}
// -----------------------------------------------------------------------------
void rfLoop() {
if (_rfModem->available()) {
static unsigned long last = 0;
if (millis() - last > RF_DEBOUNCE) {
last = millis();
if (_rfModem->getReceivedValue() > 0) {
unsigned long rf_code = _rfModem->getReceivedValue();
DEBUG_MSG_P(PSTR("[RF] Received code: %X\n"), rf_code);
if (_rf_learn_active) {
_rf_learn_active = false;
_rfStore(_rf_learn_id, _rf_learn_status, rf_code);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(
wsb, sizeof(wsb),
PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%X\"}]}"),
_rf_learn_id, _rf_learn_status ? 1 : 0, rf_code);
wsSend(wsb);
#endif
} else {
unsigned char id;
unsigned char value;
if (_rfMatch(rf_code, id, value)) {
if (2 == value) {
relayToggle(id);
} else {
relayStatus(id, 1 == value);
}
}
}
}
}
_rfModem->resetAvailable();
}
if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) {
_rf_learn_active = false;
}
}
void rfSetup() {
_rfModem = new RCSwitch();
_rfModem->enableReceive(RF_PIN);
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN);
#if WEB_SUPPORT
wsOnSendRegister(_rfWebSocketOnSend);
wsOnActionRegister(_rfWebSocketOnAction);
#endif
// Register loop
espurnaRegisterLoop(rfLoop);
}
#endif

534
espurna/rfbridge.ino Executable file
View File

@ -0,0 +1,534 @@
/*
ITEAD RF BRIDGE MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#ifdef ITEAD_SONOFF_RFBRIDGE
#include <queue>
#include <Ticker.h>
// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------
#define RF_MESSAGE_SIZE 9
#define RF_MAX_MESSAGE_SIZE (112+4)
#define RF_CODE_START 0xAA
#define RF_CODE_ACK 0xA0
#define RF_CODE_LEARN 0xA1
#define RF_CODE_LEARN_KO 0xA2
#define RF_CODE_LEARN_OK 0xA3
#define RF_CODE_RFIN 0xA4
#define RF_CODE_RFOUT 0xA5
#define RF_CODE_SNIFFING_ON 0xA6
#define RF_CODE_SNIFFING_OFF 0xA7
#define RF_CODE_RFOUT_NEW 0xA8
#define RF_CODE_LEARN_NEW 0xA9
#define RF_CODE_LEARN_KO_NEW 0xAA
#define RF_CODE_LEARN_OK_NEW 0xAB
#define RF_CODE_RFOUT_BUCKET 0xB0
#define RF_CODE_STOP 0x55
// -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE
// -----------------------------------------------------------------------------
unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
unsigned char _uartpos = 0;
unsigned char _learnId = 0;
bool _learnStatus = true;
bool _rfbin = false;
typedef struct {
byte code[RF_MESSAGE_SIZE];
byte times;
} rfb_message_t;
static std::queue<rfb_message_t> _rfb_message_queue;
Ticker _rfb_ticker;
bool _rfb_ticker_active = false;
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) {
int n = strlen(in);
if (n > RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0;
char tmp[3] = {0,0,0};
n /= 2;
for (unsigned char p = 0; p<n; p++) {
memcpy(tmp, &in[p*2], 2);
out[p] = strtol(tmp, NULL, 16);
}
return n;
}
/*
From a byte array to an hexa char array ("A220EE...", double the size)
*/
static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
for (unsigned char p = 0; p<n; p++) {
sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
}
return true;
}
void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
#if RF_RAW_SUPPORT
root["rfbrawVisible"] = 1;
#endif
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
}
}
}
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
}
void _rfbAck() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
}
void _rfbLearn() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0);
wsSend(buffer);
#endif
}
void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
for (unsigned char j=0; j<n; j++) {
Serial.write(message[j]);
}
}
void _rfbSend(byte * message) {
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
_rfbSendRaw(message);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
}
void _rfbSend() {
// Check if there is something in the queue
if (_rfb_message_queue.empty()) return;
// Pop the first element
rfb_message_t message = _rfb_message_queue.front();
_rfb_message_queue.pop();
// Send the message
_rfbSend(message.code);
// If it should be further sent, push it to the stack again
if (message.times > 1) {
message.times = message.times - 1;
_rfb_message_queue.push(message);
}
// if there are still messages in the queue...
if (_rfb_message_queue.empty()) {
_rfb_ticker.detach();
_rfb_ticker_active = false;
}
}
void _rfbSend(byte * code, unsigned char times) {
char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
rfb_message_t message;
memcpy(message.code, code, RF_MESSAGE_SIZE);
message.times = times;
_rfb_message_queue.push(message);
// Enable the ticker if not running
if (!_rfb_ticker_active) {
_rfb_ticker_active = true;
_rfb_ticker.attach_ms(RF_SEND_DELAY, _rfbSend);
}
}
#if RF_RAW_SUPPORT
void _rfbSendRawOnce(byte *code, unsigned char length) {
char buffer[length*2];
_rfbToChar(code, buffer, length);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending RAW MESSAGE '%s'\n"), buffer);
_rfbSendRaw(code, length);
}
#endif // RF_RAW_SUPPORT
bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
if (strlen(code) != 18) return false;
bool found = false;
String compareto = String(&code[12]);
compareto.toUpperCase();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str());
for (unsigned char i=0; i<relayCount(); i++) {
String code_on = rfbRetrieve(i, true);
if (code_on.length() && code_on.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
value = 1;
found = true;
}
String code_off = rfbRetrieve(i, false);
if (code_off.length() && code_off.endsWith(compareto)) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
if (found) value = 2;
found = true;
}
if (found) {
relayID = i;
return true;
}
}
return false;
}
void _rfbDecode() {
static unsigned long last = 0;
if (millis() - last < RF_RECEIVE_DELAY) return;
last = millis();
byte action = _uartbuf[0];
char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
if (action == RF_CODE_LEARN_KO) {
_rfbAck();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
#endif
}
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
#if MQTT_SUPPORT
_rfbToChar(&_uartbuf[1], buffer);
mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif
_rfbAck();
}
if (action == RF_CODE_LEARN_OK) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
rfbStore(_learnId, _learnStatus, buffer);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
wsSend(wsb);
#endif
}
if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
// Look for the code
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(buffer, id, status)) {
_rfbin = true;
if (status == 2) {
relayToggle(id);
} else {
relayStatus(id, status == 1);
}
}
}
}
void _rfbReceive() {
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
_rfbDecode();
receiving = false;
} else if (_uartpos <= RF_MESSAGE_SIZE) {
_uartbuf[_uartpos++] = c;
} else {
// wrong message, should have received a RF_CODE_STOP
receiving = false;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
}
bool _rfbCompare(const char * code1, const char * code2) {
return strcmp(&code1[12], &code2[12]) == 0;
}
bool _rfbSameOnOff(unsigned char id) {
return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
}
#if MQTT_SUPPORT
void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer);
mqttSubscribe(MQTT_TOPIC_RFOUT);
#if RF_RAW_SUPPORT
mqttSubscribe(MQTT_TOPIC_RFRAW);
#endif
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
// Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
_learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
if (_learnId >= relayCount()) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
return;
}
_learnStatus = (char)payload[0] != '0';
_rfbLearn();
return;
}
bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
#if RF_RAW_SUPPORT
bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW);
#else
bool isRFRaw = false;
#endif
if (isRFOut || isRFRaw) {
// The payload may be a code in HEX format ([0-9A-Z]{18}) or
// the code comma the number of times to transmit it.
char * tok = strtok((char *) payload, ",");
// Check if a switch is linked to that message
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(tok, id, status)) {
if (status == 2) {
relayToggle(id);
} else {
relayStatus(id, status == 1);
}
return;
}
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(tok, message, 0);
if ((len > 0) && (isRFRaw || len != RF_MESSAGE_SIZE)) {
_rfbSendRawOnce(message, len);
} else {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#else // RF_RAW_SUPPORT
byte message[RF_MESSAGE_SIZE];
if (_rfbToArray(tok, message)) {
tok = strtok(NULL, ",");
byte times = (tok != NULL) ? atoi(tok) : 1;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT
}
}
}
#endif
// -----------------------------------------------------------------------------
// PUBLIC
// -----------------------------------------------------------------------------
void rfbStore(unsigned char id, bool status, const char * code) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[8] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
setSetting(key, code);
}
String rfbRetrieve(unsigned char id, bool status) {
char key[8] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
return getSetting(key);
}
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
if (value.length() > 0) {
bool same = _rfbSameOnOff(id);
#if RF_RAW_SUPPORT
byte message[RF_MAX_MESSAGE_SIZE];
int len = _rfbToArray(value.c_str(), message, 0);
if (len == RF_MESSAGE_SIZE && // probably a standard msg
(message[0] != RF_CODE_START || // raw would start with 0xAA
message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
message[2] + 4 != len || // needs a valid length,
message[len-1] != RF_CODE_STOP)) { // and finish with 0x55
if (!_rfbin) {
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
} else {
_rfbSendRawOnce(message, len); // send a raw message
}
#else // RF_RAW_SUPPORT
if (!_rfbin) {
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = same ? 1 : RF_SEND_TIMES;
_rfbSend(message, times);
}
#endif // RF_RAW_SUPPORT
}
_rfbin = false;
}
void rfbLearn(unsigned char id, bool status) {
_learnId = id;
_learnStatus = status;
_rfbLearn();
}
void rfbForget(unsigned char id, bool status) {
char key[8] = {0};
snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
delSetting(key);
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
#endif
}
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void rfbSetup() {
#if MQTT_SUPPORT
mqttRegister(_rfbMqttCallback);
#endif
#if WEB_SUPPORT
wsOnSendRegister(_rfbWebSocketOnSend);
wsOnActionRegister(_rfbWebSocketOnAction);
#endif
// Register oop
espurnaRegisterLoop(rfbLoop);
}
void rfbLoop() {
_rfbReceive();
}
#endif

217
espurna/scheduler.ino Executable file
View File

@ -0,0 +1,217 @@
/*
SCHEDULER MODULE
Copyright (C) 2017 by faina09
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#if SCHEDULER_SUPPORT
#include <TimeLib.h>
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "sch", 3) == 0);
}
void _schWebSocketOnSend(JsonObject &root){
if (relayCount() > 0) {
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
JsonObject &scheduler = sch.createNestedObject();
scheduler["schEnabled"] = getSetting("schEnabled", i, 1).toInt() == 1;
scheduler["schSwitch"] = getSetting("schSwitch", i, 0).toInt();
scheduler["schAction"] = getSetting("schAction", i, 0).toInt();
scheduler["schType"] = getSetting("schType", i, 0).toInt();
scheduler["schHour"] = getSetting("schHour", i, 0).toInt();
scheduler["schMinute"] = getSetting("schMinute", i, 0).toInt();
scheduler["schWDs"] = getSetting("schWDs", i, "");
}
}
}
#endif // WEB_SUPPORT
// -----------------------------------------------------------------------------
void _schConfigure() {
bool delete_flag = false;
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) delete_flag = true;
if (delete_flag) {
delSetting("schEnabled", i);
delSetting("schSwitch", i);
delSetting("schAction", i);
delSetting("schHour", i);
delSetting("schMinute", i);
delSetting("schWDs", i);
delSetting("schType", i);
} else {
#if DEBUG_SUPPORT
int sch_enabled = getSetting("schEnabled", i, 1).toInt() == 1;
int sch_action = getSetting("schAction", i, 0).toInt();
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
String sch_weekdays = getSetting("schWDs", i, "");
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
DEBUG_MSG_P(
PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d on %s%s\n"),
i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch,
sch_action, sch_hour, sch_minute, (char *) sch_weekdays.c_str(),
sch_enabled ? "" : " (disabled)"
);
#endif // DEBUG_SUPPORT
}
}
}
bool _schIsThisWeekday(String weekdays){
// Convert from Sunday to Monday as day 1
int w = weekday(now()) - 1;
if (w == 0) w = 7;
char pch;
char * p = (char *) weekdays.c_str();
unsigned char position = 0;
while (pch = p[position++]) {
if ((pch - '0') == w) return true;
}
return false;
}
int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){
time_t t = now();
unsigned char now_hour = hour(t);
unsigned char now_minute = minute(t);
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
}
void _schCheck() {
// Check schedules
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) break;
// Skip disabled schedules
if (getSetting("schEnabled", i, 1).toInt() == 0) continue;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt();
if (SCHEDULER_TYPE_SWITCH == sch_type) {
int sch_action = getSetting("schAction", i, 0).toInt();
DEBUG_MSG_P(PSTR("[SCH] Switching switch %d to %d\n"), sch_switch, sch_action);
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (SCHEDULER_TYPE_DIM == sch_type) {
int sch_brightness = getSetting("schAction", i, -1).toInt();
DEBUG_MSG_P(PSTR("[SCH] Set channel %d value to %d\n"), sch_switch, sch_brightness);
lightChannel(sch_switch, sch_brightness);
lightUpdate(true, true);
}
#endif
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i);
// Show minutes to trigger every 15 minutes
// or every minute if less than 15 minutes to scheduled time.
// This only works for schedules on this same day.
// For instance, if your scheduler is set for 00:01 you will only
// get one notification before the trigger (at 00:00)
} else if (minutes_to_trigger > 0) {
#if DEBUG_SUPPORT
if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, i
);
}
#endif
}
}
}
}
void _schLoop() {
// Check time has been sync'ed
if (!ntpSynced()) return;
// Check schedules every minute at hh:mm:00
static unsigned long last_minute = 60;
unsigned char current_minute = minute();
if (current_minute != last_minute) {
last_minute = current_minute;
_schCheck();
}
}
// -----------------------------------------------------------------------------
void schSetup() {
_schConfigure();
// Update websocket clients
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnReceiveRegister(_schWebSocketOnReceive);
wsOnAfterParseRegister(_schConfigure);
#endif
// Register loop
espurnaRegisterLoop(_schLoop);
}
#endif // SCHEDULER_SUPPORT

1065
espurna/sensor.ino Executable file

File diff suppressed because it is too large Load Diff

193
espurna/sensors/AM2320Sensor.h Executable file
View File

@ -0,0 +1,193 @@
// -----------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Copyright (C) 2018 by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && AM2320_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf
#define AM2320_I2C_READ_REGISTER_DATA 0x03 // Read one or more data registers
#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS 0x10 // Multiple sets of binary data to write multiple registers
/*
Register | Address | Register | Address | Register | Address | Register | Address
-----------------+---------+--------------------+---------+-------------------------+---------+-----------+--------
High humidity | 0x00 | Model High | 0x08 | Users register a high | 0x10 | Retention | 0x18
Low humidity | 0x01 | Model Low | 0x09 | Users register a low | 0x11 | Retention | 0x19
High temperature | 0x02 | The version number | 0x0A | Users register 2 high | 0x12 | Retention | 0x1A
Low temperature | 0x03 | Device ID(24-31)Bit| 0x0B | Users register 2 low | 0x13 | Retention | 0x1B
Retention | 0x04 | Device ID(24-31)Bit| 0x0C | Retention | 0x14 | Retention | 0x1C
Retention | 0x05 | Device ID(24-31)Bit| 0x0D | Retention | 0x15 | Retention | 0x1D
Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention | 0x16 | Retention | 0x1E
Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F
*/
class AM2320Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
AM2320Sensor(): I2CSensor() {
_count = 2;
_sensor_id = SENSOR_AM2320_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C, 0xB8};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/*
// Get device model, version, device_id
void _init() {
i2c_wakeup();
delayMicroseconds(800);
unsigned char _buffer[11];
// 0x08 = read address
// 7 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return false;
}
uint16_t model = (_buffer[2] << 8) | _buffer[3];
uint8_t version = _buffer[4];
uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5];
}
*/
void _read() {
i2c_wakeup();
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800); // just to be on safe side
// 0x00 = read address
// 4 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return false;
}
unsigned char _buffer[8];
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800 + ((3000-800)/2) );
i2c_read_buffer(_address, _buffer, 8);
// Humidity : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 %
// 0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 %
// Temperature: 00FA = (F×16)+A = 250 => temperature = 250÷10 = 25.0 C
// 0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C
// Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature
// _buffer 0 = function code
// _buffer 1 = number of bytes
// _buffer 2-3 = high/low humidity
// _buffer 4-5 = high/low temperature
// _buffer 6-7 = CRC low/high
unsigned int responseCRC = 0;
responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]);
if (responseCRC == _CRC16(_buffer)) {
int foo = (_buffer[2] << 8) | _buffer[3];
_humidity = foo / 10.0;
foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5]; // clean bit 15 and merge
_temperature = foo / 10.0;
if (_buffer[4] & 0x80) { // is bit 15 == 1
_temperature = _temperature * -1; // negative temperature
}
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_CRC;
return;
}
}
unsigned int _CRC16(unsigned char buffer[]) {
unsigned int crc16 = 0xFFFF;
for (unsigned int i = 0; i < 6; i++) {
crc16 ^= buffer[i];
for (unsigned int b = 8; b != 0; b--) {
if (crc16 & 0x01) { // is lsb set
crc16 >>= 1;
crc16 ^= 0xA001;
} else {
crc16 >>= 1;
}
}
}
return crc16;
}
double _temperature = 0;
double _humidity = 0;
};
#endif // SENSOR_SUPPORT && AM2320_SUPPORT

66
espurna/sensors/AnalogSensor.h Executable file
View File

@ -0,0 +1,66 @@
// -----------------------------------------------------------------------------
// Analog Sensor (maps to an analogRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ANALOG_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class AnalogSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
AnalogSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_ANALOG_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
return String("ANALOG @ TOUT");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_ANALOG;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return analogRead(0);
return 0;
}
};
#endif // SENSOR_SUPPORT && ANALOG_SUPPORT

135
espurna/sensors/BH1750Sensor.h Executable file
View File

@ -0,0 +1,135 @@
// -----------------------------------------------------------------------------
// BH1750 Liminosity sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BH1750_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms.
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
class BH1750Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
BH1750Sensor(): I2CSensor() {
_sensor_id = SENSOR_BH1750_ID;
_count = 1;
}
// ---------------------------------------------------------------------
void setMode(unsigned char mode) {
if (_mode == mode) return;
_mode = mode;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getMode() {
return _mode;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Run configuration on next update
_run_configure = true;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "BH1750 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_LUX;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_lux = _read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _lux;
return 0;
}
protected:
double _read() {
// For one-shot modes reconfigure sensor & wait for conversion
if (_run_configure) {
// Configure mode
i2c_write_uint8(_address, _mode);
// According to datasheet
// conversion time is ~16ms for low resolution
// and ~120 for high resolution
// but more time is needed
unsigned long wait = (_mode & 0x02) ? 24 : 180;
unsigned long start = millis();
while (millis() - start < wait) delay(1);
// Keep on running configure each time if one-shot mode
_run_configure = _mode & 0x20;
}
double level = (double) i2c_read_uint16(_address);
if (level == 0xFFFF) {
_error = SENSOR_ERROR_CRC;
_run_configure = true;
return 0;
}
return level / 1.2;
}
unsigned char _mode;
bool _run_configure = false;
double _lux = 0;
};
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

438
espurna/sensors/BMX280Sensor.h Executable file
View File

@ -0,0 +1,438 @@
// -----------------------------------------------------------------------------
// BME280/BMP280 Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BMX280_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define BMX280_CHIP_BMP280 0x58
#define BMX280_CHIP_BME280 0x60
#define BMX280_REGISTER_DIG_T1 0x88
#define BMX280_REGISTER_DIG_T2 0x8A
#define BMX280_REGISTER_DIG_T3 0x8C
#define BMX280_REGISTER_DIG_P1 0x8E
#define BMX280_REGISTER_DIG_P2 0x90
#define BMX280_REGISTER_DIG_P3 0x92
#define BMX280_REGISTER_DIG_P4 0x94
#define BMX280_REGISTER_DIG_P5 0x96
#define BMX280_REGISTER_DIG_P6 0x98
#define BMX280_REGISTER_DIG_P7 0x9A
#define BMX280_REGISTER_DIG_P8 0x9C
#define BMX280_REGISTER_DIG_P9 0x9E
#define BMX280_REGISTER_DIG_H1 0xA1
#define BMX280_REGISTER_DIG_H2 0xE1
#define BMX280_REGISTER_DIG_H3 0xE3
#define BMX280_REGISTER_DIG_H4 0xE4
#define BMX280_REGISTER_DIG_H5 0xE5
#define BMX280_REGISTER_DIG_H6 0xE7
#define BMX280_REGISTER_CHIPID 0xD0
#define BMX280_REGISTER_VERSION 0xD1
#define BMX280_REGISTER_SOFTRESET 0xE0
#define BMX280_REGISTER_CAL26 0xE1
#define BMX280_REGISTER_CONTROLHUMID 0xF2
#define BMX280_REGISTER_CONTROL 0xF4
#define BMX280_REGISTER_CONFIG 0xF5
#define BMX280_REGISTER_PRESSUREDATA 0xF7
#define BMX280_REGISTER_TEMPDATA 0xFA
#define BMX280_REGISTER_HUMIDDATA 0xFD
class BMX280Sensor : public I2CSensor {
public:
static unsigned char addresses[2];
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
BMX280Sensor(): I2CSensor() {
_sensor_id = SENSOR_BMX280_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return MAGNITUDE_TEMPERATURE;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return MAGNITUDE_PRESSURE;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return MAGNITUDE_HUMIDITY;
}
#endif
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() {
if (_run_init) {
i2cClearBus();
_init();
}
if (_chip == 0) {
_error = SENSOR_ERROR_UNKNOWN_ID;
return;
}
_error = SENSOR_ERROR_OK;
#if BMX280_MODE == 1
_forceRead();
#endif
_error = _read();
if (_error != SENSOR_ERROR_OK) {
_run_init = true;
}
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return _temperature;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return _pressure / 100;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return _humidity;
}
#endif
return 0;
}
// Load the configuration manifest
static void manifest(JsonArray& sensors) {
char buffer[10];
JsonObject& sensor = sensors.createNestedObject();
sensor["sensor_id"] = SENSOR_BMX280_ID;
JsonArray& fields = sensor.createNestedArray("fields");
{
JsonObject& field = fields.createNestedObject();
field["tag"] = UI_TAG_SELECT;
field["name"] = "address";
field["label"] = "Address";
JsonArray& options = field.createNestedArray("options");
{
JsonObject& option = options.createNestedObject();
option["name"] = "auto";
option["value"] = 0;
}
for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) {
JsonObject& option = options.createNestedObject();
snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]);
option["name"] = String(buffer);
option["value"] = BMX280Sensor::addresses[i];
}
}
};
void getConfig(JsonObject& root) {
root["sensor_id"] = _sensor_id;
root["address"] = _address;
};
void setConfig(JsonObject& root) {
if (root.containsKey("address")) setAddress(root["address"]);
};
protected:
void _init() {
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
nice_delay(10);
// No chip ID by default
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
_chip = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
return;
}
_count = 0;
#if BMX280_TEMPERATURE > 0
++_count;
#endif
#if BMX280_PRESSURE > 0
++_count;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) ++_count;
#endif
_readCoefficients();
unsigned char data = 0;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
data = (BMX280_STANDBY << 0x5) & 0xE0;
data |= (BMX280_FILTER << 0x02) & 0x1C;
i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data);
data = (BMX280_HUMIDITY) & 0x07;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data);
data = (BMX280_TEMPERATURE << 5) & 0xE0;
data |= (BMX280_PRESSURE << 2) & 0x1C;
data |= (BMX280_MODE) & 0x03;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
_measurement_delay = _measurementTime();
_run_init = false;
_ready = true;
}
void _readCoefficients() {
_bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1);
_bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2);
_bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3);
_bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1);
_bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2);
_bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3);
_bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4);
_bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5);
_bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6);
_bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7);
_bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8);
_bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9);
_bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1);
_bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2);
_bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3);
_bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF);
_bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4);
_bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6);
}
unsigned long _measurementTime() {
// Measurement Time (as per BMX280 datasheet section 9.1)
// T_max(ms) = 1.25
// + (2.3 * T_oversampling)
// + (2.3 * P_oversampling + 0.575)
// + (2.4 * H_oversampling + 0.575)
// ~ 9.3ms for current settings
double t = 1.25;
#if BMX280_TEMPERATURE > 0
t += (2.3 * BMX280_TEMPERATURE);
#endif
#if BMX280_PRESSURE > 0
t += (2.3 * BMX280_PRESSURE + 0.575);
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
t += (2.4 * BMX280_HUMIDITY + 0.575);
}
#endif
return round(t + 1); // round up
}
void _forceRead() {
// We set the sensor in "forced mode" to force a reading.
// After the reading the sensor will go back to sleep mode.
uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL);
value = (value & 0xFC) + 0x01;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);
nice_delay(_measurement_delay);
}
unsigned char _read() {
#if BMX280_TEMPERATURE > 0
int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA);
if (0xFFFF == adc_T) return SENSOR_ERROR_I2C;
adc_T <<= 8;
adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2);
adc_T >>= 4;
int32_t var1t = ((((adc_T>>3) -
((int32_t)_bmx280_calib.dig_T1 <<1))) *
((int32_t)_bmx280_calib.dig_T2)) >> 11;
int32_t var2t = (((((adc_T>>4) -
((int32_t)_bmx280_calib.dig_T1)) *
((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) *
((int32_t)_bmx280_calib.dig_T3)) >> 14;
int32_t t_fine = var1t + var2t;
double T = (t_fine * 5 + 128) >> 8;
_temperature = T / 100;
#else
int32_t t_fine = 102374; // ~20ºC
#endif
// -----------------------------------------------------------------
#if BMX280_PRESSURE > 0
int64_t var1, var2, p;
int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA);
if (0xFFFF == adc_P) return SENSOR_ERROR_I2C;
adc_P <<= 8;
adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2);
adc_P >>= 4;
var1 = ((int64_t)t_fine) - 128000;
var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6;
var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17);
var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35);
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero
p = 1048576 - adc_P;
p = (((p<<31) - var2)*3125) / var1;
var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4);
_pressure = (double) p / 256;
#endif
// -----------------------------------------------------------------
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA);
if (0xFFFF == adc_H) return SENSOR_ERROR_I2C;
int32_t v_x1_u32r;
v_x1_u32r = (t_fine - ((int32_t)76800));
v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) -
(((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
(((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) *
(((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
((int32_t)_bmx280_calib.dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
double h = (v_x1_u32r >> 12);
_humidity = h / 1024.0;
}
#endif
return SENSOR_ERROR_OK;
}
// ---------------------------------------------------------------------
unsigned char _chip;
unsigned long _measurement_delay;
bool _run_init = false;
double _temperature = 0;
double _pressure = 0;
double _humidity = 0;
typedef struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
} bmx280_calib_t;
bmx280_calib_t _bmx280_calib;
};
// Static inizializations
unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77};
#endif // SENSOR_SUPPORT && BMX280_SUPPORT

101
espurna/sensors/BaseSensor.h Executable file
View File

@ -0,0 +1,101 @@
// -----------------------------------------------------------------------------
// Abstract sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out
#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
class BaseSensor {
public:
// Constructor
BaseSensor() {}
// Destructor
~BaseSensor() {}
// Initialization method, must be idempotent
virtual void begin() {}
// Loop-like method, call it in your main loop
virtual void tick() {}
// Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() {}
// Post-read hook (usually to reset things)
virtual void post() {}
// Descriptive name of the sensor
virtual String description() {}
// Address of the sensor (it could be the GPIO or I2C address)
virtual String address(unsigned char index) {}
// Descriptive name of the slot # index
virtual String slot(unsigned char index) {};
// Type for slot # index
virtual unsigned char type(unsigned char index) {}
// Current value for slot # index
virtual double value(unsigned char index) {}
// Retrieve current instance configuration
virtual void getConfig(JsonObject& root) {};
// Save current instance configuration
virtual void setConfig(JsonObject& root) {};
// Load the configuration manifest
static void manifest(JsonArray& root) {};
// Sensor ID
unsigned char getID() { return _sensor_id; };
// Return status (true if no errors)
bool status() { return 0 == _error; }
// Return ready status (true for ready)
bool ready() { return _ready; }
// Return sensor last internal error
int error() { return _error; }
// Number of available slots
unsigned char count() { return _count; }
// Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; };
protected:
TSensorCallback _callback = NULL;
unsigned char _sensor_id = 0x00;
int _error = 0;
bool _dirty = true;
unsigned char _count = 0;
bool _ready = false;
};
#endif

372
espurna/sensors/CSE7766Sensor.h Executable file
View File

@ -0,0 +1,372 @@
// -----------------------------------------------------------------------------
// CSE7766 based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && CSE7766_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class CSE7766Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
CSE7766Sensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_CSE7766_ID;
}
~CSE7766Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
void expectedCurrent(double expected) {
if ((expected > 0) && (_current > 0)) {
_ratioC = _ratioC * (expected / _current);
}
}
void expectedVoltage(unsigned int expected) {
if ((expected > 0) && (_voltage > 0)) {
_ratioV = _ratioV * (expected / _voltage);
}
}
void expectedPower(unsigned int expected) {
if ((expected > 0) && (_active > 0)) {
_ratioP = _ratioP * (expected / _active);
}
}
void setCurrentRatio(double value) {
_ratioC = value;
};
void setVoltageRatio(double value) {
_ratioV = value;
};
void setPowerRatio(double value) {
_ratioP = value;
};
double getCurrentRatio() {
return _ratioC;
};
double getVoltageRatio() {
return _ratioV;
};
double getPowerRatio() {
return _ratioP;
};
void resetRatios() {
_ratioC = _ratioV = _ratioP = 1.0;
}
void resetEnergy() {
_energy = 0;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
if (1 == _pin_rx) {
Serial.begin(CSE7766_BAUDRATE);
} else {
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(CSE7766_BAUDRATE);
}
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
if (1 == _pin_rx) {
snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _energy;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/**
* "
* Checksum is the sum of all data
* except for packet header and packet tail lowering by 8bit (...)
* "
* @return bool
*/
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 2; i < 23; i++) {
checksum += _data[i];
}
return checksum == _data[23];
}
void _process() {
// Sample data:
// 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load)
// F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load)
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: _process: ");
for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]);
DEBUG_MSG("\n");
#endif
// Checksum
if (!_checksum()) {
_error = SENSOR_ERROR_CRC;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n");
#endif
return;
}
// Calibration
if (0xAA == _data[0]) {
_error = SENSOR_ERROR_CALIBRATION;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n");
#endif
return;
}
if ((_data[0] & 0xFC) > 0xF0) {
_error = SENSOR_ERROR_OTHER;
#if SENSOR_DEBUG
if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n");
if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n");
if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n");
if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n");
#endif
return;
}
// Calibration coefficients
unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770
unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030
unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000
// Adj: this looks like a sampling report
uint8_t adj = _data[20]; // F1 11110001
// Calculate voltage
_voltage = 0;
if ((adj & 0x40) == 0x40) {
unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817
_voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41
}
// Calculate power
_active = 0;
if ((adj & 0x10) == 0x10) {
if ((_data[0] & 0xF2) != 0xF2) {
unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709
_active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20
}
}
// Calculate current
_current = 0;
if ((adj & 0x20) == 0x20) {
if (_active > 0) {
unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376
_current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75
}
}
// Calculate energy
static unsigned long cf_pulses_last = 0;
unsigned long cf_pulses = _data[21] << 8 | _data[22];
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
_energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0;
cf_pulses_last = cf_pulses;
}
void _read() {
_error = SENSOR_ERROR_OK;
static unsigned char index = 0;
static unsigned long last = millis();
while (_serial_available()) {
// A 24 bytes message takes ~55ms to go through at 4800 bps
// Reset counter if more than 1000ms have passed since last byte.
if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
last = millis();
uint8_t byte = _serial_read();
// first byte must be 0x55 or 0xF?
if (0 == index) {
if ((0x55 != byte) && (byte < 0xF0)) {
continue;
}
// second byte must be 0x5A
} else if (1 == index) {
if (0x5A != byte) {
index = 0;
continue;
}
}
_data[index++] = byte;
if (index > 23) {
_serial_flush();
break;
}
}
// Process packet
if (24 == index) {
_process();
index = 0;
}
}
// ---------------------------------------------------------------------
bool _serial_available() {
if (1 == _pin_rx) {
return Serial.available();
} else {
return _serial->available();
}
}
void _serial_flush() {
if (1 == _pin_rx) {
return Serial.flush();
} else {
return _serial->flush();
}
}
uint8_t _serial_read() {
if (1 == _pin_rx) {
return Serial.read();
} else {
return _serial->read();
}
}
// ---------------------------------------------------------------------
unsigned int _pin_rx = CSE7766_PIN;
bool _inverted = CSE7766_PIN_INVERSE;
SoftwareSerial * _serial = NULL;
double _active = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;
double _ratioV = 1.0;
double _ratioC = 1.0;
double _ratioP = 1.0;
unsigned char _data[24];
};
#endif // SENSOR_SUPPORT && CSE7766_SUPPORT

245
espurna/sensors/DHTSensor.h Executable file
View File

@ -0,0 +1,245 @@
// -----------------------------------------------------------------------------
// DHTXX Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DHT_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#define DHT_MAX_DATA 5
#define DHT_MAX_ERRORS 5
#define DHT_MIN_INTERVAL 2000
#define DHT_CHIP_DHT11 11
#define DHT_CHIP_DHT22 22
#define DHT_CHIP_DHT21 21
#define DHT_CHIP_AM2301 21
class DHTSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DHTSensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_DHTXX_ID;
}
~DHTSensor() {
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setType(unsigned char type) {
_type = type;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getType() {
return _type;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
_count = 0;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
_previous = _gpio;
_count = 2;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", _type, _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) {
_error = SENSOR_ERROR_OK;
return;
}
unsigned long low = 0;
unsigned long high = 0;
unsigned char dhtData[DHT_MAX_DATA] = {0};
unsigned char byteInx = 0;
unsigned char bitInx = 7;
// Send start signal to DHT sensor
if (++_errors > DHT_MAX_ERRORS) {
_errors = 0;
digitalWrite(_gpio, HIGH);
nice_delay(250);
}
pinMode(_gpio, OUTPUT);
noInterrupts();
digitalWrite(_gpio, LOW);
if (_type == DHT_CHIP_DHT11) {
nice_delay(20);
} else {
delayMicroseconds(500);
}
digitalWrite(_gpio, HIGH);
delayMicroseconds(40);
pinMode(_gpio, INPUT_PULLUP);
delayMicroseconds(10);
// No errors, read the 40 data bits
for( int k = 0; k < 41; k++ ) {
// Starts new data transmission with >50us low signal
low = _signal(100, LOW);
if (low == 0) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
// Check to see if after >70us rx data is a 0 or a 1
high = _signal(100, HIGH);
if (high == 0) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
// Skip the first bit
if (k == 0) continue;
// add the current read to the output data
// since all dhtData array where set to 0 at the start,
// only look for "1" (>28us us)
if (high > low) dhtData[byteInx] |= (1 << bitInx);
// index to next byte
if (bitInx == 0) {
bitInx = 7;
++byteInx;
} else {
--bitInx;
}
}
interrupts();
// Verify checksum
if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) {
_error = SENSOR_ERROR_CRC;
return;
}
// Get humidity from Data[0] and Data[1]
if (_type == DHT_CHIP_DHT11) {
_humidity = dhtData[0];
} else {
_humidity = dhtData[0] * 256 + dhtData[1];
_humidity /= 10;
}
// Get temp from Data[2] and Data[3]
if (_type == DHT_CHIP_DHT11) {
_temperature = dhtData[2];
} else {
_temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
_temperature /= 10;
if (dhtData[2] & 0x80) _temperature *= -1;
}
_last_ok = millis();
_errors = 0;
_error = SENSOR_ERROR_OK;
}
unsigned long _signal(int usTimeOut, bool state) {
unsigned long uSec = 1;
while (digitalRead(_gpio) == state) {
if (++uSec > usTimeOut) return 0;
delayMicroseconds(1);
}
return uSec;
}
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
unsigned char _type = DHT_CHIP_DHT22;
unsigned long _last_ok = 0;
unsigned char _errors = 0;
double _temperature = 0;
unsigned int _humidity = 0;
};
#endif // SENSOR_SUPPORT && DHT_SUPPORT

319
espurna/sensors/DallasSensor.h Executable file
View File

@ -0,0 +1,319 @@
// -----------------------------------------------------------------------------
// Dallas OneWire Sensor
// Uses OneWire library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DALLAS_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <vector>
#include <OneWire.h>
#define DS_CHIP_DS18S20 0x10
#define DS_CHIP_DS1822 0x22
#define DS_CHIP_DS18B20 0x28
#define DS_CHIP_DS1825 0x3B
#define DS_DATA_SIZE 9
#define DS_PARASITE 1
#define DS_DISCONNECTED -127
#define DS_CMD_START_CONVERSION 0x44
#define DS_CMD_READ_SCRATCHPAD 0xBE
class DallasSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DallasSensor(): BaseSensor() {
_sensor_id = SENSOR_DALLAS_ID;
}
~DallasSensor() {
if (_wire) delete _wire;
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
if (_gpio == gpio) return;
_gpio = gpio;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
// OneWire
if (_wire) delete _wire;
_wire = new OneWire(_gpio);
// Search devices
loadDevices();
// If no devices found check again pulling up the line
if (_count == 0) {
pinMode(_gpio, INPUT_PULLUP);
loadDevices();
}
// Check connection
if (_count == 0) {
gpioReleaseLock(_gpio);
} else {
_previous = _gpio;
}
_ready = true;
_dirty = false;
}
// Loop-like method, call it in your main loop
void tick() {
static unsigned long last = 0;
if (millis() - last < DALLAS_READ_INTERVAL) return;
last = millis();
// Every second we either start a conversion or read the scratchpad
static bool conversion = true;
if (conversion) {
// Start conversion
_wire->reset();
_wire->skip();
_wire->write(DS_CMD_START_CONVERSION, DS_PARASITE);
} else {
// Read scratchpads
for (unsigned char index=0; index<_devices.size(); index++) {
// Read scratchpad
if (_wire->reset() == 0) {
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
_wire->select(_devices[index].address);
_wire->write(DS_CMD_READ_SCRATCHPAD);
uint8_t data[DS_DATA_SIZE];
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
data[i] = _wire->read();
}
#if false
Serial.printf("[DS18B20] Data = ");
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.printf(" CRC = %02X\n", OneWire::crc8(data, DS_DATA_SIZE-1));
#endif
if (_wire->reset() != 1) {
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
memcpy(_devices[index].data, data, DS_DATA_SIZE);
}
}
conversion = !conversion;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "Dallas @ GPIO%d", _gpio);
return String(buffer);
}
// Address of the device
String address(unsigned char index) {
char buffer[20] = {0};
if (index < _count) {
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X",
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7]
);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
if (index < _count) {
char buffer[40];
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
chipAsString(index).c_str(),
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7],
_gpio
);
return String(buffer);
}
return String();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) return MAGNITUDE_TEMPERATURE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
if (index >= _count) return 0;
uint8_t * data = _devices[index].data;
if (OneWire::crc8(data, DS_DATA_SIZE-1) != data[DS_DATA_SIZE-1]) {
_error = SENSOR_ERROR_CRC;
return 0;
}
// Registers
// byte 0: temperature LSB
// byte 1: temperature MSB
// byte 2: high alarm temp
// byte 3: low alarm temp
// byte 4: DS18S20: store for crc
// DS18B20 & DS1822: configuration register
// byte 5: internal use & crc
// byte 6: DS18S20: COUNT_REMAIN
// DS18B20 & DS1822: store for crc
// byte 7: DS18S20: COUNT_PER_C
// DS18B20 & DS1822: store for crc
// byte 8: SCRATCHPAD_CRC
int16_t raw = (data[1] << 8) | data[0];
if (chip(index) == DS_CHIP_DS18S20) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
}
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw & ~7; // 9 bit res, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
// 12 bit res, 750 ms
}
double value = (float) raw / 16.0;
if (value == DS_DISCONNECTED) {
_error = SENSOR_ERROR_CRC;
return 0;
}
return value;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
bool validateID(unsigned char id) {
return (id == DS_CHIP_DS18S20) || (id == DS_CHIP_DS18B20) || (id == DS_CHIP_DS1822) || (id == DS_CHIP_DS1825);
}
unsigned char chip(unsigned char index) {
if (index < _count) return _devices[index].address[0];
return 0;
}
String chipAsString(unsigned char index) {
unsigned char chip_id = chip(index);
if (chip_id == DS_CHIP_DS18S20) return String("DS18S20");
if (chip_id == DS_CHIP_DS18B20) return String("DS18B20");
if (chip_id == DS_CHIP_DS1822) return String("DS1822");
if (chip_id == DS_CHIP_DS1825) return String("DS1825");
return String("Unknown");
}
void loadDevices() {
uint8_t address[8];
_wire->reset();
_wire->reset_search();
while (_wire->search(address)) {
// Check CRC
if (_wire->crc8(address, 7) == address[7]) {
// Check ID
if (validateID(address[0])) {
ds_device_t device;
memcpy(device.address, address, 8);
_devices.push_back(device);
}
}
}
_count = _devices.size();
}
typedef struct {
uint8_t address[8];
uint8_t data[DS_DATA_SIZE];
} ds_device_t;
std::vector<ds_device_t> _devices;
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
OneWire * _wire = NULL;
};
#endif // SENSOR_SUPPORT && DALLAS_SUPPORT

106
espurna/sensors/DigitalSensor.h Executable file
View File

@ -0,0 +1,106 @@
// -----------------------------------------------------------------------------
// Digital Sensor (maps to a digitalRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DIGITAL_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class DigitalSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DigitalSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_DIGITAL_ID;
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setDefault(bool value) {
_default = value;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
bool getDefault() {
return _default;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(_gpio, _mode);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "DIGITAL @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_DIGITAL;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return (digitalRead(_gpio) == _default) ? 0 : 1;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned char _gpio;
unsigned char _mode;
bool _default = false;
};
#endif // SENSOR_SUPPORT && DIGITAL_SUPPORT

349
espurna/sensors/ECH1560Sensor.h Executable file
View File

@ -0,0 +1,349 @@
// -----------------------------------------------------------------------------
// ECH1560 based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ECH1560_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class ECH1560Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
ECH1560Sensor(): BaseSensor(), _data() {
_count = 3;
_sensor_id = SENSOR_ECH1560_ID;
}
~ECH1560Sensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setCLK(unsigned char clk) {
if (_clk == clk) return;
_clk = clk;
_dirty = true;
}
void setMISO(unsigned char miso) {
if (_miso == miso) return;
_miso = miso;
_dirty = true;
}
void setInverted(bool inverted) {
_inverted = inverted;
}
// ---------------------------------------------------------------------
unsigned char getCLK() {
return _clk;
}
unsigned char getMISO() {
return _miso;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
pinMode(_clk, INPUT);
pinMode(_miso, INPUT);
_enableInterrupts(true);
_dirty = false;
_ready = true;
}
// Loop-like method, call it in your main loop
void tick() {
if (_dosync) _sync();
}
// Descriptive name of the sensor
String description() {
char buffer[35];
snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _clk, _miso);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_APPARENT;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _apparent;
return 0;
}
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
(void) gpio;
// if we are trying to find the sync-time (CLK goes high for 1-2ms)
if (_dosync == false) {
_clk_count = 0;
// register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms
while (digitalRead(_clk) == HIGH) {
_clk_count += 1;
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
}
// if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission
if (_clk_count >= 33 && _clk_count <= 67) {
_dosync = true;
}
// we are in sync and logging CLK-highs
} else {
// increment an integer to keep track of how many bits we have read.
_bits_count += 1;
_nextbit = true;
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_clk = GPIO_NONE;
if (value) {
if (_interrupt_clk != _clk) {
if (_interrupt_clk != GPIO_NONE) _detach(_interrupt_clk);
_attach(this, _clk, RISING);
_interrupt_clk = _clk;
}
} else if (_interrupt_clk != GPIO_NONE) {
_detach(_interrupt_clk);
_interrupt_clk = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _sync() {
unsigned int byte1 = 0;
unsigned int byte2 = 0;
unsigned int byte3 = 0;
_bits_count = 0;
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2
if (_nextbit) {
if (_bits_count < 9) { // first Byte/8 bits in byte1
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
_nextbit = false;
}
}
}
if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power.
// voltage = 2 * (byte1 + byte2 / 255)
_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
// power:
_bits_count = 0;
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
byte1 = 0;
byte2 = 0;
byte3 = 0;
while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3.
if (_nextbit) {
if (_bits_count < 9) {
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) {
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
_nextbit = false;
} else {
byte3 = byte3 << 1;
if (digitalRead(_miso) == HIGH) byte3 |= 1;
_nextbit = false;
}
}
}
if (_inverted) {
byte1 = 255 - byte1;
byte2 = 255 - byte2;
byte3 = 255 - byte3;
}
// power = (byte1*255+byte2+byte3/255)/2
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
_dosync = false;
}
// If byte2 is not 3 or something else than 0, something is wrong!
if (byte2 == 0) {
_dosync = false;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n"));
#endif
}
}
// ---------------------------------------------------------------------
unsigned char _clk = 0;
unsigned char _miso = 0;
bool _inverted = false;
volatile long _bits_count = 0;
volatile long _clk_count = 0;
volatile bool _dosync = false;
volatile bool _nextbit = true;
double _apparent = 0;
double _voltage = 0;
double _current = 0;
unsigned char _data[24];
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
ECH1560Sensor * _ech1560_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _ech1560_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_ech1560_sensor_instance[index]) {
_ech1560_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _ech1560_sensor_isr_0() { _ech1560_sensor_isr(0); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_1() { _ech1560_sensor_isr(1); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_2() { _ech1560_sensor_isr(2); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_3() { _ech1560_sensor_isr(3); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_4() { _ech1560_sensor_isr(4); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_5() { _ech1560_sensor_isr(5); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_12() { _ech1560_sensor_isr(12); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_13() { _ech1560_sensor_isr(13); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_14() { _ech1560_sensor_isr(14); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_15() { _ech1560_sensor_isr(15); }
static void (*_ech1560_sensor_isr_list[10])() = {
_ech1560_sensor_isr_0, _ech1560_sensor_isr_1, _ech1560_sensor_isr_2,
_ech1560_sensor_isr_3, _ech1560_sensor_isr_4, _ech1560_sensor_isr_5,
_ech1560_sensor_isr_12, _ech1560_sensor_isr_13, _ech1560_sensor_isr_14,
_ech1560_sensor_isr_15
};
void ECH1560Sensor::_attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_ech1560_sensor_instance[index] = instance;
attachInterrupt(gpio, _ech1560_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void ECH1560Sensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_ech1560_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _ech1560_sensor_instance[index]->description().c_str());
#endif
_ech1560_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && ECH1560_SUPPORT

View File

@ -0,0 +1,150 @@
// -----------------------------------------------------------------------------
// ADS121-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
// ADC121 Registers
#define ADC121_REG_RESULT 0x00
#define ADC121_REG_ALERT 0x01
#define ADC121_REG_CONFIG 0x02
#define ADC121_REG_LIMITL 0x03
#define ADC121_REG_LIMITH 0x04
#define ADC121_REG_HYST 0x05
#define ADC121_REG_CONVL 0x06
#define ADC121_REG_CONVH 0x07
#define ADC121_RESOLUTION 12
#define ADC121_CHANNELS 1
class EmonADC121Sensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonADC121Sensor(): EmonSensor() {
_channels = ADC121_CHANNELS;
_sensor_id = SENSOR_EMON_ADC121_ID;
init();
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// Discover
unsigned char addresses[] = {0x50, 0x51, 0x52, 0x54, 0x55, 0x56, 0x58, 0x59, 0x5A};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Init sensor
_init();
// Just one channel
_count = _magnitudes;
// Bit depth
_resolution = ADC121_RESOLUTION;
// Call the parent class method
EmonSensor::begin();
// warmup channel 0 (the only one)
read(0);
}
// Descriptive name of the sensor
String description() {
char buffer[30];
snprintf(buffer, sizeof(buffer), "EMON @ ADC121 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
if (_address == 0) {
_error = SENSOR_ERROR_UNKNOWN_ID;
return;
}
_current[0] = read(0);
#if EMON_REPORT_ENERGY
static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (index == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
#endif
#if EMON_REPORT_POWER
if (index == i++) return _current[channel] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _init() {
i2c_write_uint8(_address, ADC121_REG_CONFIG, 0);
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value = i2c_read_uint16(_address, ADC121_REG_RESULT) & 0x0FFF;
return value;
}
};
#endif // SENSOR_SUPPORT && EMON_ADC121_SUPPORT

View File

@ -0,0 +1,347 @@
// -----------------------------------------------------------------------------
// ADS1X15-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
#define ADS1X15_CHANNELS (4)
#define ADS1X15_CHIP_ADS1015 (0)
#define ADS1X15_CHIP_ADS1115 (1)
#define ADS1X15_RESOLUTION (16)
#define ADS1015_CONVERSIONDELAY (1)
#define ADS1115_CONVERSIONDELAY (8)
#define ADS1015_BIT_SHIFT (4)
#define ADS1115_BIT_SHIFT (0)
#define ADS1X15_REG_POINTER_MASK (0x03)
#define ADS1X15_REG_POINTER_CONVERT (0x00)
#define ADS1X15_REG_POINTER_CONFIG (0x01)
#define ADS1X15_REG_POINTER_LOWTHRESH (0x02)
#define ADS1X15_REG_POINTER_HITHRESH (0x03)
#define ADS1X15_REG_CONFIG_OS_MASK (0x8000)
#define ADS1X15_REG_CONFIG_OS_SINGLE (0x8000) // Write: Set to start a single-conversion
#define ADS1X15_REG_CONFIG_OS_BUSY (0x0000) // Read: Bit = 0 when conversion is in progress
#define ADS1X15_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion
#define ADS1X15_REG_CONFIG_MUX_MASK (0x7000)
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default)
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0
#define ADS1X15_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1
#define ADS1X15_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2
#define ADS1X15_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3
#define ADS1X15_REG_CONFIG_PGA_MASK (0x0E00)
#define ADS1X15_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3
#define ADS1X15_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1
#define ADS1X15_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default)
#define ADS1X15_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4
#define ADS1X15_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8
#define ADS1X15_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16
#define ADS1X15_REG_CONFIG_MODE_MASK (0x0100)
#define ADS1X15_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode
#define ADS1X15_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default)
#define ADS1X15_REG_CONFIG_DR_MASK (0x00E0)
#define ADS1015_REG_CONFIG_DR_128SPS (0x0000) // 128 samples per second
#define ADS1015_REG_CONFIG_DR_250SPS (0x0020) // 250 samples per second
#define ADS1015_REG_CONFIG_DR_490SPS (0x0040) // 490 samples per second
#define ADS1015_REG_CONFIG_DR_920SPS (0x0060) // 920 samples per second
#define ADS1015_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default)
#define ADS1015_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second
#define ADS1015_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second
#define ADS1115_REG_CONFIG_DR_8SPS (0x0000) // 8 samples per second
#define ADS1115_REG_CONFIG_DR_16SPS (0x0020) // 16 samples per second
#define ADS1115_REG_CONFIG_DR_32SPS (0x0040) // 32 samples per second
#define ADS1115_REG_CONFIG_DR_64SPS (0x0060) // 64 samples per second
#define ADS1115_REG_CONFIG_DR_128SPS (0x0080) // 128 samples per second (default)
#define ADS1115_REG_CONFIG_DR_250SPS (0x00A0) // 250 samples per second
#define ADS1115_REG_CONFIG_DR_475SPS (0x00C0) // 475 samples per second
#define ADS1115_REG_CONFIG_DR_860SPS (0x00E0) // 860 samples per second
#define ADS1X15_REG_CONFIG_CMODE_MASK (0x0010)
#define ADS1X15_REG_CONFIG_CMODE_TRAD (0x0000) // Traditional comparator with hysteresis (default)
#define ADS1X15_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator
#define ADS1X15_REG_CONFIG_CPOL_MASK (0x0008)
#define ADS1X15_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default)
#define ADS1X15_REG_CONFIG_CPOL_ACTVHI (0x0008) // ALERT/RDY pin is high when active
#define ADS1X15_REG_CONFIG_CLAT_MASK (0x0004) // Determines if ALERT/RDY pin latches once asserted
#define ADS1X15_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default)
#define ADS1X15_REG_CONFIG_CLAT_LATCH (0x0004) // Latching comparator
#define ADS1X15_REG_CONFIG_CQUE_MASK (0x0003)
#define ADS1X15_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions
#define ADS1X15_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions
#define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions
#define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default)
class EmonADS1X15Sensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonADS1X15Sensor(): EmonSensor() {
_channels = ADS1X15_CHANNELS;
_sensor_id = SENSOR_EMON_ADS1X15_ID;
init();
}
// ---------------------------------------------------------------------
void setType(unsigned char type) {
if (_type == type) return;
_type = type;
_dirty = true;
}
void setMask(unsigned char mask) {
if (_mask == mask) return;
_mask = mask;
_dirty = true;
}
void setGain(unsigned int gain) {
if (_gain == gain) return;
_gain = gain;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getType() {
return _type;
}
unsigned char getMask() {
return _mask;
}
unsigned char getGain() {
return _gain;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// Discover
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Calculate ports
_ports = 0;
unsigned char mask = _mask;
while (mask) {
if (mask & 0x01) ++_ports;
mask = mask >> 1;
}
_count = _ports * _magnitudes;
// Bit depth
_resolution = ADS1X15_RESOLUTION;
// Reference based on gain
if (_gain == ADS1X15_REG_CONFIG_PGA_6_144V) _reference = 12.288;
if (_gain == ADS1X15_REG_CONFIG_PGA_4_096V) _reference = 8.192;
if (_gain == ADS1X15_REG_CONFIG_PGA_2_048V) _reference = 4.096;
if (_gain == ADS1X15_REG_CONFIG_PGA_1_024V) _reference = 2.048;
if (_gain == ADS1X15_REG_CONFIG_PGA_0_512V) _reference = 1.024;
if (_gain == ADS1X15_REG_CONFIG_PGA_0_256V) _reference = 0.512;
// Call the parent class method
EmonSensor::begin();
// warmup all channels
warmup();
}
// Descriptive name of the sensor
String description() {
char buffer[30];
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[35];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 (A%d) @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, channel, _address);
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "0x%02X:%u", _address, channel);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
void pre() {
static unsigned long last = 0;
for (unsigned char port=0; port<_ports; port++) {
unsigned char channel = getChannel(port);
_current[port] = getCurrent(channel);
#if EMON_REPORT_ENERGY
_energy[port] += (_current[port] * _voltage * (millis() - last) / 1000);
#endif
}
last = millis();
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char port = index % _ports;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return _current[port];
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return _current[port] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return _energy[port];
#endif
return 0;
}
protected:
//----------------------------------------------------------------------
// Protected
//----------------------------------------------------------------------
unsigned char getChannel(unsigned char port) {
unsigned char count = 0;
unsigned char bit = 1;
for (unsigned char channel=0; channel<ADS1X15_CHANNELS; channel++) {
if ((_mask & bit) == bit) {
if (count == port) return channel;
++count;
}
bit <<= 1;
}
return 0;
}
void warmup() {
for (unsigned char port=0; port<_ports; port++) {
unsigned char channel = getChannel(port);
_pivot[channel] = _adc_counts >> 1;
getCurrent(channel);
}
}
//----------------------------------------------------------------------
// I2C
//----------------------------------------------------------------------
void setConfigRegistry(unsigned char channel, bool continuous, bool start) {
// Start with default values
uint16_t config = 0;
config |= _gain; // Set PGA/voltage range (0x0200)
config |= ADS1X15_REG_CONFIG_DR_MASK; // Always at max speed (0x00E0)
//config |= ADS1X15_REG_CONFIG_CMODE_TRAD; // Traditional comparator (default val) (0x0000)
//config |= ADS1X15_REG_CONFIG_CPOL_ACTVLOW; // Alert/Rdy active low (default val) (0x0000)
//config |= ADS1X15_REG_CONFIG_CLAT_NONLAT; // Non-latching (default val) (0x0000)
config |= ADS1X15_REG_CONFIG_CQUE_NONE; // Disable the comparator (default val) (0x0003)
if (start) {
config |= ADS1X15_REG_CONFIG_OS_SINGLE; // Start a single-conversion (0x8000)
}
if (continuous) {
//config |= ADS1X15_REG_CONFIG_MODE_CONTIN; // Continuous mode (default) (0x0000)
} else {
config |= ADS1X15_REG_CONFIG_MODE_SINGLE; // Single-shot mode (0x0100)
}
config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000)
#if SENSOR_DEBUG
//Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config);
#endif
// Write config register to the ADC
i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config);
}
double getCurrent(unsigned char channel) {
// Force stop by setting single mode and back to continuous
static unsigned char previous = 9;
if (previous != channel) {
setConfigRegistry(channel, true, false);
setConfigRegistry(channel, false, false);
setConfigRegistry(channel, false, true);
nice_delay(10);
readADC(channel);
previous = channel;
}
setConfigRegistry(channel, true, true);
return read(channel);
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value = i2c_read_uint16(_address, ADS1X15_REG_POINTER_CONVERT);
if (_type = ADS1X15_CHIP_ADS1015) value >>= ADS1015_BIT_SHIFT;
delayMicroseconds(500);
return value;
}
unsigned char _type = ADS1X15_CHIP_ADS1115;
unsigned char _mask = 0x0F;
unsigned int _gain = ADS1X15_REG_CONFIG_PGA_4_096V;
unsigned char _ports;
};
#endif // SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT

View File

@ -0,0 +1,124 @@
// -----------------------------------------------------------------------------
// Energy Monitor Sensor using builtin ADC
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
#define EMON_ANALOG_RESOLUTION 10
#define EMON_ANALOG_CHANNELS 1
class EmonAnalogSensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonAnalogSensor(): EmonSensor() {
_channels = EMON_ANALOG_CHANNELS;
_sensor_id = SENSOR_EMON_ANALOG_ID;
init();
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// Number of slots
_count = _magnitudes;
// Bit depth
_resolution = EMON_ANALOG_RESOLUTION;
// Init analog PIN)
pinMode(0, INPUT);
// Call the parent class method
EmonSensor::begin();
// warmup channel 0 (the only one)
read(0);
}
// Descriptive name of the sensor
String description() {
return String("EMON @ ANALOG @ GPIO0");
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (index == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_current[0] = read(0);
#if EMON_REPORT_ENERGY
static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
#endif
#if EMON_REPORT_POWER
if (index == i++) return _current[channel] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
return 0;
}
protected:
unsigned int readADC(unsigned char channel) {
(void) channel;
return analogRead(0);
}
};
#endif // SENSOR_SUPPORT && EMON_ANALOG_SUPPORT

242
espurna/sensors/EmonSensor.h Executable file
View File

@ -0,0 +1,242 @@
// -----------------------------------------------------------------------------
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
class EmonSensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonSensor(): I2CSensor() {
// Calculate # of magnitudes
#if EMON_REPORT_CURRENT
++_magnitudes;
#endif
#if EMON_REPORT_POWER
++_magnitudes;
#endif
#if EMON_REPORT_ENERGY
++_magnitudes;
#endif
}
void expectedPower(unsigned char channel, unsigned int expected) {
if (channel >= _channels) return;
unsigned int actual = _current[channel] * _voltage;
if (actual == 0) return;
if (expected == actual) return;
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
_dirty = true;
}
void resetEnergy() {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = 0;
}
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {
if (_voltage == voltage) return;
_voltage = voltage;
_dirty = true;
}
void setReference(double reference) {
if (_reference == reference) return;
_reference = reference;
_dirty = true;
}
void setCurrentRatio(unsigned char channel, double current_ratio) {
if (channel >= _channels) return;
if (_current_ratio[channel] == current_ratio) return;
_current_ratio[channel] = current_ratio;
_dirty = true;
}
// ---------------------------------------------------------------------
double getVoltage() {
return _voltage;
}
double getReference() {
return _reference;
}
double getCurrentRatio(unsigned char channel) {
if (channel >= _channels) return 0;
return _current_ratio[channel];
}
unsigned char getChannels() {
return _channels;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
void begin() {
// Resolution
_adc_counts = 1 << _resolution;
// Calculations
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0;
_pivot[i] = _adc_counts >> 1;
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
_multiplier[i] = calculateMultiplier(_current_factor[i]);
}
#if SENSOR_DEBUG
DEBUG_MSG("[EMON] Reference (mV): %d\n", int(1000 * _reference));
DEBUG_MSG("[EMON] ADC counts: %d\n", _adc_counts);
for (unsigned char i=0; i<_channels; i++) {
DEBUG_MSG("[EMON] Channel #%d current ratio (mA/V): %d\n", i, int(1000 * _current_ratio[i]));
DEBUG_MSG("[EMON] Channel #%d current factor (mA/bit): %d\n", i, int(1000 * _current_factor[i]));
DEBUG_MSG("[EMON] Channel #%d Multiplier: %d\n", i, int(_multiplier[i]));
}
#endif
_ready = true;
_dirty = false;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
// Initializes internal variables
void init() {
_current_ratio = new double[_channels];
_current_factor = new double[_channels];
_multiplier = new uint16_t[_channels];
_pivot = new double[_channels];
_current = new double[_channels];
#if EMON_REPORT_ENERGY
_energy = new uint32_t[_channels];
#endif
}
virtual unsigned int readADC(unsigned char channel) {}
unsigned int calculateMultiplier(double current_factor) {
unsigned int s = 1;
unsigned int i = 1;
unsigned int m = s * i;
unsigned int multiplier;
while (m * current_factor < 1) {
multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10;
m = s * i;
}
return multiplier;
}
double read(unsigned char channel) {
int max = 0;
int min = _adc_counts;
double sum = 0;
unsigned long time_span = millis();
for (unsigned long i=0; i<_samples; i++) {
int sample;
double filtered;
// Read analog value
sample = readADC(channel);
if (sample > max) max = sample;
if (sample < min) min = sample;
// Digital low pass filter extracts the VDC offset
_pivot[channel] = (_pivot[channel] + (sample - _pivot[channel]) / EMON_FILTER_SPEED);
filtered = sample - _pivot[channel];
// Root-mean-square method
sum += (filtered * filtered);
}
time_span = millis() - time_span;
// Quick fix
if (_pivot[channel] < min || max < _pivot[channel]) {
_pivot[channel] = (max + min) / 2.0;
}
// Calculate current
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
double current = _current_factor[channel] * rms;
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
if (current < 0) current = 0;
#if SENSOR_DEBUG
DEBUG_MSG("[EMON] Channel: %d\n", channel);
DEBUG_MSG("[EMON] Total samples: %d\n", _samples);
DEBUG_MSG("[EMON] Total time (ms): %d\n", time_span);
DEBUG_MSG("[EMON] Sample frequency (Hz): %d\n", int(1000 * _samples / time_span));
DEBUG_MSG("[EMON] Max value: %d\n", max);
DEBUG_MSG("[EMON] Min value: %d\n", min);
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
#endif
// Check timing
if ((time_span > EMON_MAX_TIME)
|| ((time_span < EMON_MAX_TIME) && (_samples < EMON_MAX_SAMPLES))) {
_samples = (_samples * EMON_MAX_TIME) / time_span;
}
return current;
}
unsigned char _channels = 0; // Number of ADC channels available
unsigned char _magnitudes = 0; // Number of magnitudes per channel
unsigned long _samples = EMON_MAX_SAMPLES; // Samples (dynamically modificable)
unsigned char _resolution = 10; // ADC resolution in bits
unsigned long _adc_counts; // Max count
double _voltage = EMON_MAINS_VOLTAGE; // Mains voltage
double _reference = EMON_REFERENCE_VOLTAGE; // ADC reference voltage (100%)
double * _current_ratio; // Ratio ampers in main loop to voltage in secondary (per channel)
double * _current_factor; // Calculated, reads (RMS) to current (per channel)
uint16_t * _multiplier; // Calculated, error (per channel)
double * _pivot; // Moving average mid point (per channel)
double * _current; // Last current reading (per channel)
#if EMON_REPORT_ENERGY
uint32_t * _energy; // Aggregated energy (per channel)
#endif
};
#endif // SENSOR_SUPPORT

211
espurna/sensors/EventSensor.h Executable file
View File

@ -0,0 +1,211 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EVENTS_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class EventSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EventSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_EVENTS_ID;
}
~EventSensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setInterruptMode(unsigned char mode) {
_interrupt_mode = mode;
}
void setDebounceTime(unsigned long debounce) {
_debounce = debounce;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
unsigned char getInterruptMode() {
return _interrupt_mode;
}
unsigned long getDebounceTime() {
return _debounce;
}
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "INTERRUPT @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_EVENTS;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) {
double value = _events;
_events = 0;
return value;
};
return 0;
}
// Handle interrupt calls
void handleInterrupt(unsigned char gpio) {
(void) gpio;
static unsigned long last = 0;
if (millis() - last > _debounce) {
_events = _events + 1;
last = millis();
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(EventSensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_gpio = GPIO_NONE;
if (value) {
if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio);
_attach(this, _gpio, _interrupt_mode);
_interrupt_gpio = _gpio;
} else if (_interrupt_gpio != GPIO_NONE) {
_detach(_interrupt_gpio);
_interrupt_gpio = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
volatile unsigned long _events = 0;
unsigned long _debounce = EVENTS_DEBOUNCE;
unsigned char _gpio;
unsigned char _mode;
unsigned char _interrupt_mode;
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
EventSensor * _event_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _event_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_event_sensor_instance[index]) {
_event_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _event_sensor_isr_0() { _event_sensor_isr(0); }
void ICACHE_RAM_ATTR _event_sensor_isr_1() { _event_sensor_isr(1); }
void ICACHE_RAM_ATTR _event_sensor_isr_2() { _event_sensor_isr(2); }
void ICACHE_RAM_ATTR _event_sensor_isr_3() { _event_sensor_isr(3); }
void ICACHE_RAM_ATTR _event_sensor_isr_4() { _event_sensor_isr(4); }
void ICACHE_RAM_ATTR _event_sensor_isr_5() { _event_sensor_isr(5); }
void ICACHE_RAM_ATTR _event_sensor_isr_12() { _event_sensor_isr(12); }
void ICACHE_RAM_ATTR _event_sensor_isr_13() { _event_sensor_isr(13); }
void ICACHE_RAM_ATTR _event_sensor_isr_14() { _event_sensor_isr(14); }
void ICACHE_RAM_ATTR _event_sensor_isr_15() { _event_sensor_isr(15); }
static void (*_event_sensor_isr_list[10])() = {
_event_sensor_isr_0, _event_sensor_isr_1, _event_sensor_isr_2,
_event_sensor_isr_3, _event_sensor_isr_4, _event_sensor_isr_5,
_event_sensor_isr_12, _event_sensor_isr_13, _event_sensor_isr_14,
_event_sensor_isr_15
};
void EventSensor::_attach(EventSensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_event_sensor_instance[index] = instance;
attachInterrupt(gpio, _event_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void EventSensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_event_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _event_sensor_instance[index]->description().c_str());
#endif
_event_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && EVENTS_SUPPORT

170
espurna/sensors/GUVAS12SDSensor.h Executable file
View File

@ -0,0 +1,170 @@
// -----------------------------------------------------------------------------
// GUVA-S12SD UV Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && GUVAS12SD_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
// http://www.eoc-inc.com/genicom/GUVA-S12SD.pdf
//
// GUVA-S12D has a wide spectral range of 200nm-400nm
// The output voltage and the UV index is linear, illumination intensity = 307 * Vsig where: Vsig is the value of voltage measured from the SIG pin of the interface, unit V.
// illumination intensity unit: mW/m2 for the combination strength of UV light with wavelength range: 200nm-400nm
// UV Index = illumination intensity / 200
//
// UV Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10+
// -----------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+--------
// mV | <50 | 227 | 318 | 408 | 503 | 606 | 696 | 795 | 881 | 976 | 1079 | 1170+
// analog val | <10 | 46 | 65 | 83 | 103 | 124 | 142 | 162 | 180 | 200 | 221 | 240+
//
#define UV_SAMPLE_RATE 1
class GUVAS12SDSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
GUVAS12SDSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_GUVAS12SD_ID;
}
~GUVAS12SDSensor() {
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
_previous = _gpio;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Descriptive name of the sensor
String description() {
char buffer[18];
snprintf(buffer, sizeof(buffer), "GUVAS12SD @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_UV;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _uvindex;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
int _average = 0;
#if UV_SAMPLE_RATE == 1
_average = analogRead(0);
#else
for (unsigned int i=0; i < UV_SAMPLE_RATE; i++) {
_average += analogRead(0);
nice_delay(2);
}
_average = (_average / UV_SAMPLE_RATE);
#endif
// _sensormV = _average / 1023*3.3;
if (_average < 10) {
_uvindex = 0;
} else if (_average < 46) {
_uvindex = (_average - 10) / (46-10);
} else if (_average < 65) {
_uvindex = 1 + ((_average - 46) / (65-46));
} else if (_average < 83) {
_uvindex = 2 + ((_average - 65) / (83-65));
} else if (_average < 103) {
_uvindex = 3 + ((_average - 83) / (103- 83));
} else if (_average < 124) {
_uvindex = 4 + ((_average - 103) / (124-103));
} else if (_average < 142) {
_uvindex = 5 + ((_average - 124) / (142-124));
} else if (_average < 162) {
_uvindex = 6 + ((_average - 142) / (162-142));
} else if (_average < 180) {
_uvindex = 7 + ((_average - 162) / (180-162));
} else if (_average < 200) {
_uvindex = 8 + ((_average - 180) / (200-180));
} else if (_average < 221) {
_uvindex = 9 + ((_average - 200) / (221-200));
} else {
_uvindex = 10;
}
return _uvindex;
}
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
double _uvindex = 0;
};
#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT

328
espurna/sensors/HLW8012Sensor.h Executable file
View File

@ -0,0 +1,328 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && HLW8012_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <ESP8266WiFi.h>
#include <HLW8012.h>
class HLW8012Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
HLW8012Sensor(): BaseSensor() {
_count = 7;
_sensor_id = SENSOR_HLW8012_ID;
_hlw8012 = new HLW8012();
}
~HLW8012Sensor() {
_enableInterrupts(false);
delete _hlw8012;
}
void expectedCurrent(double expected) {
_hlw8012->expectedCurrent(expected);
}
void expectedVoltage(unsigned int expected) {
_hlw8012->expectedVoltage(expected);
}
void expectedPower(unsigned int expected) {
_hlw8012->expectedActivePower(expected);
}
void resetRatios() {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
_hlw8012->resetEnergy();
}
// ---------------------------------------------------------------------
void setSEL(unsigned char sel) {
if (_sel == sel) return;
_sel = sel;
_dirty = true;
}
void setCF(unsigned char cf) {
if (_cf == cf) return;
_cf = cf;
_dirty = true;
}
void setCF1(unsigned char cf1) {
if (_cf1 == cf1) return;
_cf1 = cf1;
_dirty = true;
}
void setSELCurrent(bool value) {
_sel_current = value;
}
void setCurrentRatio(double value) {
_hlw8012->setCurrentMultiplier(value);
};
void setVoltageRatio(double value) {
_hlw8012->setVoltageMultiplier(value);
};
void setPowerRatio(double value) {
_hlw8012->setPowerMultiplier(value);
};
// ---------------------------------------------------------------------
unsigned char getSEL() {
return _sel;
}
unsigned char getCF() {
return _cf;
}
unsigned char getCF1() {
return _cf1;
}
unsigned char getSELCurrent() {
return _sel_current;
}
double getCurrentRatio() {
return _hlw8012->getCurrentMultiplier();
};
double getVoltageRatio() {
return _hlw8012->getVoltageMultiplier();
};
double getPowerRatio() {
return _hlw8012->getPowerMultiplier();
};
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
// Initialize HLW8012
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
// * currentWhen is the value in sel_pin to select current sampling
// * set use_interrupts to true to use interrupts to monitor pulse widths
// * leave pulse_timeout to the default value, recommended when using interrupts
#if HLW8012_USE_INTERRUPTS
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, true);
#else
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, false, 1000000);
#endif
// These values are used to calculate current, voltage and power factors as per datasheet formula
// These are the nominal values for the Sonoff POW resistors:
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
_hlw8012->setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
// Handle interrupts
#if HLW8012_USE_INTERRUPTS
_enableInterrupts(true);
#else
_onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) {
_enableInterrupts(true);
});
_ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) {
_enableInterrupts(false);
});
#endif
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%u,%u,%u)", _sel, _cf, _cf1);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
snprintf(buffer, sizeof(buffer), "%u:%u:%u", _sel, _cf, _cf1);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _hlw8012->getCurrent();
if (index == 1) return _hlw8012->getVoltage();
if (index == 2) return _hlw8012->getActivePower();
if (index == 3) return _hlw8012->getReactivePower();
if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return _hlw8012->getEnergy();
return 0;
}
// Toggle between current and voltage monitoring
#if HLW8012_USE_INTERRUPTS == 0
// Post-read hook (usually to reset things)
void post() { _hlw8012->toggleMode(); }
#endif // HLW8012_USE_INTERRUPTS == 0
// Handle interrupt calls
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
if (gpio == _cf) _hlw8012->cf_interrupt();
if (gpio == _cf1) _hlw8012->cf1_interrupt();
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_cf = GPIO_NONE;
static unsigned char _interrupt_cf1 = GPIO_NONE;
if (value) {
if (_interrupt_cf != _cf) {
if (_interrupt_cf != GPIO_NONE) _detach(_interrupt_cf);
_attach(this, _cf, CHANGE);
_interrupt_cf = _cf;
}
if (_interrupt_cf1 != _cf1) {
if (_interrupt_cf1 != GPIO_NONE) _detach(_interrupt_cf1);
_attach(this, _cf1, CHANGE);
_interrupt_cf1 = _cf1;
}
} else {
_detach(_cf);
_detach(_cf1);
_interrupt_cf = GPIO_NONE;
_interrupt_cf1 = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
unsigned char _sel = GPIO_NONE;
unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true;
HLW8012 * _hlw8012 = NULL;
#if HLW8012_USE_INTERRUPTS == 0
WiFiEventHandler _onconnect_handler;
WiFiEventHandler _ondisconnect_handler;
#endif
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
HLW8012Sensor * _hlw8012_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _hlw8012_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_hlw8012_sensor_instance[index]) {
_hlw8012_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_0() { _hlw8012_sensor_isr(0); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_1() { _hlw8012_sensor_isr(1); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_2() { _hlw8012_sensor_isr(2); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_3() { _hlw8012_sensor_isr(3); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_4() { _hlw8012_sensor_isr(4); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_5() { _hlw8012_sensor_isr(5); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_12() { _hlw8012_sensor_isr(12); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_13() { _hlw8012_sensor_isr(13); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_14() { _hlw8012_sensor_isr(14); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_15() { _hlw8012_sensor_isr(15); }
static void (*_hlw8012_sensor_isr_list[10])() = {
_hlw8012_sensor_isr_0, _hlw8012_sensor_isr_1, _hlw8012_sensor_isr_2,
_hlw8012_sensor_isr_3, _hlw8012_sensor_isr_4, _hlw8012_sensor_isr_5,
_hlw8012_sensor_isr_12, _hlw8012_sensor_isr_13, _hlw8012_sensor_isr_14,
_hlw8012_sensor_isr_15
};
void HLW8012Sensor::_attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_hlw8012_sensor_instance[index] = instance;
attachInterrupt(gpio, _hlw8012_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void HLW8012Sensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_hlw8012_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str());
#endif
_hlw8012_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && HLW8012_SUPPORT

95
espurna/sensors/I2CSensor.h Executable file
View File

@ -0,0 +1,95 @@
// -----------------------------------------------------------------------------
// Abstract I2C sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT )
#if I2C_USE_BRZO
#define I2C_TRANS_SUCCESS 0 // All i2c commands were executed without errors
#define I2C_TRANS_ERROR_BUS_NOT_FREE 1 // Bus not free, i.e. either SDA or SCL is low
#define I2C_TRANS_ERROR_NACK_WRITE 2 // Not ACK ("NACK") by slave during write:
// Either the slave did not respond to the given slave address; or the slave did not ACK a byte transferred by the master.
#define I2C_TRANS_ERROR_NACK_READ 4 // Not ACK ("NACK") by slave during read,
// i.e. slave did not respond to the given slave address
#define I2C_TRANS_ERROR_CLOCK 8 // Clock Stretching by slave exceeded maximum clock stretching time. Most probably, there is a bus stall now!
#define I2C_TRANS_ERROR_READ_NULL 16 // Read was called with 0 bytes to be read by the master. Command not sent to the slave, since this could yield to a bus stall
#define I2C_TRANS_ERROR_TIMEOUT 32 // ACK Polling timeout exceeded
#else // Wire
#define I2C_TRANS_SUCCESS 0 // success
#define I2C_TRANS_ERROR_BUFFER_OVERLOW 1 // data too long to fit in transmit buffer
#define I2C_TRANS_ERROR_NACK_ADDRESS 2 // received NACK on transmit of address
#define I2C_TRANS_ERROR_NACK_DATA 3 // received NACK on transmit of data
#define I2C_TRANS_ERROR_OTHER 4 // other error
#endif
#pragma once
#include "BaseSensor.h"
class I2CSensor : public BaseSensor {
public:
void setAddress(unsigned char address) {
if (_address == address) return;
_address = address;
_dirty = true;
}
unsigned char getAddress() {
return _address;
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[5];
snprintf(buffer, sizeof(buffer), "0x%02X", _address);
return String(buffer);
}
protected:
// Specific for I2C sensors
unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) {
// If we have already locked this address for this sensor quit
if ((address > 0) && (address == _previous_address)) {
return _previous_address;
}
// Check if we should release a previously locked address
if ((_previous_address > 0) && (_previous_address != address)) {
i2cReleaseLock(_previous_address);
_previous_address = 0;
}
// If requesting a specific address, try to ger a lock to it
if ((0 < address) && i2cGetLock(address)) {
_previous_address = address;
return _previous_address;
}
// If everything else fails, perform an auto-discover
_previous_address = i2cFindAndLock(size, addresses);
// Flag error
if (0 == _previous_address) {
_error = SENSOR_ERROR_I2C;
}
return _previous_address;
}
unsigned char _previous_address = 0;
unsigned char _address = 0;
};
#endif // SENSOR_SUPPORT && I2C_SUPPORT

221
espurna/sensors/MHZ19Sensor.h Executable file
View File

@ -0,0 +1,221 @@
// -----------------------------------------------------------------------------
// MHZ19 CO2 sensor
// Based on: https://github.com/nara256/mhz19_uart
// http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
// Uses SoftwareSerial library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MHZ19_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
#define MHZ19_REQUEST_LEN 8
#define MHZ19_RESPONSE_LEN 9
#define MHZ19_TIMEOUT 1000
#define MHZ19_GETPPM 0x8600
#define MHZ19_ZEROCALIB 0x8700
#define MHZ19_SPANCALIB 0x8800
#define MHZ19_AUTOCALIB_ON 0x79A0
#define MHZ19_AUTOCALIB_OFF 0x7900
class MHZ19Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
MHZ19Sensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_MHZ19_ID;
}
~MHZ19Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "MHZ19 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CO2;
return MAGNITUDE_NONE;
}
void pre() {
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _co2;
return 0;
}
void calibrateAuto(boolean state){
_write(state ? MHZ19_AUTOCALIB_ON : MHZ19_AUTOCALIB_OFF);
}
void calibrateZero() {
_write(MHZ19_ZEROCALIB);
}
void calibrateSpan(unsigned int ppm) {
if( ppm < 1000 ) return;
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
buffer[0] = 0xFF;
buffer[1] = 0x01;
buffer[2] = MHZ19_SPANCALIB >> 8;
buffer[3] = ppm >> 8;
buffer[4] = ppm & 0xFF;
_write(buffer);
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _write(unsigned char * command) {
_serial->write(command, MHZ19_REQUEST_LEN);
_serial->write(_checksum(command));
_serial->flush();
}
void _write(unsigned int command, unsigned char * response) {
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
buffer[0] = 0xFF;
buffer[1] = 0x01;
buffer[2] = command >> 8;
buffer[3] = command & 0xFF;
_write(buffer);
if (response != NULL) {
unsigned long start = millis();
while (_serial->available() == 0) {
if (millis() - start > MHZ19_TIMEOUT) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
yield();
}
_serial->readBytes(response, MHZ19_RESPONSE_LEN);
}
}
void _write(unsigned int command) {
_write(command, NULL);
}
void _read() {
unsigned char buffer[MHZ19_RESPONSE_LEN] = {0};
_write(MHZ19_GETPPM, buffer);
// Check response
if ((buffer[0] == 0xFF)
&& (buffer[1] == 0x86)
&& (_checksum(buffer) == buffer[MHZ19_RESPONSE_LEN-1])) {
unsigned int value = buffer[2] * 256 + buffer[3];
if (0 <= value && value <= 5000) {
_co2 = value;
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_OUT_OF_RANGE;
}
} else {
_error = SENSOR_ERROR_CRC;
}
}
uint8_t _checksum(uint8_t * command) {
uint8_t sum = 0x00;
for (unsigned char i = 1; i < MHZ19_REQUEST_LEN-1; i++) {
sum += command[i];
}
sum = 0xFF - sum + 0x01;
return sum;
}
double _co2 = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
SoftwareSerial * _serial = NULL;
};
#endif // SENSOR_SUPPORT && MHZ19_SUPPORT

152
espurna/sensors/PMSX003Sensor.h Executable file
View File

@ -0,0 +1,152 @@
// -----------------------------------------------------------------------------
// PMSX003 Dust Sensor
// Uses SoftwareSerial library
// Contribution by Òscar Rovira López
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PMSX003_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <PMS.h>
#include <SoftwareSerial.h>
class PMSX003Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PMSX003Sensor(): BaseSensor() {
_count = 3;
_sensor_id = SENSOR_PMSX003_ID;
}
~PMSX003Sensor() {
if (_serial) delete _serial;
if (_pms) delete _pms;
}
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
if (_pms) delete _pms;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
_pms = new PMS(* _serial);
_pms->passiveMode();
_startTime = millis();
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[36] = {0};
if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_PM1dot0;
if (index == 1) return MAGNITUDE_PM2dot5;
if (index == 2) return MAGNITUDE_PM10;
return MAGNITUDE_NONE;
}
void pre() {
if (millis() - _startTime < 30000) {
_error = SENSOR_ERROR_WARM_UP;
return;
}
_error = SENSOR_ERROR_OK;
if(_pms->read(_data)) {
_pm1dot0 = _data.PM_AE_UG_1_0;
_pm2dot5 = _data.PM_AE_UG_2_5;
_pm10 = _data.PM_AE_UG_10_0;
}
_pms->requestRead();
}
// Current value for slot # index
double value(unsigned char index) {
if(index == 0) return _pm1dot0;
if(index == 1) return _pm2dot5;
if(index == 2) return _pm10;
return 0;
}
protected:
unsigned int _pm1dot0;
unsigned int _pm2dot5;
unsigned int _pm10;
unsigned int _pin_rx;
unsigned int _pin_tx;
unsigned long _startTime;
SoftwareSerial * _serial = NULL;
PMS * _pms = NULL;
PMS::DATA _data;
};
#endif // SENSOR_SUPPORT && PMSX003_SUPPORT

136
espurna/sensors/PZEM004TSensor.h Executable file
View File

@ -0,0 +1,136 @@
// -----------------------------------------------------------------------------
// PZEM004T based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <PZEM004T.h>
class PZEM004TSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PZEM004TSensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_PZEM004T_ID;
}
~PZEM004TSensor() {
if (_pzem) delete _pzem;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
void setSerial(Stream & serial) {
_serial = serial;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
Stream & getSerial() {
return _serial;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_pzem) delete _pzem;
if (_serial == NULL) {
_pzem = PZEM004T(_pin_rx, _pin_tx);
} else {
_pzem = PZEM004T(_serial);
}
_pzem->setAddress(_ip);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return _ip.toString();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _pzem->current(_ip);
if (index == 1) return _pzem->voltage(_ip);
if (index == 2) return _pzem->power(_ip);
if (index == 3) return _pzem->energy(_ip);
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
Stream & _serial = NULL;
IPAddress _ip(192,168,1,1);
PZEM004T * _pzem = NULL;
};
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT

View File

@ -0,0 +1,89 @@
// -----------------------------------------------------------------------------
// SHT3X Sensor over I2C (Wemos)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
class SHT3XI2CSensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SHT3XI2CSensor(): I2CSensor() {
_sensor_id = SENSOR_SHT3X_I2C_ID;
_count = 2;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x45};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "SHT3X @ I2C (0x%02X)", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
unsigned char buffer[6];
i2c_write_uint8(_address, 0x2C, 0x06);
nice_delay(500);
i2c_read_buffer(_address, buffer, 6);
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
_temperature = ((((buffer[0] * 256.0) + buffer[1]) * 175) / 65535.0) - 45;
_humidity = ((((buffer[3] * 256.0) + buffer[4]) * 100) / 65535.0);
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
double _temperature = 0;
unsigned char _humidity = 0;
};
#endif // SENSOR_SUPPORT && SHT3X_I2C_SUPPORT

168
espurna/sensors/SI7021Sensor.h Executable file
View File

@ -0,0 +1,168 @@
// -----------------------------------------------------------------------------
// SI7021 / HTU21D Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SI7021_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define SI7021_SCL_FREQUENCY 200
#define SI7021_CHIP_SI7021 0x15
#define SI7021_CHIP_HTU21D 0x32
#define SI7021_CMD_TMP_HOLD 0xE3
#define SI7021_CMD_HUM_HOLD 0xE5
#define SI7021_CMD_TMP_NOHOLD 0xF3
#define SI7021_CMD_HUM_NOHOLD 0xF5
PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
class SI7021Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SI7021Sensor(): I2CSensor() {
_sensor_id = SENSOR_SI7021_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char name[10];
strncpy_P(name,
_chip == SI7021_CHIP_SI7021 ?
si7021_chip_si7021_name :
si7021_chip_htu21d_name,
sizeof(name)
);
char buffer[25];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_UNKNOWN_ID;
if (_chip == 0) return;
_error = SENSOR_ERROR_OK;
double value;
value = _read(SI7021_CMD_TMP_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
_temperature = (175.72 * value / 65536) - 46.85;
value = _read(SI7021_CMD_HUM_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
value = (125.0 * value / 65536) - 6;
_humidity = constrain(value, 0, 100);
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _init() {
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
_count = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
} else {
_count = 2;
}
_ready = true;
}
unsigned int _read(uint8_t command) {
// Request measurement
i2c_write_uint8(_address, command);
// When not using clock stretching (*_NOHOLD commands) delay here
// is needed to wait for the measurement.
// According to datasheet the max. conversion time is ~22ms
unsigned long start = millis();
nice_delay(50);
// Clear the last to bits of LSB to 00.
// According to datasheet LSB of RH is always xxxxxx10
unsigned int value = i2c_read_uint16(_address) & 0xFFFC;
// We should be checking there are no pending bytes in the buffer
// and raise a CRC error if there are
_error = SENSOR_ERROR_OK;
return value;
}
unsigned char _chip;
double _temperature = 0;
double _humidity = 0;
};
#endif // SENSOR_SUPPORT && SI7021_SUPPORT

259
espurna/sensors/V9261FSensor.h Executable file
View File

@ -0,0 +1,259 @@
// -----------------------------------------------------------------------------
// V9261F based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && V9261F_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class V9261FSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
V9261FSensor(): BaseSensor(), _data() {
_count = 6;
_sensor_id = SENSOR_V9261F_ID;
}
~V9261FSensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(V9261F_BAUDRATE);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _reactive;
if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
static unsigned char state = 0;
static unsigned long last = 0;
static bool found = false;
static unsigned char index = 0;
if (state == 0) {
while (_serial->available()) {
_serial->flush();
found = true;
last = millis();
}
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
_serial->flush();
index = 0;
state = 1;
}
} else if (state == 1) {
while (_serial->available()) {
_serial->read();
if (index++ >= 7) {
_serial->flush();
index = 0;
state = 2;
}
}
} else if (state == 2) {
while (_serial->available()) {
_data[index] = _serial->read();
if (index++ >= 19) {
_serial->flush();
last = millis();
state = 3;
}
}
} else if (state == 3) {
if (_checksum()) {
_active = (double) (
(_data[3]) +
(_data[4] << 8) +
(_data[5] << 16) +
(_data[6] << 24)
) / _ratioP;
_reactive = (double) (
(_data[7]) +
(_data[8] << 8) +
(_data[9] << 16) +
(_data[10] << 24)
) / _ratioR;
_voltage = (double) (
(_data[11]) +
(_data[12] << 8) +
(_data[13] << 16) +
(_data[14] << 24)
) / _ratioV;
_current = (double) (
(_data[15]) +
(_data[16] << 8) +
(_data[17] << 16) +
(_data[18] << 24)
) / _ratioC;
if (_active < 0) _active = 0;
if (_reactive < 0) _reactive = 0;
if (_voltage < 0) _voltage = 0;
if (_current < 0) _current = 0;
_apparent = sqrt(_reactive * _reactive + _active * _active);
}
last = millis();
index = 0;
state = 4;
} else if (state == 4) {
while (_serial->available()) {
_serial->flush();
last = millis();
}
if (millis() - last > V9261F_SYNC_INTERVAL) {
state = 1;
}
}
}
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 0; i < 19; i++) {
checksum = checksum + _data[i];
}
checksum = ~checksum + 0x33;
return checksum == _data[19];
}
// ---------------------------------------------------------------------
unsigned int _pin_rx = V9261F_PIN;
bool _inverted = V9261F_PIN_INVERSE;
SoftwareSerial * _serial = NULL;
double _active = 0;
double _reactive = 0;
double _voltage = 0;
double _current = 0;
double _apparent = 0;
double _ratioP = V9261F_POWER_FACTOR;
double _ratioC = V9261F_CURRENT_FACTOR;
double _ratioV = V9261F_VOLTAGE_FACTOR;
double _ratioR = V9261F_RPOWER_FACTOR;
unsigned char _data[24];
};
#endif // SENSOR_SUPPORT && V9261F_SUPPORT

479
espurna/settings.ino Executable file
View File

@ -0,0 +1,479 @@
/*
SETTINGS MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <vector>
#include "libs/EmbedisWrap.h"
#include <Stream.h>
#ifdef DEBUG_PORT
#define EMBEDIS_PORT DEBUG_PORT
#else
#define EMBEDIS_PORT Serial
#endif
#if TELNET_SUPPORT
#include "libs/StreamInjector.h"
StreamInjector _serial = StreamInjector(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE);
#undef EMBEDIS_PORT
#define EMBEDIS_PORT _serial
#endif
EmbedisWrap embedis(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE);
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
static unsigned char _serial_rx_pointer = 0;
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
bool _settings_save = false;
// -----------------------------------------------------------------------------
// Reverse engineering EEPROM storage format
// -----------------------------------------------------------------------------
unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
}
return SPI_FLASH_SEC_SIZE - pos;
}
// -----------------------------------------------------------------------------
unsigned int _settingsKeyCount() {
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
len = EEPROM.read(pos);
pos = pos - len - 2;
count ++;
}
return count;
}
String _settingsKeyName(unsigned int index) {
String s;
unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
pos = pos - len - 2;
if (count == index) {
s.reserve(len);
for (unsigned char i = 0 ; i < len; i++) {
s += (char) EEPROM.read(pos + i + 1);
}
break;
}
count++;
len = EEPROM.read(pos);
pos = pos - len - 2;
}
return s;
}
std::vector<String> _settingsKeys() {
// Get sorted list of keys
std::vector<String> keys;
//unsigned int size = settingsKeyCount();
unsigned int size = _settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
//String key = settingsKeyName(i);
String key = _settingsKeyName(i);
bool inserted = false;
for (unsigned char j=0; j<keys.size(); j++) {
// Check if we have to insert it before the current element
if (keys[j].compareTo(key) > 0) {
keys.insert(keys.begin() + j, key);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) keys.push_back(key);
}
return keys;
}
// -----------------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------------
void _settingsHelpCommand() {
// Get sorted list of commands
std::vector<String> commands;
unsigned char size = embedis.getCommandCount();
for (unsigned int i=0; i<size; i++) {
String command = embedis.getCommandName(i);
bool inserted = false;
for (unsigned char j=0; j<commands.size(); j++) {
// Check if we have to insert it before the current element
if (commands[j].compareTo(command) > 0) {
commands.insert(commands.begin() + j, command);
inserted = true;
break;
}
}
// If we could not insert it, just push it at the end
if (!inserted) commands.push_back(command);
}
// Output the list
DEBUG_MSG_P(PSTR("Available commands:\n"));
for (unsigned char i=0; i<commands.size(); i++) {
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
}
}
void _settingsKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => %s\n"), (keys[i]).c_str(), value.c_str());
}
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
}
void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
EEPROM.commit();
}
void _settingsDumpCommand(bool ascii) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
}
}
void _settingsInitCommands() {
#if DEBUG_SUPPORT
settingsRegisterCommand(F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
debugClearCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
_settingsDumpCommand(ascii);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if I2C_SUPPORT
settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) {
i2cScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) {
i2cClearBus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
_settingsFactoryResetCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HEAP"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HELP"), [](Embedis* e) {
_settingsHelpCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info();
wifiStatus();
//StreamString s;
//WiFi.printDiag(s);
//DEBUG_MSG(s.c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("KEYS"), [](Embedis* e) {
_settingsKeysCommand();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROM.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
settingsRegisterCommand(F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
// -----------------------------------------------------------------------------
// Key-value API
// -----------------------------------------------------------------------------
void moveSetting(const char * from, const char * to) {
String value = getSetting(from);
if (value.length() > 0) setSetting(to, value);
delSetting(from);
}
template<typename T> String getSetting(const String& key, T defaultValue) {
String value;
if (!Embedis::get(key, value)) value = String(defaultValue);
return value;
}
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue) {
return getSetting(key + String(index), defaultValue);
}
String getSetting(const String& key) {
return getSetting(key, "");
}
template<typename T> bool setSetting(const String& key, T value) {
return Embedis::set(key, String(value));
}
template<typename T> bool setSetting(const String& key, unsigned int index, T value) {
return setSetting(key + String(index), value);
}
bool delSetting(const String& key) {
return Embedis::del(key);
}
bool delSetting(const String& key, unsigned int index) {
return delSetting(key + String(index));
}
bool hasSetting(const String& key) {
return getSetting(key).length() != 0;
}
bool hasSetting(const String& key, unsigned int index) {
return getSetting(key, index, "").length() != 0;
}
void saveSettings() {
#if not SETTINGS_AUTOSAVE
_settings_save = true;
#endif
}
void resetSettings() {
_settingsFactoryResetCommand();
}
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
#if TELNET_SUPPORT
void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
#endif
size_t settingsMaxSize() {
size_t size = EEPROM_SIZE;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
size = (size + 3) & (~3);
return size;
}
bool settingsRestoreJson(JsonObject& data) {
const char* app = data["app"];
if (strcmp(app, APP_NAME) != 0) return false;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
saveSettings();
DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
return true;
}
bool settingsGetJson(JsonObject& root) {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
// Add the key-values to the json object
for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]);
root[keys[i]] = value;
}
}
void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
Embedis::command(name, call);
};
// -----------------------------------------------------------------------------
// Initialization
// -----------------------------------------------------------------------------
void settingsSetup() {
EEPROM.begin(SPI_FLASH_SEC_SIZE);
#if TELNET_SUPPORT
_serial.callback([](uint8_t ch) {
telnetWrite(ch);
});
#endif
Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROM.read(pos); },
[](size_t pos, char value) { EEPROM.write(pos, value); },
#if SETTINGS_AUTOSAVE
[]() { _settings_save = true; }
#else
[]() {}
#endif
);
_settingsInitCommands();
#if TERMINAL_SUPPORT
#if SERIAL_RX_ENABLED
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
// Register loop
espurnaRegisterLoop(settingsLoop);
}
void settingsLoop() {
if (_settings_save) {
EEPROM.commit();
_settings_save = false;
}
#if TERMINAL_SUPPORT
embedis.process();
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
settingsInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
_serial_rx_pointer = 0;
}
}
#endif // SERIAL_RX_ENABLED
#endif // TERMINAL_SUPPORT
}

83
espurna/ssdp.ino Executable file
View File

@ -0,0 +1,83 @@
/*
SSDP MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Uses SSDP library by PawelDino (https://github.com/PawelDino)
https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
*/
#if SSDP_SUPPORT
#include <ESP8266SSDP.h>
const char _ssdp_template[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%s:%u/</URLBase>"
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>/</presentationURL>"
"<serialNumber>%u</serialNumber>"
"<modelName>%s</modelName>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:38323636-4558-4dda-9188-cda0e6%06x</UDN>"
"</device>"
"</root>\r\n"
"\r\n";
void ssdpSetup() {
webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
DEBUG_MSG_P(PSTR("[SSDP] Schema request\n"));
IPAddress ip = WiFi.localIP();
uint32_t chipId = ESP.getChipId();
char response[strlen_P(_ssdp_template) + 100];
snprintf_P(response, sizeof(response), _ssdp_template,
WiFi.localIP().toString().c_str(), // ip
webPort(), // port
SSDP_DEVICE_TYPE, // device type
getSetting("hostname").c_str(), // friendlyName
chipId, // serialNumber
APP_NAME, // modelName
APP_VERSION, // modelNumber
APP_WEBSITE, // modelURL
DEVICE_NAME, // manufacturer
"", // manufacturerURL
chipId // UUID
);
request->send(200, "text/xml", response);
});
SSDP.setSchemaURL("description.xml");
SSDP.setHTTPPort(webPort());
SSDP.setDeviceType(SSDP_DEVICE_TYPE); //https://github.com/esp8266/Arduino/issues/2283
SSDP.setName(getSetting("hostname"));
SSDP.setSerialNumber(String(ESP.getChipId()));
SSDP.setModelName(APP_NAME);
SSDP.setModelNumber(APP_VERSION);
SSDP.setModelURL(APP_WEBSITE);
SSDP.setManufacturer(DEVICE_NAME);
SSDP.setManufacturerURL("");
SSDP.setURL("/");
SSDP.begin();
DEBUG_MSG_P(PSTR("[SSDP] Started\n"));
}
#endif // SSDP_SUPPORT

3262
espurna/static/index.html.gz.h Executable file

File diff suppressed because it is too large Load Diff

33
espurna/static/server.cer.h Executable file
View File

@ -0,0 +1,33 @@
#define server_cer_len 587
const uint8_t server_cer[] PROGMEM = {
0x30,0x82,0x02,0x47,0x30,0x82,0x01,0x2f,0x02,0x09,0x00,0x93,0x02,0xa1,0xb1,0x98,0xba,0x76,0x43,0x30,
0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x05,0x05,0x00,0x30,0x1c,0x31,0x1a,0x30,0x18,
0x06,0x03,0x55,0x04,0x0a,0x0c,0x11,0x45,0x73,0x70,0x72,0x65,0x73,0x73,0x69,0x66,0x20,0x53,0x79,0x73,
0x74,0x65,0x6d,0x73,0x30,0x1e,0x17,0x0d,0x31,0x36,0x30,0x38,0x32,0x34,0x32,0x31,0x34,0x37,0x33,0x38,
0x5a,0x17,0x0d,0x33,0x30,0x30,0x35,0x30,0x33,0x32,0x31,0x34,0x37,0x33,0x38,0x5a,0x30,0x33,0x31,0x19,
0x30,0x17,0x06,0x03,0x55,0x04,0x0a,0x0c,0x10,0x61,0x78,0x54,0x4c,0x53,0x20,0x6f,0x6e,0x20,0x45,0x53,
0x50,0x38,0x32,0x36,0x36,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x03,0x0c,0x0d,0x65,0x73,0x70,0x38,
0x32,0x36,0x36,0x2e,0x6c,0x6f,0x63,0x61,0x6c,0x30,0x81,0x9f,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,
0xf7,0x0d,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8d,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xb4,0x8e,
0x39,0xec,0xc3,0x1e,0x89,0xa6,0xce,0xa6,0x40,0xc8,0x5e,0x67,0x77,0xf6,0x6e,0xbf,0x69,0x4f,0xfc,0x76,
0xab,0xbb,0x76,0xc0,0x9e,0x8b,0x05,0x6f,0x38,0x30,0xe0,0xca,0xd9,0x13,0x14,0xf0,0xe4,0x53,0xd2,0xa5,
0x33,0x53,0xb9,0x69,0xc9,0x71,0x55,0x53,0xb9,0xb4,0x3f,0x09,0x2f,0x8c,0x07,0xdf,0xd3,0x18,0xfd,0xbe,
0x5e,0x4d,0xcf,0xc8,0x42,0x35,0x8f,0x2b,0x89,0x3e,0xec,0x1f,0xb7,0x81,0x09,0xba,0x79,0x60,0x6a,0xb0,
0xc4,0x65,0xdb,0x45,0x60,0xe8,0xfe,0xeb,0x65,0x04,0xeb,0x57,0x14,0x76,0x91,0x65,0x43,0x2f,0x55,0xdb,
0xd4,0x50,0x62,0xbc,0x79,0x1d,0x21,0x89,0xa2,0xc3,0xae,0xc7,0x89,0x7b,0x01,0xc2,0xc5,0x8f,0xf6,0xcc,
0xd7,0xe4,0x68,0x5a,0x37,0x21,0x02,0x03,0x01,0x00,0x01,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,
0x0d,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x02,0x7e,0x98,0x36,0x19,0x6a,0xea,0xdf,0xf2,
0xfa,0x3a,0x5f,0x7d,0x82,0x2b,0xb5,0x6c,0x01,0xd9,0x38,0x2b,0x7e,0xb6,0xc8,0x6a,0x9d,0x03,0x9e,0x13,
0x3d,0x3f,0xd4,0x84,0x3f,0x20,0x1b,0xd0,0x3f,0x4f,0xf5,0x33,0x19,0xfe,0x5f,0xfa,0x87,0x67,0x13,0x7e,
0x63,0xca,0xbb,0x18,0x73,0x14,0x4a,0x9b,0x8a,0x17,0x29,0x7e,0x70,0xcd,0x2e,0xd5,0xbc,0xd1,0xc6,0xd9,
0x02,0xa6,0xd0,0x7b,0x97,0x0a,0x04,0xc8,0x07,0x4f,0x8c,0x2e,0x91,0x12,0xfe,0x62,0xab,0xf9,0x45,0x2d,
0xc3,0x7b,0x77,0x84,0x41,0xfa,0x88,0x2d,0xc1,0xd9,0x8b,0xc2,0x9b,0x00,0x14,0xe8,0xa6,0xcc,0x2d,0xc1,
0x06,0xad,0x02,0x11,0x7b,0xcd,0xe0,0x94,0xe9,0x9c,0xa7,0x87,0xf7,0xac,0x60,0x24,0x4e,0xe2,0xe9,0xc8,
0x6e,0x6a,0x4c,0xf9,0xed,0x86,0x2c,0x71,0xd2,0xa3,0xec,0xd7,0xdc,0xdb,0x9b,0x61,0x5d,0x7a,0x88,0x03,
0x73,0xfb,0xf3,0x4f,0xfa,0x07,0xea,0xe4,0x62,0x98,0x90,0x89,0xed,0x7d,0x61,0x45,0xd9,0xd2,0xc8,0x5f,
0x01,0x6c,0xa5,0xf2,0xaa,0xdb,0xaf,0xa5,0xf3,0x04,0x45,0x6d,0xe4,0xdf,0xfa,0x46,0x6f,0xdc,0xf4,0xc6,
0xfe,0x7f,0x3a,0x9b,0x06,0xa9,0xbf,0x30,0xdf,0x04,0xb6,0xfe,0x0b,0x4a,0x14,0x61,0xc1,0x80,0x68,0x06,
0x5b,0x5a,0xe4,0x4f,0x01,0xb1,0x73,0xa1,0x55,0x9b,0xde,0x44,0x4b,0x4d,0xf7,0x0e,0xdd,0x8c,0x88,0x67,
0x71,0x68,0x6f,0x78,0x07,0x13,0x04,0x45,0xe3,0xc3,0x80,0x34,0x30,0x11,0x95,0x74,0x1f,0xfe,0x60,0xc0,
0x28,0x9b,0x9a,0x43,0xbd,0xc5,0xa8
};

34
espurna/static/server.key.h Executable file
View File

@ -0,0 +1,34 @@
#define server_key_len 611
const uint8_t server_key[] PROGMEM = {
0x30,0x82,0x02,0x5f,0x02,0x01,0x00,0x02,0x81,0x81,0x00,0xb4,0x8e,0x39,0xec,0xc3,0x1e,0x89,0xa6,0xce,
0xa6,0x40,0xc8,0x5e,0x67,0x77,0xf6,0x6e,0xbf,0x69,0x4f,0xfc,0x76,0xab,0xbb,0x76,0xc0,0x9e,0x8b,0x05,
0x6f,0x38,0x30,0xe0,0xca,0xd9,0x13,0x14,0xf0,0xe4,0x53,0xd2,0xa5,0x33,0x53,0xb9,0x69,0xc9,0x71,0x55,
0x53,0xb9,0xb4,0x3f,0x09,0x2f,0x8c,0x07,0xdf,0xd3,0x18,0xfd,0xbe,0x5e,0x4d,0xcf,0xc8,0x42,0x35,0x8f,
0x2b,0x89,0x3e,0xec,0x1f,0xb7,0x81,0x09,0xba,0x79,0x60,0x6a,0xb0,0xc4,0x65,0xdb,0x45,0x60,0xe8,0xfe,
0xeb,0x65,0x04,0xeb,0x57,0x14,0x76,0x91,0x65,0x43,0x2f,0x55,0xdb,0xd4,0x50,0x62,0xbc,0x79,0x1d,0x21,
0x89,0xa2,0xc3,0xae,0xc7,0x89,0x7b,0x01,0xc2,0xc5,0x8f,0xf6,0xcc,0xd7,0xe4,0x68,0x5a,0x37,0x21,0x02,
0x03,0x01,0x00,0x01,0x02,0x81,0x81,0x00,0x8d,0x0c,0x72,0x89,0xd8,0x1f,0xba,0x0e,0xfa,0x6e,0x7c,0x3b,
0x4b,0x2c,0x6f,0x55,0xaf,0x4c,0x8e,0xbb,0xb2,0x91,0x0e,0x35,0x63,0x5e,0xb4,0x3c,0x0c,0x61,0xc7,0x36,
0xbf,0xd5,0x17,0x61,0x45,0xc1,0xad,0xcd,0x21,0xc8,0x76,0x61,0x58,0x7e,0x20,0xa5,0x0d,0xb0,0x5b,0x69,
0x48,0xb9,0x27,0x50,0xb0,0x32,0x15,0x19,0xf7,0xd7,0xc8,0x98,0x96,0x55,0x51,0x85,0xd6,0x6a,0xe6,0xdb,
0x9e,0x44,0xa2,0x55,0x5f,0xeb,0x0a,0xc2,0x99,0xad,0x25,0x46,0xa9,0x38,0xb6,0x7e,0xaa,0xcc,0x5f,0x02,
0x73,0x93,0xc5,0x2d,0xe6,0x62,0x8c,0x12,0x20,0x28,0xf3,0xe3,0x4c,0xbc,0xc7,0x13,0x8c,0xac,0x78,0x70,
0x34,0xeb,0x63,0xea,0x13,0x02,0xb6,0x69,0xdc,0xef,0x0a,0x4d,0xcd,0xde,0x29,0x01,0x02,0x41,0x00,0xe4,
0x03,0xff,0xe9,0xe8,0xd6,0x75,0x3f,0x3c,0x88,0x86,0x12,0xc6,0xe5,0xa6,0x7c,0x41,0xb2,0x64,0xe5,0x0c,
0x6e,0x83,0xf2,0x2a,0x3a,0xfd,0x9a,0x47,0x93,0x69,0xe5,0x11,0xa0,0x37,0x03,0x74,0x9d,0x28,0x65,0x62,
0xb9,0xe2,0x3d,0x6d,0xb2,0x46,0xc3,0x02,0x93,0xcc,0x1a,0x3c,0x20,0xd5,0x93,0xc6,0xb3,0x1d,0x70,0x0e,
0xbc,0x78,0xf1,0x02,0x41,0x00,0xca,0xb7,0x15,0x7b,0x6a,0xf7,0x14,0xcb,0xd5,0x0d,0x31,0x26,0x90,0xcb,
0x38,0x47,0x6b,0x34,0x2b,0xe4,0x1f,0x0d,0x59,0x06,0x49,0x06,0x94,0x79,0x9f,0x65,0xe0,0x83,0x5e,0xd5,
0xe6,0xb8,0x89,0x75,0x93,0x14,0x8b,0x5a,0x57,0x26,0x02,0x04,0x06,0xc4,0xed,0x68,0x6f,0x05,0x66,0xeb,
0x07,0x94,0x20,0x03,0xd9,0xd2,0x75,0xb0,0x21,0x31,0x02,0x41,0x00,0xbb,0xc1,0x23,0x60,0xab,0xee,0xb3,
0xfb,0x0f,0x50,0x67,0xfe,0x5c,0x33,0x4c,0x44,0xf3,0x1f,0xff,0x7e,0xb0,0x1e,0xec,0x9d,0x62,0xf2,0xd2,
0x52,0xd0,0xef,0x6c,0xfa,0x47,0xea,0x42,0x82,0xa0,0xea,0xac,0x11,0xd8,0x1a,0xb2,0x55,0xdc,0xd7,0x38,
0xf7,0x69,0x4e,0xe4,0x79,0x11,0xdc,0x03,0xa2,0x3c,0xb2,0xce,0xe1,0xaf,0xf0,0xb8,0x31,0x02,0x41,0x00,
0xac,0xb8,0x92,0x75,0x36,0x0e,0x90,0x89,0x9c,0x5e,0x41,0x22,0xba,0xfc,0x6c,0x57,0x6d,0xe0,0x66,0x05,
0x58,0xef,0xe6,0x8a,0x94,0x6e,0x26,0xd3,0xfa,0x1c,0xb3,0xcf,0x3b,0x5e,0xc5,0xd7,0x36,0x48,0x17,0xa9,
0xc9,0x92,0x8f,0xee,0xb2,0x88,0xfb,0xbb,0x8f,0x0d,0x0c,0x8b,0x6d,0xc5,0x94,0x0f,0x81,0xb7,0xc6,0x40,
0xac,0x46,0x06,0x01,0x02,0x41,0x00,0xa3,0x7d,0x8a,0x62,0x0e,0x8d,0xe7,0x00,0xea,0x21,0x86,0x61,0xb9,
0x13,0xc3,0xc2,0xcf,0x96,0x88,0x79,0xff,0xaa,0x94,0x6f,0x5a,0xb8,0xe4,0x70,0x84,0xe8,0x3e,0xe0,0x20,
0xd9,0x15,0xca,0xf5,0x0c,0xf3,0xa1,0xd3,0xca,0xf9,0x9f,0xa9,0x9a,0x8c,0x79,0x84,0x55,0xba,0xca,0x4c,
0xe5,0xb2,0x96,0x5a,0x8c,0x82,0xe7,0xf8,0x4b,0x1d,0x50
};

168
espurna/system.ino Executable file
View File

@ -0,0 +1,168 @@
/*
SYSTEM MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
// -----------------------------------------------------------------------------
unsigned long _loop_delay = 0;
bool _system_send_heartbeat = false;
// Calculated load average 0 to 100;
unsigned short int _load_average = 100;
// -----------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
// Call this method on boot with start=true to increase the crash counter
// Call it again once the system is stable to decrease the counter
// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable
// setting _systemOK = false;
//
// An unstable system will only have serial access, WiFi in AP mode and OTA
bool _systemStable = true;
void systemCheck(bool stable) {
unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER);
if (stable) {
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
} else {
if (++value > SYSTEM_CHECK_MAX) {
_systemStable = false;
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
}
}
EEPROM.write(EEPROM_CRASH_COUNTER, value);
EEPROM.commit();
}
bool systemCheck() {
return _systemStable;
}
void systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
}
#endif
// -----------------------------------------------------------------------------
void systemSendHeartbeat() {
_system_send_heartbeat = true;
}
unsigned long systemLoopDelay() {
return _loop_delay;
}
unsigned long systemLoadAverage() {
return _load_average;
}
void systemLoop() {
// -------------------------------------------------------------------------
// Check system stability
// -------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
#endif
// -------------------------------------------------------------------------
// Heartbeat
// -------------------------------------------------------------------------
#if HEARTBEAT_ENABLED
// Heartbeat
static unsigned long last_hbeat = 0;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
last_hbeat = millis();
heartbeat();
}
#endif // HEARTBEAT_ENABLED
// -------------------------------------------------------------------------
// Load Average calculation
// -------------------------------------------------------------------------
static unsigned long last_loadcheck = 0;
static unsigned long load_counter_temp = 0;
load_counter_temp++;
if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
static unsigned long load_counter = 0;
static unsigned long load_counter_max = 1;
load_counter = load_counter_temp;
load_counter_temp = 0;
if (load_counter > load_counter_max) {
load_counter_max = load_counter;
}
_load_average = 100 - (100 * load_counter / load_counter_max);
last_loadcheck = millis();
}
// -------------------------------------------------------------------------
// Power saving delay
// -------------------------------------------------------------------------
delay(_loop_delay);
}
void systemSetup() {
EEPROM.begin(EEPROM_SIZE);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
#if DEBUG_ESP_WIFI
DEBUG_PORT.setDebugOutput(true);
#endif
#elif defined(SERIAL_BAUDRATE)
Serial.begin(SERIAL_BAUDRATE);
#endif
#if SPIFFS_SUPPORT
SPIFFS.begin();
#endif
// Question system stability
#if SYSTEM_CHECK_ENABLED
systemCheck(false);
#endif
#if defined(ESPLIVE)
//The ESPLive has an ADC MUX which needs to be configured.
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
// Cache loop delay value to speed things (recommended max 250ms)
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
// Register Loop
espurnaRegisterLoop(systemLoop);
}

187
espurna/telnet.ino Executable file
View File

@ -0,0 +1,187 @@
/*
TELNET MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
Parts of the code have been borrowed from Thomas Sarlandie's NetServer
(https://github.com/sarfata/kbox-firmware/tree/master/src/esp)
*/
#if TELNET_SUPPORT
#include <ESPAsyncTCP.h>
AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
bool _telnetFirst = true;
// -----------------------------------------------------------------------------
// Private methods
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _telnetWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "telnet", 6) == 0);
}
void _telnetWebSocketOnSend(JsonObject& root) {
root["telnetVisible"] = 1;
root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
}
#endif
void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
delete _telnetClients[clientId];
wifiReconnectCheck();
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
}
bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) {
return (_telnetClients[clientId]->write((const char*) data, len) > 0);
}
return false;
}
unsigned char _telnetWrite(void *data, size_t len) {
unsigned char count = 0;
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (_telnetWrite(i, data, len)) ++count;
}
return count;
}
void _telnetData(unsigned char clientId, void *data, size_t len) {
// Skip first message since it's always garbage
if (_telnetFirst) {
_telnetFirst = false;
return;
}
// Capture close connection
char * p = (char *) data;
if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
_telnetClients[clientId]->close();
return;
}
// Inject into Embedis stream
settingsInject(data, len);
}
void _telnetNewClient(AsyncClient *client) {
if (client->localIP() != WiFi.softAPIP()) {
// Telnet is always available for the ESPurna Core image
#ifdef ESPURNA_CORE
bool telnetSTA = true;
#else
bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
#endif
if (!telnetSTA) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) {
c->free();
delete c;
});
client->close(true);
return;
}
}
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
_telnetClients[i] = client;
client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) {
}, 0);
client->onData([i](void *s, AsyncClient *c, void *data, size_t len) {
_telnetData(i, data, len);
}, 0);
client->onDisconnect([i](void *s, AsyncClient *c) {
_telnetDisconnect(i);
}, 0);
client->onError([i](void *s, AsyncClient *c, int8_t error) {
DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%u\n"), c->errorToString(error), error, i);
}, 0);
client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) {
DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%u at %lu\n"), i, time);
c->close();
}, 0);
DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i);
// If there is no terminal support automatically dump info and crash data
#if TERMINAL_SUPPORT == 0
info();
wifiStatus();
debugDumpCrashInfo();
debugClearCrashInfo();
#endif
_telnetFirst = true;
wifiReconnectCheck();
return;
}
}
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) {
c->free();
delete c;
});
client->close(true);
}
// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------
bool telnetConnected() {
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (_telnetClients[i] && _telnetClients[i]->connected()) return true;
}
return false;
}
unsigned char telnetWrite(unsigned char ch) {
char data[1] = {ch};
return _telnetWrite(data, 1);
}
void telnetSetup() {
_telnetServer = new AsyncServer(TELNET_PORT);
_telnetServer->onClient([](void *s, AsyncClient* c) {
_telnetNewClient(c);
}, 0);
_telnetServer->begin();
#if WEB_SUPPORT
wsOnSendRegister(_telnetWebSocketOnSend);
wsOnReceiveRegister(_telnetWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT);
}
#endif // TELNET_SUPPORT

281
espurna/thinkspeak.ino Executable file
View File

@ -0,0 +1,281 @@
/*
THINGSPEAK MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if THINGSPEAK_SUPPORT
#if THINGSPEAK_USE_ASYNC
#include <ESPAsyncTCP.h>
AsyncClient * _tspk_client;
#else
#include <ESP8266WiFi.h>
#endif
const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: ESPurna\r\n"
"Connection: close\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %d\r\n\r\n"
"%s\r\n";
bool _tspk_enabled = false;
char * _tspk_queue[8] = {NULL};
bool _tspk_flush = false;
unsigned long _tspk_last_flush = 0;
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) {
return (strncmp(key, "tspk", 4) == 0);
}
void _tspkWebSocketOnSend(JsonObject& root) {
unsigned char visible = 0;
root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
root["tspkKey"] = getSetting("tspkKey");
JsonArray& relays = root.createNestedArray("tspkRelays");
for (byte i=0; i<relayCount(); i++) {
relays.add(getSetting("tspkRelay", i, 0).toInt());
}
if (relayCount() > 0) visible = 1;
#if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("tspkMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("tspkMagnitude", i, 0).toInt();
}
if (magnitudeCount() > 0) visible = 1;
#endif
root["tspkVisible"] = visible;
}
#endif
void _tspkConfigure() {
_tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
_tspk_enabled = false;
setSetting("tspkEnabled", 0);
}
}
#if THINGSPEAK_USE_ASYNC
void _tspkPost(String data) {
if (_tspk_client == NULL) {
_tspk_client = new AsyncClient();
}
_tspk_client->onDisconnect([](void *s, AsyncClient *c) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Disconnected\n"));
_tspk_client->free();
delete _tspk_client;
_tspk_client = NULL;
}, 0);
_tspk_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
_tspk_client->close(true);
}, 0);
_tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) {
char * b = (char *) response;
b[len] = 0;
char * p = strstr((char *)response, "\r\n\r\n");
unsigned int code = (p != NULL) ? atoi(&p[4]) : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_client->close(true);
}, NULL);
_tspk_client->onConnect([data](void * arg, AsyncClient * client) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
#if THINGSPEAK_USE_SSL
uint8_t fp[20] = {0};
sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp);
SSL * ssl = _tspk_client->getSSL();
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
}
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE,
THINGSPEAK_URL,
THINGSPEAK_HOST,
data.length(),
data.c_str()
);
client->write(buffer);
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT, THINGSPEAK_USE_SSL);
#else
bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT);
#endif
if (!connected) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
_tspk_client->close(true);
}
}
#else // THINGSPEAK_USE_ASYNC
void _tspkPost(String data) {
#if THINGSPEAK_USE_SSL
WiFiClientSecure _tspk_client;
#else
WiFiClient _tspk_client;
#endif
if (_tspk_client.connect(THINGSPEAK_HOST, THINGSPEAK_PORT)) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, THINGSPEAK_HOST)) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
}
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE,
THINGSPEAK_URL,
THINGSPEAK_HOST,
data.length(),
data.c_str()
);
_tspk_client.print(buffer);
nice_delay(100);
String response = _tspk_client.readString();
int pos = response.indexOf("\r\n\r\n");
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_client.stop();
return;
}
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
}
#endif // THINGSPEAK_USE_ASYNC
bool _tspkEnqueue(unsigned char index, char * payload) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%d with value %s\n"), index, payload);
--index;
if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
_tspk_queue[index] = strdup(payload);
}
void _tspkFlush() {
String data;
// Walk the fields
for (unsigned char id=0; id<8; id++) {
if (_tspk_queue[id] != NULL) {
if (data.length() > 0) data = data + String("&");
data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]);
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
}
// POST data if any
if (data.length() > 0) {
data = data + String("&api_key=") + getSetting("tspkKey");
_tspkPost(data);
_tspk_last_flush = millis();
}
}
// -----------------------------------------------------------------------------
bool tspkEnqueueRelay(unsigned char index, unsigned char status) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting("tspkRelay", index, 0).toInt();
if (id > 0) {
char payload[3] = {0};
itoa(status ? 1 : 0, payload, 10);
_tspkEnqueue(id, payload);
}
}
bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting("tspkMagnitude", index, 0).toInt();
if (id > 0) {
_tspkEnqueue(id, payload);
}
}
void tspkFlush() {
_tspk_flush = true;
}
bool tspkEnabled() {
return _tspk_enabled;
}
void tspkSetup() {
_tspkConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure);
wsOnReceiveRegister(_tspkWebSocketOnReceive);
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
);
// Register loop
espurnaRegisterLoop(tspkLoop);
}
void tspkLoop() {
if (!_tspk_enabled) return;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) {
_tspkFlush();
_tspk_flush = false;
}
}
#endif

103
espurna/uartmqtt.ino Executable file
View File

@ -0,0 +1,103 @@
/*
UART_MQTT MODULE
Copyright (C) 2018 by Albert Weterings
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#if UART_MQTT_SUPPORT
char _uartmqttBuffer[UART_MQTT_BUFFER_SIZE];
bool _uartmqttNewData = false;
#if UART_MQTT_USE_SOFT
#include <SoftwareSerial.h>
SoftwareSerial _uart_mqtt_serial(UART_MQTT_RX_PIN, UART_MQTT_TX_PIN, false, UART_MQTT_BUFFER_SIZE);
#define UART_MQTT_PORT _uart_mqtt_serial
#else
#define UART_MQTT_PORT UART_MQTT_HW_PORT
#endif
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
void _uartmqttReceiveUART() {
static unsigned char ndx = 0;
while (UART_MQTT_PORT.available() > 0 && _uartmqttNewData == false) {
char rc = UART_MQTT_PORT.read();
if (rc != '\n') {
_uartmqttBuffer[ndx] = rc;
if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++;
} else {
_uartmqttBuffer[ndx] = '\0';
_uartmqttNewData = true;
ndx = 0;
}
}
}
void _uartmqttSendMQTT() {
if (_uartmqttNewData == true && MQTT_SUPPORT) {
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over MQTT: %s\n"), _uartmqttBuffer);
mqttSend(MQTT_TOPIC_UARTIN, _uartmqttBuffer);
_uartmqttNewData = false;
}
}
void _uartmqttSendUART(const char * message) {
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over UART: %s\n"), message);
UART_MQTT_PORT.println(message);
}
void _uartmqttMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_UARTOUT);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (t.equals(MQTT_TOPIC_UARTOUT)) {
_uartmqttSendUART(payload);
}
}
}
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void _uartmqttLoop() {
_uartmqttReceiveUART();
_uartmqttSendMQTT();
}
void uartmqttSetup() {
// Init port
UART_MQTT_PORT.begin(UART_MQTT_BAUDRATE);
// Register MQTT callbackj
mqttRegister(_uartmqttMQTTCallback);
// Register loop
espurnaRegisterLoop(_uartmqttLoop);
}
#endif // UART_MQTT_SUPPORT

527
espurna/utils.ino Executable file
View File

@ -0,0 +1,527 @@
/*
UTILS MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <Ticker.h>
Ticker _defer_reset;
String getIdentifier() {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId());
return String(buffer);
}
void setDefaultHostname() {
if (strlen(HOSTNAME) > 0) {
setSetting("hostname", HOSTNAME);
} else {
setSetting("hostname", getIdentifier());
}
}
void setBoardName() {
#ifndef ESPURNA_CORE
setSetting("boardName", DEVICE_NAME);
#endif
}
String getBoardName() {
return getSetting("boardName", DEVICE_NAME);
}
String getCoreVersion() {
String version = ESP.getCoreVersion();
#ifdef ARDUINO_ESP8266_RELEASE
if (version.equals("00000000")) {
version = String(ARDUINO_ESP8266_RELEASE);
}
#endif
version.replace("_", ".");
return version;
}
String getCoreRevision() {
#ifdef ARDUINO_ESP8266_GIT_VER
return String(ARDUINO_ESP8266_GIT_VER);
#else
return String("");
#endif
}
// WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else...
unsigned int getFreeHeap() {
if (getSetting("wtfHeap", 0).toInt() == 1) return 9999;
return ESP.getFreeHeap();
}
String buildTime() {
const char time_now[] = __TIME__; // hh:mm:ss
unsigned int hour = atoi(&time_now[0]);
unsigned int minute = atoi(&time_now[3]);
unsigned int second = atoi(&time_now[6]);
const char date_now[] = __DATE__; // Mmm dd yyyy
const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
unsigned int month = 0;
for ( int i = 0; i < 12; i++ ) {
if (strncmp(date_now, months[i], 3) == 0 ) {
month = i + 1;
break;
}
}
unsigned int day = atoi(&date_now[3]);
unsigned int year = atoi(&date_now[7]);
char buffer[20];
snprintf_P(
buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
year, month, day, hour, minute, second
);
return String(buffer);
}
unsigned long getUptime() {
static unsigned long last_uptime = 0;
static unsigned char uptime_overflows = 0;
if (millis() < last_uptime) ++uptime_overflows;
last_uptime = millis();
unsigned long uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000);
return uptime_seconds;
}
#if HEARTBEAT_ENABLED
void heartbeat() {
unsigned long uptime_seconds = getUptime();
unsigned int free_heap = getFreeHeap();
#if MQTT_SUPPORT
bool serial = !mqttConnected();
#else
bool serial = true;
#endif
// -------------------------------------------------------------------------
// Serial
// -------------------------------------------------------------------------
if (serial) {
DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds);
DEBUG_MSG_P(PSTR("[MAIN] Free heap: %lu bytes\n"), free_heap);
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc());
#endif
#if NTP_SUPPORT
if (ntpSynced()) DEBUG_MSG_P(PSTR("[MAIN] Time: %s\n"), (char *) ntpDateTime().c_str());
#endif
}
// -------------------------------------------------------------------------
// MQTT
// -------------------------------------------------------------------------
#if MQTT_SUPPORT
if (!serial) {
#if (HEARTBEAT_REPORT_INTERVAL)
mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000);
#endif
#if (HEARTBEAT_REPORT_APP)
mqttSend(MQTT_TOPIC_APP, APP_NAME);
#endif
#if (HEARTBEAT_REPORT_VERSION)
mqttSend(MQTT_TOPIC_VERSION, APP_VERSION);
#endif
#if (HEARTBEAT_REPORT_BOARD)
mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
#endif
#if (HEARTBEAT_REPORT_HOSTNAME)
mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str());
#endif
#if (HEARTBEAT_REPORT_IP)
mqttSend(MQTT_TOPIC_IP, getIP().c_str());
#endif
#if (HEARTBEAT_REPORT_MAC)
mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
#endif
#if (HEARTBEAT_REPORT_RSSI)
mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
#endif
#if (HEARTBEAT_REPORT_UPTIME)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_DATETIME) && (NTP_SUPPORT)
if (ntpSynced()) mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#if (HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ADC_VCC_ENABLED
mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_STATUS)
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
#if (LOADAVG_REPORT)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
#endif
}
#endif
// -------------------------------------------------------------------------
// InfluxDB
// -------------------------------------------------------------------------
#if INFLUXDB_SUPPORT
#if (HEARTBEAT_REPORT_UPTIME)
idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#endif
}
#endif /// HEARTBEAT_ENABLED
unsigned int sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
}
void info() {
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR);
DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE);
DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %u MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
FlashMode_t mode = ESP.getFlashChipMode();
DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
#if SPIFFS_SUPPORT
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
if (fs) {
DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength);
} else {
DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
}
DEBUG_MSG_P(PSTR("\n"));
#endif
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if BROKER_SUPPORT
DEBUG_MSG_P(PSTR(" BROKER"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
#if I2C_SUPPORT
DEBUG_MSG_P(PSTR(" I2C"));
#endif
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_SERVER"));
#endif
#if MDNS_CLIENT_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_CLIENT"));
#endif
#if MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" MQTT"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if UART_MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" UART_MQTT"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] SENSORS:"));
#if AM2320_SUPPORT
DEBUG_MSG_P(PSTR(" AM2320_I2C"));
#endif
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if GUVAS12SD_SUPPORT
DEBUG_MSG_P(PSTR(" GUVAS12SD"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if PZEM004T_SUPPORT
DEBUG_MSG_P(PSTR(" PZEM004T"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
#endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
// -------------------------------------------------------------------------
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
}
DEBUG_MSG_P(PSTR("[INIT] Settings size: %u bytes\n"), settingsSize());
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap());
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), systemLoopDelay());
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) DEBUG_MSG_P(PSTR("\n[INIT] Device is in SAFE MODE\n"));
#endif
DEBUG_MSG_P(PSTR("\n"));
}
// -----------------------------------------------------------------------------
// SSL
// -----------------------------------------------------------------------------
#if ASYNC_TCP_SSL_ENABLED
bool sslCheckFingerPrint(const char * fingerprint) {
return (strlen(fingerprint) == 59);
}
bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (!sslCheckFingerPrint(fingerprint)) return false;
// walk the fingerprint
for (unsigned int i=0; i<20; i++) {
bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
}
return true;
}
bool sslFingerPrintChar(const char * fingerprint, char * destination) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (!sslCheckFingerPrint(fingerprint)) return false;
// copy it
strncpy(destination, fingerprint, 59);
// walk the fingerprint replacing ':' for ' '
for (unsigned char i = 0; i<59; i++) {
if (destination[i] == ':') destination[i] = ' ';
}
return true;
}
#endif
// -----------------------------------------------------------------------------
// Reset
// -----------------------------------------------------------------------------
unsigned char resetReason() {
static unsigned char status = 255;
if (status == 255) {
status = EEPROM.read(EEPROM_CUSTOM_RESET);
if (status > 0) resetReason(0);
if (status > CUSTOM_RESET_MAX) status = 0;
}
return status;
}
void resetReason(unsigned char reason) {
EEPROM.write(EEPROM_CUSTOM_RESET, reason);
EEPROM.commit();
}
void reset(unsigned char reason) {
resetReason(reason);
ESP.restart();
}
void deferredReset(unsigned long delay, unsigned char reason) {
_defer_reset.once_ms(delay, reset, reason);
}
// -----------------------------------------------------------------------------
char * ltrim(char * s) {
char *p = s;
while ((unsigned char) *p == ' ') ++p;
return p;
}
double roundTo(double num, unsigned char positions) {
double multiplier = 1;
while (positions-- > 0) multiplier *= 10;
return round(num * multiplier) / multiplier;
}
void nice_delay(unsigned long ms) {
unsigned long start = millis();
while (millis() - start < ms) delay(1);
}

341
espurna/web.ino Executable file
View File

@ -0,0 +1,341 @@
/*
WEBSERVER MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if WEB_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Hash.h>
#include <FS.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
#if WEB_EMBEDDED
#include "static/index.html.gz.h"
#endif // WEB_EMBEDDED
#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
#include "static/server.cer.h"
#include "static/server.key.h"
#endif // ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
// -----------------------------------------------------------------------------
AsyncWebServer * _server;
char _last_modified[50];
std::vector<uint8_t> * _webConfigBuffer;
bool _webConfigSuccess = false;
// -----------------------------------------------------------------------------
// HOOKS
// -----------------------------------------------------------------------------
void _onReset(AsyncWebServerRequest *request) {
deferredReset(100, CUSTOM_RESET_HTTP);
request->send(200);
}
void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
AsyncResponseStream *response = request->beginResponseStream("text/json");
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
settingsGetJson(root);
root.prettyPrintTo(*response);
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
response->addHeader("Content-Disposition", buffer);
request->send(response);
}
void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
request->send(_webConfigSuccess ? 200 : 400);
}
void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
// No buffer
if (final && (index == 0)) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) data);
if (root.success()) _webConfigSuccess = settingsRestoreJson(root);
return;
}
// Buffer start => reset
if (index == 0) if (_webConfigBuffer) delete _webConfigBuffer;
// init buffer if it doesn't exist
if (!_webConfigBuffer) {
_webConfigBuffer = new std::vector<uint8_t>();
_webConfigSuccess = false;
}
// Copy
if (len > 0) {
_webConfigBuffer->reserve(_webConfigBuffer->size() + len);
_webConfigBuffer->insert(_webConfigBuffer->end(), data, data + len);
}
// Ending
if (final) {
_webConfigBuffer->push_back(0);
// Parse JSON
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) _webConfigBuffer->data());
if (root.success()) _webConfigSuccess = settingsRestoreJson(root);
delete _webConfigBuffer;
}
}
#if WEB_EMBEDDED
void _onHome(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (request->header("If-Modified-Since").equals(_last_modified)) {
request->send(304);
} else {
#if ASYNC_TCP_SSL_ENABLED
// Chunked response, we calculate the chunks based on free heap (in multiples of 32)
// This is necessary when a TLS connection is open since it sucks too much memory
DEBUG_MSG_P(PSTR("[MAIN] Free heap: %d bytes\n"), getFreeHeap());
size_t max = (getFreeHeap() / 3) & 0xFFE0;
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [max](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
// Get the chunk based on the index and maxLen
size_t len = index_html_gz_len - index;
if (len > maxLen) len = maxLen;
if (len > max) len = max;
if (len > 0) memcpy_P(buffer, index_html_gz + index, len);
DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / index_html_gz_len), max);
if (len == 0) DEBUG_MSG_P(PSTR("\n"));
// Return the actual length of the chunk (0 for end of file)
return len;
});
#else
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html_gz, index_html_gz_len);
#endif
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Last-Modified", _last_modified);
request->send(response);
}
}
#endif
#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
#if WEB_EMBEDDED
if (strcmp(filename, "server.cer") == 0) {
uint8_t * nbuf = (uint8_t*) malloc(server_cer_len);
memcpy_P(nbuf, server_cer, server_cer_len);
*buf = nbuf;
DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
return server_cer_len;
}
if (strcmp(filename, "server.key") == 0) {
uint8_t * nbuf = (uint8_t*) malloc(server_key_len);
memcpy_P(nbuf, server_key, server_key_len);
*buf = nbuf;
DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
return server_key_len;
}
DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename);
*buf = 0;
return 0;
#else
File file = SPIFFS.open(filename, "r");
if (file) {
size_t size = file.size();
uint8_t * nbuf = (uint8_t*) malloc(size);
if (nbuf) {
size = file.read(nbuf, size);
file.close();
*buf = nbuf;
DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename);
return size;
}
file.close();
}
DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename);
*buf = 0;
return 0;
#endif
}
#endif
void _onUpgrade(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
char buffer[10];
if (!Update.hasError()) {
sprintf_P(buffer, PSTR("OK"));
} else {
sprintf_P(buffer, PSTR("ERROR %d"), Update.getError());
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer);
response->addHeader("Connection", "close");
if (!Update.hasError()) {
deferredReset(100, CUSTOM_RESET_UPGRADE);
}
request->send(response);
}
void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str());
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
}
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
}
if (final) {
if (Update.end(true)){
DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len);
} else {
#ifdef DEBUG_PORT
Update.printError(DEBUG_PORT);
#endif
}
} else {
DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len);
}
}
// -----------------------------------------------------------------------------
bool _authenticate(AsyncWebServerRequest *request) {
#if USE_PASSWORD
String password = getSetting("adminPass", ADMIN_PASS);
char httpPassword[password.length() + 1];
password.toCharArray(httpPassword, password.length() + 1);
return request->authenticate(WEB_USERNAME, httpPassword);
#else
return true;
#endif
}
// -----------------------------------------------------------------------------
AsyncWebServer * webServer() {
return _server;
}
unsigned int webPort() {
#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
return 443;
#else
return getSetting("webPort", WEB_PORT).toInt();
#endif
}
void webLog(AsyncWebServerRequest *request) {
DEBUG_MSG_P(PSTR("[WEBSERVER] Request: %s %s\n"), request->methodToString(), request->url().c_str());
}
void webSetup() {
// Cache the Last-Modifier header value
snprintf_P(_last_modified, sizeof(_last_modified), PSTR("%s %s GMT"), __DATE__, __TIME__);
// Create server
unsigned int port = webPort();
_server = new AsyncWebServer(port);
// Rewrites
_server->rewrite("/", "/index.html");
// Serve home (basic authentication protection)
#if WEB_EMBEDDED
_server->on("/index.html", HTTP_GET, _onHome);
#endif
_server->on("/reset", HTTP_GET, _onReset);
_server->on("/config", HTTP_GET, _onGetConfig);
_server->on("/config", HTTP_POST | HTTP_PUT, _onPostConfig, _onPostConfigData);
_server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData);
// Serve static files
#if SPIFFS_SUPPORT
_server->serveStatic("/", SPIFFS, "/")
.setLastModified(_last_modified)
.setFilter([](AsyncWebServerRequest *request) -> bool {
webLog(request);
return true;
});
#endif
// 404
_server->onNotFound([](AsyncWebServerRequest *request){
request->send(404);
});
// Run server
#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED
_server->onSslFileRequest(_onCertificate, NULL);
_server->beginSecure("server.cer", "server.key", NULL);
#else
_server->begin();
#endif
DEBUG_MSG_P(PSTR("[WEBSERVER] Webserver running on port %u\n"), port);
}
#endif // WEB_SUPPORT

454
espurna/wifi.ino Executable file
View File

@ -0,0 +1,454 @@
/*
WIFI MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "JustWifi.h"
#include <Ticker.h>
uint32_t _wifi_scan_client_id = 0;
// -----------------------------------------------------------------------------
// PRIVATE
// -----------------------------------------------------------------------------
void _wifiConfigure() {
jw.setHostname(getSetting("hostname").c_str());
#if USE_PASSWORD
jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
#else
jw.setSoftAP(getSetting("hostname").c_str());
#endif
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
wifiReconnectCheck();
jw.setAPMode(WIFI_AP_MODE);
jw.cleanNetworks();
// If system is flagged unstable we do not init wifi networks
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
#endif
// Clean settings
_wifiClean(WIFI_MAX_NETWORKS);
int i;
for (i = 0; i< WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
if (getSetting("ip" + String(i)).length() == 0) {
jw.addNetwork(
getSetting("ssid" + String(i)).c_str(),
getSetting("pass" + String(i)).c_str()
);
} else {
jw.addNetwork(
getSetting("ssid" + String(i)).c_str(),
getSetting("pass" + String(i)).c_str(),
getSetting("ip" + String(i)).c_str(),
getSetting("gw" + String(i)).c_str(),
getSetting("mask" + String(i)).c_str(),
getSetting("dns" + String(i)).c_str()
);
}
}
jw.scanNetworks(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
}
void _wifiScan(uint32_t client_id = 0) {
DEBUG_MSG_P(PSTR("[WIFI] Start scanning\n"));
#if WEB_SUPPORT
String output;
#endif
unsigned char result = WiFi.scanNetworks();
if (result == WIFI_SCAN_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
#if WEB_SUPPORT
output = String("Failed scan");
#endif
} else if (result == 0) {
DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
#if WEB_SUPPORT
output = String("No networks found");
#endif
} else {
DEBUG_MSG_P(PSTR("[WIFI] %d networks found:\n"), result);
// Populate defined networks with scan data
for (int8_t i = 0; i < result; ++i) {
String ssid_scan;
int32_t rssi_scan;
uint8_t sec_scan;
uint8_t* BSSID_scan;
int32_t chan_scan;
bool hidden_scan;
char buffer[128];
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan, hidden_scan);
snprintf_P(buffer, sizeof(buffer),
PSTR("BSSID: %02X:%02X:%02X:%02X:%02X:%02X SEC: %s RSSI: %3d CH: %2d SSID: %s"),
BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], BSSID_scan[6],
(sec_scan != ENC_TYPE_NONE ? "YES" : "NO "),
rssi_scan,
chan_scan,
(char *) ssid_scan.c_str()
);
DEBUG_MSG_P(PSTR("[WIFI] > %s\n"), buffer);
#if WEB_SUPPORT
if (client_id > 0) output = output + String(buffer) + String("<br />");
#endif
}
}
#if WEB_SUPPORT
if (client_id > 0) {
output = String("{\"scanResult\": \"") + output + String("\"}");
wsSend(client_id, output.c_str());
}
#endif
WiFi.scanDelete();
}
bool _wifiClean(unsigned char num) {
bool changed = false;
int i = 0;
// Clean defined settings
while (i < num) {
// Skip on first non-defined setting
if (!hasSetting("ssid", i)) {
delSetting("ssid", i);
break;
}
// Delete empty values
if (!hasSetting("pass", i)) delSetting("pass", i);
if (!hasSetting("ip", i)) delSetting("ip", i);
if (!hasSetting("gw", i)) delSetting("gw", i);
if (!hasSetting("mask", i)) delSetting("mask", i);
if (!hasSetting("dns", i)) delSetting("dns", i);
++i;
}
// Delete all other settings
while (i < WIFI_MAX_NETWORKS) {
changed = hasSetting("ssid", i);
delSetting("ssid", i);
delSetting("pass", i);
delSetting("ip", i);
delSetting("gw", i);
delSetting("mask", i);
delSetting("dns", i);
++i;
}
return changed;
}
// Inject hardcoded networks
void _wifiInject() {
if (strlen(WIFI1_SSID)) {
if (!hasSetting("ssid", 0)) {
setSetting("ssid", 0, WIFI1_SSID);
setSetting("pass", 0, WIFI1_PASS);
setSetting("ip", 0, WIFI1_IP);
setSetting("gw", 0, WIFI1_GW);
setSetting("mask", 0, WIFI1_MASK);
setSetting("dns", 0, WIFI1_DNS);
}
if (strlen(WIFI2_SSID)) {
if (!hasSetting("ssid", 1)) {
setSetting("ssid", 1, WIFI2_SSID);
setSetting("pass", 1, WIFI2_PASS);
setSetting("ip", 1, WIFI2_IP);
setSetting("gw", 1, WIFI2_GW);
setSetting("mask", 1, WIFI2_MASK);
setSetting("dns", 1, WIFI2_DNS);
}
}
}
}
#if DEBUG_SUPPORT
void _wifiDebug(justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_SCANNING) {
DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
}
if (code == MESSAGE_SCAN_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
}
if (code == MESSAGE_NO_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
}
if (code == MESSAGE_NO_KNOWN_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No known networks found\n"));
}
if (code == MESSAGE_FOUND_NETWORK) {
DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
}
if (code == MESSAGE_CONNECTING) {
DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
}
if (code == MESSAGE_CONNECT_WAITING) {
// too much noise
}
if (code == MESSAGE_CONNECT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not connect to %s\n"), parameter);
}
if (code == MESSAGE_CONNECTED) {
wifiStatus();
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiStatus();
}
if (code == MESSAGE_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
}
if (code == MESSAGE_ACCESSPOINT_CREATING) {
DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
}
if (code == MESSAGE_ACCESSPOINT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
}
}
#endif // DEBUG_SUPPORT
// -----------------------------------------------------------------------------
// SETTINGS
// -----------------------------------------------------------------------------
#if TERMINAL_SUPPORT
void _wifiInitCommands() {
settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) {
_wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
createAP();
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_wifiScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
bool _wifiWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "wifi", 4) == 0) return true;
if (strncmp(key, "ssid", 4) == 0) return true;
if (strncmp(key, "pass", 4) == 0) return true;
if (strncmp(key, "ip", 2) == 0) return true;
if (strncmp(key, "gw", 2) == 0) return true;
if (strncmp(key, "mask", 4) == 0) return true;
if (strncmp(key, "dns", 3) == 0) return true;
return false;
}
void _wifiWebSocketOnSend(JsonObject& root) {
root["maxNetworks"] = WIFI_MAX_NETWORKS;
root["wifiScan"] = getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
if (!hasSetting("ssid", i)) break;
JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid", i, "");
network["pass"] = getSetting("pass", i, "");
network["ip"] = getSetting("ip", i, "");
network["gw"] = getSetting("gw", i, "");
network["mask"] = getSetting("mask", i, "");
network["dns"] = getSetting("dns", i, "");
}
}
void _wifiWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "scan") == 0) _wifi_scan_client_id = client_id;
}
#endif
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
String getIP() {
if (WiFi.getMode() == WIFI_AP) {
return WiFi.softAPIP().toString();
}
return WiFi.localIP().toString();
}
String getNetwork() {
if (WiFi.getMode() == WIFI_AP) {
return jw.getAPSSID();
}
return WiFi.SSID();
}
bool wifiConnected() {
return jw.connected();
}
void wifiDisconnect() {
jw.disconnect();
}
bool createAP() {
jw.disconnect();
jw.resetReconnectTimeout();
return jw.createAP();
}
void wifiReconnectCheck() {
bool connected = false;
#if WEB_SUPPORT
if (wsConnected()) connected = true;
#endif
#if TELNET_SUPPORT
if (telnetConnected()) connected = true;
#endif
jw.setReconnectTimeout(connected ? 0 : WIFI_RECONNECT_INTERVAL);
}
void wifiStatus() {
if (WiFi.getMode() == WIFI_AP_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP + STA --------------------------------\n"));
} else if (WiFi.getMode() == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP --------------------------------------\n"));
} else if (WiFi.getMode() == WIFI_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE STA -------------------------------------\n"));
} else {
DEBUG_MSG_P(PSTR("[WIFI] MODE OFF -------------------------------------\n"));
DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
}
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
}
if ((WiFi.getMode() & WIFI_STA) == WIFI_STA) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
}
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
}
void wifiRegister(wifi_callback_f callback) {
jw.subscribe(callback);
}
// -----------------------------------------------------------------------------
// INITIALIZATION
// -----------------------------------------------------------------------------
void wifiSetup() {
WiFi.setSleepMode(WIFI_SLEEP_MODE);
_wifiInject();
_wifiConfigure();
// Message callbacks
#if DEBUG_SUPPORT
wifiRegister(_wifiDebug);
#endif
#if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend);
wsOnReceiveRegister(_wifiWebSocketOnReceive);
wsOnAfterParseRegister(_wifiConfigure);
wsOnActionRegister(_wifiWebSocketOnAction);
#endif
#if TERMINAL_SUPPORT
_wifiInitCommands();
#endif
// Register loop
espurnaRegisterLoop(wifiLoop);
}
void wifiLoop() {
jw.loop();
if (_wifi_scan_client_id > 0) {
_wifiScan(_wifi_scan_client_id);
_wifi_scan_client_id = 0;
}
}

448
espurna/ws.ino Executable file
View File

@ -0,0 +1,448 @@
/*
WEBSOCKET MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if WEB_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <Ticker.h>
#include <vector>
#include "libs/WebSocketIncommingBuffer.h"
AsyncWebSocket _ws("/ws");
Ticker _web_defer;
std::vector<ws_on_send_callback_f> _ws_on_send_callbacks;
std::vector<ws_on_action_callback_f> _ws_on_action_callbacks;
std::vector<ws_on_after_parse_callback_f> _ws_on_after_parse_callbacks;
std::vector<ws_on_receive_callback_f> _ws_on_receive_callbacks;
// -----------------------------------------------------------------------------
// Private methods
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}"));
if (type == MQTT_DISCONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": false}"));
}
#endif
bool _wsStore(String key, String value) {
// HTTP port
if (key == "webPort") {
if ((value.toInt() == 0) || (value.toInt() == 80)) {
return delSetting(key);
}
}
if (value != getSetting(key)) {
return setSetting(key, value);
}
return false;
}
bool _wsStore(String key, JsonArray& value) {
bool changed = false;
unsigned char index = 0;
for (auto element : value) {
if (_wsStore(key + index, element.as<String>())) changed = true;
index++;
}
// Delete further values
for (unsigned char i=index; i<SETTINGS_MAX_LIST_COUNT; i++) {
if (!delSetting(key, index)) break;
changed = true;
}
return changed;
}
void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : "");
// Get client ID
uint32_t client_id = client->id();
// Parse JSON input
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Error parsing data\n"));
wsSend_P(client_id, PSTR("{\"message\": 3}"));
return;
}
// Check actions -----------------------------------------------------------
const char* action = root["action"];
if (action) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action);
if (strcmp(action, "reboot") == 0) {
deferredReset(100, CUSTOM_RESET_WEB);
return;
}
if (strcmp(action, "reconnect") == 0) {
_web_defer.once_ms(100, wifiDisconnect);
return;
}
if (strcmp(action, "factory_reset") == 0) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
resetSettings();
deferredReset(100, CUSTOM_RESET_FACTORY);
return;
}
JsonObject& data = root["data"];
if (data.success()) {
// Callbacks
for (unsigned char i = 0; i < _ws_on_action_callbacks.size(); i++) {
(_ws_on_action_callbacks[i])(client_id, action, data);
}
// Restore configuration via websockets
if (strcmp(action, "restore") == 0) {
if (settingsRestoreJson(data)) {
wsSend_P(client_id, PSTR("{\"message\": 5}"));
} else {
wsSend_P(client_id, PSTR("{\"message\": 4}"));
}
}
return;
}
};
// Check configuration -----------------------------------------------------
JsonObject& config = root["config"];
if (config.success()) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n"));
String adminPass;
bool save = false;
#if MQTT_SUPPORT
bool changedMQTT = false;
#endif
for (auto kv: config) {
bool changed = false;
String key = kv.key;
JsonVariant& value = kv.value;
// Check password
if (key == "adminPass") {
if (!value.is<JsonArray&>()) continue;
JsonArray& values = value.as<JsonArray&>();
if (values.size() != 2) continue;
if (values[0].as<String>().equals(values[1].as<String>())) {
String password = values[0].as<String>();
if (password.length() > 0) {
setSetting(key, password);
save = true;
wsSend_P(client_id, PSTR("{\"action\": \"reload\"}"));
}
} else {
wsSend_P(client_id, PSTR("{\"message\": 7}"));
}
continue;
}
// Check if key has to be processed
bool found = false;
for (unsigned char i = 0; i < _ws_on_receive_callbacks.size(); i++) {
found |= (_ws_on_receive_callbacks[i])(key.c_str(), value);
// TODO: remove this to call all OnReceiveCallbacks with the
// current key/value
if (found) break;
}
if (!found) {
delSetting(key);
continue;
}
// Store values
if (value.is<JsonArray&>()) {
if (_wsStore(key, value.as<JsonArray&>())) changed = true;
} else {
if (_wsStore(key, value.as<String>())) changed = true;
}
// Update flags if value has changed
if (changed) {
save = true;
#if MQTT_SUPPORT
if (key.startsWith("mqtt")) changedMQTT = true;
#endif
}
}
// Save settings
if (save) {
// Callbacks
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
// This should got to callback as well
// but first change management has to be in place
#if MQTT_SUPPORT
if (changedMQTT) mqttReset();
#endif
// Persist settings
saveSettings();
wsSend_P(client_id, PSTR("{\"message\": 8}"));
} else {
wsSend_P(client_id, PSTR("{\"message\": 9}"));
}
}
}
void _wsUpdate(JsonObject& root) {
root["heap"] = getFreeHeap();
root["uptime"] = getUptime();
root["rssi"] = WiFi.RSSI();
root["loadaverage"] = systemLoadAverage();
root["vcc"] = ESP.getVcc();
#if NTP_SUPPORT
if (ntpSynced()) root["now"] = now();
#endif
}
bool _wsOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "ws", 2) == 0) return true;
if (strncmp(key, "admin", 5) == 0) return true;
if (strncmp(key, "hostname", 8) == 0) return true;
if (strncmp(key, "webPort", 7) == 0) return true;
return false;
}
void _wsOnStart(JsonObject& root) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
String adminPass = getSetting("adminPass", ADMIN_PASS);
bool changePassword = adminPass.equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif
if (changePassword) {
root["webMode"] = WEB_MODE_PASSWORD;
} else {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
root["app_revision"] = APP_REVISION;
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
root["sdk"] = ESP.getSdkVersion();
root["core"] = getCoreVersion();
_wsUpdate(root);
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
#if TERMINAL_SUPPORT
root["cmdVisible"] = 1;
#endif
}
}
void _wsStart(uint32_t client_id) {
for (unsigned char i = 0; i < _ws_on_send_callbacks.size(); i++) {
wsSend(client_id, _ws_on_send_callbacks[i]);
}
}
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) {
IPAddress ip = client->remoteIP();
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id());
client->_tempObject = new WebSocketIncommingBuffer(&_wsParse, true);
wifiReconnectCheck();
} else if(type == WS_EVT_DISCONNECT) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u disconnected\n"), client->id());
if (client->_tempObject) {
delete (WebSocketIncommingBuffer *) client->_tempObject;
}
wifiReconnectCheck();
} else if(type == WS_EVT_ERROR) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u error(%u): %s\n"), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u pong(%u): %s\n"), client->id(), len, len ? (char*) data : "");
} else if(type == WS_EVT_DATA) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u data(%u): %s\n"), client->id(), len, len ? (char*) data : "");
WebSocketIncommingBuffer *buffer = (WebSocketIncommingBuffer *)client->_tempObject;
AwsFrameInfo * info = (AwsFrameInfo*)arg;
buffer->data_event(client, info, data, len);
}
}
void _wsLoop() {
static unsigned long last = 0;
if (!wsConnected()) return;
if (millis() - last > WS_UPDATE_INTERVAL) {
last = millis();
wsSend(_wsUpdate);
}
}
// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------
bool wsConnected() {
return (_ws.count() > 0);
}
void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback);
}
void wsOnReceiveRegister(ws_on_receive_callback_f callback) {
_ws_on_receive_callbacks.push_back(callback);
}
void wsOnActionRegister(ws_on_action_callback_f callback) {
_ws_on_action_callbacks.push_back(callback);
}
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback) {
_ws_on_after_parse_callbacks.push_back(callback);
}
void wsSend(ws_on_send_callback_f callback) {
if (_ws.count() > 0) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
callback(root);
String output;
root.printTo(output);
_ws.textAll((char *) output.c_str());
}
}
void wsSend(const char * payload) {
if (_ws.count() > 0) {
_ws.textAll(payload);
}
}
void wsSend_P(PGM_P payload) {
if (_ws.count() > 0) {
char buffer[strlen_P(payload)];
strcpy_P(buffer, payload);
_ws.textAll(buffer);
}
}
void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
callback(root);
String output;
root.printTo(output);
_ws.text(client_id, (char *) output.c_str());
}
void wsSend(uint32_t client_id, const char * payload) {
_ws.text(client_id, payload);
}
void wsSend_P(uint32_t client_id, PGM_P payload) {
char buffer[strlen_P(payload)];
strcpy_P(buffer, payload);
_ws.text(client_id, buffer);
}
void wsConfigure() {
#if USE_PASSWORD
bool auth = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
if (auth) {
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
} else {
_ws.setAuthentication("", "");
}
#endif
}
void wsSetup() {
_ws.onEvent(_wsEvent);
wsConfigure();
webServer()->addHandler(&_ws);
#if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback);
#endif
wsOnSendRegister(_wsOnStart);
wsOnReceiveRegister(_wsOnReceive);
wsOnAfterParseRegister(wsConfigure);
espurnaRegisterLoop(_wsLoop);
}
#endif // WEB_SUPPORT

68
extra_scripts.py Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
from subprocess import call
import os
import time
Import("env")
# ------------------------------------------------------------------------------
# Utils
# ------------------------------------------------------------------------------
class Color(object):
BLACK = '\x1b[1;30m'
RED = '\x1b[1;31m'
GREEN = '\x1b[1;32m'
YELLOW = '\x1b[1;33m'
BLUE = '\x1b[1;34m'
MAGENTA = '\x1b[1;35m'
CYAN = '\x1b[1;36m'
WHITE = '\x1b[1;37m'
LIGHT_GREY = '\x1b[0;30m'
LIGHT_RED = '\x1b[0;31m'
LIGHT_GREEN = '\x1b[0;32m'
LIGHT_YELLOW = '\x1b[0;33m'
LIGHT_BLUE = '\x1b[0;34m'
LIGHT_MAGENTA = '\x1b[0;35m'
LIGHT_CYAN = '\x1b[0;36m'
LIGHT_WHITE = '\x1b[0;37m'
def clr(color, text):
return color + str(text) + '\x1b[0m'
# ------------------------------------------------------------------------------
# Callbacks
# ------------------------------------------------------------------------------
def remove_float_support():
flags = " ".join(env['LINKFLAGS'])
flags = flags.replace("-u _printf_float", "")
flags = flags.replace("-u _scanf_float", "")
newflags = flags.split()
env.Replace(
LINKFLAGS = newflags
)
def cpp_check(source, target, env):
print("Started cppcheck...\n")
call(["cppcheck", os.getcwd()+"/espurna", "--force", "--enable=all"])
print("Finished cppcheck...\n")
def check_size(source, target, env):
time.sleep(2)
size = target[0].get_size()
print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size)
#if size > 512000:
# print clr(Color.LIGHT_RED, "File too large for OTA!")
# Exit(1)
# ------------------------------------------------------------------------------
# Hooks
# ------------------------------------------------------------------------------
remove_float_support()
#env.AddPreAction("buildprog", cpp_check)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size)

134
gulpfile.js Executable file
View File

@ -0,0 +1,134 @@
/*
ESP8266 file system builder
Copyright (C) 2016-2018 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/>.
*/
/*eslint quotes: ["error", "single"]*/
/*eslint-env es6*/
// -----------------------------------------------------------------------------
// File system builder
// -----------------------------------------------------------------------------
const fs = require('fs');
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const uglify = require('gulp-uglify');
const gzip = require('gulp-gzip');
const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon');
const htmllint = require('gulp-htmllint');
const log = require('fancy-log');
const csslint = require('gulp-csslint');
const crass = require('gulp-crass');
const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/';
var toHeader = function(filename) {
var source = dataFolder + filename;
var destination = staticFolder + filename + '.h';
var safename = filename.split('.').join('_');
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
log.error(err);
});
var data = fs.readFileSync(source);
wstream.write('#define ' + safename + '_len ' + data.length + '\n');
wstream.write('const uint8_t ' + safename + '[] PROGMEM = {');
for (var i=0; i<data.length; i++) {
if (0 === (i % 20)) {
wstream.write('\n');
}
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < (data.length - 1)) {
wstream.write(',');
}
}
wstream.write('\n};');
wstream.end();
};
var htmllintReporter = function(filepath, issues) {
if (issues.length > 0) {
issues.forEach(function (issue) {
log.info(
'[gulp-htmllint] ' +
filepath + ' [' +
issue.line + ',' +
issue.column + ']: ' +
'(' + issue.code + ') ' +
issue.msg
);
});
process.exitCode = 1;
}
};
gulp.task('build_certs', function() {
toHeader('server.cer');
toHeader('server.key');
});
gulp.task('csslint', function() {
gulp.src('html/*.css').
pipe(csslint({ids: false})).
pipe(csslint.formatter());
});
gulp.task('buildfs_embeded', ['buildfs_inline'], function() {
toHeader('index.html.gz');
});
gulp.task('buildfs_inline', function() {
return gulp.src('html/*.html').
pipe(htmllint({
'failOnError': true,
'rules': {
'id-class-style': false,
'label-req-for': false,
}
}, htmllintReporter)).
pipe(favicon()).
pipe(inline({
base: 'html/',
js: [uglify],
css: [crass, inlineImages],
disabledTypes: ['svg', 'img']
})).
pipe(htmlmin({
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true
})).
pipe(gzip()).
pipe(gulp.dest(dataFolder));
});
gulp.task('default', ['buildfs_embeded']);

320
html/custom.css Executable file
View File

@ -0,0 +1,320 @@
/* -----------------------------------------------------------------------------
General
-------------------------------------------------------------------------- */
#menu .pure-menu-heading {
font-size: 100%;
padding: .5em .5em;
white-space: normal;
}
.pure-g {
margin-bottom: 0;
}
.pure-form legend {
font-weight: bold;
letter-spacing: 0;
margin: 10px 0 1em 0;
}
.pure-form .pure-g > label {
margin: .4em 0 .2em;
}
.pure-form input {
margin-bottom: 10px;
}
.pure-form input[type=text][disabled] {
color: #777777;
}
@media screen and (max-width: 32em) {
.header > h1 {
line-height: 100%;
font-size: 2em;
}
}
h2 {
font-size: 1em;
}
.panel {
display: none;
}
.block {
display: block;
}
.content {
margin: 0;
}
.page {
margin-top: 10px;
}
.hint {
color: #ccc;
font-size: 80%;
margin: -10px 0 10px 0;
}
.hint a {
color:inherit;
}
legend.module,
.module {
display: none;
}
.template {
display: none;
}
input[name=upgrade] {
display: none;
}
select {
margin-bottom: 10px;
width: 100%;
}
input.center {
margin-bottom: 0;
}
div.center {
margin: .5em 0 1em;
}
.webmode {
display: none;
}
#credentials {
font-size: 200%;
height: 100px;
left: 50%;
margin-left: -200px;
margin-top: -50px;
position: fixed;
text-align: center;
top: 50%;
width: 400px;
}
div.state {
border-top: 1px solid #eee;
margin-top: 20px;
padding-top: 30px;
}
.state div {
font-size: 80%;
}
.state span {
font-size: 80%;
font-weight: bold;
}
.right {
text-align: right;
}
.pure-g span.terminal,
.pure-g textarea.terminal {
font-family: 'Courier New', monospace;
font-size: 80%;
line-height: 100%;
background-color: #000;
color: #0F0;
}
/* -----------------------------------------------------------------------------
Buttons
-------------------------------------------------------------------------- */
.pure-button {
border-radius: 4px;
color: white;
letter-spacing: 0;
margin-bottom: 10px;
padding: 8px 8px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.main-buttons {
margin: 20px auto;
text-align: center;
}
.main-buttons button {
width: 100px;
}
.button-reboot,
.button-reconnect,
.button-ha-del,
.button-rfb-forget,
.button-del-network,
.button-del-schedule,
.button-dbg-clear,
.button-upgrade,
.button-settings-factory {
background: rgb(192, 0, 0); /* redish */
}
.button-update,
.button-update-password,
.button-add-network,
.button-rfb-learn,
.button-upgrade-browse,
.button-ha-add,
.button-ha-config,
.button-settings-backup,
.button-settings-restore,
.button-dbgcmd,
.button-apikey {
background: rgb(0, 192, 0); /* green */
}
.button-add-switch-schedule,
.button-add-light-schedule {
background: rgb(0, 192, 0); /* green */
display: none;
}
.button-more-network,
.button-more-schedule,
.button-wifi-scan,
.button-rfb-send {
background: rgb(255, 128, 0); /* orange */
}
.button-upgrade-browse,
.button-dbgcmd,
.button-ha-add,
.button-apikey,
.button-upgrade {
margin-left: 5px;
}
/* -----------------------------------------------------------------------------
Sliders
-------------------------------------------------------------------------- */
input.slider {
margin-top: 10px;
}
span.slider {
font-size: 70%;
letter-spacing: 0;
margin-left: 10px;
margin-top: 7px;
}
/* -----------------------------------------------------------------------------
Loading
-------------------------------------------------------------------------- */
.loading {
background-image: url('images/loading.gif');
display: none;
height: 20px;
margin: 8px 0 0 10px;
width: 20px;
}
/* -----------------------------------------------------------------------------
Menu
-------------------------------------------------------------------------- */
#menu .small {
font-size: 60%;
padding-left: 9px;
}
#menu div.footer {
color: #999;
font-size: 80%;
padding: 10px;
}
#menu div.footer a {
padding: 0;
text-decoration: none;
}
/* -----------------------------------------------------------------------------
RF Bridge panel
-------------------------------------------------------------------------- */
#panel-rfb fieldset {
margin: 10px 2px;
padding: 20px;
}
#panel-rfb input {
margin-right: 5px;
}
#panel-rfb label {
padding-top: 5px;
}
#panel-rfb input {
text-align: center;
}
/* -----------------------------------------------------------------------------
Admin panel
-------------------------------------------------------------------------- */
#upgrade-progress {
display: none;
height: 20px;
margin-top: 10px;
width: 100%;
}
#uploader,
#downloader {
display: none;
}
/* -----------------------------------------------------------------------------
Wifi panel
-------------------------------------------------------------------------- */
#networks .pure-g,
#schedules .pure-g {
border-bottom: 1px solid #eee;
margin-bottom: 10px;
padding: 10px 0 10px 0;
}
#networks .more {
display: none;
}
#haConfig,
#scanResult {
margin-top: 10px;
display: none;
padding: 10px;
}
/* -----------------------------------------------------------------------------
Logs
-------------------------------------------------------------------------- */
#weblog {
height: 400px;
margin-bottom: 10px;
}

1363
html/custom.js Executable file

File diff suppressed because it is too large Load Diff

BIN
html/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
html/images/loading.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

1425
html/index.html Executable file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More