@ -0,0 +1,48 @@ | |||
--- | |||
name: Bug report | |||
about: Create a report to help us improve | |||
title: '' | |||
labels: '' | |||
assignees: '' | |||
--- | |||
*Before creating a new issue please check that you have:* | |||
* *searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)* | |||
* *searched the [wiki](https://github.com/xoseperez/espurna/wiki)* | |||
* *asked for help in the [chat](https://gitter.im/tinkerman-cat/espurna)* | |||
* *done the previous things again :)* | |||
*Fulfilling this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.* | |||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.* | |||
**Bug description** | |||
*A clear and concise description of what the bug is.* | |||
**Steps to reproduce** | |||
*Steps to reproduce the behavior.* | |||
**Expected behavior** | |||
*A clear and concise description of what you expected to happen.* | |||
**Screenshots** | |||
*If applicable, add screenshots to help explain your problem.* | |||
**Device information** | |||
*Copy-paste here the information as it is outputted by the device. You can get this information by typing `info` via serial, terminal or in the debug tab in the web UI. The relevant information is that surrounded by the scissors-cut lines (`---8<-------`).* | |||
*If you cannot get this info from the device, please answer this questions:* | |||
* *Arduino Core version* | |||
* *ESPurna version* | |||
* *Flash mode* | |||
* *Device brand, model and version* | |||
**Tools used** | |||
* *Desktop operating system* | |||
* *Browser & version* | |||
* *IDE & version* | |||
* *Compiler & version (if not embedded in IDE)* | |||
**Additional context** | |||
*Add any other context about the problem here.* |
@ -0,0 +1,26 @@ | |||
--- | |||
name: Feature request | |||
about: Suggest an idea for this project | |||
title: '' | |||
labels: enhancement | |||
assignees: '' | |||
--- | |||
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)* | |||
*Fulfilling this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.* | |||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.* | |||
**Is your feature request related to a problem? Please describe.** | |||
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]* | |||
**Describe the solution you'd like** | |||
*A clear and concise description of what you want to happen.* | |||
**Describe alternatives you've considered** | |||
*A clear and concise description of any alternative solutions or features you've considered.* | |||
**Additional context** | |||
*Add any other context or screenshots about the feature request here.* |
@ -0,0 +1,42 @@ | |||
--- | |||
name: Questions & troubleshooting | |||
about: Anything not a bug or feature request | |||
title: '' | |||
labels: question | |||
assignees: '' | |||
--- | |||
*Before creating a new issue please check that you have:* | |||
* *searched the existing [issues](https://github.com/xoseperez/espurna/issues) (both open and closed)* | |||
* *searched the [wiki](https://github.com/xoseperez/espurna/wiki)* | |||
* *asked for help in the [chat](https://gitter.im/tinkerman-cat/espurna)* | |||
* *done the previous things again :)* | |||
*Fulfilling this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.* | |||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.* | |||
**Question** | |||
*A clear and concise description of what the problem/doubt is.* | |||
**Screenshots** | |||
*If applicable, add screenshots to help explain your problem.* | |||
**Device information** | |||
*Copy-paste here the information as it is outputted by the device. You can get this information by typing `info` via serial, terminal or in the debug tab in the web UI. The relevant information is that surrounded by the scissors-cut lines (`---8<-------`).* | |||
*If you cannot get this info from the device, please answer this questions:* | |||
* *Arduino Core version* | |||
* *ESPurna version* | |||
* *Flash mode* | |||
* *Device brand, model and version* | |||
**Tools used** | |||
* *Desktop operating system* | |||
* *Browser & version* | |||
* *IDE & version* | |||
* *Compiler & version (if not embedded in IDE)* | |||
**Additional context** | |||
*Add any other context about the problem here.* |
@ -0,0 +1,16 @@ | |||
Do you want to do a pull request? | |||
First things first: **THANK YOU!**. ESPurna started as a personal project and it will be great if it becomes a community project. There are so many things that can be improved, added and fixed (yeah, a lot a small bugs and not so small bugs there, I'm sure). And sometimes I just don't have the time to work on it as much as I'd like to. | |||
Second. Let's try to keep it homogeneous and readable. I have my coding style. It's mostly standard but sometimes it can be opinionated. It you are willing to do a pull request, there are a few things I would ask you first: | |||
## Pull request ## | |||
* Do the pull request against the **`dev` branch** | |||
* **Only touch relevant files** (beware if your editor has auto-formatting feature enabled) | |||
* If you are adding a new functionality (new hardware, new library support) not related to an existing component move it to it's **own modules** (.ino file) | |||
* If you are adding new library, include it in one of the **sample travis profiles**, so our integrated CI will try to compile it. | |||
* Make sure you check [Coding Style](https://github.com/xoseperez/espurna/wiki/CodingStyle) | |||
* PRs that don't compile (break Travis) or cause more coding errors (as reported by Codacy) will not be merged. Please fix the issue. Same goes for PRs that are raised against older commit in dev - you might need to rebase and resolve conflicts. | |||
And thank you again! |
@ -0,0 +1,8 @@ | |||
# ESPurna Support | |||
If you're looking for support for ESPurna there are some options available: | |||
* [Issues](https://github.com/xoseperez/espurna/issues?utf8=%E2%9C%93&q=is%3Aissue): this is the most dinamic channel at the moment, you might find an answer to your question by searching current or closed issues. | |||
* [Wiki pages](https://github.com/xoseperez/espurna/wiki): might not be as up-to-date as we all would like (hey, you can also contribute in the documentation!). | |||
* [Gitter channel](https://gitter.im/tinkerman-cat/espurna): you have better chances to get fast answers from me or other ESPurna users. | |||
* [Issue a question](https://github.com/xoseperez/espurna/issues/new/choose): as a last resort, you can open new *question* issues on GitHub. Just remember: the more info you provide the more chances you'll have to get an accurate answer. |
@ -0,0 +1,9 @@ | |||
#pragma once | |||
// 1.13.3 added TELNET_PASSWORD build-only flag | |||
// 1.13.4 replaces it with TELNET_AUTHENTICATION runtime setting default | |||
// TODO warning should be removed eventually | |||
#ifdef TELNET_PASSWORD | |||
#warning TELNET_PASSWORD is deprecated! Please replace it with TELNET_AUTHENTICATION | |||
#define TELNET_AUTHENTICATION TELNET_PASSWORD | |||
#endif |
@ -1,5 +1,5 @@ | |||
#define APP_NAME "ESPURNA" | |||
#define APP_VERSION "1.13.3" | |||
#define APP_VERSION "1.13.4" | |||
#define APP_AUTHOR "xose.perez@gmail.com" | |||
#define APP_WEBSITE "http://tinkerman.cat" | |||
#define CFG_VERSION 3 |
@ -0,0 +1,157 @@ | |||
// ----------------------------------------------------------------------------- | |||
// Save crash info | |||
// Taken from krzychb EspSaveCrash | |||
// https://github.com/krzychb/EspSaveCrash | |||
// ----------------------------------------------------------------------------- | |||
#if DEBUG_SUPPORT | |||
#include <stdio.h> | |||
#include <stdarg.h> | |||
#include <EEPROM_Rotate.h> | |||
extern "C" { | |||
#include "user_interface.h" | |||
} | |||
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data | |||
/** | |||
* Structure of the single crash data set | |||
* | |||
* 1. Crash time | |||
* 2. Restart reason | |||
* 3. Exception cause | |||
* 4. epc1 | |||
* 5. epc2 | |||
* 6. epc3 | |||
* 7. excvaddr | |||
* 8. depc | |||
* 9. adress of stack start | |||
* 10. adress of stack end | |||
* 11. stack trace bytes | |||
* ... | |||
*/ | |||
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes | |||
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte | |||
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte | |||
#define SAVE_CRASH_EPC1 0x06 // 4 bytes | |||
#define SAVE_CRASH_EPC2 0x0A // 4 bytes | |||
#define SAVE_CRASH_EPC3 0x0E // 4 bytes | |||
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes | |||
#define SAVE_CRASH_DEPC 0x16 // 4 bytes | |||
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes | |||
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes | |||
#define SAVE_CRASH_STACK_TRACE 0x22 // variable | |||
/** | |||
* Save crash information in EEPROM | |||
* This function is called automatically if ESP8266 suffers an exception | |||
* It should be kept quick / consise to be able to execute before hardware wdt may kick in | |||
*/ | |||
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) { | |||
// Do not record crash data when resetting the board | |||
if (checkNeedsReset()) { | |||
return; | |||
} | |||
// This method assumes EEPROM has already been initialized | |||
// which is the first thing ESPurna does | |||
// write crash time to EEPROM | |||
uint32_t crash_time = millis(); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); | |||
// write reset info to EEPROM | |||
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason); | |||
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause); | |||
// write epc1, epc2, epc3, excvaddr and depc to EEPROM | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc); | |||
// write stack start and end address to EEPROM | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); | |||
// starting address of Embedis data plus reserve | |||
const uint16_t settings_start = SPI_FLASH_SEC_SIZE - settingsSize() - 0x10; | |||
// write stack trace to EEPROM and avoid overwriting settings | |||
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; | |||
for (uint32_t i = stack_start; i < stack_end; i++) { | |||
if (current_address >= settings_start) break; | |||
byte* byteValue = (byte*) i; | |||
EEPROMr.write(current_address++, *byteValue); | |||
} | |||
EEPROMr.commit(); | |||
} | |||
/** | |||
* Clears crash info | |||
*/ | |||
void crashClear() { | |||
uint32_t crash_time = 0xFFFFFFFF; | |||
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); | |||
EEPROMr.commit(); | |||
} | |||
/** | |||
* Print out crash information that has been previusly saved in EEPROM | |||
*/ | |||
void crashDump() { | |||
uint32_t crash_time; | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); | |||
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) { | |||
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n")); | |||
return; | |||
} | |||
DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time); | |||
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON)); | |||
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE)); | |||
uint32_t epc1, epc2, epc3, excvaddr, depc; | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1); | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2); | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3); | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr); | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc); | |||
DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3); | |||
DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc); | |||
uint32_t stack_start, stack_end; | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); | |||
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); | |||
DEBUG_MSG_P(PSTR("[DEBUG] sp=0x%08x end=0x%08x\n"), stack_start, stack_end); | |||
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; | |||
int16_t stack_len = stack_end - stack_start; | |||
uint32_t stack_trace; | |||
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] ")); | |||
for (int16_t i = 0; i < stack_len; i += 0x10) { | |||
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i); | |||
for (byte j = 0; j < 4; j++) { | |||
EEPROMr.get(current_address, stack_trace); | |||
DEBUG_MSG_P(PSTR("%08x "), stack_trace); | |||
current_address += 4; | |||
} | |||
DEBUG_MSG_P(PSTR("\n[DEBUG] ")); | |||
} | |||
DEBUG_MSG_P(PSTR("<<<stack<<<\n")); | |||
} | |||
#endif // DEBUG_SUPPORT |
@ -1,192 +0,0 @@ | |||
/* | |||
RF MODULE | |||
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if RF_SUPPORT | |||
#include <RCSwitch.h> | |||
RCSwitch * _rfModem; | |||
unsigned long _rf_learn_start = 0; | |||
unsigned char _rf_learn_id = 0; | |||
bool _rf_learn_status = true; | |||
bool _rf_learn_active = false; | |||
// ----------------------------------------------------------------------------- | |||
// RF | |||
// ----------------------------------------------------------------------------- | |||
unsigned long _rfRetrieve(unsigned char id, bool status) { | |||
String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0"); | |||
return strtoul(code.c_str(), 0, 16); | |||
} | |||
void _rfStore(unsigned char id, bool status, unsigned long code) { | |||
DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => %X\n"), id, status ? "ON" : "OFF", code); | |||
char buffer[20]; | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), code); | |||
setSetting(status ? "rfbON" : "rfbOFF", id, buffer); | |||
} | |||
void _rfLearn(unsigned char id, bool status) { | |||
_rf_learn_start = millis(); | |||
_rf_learn_id = id; | |||
_rf_learn_status = status; | |||
_rf_learn_active = true; | |||
} | |||
void _rfForget(unsigned char id, bool status) { | |||
delSetting(status ? "rfbON" : "rfbOFF", id); | |||
// Websocket update | |||
#if WEB_SUPPORT | |||
char wsb[100]; | |||
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0); | |||
wsSend(wsb); | |||
#endif | |||
} | |||
bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) { | |||
bool found = false; | |||
DEBUG_MSG_P(PSTR("[RF] Trying to match code %X\n"), code); | |||
for (unsigned char i=0; i<relayCount(); i++) { | |||
unsigned long code_on = _rfRetrieve(i, true); | |||
unsigned long code_off = _rfRetrieve(i, false); | |||
if (code == code_on) { | |||
DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i); | |||
value = 1; | |||
found = true; | |||
} | |||
if (code == code_off) { | |||
DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i); | |||
if (found) value = 2; | |||
found = true; | |||
} | |||
if (found) { | |||
relayID = i; | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// WEB | |||
// ----------------------------------------------------------------------------- | |||
void _rfWebSocketOnSend(JsonObject& root) { | |||
char buffer[20]; | |||
root["rfbVisible"] = 1; | |||
root["rfbCount"] = relayCount(); | |||
JsonArray& rfb = root.createNestedArray("rfb"); | |||
for (byte id=0; id<relayCount(); id++) { | |||
for (byte status=0; status<2; status++) { | |||
JsonObject& node = rfb.createNestedObject(); | |||
snprintf_P(buffer, sizeof(buffer), PSTR("%X"), _rfRetrieve(id, status == 1)); | |||
node["id"] = id; | |||
node["status"] = status; | |||
node["data"] = String(buffer); | |||
} | |||
} | |||
} | |||
void _rfWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { | |||
if (strcmp(action, "rfblearn") == 0) _rfLearn(data["id"], data["status"]); | |||
if (strcmp(action, "rfbforget") == 0) _rfForget(data["id"], data["status"]); | |||
if (strcmp(action, "rfbsend") == 0) _rfStore(data["id"], data["status"], data["data"].as<long>()); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
void rfLoop() { | |||
if (_rfModem->available()) { | |||
static unsigned long last = 0; | |||
if (millis() - last > RF_DEBOUNCE) { | |||
last = millis(); | |||
if (_rfModem->getReceivedValue() > 0) { | |||
unsigned long rf_code = _rfModem->getReceivedValue(); | |||
DEBUG_MSG_P(PSTR("[RF] Received code: %X\n"), rf_code); | |||
if (_rf_learn_active) { | |||
_rf_learn_active = false; | |||
_rfStore(_rf_learn_id, _rf_learn_status, rf_code); | |||
// Websocket update | |||
#if WEB_SUPPORT | |||
char wsb[100]; | |||
snprintf_P( | |||
wsb, sizeof(wsb), | |||
PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%X\"}]}"), | |||
_rf_learn_id, _rf_learn_status ? 1 : 0, rf_code); | |||
wsSend(wsb); | |||
#endif | |||
} else { | |||
unsigned char id; | |||
unsigned char value; | |||
if (_rfMatch(rf_code, id, value)) { | |||
if (2 == value) { | |||
relayToggle(id); | |||
} else { | |||
relayStatus(id, 1 == value); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
_rfModem->resetAvailable(); | |||
} | |||
if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) { | |||
_rf_learn_active = false; | |||
} | |||
} | |||
void rfSetup() { | |||
_rfModem = new RCSwitch(); | |||
_rfModem->enableReceive(RF_PIN); | |||
DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN); | |||
#if WEB_SUPPORT | |||
wsOnSendRegister(_rfWebSocketOnSend); | |||
wsOnActionRegister(_rfWebSocketOnAction); | |||
#endif | |||
// Register loop | |||
espurnaRegisterLoop(rfLoop); | |||
} | |||
#endif |
@ -0,0 +1,253 @@ | |||
// ----------------------------------------------------------------------------- | |||
// BMP085/BMP180 Sensor over I2C | |||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && BMP180_SUPPORT | |||
#pragma once | |||
#undef I2C_SUPPORT | |||
#define I2C_SUPPORT 1 // Explicitly request I2C support. | |||
#include "Arduino.h" | |||
#include "I2CSensor.h" | |||
#define BMP180_CHIP_ID 0x55 | |||
#define BMP180_REGISTER_CHIPID 0xD0 | |||
#define BMP180_REGISTER_CAL_AC1 0xAA | |||
#define BMP180_REGISTER_CAL_AC2 0xAC | |||
#define BMP180_REGISTER_CAL_AC3 0xAE | |||
#define BMP180_REGISTER_CAL_AC4 0xB0 | |||
#define BMP180_REGISTER_CAL_AC5 0xB2 | |||
#define BMP180_REGISTER_CAL_AC6 0xB4 | |||
#define BMP180_REGISTER_CAL_B1 0xB6 | |||
#define BMP180_REGISTER_CAL_B2 0xB8 | |||
#define BMP180_REGISTER_CAL_MB 0xBA | |||
#define BMP180_REGISTER_CAL_MC 0xBC | |||
#define BMP180_REGISTER_CAL_MD 0xBE | |||
#define BMP180_REGISTER_VERSION 0xD1 | |||
#define BMP180_REGISTER_SOFTRESET 0xE0 | |||
#define BMP180_REGISTER_CONTROL 0xF4 | |||
#define BMP180_REGISTER_TEMPDATA 0xF6 | |||
#define BMP180_REGISTER_PRESSUREDATA 0xF6 | |||
#define BMP180_REGISTER_READTEMPCMD 0x2E | |||
#define BMP180_REGISTER_READPRESSURECMD 0x34 | |||
class BMP180Sensor : public I2CSensor { | |||
public: | |||
static unsigned char addresses[1]; | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
BMP180Sensor(): I2CSensor() { | |||
_sensor_id = SENSOR_BMP180_ID; | |||
_count = 2; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensor API | |||
// --------------------------------------------------------------------- | |||
// Initialization method, must be idempotent | |||
void begin() { | |||
if (!_dirty) return; | |||
_init(); | |||
_dirty = !_ready; | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[20]; | |||
snprintf(buffer, sizeof(buffer), "BMP180 @ I2C (0x%02X)", _address); | |||
return String(buffer); | |||
} | |||
// Type for slot # index | |||
unsigned char type(unsigned char index) { | |||
if (index == 0) return MAGNITUDE_TEMPERATURE; | |||
if (index == 1) return MAGNITUDE_PRESSURE; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
virtual void pre() { | |||
if (_run_init) { | |||
i2cClearBus(); | |||
_init(); | |||
} | |||
if (_chip == 0) { | |||
_error = SENSOR_ERROR_UNKNOWN_ID; | |||
return; | |||
} | |||
_error = SENSOR_ERROR_OK; | |||
_error = _read(); | |||
if (_error != SENSOR_ERROR_OK) { | |||
_run_init = true; | |||
} | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index == 0) return _temperature; | |||
if (index == 1) return _pressure / 100; | |||
return 0; | |||
} | |||
protected: | |||
void _init() { | |||
// Make sure sensor had enough time to turn on. BMP180 requires 2ms to start up | |||
nice_delay(10); | |||
// I2C auto-discover | |||
_address = _begin_i2c(_address, sizeof(BMP180Sensor::addresses), BMP180Sensor::addresses); | |||
if (_address == 0) return; | |||
// Check sensor correctly initialized | |||
_chip = i2c_read_uint8(_address, BMP180_REGISTER_CHIPID); | |||
if (_chip != BMP180_CHIP_ID) { | |||
_chip = 0; | |||
i2cReleaseLock(_address); | |||
_previous_address = 0; | |||
_error = SENSOR_ERROR_UNKNOWN_ID; | |||
// Setting _address to 0 forces auto-discover | |||
// This might be necessary at this stage if there is a | |||
// different sensor in the hardcoded address | |||
_address = 0; | |||
return; | |||
} | |||
_readCoefficients(); | |||
_run_init = false; | |||
_ready = true; | |||
} | |||
void _readCoefficients() { | |||
_bmp180_calib.ac1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC1); | |||
_bmp180_calib.ac2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC2); | |||
_bmp180_calib.ac3 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC3); | |||
_bmp180_calib.ac4 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC4); | |||
_bmp180_calib.ac5 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC5); | |||
_bmp180_calib.ac6 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC6); | |||
_bmp180_calib.b1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B1); | |||
_bmp180_calib.b2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B2); | |||
_bmp180_calib.mb = i2c_read_int16(_address, BMP180_REGISTER_CAL_MB); | |||
_bmp180_calib.mc = i2c_read_int16(_address, BMP180_REGISTER_CAL_MC); | |||
_bmp180_calib.md = i2c_read_int16(_address, BMP180_REGISTER_CAL_MD); | |||
} | |||
// Compute B5 coefficient used in temperature & pressure calcs. | |||
// Based on Adafruit_BMP085_Unified library | |||
long _computeB5(unsigned long t) { | |||
long X1 = (t - (long)_bmp180_calib.ac6) * ((long)_bmp180_calib.ac5) >> 15; | |||
long X2 = ((long)_bmp180_calib.mc << 11) / (X1+(long)_bmp180_calib.md); | |||
return X1 + X2; | |||
} | |||
unsigned char _read() { | |||
// Read raw temperature | |||
i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READTEMPCMD); | |||
nice_delay(5); | |||
unsigned long t = i2c_read_uint16(_address, BMP180_REGISTER_TEMPDATA); | |||
// Compute B5 coeficient | |||
long b5 = _computeB5(t); | |||
// Final temperature | |||
_temperature = ((double) ((b5 + 8) >> 4)) / 10.0; | |||
// Read raw pressure | |||
i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READPRESSURECMD + (_mode << 6)); | |||
nice_delay(26); | |||
unsigned long p1 = i2c_read_uint16(_address, BMP180_REGISTER_PRESSUREDATA); | |||
unsigned long p2 = i2c_read_uint8(_address, BMP180_REGISTER_PRESSUREDATA+2); | |||
long p = ((p1 << 8) + p2) >> (8 - _mode); | |||
// Pressure compensation | |||
long b6 = b5 - 4000; | |||
long x1 = (_bmp180_calib.b2 * ((b6 * b6) >> 12)) >> 11; | |||
long x2 = (_bmp180_calib.ac2 * b6) >> 11; | |||
long x3 = x1 + x2; | |||
long b3 = (((((int32_t) _bmp180_calib.ac1) * 4 + x3) << _mode) + 2) >> 2; | |||
x1 = (_bmp180_calib.ac3 * b6) >> 13; | |||
x2 = (_bmp180_calib.b1 * ((b6 * b6) >> 12)) >> 16; | |||
x3 = ((x1 + x2) + 2) >> 2; | |||
unsigned long b4 = (_bmp180_calib.ac4 * (uint32_t) (x3 + 32768)) >> 15; | |||
unsigned long b7 = ((uint32_t) (p - b3) * (50000 >> _mode)); | |||
if (b7 < 0x80000000) { | |||
p = (b7 << 1) / b4; | |||
} else { | |||
p = (b7 / b4) << 1; | |||
} | |||
x1 = (p >> 8) * (p >> 8); | |||
x1 = (x1 * 3038) >> 16; | |||
x2 = (-7357 * p) >> 16; | |||
_pressure = p + ((x1 + x2 + 3791) >> 4); | |||
return SENSOR_ERROR_OK; | |||
} | |||
// --------------------------------------------------------------------- | |||
unsigned char _chip; | |||
bool _run_init = false; | |||
double _temperature = 0; | |||
double _pressure = 0; | |||
unsigned int _mode = BMP180_MODE; | |||
typedef struct { | |||
int16_t ac1; | |||
int16_t ac2; | |||
int16_t ac3; | |||
uint16_t ac4; | |||
uint16_t ac5; | |||
uint16_t ac6; | |||
int16_t b1; | |||
int16_t b2; | |||
int16_t mb; | |||
int16_t mc; | |||
int16_t md; | |||
} bmp180_calib_t; | |||
bmp180_calib_t _bmp180_calib; | |||
}; | |||
// Static inizializations | |||
unsigned char BMP180Sensor::addresses[1] = {0x77}; | |||
#endif // SENSOR_SUPPORT && BMP180_SUPPORT |
@ -0,0 +1,212 @@ | |||
// ----------------------------------------------------------------------------- | |||
// EZO™ pH Circuit from Atlas Scientific | |||
// | |||
// Uses SoftwareSerial library | |||
// Copyright (C) 2018 by Rui Marinho <ruipmarinho at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && EZOPH_SUPPORT | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "BaseSensor.h" | |||
#include <SoftwareSerial.h> | |||
class EZOPHSensor : public BaseSensor { | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
EZOPHSensor(): BaseSensor() { | |||
_count = 1; | |||
_sensor_id = SENSOR_EZOPH_ID; | |||
} | |||
~EZOPHSensor() { | |||
if (_serial) delete _serial; | |||
} | |||
// --------------------------------------------------------------------- | |||
void setRX(unsigned char pin_rx) { | |||
if (_pin_rx == pin_rx) return; | |||
_pin_rx = pin_rx; | |||
_dirty = true; | |||
} | |||
void setTX(unsigned char pin_tx) { | |||
if (_pin_tx == pin_tx) return; | |||
_pin_tx = pin_tx; | |||
_dirty = true; | |||
} | |||
// --------------------------------------------------------------------- | |||
unsigned char getRX() { | |||
return _pin_rx; | |||
} | |||
unsigned char getTX() { | |||
return _pin_tx; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensor API | |||
// --------------------------------------------------------------------- | |||
// Initialization method, must be idempotent | |||
void begin() { | |||
if (!_dirty) return; | |||
if (_serial) delete _serial; | |||
_serial = new SoftwareSerial(_pin_rx, _pin_tx); | |||
_serial->enableIntTx(false); | |||
_serial->begin(9600); | |||
_ready = true; | |||
_dirty = false; | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[28]; | |||
snprintf(buffer, sizeof(buffer), "EZOPH @ SwSerial(%u,%u)", _pin_rx, _pin_tx); | |||
return String(buffer); | |||
} | |||
// Descriptive name of the slot # index | |||
String slot(unsigned char index) { | |||
return description(); | |||
}; | |||
// Address of the sensor (it could be the GPIO or I2C address) | |||
String address(unsigned char index) { | |||
char buffer[6]; | |||
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx); | |||
return String(buffer); | |||
} | |||
// Type for slot # index | |||
unsigned char type(unsigned char index) { | |||
if (index == 0) return MAGNITUDE_PH; | |||
return MAGNITUDE_NONE; | |||
} | |||
void tick() { | |||
_setup(); | |||
_read(); | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index == 0) return _ph; | |||
return 0; | |||
} | |||
protected: | |||
// --------------------------------------------------------------------- | |||
// Protected | |||
// --------------------------------------------------------------------- | |||
void _setup() { | |||
if (_sync_responded) { | |||
return; | |||
} | |||
_error = SENSOR_ERROR_WARM_UP; | |||
String sync_serial = ""; | |||
sync_serial.reserve(30); | |||
if (!_sync_requested) { | |||
_serial->write(67); // C | |||
_serial->write(44); // , | |||
_serial->write(63); // ? | |||
_serial->write(13); // \r | |||
_serial->flush(); | |||
_sync_requested = true; | |||
} | |||
while ((_serial->available() > 0)) { | |||
char sync_char = (char)_serial->read(); | |||
sync_serial += sync_char; | |||
if (sync_char == '\r') { | |||
break; | |||
} | |||
} | |||
if (sync_serial.startsWith("?C,")) { | |||
_sync_interval = sync_serial.substring(sync_serial.indexOf(",") + 1).toInt() * 1000; | |||
if (_sync_interval == 0) { | |||
_error = SENSOR_ERROR_OTHER; | |||
return; | |||
} | |||
} | |||
if (sync_serial.startsWith("*OK")) { | |||
_sync_responded = true; | |||
} | |||
if (!_sync_responded) { | |||
return; | |||
} | |||
_error = SENSOR_ERROR_OK; | |||
} | |||
void _read() { | |||
if (_error != SENSOR_ERROR_OK) { | |||
return; | |||
} | |||
if (millis() - _ts <= _sync_interval) { | |||
return; | |||
} | |||
_ts = millis(); | |||
String ph_serial = ""; | |||
ph_serial.reserve(30); | |||
while ((_serial->available() > 0)) { | |||
char ph_char = (char)_serial->read(); | |||
ph_serial += ph_char; | |||
if (ph_char == '\r') { | |||
break; | |||
} | |||
} | |||
if (ph_serial == "*ER") { | |||
_error = SENSOR_ERROR_OTHER; | |||
return; | |||
} | |||
_ph = ph_serial.toFloat(); | |||
_error = SENSOR_ERROR_OK; | |||
} | |||
bool _sync_requested = false; | |||
bool _sync_responded = false; | |||
unsigned long _sync_interval = 100000; // Maximum continuous reading interval allowed is 99000 milliseconds. | |||
unsigned long _ts = 0; | |||
double _ph = 0; | |||
unsigned int _pin_rx; | |||
unsigned int _pin_tx; | |||
SoftwareSerial * _serial = NULL; | |||
}; | |||
#endif // SENSOR_SUPPORT && EZOPH_SUPPORT |
@ -0,0 +1,150 @@ | |||
// ----------------------------------------------------------------------------- | |||
// MAX6675 Sensor | |||
// Uses MAX6675_Thermocouple library | |||
// Copyright (C) 2017-2018 by Xose Pérez <andrade dot luciano at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && MAX6675_SUPPORT | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "BaseSensor.h" | |||
#include <vector> | |||
#include <MAX6675.h> | |||
#define MAX6675_READ_INTERVAL 3000 | |||
class MAX6675Sensor : public BaseSensor { | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
MAX6675Sensor(): BaseSensor() { | |||
_sensor_id = SENSOR_MAX6675_ID; | |||
} | |||
~MAX6675Sensor() { | |||
} | |||
// --------------------------------------------------------------------- | |||
// --------------------------------------------------------------------- | |||
void setCS(unsigned char pin_cs) { | |||
if (_pin_cs == pin_cs) return; | |||
_pin_cs = pin_cs; | |||
_dirty = true; | |||
} | |||
void setSO(unsigned char pin_so) { | |||
if (_pin_so == pin_so) return; | |||
_pin_so = pin_so; | |||
_dirty = true; | |||
} | |||
void setSCK(unsigned char pin_sck) { | |||
if (_pin_sck == pin_sck) return; | |||
_pin_sck = pin_sck; | |||
_dirty = true; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensor API | |||
// --------------------------------------------------------------------- | |||
// Initialization method, must be idempotent | |||
void begin() { | |||
if (!_dirty) return; | |||
//// MAX6675 | |||
int units = 1; // Units to readout temp (0 = raw, 1 = ˚C, 2 = ˚F) | |||
if (_max) delete _max; | |||
_max = new MAX6675(_pin_cs,_pin_so,_pin_sck,units); | |||
_count = 1; | |||
_ready = true; | |||
_dirty = false; | |||
} | |||
// Loop-like method, call it in your main loop | |||
void tick() { | |||
static unsigned long last = 0; | |||
if (millis() - last < MAX6675_READ_INTERVAL) return; | |||
last = millis(); | |||
last_read = _max->read_temp(); | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[20]; | |||
//snprintf(buffer, sizeof(buffer), "MAX6675 @ CS %d", _gpio); | |||
snprintf(buffer, sizeof(buffer), "MAX6675 "); | |||
return String(buffer); | |||
} | |||
String address(unsigned char index){ | |||
return String("@ address"); | |||
} | |||
// Address of the device | |||
// Descriptive name of the slot # index | |||
String slot(unsigned char index) { | |||
if (index < _count) { | |||
// char buffer[40]; | |||
// uint8_t * address = _devices[index].address; | |||
// snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d", | |||
// chipAsString(index).c_str(), | |||
// address[0], address[1], address[2], address[3], | |||
// address[4], address[5], address[6], address[7], | |||
// _gpio | |||
// ); | |||
return description(); | |||
} | |||
return String(); | |||
} | |||
// Type for slot # index | |||
unsigned char type(unsigned char index) { | |||
if (index < _count) return MAGNITUDE_TEMPERATURE; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
void pre() { | |||
_error = SENSOR_ERROR_OK; | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
return last_read; | |||
} | |||
protected: | |||
// --------------------------------------------------------------------- | |||
// Protected | |||
// --------------------------------------------------------------------- | |||
unsigned int _pin_cs = MAX6675_CS_PIN; | |||
unsigned int _pin_so = MAX6675_SO_PIN; | |||
unsigned int _pin_sck = MAX6675_SCK_PIN; | |||
bool _busy = false; | |||
double last_read = 0; | |||
MAX6675 * _max = NULL; | |||
}; | |||
#endif // SENSOR_SUPPORT && MAX6675_SUPPORT |
@ -0,0 +1,231 @@ | |||
// ----------------------------------------------------------------------------- | |||
// Pulse Meter Power Monitor Sensor | |||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && PULSEMETER_SUPPORT | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "BaseSensor.h" | |||
class PulseMeterSensor : public BaseSensor { | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
PulseMeterSensor(): BaseSensor() { | |||
_count = 2; | |||
_sensor_id = SENSOR_PULSEMETER_ID; | |||
} | |||
~PulseMeterSensor() { | |||
_enableInterrupts(false); | |||
} | |||
void resetEnergy(double value = 0) { | |||
_energy = value; | |||
} | |||
// --------------------------------------------------------------------- | |||
void setGPIO(unsigned char gpio) { | |||
if (_gpio == gpio) return; | |||
_gpio = gpio; | |||
_dirty = true; | |||
} | |||
void setEnergyRatio(unsigned long ratio) { | |||
if (ratio > 0) _ratio = ratio; | |||
} | |||
void setDebounceTime(unsigned long debounce) { | |||
_debounce = debounce; | |||
} | |||
// --------------------------------------------------------------------- | |||
unsigned char getGPIO() { | |||
return _gpio; | |||
} | |||
unsigned long getEnergyRatio() { | |||
return _ratio; | |||
} | |||
unsigned long getDebounceTime() { | |||
return _debounce; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensors API | |||
// --------------------------------------------------------------------- | |||
// Initialization method, must be idempotent | |||
// Defined outside the class body | |||
void begin() { | |||
_enableInterrupts(true); | |||
_ready = true; | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[24]; | |||
snprintf(buffer, sizeof(buffer), "PulseMeter @ GPIO(%u)", _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); | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
void pre() { | |||
unsigned long lapse = millis() - _previous_time; | |||
_previous_time = millis(); | |||
unsigned long pulses = _pulses - _previous_pulses; | |||
_previous_pulses = _pulses; | |||
unsigned long _energy_delta = 1000 * 3600 * pulses / _ratio; | |||
_energy += _energy_delta; | |||
if (lapse > 0) _active = 1000 * _energy_delta / lapse; | |||
} | |||
// Type for slot # index | |||
unsigned char type(unsigned char index) { | |||
if (index == 0) return MAGNITUDE_POWER_ACTIVE; | |||
if (index == 1) return MAGNITUDE_ENERGY; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index == 0) return _active; | |||
if (index == 1) return _energy; | |||
return 0; | |||
} | |||
// Handle interrupt calls | |||
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) { | |||
static unsigned long last = 0; | |||
if (millis() - last > _debounce) { | |||
last = millis(); | |||
_pulses++; | |||
} | |||
} | |||
protected: | |||
// --------------------------------------------------------------------- | |||
// Interrupt management | |||
// --------------------------------------------------------------------- | |||
void _attach(PulseMeterSensor * instance, unsigned char gpio, unsigned char mode); | |||
void _detach(unsigned char gpio); | |||
void _enableInterrupts(bool value) { | |||
if (value) { | |||
if (_gpio != _previous) { | |||
if (_previous != GPIO_NONE) _detach(_previous); | |||
_attach(this, _gpio, PULSEMETER_INTERRUPT_ON); | |||
_previous = _gpio; | |||
} | |||
} else { | |||
_detach(_previous); | |||
_previous = GPIO_NONE; | |||
} | |||
} | |||
// --------------------------------------------------------------------- | |||
unsigned char _previous = GPIO_NONE; | |||
unsigned char _gpio = GPIO_NONE; | |||
unsigned long _ratio = PULSEMETER_ENERGY_RATIO; | |||
unsigned long _debounce = PULSEMETER_DEBOUNCE; | |||
double _active = 0; | |||
double _energy = 0; | |||
volatile unsigned long _pulses = 0; | |||
unsigned long _previous_pulses = 0; | |||
unsigned long _previous_time = 0; | |||
}; | |||
// ----------------------------------------------------------------------------- | |||
// Interrupt helpers | |||
// ----------------------------------------------------------------------------- | |||
PulseMeterSensor * _pulsemeter_sensor_instance[10] = {NULL}; | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr(unsigned char gpio) { | |||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||
if (_pulsemeter_sensor_instance[index]) { | |||
_pulsemeter_sensor_instance[index]->handleInterrupt(gpio); | |||
} | |||
} | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_0() { _pulsemeter_sensor_isr(0); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_1() { _pulsemeter_sensor_isr(1); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_2() { _pulsemeter_sensor_isr(2); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_3() { _pulsemeter_sensor_isr(3); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_4() { _pulsemeter_sensor_isr(4); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_5() { _pulsemeter_sensor_isr(5); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_12() { _pulsemeter_sensor_isr(12); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_13() { _pulsemeter_sensor_isr(13); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_14() { _pulsemeter_sensor_isr(14); } | |||
void ICACHE_RAM_ATTR _pulsemeter_sensor_isr_15() { _pulsemeter_sensor_isr(15); } | |||
static void (*_pulsemeter_sensor_isr_list[10])() = { | |||
_pulsemeter_sensor_isr_0, _pulsemeter_sensor_isr_1, _pulsemeter_sensor_isr_2, | |||
_pulsemeter_sensor_isr_3, _pulsemeter_sensor_isr_4, _pulsemeter_sensor_isr_5, | |||
_pulsemeter_sensor_isr_12, _pulsemeter_sensor_isr_13, _pulsemeter_sensor_isr_14, | |||
_pulsemeter_sensor_isr_15 | |||
}; | |||
void PulseMeterSensor::_attach(PulseMeterSensor * instance, unsigned char gpio, unsigned char mode) { | |||
if (!gpioValid(gpio)) return; | |||
_detach(gpio); | |||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||
_pulsemeter_sensor_instance[index] = instance; | |||
attachInterrupt(gpio, _pulsemeter_sensor_isr_list[index], mode); | |||
#if SENSOR_DEBUG | |||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str()); | |||
#endif | |||
} | |||
void PulseMeterSensor::_detach(unsigned char gpio) { | |||
if (!gpioValid(gpio)) return; | |||
unsigned char index = gpio > 5 ? gpio-6 : gpio; | |||
if (_pulsemeter_sensor_instance[index]) { | |||
detachInterrupt(gpio); | |||
#if SENSOR_DEBUG | |||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _pulsemeter_sensor_instance[index]->description().c_str()); | |||
#endif | |||
_pulsemeter_sensor_instance[index] = NULL; | |||
} | |||
} | |||
#endif // SENSOR_SUPPORT && PULSEMETER_SUPPORT |
@ -0,0 +1,96 @@ | |||
// ----------------------------------------------------------------------------- | |||
// VEML6075 Sensor over I2C | |||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && VEML6075_SUPPORT | |||
#pragma once | |||
#undef I2C_SUPPORT | |||
#define I2C_SUPPORT 1 // Explicitly request I2C support. | |||
#include "Arduino.h" | |||
#include "I2CSensor.h" | |||
#include "SparkFun_VEML6075_Arduino_Library.h" | |||
class VEML6075Sensor : public I2CSensor { | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
VEML6075Sensor(): I2CSensor() { | |||
_count = 3; | |||
_sensor_id = SENSOR_VEML6075_ID; | |||
_veml6075 = new VEML6075(); | |||
} | |||
~VEML6075Sensor() { | |||
delete _veml6075; | |||
} | |||
void begin() { | |||
if (!_veml6075->begin()) { | |||
return; | |||
}; | |||
_ready = true; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensor API | |||
// --------------------------------------------------------------------- | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[25]; | |||
snprintf(buffer, sizeof(buffer), "VEML6075 @ 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_UVA; | |||
if (index == 1) return MAGNITUDE_UVB; | |||
if (index == 2) return MAGNITUDE_UVI; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
void pre() { | |||
_error = SENSOR_ERROR_OK; | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index == 0) return _veml6075->a(); | |||
if (index == 1) return _veml6075->b(); | |||
if (index == 2) return _veml6075->index(); | |||
return 0; | |||
} | |||
void setIntegrationTime(VEML6075::veml6075_uv_it_t integration_time) { | |||
_veml6075->setIntegrationTime(integration_time); | |||
} | |||
void setDynamicMode(VEML6075::veml6075_hd_t dynamic_mode) { | |||
_veml6075->setHighDynamic(dynamic_mode); | |||
} | |||
protected: | |||
VEML6075 * _veml6075 = NULL; | |||
}; | |||
#endif // SENSOR_SUPPORT && VEML6075_SUPPORT |
@ -0,0 +1,119 @@ | |||
// ----------------------------------------------------------------------------- | |||
// VL53L1X Sensor over I2C | |||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && VL53L1X_SUPPORT | |||
#pragma once | |||
#undef I2C_SUPPORT | |||
#define I2C_SUPPORT 1 // Explicitly request I2C support. | |||
#include "Arduino.h" | |||
#include "I2CSensor.h" | |||
#include "VL53L1X.h" | |||
class VL53L1XSensor : public I2CSensor { | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
VL53L1XSensor(): I2CSensor() { | |||
_count = 1; | |||
_sensor_id = SENSOR_VL53L1X_ID; | |||
_vl53l1x = new VL53L1X(); | |||
} | |||
~VL53L1XSensor() { | |||
delete _vl53l1x; | |||
} | |||
// --------------------------------------------------------------------- | |||
void setDistanceMode(VL53L1X::DistanceMode mode) { | |||
_vl53l1x->setDistanceMode(mode); | |||
} | |||
void setMeasurementTimingBudget(uint32_t budget_us) { | |||
_vl53l1x->setMeasurementTimingBudget(budget_us); | |||
} | |||
void setInterMeasurementPeriod(unsigned int period) { | |||
if (_inter_measurement_period == period) return; | |||
_inter_measurement_period = period; | |||
_dirty = true; | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensor API | |||
// --------------------------------------------------------------------- | |||
void begin() { | |||
if (!_dirty) { | |||
return; | |||
} | |||
// I2C auto-discover | |||
unsigned char addresses[] = {0x29}; | |||
_address = _begin_i2c(_address, sizeof(addresses), addresses); | |||
if (_address == 0) return; | |||
_vl53l1x->setAddress(_address); | |||
if (!_vl53l1x->init()) { | |||
return; | |||
}; | |||
_vl53l1x->startContinuous(_inter_measurement_period); | |||
_ready = true; | |||
_dirty = false; | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[21]; | |||
snprintf(buffer, sizeof(buffer), "VL53L1X @ 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_DISTANCE; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
void pre() { | |||
if (!_vl53l1x->dataReady()) { | |||
return; | |||
} | |||
_distance = (double) _vl53l1x->read(false) / 1000.00; | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index != 0) return 0; | |||
return _distance; | |||
} | |||
protected: | |||
VL53L1X * _vl53l1x = NULL; | |||
unsigned int _inter_measurement_period; | |||
double _distance = 0; | |||
}; | |||
#endif // SENSOR_SUPPORT && VL53L1X_SUPPORT |
@ -0,0 +1,283 @@ | |||
/* | |||
TERMINAL MODULE | |||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if TERMINAL_SUPPORT | |||
#include <vector> | |||
#include "libs/EmbedisWrap.h" | |||
#include <Stream.h> | |||
#include "libs/StreamInjector.h" | |||
StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE); | |||
EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE); | |||
#if SERIAL_RX_ENABLED | |||
char _serial_rx_buffer[TERMINAL_BUFFER_SIZE]; | |||
static unsigned char _serial_rx_pointer = 0; | |||
#endif // SERIAL_RX_ENABLED | |||
// ----------------------------------------------------------------------------- | |||
// Commands | |||
// ----------------------------------------------------------------------------- | |||
void _terminalHelpCommand() { | |||
// Get sorted list of commands | |||
std::vector<String> commands; | |||
unsigned char size = embedis.getCommandCount(); | |||
for (unsigned int i=0; i<size; i++) { | |||
String command = embedis.getCommandName(i); | |||
bool inserted = false; | |||
for (unsigned char j=0; j<commands.size(); j++) { | |||
// Check if we have to insert it before the current element | |||
if (commands[j].compareTo(command) > 0) { | |||
commands.insert(commands.begin() + j, command); | |||
inserted = true; | |||
break; | |||
} | |||
} | |||
// If we could not insert it, just push it at the end | |||
if (!inserted) commands.push_back(command); | |||
} | |||
// Output the list | |||
DEBUG_MSG_P(PSTR("Available commands:\n")); | |||
for (unsigned char i=0; i<commands.size(); i++) { | |||
DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str()); | |||
} | |||
} | |||
void _terminalKeysCommand() { | |||
// Get sorted list of keys | |||
std::vector<String> keys = _settingsKeys(); | |||
// Write key-values | |||
DEBUG_MSG_P(PSTR("Current settings:\n")); | |||
for (unsigned int i=0; i<keys.size(); i++) { | |||
String value = getSetting(keys[i]); | |||
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str()); | |||
} | |||
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize(); | |||
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size()); | |||
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current()); | |||
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE); | |||
} | |||
void _terminalInitCommand() { | |||
#if DEBUG_SUPPORT | |||
terminalRegisterCommand(F("CRASH"), [](Embedis* e) { | |||
crashDump(); | |||
crashClear(); | |||
terminalOK(); | |||
}); | |||
#endif | |||
terminalRegisterCommand(F("COMMANDS"), [](Embedis* e) { | |||
_terminalHelpCommand(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) { | |||
terminalOK(); | |||
resetReason(CUSTOM_RESET_TERMINAL); | |||
_eepromCommit(); | |||
ESP.eraseConfig(); | |||
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494 | |||
}); | |||
terminalRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) { | |||
resetSettings(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("GPIO"), [](Embedis* e) { | |||
if (e->argc < 2) { | |||
terminalError(F("Wrong arguments")); | |||
return; | |||
} | |||
int pin = String(e->argv[1]).toInt(); | |||
//if (!gpioValid(pin)) { | |||
// terminalError(F("Invalid GPIO")); | |||
// return; | |||
//} | |||
if (e->argc > 2) { | |||
bool state = String(e->argv[2]).toInt() == 1; | |||
digitalWrite(pin, state); | |||
} | |||
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW"); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("HEAP"), [](Embedis* e) { | |||
infoMemory("Heap", getInitialFreeHeap(), getFreeHeap()); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("STACK"), [](Embedis* e) { | |||
infoMemory("Stack", 4096, getFreeStack()); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("HELP"), [](Embedis* e) { | |||
_terminalHelpCommand(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("INFO"), [](Embedis* e) { | |||
info(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("KEYS"), [](Embedis* e) { | |||
_terminalKeysCommand(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("GET"), [](Embedis* e) { | |||
if (e->argc < 2) { | |||
terminalError(F("Wrong arguments")); | |||
return; | |||
} | |||
for (unsigned char i = 1; i < e->argc; i++) { | |||
String key = String(e->argv[i]); | |||
String value; | |||
if (!Embedis::get(key, value)) { | |||
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str()); | |||
continue; | |||
} | |||
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str()); | |||
} | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("RELOAD"), [](Embedis* e) { | |||
espurnaReload(); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("RESET"), [](Embedis* e) { | |||
terminalOK(); | |||
deferredReset(100, CUSTOM_RESET_TERMINAL); | |||
}); | |||
terminalRegisterCommand(F("RESET.SAFE"), [](Embedis* e) { | |||
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX); | |||
terminalOK(); | |||
deferredReset(100, CUSTOM_RESET_TERMINAL); | |||
}); | |||
terminalRegisterCommand(F("UPTIME"), [](Embedis* e) { | |||
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime()); | |||
terminalOK(); | |||
}); | |||
terminalRegisterCommand(F("CONFIG"), [](Embedis* e) { | |||
DynamicJsonBuffer jsonBuffer; | |||
JsonObject& root = jsonBuffer.createObject(); | |||
settingsGetJson(root); | |||
String output; | |||
root.printTo(output); | |||
DEBUG_MSG(output.c_str()); | |||
}); | |||
#if not SETTINGS_AUTOSAVE | |||
terminalRegisterCommand(F("SAVE"), [](Embedis* e) { | |||
eepromCommit(); | |||
DEBUG_MSG_P(PSTR("\n+OK\n")); | |||
}); | |||
#endif | |||
} | |||
void _terminalLoop() { | |||
#if DEBUG_SERIAL_SUPPORT | |||
while (DEBUG_PORT.available()) { | |||
_serial.inject(DEBUG_PORT.read()); | |||
} | |||
#endif | |||
embedis.process(); | |||
#if SERIAL_RX_ENABLED | |||
while (SERIAL_RX_PORT.available() > 0) { | |||
char rc = Serial.read(); | |||
_serial_rx_buffer[_serial_rx_pointer++] = rc; | |||
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) { | |||
terminalInject(_serial_rx_buffer, (size_t) _serial_rx_pointer); | |||
_serial_rx_pointer = 0; | |||
} | |||
} | |||
#endif // SERIAL_RX_ENABLED | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// Pubic API | |||
// ----------------------------------------------------------------------------- | |||
void terminalInject(void *data, size_t len) { | |||
_serial.inject((char *) data, len); | |||
} | |||
Stream & terminalSerial() { | |||
return (Stream &) _serial; | |||
} | |||
void terminalRegisterCommand(const String& name, void (*call)(Embedis*)) { | |||
Embedis::command(name, call); | |||
}; | |||
void terminalOK() { | |||
DEBUG_MSG_P(PSTR("+OK\n")); | |||
} | |||
void terminalError(const String& error) { | |||
DEBUG_MSG_P(PSTR("-ERROR: %s\n"), error.c_str()); | |||
} | |||
void terminalSetup() { | |||
_serial.callback([](uint8_t ch) { | |||
#if TELNET_SUPPORT | |||
telnetWrite(ch); | |||
#endif | |||
#if DEBUG_SERIAL_SUPPORT | |||
DEBUG_PORT.write(ch); | |||
#endif | |||
}); | |||
_terminalInitCommand(); | |||
#if SERIAL_RX_ENABLED | |||
SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE); | |||
#endif // SERIAL_RX_ENABLED | |||
// Register loop | |||
espurnaRegisterLoop(_terminalLoop); | |||
} | |||
#endif // TERMINAL_SUPPORT | |||