@ -0,0 +1,41 @@ | |||||
# Number of days of inactivity before an Issue or Pull Request becomes stale | |||||
daysUntilStale: 120 | |||||
# Number of days of inactivity before a stale Issue or Pull Request is closed. | |||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. | |||||
daysUntilClose: 30 | |||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable | |||||
exemptLabels: | |||||
- enhancement | |||||
- bug | |||||
- staged for release | |||||
# Set to true to ignore issues in a project (defaults to false) | |||||
exemptProjects: false | |||||
# Set to true to ignore issues in a milestone (defaults to false) | |||||
exemptMilestones: false | |||||
# Label to use when marking as stale | |||||
staleLabel: stale | |||||
# Comment to post when marking as stale. Set to `false` to disable | |||||
markComment: > | |||||
This issue has been automatically marked as stale because it has not had | |||||
recent activity. It will be closed in 30 days if no further activity occurs. | |||||
Thank you for your contributions. | |||||
# Comment to post when removing the stale label. | |||||
# unmarkComment: > | |||||
# Your comment here. | |||||
# Comment to post when closing a stale Issue or Pull Request. | |||||
closeComment: > | |||||
This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem. | |||||
# Limit the number of actions per hour, from 1-30. Default is 30 | |||||
limitPerRun: 30 | |||||
# Limit to only `issues` or `pulls` | |||||
only: issues |
@ -0,0 +1,30 @@ | |||||
language: python | |||||
python: | |||||
- '2.7' | |||||
sudo: false | |||||
cache: | |||||
directories: | |||||
- "~/.platformio" | |||||
install: | |||||
- pip install -U platformio | |||||
- cd code ; npm install --only=dev ; cd .. | |||||
script: | |||||
- cd code && ./build.sh && cd .. | |||||
before_deploy: | |||||
- mv firmware/*/espurna-*.bin firmware/ | |||||
deploy: | |||||
provider: releases | |||||
api_key: | |||||
secure: LMCdaQnCxSQ5EuKhqcFR6VTfDCWc06jwD4fdHfkmBaeWBMMzdoZEqN26AwdTnoLLlQJTR9l21NypgGybssBr69Md/ZinMahWCJJ4gVzPe9Adr9ijRbzj/wckirLBVZjRWn8fxTjJgjpu1ten2CgBfNcc/roN3fI6DV/1Fvv1REfihND4EeIermsxIVRXOyluu1vnPV9ZM28XBNch9XfkKwIpLEaHNNtkzlSEua39U34WpZShrxxEVoZhiF/R1ZF+NiAnursPcPsn5hdrXyHFLFT2dVDnlpdIp79c6SGkCZ636//5erB6hgBHshbwuT9TYXxtwLyL7AeN/MfCg0gtZSsDII8mLKzytW1tl7r1W9l7s21z/55tljSz2Z6dcbJC3bxucgcLxM9R6PrsPdDrPQTd0QwYqYalMNlfuA8KGcesZueqI9Q4uw7uHFUwIp7FIaoKhwhjTKe/ZHZ75zU4wdBTXof4dnSQOudoGGjRyyj7V0eR/Mhuni45N2Bldy9hDaldgtWMuEdx3ABW1IrPXtPDVTXM6QjUHm0nj5zzCgoCvQEw3jycA7Wlgbzo8MQkKH3u4MtupJcEGHJAqMbk+JlRpIfdWTiqtI697CReRGYSWT6YxF68TeJo77JPPph724BIBMtKtIk2+4RkHxz5RqM8O2vt+AFNjnWNIvVQtY8= | |||||
file_glob: true | |||||
file: firmware/espurna-*.bin | |||||
skip_cleanup: true | |||||
on: | |||||
all_branches: true | |||||
tags: true | |||||
notifications: | |||||
pushover: | |||||
api_key: | |||||
secure: wtAleSibUqDOH7KskYMIFXwNqqZMA/2flCJzu835hSFWDFTjdXGeYLmQxCI7FGTWHcvWgf5pt3KR3OXtABVDfwqLj0HZjEubIypm56mZKsEaP32JlQiAw+Qz1KSU/5gEKEI3NU5MuL1rIebSEo9iVgwVDMn51GI9qv0LbjLKfXpxBmJXL/OmBRggVMswScCppXJLVlyMSRj9qT0Ds/lz+SL1g6vZHPO8r3z1BSOMA0eWNUYd/Huhe1XAHYzS4mS3aybPsPKldpv0igsvE5BHT37rb43QA0pHxv2M09QlBRNBI6kHgxzCw2OJ4uz5/K2U/4LBMDJrAj6rUGAhKn8Wuiw4B/kZb+WsvRCcCTHGYTc20KWrrV4aOojQRLht1FgNg7Ub+NNm5T9LBG+XKKHARTv1Kv09nCpPVD8NvF5YPRsSb7ZL6s/wUGSNuu365aVs3BnnazXb+ha33Hg0bCBEwmlW6FKdQ7S8OYsoIobTUcporRs8p089EQ8vNxN0osHnKPX5M0ZOxbBOIt5fQO2B+Cdn+m4hx/DETM5HMLZ/GpsbY9eiN7HWaaQPicSrSLprY6tzfvcHePk9Y6t8rjCKzehwlYtrfiMRvrPhZuOLcB4s3OmJngxvOCGxdWkh/6F2CY6sDslyviaK0ZwX93Gn7uNThAviFpZNUlPWP6jHIkA= | |||||
users: | |||||
secure: atCRvGYPuAT1DH5UupNIV80sqrIgwdYrxO69tq+lIBfi3BIJ4LLlvUQwigWak6otfJbEqAQKVNi8I8J4g7ASHx1wn335vpAqLAbhJqfbLznAcU9nZ+bdc8+NJ57qY66ZZJNpljbMC0HWgw2kgyiCLJ1wS3zsQAyuzyGuMcmc43zFOQMA7QUaefE6LGlHPn0i6Ub04QgQ1413IEu2/FR5i4hVXrgRQzaPy07pSPbvFTvoxfWDgWjTQM+R2AG8uStesO+2yzeb8Nu2pJDvFf3R1N8P2e3zg5YN86DLNRQ+Kxl7M1FJ/txbJ/4D1jdNwAmtUzEoYnsKCcCnMHWGuSlJF8fgXSVBDi9KVH7Y6rlWXzMcqesR5lzNTlD7JQ01yw2WrO6Nj7fvan+QNGp3d5F4Kf3WE+5rkmM5Sbo5rb9YOGt688i1qJ3Xf1MTkQNCzDiAg3qf1hu8j+AALblY73gNSAgv4F+/SnmnfKy5Cz/oRQQfboiC/VphNa25Fzq3s3uahQfLha4tyHrc1LltSM6bztErRSPqDp96qVTQORVHr6jqJhl6c9R0XXrnc0Pc5lpBoPKOqug1yPp15k9AAssGxtXZqg2loHjwmS+Qm1i77mNGKNhzqDpmLHEzmIejc4n4lIZmze+dLucStiNnkN6TF3nvIh37CNjH6slT5t7WfJg= |
@ -1,8 +1,13 @@ | |||||
.clang_complete | .clang_complete | ||||
core_version.h | |||||
custom.h | |||||
.DS_Store | |||||
.gcc-flags.json | .gcc-flags.json | ||||
.pioenvs | .pioenvs | ||||
.piolibdeps | .piolibdeps | ||||
.python-version | |||||
.travis.yml | |||||
.vscode | |||||
.vscode/.browse.c_cpp.db* | |||||
.vscode/c_cpp_properties.json | .vscode/c_cpp_properties.json | ||||
core_version.h | |||||
.pioenvs | |||||
.piolibdeps | |||||
.vscode/launch.json |
@ -1,65 +0,0 @@ | |||||
# 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/en/latest/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/en/latest/ci/travis.html > | |||||
# | |||||
# * User Guide for `platformio ci` command | |||||
# < http://docs.platformio.org/en/latest/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 | |||||
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 | |||||
# | |||||
# script: | |||||
# - platformio ci --lib="." --board=TYPE_1 --board=TYPE_2 --board=TYPE_N |
@ -1,51 +0,0 @@ | |||||
#!/bin/python | |||||
import json | |||||
import commands | |||||
import subprocess | |||||
import os | |||||
import sys | |||||
def core_version(env): | |||||
# Get the core folder | |||||
fwdir = env["FRAMEWORK_ARDUINOESP8266_DIR"] | |||||
# Get the core version | |||||
with open(fwdir + '/package.json') as data_file: | |||||
data = json.load(data_file) | |||||
core_version = data["version"].upper().replace(".", "_").replace("-", "_") | |||||
print "CORE VERSION: %s" % core_version | |||||
# Get git version | |||||
pr = subprocess.Popen( | |||||
"git --git-dir .git rev-parse --short=8 HEAD 2>/dev/null || echo ffffffff", | |||||
cwd = fwdir, | |||||
shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE ) | |||||
(out, error) = pr.communicate() | |||||
git_version = str(out).replace('\n', "") | |||||
print "GIT VERSION: %s" % git_version | |||||
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE=" + core_version) | |||||
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE_" + core_version) | |||||
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_GIT_VER=" + git_version) | |||||
with open('espurna/config/core_version.h', 'w') as the_file: | |||||
the_file.write('#define ARDUINO_ESP8266_RELEASE "%s"\n' % core_version) | |||||
the_file.write('#define ARDUINO_ESP8266_RELEASE_%s\n' % core_version) | |||||
the_file.write('#define ARDUINO_ESP8266_GIT_VER "%s"\n' % git_version) | |||||
#env.Append( | |||||
# CFLAGS = [ | |||||
# str("-DARDUINO_ESP8266_RELEASE=" + core_version), | |||||
# str("-DARDUINO_ESP8266_RELEASE_" + core_version), | |||||
# str("-DARDUINO_ESP8266_GIT_VER=" + git_version) | |||||
# ] | |||||
#) | |||||
#print " -DARDUINO_ESP8266_RELEASE=" + core_version + | |||||
# " -DARDUINO_ESP8266_RELEASE_" + core_version + | |||||
# " -DARDUINO_ESP8266_GIT_VER=" + git_version | |||||
Import('env') | |||||
core_version(env) |
@ -0,0 +1,19 @@ | |||||
/* Flash Split for 1M chips, no SPIFFS */ | |||||
/* sketch 995KB */ | |||||
/* eeprom 8KB */ | |||||
/* reserved 16KB */ | |||||
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 = 0xf8ff0 | |||||
} | |||||
PROVIDE ( _SPIFFS_start = 0x402FA000 ); | |||||
PROVIDE ( _SPIFFS_end = 0x402FA000 ); | |||||
PROVIDE ( _SPIFFS_page = 0 ); | |||||
PROVIDE ( _SPIFFS_block = 0 ); | |||||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -0,0 +1,21 @@ | |||||
/* Flash Split for 4M chips */ | |||||
/* sketch 1019KB */ | |||||
/* empty/ota? 2048KB */ | |||||
/* spiffs 992KB */ | |||||
/* eeprom 16KB */ | |||||
/* reserved 16KB */ | |||||
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 = 0xfeff0 | |||||
} | |||||
PROVIDE ( _SPIFFS_start = 0x40500000 ); | |||||
PROVIDE ( _SPIFFS_end = 0x405F8000 ); | |||||
PROVIDE ( _SPIFFS_page = 0x100 ); | |||||
PROVIDE ( _SPIFFS_block = 0x2000 ); | |||||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -0,0 +1,20 @@ | |||||
/* Flash Split for 4M chips */ | |||||
/* sketch 1019KB */ | |||||
/* spiffs 3040KB */ | |||||
/* eeprom 16KB */ | |||||
/* reserved 16KB */ | |||||
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 = 0xfeff0 | |||||
} | |||||
PROVIDE ( _SPIFFS_start = 0x40300000 ); | |||||
PROVIDE ( _SPIFFS_end = 0x405F8000 ); | |||||
PROVIDE ( _SPIFFS_page = 0x100 ); | |||||
PROVIDE ( _SPIFFS_block = 0x2000 ); | |||||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -0,0 +1,18 @@ | |||||
/* Flash Split for 512K chips */ | |||||
/* sketch 487KB */ | |||||
/* 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 = 0x79ff0 | |||||
} | |||||
PROVIDE ( _SPIFFS_start = 0x4027B000 ); | |||||
PROVIDE ( _SPIFFS_end = 0x4027B000 ); | |||||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -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 |
@ -1,26 +1,40 @@ | |||||
/* | |||||
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 "version.h" | ||||
#include "types.h" | |||||
#include "arduino.h" | #include "arduino.h" | ||||
#include "hardware.h" | #include "hardware.h" | ||||
#include "defaults.h" | #include "defaults.h" | ||||
#include "general.h" | #include "general.h" | ||||
#include "prototypes.h" | #include "prototypes.h" | ||||
#include "sensors.h" | #include "sensors.h" | ||||
#include "dependencies.h" | |||||
#include "progmem.h" | |||||
#include "debug.h" | |||||
#ifdef USE_CORE_VERSION_H | #ifdef USE_CORE_VERSION_H | ||||
#include "core_version.h" | #include "core_version.h" | ||||
#endif | #endif | ||||
/* | |||||
If you want to modify the stock configuration but you don't want to touch | |||||
the repo files you can either define USE_CUSTOM_H or remove the | |||||
"#ifdef USE_CUSTOM_H" & "#endif" lines and add a "custom.h" | |||||
file to this same folder. | |||||
Check https://bitbucket.org/xoseperez/espurna/issues/104/general_customh | |||||
for an example on how to use this file. | |||||
(Define USE_CUSTOM_H on commandline for platformio: | |||||
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" ) | |||||
*/ | |||||
#ifdef USE_CUSTOM_H | |||||
#include "custom.h" | |||||
#endif |
@ -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 "" |
@ -0,0 +1,17 @@ | |||||
#pragma once | |||||
// ----------------------------------------------------------------------------- | |||||
// Debug | |||||
// ----------------------------------------------------------------------------- | |||||
#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT || DEBUG_WEB_SUPPORT | |||||
#if DEBUG_SUPPORT | |||||
#define DEBUG_MSG(...) debugSend(__VA_ARGS__) | |||||
#define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__) | |||||
#endif | |||||
#ifndef DEBUG_MSG | |||||
#define DEBUG_MSG(...) | |||||
#define DEBUG_MSG_P(...) | |||||
#endif |
@ -0,0 +1,50 @@ | |||||
#pragma once | |||||
//------------------------------------------------------------------------------ | |||||
// Do not change this file unless you know what you are doing | |||||
// Configuration settings are in the general.h file | |||||
//------------------------------------------------------------------------------ | |||||
#if DEBUG_TELNET_SUPPORT | |||||
#undef TELNET_SUPPORT | |||||
#define TELNET_SUPPORT 1 | |||||
#endif | |||||
#if not WEB_SUPPORT | |||||
#undef DEBUG_WEB_SUPPORT | |||||
#define DEBUG_WEB_SUPPORT 0 | |||||
#endif | |||||
#if not WEB_SUPPORT | |||||
#undef SSDP_SUPPORT | |||||
#define SSDP_SUPPORT 0 // SSDP support requires web support | |||||
#endif | |||||
#if UART_MQTT_SUPPORT | |||||
#define MQTT_SUPPORT 1 | |||||
#undef TERMINAL_SUPPORT | |||||
#define TERMINAL_SUPPORT 0 | |||||
#undef DEBUG_SERIAL_SUPPORT | |||||
#define DEBUG_SERIAL_SUPPORT 0 | |||||
#endif | |||||
#if DOMOTICZ_SUPPORT | |||||
#undef MQTT_SUPPORT | |||||
#define MQTT_SUPPORT 1 // If Domoticz enabled enable MQTT | |||||
#endif | |||||
#if HOMEASSISTANT_SUPPORT | |||||
#undef MQTT_SUPPORT | |||||
#define MQTT_SUPPORT 1 // If Home Assistant enabled enable MQTT | |||||
#endif | |||||
#ifndef ASYNC_TCP_SSL_ENABLED | |||||
#if THINGSPEAK_USE_SSL && THINGSPEAK_USE_ASYNC | |||||
#undef THINGSPEAK_SUPPORT // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED | |||||
#endif | |||||
#endif | |||||
#if SCHEDULER_SUPPORT | |||||
#undef NTP_SUPPORT | |||||
#define NTP_SUPPORT 1 // Scheduler needs NTP | |||||
#endif |
@ -0,0 +1,280 @@ | |||||
//-------------------------------------------------------------------------------- | |||||
// PROGMEM definitions | |||||
//-------------------------------------------------------------------------------- | |||||
//-------------------------------------------------------------------------------- | |||||
// Reset reasons | |||||
//-------------------------------------------------------------------------------- | |||||
PROGMEM const char custom_reset_hardware[] = "Hardware button"; | |||||
PROGMEM const char custom_reset_web[] = "Reboot from web interface"; | |||||
PROGMEM const char custom_reset_terminal[] = "Reboot from terminal"; | |||||
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT"; | |||||
PROGMEM const char custom_reset_rpc[] = "Reboot from RPC"; | |||||
PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update"; | |||||
PROGMEM const char custom_reset_http[] = "Reboot from HTTP"; | |||||
PROGMEM const char custom_reset_nofuss[] = "Reboot after successful NoFUSS update"; | |||||
PROGMEM const char custom_reset_upgrade[] = "Reboot after successful web update"; | |||||
PROGMEM const char custom_reset_factory[] = "Factory reset"; | |||||
PROGMEM const char* const custom_reset_string[] = { | |||||
custom_reset_hardware, custom_reset_web, custom_reset_terminal, | |||||
custom_reset_mqtt, custom_reset_rpc, custom_reset_ota, | |||||
custom_reset_http, custom_reset_nofuss, custom_reset_upgrade, | |||||
custom_reset_factory | |||||
}; | |||||
//-------------------------------------------------------------------------------- | |||||
// Capabilities | |||||
//-------------------------------------------------------------------------------- | |||||
PROGMEM const char espurna_modules[] = | |||||
#if ALEXA_SUPPORT | |||||
"ALEXA " | |||||
#endif | |||||
#if BROKER_SUPPORT | |||||
"BROKER " | |||||
#endif | |||||
#if DEBUG_SERIAL_SUPPORT | |||||
"DEBUG_SERIAL " | |||||
#endif | |||||
#if DEBUG_TELNET_SUPPORT | |||||
"DEBUG_TELNET " | |||||
#endif | |||||
#if DEBUG_UDP_SUPPORT | |||||
"DEBUG_UDP " | |||||
#endif | |||||
#if DEBUG_WEB_SUPPORT | |||||
"DEBUG_WEB " | |||||
#endif | |||||
#if DOMOTICZ_SUPPORT | |||||
"DOMOTICZ " | |||||
#endif | |||||
#if HOMEASSISTANT_SUPPORT | |||||
"HOMEASSISTANT " | |||||
#endif | |||||
#if I2C_SUPPORT | |||||
"I2C " | |||||
#endif | |||||
#if INFLUXDB_SUPPORT | |||||
"INFLUXDB " | |||||
#endif | |||||
#if LLMNR_SUPPORT | |||||
"LLMNR " | |||||
#endif | |||||
#if MDNS_SERVER_SUPPORT | |||||
"MDNS_SERVER " | |||||
#endif | |||||
#if MDNS_CLIENT_SUPPORT | |||||
"MDNS_CLIENT " | |||||
#endif | |||||
#if MQTT_SUPPORT | |||||
"MQTT " | |||||
#endif | |||||
#if NETBIOS_SUPPORT | |||||
"NETBIOS " | |||||
#endif | |||||
#if NOFUSS_SUPPORT | |||||
"NOFUSS " | |||||
#endif | |||||
#if NTP_SUPPORT | |||||
"NTP " | |||||
#endif | |||||
#if RF_SUPPORT | |||||
"RF " | |||||
#endif | |||||
#if SCHEDULER_SUPPORT | |||||
"SCHEDULER " | |||||
#endif | |||||
#if SENSOR_SUPPORT | |||||
"SENSOR " | |||||
#endif | |||||
#if SPIFFS_SUPPORT | |||||
"SPIFFS " | |||||
#endif | |||||
#if SSDP_SUPPORT | |||||
"SSDP " | |||||
#endif | |||||
#if TELNET_SUPPORT | |||||
"TELNET " | |||||
#endif | |||||
#if TERMINAL_SUPPORT | |||||
"TERMINAL " | |||||
#endif | |||||
#if THINGSPEAK_SUPPORT | |||||
"THINGSPEAK " | |||||
#endif | |||||
#if UART_MQTT_SUPPORT | |||||
"UART_MQTT " | |||||
#endif | |||||
#if WEB_SUPPORT | |||||
"WEB " | |||||
#endif | |||||
""; | |||||
//-------------------------------------------------------------------------------- | |||||
// Sensors | |||||
//-------------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT | |||||
PROGMEM const char espurna_sensors[] = | |||||
#if AM2320_SUPPORT | |||||
"AM2320_I2C " | |||||
#endif | |||||
#if ANALOG_SUPPORT | |||||
"ANALOG " | |||||
#endif | |||||
#if BH1750_SUPPORT | |||||
"BH1750 " | |||||
#endif | |||||
#if BMX280_SUPPORT | |||||
"BMX280 " | |||||
#endif | |||||
#if CSE7766_SUPPORT | |||||
"CSE7766 " | |||||
#endif | |||||
#if DALLAS_SUPPORT | |||||
"DALLAS " | |||||
#endif | |||||
#if DHT_SUPPORT | |||||
"DHTXX " | |||||
#endif | |||||
#if DIGITAL_SUPPORT | |||||
"DIGITAL " | |||||
#endif | |||||
#if ECH1560_SUPPORT | |||||
"ECH1560 " | |||||
#endif | |||||
#if EMON_ADC121_SUPPORT | |||||
"EMON_ADC121 " | |||||
#endif | |||||
#if EMON_ADS1X15_SUPPORT | |||||
"EMON_ADX1X15 " | |||||
#endif | |||||
#if EMON_ANALOG_SUPPORT | |||||
"EMON_ANALOG " | |||||
#endif | |||||
#if EVENTS_SUPPORT | |||||
"EVENTS " | |||||
#endif | |||||
#if GEIGER_SUPPORT | |||||
"GEIGER " | |||||
#endif | |||||
#if GUVAS12SD_SUPPORT | |||||
"GUVAS12SD " | |||||
#endif | |||||
#if HCSR04_SUPPORT | |||||
"HCSR04 " | |||||
#endif | |||||
#if HLW8012_SUPPORT | |||||
"HLW8012 " | |||||
#endif | |||||
#if MHZ19_SUPPORT | |||||
"MHZ19 " | |||||
#endif | |||||
#if PMSX003_SUPPORT | |||||
"PMSX003 " | |||||
#endif | |||||
#if PZEM004T_SUPPORT | |||||
"PZEM004T " | |||||
#endif | |||||
#if SENSEAIR_SUPPORT | |||||
"SENSEAIR " | |||||
#endif | |||||
#if SHT3X_I2C_SUPPORT | |||||
"SHT3X_I2C " | |||||
#endif | |||||
#if SI7021_SUPPORT | |||||
"SI7021 " | |||||
#endif | |||||
#if TMP3X_SUPPORT | |||||
"TMP3X " | |||||
#endif | |||||
#if V9261F_SUPPORT | |||||
"V9261F " | |||||
#endif | |||||
""; | |||||
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, 3, 3, | |||||
4, 4 // Geiger Counter decimals | |||||
}; | |||||
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 magnitude_distance_topic[] = "distance"; | |||||
PROGMEM const char magnitude_hcho_topic[] = "hcho"; | |||||
PROGMEM const char magnitude_geiger_cpm_topic[] = "ldr_cpm"; // local dose rate [Counts per minute] | |||||
PROGMEM const char magnitude_geiger_sv_topic[] = "ldr_uSvh"; // local dose rate [µSievert per hour] | |||||
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, | |||||
magnitude_distance_topic, magnitude_hcho_topic, | |||||
magnitude_geiger_cpm_topic, magnitude_geiger_sv_topic // Geiger Counter topics | |||||
}; | |||||
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/m³"; | |||||
PROGMEM const char magnitude_ppm[] = "ppm"; | |||||
PROGMEM const char magnitude_lux[] = "lux"; | |||||
PROGMEM const char magnitude_uv[] = "uv"; | |||||
PROGMEM const char magnitude_distance[] = "m"; | |||||
PROGMEM const char magnitude_mgm3[] = "mg/m³"; | |||||
PROGMEM const char magnitude_geiger_cpm[] = "cpm"; // Counts per Minute: Unit of local dose rate (Geiger counting) | |||||
PROGMEM const char magnitude_geiger_sv[] = "µSv/h"; // µSievert per hour: 2nd unit of local dose rate (Geiger counting) | |||||
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, | |||||
magnitude_distance, magnitude_mgm3, | |||||
magnitude_geiger_cpm, magnitude_geiger_sv // Geiger counter units | |||||
}; | |||||
#endif |
@ -0,0 +1,303 @@ | |||||
//------------------------------------------------------------------------------ | |||||
// Type definitions | |||||
// Do not touch this definitions | |||||
//------------------------------------------------------------------------------ | |||||
// ----------------------------------------------------------------------------- | |||||
// WIFI | |||||
// ----------------------------------------------------------------------------- | |||||
#define WIFI_STATE_AP 1 | |||||
#define WIFI_STATE_STA 2 | |||||
#define WIFI_STATE_AP_STA 3 | |||||
#define WIFI_STATE_WPS 4 | |||||
#define WIFI_STATE_SMARTCONFIG 8 | |||||
#define WIFI_AP_ALLWAYS 1 | |||||
#define WIFI_AP_FALLBACK 2 | |||||
//------------------------------------------------------------------------------ | |||||
// BUTTONS | |||||
//------------------------------------------------------------------------------ | |||||
#define BUTTON_EVENT_NONE 0 | |||||
#define BUTTON_EVENT_PRESSED 1 | |||||
#define BUTTON_EVENT_RELEASED 2 | |||||
#define BUTTON_EVENT_CLICK 2 | |||||
#define BUTTON_EVENT_DBLCLICK 3 | |||||
#define BUTTON_EVENT_LNGCLICK 4 | |||||
#define BUTTON_EVENT_LNGLNGCLICK 5 | |||||
#define BUTTON_EVENT_TRIPLECLICK 6 | |||||
#define BUTTON_MODE_NONE 0 | |||||
#define BUTTON_MODE_TOGGLE 1 | |||||
#define BUTTON_MODE_ON 2 | |||||
#define BUTTON_MODE_OFF 3 | |||||
#define BUTTON_MODE_AP 4 | |||||
#define BUTTON_MODE_RESET 5 | |||||
#define BUTTON_MODE_PULSE 6 | |||||
#define BUTTON_MODE_FACTORY 7 | |||||
#define BUTTON_MODE_WPS 8 | |||||
#define BUTTON_MODE_SMART_CONFIG 9 | |||||
// 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 | |||||
//------------------------------------------------------------------------------ | |||||
#define RELAY_BOOT_OFF 0 | |||||
#define RELAY_BOOT_ON 1 | |||||
#define RELAY_BOOT_SAME 2 | |||||
#define RELAY_BOOT_TOGGLE 3 | |||||
#define RELAY_TYPE_NORMAL 0 | |||||
#define RELAY_TYPE_INVERSE 1 | |||||
#define RELAY_TYPE_LATCHED 2 | |||||
#define RELAY_TYPE_LATCHED_INVERSE 3 | |||||
#define RELAY_SYNC_ANY 0 | |||||
#define RELAY_SYNC_NONE_OR_ONE 1 | |||||
#define RELAY_SYNC_ONE 2 | |||||
#define RELAY_SYNC_SAME 3 | |||||
#define RELAY_PULSE_NONE 0 | |||||
#define RELAY_PULSE_OFF 1 | |||||
#define RELAY_PULSE_ON 2 | |||||
#define RELAY_PROVIDER_RELAY 0 | |||||
#define RELAY_PROVIDER_DUAL 1 | |||||
#define RELAY_PROVIDER_LIGHT 2 | |||||
#define RELAY_PROVIDER_RFBRIDGE 3 | |||||
#define RELAY_PROVIDER_STM 4 | |||||
//------------------------------------------------------------------------------ | |||||
// UDP SYSLOG | |||||
//------------------------------------------------------------------------------ | |||||
// Priority codes: | |||||
#define SYSLOG_EMERG 0 /* system is unusable */ | |||||
#define SYSLOG_ALERT 1 /* action must be taken immediately */ | |||||
#define SYSLOG_CRIT 2 /* critical conditions */ | |||||
#define SYSLOG_ERR 3 /* error conditions */ | |||||
#define SYSLOG_WARNING 4 /* warning conditions */ | |||||
#define SYSLOG_NOTICE 5 /* normal but significant condition */ | |||||
#define SYSLOG_INFO 6 /* informational */ | |||||
#define SYSLOG_DEBUG 7 /* debug-level messages */ | |||||
// Facility codes: | |||||
#define SYSLOG_KERN (0<<3) /* kernel messages */ | |||||
#define SYSLOG_USER (1<<3) /* random user-level messages */ | |||||
#define SYSLOG_MAIL (2<<3) /* mail system */ | |||||
#define SYSLOG_DAEMON (3<<3) /* system daemons */ | |||||
#define SYSLOG_AUTH (4<<3) /* security/authorization messages */ | |||||
#define SYSLOG_SYSLOG (5<<3) /* messages generated internally by syslogd */ | |||||
#define SYSLOG_LPR (6<<3) /* line printer subsystem */ | |||||
#define SYSLOG_NEWS (7<<3) /* network news subsystem */ | |||||
#define SYSLOG_UUCP (8<<3) /* UUCP subsystem */ | |||||
#define SYSLOG_CRON (9<<3) /* clock daemon */ | |||||
#define SYSLOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */ | |||||
#define SYSLOG_FTP (11<<3) /* ftp daemon */ | |||||
#define SYSLOG_LOCAL0 (16<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL1 (17<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL2 (18<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL3 (19<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL4 (20<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL5 (21<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL6 (22<<3) /* reserved for local use */ | |||||
#define SYSLOG_LOCAL7 (23<<3) /* reserved for local use */ | |||||
//------------------------------------------------------------------------------ | |||||
// MQTT | |||||
//------------------------------------------------------------------------------ | |||||
// Internal MQTT events | |||||
#define MQTT_CONNECT_EVENT 0 | |||||
#define MQTT_DISCONNECT_EVENT 1 | |||||
#define MQTT_MESSAGE_EVENT 2 | |||||
//------------------------------------------------------------------------------ | |||||
// LED | |||||
//------------------------------------------------------------------------------ | |||||
#define LED_MODE_MQTT 0 // LED will be managed from MQTT (OFF by default) | |||||
#define LED_MODE_WIFI 1 // LED will blink according to the WIFI status | |||||
#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED) | |||||
#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED) | |||||
#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF | |||||
#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME | |||||
#define LED_MODE_ON 6 // LED always ON | |||||
#define LED_MODE_OFF 7 // LED always OFF | |||||
#define LED_MODE_RELAY 8 // If any relay is ON, LED will be ON, otherwise OFF | |||||
#define LED_MODE_RELAY_WIFI 9 // A mixture between WIFI and RELAY, the reverse of MIXED | |||||
// ----------------------------------------------------------------------------- | |||||
// UI | |||||
// ----------------------------------------------------------------------------- | |||||
#define UI_TAG_INPUT 0 | |||||
#define UI_TAG_CHECKBOX 1 | |||||
#define UI_TAG_SELECT 2 | |||||
#define WEB_MODE_NORMAL 0 | |||||
#define WEB_MODE_PASSWORD 1 | |||||
// ----------------------------------------------------------------------------- | |||||
// LIGHT | |||||
// ----------------------------------------------------------------------------- | |||||
// Available light providers | |||||
#define LIGHT_PROVIDER_NONE 0 | |||||
#define LIGHT_PROVIDER_MY92XX 1 // works with MY9291 and MY9231 | |||||
#define LIGHT_PROVIDER_DIMMER 2 | |||||
#define LIGHT_PROVIDER_FASTLED 3 | |||||
// ----------------------------------------------------------------------------- | |||||
// SCHEDULER | |||||
// ----------------------------------------------------------------------------- | |||||
#define SCHEDULER_TYPE_SWITCH 1 | |||||
#define SCHEDULER_TYPE_DIM 2 | |||||
// ----------------------------------------------------------------------------- | |||||
// IR | |||||
// ----------------------------------------------------------------------------- | |||||
// IR Button modes | |||||
#define IR_BUTTON_MODE_NONE 0 | |||||
#define IR_BUTTON_MODE_RGB 1 | |||||
#define IR_BUTTON_MODE_HSV 2 | |||||
#define IR_BUTTON_MODE_BRIGHTER 3 | |||||
#define IR_BUTTON_MODE_STATE 4 | |||||
#define IR_BUTTON_MODE_EFFECT 5 | |||||
#define IR_BUTTON_MODE_TOGGLE 6 | |||||
#define LIGHT_EFFECT_SOLID 0 | |||||
#define LIGHT_EFFECT_FLASH 1 | |||||
#define LIGHT_EFFECT_STROBE 2 | |||||
#define LIGHT_EFFECT_FADE 3 | |||||
#define LIGHT_EFFECT_SMOOTH 4 | |||||
//------------------------------------------------------------------------------ | |||||
// RESET | |||||
//------------------------------------------------------------------------------ | |||||
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button | |||||
#define CUSTOM_RESET_WEB 2 // Reset from web interface | |||||
#define CUSTOM_RESET_TERMINAL 3 // Reset from terminal | |||||
#define CUSTOM_RESET_MQTT 4 // Reset via MQTT | |||||
#define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP) | |||||
#define CUSTOM_RESET_OTA 6 // Reset after successful OTA update | |||||
#define CUSTOM_RESET_HTTP 7 // Reset via HTTP GET | |||||
#define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update | |||||
#define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface | |||||
#define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal | |||||
#define CUSTOM_RESET_MAX 10 | |||||
//------------------------------------------------------------------------------ | |||||
// ENVIRONMENTAL | |||||
//------------------------------------------------------------------------------ | |||||
// 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. | |||||
//------------------------------------------------------------------------------ | |||||
// 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 | |||||
#define TMP_KELVIN 2 | |||||
//-------------------------------------------------------------------------------- | |||||
// 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 | |||||
#define SENSOR_TMP3X_ID 0x22 | |||||
#define SENSOR_HCSR04_ID 0x23 | |||||
#define SENSOR_SENSEAIR_ID 0x24 | |||||
#define SENSOR_GEIGER_ID 0x25 | |||||
//-------------------------------------------------------------------------------- | |||||
// 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_DISTANCE 21 | |||||
#define MAGNITUDE_HCHO 22 | |||||
#define MAGNITUDE_GEIGER_CPM 23 | |||||
#define MAGNITUDE_GEIGER_SIEVERT 24 | |||||
#define MAGNITUDE_MAX 25 |
@ -1,5 +1,6 @@ | |||||
#define APP_NAME "ESPURNA" | #define APP_NAME "ESPURNA" | ||||
#define APP_VERSION "1.11.2" | |||||
#define APP_VERSION "1.13.0c" | |||||
#define APP_REVISION "db84006" | |||||
#define APP_AUTHOR "xose.perez@gmail.com" | #define APP_AUTHOR "xose.perez@gmail.com" | ||||
#define APP_WEBSITE "http://tinkerman.cat" | #define APP_WEBSITE "http://tinkerman.cat" | ||||
#define CFG_VERSION 3 | #define CFG_VERSION 3 |
@ -0,0 +1,86 @@ | |||||
/* | |||||
EEPROM MODULE | |||||
*/ | |||||
#include <EEPROM_Rotate.h> | |||||
// ----------------------------------------------------------------------------- | |||||
bool eepromRotate(bool value) { | |||||
// Enable/disable EEPROM rotation only if we are using more sectors than the | |||||
// reserved by the memory layout | |||||
if (EEPROMr.size() > EEPROMr.reserved()) { | |||||
if (value) { | |||||
DEBUG_MSG_P(PSTR("[EEPROM] Reenabling EEPROM rotation\n")); | |||||
} else { | |||||
DEBUG_MSG_P(PSTR("[EEPROM] Disabling EEPROM rotation\n")); | |||||
} | |||||
EEPROMr.rotate(value); | |||||
} | |||||
} | |||||
String eepromSectors() { | |||||
String response; | |||||
for (uint32_t i = 0; i < EEPROMr.size(); i++) { | |||||
if (i > 0) response = response + String(", "); | |||||
response = response + String(EEPROMr.base() - i); | |||||
} | |||||
return response; | |||||
} | |||||
#if TERMINAL_SUPPORT | |||||
void _eepromInitCommands() { | |||||
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) { | |||||
EEPROMr.dump(settingsSerial()); | |||||
DEBUG_MSG_P(PSTR("\n+OK\n")); | |||||
}); | |||||
settingsRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) { | |||||
if (e->argc < 2) { | |||||
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); | |||||
return; | |||||
} | |||||
uint32_t sector = String(e->argv[1]).toInt(); | |||||
uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; | |||||
if (sector >= max) { | |||||
DEBUG_MSG_P(PSTR("-ERROR: Sector out of range\n")); | |||||
return; | |||||
} | |||||
EEPROMr.dump(settingsSerial(), sector); | |||||
DEBUG_MSG_P(PSTR("\n+OK\n")); | |||||
}); | |||||
} | |||||
#endif | |||||
// ----------------------------------------------------------------------------- | |||||
void eepromSetup() { | |||||
#ifdef EEPROM_ROTATE_SECTORS | |||||
EEPROMr.size(EEPROM_ROTATE_SECTORS); | |||||
#else | |||||
// If the memory layout has more than one sector reserved use those, | |||||
// otherwise calculate pool size based on memory size. | |||||
if (EEPROMr.size() == 1) { | |||||
if (EEPROMr.last() > 1000) { // 4Mb boards | |||||
EEPROMr.size(4); | |||||
} else if (EEPROMr.last() > 250) { // 1Mb boards | |||||
EEPROMr.size(2); | |||||
} | |||||
} | |||||
#endif | |||||
EEPROMr.offset(EEPROM_ROTATE_DATA); | |||||
EEPROMr.begin(EEPROM_SIZE); | |||||
#if TERMINAL_SUPPORT | |||||
_eepromInitCommands(); | |||||
#endif | |||||
} |
@ -0,0 +1,306 @@ | |||||
/* | |||||
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["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); | |||||
config["unit_of_measurement"] = magnitudeUnits(type); | |||||
} | |||||
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); | |||||
jsonBuffer.clear(); | |||||
} | |||||
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); | |||||
jsonBuffer.clear(); | |||||
} | |||||
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"; | |||||
jsonBuffer.clear(); | |||||
} | |||||
#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"; | |||||
jsonBuffer.clear(); | |||||
} | |||||
#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(); | |||||
#if WEB_SUPPORT | |||||
wsSend(_haWebSocketOnSend); | |||||
#endif | |||||
DEBUG_MSG_P(PSTR("+OK\n")); | |||||
}); | |||||
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) { | |||||
setSetting("haEnabled", "0"); | |||||
_haConfigure(); | |||||
#if WEB_SUPPORT | |||||
wsSend(_haWebSocketOnSend); | |||||
#endif | |||||
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 |
@ -1,101 +0,0 @@ | |||||
/* | |||||
HOME ASSISTANT MODULE | |||||
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com> | |||||
*/ | |||||
#if HOMEASSISTANT_SUPPORT | |||||
#include <ArduinoJson.h> | |||||
bool _haEnabled = false; | |||||
// ----------------------------------------------------------------------------- | |||||
void _haWebSocketOnSend(JsonObject& root) { | |||||
root["haVisible"] = 1; | |||||
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX); | |||||
} | |||||
void _haConfigure() { | |||||
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1; | |||||
if (enabled != _haEnabled) haSend(enabled); | |||||
_haEnabled = enabled; | |||||
} | |||||
// ----------------------------------------------------------------------------- | |||||
void haSend(bool add) { | |||||
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n")); | |||||
String output; | |||||
if (add) { | |||||
DynamicJsonBuffer jsonBuffer; | |||||
JsonObject& root = jsonBuffer.createObject(); | |||||
root["name"] = getSetting("hostname"); | |||||
root["platform"] = "mqtt"; | |||||
if (relayCount()) { | |||||
root["state_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, false); | |||||
root["command_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, true); | |||||
root["payload_on"] = String("1"); | |||||
root["payload_off"] = String("0"); | |||||
root["availability_topic"] = getTopic(MQTT_TOPIC_STATUS, false); | |||||
root["payload_available"] = String("1"); | |||||
root["payload_not_available"] = String("0"); | |||||
} | |||||
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE | |||||
if (lightHasColor()) { | |||||
root["brightness_state_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, false); | |||||
root["brightness_command_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, true); | |||||
root["rgb_state_topic"] = getTopic(MQTT_TOPIC_COLOR_RGB, false); | |||||
root["rgb_command_topic"] = getTopic(MQTT_TOPIC_COLOR_RGB, true); | |||||
root["color_temp_command_topic"] = getTopic(MQTT_TOPIC_MIRED, true); | |||||
} | |||||
if (lightChannels() > 3) { | |||||
root["white_value_state_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, false); | |||||
root["white_value_command_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, true); | |||||
} | |||||
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE | |||||
root.printTo(output); | |||||
} | |||||
#if LIGHT_PROVIDER == LIGHT_PROVIDER_NONE | |||||
String component = String("switch"); | |||||
#else | |||||
String component = String("light"); | |||||
#endif | |||||
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) + | |||||
"/" + component + | |||||
"/" + getSetting("hostname") + | |||||
"/config"; | |||||
mqttSendRaw(topic.c_str(), output.c_str()); | |||||
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); | |||||
} | |||||
void haSetup() { | |||||
_haConfigure(); | |||||
#if WEB_SUPPORT | |||||
wsOnSendRegister(_haWebSocketOnSend); | |||||
wsOnAfterParseRegister(_haConfigure); | |||||
#endif | |||||
mqttRegister([](unsigned int type, const char * topic, const char * payload) { | |||||
if (type == MQTT_CONNECT_EVENT) haSend(_haEnabled); | |||||
}); | |||||
} | |||||
#endif // HOMEASSISTANT_SUPPORT |
@ -1,376 +0,0 @@ | |||||
#if SSDP_SUPPORT | |||||
#include "SSDPDevice.h" | |||||
#include "lwip/igmp.h" | |||||
SSDPDeviceClass::SSDPDeviceClass() : | |||||
m_server(0), | |||||
m_port(80), | |||||
m_ttl(SSDP_MULTICAST_TTL) | |||||
{ | |||||
m_uuid[0] = '\0'; | |||||
m_modelNumber[0] = '\0'; | |||||
sprintf(m_deviceType, "urn:schemas-upnp-org:device:Basic:1"); | |||||
m_friendlyName[0] = '\0'; | |||||
m_presentationURL[0] = '\0'; | |||||
m_serialNumber[0] = '\0'; | |||||
m_modelName[0] = '\0'; | |||||
m_modelURL[0] = '\0'; | |||||
m_manufacturer[0] = '\0'; | |||||
m_manufacturerURL[0] = '\0'; | |||||
sprintf(m_schemaURL, "ssdp/schema.xml"); | |||||
uint32_t chipId = ESP.getChipId(); | |||||
sprintf(m_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", | |||||
(uint16_t)((chipId >> 16) & 0xff), | |||||
(uint16_t)((chipId >> 8) & 0xff), | |||||
(uint16_t)chipId & 0xff); | |||||
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) { | |||||
m_queue[i].time = 0; | |||||
} | |||||
} | |||||
void SSDPDeviceClass::update() { | |||||
postNotifyUpdate(); | |||||
} | |||||
bool SSDPDeviceClass::readLine(String &value) { | |||||
char buffer[65]; | |||||
int bufferPos = 0; | |||||
while (1) { | |||||
int c = m_server->read(); | |||||
if (c < 0) { | |||||
buffer[bufferPos] = '\0'; | |||||
break; | |||||
} | |||||
if (c == '\r' && m_server->peek() == '\n') { | |||||
m_server->read(); | |||||
buffer[bufferPos] = '\0'; | |||||
break; | |||||
} | |||||
if (bufferPos < 64) { | |||||
buffer[bufferPos++] = c; | |||||
} | |||||
} | |||||
value = String(buffer); | |||||
return bufferPos > 0; | |||||
} | |||||
bool SSDPDeviceClass::readKeyValue(String &key, String &value) { | |||||
char buffer[65]; | |||||
int bufferPos = 0; | |||||
while (1) { | |||||
int c = m_server->read(); | |||||
if (c < 0) { | |||||
if (bufferPos == 0) return false; | |||||
buffer[bufferPos] = '\0'; | |||||
break; | |||||
} | |||||
if (c == ':') { | |||||
buffer[bufferPos] = '\0'; | |||||
while (m_server->peek() == ' ') m_server->read(); | |||||
break; | |||||
} | |||||
else if (c == '\r' && m_server->peek() == '\n') { | |||||
m_server->read(); | |||||
if (bufferPos == 0) return false; | |||||
buffer[bufferPos] = '\0'; | |||||
key = String(); | |||||
value = String(buffer); | |||||
return true; | |||||
} | |||||
if (bufferPos < 64) { | |||||
buffer[bufferPos++] = c; | |||||
} | |||||
} | |||||
key = String(buffer); | |||||
readLine(value); | |||||
return true; | |||||
} | |||||
void SSDPDeviceClass::postNotifyALive() { | |||||
unsigned long time = millis(); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 210); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 255); | |||||
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 280); | |||||
post(NOTIFY_ALIVE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 610); | |||||
post(NOTIFY_ALIVE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 655); | |||||
post(NOTIFY_ALIVE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 680); | |||||
} | |||||
void SSDPDeviceClass::postNotifyUpdate() { | |||||
unsigned long time = millis(); | |||||
post(NOTIFY_UPDATE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10); | |||||
post(NOTIFY_UPDATE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55); | |||||
post(NOTIFY_UPDATE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80); | |||||
} | |||||
void SSDPDeviceClass::postResponse(long mx) { | |||||
unsigned long time = millis(); | |||||
unsigned long delay = random(0, mx) * 900L; // 1000 ms - 100 ms | |||||
IPAddress address = m_server->remoteIP(); | |||||
uint16_t port = m_server->remotePort(); | |||||
post(RESPONSE, ROOT_FOR_ALL, address, port, time + delay / 3); | |||||
post(RESPONSE, ROOT_BY_UUID, address, port, time + delay / 3 * 2); | |||||
post(RESPONSE, ROOT_BY_TYPE, address, port, time + delay); | |||||
} | |||||
void SSDPDeviceClass::postResponse(ssdp_udn_t udn, long mx) { | |||||
post(RESPONSE, udn, m_server->remoteIP(), m_server->remotePort(), millis() + random(0, mx) * 900L); // 1000 ms - 100 ms | |||||
} | |||||
void SSDPDeviceClass::post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time) { | |||||
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) { | |||||
if (m_queue[i].time == 0) { | |||||
m_queue[i].type = type; | |||||
m_queue[i].udn = udn; | |||||
m_queue[i].address = address; | |||||
m_queue[i].port = port; | |||||
m_queue[i].time = time; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
void SSDPDeviceClass::send(ssdp_send_parameters_t *parameters) { | |||||
char buffer[1460]; | |||||
unsigned int ip = WiFi.localIP(); | |||||
const char *typeTemplate; | |||||
const char *uri, *usn1, *usn2, *usn3; | |||||
switch (parameters->type) { | |||||
case NOTIFY_ALIVE_INIT: | |||||
case NOTIFY_ALIVE: | |||||
typeTemplate = SSDP_NOTIFY_ALIVE_TEMPLATE; | |||||
break; | |||||
case NOTIFY_UPDATE: | |||||
typeTemplate = SSDP_NOTIFY_UPDATE_TEMPLATE; | |||||
break; | |||||
default: // RESPONSE | |||||
typeTemplate = SSDP_RESPONSE_TEMPLATE; | |||||
break; | |||||
} | |||||
String uuid = "uuid:" + String(m_uuid); | |||||
switch (parameters->udn) { | |||||
case ROOT_FOR_ALL: | |||||
uri = "upnp:rootdevice"; | |||||
usn1 = uuid.c_str(); | |||||
usn2 = "::"; | |||||
usn3 = "upnp:rootdevice"; | |||||
break; | |||||
case ROOT_BY_UUID: | |||||
uri = uuid.c_str(); | |||||
usn1 = uuid.c_str(); | |||||
usn2 = ""; | |||||
usn3 = ""; | |||||
break; | |||||
case ROOT_BY_TYPE: | |||||
uri = m_deviceType; | |||||
usn1 = uuid.c_str(); | |||||
usn2 = "::"; | |||||
usn3 = m_deviceType; | |||||
break; | |||||
} | |||||
int len = snprintf_P(buffer, sizeof(buffer), | |||||
SSDP_PACKET_TEMPLATE, typeTemplate, | |||||
SSDP_INTERVAL, m_modelName, m_modelNumber, usn1, usn2, usn3, parameters->type == RESPONSE ? "ST" : "NT", uri, | |||||
IP2STR(&ip), m_port, m_schemaURL | |||||
); | |||||
if (parameters->address == SSDP_MULTICAST_ADDR) { | |||||
m_server->beginPacketMulticast(parameters->address, parameters->port, m_ttl); | |||||
} | |||||
else { | |||||
m_server->beginPacket(parameters->address, parameters->port); | |||||
} | |||||
m_server->write(buffer, len); | |||||
m_server->endPacket(); | |||||
parameters->time = parameters->type == NOTIFY_ALIVE ? parameters->time + SSDP_INTERVAL * 900L : 0; // 1000 ms - 100 ms | |||||
} | |||||
String SSDPDeviceClass::schema() { | |||||
char buffer[1024]; | |||||
uint32_t ip = WiFi.localIP(); | |||||
snprintf(buffer, sizeof(buffer), SSDP_SCHEMA_TEMPLATE, | |||||
IP2STR(&ip), m_port, m_schemaURL, | |||||
m_deviceType, | |||||
m_friendlyName, | |||||
m_presentationURL, | |||||
m_serialNumber, | |||||
m_modelName, | |||||
m_modelNumber, | |||||
m_modelURL, | |||||
m_manufacturer, | |||||
m_manufacturerURL, | |||||
m_uuid | |||||
); | |||||
return String(buffer); | |||||
} | |||||
void SSDPDeviceClass::handleClient() { | |||||
IPAddress current = WiFi.localIP(); | |||||
if (m_last != current) { | |||||
m_last = current; | |||||
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) { | |||||
m_queue[i].time = 0; | |||||
} | |||||
if (current != INADDR_NONE) { | |||||
if (!m_server) m_server = new WiFiUDP(); | |||||
m_server->beginMulticast(current, SSDP_MULTICAST_ADDR, SSDP_PORT); | |||||
postNotifyALive(); | |||||
} | |||||
else if (m_server) { | |||||
m_server->stop(); | |||||
} | |||||
} | |||||
if (m_server && m_server->parsePacket()) { | |||||
String value; | |||||
if (readLine(value) && value.equalsIgnoreCase("M-SEARCH * HTTP/1.1")) { | |||||
String key, st; | |||||
bool host = false, man = false; | |||||
long mx = 0; | |||||
while (readKeyValue(key, value)) { | |||||
if (key.equalsIgnoreCase("HOST") && value.equals("239.255.255.250:1900")) { | |||||
host = true; | |||||
} | |||||
else if (key.equalsIgnoreCase("MAN") && value.equals("\"ssdp:discover\"")) { | |||||
man = true; | |||||
} | |||||
else if (key.equalsIgnoreCase("ST")) { | |||||
st = value; | |||||
} | |||||
else if (key.equalsIgnoreCase("MX")) { | |||||
mx = value.toInt(); | |||||
} | |||||
} | |||||
if (host && man && mx > 0) { | |||||
if (st.equals("ssdp:all")) { | |||||
postResponse(mx); | |||||
} | |||||
else if (st.equals("upnp:rootdevice")) { | |||||
postResponse(ROOT_FOR_ALL, mx); | |||||
} | |||||
else if (st.equals("uuid:" + String(m_uuid))) { | |||||
postResponse(ROOT_BY_UUID, mx); | |||||
} | |||||
else if (st.equals(m_deviceType)) { | |||||
postResponse(ROOT_BY_TYPE, mx); | |||||
} | |||||
} | |||||
} | |||||
m_server->flush(); | |||||
} | |||||
else { | |||||
unsigned long time = millis(); | |||||
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) { | |||||
if (m_queue[i].time > 0 && m_queue[i].time < time) { | |||||
send(&m_queue[i]); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void SSDPDeviceClass::setSchemaURL(const char *url) { | |||||
strlcpy(m_schemaURL, url, sizeof(m_schemaURL)); | |||||
} | |||||
void SSDPDeviceClass::setHTTPPort(uint16_t port) { | |||||
m_port = port; | |||||
} | |||||
void SSDPDeviceClass::setDeviceType(const char *deviceType) { | |||||
strlcpy(m_deviceType, deviceType, sizeof(m_deviceType)); | |||||
} | |||||
void SSDPDeviceClass::setName(const char *name) { | |||||
strlcpy(m_friendlyName, name, sizeof(m_friendlyName)); | |||||
} | |||||
void SSDPDeviceClass::setURL(const char *url) { | |||||
strlcpy(m_presentationURL, url, sizeof(m_presentationURL)); | |||||
} | |||||
void SSDPDeviceClass::setSerialNumber(const char *serialNumber) { | |||||
strlcpy(m_serialNumber, serialNumber, sizeof(m_serialNumber)); | |||||
} | |||||
void SSDPDeviceClass::setSerialNumber(const uint32_t serialNumber) { | |||||
snprintf(m_serialNumber, sizeof(uint32_t) * 2 + 1, "%08X", serialNumber); | |||||
} | |||||
void SSDPDeviceClass::setModelName(const char *name) { | |||||
strlcpy(m_modelName, name, sizeof(m_modelName)); | |||||
} | |||||
void SSDPDeviceClass::setModelNumber(const char *num) { | |||||
strlcpy(m_modelNumber, num, sizeof(m_modelNumber)); | |||||
} | |||||
void SSDPDeviceClass::setModelURL(const char *url) { | |||||
strlcpy(m_modelURL, url, sizeof(m_modelURL)); | |||||
} | |||||
void SSDPDeviceClass::setManufacturer(const char *name) { | |||||
strlcpy(m_manufacturer, name, sizeof(m_manufacturer)); | |||||
} | |||||
void SSDPDeviceClass::setManufacturerURL(const char *url) { | |||||
strlcpy(m_manufacturerURL, url, sizeof(m_manufacturerURL)); | |||||
} | |||||
void SSDPDeviceClass::setTTL(const uint8_t ttl) { | |||||
m_ttl = ttl; | |||||
} | |||||
SSDPDeviceClass SSDPDevice; | |||||
#endif |
@ -1,198 +0,0 @@ | |||||
#if SSDP_SUPPORT // SSDP_SUPPORT | |||||
#ifndef _SSDPDEVICE_h | |||||
#define _SSDPDEVICE_h | |||||
#if defined(ARDUINO) && ARDUINO >= 100 | |||||
#include "Arduino.h" | |||||
#else | |||||
#include "WProgram.h" | |||||
#endif | |||||
#include <ESP8266WiFi.h> | |||||
#include <WiFiUdp.h> | |||||
#define SSDP_INTERVAL 1200 | |||||
#define SSDP_PORT 1900 | |||||
//#define SSDP_METHOD_SIZE 10 | |||||
//#define SSDP_URI_SIZE 2 | |||||
//#define SSDP_BUFFER_SIZE 64 | |||||
#define SSDP_MULTICAST_TTL 2 | |||||
#define SSDP_QUEUE_SIZE 21 | |||||
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); | |||||
#define SSDP_UUID_SIZE 37 | |||||
#define SSDP_SCHEMA_URL_SIZE 64 | |||||
#define SSDP_DEVICE_TYPE_SIZE 64 | |||||
#define SSDP_FRIENDLY_NAME_SIZE 64 | |||||
#define SSDP_SERIAL_NUMBER_SIZE 32 | |||||
#define SSDP_PRESENTATION_URL_SIZE 128 | |||||
#define SSDP_MODEL_NAME_SIZE 64 | |||||
#define SSDP_MODEL_URL_SIZE 128 | |||||
#define SSDP_MODEL_VERSION_SIZE 32 | |||||
#define SSDP_MANUFACTURER_SIZE 64 | |||||
#define SSDP_MANUFACTURER_URL_SIZE 128 | |||||
static const char* PROGMEM SSDP_RESPONSE_TEMPLATE = | |||||
"HTTP/1.1 200 OK\r\n" | |||||
"EXT:\r\n"; | |||||
static const char* PROGMEM SSDP_NOTIFY_ALIVE_TEMPLATE = | |||||
"NOTIFY * HTTP/1.1\r\n" | |||||
"HOST: 239.255.255.250:1900\r\n" | |||||
"NTS: ssdp:alive\r\n"; | |||||
static const char* PROGMEM SSDP_NOTIFY_UPDATE_TEMPLATE = | |||||
"NOTIFY * HTTP/1.1\r\n" | |||||
"HOST: 239.255.255.250:1900\r\n" | |||||
"NTS: ssdp:update\r\n"; | |||||
static const char* PROGMEM SSDP_PACKET_TEMPLATE = | |||||
"%s" // _ssdp_response_template / _ssdp_notify_template | |||||
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL | |||||
"SERVER: UPNP/1.1 %s/%s\r\n" // m_modelName, m_modelNumber | |||||
"USN: %s%s%s\r\n" // m_uuid | |||||
"%s: %s\r\n" // "NT" or "ST", m_deviceType | |||||
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), m_port, m_schemaURL | |||||
"\r\n"; | |||||
static const char* PROGMEM SSDP_SCHEMA_TEMPLATE = | |||||
"HTTP/1.1 200 OK\r\n" | |||||
"Content-Type: text/xml\r\n" | |||||
"Connection: close\r\n" | |||||
"Access-Control-Allow-Origin: *\r\n" | |||||
"\r\n" | |||||
"<?xml version=\"1.0\"?>" | |||||
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">" | |||||
"<specVersion>" | |||||
"<major>1</major>" | |||||
"<minor>0</minor>" | |||||
"</specVersion>" | |||||
"<URLBase>http://%u.%u.%u.%u:%u/%s</URLBase>" // WiFi.localIP(), _port | |||||
"<device>" | |||||
"<deviceType>%s</deviceType>" | |||||
"<friendlyName>%s</friendlyName>" | |||||
"<presentationURL>%s</presentationURL>" | |||||
"<serialNumber>%s</serialNumber>" | |||||
"<modelName>%s</modelName>" | |||||
"<modelNumber>%s</modelNumber>" | |||||
"<modelURL>%s</modelURL>" | |||||
"<manufacturer>%s</manufacturer>" | |||||
"<manufacturerURL>%s</manufacturerURL>" | |||||
"<UDN>uuid:%s</UDN>" | |||||
"</device>" | |||||
// "<iconList>" | |||||
// "<icon>" | |||||
// "<mimetype>image/png</mimetype>" | |||||
// "<height>48</height>" | |||||
// "<width>48</width>" | |||||
// "<depth>24</depth>" | |||||
// "<url>icon48.png</url>" | |||||
// "</icon>" | |||||
// "<icon>" | |||||
// "<mimetype>image/png</mimetype>" | |||||
// "<height>120</height>" | |||||
// "<width>120</width>" | |||||
// "<depth>24</depth>" | |||||
// "<url>icon120.png</url>" | |||||
// "</icon>" | |||||
// "</iconList>" | |||||
"</root>\r\n" | |||||
"\r\n"; | |||||
typedef enum { | |||||
NOTIFY_ALIVE_INIT, | |||||
NOTIFY_ALIVE, | |||||
NOTIFY_UPDATE, | |||||
RESPONSE | |||||
} ssdp_message_t; | |||||
typedef enum { | |||||
ROOT_FOR_ALL, | |||||
ROOT_BY_UUID, | |||||
ROOT_BY_TYPE | |||||
} ssdp_udn_t; | |||||
typedef struct { | |||||
unsigned long time; | |||||
ssdp_message_t type; | |||||
ssdp_udn_t udn; | |||||
uint32_t address; | |||||
uint16_t port; | |||||
} ssdp_send_parameters_t; | |||||
class SSDPDeviceClass { | |||||
private: | |||||
WiFiUDP *m_server; | |||||
IPAddress m_last; | |||||
char m_schemaURL[SSDP_SCHEMA_URL_SIZE]; | |||||
char m_uuid[SSDP_UUID_SIZE]; | |||||
char m_deviceType[SSDP_DEVICE_TYPE_SIZE]; | |||||
char m_friendlyName[SSDP_FRIENDLY_NAME_SIZE]; | |||||
char m_serialNumber[SSDP_SERIAL_NUMBER_SIZE]; | |||||
char m_presentationURL[SSDP_PRESENTATION_URL_SIZE]; | |||||
char m_manufacturer[SSDP_MANUFACTURER_SIZE]; | |||||
char m_manufacturerURL[SSDP_MANUFACTURER_URL_SIZE]; | |||||
char m_modelName[SSDP_MODEL_NAME_SIZE]; | |||||
char m_modelURL[SSDP_MODEL_URL_SIZE]; | |||||
char m_modelNumber[SSDP_MODEL_VERSION_SIZE]; | |||||
uint16_t m_port; | |||||
uint8_t m_ttl; | |||||
ssdp_send_parameters_t m_queue[SSDP_QUEUE_SIZE]; | |||||
protected: | |||||
bool readLine(String &value); | |||||
bool readKeyValue(String &key, String &value); | |||||
void postNotifyALive(); | |||||
void postNotifyUpdate(); | |||||
void postResponse(long mx); | |||||
void postResponse(ssdp_udn_t udn, long mx); | |||||
void post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time); | |||||
void send(ssdp_send_parameters_t *parameters); | |||||
public: | |||||
SSDPDeviceClass(); | |||||
void update(); | |||||
String schema(); | |||||
void handleClient(); | |||||
void setDeviceType(const String& deviceType) { setDeviceType(deviceType.c_str()); } | |||||
void setDeviceType(const char *deviceType); | |||||
void setName(const String& name) { setName(name.c_str()); } | |||||
void setName(const char *name); | |||||
void setURL(const String& url) { setURL(url.c_str()); } | |||||
void setURL(const char *url); | |||||
void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); } | |||||
void setSchemaURL(const char *url); | |||||
void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); } | |||||
void setSerialNumber(const char *serialNumber); | |||||
void setSerialNumber(const uint32_t serialNumber); | |||||
void setModelName(const String& name) { setModelName(name.c_str()); } | |||||
void setModelName(const char *name); | |||||
void setModelNumber(const String& num) { setModelNumber(num.c_str()); } | |||||
void setModelNumber(const char *num); | |||||
void setModelURL(const String& url) { setModelURL(url.c_str()); } | |||||
void setModelURL(const char *url); | |||||
void setManufacturer(const String& name) { setManufacturer(name.c_str()); } | |||||
void setManufacturer(const char *name); | |||||
void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); } | |||||
void setManufacturerURL(const char *url); | |||||
void setHTTPPort(uint16_t port); | |||||
void setTTL(uint8_t ttl); | |||||
}; | |||||
extern SSDPDeviceClass SSDPDevice; | |||||
#endif | |||||
#endif // SSDP_SUPPORT |
@ -0,0 +1,636 @@ | |||||
/** | |||||
* This code is available at | |||||
* http://www.mindspring.com/~pfilandr/C/fs_math/ | |||||
* and it is believed to be public domain. | |||||
*/ | |||||
/* BEGIN fs_math.c */ | |||||
#include "fs_math.h" | |||||
#include <float.h> | |||||
/* | |||||
** pi == (atan(1.0 / 3) + atan(1.0 / 2)) * 4 | |||||
*/ | |||||
static double fs_pi(void); | |||||
static long double fs_pil(void); | |||||
double fs_sqrt(double x) | |||||
{ | |||||
int n; | |||||
double a, b; | |||||
if (x > 0 && DBL_MAX >= x) { | |||||
for (n = 0; x > 2; x /= 4) { | |||||
++n; | |||||
} | |||||
while (0.5 > x) { | |||||
--n; | |||||
x *= 4; | |||||
} | |||||
a = x; | |||||
b = (1 + x) / 2; | |||||
do { | |||||
x = b; | |||||
b = (a / x + x) / 2; | |||||
} while (x > b); | |||||
while (n > 0) { | |||||
x *= 2; | |||||
--n; | |||||
} | |||||
while (0 > n) { | |||||
x /= 2; | |||||
++n; | |||||
} | |||||
} else { | |||||
if (x != 0) { | |||||
x = DBL_MAX; | |||||
} | |||||
} | |||||
return x; | |||||
} | |||||
double fs_log(double x) | |||||
{ | |||||
int n; | |||||
double a, b, c, epsilon; | |||||
static double A, B, C; | |||||
static int initialized; | |||||
if (x > 0 && DBL_MAX >= x) { | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
A = fs_sqrt(2); | |||||
B = A / 2; | |||||
C = fs_log(A); | |||||
} | |||||
for (n = 0; x > A; x /= 2) { | |||||
++n; | |||||
} | |||||
while (B > x) { | |||||
--n; | |||||
x *= 2; | |||||
} | |||||
a = (x - 1) / (x + 1); | |||||
x = C * n + a; | |||||
c = a * a; | |||||
n = 1; | |||||
epsilon = DBL_EPSILON * x; | |||||
if (0 > a) { | |||||
if (epsilon > 0) { | |||||
epsilon = -epsilon; | |||||
} | |||||
do { | |||||
n += 2; | |||||
a *= c; | |||||
b = a / n; | |||||
x += b; | |||||
} while (epsilon > b); | |||||
} else { | |||||
if (0 > epsilon) { | |||||
epsilon = -epsilon; | |||||
} | |||||
do { | |||||
n += 2; | |||||
a *= c; | |||||
b = a / n; | |||||
x += b; | |||||
} while (b > epsilon); | |||||
} | |||||
x *= 2; | |||||
} else { | |||||
x = -DBL_MAX; | |||||
} | |||||
return x; | |||||
} | |||||
double fs_log10(double x) | |||||
{ | |||||
static double log_10; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
log_10 = fs_log(10); | |||||
} | |||||
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_10 : fs_log(x); | |||||
} | |||||
double fs_exp(double x) | |||||
{ | |||||
unsigned n, square; | |||||
double b, e; | |||||
static double x_max, x_min, epsilon; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
x_max = fs_log(DBL_MAX); | |||||
x_min = fs_log(DBL_MIN); | |||||
epsilon = DBL_EPSILON / 4; | |||||
} | |||||
if (x_max >= x && x >= x_min) { | |||||
for (square = 0; x > 1; x /= 2) { | |||||
++square; | |||||
} | |||||
while (-1 > x) { | |||||
++square; | |||||
x /= 2; | |||||
} | |||||
e = b = n = 1; | |||||
do { | |||||
b /= n++; | |||||
b *= x; | |||||
e += b; | |||||
b /= n++; | |||||
b *= x; | |||||
e += b; | |||||
} while (b > epsilon); | |||||
while (square-- != 0) { | |||||
e *= e; | |||||
} | |||||
} else { | |||||
e = x > 0 ? DBL_MAX : 0; | |||||
} | |||||
return e; | |||||
} | |||||
double fs_modf(double value, double *iptr) | |||||
{ | |||||
double a, b; | |||||
const double c = value; | |||||
if (0 > c) { | |||||
value = -value; | |||||
} | |||||
if (DBL_MAX >= value) { | |||||
for (*iptr = 0; value >= 1; value -= b) { | |||||
a = value / 2; | |||||
b = 1; | |||||
while (a >= b) { | |||||
b *= 2; | |||||
} | |||||
*iptr += b; | |||||
} | |||||
} else { | |||||
*iptr = value; | |||||
value = 0; | |||||
} | |||||
if (0 > c) { | |||||
*iptr = -*iptr; | |||||
value = -value; | |||||
} | |||||
return value; | |||||
} | |||||
double fs_fmod(double x, double y) | |||||
{ | |||||
double a, b; | |||||
const double c = x; | |||||
if (0 > c) { | |||||
x = -x; | |||||
} | |||||
if (0 > y) { | |||||
y = -y; | |||||
} | |||||
if (y != 0 && DBL_MAX >= y && DBL_MAX >= x) { | |||||
while (x >= y) { | |||||
a = x / 2; | |||||
b = y; | |||||
while (a >= b) { | |||||
b *= 2; | |||||
} | |||||
x -= b; | |||||
} | |||||
} else { | |||||
x = 0; | |||||
} | |||||
return 0 > c ? -x : x; | |||||
} | |||||
double fs_pow(double x, double y) | |||||
{ | |||||
double p = 0; | |||||
if (0 > x && fs_fmod(y, 1) == 0) { | |||||
if (fs_fmod(y, 2) == 0) { | |||||
p = fs_exp(fs_log(-x) * y); | |||||
} else { | |||||
p = -fs_exp(fs_log(-x) * y); | |||||
} | |||||
} else { | |||||
if (x != 0 || 0 >= y) { | |||||
p = fs_exp(fs_log( x) * y); | |||||
} | |||||
} | |||||
return p; | |||||
} | |||||
static double fs_pi(void) | |||||
{ | |||||
unsigned n; | |||||
double a, b, epsilon; | |||||
static double p; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
epsilon = DBL_EPSILON / 4; | |||||
n = 1; | |||||
a = 3; | |||||
do { | |||||
a /= 9; | |||||
b = a / n; | |||||
n += 2; | |||||
a /= 9; | |||||
b -= a / n; | |||||
n += 2; | |||||
p += b; | |||||
} while (b > epsilon); | |||||
epsilon = DBL_EPSILON / 2; | |||||
n = 1; | |||||
a = 2; | |||||
do { | |||||
a /= 4; | |||||
b = a / n; | |||||
n += 2; | |||||
a /= 4; | |||||
b -= a / n; | |||||
n += 2; | |||||
p += b; | |||||
} while (b > epsilon); | |||||
p *= 4; | |||||
} | |||||
return p; | |||||
} | |||||
double fs_cos(double x) | |||||
{ | |||||
unsigned n; | |||||
int negative, sine; | |||||
double a, b, c; | |||||
static double pi, two_pi, half_pi, third_pi, epsilon; | |||||
static int initialized; | |||||
if (0 > x) { | |||||
x = -x; | |||||
} | |||||
if (DBL_MAX >= x) { | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
pi = fs_pi(); | |||||
two_pi = 2 * pi; | |||||
half_pi = pi / 2; | |||||
third_pi = pi / 3; | |||||
epsilon = DBL_EPSILON / 2; | |||||
} | |||||
if (x > two_pi) { | |||||
x = fs_fmod(x, two_pi); | |||||
} | |||||
if (x > pi) { | |||||
x = two_pi - x; | |||||
} | |||||
if (x > half_pi) { | |||||
x = pi - x; | |||||
negative = 1; | |||||
} else { | |||||
negative = 0; | |||||
} | |||||
if (x > third_pi) { | |||||
x = half_pi - x; | |||||
sine = 1; | |||||
} else { | |||||
sine = 0; | |||||
} | |||||
c = x * x; | |||||
x = n = 0; | |||||
a = 1; | |||||
do { | |||||
b = a; | |||||
a *= c; | |||||
a /= ++n; | |||||
a /= ++n; | |||||
b -= a; | |||||
a *= c; | |||||
a /= ++n; | |||||
a /= ++n; | |||||
x += b; | |||||
} while (b > epsilon); | |||||
if (sine) { | |||||
x = fs_sqrt((1 - x) * (1 + x)); | |||||
} | |||||
if (negative) { | |||||
x = -x; | |||||
} | |||||
} else { | |||||
x = -DBL_MAX; | |||||
} | |||||
return x; | |||||
} | |||||
double fs_log2(double x) | |||||
{ | |||||
static double log_2; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
log_2 = fs_log(2); | |||||
} | |||||
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_2 : fs_log(x); | |||||
} | |||||
double fs_exp2(double x) | |||||
{ | |||||
static double log_2; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
log_2 = fs_log(2); | |||||
} | |||||
return fs_exp(x * log_2); | |||||
} | |||||
long double fs_powl(long double x, long double y) | |||||
{ | |||||
long double p; | |||||
if (0 > x && fs_fmodl(y, 1) == 0) { | |||||
if (fs_fmodl(y, 2) == 0) { | |||||
p = fs_expl(fs_logl(-x) * y); | |||||
} else { | |||||
p = -fs_expl(fs_logl(-x) * y); | |||||
} | |||||
} else { | |||||
if (x != 0 || 0 >= y) { | |||||
p = fs_expl(fs_logl( x) * y); | |||||
} else { | |||||
p = 0; | |||||
} | |||||
} | |||||
return p; | |||||
} | |||||
long double fs_sqrtl(long double x) | |||||
{ | |||||
long int n; | |||||
long double a, b; | |||||
if (x > 0 && LDBL_MAX >= x) { | |||||
for (n = 0; x > 2; x /= 4) { | |||||
++n; | |||||
} | |||||
while (0.5 > x) { | |||||
--n; | |||||
x *= 4; | |||||
} | |||||
a = x; | |||||
b = (1 + x) / 2; | |||||
do { | |||||
x = b; | |||||
b = (a / x + x) / 2; | |||||
} while (x > b); | |||||
while (n > 0) { | |||||
x *= 2; | |||||
--n; | |||||
} | |||||
while (0 > n) { | |||||
x /= 2; | |||||
++n; | |||||
} | |||||
} else { | |||||
if (x != 0) { | |||||
x = LDBL_MAX; | |||||
} | |||||
} | |||||
return x; | |||||
} | |||||
long double fs_logl(long double x) | |||||
{ | |||||
long int n; | |||||
long double a, b, c, epsilon; | |||||
static long double A, B, C; | |||||
static int initialized; | |||||
if (x > 0 && LDBL_MAX >= x) { | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
B = 1.5; | |||||
do { | |||||
A = B; | |||||
B = 1 / A + A / 2; | |||||
} while (A > B); | |||||
B /= 2; | |||||
C = fs_logl(A); | |||||
} | |||||
for (n = 0; x > A; x /= 2) { | |||||
++n; | |||||
} | |||||
while (B > x) { | |||||
--n; | |||||
x *= 2; | |||||
} | |||||
a = (x - 1) / (x + 1); | |||||
x = C * n + a; | |||||
c = a * a; | |||||
n = 1; | |||||
epsilon = LDBL_EPSILON * x; | |||||
if (0 > a) { | |||||
if (epsilon > 0) { | |||||
epsilon = -epsilon; | |||||
} | |||||
do { | |||||
n += 2; | |||||
a *= c; | |||||
b = a / n; | |||||
x += b; | |||||
} while (epsilon > b); | |||||
} else { | |||||
if (0 > epsilon) { | |||||
epsilon = -epsilon; | |||||
} | |||||
do { | |||||
n += 2; | |||||
a *= c; | |||||
b = a / n; | |||||
x += b; | |||||
} while (b > epsilon); | |||||
} | |||||
x *= 2; | |||||
} else { | |||||
x = -LDBL_MAX; | |||||
} | |||||
return x; | |||||
} | |||||
long double fs_expl(long double x) | |||||
{ | |||||
long unsigned n, square; | |||||
long double b, e; | |||||
static long double x_max, x_min, epsilon; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
x_max = fs_logl(LDBL_MAX); | |||||
x_min = fs_logl(LDBL_MIN); | |||||
epsilon = LDBL_EPSILON / 4; | |||||
} | |||||
if (x_max >= x && x >= x_min) { | |||||
for (square = 0; x > 1; x /= 2) { | |||||
++square; | |||||
} | |||||
while (-1 > x) { | |||||
++square; | |||||
x /= 2; | |||||
} | |||||
e = b = n = 1; | |||||
do { | |||||
b /= n++; | |||||
b *= x; | |||||
e += b; | |||||
b /= n++; | |||||
b *= x; | |||||
e += b; | |||||
} while (b > epsilon); | |||||
while (square-- != 0) { | |||||
e *= e; | |||||
} | |||||
} else { | |||||
e = x > 0 ? LDBL_MAX : 0; | |||||
} | |||||
return e; | |||||
} | |||||
static long double fs_pil(void) | |||||
{ | |||||
long unsigned n; | |||||
long double a, b, epsilon; | |||||
static long double p; | |||||
static int initialized; | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
epsilon = LDBL_EPSILON / 4; | |||||
n = 1; | |||||
a = 3; | |||||
do { | |||||
a /= 9; | |||||
b = a / n; | |||||
n += 2; | |||||
a /= 9; | |||||
b -= a / n; | |||||
n += 2; | |||||
p += b; | |||||
} while (b > epsilon); | |||||
epsilon = LDBL_EPSILON / 2; | |||||
n = 1; | |||||
a = 2; | |||||
do { | |||||
a /= 4; | |||||
b = a / n; | |||||
n += 2; | |||||
a /= 4; | |||||
b -= a / n; | |||||
n += 2; | |||||
p += b; | |||||
} while (b > epsilon); | |||||
p *= 4; | |||||
} | |||||
return p; | |||||
} | |||||
long double fs_cosl(long double x) | |||||
{ | |||||
long unsigned n; | |||||
int negative, sine; | |||||
long double a, b, c; | |||||
static long double pi, two_pi, half_pi, third_pi, epsilon; | |||||
static int initialized; | |||||
if (0 > x) { | |||||
x = -x; | |||||
} | |||||
if (LDBL_MAX >= x) { | |||||
if (!initialized) { | |||||
initialized = 1; | |||||
pi = fs_pil(); | |||||
two_pi = 2 * pi; | |||||
half_pi = pi / 2; | |||||
third_pi = pi / 3; | |||||
epsilon = LDBL_EPSILON / 2; | |||||
} | |||||
if (x > two_pi) { | |||||
x = fs_fmodl(x, two_pi); | |||||
} | |||||
if (x > pi) { | |||||
x = two_pi - x; | |||||
} | |||||
if (x > half_pi) { | |||||
x = pi - x; | |||||
negative = 1; | |||||
} else { | |||||
negative = 0; | |||||
} | |||||
if (x > third_pi) { | |||||
x = half_pi - x; | |||||
sine = 1; | |||||
} else { | |||||
sine = 0; | |||||
} | |||||
c = x * x; | |||||
x = n = 0; | |||||
a = 1; | |||||
do { | |||||
b = a; | |||||
a *= c; | |||||
a /= ++n; | |||||
a /= ++n; | |||||
b -= a; | |||||
a *= c; | |||||
a /= ++n; | |||||
a /= ++n; | |||||
x += b; | |||||
} while (b > epsilon); | |||||
if (sine) { | |||||
x = fs_sqrtl((1 - x) * (1 + x)); | |||||
} | |||||
if (negative) { | |||||
x = -x; | |||||
} | |||||
} else { | |||||
x = -LDBL_MAX; | |||||
} | |||||
return x; | |||||
} | |||||
long double fs_fmodl(long double x, long double y) | |||||
{ | |||||
long double a, b; | |||||
const long double c = x; | |||||
if (0 > c) { | |||||
x = -x; | |||||
} | |||||
if (0 > y) { | |||||
y = -y; | |||||
} | |||||
if (y != 0 && LDBL_MAX >= y && LDBL_MAX >= x) { | |||||
while (x >= y) { | |||||
a = x / 2; | |||||
b = y; | |||||
while (a >= b) { | |||||
b *= 2; | |||||
} | |||||
x -= b; | |||||
} | |||||
} else { | |||||
x = 0; | |||||
} | |||||
return 0 > c ? -x : x; | |||||
} | |||||
/* END fs_math.c */ |
@ -0,0 +1,116 @@ | |||||
/** | |||||
* This code is available at | |||||
* http://www.mindspring.com/~pfilandr/C/fs_math/ | |||||
* and it is believed to be public domain. | |||||
*/ | |||||
/* BEGIN fs_math.h */ | |||||
/* | |||||
** Portable freestanding code. | |||||
*/ | |||||
#ifndef H_FS_MATH_H | |||||
#define H_FS_MATH_H | |||||
double fs_sqrt(double x); | |||||
double fs_log(double x); | |||||
double fs_log10(double x); | |||||
/* | |||||
** exp(x) = 1 + x + x^2/2! + x^3/3! + ... | |||||
*/ | |||||
double fs_exp(double x); | |||||
double fs_modf(double value, double *iptr); | |||||
double fs_fmod(double x, double y); | |||||
double fs_pow(double x, double y); | |||||
double fs_cos(double x); | |||||
/* | |||||
** C99 | |||||
*/ | |||||
double fs_log2(double x); | |||||
double fs_exp2(double x); | |||||
long double fs_powl(long double x, long double y); | |||||
long double fs_sqrtl(long double x); | |||||
long double fs_logl(long double x); | |||||
long double fs_expl(long double x); | |||||
long double fs_cosl(long double x); | |||||
long double fs_fmodl(long double x, long double y); | |||||
#endif | |||||
/* END fs_math.h */ | |||||
#if 0 | |||||
/* | |||||
> > Anybody know where I can get some source code for a | |||||
> > reasonably fast double | |||||
> > precision square root algorithm in C. | |||||
> > I'm looking for one that is not IEEE | |||||
> > compliant as I am running on a Z/OS mainframe. | |||||
> > | |||||
> > I would love to use the standard library but | |||||
> > unfortunatly I'm using a | |||||
> > stripped down version of C that looses the the runtime library | |||||
> > (we have to write our own). | |||||
> | |||||
> long double Ssqrt(long double x) | |||||
> { | |||||
> long double a, b; | |||||
> size_t c; | |||||
size_t is a bug here. | |||||
c needs to be a signed type: | |||||
long c; | |||||
> if (x > 0) { | |||||
> c = 0; | |||||
> while (x > 4) { | |||||
> x /= 4; | |||||
> ++c; | |||||
> } | |||||
> while (1.0 / 4 > x) { | |||||
> x *= 4; | |||||
> --c; | |||||
> } | |||||
> a = x; | |||||
> b = ((4 > a) + a) / 2; | |||||
Not a bug, but should be: | |||||
b = (1 + a) / 2; | |||||
> do { | |||||
> x = b; | |||||
> b = (a / x + x) / 2; | |||||
> } while (x > b); | |||||
> if (c > 0) { | |||||
The above line is why c needs to be a signed type, | |||||
otherwise the decremented values of c, are greater than zero, | |||||
and the function won't work if the initial value of x | |||||
is less than 0.25 | |||||
> while (c--) { | |||||
> x *= 2; | |||||
> } | |||||
> } else { | |||||
> while (c++) { | |||||
> x /= 2; | |||||
> } | |||||
> } | |||||
> } | |||||
> return x; | |||||
> } | |||||
> | |||||
> > | |||||
> > That algorithm was actually 4 times slower | |||||
> > then the one below, and more | |||||
> > code. It was accurate though. | |||||
> > | |||||
> | |||||
> Sorry Pete, I wasn't looking very carefully. | |||||
> When I converted your function | |||||
> to double precision it's was much quicker, the best I've seen yet. | |||||
*/ | |||||
#endif |
@ -0,0 +1,227 @@ | |||||
/* | |||||
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["schUTC"] = getSetting("schUTC", i, 0).toInt() == 1; | |||||
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); | |||||
delSetting("schUTC", i); | |||||
} else { | |||||
#if DEBUG_SUPPORT | |||||
bool 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(); | |||||
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1; | |||||
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 %s on %s%s\n"), | |||||
i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch, | |||||
sch_action, sch_hour, sch_minute, sch_utc ? "UTC" : "local time", | |||||
(char *) sch_weekdays.c_str(), | |||||
sch_enabled ? "" : " (disabled)" | |||||
); | |||||
#endif // DEBUG_SUPPORT | |||||
} | |||||
} | |||||
} | |||||
bool _schIsThisWeekday(time_t t, String weekdays){ | |||||
// Convert from Sunday to Monday as day 1 | |||||
int w = weekday(t) - 1; | |||||
if (0 == w) 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(time_t t, unsigned char schedule_hour, unsigned char schedule_minute){ | |||||
unsigned char now_hour = hour(t); | |||||
unsigned char now_minute = minute(t); | |||||
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute; | |||||
} | |||||
void _schCheck() { | |||||
time_t local_time = now(); | |||||
time_t utc_time = ntpLocal2UTC(local_time); | |||||
// 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; | |||||
// Get the datetime used for the calculation | |||||
bool sch_utc = getSetting("schUTC", i, 0).toInt() == 1; | |||||
time_t t = sch_utc ? utc_time : local_time; | |||||
String sch_weekdays = getSetting("schWDs", i, ""); | |||||
if (_schIsThisWeekday(t, sch_weekdays)) { | |||||
int sch_hour = getSetting("schHour", i, 0).toInt(); | |||||
int sch_minute = getSetting("schMinute", i, 0).toInt(); | |||||
int minutes_to_trigger = _schMinutesLeft(t, 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 |
@ -0,0 +1,198 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// AM2320 Humidity & Temperature sensor over I2C | |||||
// Copyright (C) 2018 by Mustafa Tufan | |||||
// ----------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT && AM2320_SUPPORT | |||||
#pragma once | |||||
#undef I2C_SUPPORT | |||||
#define I2C_SUPPORT 1 // Explicitly request I2C support. | |||||
#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(_address); | |||||
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(_address); | |||||
// 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; | |||||
} | |||||
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 |
@ -0,0 +1,378 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// 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 | |||||
unsigned int difference; | |||||
static unsigned int cf_pulses_last = 0; | |||||
unsigned int cf_pulses = _data[21] << 8 | _data[22]; | |||||
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses; | |||||
if (cf_pulses < cf_pulses_last) { | |||||
difference = cf_pulses + (0xFFFF - cf_pulses_last) + 1; | |||||
} else { | |||||
difference = cf_pulses - cf_pulses_last; | |||||
} | |||||
_energy += difference * (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 |
@ -0,0 +1,174 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// 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 | |||||
// Set ADC to TOUT pin | |||||
#undef ADC_MODE_VALUE | |||||
#define ADC_MODE_VALUE ADC_TOUT | |||||
#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; | |||||
} | |||||
} | |||||
unsigned char _gpio = GPIO_NONE; | |||||
unsigned char _previous = GPIO_NONE; | |||||
double _uvindex = 0; | |||||
}; | |||||
#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT |
@ -0,0 +1,298 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// Geiger Sensor based on Event Counter Sensor | |||||
// Copyright (C) 2018 by Sven Kopetzki <skopetzki at web dot de> | |||||
// Documentation: https://github.com/Trickx/espurna/wiki/Geiger-counter | |||||
// ----------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT && GEIGER_SUPPORT | |||||
#pragma once | |||||
#include "Arduino.h" | |||||
#include "BaseSensor.h" | |||||
class GeigerSensor : public BaseSensor { | |||||
public: | |||||
// --------------------------------------------------------------------- | |||||
// Public | |||||
// --------------------------------------------------------------------- | |||||
GeigerSensor() : BaseSensor() { | |||||
_count = 2; | |||||
_sensor_id = SENSOR_GEIGER_ID; | |||||
} | |||||
~GeigerSensor() { | |||||
_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; | |||||
} | |||||
void setCPM2SievertFactor(unsigned int cpm2sievert) { | |||||
_cpm2sievert = cpm2sievert; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
unsigned char getGPIO() { | |||||
return _gpio; | |||||
} | |||||
unsigned char getMode() { | |||||
return _mode; | |||||
} | |||||
unsigned char getInterruptMode() { | |||||
return _interrupt_mode; | |||||
} | |||||
unsigned long getDebounceTime() { | |||||
return _debounce; | |||||
} | |||||
unsigned long getCPM2SievertFactor() { | |||||
return _cpm2sievert; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
// 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), "µSv/h @ GPIO%d", _gpio); | |||||
return String(buffer); | |||||
} | |||||
// Descriptive name of the slot # index | |||||
String slot(unsigned char index) { | |||||
char buffer[30]; | |||||
unsigned char i=0; | |||||
#if GEIGER_REPORT_CPM | |||||
if (index == i++) { | |||||
snprintf(buffer, sizeof(buffer), "Counts per Minute @ GPIO%d", _gpio); | |||||
return String(buffer); | |||||
} | |||||
#endif | |||||
#if GEIGER_REPORT_SIEVERTS | |||||
if (index == i++) { | |||||
snprintf(buffer, sizeof(buffer), "CPM / %d = µSv/h", _cpm2sievert); | |||||
return String(buffer); | |||||
} | |||||
#endif | |||||
snprintf(buffer, sizeof(buffer), "Events @ GPIO%d", _gpio); | |||||
return String(buffer); | |||||
}; | |||||
// 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) { | |||||
unsigned char i=0; | |||||
#if GEIGER_REPORT_CPM | |||||
if (index == i++) return MAGNITUDE_GEIGER_CPM; | |||||
#endif | |||||
#if GEIGER_REPORT_SIEVERTS | |||||
if (index == i++) return MAGNITUDE_GEIGER_SIEVERT; | |||||
#endif | |||||
return MAGNITUDE_NONE; | |||||
} | |||||
// Current value for slot # index | |||||
double value(unsigned char index) { | |||||
unsigned char i=0; | |||||
#if GEIGER_REPORT_CPM | |||||
if (index == i++) { | |||||
unsigned long _period_begin = _lastreport_cpm; | |||||
_lastreport_cpm = millis(); | |||||
double value = _events * 60000; | |||||
value = value / (_lastreport_cpm-_period_begin); | |||||
#if SENSOR_DEBUG | |||||
char data[128]; char buffer[10]; | |||||
dtostrf(value, 1-sizeof(buffer), 4, buffer); | |||||
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | CPM: %s", _ticks, (_lastreport_cpm-_period_begin), buffer); | |||||
DEBUG_MSG("[GEIGER] %s\n", data); | |||||
#endif | |||||
_events = 0; | |||||
return value; | |||||
} | |||||
#endif | |||||
#if GEIGER_REPORT_SIEVERTS | |||||
if (index == i++) { | |||||
unsigned long _period_begin = _lastreport_sv; | |||||
_lastreport_sv = millis(); | |||||
double value = _ticks * 60000 / _cpm2sievert; | |||||
value = value / (_lastreport_sv-_period_begin); | |||||
#if SENSOR_DEBUG | |||||
char data[128]; char buffer[10]; | |||||
dtostrf(value, 1-sizeof(buffer), 4, buffer); | |||||
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | µSievert: %s", _ticks, (_lastreport_sv-_period_begin), buffer); | |||||
DEBUG_MSG("[GEIGER] %s\n", data); | |||||
#endif | |||||
_ticks = 0; | |||||
return value; | |||||
} | |||||
#endif | |||||
return 0; | |||||
} | |||||
// Handle interrupt calls | |||||
void handleInterrupt(unsigned char gpio) { | |||||
(void) gpio; | |||||
static unsigned long last = 0; | |||||
if (millis() - last > _debounce) { | |||||
_events = _events + 1; | |||||
_ticks = _ticks + 1; | |||||
last = millis(); | |||||
} | |||||
} | |||||
protected: | |||||
// --------------------------------------------------------------------- | |||||
// Interrupt management | |||||
// --------------------------------------------------------------------- | |||||
void _attach(GeigerSensor * 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; | |||||
volatile unsigned long _ticks = 0; | |||||
unsigned long _debounce = GEIGER_DEBOUNCE; | |||||
unsigned int _cpm2sievert = GEIGER_CPM2SIEVERT; | |||||
unsigned char _gpio; | |||||
unsigned char _mode; | |||||
unsigned char _interrupt_mode; | |||||
// Added for µSievert calculations | |||||
unsigned long _lastreport_cpm = millis(); | |||||
unsigned long _lastreport_sv = _lastreport_cpm; | |||||
}; | |||||
// ----------------------------------------------------------------------------- | |||||
// Interrupt helpers | |||||
// ----------------------------------------------------------------------------- | |||||
GeigerSensor * _geiger_sensor_instance[10] = {NULL}; | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr(unsigned char gpio) { | |||||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||||
if (_geiger_sensor_instance[index]) { | |||||
_geiger_sensor_instance[index]->handleInterrupt(gpio); | |||||
} | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_0() { | |||||
_geiger_sensor_isr(0); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_1() { | |||||
_geiger_sensor_isr(1); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_2() { | |||||
_geiger_sensor_isr(2); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_3() { | |||||
_geiger_sensor_isr(3); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_4() { | |||||
_geiger_sensor_isr(4); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_5() { | |||||
_geiger_sensor_isr(5); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_12() { | |||||
_geiger_sensor_isr(12); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_13() { | |||||
_geiger_sensor_isr(13); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_14() { | |||||
_geiger_sensor_isr(14); | |||||
} | |||||
void ICACHE_RAM_ATTR _geiger_sensor_isr_15() { | |||||
_geiger_sensor_isr(15); | |||||
} | |||||
static void (*_geiger_sensor_isr_list[10])() = { | |||||
_geiger_sensor_isr_0, _geiger_sensor_isr_1, _geiger_sensor_isr_2, | |||||
_geiger_sensor_isr_3, _geiger_sensor_isr_4, _geiger_sensor_isr_5, | |||||
_geiger_sensor_isr_12, _geiger_sensor_isr_13, _geiger_sensor_isr_14, | |||||
_geiger_sensor_isr_15 | |||||
}; | |||||
void GeigerSensor::_attach(GeigerSensor * instance, unsigned char gpio, unsigned char mode) { | |||||
if (!gpioValid(gpio)) return; | |||||
_detach(gpio); | |||||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||||
_geiger_sensor_instance[index] = instance; | |||||
attachInterrupt(gpio, _geiger_sensor_isr_list[index], mode); | |||||
#if SENSOR_DEBUG | |||||
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str()); | |||||
#endif | |||||
} | |||||
void GeigerSensor::_detach(unsigned char gpio) { | |||||
if (!gpioValid(gpio)) return; | |||||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||||
if (_geiger_sensor_instance[index]) { | |||||
detachInterrupt(gpio); | |||||
#if SENSOR_DEBUG | |||||
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt detached from %s\n"), gpio, _geiger_sensor_instance[index]->description().c_str()); | |||||
#endif | |||||
_geiger_sensor_instance[index] = NULL; | |||||
} | |||||
} | |||||
#endif // SENSOR_SUPPORT && GEIGER_SUPPORT |
@ -0,0 +1,119 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// HC-SR04 Ultrasonic sensor | |||||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com> | |||||
// ----------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT && HCSR04_SUPPORT | |||||
#pragma once | |||||
#include "Arduino.h" | |||||
#include "BaseSensor.h" | |||||
class HCSR04Sensor : public BaseSensor { | |||||
public: | |||||
// --------------------------------------------------------------------- | |||||
// Public | |||||
// --------------------------------------------------------------------- | |||||
HCSR04Sensor(): BaseSensor() { | |||||
_count = 1; | |||||
_sensor_id = SENSOR_HCSR04_ID; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
void setEcho(unsigned char echo) { | |||||
_echo = echo; | |||||
} | |||||
void setTrigger(unsigned char trigger) { | |||||
_trigger = trigger; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
unsigned char getEcho() { | |||||
return _echo; | |||||
} | |||||
unsigned char getTrigger() { | |||||
return _trigger; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
// Sensor API | |||||
// --------------------------------------------------------------------- | |||||
// Initialization method, must be idempotent | |||||
void begin() { | |||||
pinMode(_echo, INPUT); | |||||
pinMode(_trigger, OUTPUT); | |||||
digitalWrite(_trigger, LOW); | |||||
_ready = true; | |||||
} | |||||
// Descriptive name of the sensor | |||||
String description() { | |||||
char buffer[24]; | |||||
snprintf(buffer, sizeof(buffer), "HCSR04 @ GPIO(%u, %u)", _trigger, _echo); | |||||
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(_trigger); | |||||
} | |||||
// Type for slot # index | |||||
unsigned char type(unsigned char index) { | |||||
if (index == 0) return MAGNITUDE_DISTANCE; | |||||
return MAGNITUDE_NONE; | |||||
} | |||||
// Current value for slot # index | |||||
double value(unsigned char index) { | |||||
if (index == 0) { | |||||
// Trigger pulse | |||||
digitalWrite(_trigger, HIGH); | |||||
delayMicroseconds(10); | |||||
digitalWrite(_trigger, LOW); | |||||
// Wait for echo pulse low-high-low | |||||
while ( digitalRead(_echo) == 0 ) yield(); | |||||
unsigned long start = micros(); | |||||
while ( digitalRead(_echo) == 1 ) yield(); | |||||
unsigned long travel_time = micros() - start; | |||||
// Assuming a speed of sound of 340m/s | |||||
// Dividing by 2 since it is a round trip | |||||
return 340.0 * (double) travel_time / 1000000.0 / 2; | |||||
} | |||||
return 0; | |||||
} | |||||
protected: | |||||
// --------------------------------------------------------------------- | |||||
// Protected | |||||
// --------------------------------------------------------------------- | |||||
unsigned char _trigger; | |||||
unsigned char _echo; | |||||
}; | |||||
#endif // SENSOR_SUPPORT && HCSR04_SUPPORT |
@ -0,0 +1,133 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// 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() { | |||||
_count = 4; | |||||
_sensor_id = SENSOR_PZEM004T_ID; | |||||
_ip = IPAddress(192,168,1,1); | |||||
} | |||||
~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(HardwareSerial * serial) { | |||||
_serial = serial; | |||||
_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 (_pzem) delete _pzem; | |||||
if (_serial) { | |||||
_pzem = new PZEM004T(_serial); | |||||
} else { | |||||
_pzem = new PZEM004T(_pin_rx, _pin_tx); | |||||
} | |||||
_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; | |||||
IPAddress _ip; | |||||
HardwareSerial * _serial = NULL; | |||||
PZEM004T * _pzem = NULL; | |||||
}; | |||||
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT |
@ -0,0 +1,233 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// SenseAir S8 CO2 Sensor | |||||
// Uses SoftwareSerial library | |||||
// Contribution by Yonsm Guo | |||||
// ----------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT && SENSEAIR_SUPPORT | |||||
#pragma once | |||||
#include "Arduino.h" | |||||
#include "BaseSensor.h" | |||||
#include <SoftwareSerial.h> | |||||
// SenseAir sensor utils | |||||
class SenseAir | |||||
{ | |||||
protected: | |||||
SoftwareSerial *_serial; // Should initialized by child class | |||||
public: | |||||
int sendCommand(byte command[]) { | |||||
byte recv_buf[7] = {0xff}; | |||||
byte data_buf[2] = {0xff}; | |||||
long value = -1; | |||||
_serial->write(command, 8); //Send the byte array | |||||
delay(50); | |||||
// Read answer from sensor | |||||
int ByteCounter = 0; | |||||
while(_serial->available()) { | |||||
recv_buf[ByteCounter] = _serial->read(); | |||||
ByteCounter++; | |||||
} | |||||
data_buf[0] = recv_buf[3]; | |||||
data_buf[1] = recv_buf[4]; | |||||
value = (data_buf[0] << 8) | (data_buf[1]); | |||||
return value; | |||||
} | |||||
int readCo2(void) { | |||||
int co2 = 0; | |||||
byte frame[8] = {0}; | |||||
buildFrame(0xFE, 0x04, 0x03, 1, frame); | |||||
co2 = sendCommand(frame); | |||||
return co2; | |||||
} | |||||
private: | |||||
// Compute the MODBUS RTU CRC | |||||
static unsigned int modRTU_CRC(byte buf[], int len, byte checkSum[2]) { | |||||
unsigned int crc = 0xFFFF; | |||||
for (int pos = 0; pos < len; pos++) { | |||||
crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc | |||||
for (int i = 8; i != 0; i--) { // Loop over each bit | |||||
if ((crc & 0x0001) != 0) { // If the LSB is set | |||||
crc >>= 1; // Shift right and XOR 0xA001 | |||||
crc ^= 0xA001; | |||||
} | |||||
else // Else LSB is not set | |||||
crc >>= 1; // Just shift right | |||||
} | |||||
} | |||||
// Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) | |||||
checkSum[1] = (byte)((crc >> 8) & 0xFF); | |||||
checkSum[0] = (byte)(crc & 0xFF); | |||||
return crc; | |||||
} | |||||
static int getBitOfInt(int reg, int pos) { | |||||
// Create a mask | |||||
int mask = 0x01 << pos; | |||||
// Mask the status register | |||||
int masked_register = mask & reg; | |||||
// Shift the result of masked register back to position 0 | |||||
int result = masked_register >> pos; | |||||
return result; | |||||
} | |||||
static void buildFrame(byte slaveAddress, | |||||
byte functionCode, | |||||
short startAddress, | |||||
short numberOfRegisters, | |||||
byte frame[8]) { | |||||
frame[0] = slaveAddress; | |||||
frame[1] = functionCode; | |||||
frame[2] = (byte)(startAddress >> 8); | |||||
frame[3] = (byte)(startAddress); | |||||
frame[4] = (byte)(numberOfRegisters >> 8); | |||||
frame[5] = (byte)(numberOfRegisters); | |||||
// CRC-calculation | |||||
byte checkSum[2] = {0}; | |||||
modRTU_CRC(frame, 6, checkSum); | |||||
frame[6] = checkSum[0]; | |||||
frame[7] = checkSum[1]; | |||||
} | |||||
}; | |||||
// | |||||
class SenseAirSensor : public BaseSensor, SenseAir { | |||||
public: | |||||
// --------------------------------------------------------------------- | |||||
// Public | |||||
// --------------------------------------------------------------------- | |||||
SenseAirSensor(): BaseSensor() { | |||||
_count = 1; | |||||
_co2 = 0; | |||||
_lastCo2 = 0; | |||||
_serial = NULL; | |||||
_sensor_id = SENSOR_SENSEAIR_ID; | |||||
} | |||||
~SenseAirSensor() { | |||||
if (_serial) delete _serial; | |||||
_serial = NULL; | |||||
} | |||||
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, 64); | |||||
_serial->enableIntTx(false); | |||||
_serial->begin(9600); | |||||
_serial->enableRx(true); | |||||
_startTime = 0; | |||||
_ready = true; | |||||
_dirty = false; | |||||
} | |||||
// Descriptive name of the sensor | |||||
String description() { | |||||
char buffer[28]; | |||||
snprintf(buffer, sizeof(buffer), "SenseAir S8 @ 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) { | |||||
return MAGNITUDE_CO2; | |||||
} | |||||
void pre() { | |||||
if (millis() - _startTime < 20000) { | |||||
_error = SENSOR_ERROR_WARM_UP; | |||||
return; | |||||
} | |||||
_error = SENSOR_ERROR_OK; | |||||
unsigned int co2 = readCo2(); | |||||
if (co2 >= 5000 || co2 < 100) | |||||
{ | |||||
_co2 = _lastCo2; | |||||
} | |||||
else | |||||
{ | |||||
_co2 = (co2 > _lastCo2 + 2000) ? _lastCo2 : co2; | |||||
_lastCo2 = co2; | |||||
} | |||||
} | |||||
// Current value for slot # index | |||||
double value(unsigned char index) { | |||||
return _co2; | |||||
} | |||||
protected: | |||||
unsigned int _pin_rx; | |||||
unsigned int _pin_tx; | |||||
unsigned long _startTime; | |||||
unsigned int _co2; | |||||
unsigned int _lastCo2; | |||||
}; | |||||
#endif // SENSOR_SUPPORT && SENSEAIR_SUPPORT |
@ -0,0 +1,94 @@ | |||||
// ----------------------------------------------------------------------------- | |||||
// TMP3X Temperature Analog Sensor | |||||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com> | |||||
// ----------------------------------------------------------------------------- | |||||
#if SENSOR_SUPPORT && TMP3X_SUPPORT | |||||
#pragma once | |||||
// Set ADC to TOUT pin | |||||
#undef ADC_MODE_VALUE | |||||
#define ADC_MODE_VALUE ADC_TOUT | |||||
#include "Arduino.h" | |||||
#include "BaseSensor.h" | |||||
#define TMP3X_TMP35 35 | |||||
#define TMP3X_TMP36 36 | |||||
#define TMP3X_TMP37 37 | |||||
class TMP3XSensor : public BaseSensor { | |||||
public: | |||||
// --------------------------------------------------------------------- | |||||
// Public | |||||
// --------------------------------------------------------------------- | |||||
TMP3XSensor(): BaseSensor() { | |||||
_count = 1; | |||||
_sensor_id = SENSOR_TMP3X_ID; | |||||
} | |||||
void setType(unsigned char type) { | |||||
if (35 <= type && type <= 37) { | |||||
_type = type; | |||||
} | |||||
} | |||||
unsigned char getType() { | |||||
return _type; | |||||
} | |||||
// --------------------------------------------------------------------- | |||||
// Sensor API | |||||
// --------------------------------------------------------------------- | |||||
// Initialization method, must be idempotent | |||||
void begin() { | |||||
pinMode(0, INPUT); | |||||
_ready = true; | |||||
} | |||||
// Descriptive name of the sensor | |||||
String description() { | |||||
char buffer[14]; | |||||
snprintf(buffer, sizeof(buffer), "TMP%d @ TOUT", _type); | |||||
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("0"); | |||||
} | |||||
// Type for slot # index | |||||
unsigned char type(unsigned char index) { | |||||
if (index == 0) return MAGNITUDE_TEMPERATURE; | |||||
return MAGNITUDE_NONE; | |||||
} | |||||
// Current value for slot # index | |||||
double value(unsigned char index) { | |||||
if (index == 0) { | |||||
double mV = 3300.0 * analogRead(0) / 1024.0; | |||||
if (_type == TMP3X_TMP35) return mV / 10.0; | |||||
if (_type == TMP3X_TMP36) return mV / 10.0 - 50.0; | |||||
if (_type == TMP3X_TMP37) return mV / 20.0; | |||||
} | |||||
return 0; | |||||
} | |||||
private: | |||||
unsigned char _type = TMP3X_TMP35; | |||||
}; | |||||
#endif // SENSOR_SUPPORT && TMP3X_SUPPORT |