Initial
This commit is contained in:
commit
aece7ca1b0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
BIN
.pioenvs Alias
Normal file
BIN
.pioenvs Alias
Normal file
Binary file not shown.
67
.travis.yml
Normal file
67
.travis.yml
Normal 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
|
18
astyle.conf
Executable file
18
astyle.conf
Executable 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
72
build.sh
Executable 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
75
debug.sh
Executable 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
18
esp8266.flash.1m0.ld
Executable 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
95
espurna/alexa.ino
Executable 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
197
espurna/api.ino
Executable 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
32
espurna/broker.ino
Executable 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
258
espurna/button.ino
Executable 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
36
espurna/config/all.h
Executable 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
133
espurna/config/arduino.h
Executable 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
5
espurna/config/build.h
Executable 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
423
espurna/config/defaults.h
Executable 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
1041
espurna/config/general.h
Executable file
File diff suppressed because it is too large
Load Diff
2023
espurna/config/hardware.h
Executable file
2023
espurna/config/hardware.h
Executable file
File diff suppressed because it is too large
Load Diff
118
espurna/config/prototypes.h
Executable file
118
espurna/config/prototypes.h
Executable 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
796
espurna/config/sensors.h
Executable 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
6
espurna/config/version.h
Executable 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
BIN
espurna/data/index.html.gz
Executable file
Binary file not shown.
BIN
espurna/data/server.cer
Executable file
BIN
espurna/data/server.cer
Executable file
Binary file not shown.
BIN
espurna/data/server.key
Executable file
BIN
espurna/data/server.key
Executable file
Binary file not shown.
268
espurna/debug.ino
Executable file
268
espurna/debug.ino
Executable 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("\"", """);
|
||||
m.replace("{", "{");
|
||||
m.replace("}", "}");
|
||||
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
161
espurna/domoticz.ino
Executable 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
177
espurna/espurna.ino
Executable 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
25
espurna/filters/BaseFilter.h
Executable 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
40
espurna/filters/MaxFilter.h
Executable 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
92
espurna/filters/MedianFilter.h
Executable 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
|
51
espurna/filters/MovingAverageFilter.h
Executable file
51
espurna/filters/MovingAverageFilter.h
Executable 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
39
espurna/gpio.ino
Executable 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
297
espurna/homeassistant.ino
Executable 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(" ", " ");
|
||||
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
379
espurna/i2c.ino
Executable 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
111
espurna/influxdb.ino
Executable 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
112
espurna/ir.ino
Executable 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
290
espurna/led.ino
Executable 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
24
espurna/libs/EmbedisWrap.h
Executable 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
85
espurna/libs/StreamInjector.h
Executable 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;
|
||||
|
||||
|
||||
};
|
87
espurna/libs/WebSocketIncommingBuffer.h
Executable file
87
espurna/libs/WebSocketIncommingBuffer.h
Executable 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
33
espurna/libs/pwm.h
Executable 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
1054
espurna/light.ino
Executable file
File diff suppressed because it is too large
Load Diff
18
espurna/llmnr.ino
Executable file
18
espurna/llmnr.ino
Executable 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
132
espurna/mdns.ino
Executable 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
894
espurna/migrate.ino
Executable 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
843
espurna/mqtt.ino
Executable 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
20
espurna/netbios.ino
Executable 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
180
espurna/nofuss.ino
Executable 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
179
espurna/ntp.ino
Executable 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
249
espurna/ota.ino
Executable 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
447
espurna/pwm.c
Executable 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
886
espurna/relay.ino
Executable 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
192
espurna/rf.ino
Executable 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
534
espurna/rfbridge.ino
Executable 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
217
espurna/scheduler.ino
Executable 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
1065
espurna/sensor.ino
Executable file
File diff suppressed because it is too large
Load Diff
193
espurna/sensors/AM2320Sensor.h
Executable file
193
espurna/sensors/AM2320Sensor.h
Executable 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
66
espurna/sensors/AnalogSensor.h
Executable 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
135
espurna/sensors/BH1750Sensor.h
Executable 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
438
espurna/sensors/BMX280Sensor.h
Executable 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
101
espurna/sensors/BaseSensor.h
Executable 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
372
espurna/sensors/CSE7766Sensor.h
Executable 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
245
espurna/sensors/DHTSensor.h
Executable 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
319
espurna/sensors/DallasSensor.h
Executable 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
106
espurna/sensors/DigitalSensor.h
Executable 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
349
espurna/sensors/ECH1560Sensor.h
Executable 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
|
150
espurna/sensors/EmonADC121Sensor.h
Executable file
150
espurna/sensors/EmonADC121Sensor.h
Executable 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
|
347
espurna/sensors/EmonADS1X15Sensor.h
Executable file
347
espurna/sensors/EmonADS1X15Sensor.h
Executable 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
|
124
espurna/sensors/EmonAnalogSensor.h
Executable file
124
espurna/sensors/EmonAnalogSensor.h
Executable 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
242
espurna/sensors/EmonSensor.h
Executable 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
211
espurna/sensors/EventSensor.h
Executable 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
170
espurna/sensors/GUVAS12SDSensor.h
Executable 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
328
espurna/sensors/HLW8012Sensor.h
Executable 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
95
espurna/sensors/I2CSensor.h
Executable 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
221
espurna/sensors/MHZ19Sensor.h
Executable 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
152
espurna/sensors/PMSX003Sensor.h
Executable 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
136
espurna/sensors/PZEM004TSensor.h
Executable 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
|
89
espurna/sensors/SHT3XI2CSensor.h
Executable file
89
espurna/sensors/SHT3XI2CSensor.h
Executable 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
168
espurna/sensors/SI7021Sensor.h
Executable 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
259
espurna/sensors/V9261FSensor.h
Executable 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
479
espurna/settings.ino
Executable 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
83
espurna/ssdp.ino
Executable 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
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
33
espurna/static/server.cer.h
Executable 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
34
espurna/static/server.key.h
Executable 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
168
espurna/system.ino
Executable 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
187
espurna/telnet.ino
Executable 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
281
espurna/thinkspeak.ino
Executable 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
103
espurna/uartmqtt.ino
Executable 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
527
espurna/utils.ino
Executable 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
341
espurna/web.ino
Executable 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
454
espurna/wifi.ino
Executable 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
448
espurna/ws.ino
Executable 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
68
extra_scripts.py
Executable 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
134
gulpfile.js
Executable 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
320
html/custom.css
Executable 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
1363
html/custom.js
Executable file
File diff suppressed because it is too large
Load Diff
BIN
html/favicon.ico
Executable file
BIN
html/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
html/images/loading.gif
Executable file
BIN
html/images/loading.gif
Executable file
Binary file not shown.
After Width: | Height: | Size: 433 B |
1425
html/index.html
Executable file
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
Loading…
Reference in New Issue
Block a user