@ -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 |
@ -1,16 +1,30 @@ | |||
language: python | |||
python: | |||
- "2.7" | |||
- '2.7' | |||
sudo: false | |||
cache: | |||
directories: | |||
- "~/.platformio" | |||
directories: | |||
- "~/.platformio" | |||
install: | |||
- pip install -U platformio | |||
- cd code ; npm install --only=dev ; cd .. | |||
- pip install -U platformio | |||
- cd code ; npm install --only=dev ; cd .. | |||
script: | |||
- cd code ; platformio run -e itead-sonoff-basic | |||
branches: | |||
only: | |||
- master | |||
- dev | |||
- 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 | |||
core_version.h | |||
custom.h | |||
.DS_Store | |||
.gcc-flags.json | |||
.pioenvs | |||
.piolibdeps | |||
.python-version | |||
.travis.yml | |||
.vscode | |||
.vscode/.browse.c_cpp.db* | |||
.vscode/c_cpp_properties.json | |||
core_version.h | |||
.pioenvs | |||
.piolibdeps | |||
.vscode/launch.json |
@ -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" |
@ -1,24 +1,39 @@ | |||
/* | |||
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. | |||
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. | |||
(Define USE_CUSTOM_H on commandline for platformio: | |||
export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" ) | |||
*/ | |||
#ifdef USE_CUSTOM_H | |||
#include "custom.h" | |||
#endif | |||
#include "version.h" | |||
#include "types.h" | |||
#include "arduino.h" | |||
#include "hardware.h" | |||
#include "defaults.h" | |||
#include "general.h" | |||
#include "prototypes.h" | |||
#include "sensors.h" | |||
#include "dependencies.h" | |||
#include "progmem.h" | |||
#include "debug.h" | |||
#ifdef USE_CORE_VERSION_H | |||
#include "core_version.h" | |||
@ -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,302 @@ | |||
//------------------------------------------------------------------------------ | |||
// 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 | |||
// ----------------------------------------------------------------------------- | |||
// 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_VERSION "1.12.2b" | |||
#define APP_VERSION "1.13.0c" | |||
#define APP_REVISION "db84006" | |||
#define APP_AUTHOR "xose.perez@gmail.com" | |||
#define APP_WEBSITE "http://tinkerman.cat" | |||
#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,181 +0,0 @@ | |||
/* | |||
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; | |||
// ----------------------------------------------------------------------------- | |||
void _haWebSocketOnSend(JsonObject& root) { | |||
root["haVisible"] = 1; | |||
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX); | |||
} | |||
#if SENSOR_SUPPORT | |||
void _haSendMagnitude(unsigned char i) { | |||
String output; | |||
if (_haEnabled) { | |||
DynamicJsonBuffer jsonBuffer; | |||
JsonObject& root = jsonBuffer.createObject(); | |||
unsigned char type = magnitudeType(i); | |||
root["device_class"] = "sensor"; | |||
root["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type); | |||
root["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); | |||
root["unit_of_measurement"] = magnitudeUnits(type); | |||
root.printTo(output); | |||
} | |||
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) + | |||
"/sensor/" + | |||
getSetting("hostname") + "_" + String(i) + | |||
"/config"; | |||
mqttSendRaw(topic.c_str(), output.c_str()); | |||
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); | |||
} | |||
void _haSendMagnitudes() { | |||
for (unsigned char i=0; i<magnitudeCount(); i++) { | |||
_haSendMagnitude(i); | |||
} | |||
} | |||
#endif | |||
void _haSendSwitch(unsigned char i) { | |||
String output; | |||
if (_haEnabled) { | |||
DynamicJsonBuffer jsonBuffer; | |||
JsonObject& root = jsonBuffer.createObject(); | |||
String name = getSetting("hostname"); | |||
if (relayCount() > 1) { | |||
name += String(" switch #") + String(i); | |||
} | |||
root["name"] = name; | |||
root["platform"] = "mqtt"; | |||
if (relayCount()) { | |||
root["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false); | |||
root["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true); | |||
root["payload_on"] = String("1"); | |||
root["payload_off"] = String("0"); | |||
root["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false); | |||
root["payload_available"] = String("1"); | |||
root["payload_not_available"] = String("0"); | |||
} | |||
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE | |||
if (i == 0) { | |||
if (lightHasColor()) { | |||
root["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false); | |||
root["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true); | |||
root["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false); | |||
root["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true); | |||
root["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true); | |||
} | |||
if (lightChannels() > 3) { | |||
root["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false); | |||
root["white_value_command_topic"] = mqttTopic(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") + "_" + String(i) + | |||
"/config"; | |||
mqttSendRaw(topic.c_str(), output.c_str()); | |||
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); | |||
} | |||
void _haSendSwitches() { | |||
for (unsigned char i=0; i<relayCount(); i++) { | |||
_haSendSwitch(i); | |||
} | |||
} | |||
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(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
void haSetup() { | |||
_haConfigure(); | |||
#if WEB_SUPPORT | |||
wsOnSendRegister(_haWebSocketOnSend); | |||
wsOnAfterParseRegister(_haConfigure); | |||
#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(); | |||
}); | |||
} | |||
#endif // HOMEASSISTANT_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,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 |
@ -0,0 +1,105 @@ | |||
/* | |||
UART_MQTT MODULE | |||
Copyright (C) 2018 by Albert Weterings | |||
Adapted by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if UART_MQTT_SUPPORT | |||
char _uartmqttBuffer[UART_MQTT_BUFFER_SIZE]; | |||
bool _uartmqttNewData = false; | |||
#if UART_MQTT_USE_SOFT | |||
#include <SoftwareSerial.h> | |||
SoftwareSerial _uart_mqtt_serial(UART_MQTT_RX_PIN, UART_MQTT_TX_PIN, false, UART_MQTT_BUFFER_SIZE); | |||
#define UART_MQTT_PORT _uart_mqtt_serial | |||
#else | |||
#define UART_MQTT_PORT UART_MQTT_HW_PORT | |||
#endif | |||
// ----------------------------------------------------------------------------- | |||
// Private | |||
// ----------------------------------------------------------------------------- | |||
void _uartmqttReceiveUART() { | |||
static unsigned char ndx = 0; | |||
while (UART_MQTT_PORT.available() > 0 && _uartmqttNewData == false) { | |||
char rc = UART_MQTT_PORT.read(); | |||
if (rc != UART_MQTT_TERMINATION) { | |||
_uartmqttBuffer[ndx] = rc; | |||
if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++; | |||
} else { | |||
_uartmqttBuffer[ndx] = '\0'; | |||
_uartmqttNewData = true; | |||
ndx = 0; | |||
} | |||
} | |||
} | |||
void _uartmqttSendMQTT() { | |||
if (_uartmqttNewData == true && MQTT_SUPPORT) { | |||
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over MQTT: %s\n"), _uartmqttBuffer); | |||
mqttSend(MQTT_TOPIC_UARTIN, _uartmqttBuffer); | |||
_uartmqttNewData = false; | |||
} | |||
} | |||
void _uartmqttSendUART(const char * message) { | |||
DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over UART: %s\n"), message); | |||
UART_MQTT_PORT.println(message); | |||
} | |||
void _uartmqttMQTTCallback(unsigned int type, const char * topic, const char * payload) { | |||
if (type == MQTT_CONNECT_EVENT) { | |||
mqttSubscribe(MQTT_TOPIC_UARTOUT); | |||
} | |||
if (type == MQTT_MESSAGE_EVENT) { | |||
// Match topic | |||
String t = mqttMagnitude((char *) topic); | |||
if (t.equals(MQTT_TOPIC_UARTOUT)) { | |||
_uartmqttSendUART(payload); | |||
} | |||
} | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// SETUP & LOOP | |||
// ----------------------------------------------------------------------------- | |||
void _uartmqttLoop() { | |||
_uartmqttReceiveUART(); | |||
_uartmqttSendMQTT(); | |||
} | |||
void uartmqttSetup() { | |||
// Init port | |||
UART_MQTT_PORT.begin(UART_MQTT_BAUDRATE); | |||
// Register MQTT callbackj | |||
mqttRegister(_uartmqttMQTTCallback); | |||
// Register loop | |||
espurnaRegisterLoop(_uartmqttLoop); | |||
} | |||
#endif // UART_MQTT_SUPPORT |