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…
x
Reference in New Issue
Block a user