This commit is contained in:
2018-06-06 15:28:17 +02:00
commit aece7ca1b0
124 changed files with 39029 additions and 0 deletions

193
espurna/sensors/AM2320Sensor.h Executable file
View File

@ -0,0 +1,193 @@
// -----------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Copyright (C) 2018 by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && AM2320_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf
#define AM2320_I2C_READ_REGISTER_DATA 0x03 // Read one or more data registers
#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS 0x10 // Multiple sets of binary data to write multiple registers
/*
Register | Address | Register | Address | Register | Address | Register | Address
-----------------+---------+--------------------+---------+-------------------------+---------+-----------+--------
High humidity | 0x00 | Model High | 0x08 | Users register a high | 0x10 | Retention | 0x18
Low humidity | 0x01 | Model Low | 0x09 | Users register a low | 0x11 | Retention | 0x19
High temperature | 0x02 | The version number | 0x0A | Users register 2 high | 0x12 | Retention | 0x1A
Low temperature | 0x03 | Device ID(24-31)Bit| 0x0B | Users register 2 low | 0x13 | Retention | 0x1B
Retention | 0x04 | Device ID(24-31)Bit| 0x0C | Retention | 0x14 | Retention | 0x1C
Retention | 0x05 | Device ID(24-31)Bit| 0x0D | Retention | 0x15 | Retention | 0x1D
Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention | 0x16 | Retention | 0x1E
Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F
*/
class AM2320Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
AM2320Sensor(): I2CSensor() {
_count = 2;
_sensor_id = SENSOR_AM2320_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C, 0xB8};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/*
// Get device model, version, device_id
void _init() {
i2c_wakeup();
delayMicroseconds(800);
unsigned char _buffer[11];
// 0x08 = read address
// 7 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return false;
}
uint16_t model = (_buffer[2] << 8) | _buffer[3];
uint8_t version = _buffer[4];
uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5];
}
*/
void _read() {
i2c_wakeup();
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800); // just to be on safe side
// 0x00 = read address
// 4 = number of bytes to read
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) {
_error = SENSOR_ERROR_TIMEOUT;
return false;
}
unsigned char _buffer[8];
// waiting time of at least 800 μs, the maximum 3000 μs
delayMicroseconds(800 + ((3000-800)/2) );
i2c_read_buffer(_address, _buffer, 8);
// Humidity : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 %
// 0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 %
// Temperature: 00FA = (F×16)+A = 250 => temperature = 250÷10 = 25.0 C
// 0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C
// Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature
// _buffer 0 = function code
// _buffer 1 = number of bytes
// _buffer 2-3 = high/low humidity
// _buffer 4-5 = high/low temperature
// _buffer 6-7 = CRC low/high
unsigned int responseCRC = 0;
responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]);
if (responseCRC == _CRC16(_buffer)) {
int foo = (_buffer[2] << 8) | _buffer[3];
_humidity = foo / 10.0;
foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5]; // clean bit 15 and merge
_temperature = foo / 10.0;
if (_buffer[4] & 0x80) { // is bit 15 == 1
_temperature = _temperature * -1; // negative temperature
}
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_CRC;
return;
}
}
unsigned int _CRC16(unsigned char buffer[]) {
unsigned int crc16 = 0xFFFF;
for (unsigned int i = 0; i < 6; i++) {
crc16 ^= buffer[i];
for (unsigned int b = 8; b != 0; b--) {
if (crc16 & 0x01) { // is lsb set
crc16 >>= 1;
crc16 ^= 0xA001;
} else {
crc16 >>= 1;
}
}
}
return crc16;
}
double _temperature = 0;
double _humidity = 0;
};
#endif // SENSOR_SUPPORT && AM2320_SUPPORT

66
espurna/sensors/AnalogSensor.h Executable file
View File

@ -0,0 +1,66 @@
// -----------------------------------------------------------------------------
// Analog Sensor (maps to an analogRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ANALOG_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class AnalogSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
AnalogSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_ANALOG_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(0, INPUT);
_ready = true;
}
// Descriptive name of the sensor
String description() {
return String("ANALOG @ TOUT");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_ANALOG;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return analogRead(0);
return 0;
}
};
#endif // SENSOR_SUPPORT && ANALOG_SUPPORT

135
espurna/sensors/BH1750Sensor.h Executable file
View File

@ -0,0 +1,135 @@
// -----------------------------------------------------------------------------
// BH1750 Liminosity sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BH1750_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms.
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
class BH1750Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
BH1750Sensor(): I2CSensor() {
_sensor_id = SENSOR_BH1750_ID;
_count = 1;
}
// ---------------------------------------------------------------------
void setMode(unsigned char mode) {
if (_mode == mode) return;
_mode = mode;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getMode() {
return _mode;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x23, 0x5C};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Run configuration on next update
_run_configure = true;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "BH1750 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_LUX;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_lux = _read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _lux;
return 0;
}
protected:
double _read() {
// For one-shot modes reconfigure sensor & wait for conversion
if (_run_configure) {
// Configure mode
i2c_write_uint8(_address, _mode);
// According to datasheet
// conversion time is ~16ms for low resolution
// and ~120 for high resolution
// but more time is needed
unsigned long wait = (_mode & 0x02) ? 24 : 180;
unsigned long start = millis();
while (millis() - start < wait) delay(1);
// Keep on running configure each time if one-shot mode
_run_configure = _mode & 0x20;
}
double level = (double) i2c_read_uint16(_address);
if (level == 0xFFFF) {
_error = SENSOR_ERROR_CRC;
_run_configure = true;
return 0;
}
return level / 1.2;
}
unsigned char _mode;
bool _run_configure = false;
double _lux = 0;
};
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

438
espurna/sensors/BMX280Sensor.h Executable file
View File

@ -0,0 +1,438 @@
// -----------------------------------------------------------------------------
// BME280/BMP280 Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && BMX280_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define BMX280_CHIP_BMP280 0x58
#define BMX280_CHIP_BME280 0x60
#define BMX280_REGISTER_DIG_T1 0x88
#define BMX280_REGISTER_DIG_T2 0x8A
#define BMX280_REGISTER_DIG_T3 0x8C
#define BMX280_REGISTER_DIG_P1 0x8E
#define BMX280_REGISTER_DIG_P2 0x90
#define BMX280_REGISTER_DIG_P3 0x92
#define BMX280_REGISTER_DIG_P4 0x94
#define BMX280_REGISTER_DIG_P5 0x96
#define BMX280_REGISTER_DIG_P6 0x98
#define BMX280_REGISTER_DIG_P7 0x9A
#define BMX280_REGISTER_DIG_P8 0x9C
#define BMX280_REGISTER_DIG_P9 0x9E
#define BMX280_REGISTER_DIG_H1 0xA1
#define BMX280_REGISTER_DIG_H2 0xE1
#define BMX280_REGISTER_DIG_H3 0xE3
#define BMX280_REGISTER_DIG_H4 0xE4
#define BMX280_REGISTER_DIG_H5 0xE5
#define BMX280_REGISTER_DIG_H6 0xE7
#define BMX280_REGISTER_CHIPID 0xD0
#define BMX280_REGISTER_VERSION 0xD1
#define BMX280_REGISTER_SOFTRESET 0xE0
#define BMX280_REGISTER_CAL26 0xE1
#define BMX280_REGISTER_CONTROLHUMID 0xF2
#define BMX280_REGISTER_CONTROL 0xF4
#define BMX280_REGISTER_CONFIG 0xF5
#define BMX280_REGISTER_PRESSUREDATA 0xF7
#define BMX280_REGISTER_TEMPDATA 0xFA
#define BMX280_REGISTER_HUMIDDATA 0xFD
class BMX280Sensor : public I2CSensor {
public:
static unsigned char addresses[2];
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
BMX280Sensor(): I2CSensor() {
_sensor_id = SENSOR_BMX280_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return MAGNITUDE_TEMPERATURE;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return MAGNITUDE_PRESSURE;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return MAGNITUDE_HUMIDITY;
}
#endif
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() {
if (_run_init) {
i2cClearBus();
_init();
}
if (_chip == 0) {
_error = SENSOR_ERROR_UNKNOWN_ID;
return;
}
_error = SENSOR_ERROR_OK;
#if BMX280_MODE == 1
_forceRead();
#endif
_error = _read();
if (_error != SENSOR_ERROR_OK) {
_run_init = true;
}
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char i = 0;
#if BMX280_TEMPERATURE > 0
if (index == i++) return _temperature;
#endif
#if BMX280_PRESSURE > 0
if (index == i++) return _pressure / 100;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
if (index == i) return _humidity;
}
#endif
return 0;
}
// Load the configuration manifest
static void manifest(JsonArray& sensors) {
char buffer[10];
JsonObject& sensor = sensors.createNestedObject();
sensor["sensor_id"] = SENSOR_BMX280_ID;
JsonArray& fields = sensor.createNestedArray("fields");
{
JsonObject& field = fields.createNestedObject();
field["tag"] = UI_TAG_SELECT;
field["name"] = "address";
field["label"] = "Address";
JsonArray& options = field.createNestedArray("options");
{
JsonObject& option = options.createNestedObject();
option["name"] = "auto";
option["value"] = 0;
}
for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) {
JsonObject& option = options.createNestedObject();
snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]);
option["name"] = String(buffer);
option["value"] = BMX280Sensor::addresses[i];
}
}
};
void getConfig(JsonObject& root) {
root["sensor_id"] = _sensor_id;
root["address"] = _address;
};
void setConfig(JsonObject& root) {
if (root.containsKey("address")) setAddress(root["address"]);
};
protected:
void _init() {
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
nice_delay(10);
// No chip ID by default
_chip = 0;
// I2C auto-discover
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
if (_address == 0) return;
// Check sensor correctly initialized
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
_chip = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
return;
}
_count = 0;
#if BMX280_TEMPERATURE > 0
++_count;
#endif
#if BMX280_PRESSURE > 0
++_count;
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) ++_count;
#endif
_readCoefficients();
unsigned char data = 0;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
data = (BMX280_STANDBY << 0x5) & 0xE0;
data |= (BMX280_FILTER << 0x02) & 0x1C;
i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data);
data = (BMX280_HUMIDITY) & 0x07;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data);
data = (BMX280_TEMPERATURE << 5) & 0xE0;
data |= (BMX280_PRESSURE << 2) & 0x1C;
data |= (BMX280_MODE) & 0x03;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
_measurement_delay = _measurementTime();
_run_init = false;
_ready = true;
}
void _readCoefficients() {
_bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1);
_bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2);
_bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3);
_bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1);
_bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2);
_bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3);
_bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4);
_bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5);
_bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6);
_bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7);
_bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8);
_bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9);
_bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1);
_bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2);
_bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3);
_bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF);
_bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4);
_bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6);
}
unsigned long _measurementTime() {
// Measurement Time (as per BMX280 datasheet section 9.1)
// T_max(ms) = 1.25
// + (2.3 * T_oversampling)
// + (2.3 * P_oversampling + 0.575)
// + (2.4 * H_oversampling + 0.575)
// ~ 9.3ms for current settings
double t = 1.25;
#if BMX280_TEMPERATURE > 0
t += (2.3 * BMX280_TEMPERATURE);
#endif
#if BMX280_PRESSURE > 0
t += (2.3 * BMX280_PRESSURE + 0.575);
#endif
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
t += (2.4 * BMX280_HUMIDITY + 0.575);
}
#endif
return round(t + 1); // round up
}
void _forceRead() {
// We set the sensor in "forced mode" to force a reading.
// After the reading the sensor will go back to sleep mode.
uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL);
value = (value & 0xFC) + 0x01;
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);
nice_delay(_measurement_delay);
}
unsigned char _read() {
#if BMX280_TEMPERATURE > 0
int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA);
if (0xFFFF == adc_T) return SENSOR_ERROR_I2C;
adc_T <<= 8;
adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2);
adc_T >>= 4;
int32_t var1t = ((((adc_T>>3) -
((int32_t)_bmx280_calib.dig_T1 <<1))) *
((int32_t)_bmx280_calib.dig_T2)) >> 11;
int32_t var2t = (((((adc_T>>4) -
((int32_t)_bmx280_calib.dig_T1)) *
((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) *
((int32_t)_bmx280_calib.dig_T3)) >> 14;
int32_t t_fine = var1t + var2t;
double T = (t_fine * 5 + 128) >> 8;
_temperature = T / 100;
#else
int32_t t_fine = 102374; // ~20ºC
#endif
// -----------------------------------------------------------------
#if BMX280_PRESSURE > 0
int64_t var1, var2, p;
int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA);
if (0xFFFF == adc_P) return SENSOR_ERROR_I2C;
adc_P <<= 8;
adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2);
adc_P >>= 4;
var1 = ((int64_t)t_fine) - 128000;
var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6;
var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17);
var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35);
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero
p = 1048576 - adc_P;
p = (((p<<31) - var2)*3125) / var1;
var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4);
_pressure = (double) p / 256;
#endif
// -----------------------------------------------------------------
#if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) {
int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA);
if (0xFFFF == adc_H) return SENSOR_ERROR_I2C;
int32_t v_x1_u32r;
v_x1_u32r = (t_fine - ((int32_t)76800));
v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) -
(((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
(((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) *
(((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
((int32_t)_bmx280_calib.dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
double h = (v_x1_u32r >> 12);
_humidity = h / 1024.0;
}
#endif
return SENSOR_ERROR_OK;
}
// ---------------------------------------------------------------------
unsigned char _chip;
unsigned long _measurement_delay;
bool _run_init = false;
double _temperature = 0;
double _pressure = 0;
double _humidity = 0;
typedef struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
} bmx280_calib_t;
bmx280_calib_t _bmx280_calib;
};
// Static inizializations
unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77};
#endif // SENSOR_SUPPORT && BMX280_SUPPORT

101
espurna/sensors/BaseSensor.h Executable file
View File

@ -0,0 +1,101 @@
// -----------------------------------------------------------------------------
// Abstract sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out
#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
class BaseSensor {
public:
// Constructor
BaseSensor() {}
// Destructor
~BaseSensor() {}
// Initialization method, must be idempotent
virtual void begin() {}
// Loop-like method, call it in your main loop
virtual void tick() {}
// Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() {}
// Post-read hook (usually to reset things)
virtual void post() {}
// Descriptive name of the sensor
virtual String description() {}
// Address of the sensor (it could be the GPIO or I2C address)
virtual String address(unsigned char index) {}
// Descriptive name of the slot # index
virtual String slot(unsigned char index) {};
// Type for slot # index
virtual unsigned char type(unsigned char index) {}
// Current value for slot # index
virtual double value(unsigned char index) {}
// Retrieve current instance configuration
virtual void getConfig(JsonObject& root) {};
// Save current instance configuration
virtual void setConfig(JsonObject& root) {};
// Load the configuration manifest
static void manifest(JsonArray& root) {};
// Sensor ID
unsigned char getID() { return _sensor_id; };
// Return status (true if no errors)
bool status() { return 0 == _error; }
// Return ready status (true for ready)
bool ready() { return _ready; }
// Return sensor last internal error
int error() { return _error; }
// Number of available slots
unsigned char count() { return _count; }
// Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; };
protected:
TSensorCallback _callback = NULL;
unsigned char _sensor_id = 0x00;
int _error = 0;
bool _dirty = true;
unsigned char _count = 0;
bool _ready = false;
};
#endif

372
espurna/sensors/CSE7766Sensor.h Executable file
View File

@ -0,0 +1,372 @@
// -----------------------------------------------------------------------------
// CSE7766 based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && CSE7766_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class CSE7766Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
CSE7766Sensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_CSE7766_ID;
}
~CSE7766Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
void expectedCurrent(double expected) {
if ((expected > 0) && (_current > 0)) {
_ratioC = _ratioC * (expected / _current);
}
}
void expectedVoltage(unsigned int expected) {
if ((expected > 0) && (_voltage > 0)) {
_ratioV = _ratioV * (expected / _voltage);
}
}
void expectedPower(unsigned int expected) {
if ((expected > 0) && (_active > 0)) {
_ratioP = _ratioP * (expected / _active);
}
}
void setCurrentRatio(double value) {
_ratioC = value;
};
void setVoltageRatio(double value) {
_ratioV = value;
};
void setPowerRatio(double value) {
_ratioP = value;
};
double getCurrentRatio() {
return _ratioC;
};
double getVoltageRatio() {
return _ratioV;
};
double getPowerRatio() {
return _ratioP;
};
void resetRatios() {
_ratioC = _ratioV = _ratioP = 1.0;
}
void resetEnergy() {
_energy = 0;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
if (1 == _pin_rx) {
Serial.begin(CSE7766_BAUDRATE);
} else {
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(CSE7766_BAUDRATE);
}
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
if (1 == _pin_rx) {
snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _energy;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
/**
* "
* Checksum is the sum of all data
* except for packet header and packet tail lowering by 8bit (...)
* "
* @return bool
*/
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 2; i < 23; i++) {
checksum += _data[i];
}
return checksum == _data[23];
}
void _process() {
// Sample data:
// 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load)
// F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load)
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: _process: ");
for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]);
DEBUG_MSG("\n");
#endif
// Checksum
if (!_checksum()) {
_error = SENSOR_ERROR_CRC;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n");
#endif
return;
}
// Calibration
if (0xAA == _data[0]) {
_error = SENSOR_ERROR_CALIBRATION;
#if SENSOR_DEBUG
DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n");
#endif
return;
}
if ((_data[0] & 0xFC) > 0xF0) {
_error = SENSOR_ERROR_OTHER;
#if SENSOR_DEBUG
if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n");
if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n");
if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n");
if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n");
#endif
return;
}
// Calibration coefficients
unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770
unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030
unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000
// Adj: this looks like a sampling report
uint8_t adj = _data[20]; // F1 11110001
// Calculate voltage
_voltage = 0;
if ((adj & 0x40) == 0x40) {
unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817
_voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41
}
// Calculate power
_active = 0;
if ((adj & 0x10) == 0x10) {
if ((_data[0] & 0xF2) != 0xF2) {
unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709
_active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20
}
}
// Calculate current
_current = 0;
if ((adj & 0x20) == 0x20) {
if (_active > 0) {
unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376
_current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75
}
}
// Calculate energy
static unsigned long cf_pulses_last = 0;
unsigned long cf_pulses = _data[21] << 8 | _data[22];
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
_energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0;
cf_pulses_last = cf_pulses;
}
void _read() {
_error = SENSOR_ERROR_OK;
static unsigned char index = 0;
static unsigned long last = millis();
while (_serial_available()) {
// A 24 bytes message takes ~55ms to go through at 4800 bps
// Reset counter if more than 1000ms have passed since last byte.
if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
last = millis();
uint8_t byte = _serial_read();
// first byte must be 0x55 or 0xF?
if (0 == index) {
if ((0x55 != byte) && (byte < 0xF0)) {
continue;
}
// second byte must be 0x5A
} else if (1 == index) {
if (0x5A != byte) {
index = 0;
continue;
}
}
_data[index++] = byte;
if (index > 23) {
_serial_flush();
break;
}
}
// Process packet
if (24 == index) {
_process();
index = 0;
}
}
// ---------------------------------------------------------------------
bool _serial_available() {
if (1 == _pin_rx) {
return Serial.available();
} else {
return _serial->available();
}
}
void _serial_flush() {
if (1 == _pin_rx) {
return Serial.flush();
} else {
return _serial->flush();
}
}
uint8_t _serial_read() {
if (1 == _pin_rx) {
return Serial.read();
} else {
return _serial->read();
}
}
// ---------------------------------------------------------------------
unsigned int _pin_rx = CSE7766_PIN;
bool _inverted = CSE7766_PIN_INVERSE;
SoftwareSerial * _serial = NULL;
double _active = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;
double _ratioV = 1.0;
double _ratioC = 1.0;
double _ratioP = 1.0;
unsigned char _data[24];
};
#endif // SENSOR_SUPPORT && CSE7766_SUPPORT

245
espurna/sensors/DHTSensor.h Executable file
View File

@ -0,0 +1,245 @@
// -----------------------------------------------------------------------------
// DHTXX Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DHT_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#define DHT_MAX_DATA 5
#define DHT_MAX_ERRORS 5
#define DHT_MIN_INTERVAL 2000
#define DHT_CHIP_DHT11 11
#define DHT_CHIP_DHT22 22
#define DHT_CHIP_DHT21 21
#define DHT_CHIP_AM2301 21
class DHTSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DHTSensor(): BaseSensor() {
_count = 2;
_sensor_id = SENSOR_DHTXX_ID;
}
~DHTSensor() {
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setType(unsigned char type) {
_type = type;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getType() {
return _type;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
_count = 0;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
_previous = _gpio;
_count = 2;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", _type, _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) {
_error = SENSOR_ERROR_OK;
return;
}
unsigned long low = 0;
unsigned long high = 0;
unsigned char dhtData[DHT_MAX_DATA] = {0};
unsigned char byteInx = 0;
unsigned char bitInx = 7;
// Send start signal to DHT sensor
if (++_errors > DHT_MAX_ERRORS) {
_errors = 0;
digitalWrite(_gpio, HIGH);
nice_delay(250);
}
pinMode(_gpio, OUTPUT);
noInterrupts();
digitalWrite(_gpio, LOW);
if (_type == DHT_CHIP_DHT11) {
nice_delay(20);
} else {
delayMicroseconds(500);
}
digitalWrite(_gpio, HIGH);
delayMicroseconds(40);
pinMode(_gpio, INPUT_PULLUP);
delayMicroseconds(10);
// No errors, read the 40 data bits
for( int k = 0; k < 41; k++ ) {
// Starts new data transmission with >50us low signal
low = _signal(100, LOW);
if (low == 0) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
// Check to see if after >70us rx data is a 0 or a 1
high = _signal(100, HIGH);
if (high == 0) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
// Skip the first bit
if (k == 0) continue;
// add the current read to the output data
// since all dhtData array where set to 0 at the start,
// only look for "1" (>28us us)
if (high > low) dhtData[byteInx] |= (1 << bitInx);
// index to next byte
if (bitInx == 0) {
bitInx = 7;
++byteInx;
} else {
--bitInx;
}
}
interrupts();
// Verify checksum
if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) {
_error = SENSOR_ERROR_CRC;
return;
}
// Get humidity from Data[0] and Data[1]
if (_type == DHT_CHIP_DHT11) {
_humidity = dhtData[0];
} else {
_humidity = dhtData[0] * 256 + dhtData[1];
_humidity /= 10;
}
// Get temp from Data[2] and Data[3]
if (_type == DHT_CHIP_DHT11) {
_temperature = dhtData[2];
} else {
_temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
_temperature /= 10;
if (dhtData[2] & 0x80) _temperature *= -1;
}
_last_ok = millis();
_errors = 0;
_error = SENSOR_ERROR_OK;
}
unsigned long _signal(int usTimeOut, bool state) {
unsigned long uSec = 1;
while (digitalRead(_gpio) == state) {
if (++uSec > usTimeOut) return 0;
delayMicroseconds(1);
}
return uSec;
}
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
unsigned char _type = DHT_CHIP_DHT22;
unsigned long _last_ok = 0;
unsigned char _errors = 0;
double _temperature = 0;
unsigned int _humidity = 0;
};
#endif // SENSOR_SUPPORT && DHT_SUPPORT

319
espurna/sensors/DallasSensor.h Executable file
View File

@ -0,0 +1,319 @@
// -----------------------------------------------------------------------------
// Dallas OneWire Sensor
// Uses OneWire library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DALLAS_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <vector>
#include <OneWire.h>
#define DS_CHIP_DS18S20 0x10
#define DS_CHIP_DS1822 0x22
#define DS_CHIP_DS18B20 0x28
#define DS_CHIP_DS1825 0x3B
#define DS_DATA_SIZE 9
#define DS_PARASITE 1
#define DS_DISCONNECTED -127
#define DS_CMD_START_CONVERSION 0x44
#define DS_CMD_READ_SCRATCHPAD 0xBE
class DallasSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DallasSensor(): BaseSensor() {
_sensor_id = SENSOR_DALLAS_ID;
}
~DallasSensor() {
if (_wire) delete _wire;
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
if (_gpio == gpio) return;
_gpio = gpio;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
// OneWire
if (_wire) delete _wire;
_wire = new OneWire(_gpio);
// Search devices
loadDevices();
// If no devices found check again pulling up the line
if (_count == 0) {
pinMode(_gpio, INPUT_PULLUP);
loadDevices();
}
// Check connection
if (_count == 0) {
gpioReleaseLock(_gpio);
} else {
_previous = _gpio;
}
_ready = true;
_dirty = false;
}
// Loop-like method, call it in your main loop
void tick() {
static unsigned long last = 0;
if (millis() - last < DALLAS_READ_INTERVAL) return;
last = millis();
// Every second we either start a conversion or read the scratchpad
static bool conversion = true;
if (conversion) {
// Start conversion
_wire->reset();
_wire->skip();
_wire->write(DS_CMD_START_CONVERSION, DS_PARASITE);
} else {
// Read scratchpads
for (unsigned char index=0; index<_devices.size(); index++) {
// Read scratchpad
if (_wire->reset() == 0) {
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
_wire->select(_devices[index].address);
_wire->write(DS_CMD_READ_SCRATCHPAD);
uint8_t data[DS_DATA_SIZE];
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
data[i] = _wire->read();
}
#if false
Serial.printf("[DS18B20] Data = ");
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.printf(" CRC = %02X\n", OneWire::crc8(data, DS_DATA_SIZE-1));
#endif
if (_wire->reset() != 1) {
// Force a CRC check error
_devices[index].data[0] = _devices[index].data[0] + 1;
return;
}
memcpy(_devices[index].data, data, DS_DATA_SIZE);
}
}
conversion = !conversion;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "Dallas @ GPIO%d", _gpio);
return String(buffer);
}
// Address of the device
String address(unsigned char index) {
char buffer[20] = {0};
if (index < _count) {
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X",
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7]
);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
if (index < _count) {
char buffer[40];
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
chipAsString(index).c_str(),
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7],
_gpio
);
return String(buffer);
}
return String();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) return MAGNITUDE_TEMPERATURE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
if (index >= _count) return 0;
uint8_t * data = _devices[index].data;
if (OneWire::crc8(data, DS_DATA_SIZE-1) != data[DS_DATA_SIZE-1]) {
_error = SENSOR_ERROR_CRC;
return 0;
}
// Registers
// byte 0: temperature LSB
// byte 1: temperature MSB
// byte 2: high alarm temp
// byte 3: low alarm temp
// byte 4: DS18S20: store for crc
// DS18B20 & DS1822: configuration register
// byte 5: internal use & crc
// byte 6: DS18S20: COUNT_REMAIN
// DS18B20 & DS1822: store for crc
// byte 7: DS18S20: COUNT_PER_C
// DS18B20 & DS1822: store for crc
// byte 8: SCRATCHPAD_CRC
int16_t raw = (data[1] << 8) | data[0];
if (chip(index) == DS_CHIP_DS18S20) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
}
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw & ~7; // 9 bit res, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
// 12 bit res, 750 ms
}
double value = (float) raw / 16.0;
if (value == DS_DISCONNECTED) {
_error = SENSOR_ERROR_CRC;
return 0;
}
return value;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
bool validateID(unsigned char id) {
return (id == DS_CHIP_DS18S20) || (id == DS_CHIP_DS18B20) || (id == DS_CHIP_DS1822) || (id == DS_CHIP_DS1825);
}
unsigned char chip(unsigned char index) {
if (index < _count) return _devices[index].address[0];
return 0;
}
String chipAsString(unsigned char index) {
unsigned char chip_id = chip(index);
if (chip_id == DS_CHIP_DS18S20) return String("DS18S20");
if (chip_id == DS_CHIP_DS18B20) return String("DS18B20");
if (chip_id == DS_CHIP_DS1822) return String("DS1822");
if (chip_id == DS_CHIP_DS1825) return String("DS1825");
return String("Unknown");
}
void loadDevices() {
uint8_t address[8];
_wire->reset();
_wire->reset_search();
while (_wire->search(address)) {
// Check CRC
if (_wire->crc8(address, 7) == address[7]) {
// Check ID
if (validateID(address[0])) {
ds_device_t device;
memcpy(device.address, address, 8);
_devices.push_back(device);
}
}
}
_count = _devices.size();
}
typedef struct {
uint8_t address[8];
uint8_t data[DS_DATA_SIZE];
} ds_device_t;
std::vector<ds_device_t> _devices;
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
OneWire * _wire = NULL;
};
#endif // SENSOR_SUPPORT && DALLAS_SUPPORT

106
espurna/sensors/DigitalSensor.h Executable file
View File

@ -0,0 +1,106 @@
// -----------------------------------------------------------------------------
// Digital Sensor (maps to a digitalRead)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && DIGITAL_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class DigitalSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
DigitalSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_DIGITAL_ID;
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setDefault(bool value) {
_default = value;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
bool getDefault() {
return _default;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
pinMode(_gpio, _mode);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "DIGITAL @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_DIGITAL;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return (digitalRead(_gpio) == _default) ? 0 : 1;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned char _gpio;
unsigned char _mode;
bool _default = false;
};
#endif // SENSOR_SUPPORT && DIGITAL_SUPPORT

349
espurna/sensors/ECH1560Sensor.h Executable file
View File

@ -0,0 +1,349 @@
// -----------------------------------------------------------------------------
// ECH1560 based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ECH1560_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class ECH1560Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
ECH1560Sensor(): BaseSensor(), _data() {
_count = 3;
_sensor_id = SENSOR_ECH1560_ID;
}
~ECH1560Sensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setCLK(unsigned char clk) {
if (_clk == clk) return;
_clk = clk;
_dirty = true;
}
void setMISO(unsigned char miso) {
if (_miso == miso) return;
_miso = miso;
_dirty = true;
}
void setInverted(bool inverted) {
_inverted = inverted;
}
// ---------------------------------------------------------------------
unsigned char getCLK() {
return _clk;
}
unsigned char getMISO() {
return _miso;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
pinMode(_clk, INPUT);
pinMode(_miso, INPUT);
_enableInterrupts(true);
_dirty = false;
_ready = true;
}
// Loop-like method, call it in your main loop
void tick() {
if (_dosync) _sync();
}
// Descriptive name of the sensor
String description() {
char buffer[35];
snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _clk, _miso);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_APPARENT;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _apparent;
return 0;
}
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
(void) gpio;
// if we are trying to find the sync-time (CLK goes high for 1-2ms)
if (_dosync == false) {
_clk_count = 0;
// register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms
while (digitalRead(_clk) == HIGH) {
_clk_count += 1;
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
}
// if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission
if (_clk_count >= 33 && _clk_count <= 67) {
_dosync = true;
}
// we are in sync and logging CLK-highs
} else {
// increment an integer to keep track of how many bits we have read.
_bits_count += 1;
_nextbit = true;
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_clk = GPIO_NONE;
if (value) {
if (_interrupt_clk != _clk) {
if (_interrupt_clk != GPIO_NONE) _detach(_interrupt_clk);
_attach(this, _clk, RISING);
_interrupt_clk = _clk;
}
} else if (_interrupt_clk != GPIO_NONE) {
_detach(_interrupt_clk);
_interrupt_clk = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _sync() {
unsigned int byte1 = 0;
unsigned int byte2 = 0;
unsigned int byte3 = 0;
_bits_count = 0;
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2
if (_nextbit) {
if (_bits_count < 9) { // first Byte/8 bits in byte1
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
_nextbit = false;
}
}
}
if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power.
// voltage = 2 * (byte1 + byte2 / 255)
_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
// power:
_bits_count = 0;
while (_bits_count < 40); // skip the uninteresting 5 first bytes
_bits_count = 0;
byte1 = 0;
byte2 = 0;
byte3 = 0;
while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3.
if (_nextbit) {
if (_bits_count < 9) {
byte1 = byte1 << 1;
if (digitalRead(_miso) == HIGH) byte1 |= 1;
_nextbit = false;
} else if (_bits_count < 17) {
byte2 = byte2 << 1;
if (digitalRead(_miso) == HIGH) byte2 |= 1;
_nextbit = false;
} else {
byte3 = byte3 << 1;
if (digitalRead(_miso) == HIGH) byte3 |= 1;
_nextbit = false;
}
}
}
if (_inverted) {
byte1 = 255 - byte1;
byte2 = 255 - byte2;
byte3 = 255 - byte3;
}
// power = (byte1*255+byte2+byte3/255)/2
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
_dosync = false;
}
// If byte2 is not 3 or something else than 0, something is wrong!
if (byte2 == 0) {
_dosync = false;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n"));
#endif
}
}
// ---------------------------------------------------------------------
unsigned char _clk = 0;
unsigned char _miso = 0;
bool _inverted = false;
volatile long _bits_count = 0;
volatile long _clk_count = 0;
volatile bool _dosync = false;
volatile bool _nextbit = true;
double _apparent = 0;
double _voltage = 0;
double _current = 0;
unsigned char _data[24];
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
ECH1560Sensor * _ech1560_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _ech1560_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_ech1560_sensor_instance[index]) {
_ech1560_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _ech1560_sensor_isr_0() { _ech1560_sensor_isr(0); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_1() { _ech1560_sensor_isr(1); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_2() { _ech1560_sensor_isr(2); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_3() { _ech1560_sensor_isr(3); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_4() { _ech1560_sensor_isr(4); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_5() { _ech1560_sensor_isr(5); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_12() { _ech1560_sensor_isr(12); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_13() { _ech1560_sensor_isr(13); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_14() { _ech1560_sensor_isr(14); }
void ICACHE_RAM_ATTR _ech1560_sensor_isr_15() { _ech1560_sensor_isr(15); }
static void (*_ech1560_sensor_isr_list[10])() = {
_ech1560_sensor_isr_0, _ech1560_sensor_isr_1, _ech1560_sensor_isr_2,
_ech1560_sensor_isr_3, _ech1560_sensor_isr_4, _ech1560_sensor_isr_5,
_ech1560_sensor_isr_12, _ech1560_sensor_isr_13, _ech1560_sensor_isr_14,
_ech1560_sensor_isr_15
};
void ECH1560Sensor::_attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_ech1560_sensor_instance[index] = instance;
attachInterrupt(gpio, _ech1560_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void ECH1560Sensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_ech1560_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _ech1560_sensor_instance[index]->description().c_str());
#endif
_ech1560_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && ECH1560_SUPPORT

View File

@ -0,0 +1,150 @@
// -----------------------------------------------------------------------------
// ADS121-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
// ADC121 Registers
#define ADC121_REG_RESULT 0x00
#define ADC121_REG_ALERT 0x01
#define ADC121_REG_CONFIG 0x02
#define ADC121_REG_LIMITL 0x03
#define ADC121_REG_LIMITH 0x04
#define ADC121_REG_HYST 0x05
#define ADC121_REG_CONVL 0x06
#define ADC121_REG_CONVH 0x07
#define ADC121_RESOLUTION 12
#define ADC121_CHANNELS 1
class EmonADC121Sensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonADC121Sensor(): EmonSensor() {
_channels = ADC121_CHANNELS;
_sensor_id = SENSOR_EMON_ADC121_ID;
init();
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// Discover
unsigned char addresses[] = {0x50, 0x51, 0x52, 0x54, 0x55, 0x56, 0x58, 0x59, 0x5A};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Init sensor
_init();
// Just one channel
_count = _magnitudes;
// Bit depth
_resolution = ADC121_RESOLUTION;
// Call the parent class method
EmonSensor::begin();
// warmup channel 0 (the only one)
read(0);
}
// Descriptive name of the sensor
String description() {
char buffer[30];
snprintf(buffer, sizeof(buffer), "EMON @ ADC121 @ I2C (0x%02X)", _address);
return String(buffer);
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
if (_address == 0) {
_error = SENSOR_ERROR_UNKNOWN_ID;
return;
}
_current[0] = read(0);
#if EMON_REPORT_ENERGY
static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (index == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
#endif
#if EMON_REPORT_POWER
if (index == i++) return _current[channel] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _init() {
i2c_write_uint8(_address, ADC121_REG_CONFIG, 0);
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value = i2c_read_uint16(_address, ADC121_REG_RESULT) & 0x0FFF;
return value;
}
};
#endif // SENSOR_SUPPORT && EMON_ADC121_SUPPORT

View File

@ -0,0 +1,347 @@
// -----------------------------------------------------------------------------
// ADS1X15-based Energy Monitor Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
#define ADS1X15_CHANNELS (4)
#define ADS1X15_CHIP_ADS1015 (0)
#define ADS1X15_CHIP_ADS1115 (1)
#define ADS1X15_RESOLUTION (16)
#define ADS1015_CONVERSIONDELAY (1)
#define ADS1115_CONVERSIONDELAY (8)
#define ADS1015_BIT_SHIFT (4)
#define ADS1115_BIT_SHIFT (0)
#define ADS1X15_REG_POINTER_MASK (0x03)
#define ADS1X15_REG_POINTER_CONVERT (0x00)
#define ADS1X15_REG_POINTER_CONFIG (0x01)
#define ADS1X15_REG_POINTER_LOWTHRESH (0x02)
#define ADS1X15_REG_POINTER_HITHRESH (0x03)
#define ADS1X15_REG_CONFIG_OS_MASK (0x8000)
#define ADS1X15_REG_CONFIG_OS_SINGLE (0x8000) // Write: Set to start a single-conversion
#define ADS1X15_REG_CONFIG_OS_BUSY (0x0000) // Read: Bit = 0 when conversion is in progress
#define ADS1X15_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion
#define ADS1X15_REG_CONFIG_MUX_MASK (0x7000)
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default)
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3
#define ADS1X15_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0
#define ADS1X15_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1
#define ADS1X15_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2
#define ADS1X15_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3
#define ADS1X15_REG_CONFIG_PGA_MASK (0x0E00)
#define ADS1X15_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3
#define ADS1X15_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1
#define ADS1X15_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default)
#define ADS1X15_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4
#define ADS1X15_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8
#define ADS1X15_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16
#define ADS1X15_REG_CONFIG_MODE_MASK (0x0100)
#define ADS1X15_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode
#define ADS1X15_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default)
#define ADS1X15_REG_CONFIG_DR_MASK (0x00E0)
#define ADS1015_REG_CONFIG_DR_128SPS (0x0000) // 128 samples per second
#define ADS1015_REG_CONFIG_DR_250SPS (0x0020) // 250 samples per second
#define ADS1015_REG_CONFIG_DR_490SPS (0x0040) // 490 samples per second
#define ADS1015_REG_CONFIG_DR_920SPS (0x0060) // 920 samples per second
#define ADS1015_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default)
#define ADS1015_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second
#define ADS1015_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second
#define ADS1115_REG_CONFIG_DR_8SPS (0x0000) // 8 samples per second
#define ADS1115_REG_CONFIG_DR_16SPS (0x0020) // 16 samples per second
#define ADS1115_REG_CONFIG_DR_32SPS (0x0040) // 32 samples per second
#define ADS1115_REG_CONFIG_DR_64SPS (0x0060) // 64 samples per second
#define ADS1115_REG_CONFIG_DR_128SPS (0x0080) // 128 samples per second (default)
#define ADS1115_REG_CONFIG_DR_250SPS (0x00A0) // 250 samples per second
#define ADS1115_REG_CONFIG_DR_475SPS (0x00C0) // 475 samples per second
#define ADS1115_REG_CONFIG_DR_860SPS (0x00E0) // 860 samples per second
#define ADS1X15_REG_CONFIG_CMODE_MASK (0x0010)
#define ADS1X15_REG_CONFIG_CMODE_TRAD (0x0000) // Traditional comparator with hysteresis (default)
#define ADS1X15_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator
#define ADS1X15_REG_CONFIG_CPOL_MASK (0x0008)
#define ADS1X15_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default)
#define ADS1X15_REG_CONFIG_CPOL_ACTVHI (0x0008) // ALERT/RDY pin is high when active
#define ADS1X15_REG_CONFIG_CLAT_MASK (0x0004) // Determines if ALERT/RDY pin latches once asserted
#define ADS1X15_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default)
#define ADS1X15_REG_CONFIG_CLAT_LATCH (0x0004) // Latching comparator
#define ADS1X15_REG_CONFIG_CQUE_MASK (0x0003)
#define ADS1X15_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions
#define ADS1X15_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions
#define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions
#define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default)
class EmonADS1X15Sensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonADS1X15Sensor(): EmonSensor() {
_channels = ADS1X15_CHANNELS;
_sensor_id = SENSOR_EMON_ADS1X15_ID;
init();
}
// ---------------------------------------------------------------------
void setType(unsigned char type) {
if (_type == type) return;
_type = type;
_dirty = true;
}
void setMask(unsigned char mask) {
if (_mask == mask) return;
_mask = mask;
_dirty = true;
}
void setGain(unsigned int gain) {
if (_gain == gain) return;
_gain = gain;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getType() {
return _type;
}
unsigned char getMask() {
return _mask;
}
unsigned char getGain() {
return _gain;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// Discover
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Calculate ports
_ports = 0;
unsigned char mask = _mask;
while (mask) {
if (mask & 0x01) ++_ports;
mask = mask >> 1;
}
_count = _ports * _magnitudes;
// Bit depth
_resolution = ADS1X15_RESOLUTION;
// Reference based on gain
if (_gain == ADS1X15_REG_CONFIG_PGA_6_144V) _reference = 12.288;
if (_gain == ADS1X15_REG_CONFIG_PGA_4_096V) _reference = 8.192;
if (_gain == ADS1X15_REG_CONFIG_PGA_2_048V) _reference = 4.096;
if (_gain == ADS1X15_REG_CONFIG_PGA_1_024V) _reference = 2.048;
if (_gain == ADS1X15_REG_CONFIG_PGA_0_512V) _reference = 1.024;
if (_gain == ADS1X15_REG_CONFIG_PGA_0_256V) _reference = 0.512;
// Call the parent class method
EmonSensor::begin();
// warmup all channels
warmup();
}
// Descriptive name of the sensor
String description() {
char buffer[30];
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[35];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 (A%d) @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, channel, _address);
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "0x%02X:%u", _address, channel);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
void pre() {
static unsigned long last = 0;
for (unsigned char port=0; port<_ports; port++) {
unsigned char channel = getChannel(port);
_current[port] = getCurrent(channel);
#if EMON_REPORT_ENERGY
_energy[port] += (_current[port] * _voltage * (millis() - last) / 1000);
#endif
}
last = millis();
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char port = index % _ports;
unsigned char magnitude = index / _ports;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (magnitude == i++) return _current[port];
#endif
#if EMON_REPORT_POWER
if (magnitude == i++) return _current[port] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (magnitude == i) return _energy[port];
#endif
return 0;
}
protected:
//----------------------------------------------------------------------
// Protected
//----------------------------------------------------------------------
unsigned char getChannel(unsigned char port) {
unsigned char count = 0;
unsigned char bit = 1;
for (unsigned char channel=0; channel<ADS1X15_CHANNELS; channel++) {
if ((_mask & bit) == bit) {
if (count == port) return channel;
++count;
}
bit <<= 1;
}
return 0;
}
void warmup() {
for (unsigned char port=0; port<_ports; port++) {
unsigned char channel = getChannel(port);
_pivot[channel] = _adc_counts >> 1;
getCurrent(channel);
}
}
//----------------------------------------------------------------------
// I2C
//----------------------------------------------------------------------
void setConfigRegistry(unsigned char channel, bool continuous, bool start) {
// Start with default values
uint16_t config = 0;
config |= _gain; // Set PGA/voltage range (0x0200)
config |= ADS1X15_REG_CONFIG_DR_MASK; // Always at max speed (0x00E0)
//config |= ADS1X15_REG_CONFIG_CMODE_TRAD; // Traditional comparator (default val) (0x0000)
//config |= ADS1X15_REG_CONFIG_CPOL_ACTVLOW; // Alert/Rdy active low (default val) (0x0000)
//config |= ADS1X15_REG_CONFIG_CLAT_NONLAT; // Non-latching (default val) (0x0000)
config |= ADS1X15_REG_CONFIG_CQUE_NONE; // Disable the comparator (default val) (0x0003)
if (start) {
config |= ADS1X15_REG_CONFIG_OS_SINGLE; // Start a single-conversion (0x8000)
}
if (continuous) {
//config |= ADS1X15_REG_CONFIG_MODE_CONTIN; // Continuous mode (default) (0x0000)
} else {
config |= ADS1X15_REG_CONFIG_MODE_SINGLE; // Single-shot mode (0x0100)
}
config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000)
#if SENSOR_DEBUG
//Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config);
#endif
// Write config register to the ADC
i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config);
}
double getCurrent(unsigned char channel) {
// Force stop by setting single mode and back to continuous
static unsigned char previous = 9;
if (previous != channel) {
setConfigRegistry(channel, true, false);
setConfigRegistry(channel, false, false);
setConfigRegistry(channel, false, true);
nice_delay(10);
readADC(channel);
previous = channel;
}
setConfigRegistry(channel, true, true);
return read(channel);
}
unsigned int readADC(unsigned char channel) {
(void) channel;
unsigned int value = i2c_read_uint16(_address, ADS1X15_REG_POINTER_CONVERT);
if (_type = ADS1X15_CHIP_ADS1015) value >>= ADS1015_BIT_SHIFT;
delayMicroseconds(500);
return value;
}
unsigned char _type = ADS1X15_CHIP_ADS1115;
unsigned char _mask = 0x0F;
unsigned int _gain = ADS1X15_REG_CONFIG_PGA_4_096V;
unsigned char _ports;
};
#endif // SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT

View File

@ -0,0 +1,124 @@
// -----------------------------------------------------------------------------
// Energy Monitor Sensor using builtin ADC
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT
#pragma once
#include "Arduino.h"
#include "EmonSensor.h"
#define EMON_ANALOG_RESOLUTION 10
#define EMON_ANALOG_CHANNELS 1
class EmonAnalogSensor : public EmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonAnalogSensor(): EmonSensor() {
_channels = EMON_ANALOG_CHANNELS;
_sensor_id = SENSOR_EMON_ANALOG_ID;
init();
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_dirty = false;
// Number of slots
_count = _magnitudes;
// Bit depth
_resolution = EMON_ANALOG_RESOLUTION;
// Init analog PIN)
pinMode(0, INPUT);
// Call the parent class method
EmonSensor::begin();
// warmup channel 0 (the only one)
read(0);
}
// Descriptive name of the sensor
String description() {
return String("EMON @ ANALOG @ GPIO0");
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return MAGNITUDE_CURRENT;
#endif
#if EMON_REPORT_POWER
if (index == i++) return MAGNITUDE_POWER_APPARENT;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return MAGNITUDE_ENERGY;
#endif
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_current[0] = read(0);
#if EMON_REPORT_ENERGY
static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
last = millis();
#endif
_error = SENSOR_ERROR_OK;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char channel = index / _magnitudes;
unsigned char i=0;
#if EMON_REPORT_CURRENT
if (index == i++) return _current[channel];
#endif
#if EMON_REPORT_POWER
if (index == i++) return _current[channel] * _voltage;
#endif
#if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
#endif
return 0;
}
protected:
unsigned int readADC(unsigned char channel) {
(void) channel;
return analogRead(0);
}
};
#endif // SENSOR_SUPPORT && EMON_ANALOG_SUPPORT

242
espurna/sensors/EmonSensor.h Executable file
View File

@ -0,0 +1,242 @@
// -----------------------------------------------------------------------------
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
class EmonSensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EmonSensor(): I2CSensor() {
// Calculate # of magnitudes
#if EMON_REPORT_CURRENT
++_magnitudes;
#endif
#if EMON_REPORT_POWER
++_magnitudes;
#endif
#if EMON_REPORT_ENERGY
++_magnitudes;
#endif
}
void expectedPower(unsigned char channel, unsigned int expected) {
if (channel >= _channels) return;
unsigned int actual = _current[channel] * _voltage;
if (actual == 0) return;
if (expected == actual) return;
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
_dirty = true;
}
void resetEnergy() {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = 0;
}
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {
if (_voltage == voltage) return;
_voltage = voltage;
_dirty = true;
}
void setReference(double reference) {
if (_reference == reference) return;
_reference = reference;
_dirty = true;
}
void setCurrentRatio(unsigned char channel, double current_ratio) {
if (channel >= _channels) return;
if (_current_ratio[channel] == current_ratio) return;
_current_ratio[channel] = current_ratio;
_dirty = true;
}
// ---------------------------------------------------------------------
double getVoltage() {
return _voltage;
}
double getReference() {
return _reference;
}
double getCurrentRatio(unsigned char channel) {
if (channel >= _channels) return 0;
return _current_ratio[channel];
}
unsigned char getChannels() {
return _channels;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
void begin() {
// Resolution
_adc_counts = 1 << _resolution;
// Calculations
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0;
_pivot[i] = _adc_counts >> 1;
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
_multiplier[i] = calculateMultiplier(_current_factor[i]);
}
#if SENSOR_DEBUG
DEBUG_MSG("[EMON] Reference (mV): %d\n", int(1000 * _reference));
DEBUG_MSG("[EMON] ADC counts: %d\n", _adc_counts);
for (unsigned char i=0; i<_channels; i++) {
DEBUG_MSG("[EMON] Channel #%d current ratio (mA/V): %d\n", i, int(1000 * _current_ratio[i]));
DEBUG_MSG("[EMON] Channel #%d current factor (mA/bit): %d\n", i, int(1000 * _current_factor[i]));
DEBUG_MSG("[EMON] Channel #%d Multiplier: %d\n", i, int(_multiplier[i]));
}
#endif
_ready = true;
_dirty = false;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
// Initializes internal variables
void init() {
_current_ratio = new double[_channels];
_current_factor = new double[_channels];
_multiplier = new uint16_t[_channels];
_pivot = new double[_channels];
_current = new double[_channels];
#if EMON_REPORT_ENERGY
_energy = new uint32_t[_channels];
#endif
}
virtual unsigned int readADC(unsigned char channel) {}
unsigned int calculateMultiplier(double current_factor) {
unsigned int s = 1;
unsigned int i = 1;
unsigned int m = s * i;
unsigned int multiplier;
while (m * current_factor < 1) {
multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10;
m = s * i;
}
return multiplier;
}
double read(unsigned char channel) {
int max = 0;
int min = _adc_counts;
double sum = 0;
unsigned long time_span = millis();
for (unsigned long i=0; i<_samples; i++) {
int sample;
double filtered;
// Read analog value
sample = readADC(channel);
if (sample > max) max = sample;
if (sample < min) min = sample;
// Digital low pass filter extracts the VDC offset
_pivot[channel] = (_pivot[channel] + (sample - _pivot[channel]) / EMON_FILTER_SPEED);
filtered = sample - _pivot[channel];
// Root-mean-square method
sum += (filtered * filtered);
}
time_span = millis() - time_span;
// Quick fix
if (_pivot[channel] < min || max < _pivot[channel]) {
_pivot[channel] = (max + min) / 2.0;
}
// Calculate current
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
double current = _current_factor[channel] * rms;
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
if (current < 0) current = 0;
#if SENSOR_DEBUG
DEBUG_MSG("[EMON] Channel: %d\n", channel);
DEBUG_MSG("[EMON] Total samples: %d\n", _samples);
DEBUG_MSG("[EMON] Total time (ms): %d\n", time_span);
DEBUG_MSG("[EMON] Sample frequency (Hz): %d\n", int(1000 * _samples / time_span));
DEBUG_MSG("[EMON] Max value: %d\n", max);
DEBUG_MSG("[EMON] Min value: %d\n", min);
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
#endif
// Check timing
if ((time_span > EMON_MAX_TIME)
|| ((time_span < EMON_MAX_TIME) && (_samples < EMON_MAX_SAMPLES))) {
_samples = (_samples * EMON_MAX_TIME) / time_span;
}
return current;
}
unsigned char _channels = 0; // Number of ADC channels available
unsigned char _magnitudes = 0; // Number of magnitudes per channel
unsigned long _samples = EMON_MAX_SAMPLES; // Samples (dynamically modificable)
unsigned char _resolution = 10; // ADC resolution in bits
unsigned long _adc_counts; // Max count
double _voltage = EMON_MAINS_VOLTAGE; // Mains voltage
double _reference = EMON_REFERENCE_VOLTAGE; // ADC reference voltage (100%)
double * _current_ratio; // Ratio ampers in main loop to voltage in secondary (per channel)
double * _current_factor; // Calculated, reads (RMS) to current (per channel)
uint16_t * _multiplier; // Calculated, error (per channel)
double * _pivot; // Moving average mid point (per channel)
double * _current; // Last current reading (per channel)
#if EMON_REPORT_ENERGY
uint32_t * _energy; // Aggregated energy (per channel)
#endif
};
#endif // SENSOR_SUPPORT

211
espurna/sensors/EventSensor.h Executable file
View File

@ -0,0 +1,211 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && EVENTS_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class EventSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
EventSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_EVENTS_ID;
}
~EventSensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setInterruptMode(unsigned char mode) {
_interrupt_mode = mode;
}
void setDebounceTime(unsigned long debounce) {
_debounce = debounce;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
unsigned char getInterruptMode() {
return _interrupt_mode;
}
unsigned long getDebounceTime() {
return _debounce;
}
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "INTERRUPT @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_EVENTS;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) {
double value = _events;
_events = 0;
return value;
};
return 0;
}
// Handle interrupt calls
void handleInterrupt(unsigned char gpio) {
(void) gpio;
static unsigned long last = 0;
if (millis() - last > _debounce) {
_events = _events + 1;
last = millis();
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(EventSensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_gpio = GPIO_NONE;
if (value) {
if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio);
_attach(this, _gpio, _interrupt_mode);
_interrupt_gpio = _gpio;
} else if (_interrupt_gpio != GPIO_NONE) {
_detach(_interrupt_gpio);
_interrupt_gpio = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
volatile unsigned long _events = 0;
unsigned long _debounce = EVENTS_DEBOUNCE;
unsigned char _gpio;
unsigned char _mode;
unsigned char _interrupt_mode;
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
EventSensor * _event_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _event_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_event_sensor_instance[index]) {
_event_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _event_sensor_isr_0() { _event_sensor_isr(0); }
void ICACHE_RAM_ATTR _event_sensor_isr_1() { _event_sensor_isr(1); }
void ICACHE_RAM_ATTR _event_sensor_isr_2() { _event_sensor_isr(2); }
void ICACHE_RAM_ATTR _event_sensor_isr_3() { _event_sensor_isr(3); }
void ICACHE_RAM_ATTR _event_sensor_isr_4() { _event_sensor_isr(4); }
void ICACHE_RAM_ATTR _event_sensor_isr_5() { _event_sensor_isr(5); }
void ICACHE_RAM_ATTR _event_sensor_isr_12() { _event_sensor_isr(12); }
void ICACHE_RAM_ATTR _event_sensor_isr_13() { _event_sensor_isr(13); }
void ICACHE_RAM_ATTR _event_sensor_isr_14() { _event_sensor_isr(14); }
void ICACHE_RAM_ATTR _event_sensor_isr_15() { _event_sensor_isr(15); }
static void (*_event_sensor_isr_list[10])() = {
_event_sensor_isr_0, _event_sensor_isr_1, _event_sensor_isr_2,
_event_sensor_isr_3, _event_sensor_isr_4, _event_sensor_isr_5,
_event_sensor_isr_12, _event_sensor_isr_13, _event_sensor_isr_14,
_event_sensor_isr_15
};
void EventSensor::_attach(EventSensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_event_sensor_instance[index] = instance;
attachInterrupt(gpio, _event_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void EventSensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_event_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _event_sensor_instance[index]->description().c_str());
#endif
_event_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && EVENTS_SUPPORT

170
espurna/sensors/GUVAS12SDSensor.h Executable file
View File

@ -0,0 +1,170 @@
// -----------------------------------------------------------------------------
// GUVA-S12SD UV Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// by Mustafa Tufan
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && GUVAS12SD_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
// http://www.eoc-inc.com/genicom/GUVA-S12SD.pdf
//
// GUVA-S12D has a wide spectral range of 200nm-400nm
// The output voltage and the UV index is linear, illumination intensity = 307 * Vsig where: Vsig is the value of voltage measured from the SIG pin of the interface, unit V.
// illumination intensity unit: mW/m2 for the combination strength of UV light with wavelength range: 200nm-400nm
// UV Index = illumination intensity / 200
//
// UV Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10+
// -----------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+--------
// mV | <50 | 227 | 318 | 408 | 503 | 606 | 696 | 795 | 881 | 976 | 1079 | 1170+
// analog val | <10 | 46 | 65 | 83 | 103 | 124 | 142 | 162 | 180 | 200 | 221 | 240+
//
#define UV_SAMPLE_RATE 1
class GUVAS12SDSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
GUVAS12SDSensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_GUVAS12SD_ID;
}
~GUVAS12SDSensor() {
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
// Manage GPIO lock
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
_previous = GPIO_NONE;
if (!gpioGetLock(_gpio)) {
_error = SENSOR_ERROR_GPIO_USED;
return;
}
_previous = _gpio;
_ready = true;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
_read();
}
// Descriptive name of the sensor
String description() {
char buffer[18];
snprintf(buffer, sizeof(buffer), "GUVAS12SD @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_UV;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _uvindex;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
int _average = 0;
#if UV_SAMPLE_RATE == 1
_average = analogRead(0);
#else
for (unsigned int i=0; i < UV_SAMPLE_RATE; i++) {
_average += analogRead(0);
nice_delay(2);
}
_average = (_average / UV_SAMPLE_RATE);
#endif
// _sensormV = _average / 1023*3.3;
if (_average < 10) {
_uvindex = 0;
} else if (_average < 46) {
_uvindex = (_average - 10) / (46-10);
} else if (_average < 65) {
_uvindex = 1 + ((_average - 46) / (65-46));
} else if (_average < 83) {
_uvindex = 2 + ((_average - 65) / (83-65));
} else if (_average < 103) {
_uvindex = 3 + ((_average - 83) / (103- 83));
} else if (_average < 124) {
_uvindex = 4 + ((_average - 103) / (124-103));
} else if (_average < 142) {
_uvindex = 5 + ((_average - 124) / (142-124));
} else if (_average < 162) {
_uvindex = 6 + ((_average - 142) / (162-142));
} else if (_average < 180) {
_uvindex = 7 + ((_average - 162) / (180-162));
} else if (_average < 200) {
_uvindex = 8 + ((_average - 180) / (200-180));
} else if (_average < 221) {
_uvindex = 9 + ((_average - 200) / (221-200));
} else {
_uvindex = 10;
}
return _uvindex;
}
unsigned char _gpio = GPIO_NONE;
unsigned char _previous = GPIO_NONE;
double _uvindex = 0;
};
#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT

328
espurna/sensors/HLW8012Sensor.h Executable file
View File

@ -0,0 +1,328 @@
// -----------------------------------------------------------------------------
// Event Counter Sensor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && HLW8012_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <ESP8266WiFi.h>
#include <HLW8012.h>
class HLW8012Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
HLW8012Sensor(): BaseSensor() {
_count = 7;
_sensor_id = SENSOR_HLW8012_ID;
_hlw8012 = new HLW8012();
}
~HLW8012Sensor() {
_enableInterrupts(false);
delete _hlw8012;
}
void expectedCurrent(double expected) {
_hlw8012->expectedCurrent(expected);
}
void expectedVoltage(unsigned int expected) {
_hlw8012->expectedVoltage(expected);
}
void expectedPower(unsigned int expected) {
_hlw8012->expectedActivePower(expected);
}
void resetRatios() {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
_hlw8012->resetEnergy();
}
// ---------------------------------------------------------------------
void setSEL(unsigned char sel) {
if (_sel == sel) return;
_sel = sel;
_dirty = true;
}
void setCF(unsigned char cf) {
if (_cf == cf) return;
_cf = cf;
_dirty = true;
}
void setCF1(unsigned char cf1) {
if (_cf1 == cf1) return;
_cf1 = cf1;
_dirty = true;
}
void setSELCurrent(bool value) {
_sel_current = value;
}
void setCurrentRatio(double value) {
_hlw8012->setCurrentMultiplier(value);
};
void setVoltageRatio(double value) {
_hlw8012->setVoltageMultiplier(value);
};
void setPowerRatio(double value) {
_hlw8012->setPowerMultiplier(value);
};
// ---------------------------------------------------------------------
unsigned char getSEL() {
return _sel;
}
unsigned char getCF() {
return _cf;
}
unsigned char getCF1() {
return _cf1;
}
unsigned char getSELCurrent() {
return _sel_current;
}
double getCurrentRatio() {
return _hlw8012->getCurrentMultiplier();
};
double getVoltageRatio() {
return _hlw8012->getVoltageMultiplier();
};
double getPowerRatio() {
return _hlw8012->getPowerMultiplier();
};
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
// Initialize HLW8012
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
// * currentWhen is the value in sel_pin to select current sampling
// * set use_interrupts to true to use interrupts to monitor pulse widths
// * leave pulse_timeout to the default value, recommended when using interrupts
#if HLW8012_USE_INTERRUPTS
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, true);
#else
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, false, 1000000);
#endif
// These values are used to calculate current, voltage and power factors as per datasheet formula
// These are the nominal values for the Sonoff POW resistors:
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
_hlw8012->setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
// Handle interrupts
#if HLW8012_USE_INTERRUPTS
_enableInterrupts(true);
#else
_onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) {
_enableInterrupts(true);
});
_ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) {
_enableInterrupts(false);
});
#endif
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%u,%u,%u)", _sel, _cf, _cf1);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
snprintf(buffer, sizeof(buffer), "%u:%u:%u", _sel, _cf, _cf1);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _hlw8012->getCurrent();
if (index == 1) return _hlw8012->getVoltage();
if (index == 2) return _hlw8012->getActivePower();
if (index == 3) return _hlw8012->getReactivePower();
if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return _hlw8012->getEnergy();
return 0;
}
// Toggle between current and voltage monitoring
#if HLW8012_USE_INTERRUPTS == 0
// Post-read hook (usually to reset things)
void post() { _hlw8012->toggleMode(); }
#endif // HLW8012_USE_INTERRUPTS == 0
// Handle interrupt calls
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
if (gpio == _cf) _hlw8012->cf_interrupt();
if (gpio == _cf1) _hlw8012->cf1_interrupt();
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_cf = GPIO_NONE;
static unsigned char _interrupt_cf1 = GPIO_NONE;
if (value) {
if (_interrupt_cf != _cf) {
if (_interrupt_cf != GPIO_NONE) _detach(_interrupt_cf);
_attach(this, _cf, CHANGE);
_interrupt_cf = _cf;
}
if (_interrupt_cf1 != _cf1) {
if (_interrupt_cf1 != GPIO_NONE) _detach(_interrupt_cf1);
_attach(this, _cf1, CHANGE);
_interrupt_cf1 = _cf1;
}
} else {
_detach(_cf);
_detach(_cf1);
_interrupt_cf = GPIO_NONE;
_interrupt_cf1 = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
unsigned char _sel = GPIO_NONE;
unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true;
HLW8012 * _hlw8012 = NULL;
#if HLW8012_USE_INTERRUPTS == 0
WiFiEventHandler _onconnect_handler;
WiFiEventHandler _ondisconnect_handler;
#endif
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
HLW8012Sensor * _hlw8012_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _hlw8012_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_hlw8012_sensor_instance[index]) {
_hlw8012_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_0() { _hlw8012_sensor_isr(0); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_1() { _hlw8012_sensor_isr(1); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_2() { _hlw8012_sensor_isr(2); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_3() { _hlw8012_sensor_isr(3); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_4() { _hlw8012_sensor_isr(4); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_5() { _hlw8012_sensor_isr(5); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_12() { _hlw8012_sensor_isr(12); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_13() { _hlw8012_sensor_isr(13); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_14() { _hlw8012_sensor_isr(14); }
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_15() { _hlw8012_sensor_isr(15); }
static void (*_hlw8012_sensor_isr_list[10])() = {
_hlw8012_sensor_isr_0, _hlw8012_sensor_isr_1, _hlw8012_sensor_isr_2,
_hlw8012_sensor_isr_3, _hlw8012_sensor_isr_4, _hlw8012_sensor_isr_5,
_hlw8012_sensor_isr_12, _hlw8012_sensor_isr_13, _hlw8012_sensor_isr_14,
_hlw8012_sensor_isr_15
};
void HLW8012Sensor::_attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_hlw8012_sensor_instance[index] = instance;
attachInterrupt(gpio, _hlw8012_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void HLW8012Sensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_hlw8012_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str());
#endif
_hlw8012_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && HLW8012_SUPPORT

95
espurna/sensors/I2CSensor.h Executable file
View File

@ -0,0 +1,95 @@
// -----------------------------------------------------------------------------
// Abstract I2C sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT )
#if I2C_USE_BRZO
#define I2C_TRANS_SUCCESS 0 // All i2c commands were executed without errors
#define I2C_TRANS_ERROR_BUS_NOT_FREE 1 // Bus not free, i.e. either SDA or SCL is low
#define I2C_TRANS_ERROR_NACK_WRITE 2 // Not ACK ("NACK") by slave during write:
// Either the slave did not respond to the given slave address; or the slave did not ACK a byte transferred by the master.
#define I2C_TRANS_ERROR_NACK_READ 4 // Not ACK ("NACK") by slave during read,
// i.e. slave did not respond to the given slave address
#define I2C_TRANS_ERROR_CLOCK 8 // Clock Stretching by slave exceeded maximum clock stretching time. Most probably, there is a bus stall now!
#define I2C_TRANS_ERROR_READ_NULL 16 // Read was called with 0 bytes to be read by the master. Command not sent to the slave, since this could yield to a bus stall
#define I2C_TRANS_ERROR_TIMEOUT 32 // ACK Polling timeout exceeded
#else // Wire
#define I2C_TRANS_SUCCESS 0 // success
#define I2C_TRANS_ERROR_BUFFER_OVERLOW 1 // data too long to fit in transmit buffer
#define I2C_TRANS_ERROR_NACK_ADDRESS 2 // received NACK on transmit of address
#define I2C_TRANS_ERROR_NACK_DATA 3 // received NACK on transmit of data
#define I2C_TRANS_ERROR_OTHER 4 // other error
#endif
#pragma once
#include "BaseSensor.h"
class I2CSensor : public BaseSensor {
public:
void setAddress(unsigned char address) {
if (_address == address) return;
_address = address;
_dirty = true;
}
unsigned char getAddress() {
return _address;
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[5];
snprintf(buffer, sizeof(buffer), "0x%02X", _address);
return String(buffer);
}
protected:
// Specific for I2C sensors
unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) {
// If we have already locked this address for this sensor quit
if ((address > 0) && (address == _previous_address)) {
return _previous_address;
}
// Check if we should release a previously locked address
if ((_previous_address > 0) && (_previous_address != address)) {
i2cReleaseLock(_previous_address);
_previous_address = 0;
}
// If requesting a specific address, try to ger a lock to it
if ((0 < address) && i2cGetLock(address)) {
_previous_address = address;
return _previous_address;
}
// If everything else fails, perform an auto-discover
_previous_address = i2cFindAndLock(size, addresses);
// Flag error
if (0 == _previous_address) {
_error = SENSOR_ERROR_I2C;
}
return _previous_address;
}
unsigned char _previous_address = 0;
unsigned char _address = 0;
};
#endif // SENSOR_SUPPORT && I2C_SUPPORT

221
espurna/sensors/MHZ19Sensor.h Executable file
View File

@ -0,0 +1,221 @@
// -----------------------------------------------------------------------------
// MHZ19 CO2 sensor
// Based on: https://github.com/nara256/mhz19_uart
// http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
// Uses SoftwareSerial library
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && MHZ19_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
#define MHZ19_REQUEST_LEN 8
#define MHZ19_RESPONSE_LEN 9
#define MHZ19_TIMEOUT 1000
#define MHZ19_GETPPM 0x8600
#define MHZ19_ZEROCALIB 0x8700
#define MHZ19_SPANCALIB 0x8800
#define MHZ19_AUTOCALIB_ON 0x79A0
#define MHZ19_AUTOCALIB_OFF 0x7900
class MHZ19Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
MHZ19Sensor(): BaseSensor() {
_count = 1;
_sensor_id = SENSOR_MHZ19_ID;
}
~MHZ19Sensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "MHZ19 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CO2;
return MAGNITUDE_NONE;
}
void pre() {
_read();
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _co2;
return 0;
}
void calibrateAuto(boolean state){
_write(state ? MHZ19_AUTOCALIB_ON : MHZ19_AUTOCALIB_OFF);
}
void calibrateZero() {
_write(MHZ19_ZEROCALIB);
}
void calibrateSpan(unsigned int ppm) {
if( ppm < 1000 ) return;
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
buffer[0] = 0xFF;
buffer[1] = 0x01;
buffer[2] = MHZ19_SPANCALIB >> 8;
buffer[3] = ppm >> 8;
buffer[4] = ppm & 0xFF;
_write(buffer);
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _write(unsigned char * command) {
_serial->write(command, MHZ19_REQUEST_LEN);
_serial->write(_checksum(command));
_serial->flush();
}
void _write(unsigned int command, unsigned char * response) {
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
buffer[0] = 0xFF;
buffer[1] = 0x01;
buffer[2] = command >> 8;
buffer[3] = command & 0xFF;
_write(buffer);
if (response != NULL) {
unsigned long start = millis();
while (_serial->available() == 0) {
if (millis() - start > MHZ19_TIMEOUT) {
_error = SENSOR_ERROR_TIMEOUT;
return;
}
yield();
}
_serial->readBytes(response, MHZ19_RESPONSE_LEN);
}
}
void _write(unsigned int command) {
_write(command, NULL);
}
void _read() {
unsigned char buffer[MHZ19_RESPONSE_LEN] = {0};
_write(MHZ19_GETPPM, buffer);
// Check response
if ((buffer[0] == 0xFF)
&& (buffer[1] == 0x86)
&& (_checksum(buffer) == buffer[MHZ19_RESPONSE_LEN-1])) {
unsigned int value = buffer[2] * 256 + buffer[3];
if (0 <= value && value <= 5000) {
_co2 = value;
_error = SENSOR_ERROR_OK;
} else {
_error = SENSOR_ERROR_OUT_OF_RANGE;
}
} else {
_error = SENSOR_ERROR_CRC;
}
}
uint8_t _checksum(uint8_t * command) {
uint8_t sum = 0x00;
for (unsigned char i = 1; i < MHZ19_REQUEST_LEN-1; i++) {
sum += command[i];
}
sum = 0xFF - sum + 0x01;
return sum;
}
double _co2 = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
SoftwareSerial * _serial = NULL;
};
#endif // SENSOR_SUPPORT && MHZ19_SUPPORT

152
espurna/sensors/PMSX003Sensor.h Executable file
View File

@ -0,0 +1,152 @@
// -----------------------------------------------------------------------------
// PMSX003 Dust Sensor
// Uses SoftwareSerial library
// Contribution by Òscar Rovira López
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PMSX003_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <PMS.h>
#include <SoftwareSerial.h>
class PMSX003Sensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PMSX003Sensor(): BaseSensor() {
_count = 3;
_sensor_id = SENSOR_PMSX003_ID;
}
~PMSX003Sensor() {
if (_serial) delete _serial;
if (_pms) delete _pms;
}
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
if (_pms) delete _pms;
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
_pms = new PMS(* _serial);
_pms->passiveMode();
_startTime = millis();
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[36] = {0};
if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_PM1dot0;
if (index == 1) return MAGNITUDE_PM2dot5;
if (index == 2) return MAGNITUDE_PM10;
return MAGNITUDE_NONE;
}
void pre() {
if (millis() - _startTime < 30000) {
_error = SENSOR_ERROR_WARM_UP;
return;
}
_error = SENSOR_ERROR_OK;
if(_pms->read(_data)) {
_pm1dot0 = _data.PM_AE_UG_1_0;
_pm2dot5 = _data.PM_AE_UG_2_5;
_pm10 = _data.PM_AE_UG_10_0;
}
_pms->requestRead();
}
// Current value for slot # index
double value(unsigned char index) {
if(index == 0) return _pm1dot0;
if(index == 1) return _pm2dot5;
if(index == 2) return _pm10;
return 0;
}
protected:
unsigned int _pm1dot0;
unsigned int _pm2dot5;
unsigned int _pm10;
unsigned int _pin_rx;
unsigned int _pin_tx;
unsigned long _startTime;
SoftwareSerial * _serial = NULL;
PMS * _pms = NULL;
PMS::DATA _data;
};
#endif // SENSOR_SUPPORT && PMSX003_SUPPORT

136
espurna/sensors/PZEM004TSensor.h Executable file
View File

@ -0,0 +1,136 @@
// -----------------------------------------------------------------------------
// PZEM004T based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <PZEM004T.h>
class PZEM004TSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
PZEM004TSensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_PZEM004T_ID;
}
~PZEM004TSensor() {
if (_pzem) delete _pzem;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
void setSerial(Stream & serial) {
_serial = serial;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
Stream & getSerial() {
return _serial;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_pzem) delete _pzem;
if (_serial == NULL) {
_pzem = PZEM004T(_pin_rx, _pin_tx);
} else {
_pzem = PZEM004T(_serial);
}
_pzem->setAddress(_ip);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return _ip.toString();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _pzem->current(_ip);
if (index == 1) return _pzem->voltage(_ip);
if (index == 2) return _pzem->power(_ip);
if (index == 3) return _pzem->energy(_ip);
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
Stream & _serial = NULL;
IPAddress _ip(192,168,1,1);
PZEM004T * _pzem = NULL;
};
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT

View File

@ -0,0 +1,89 @@
// -----------------------------------------------------------------------------
// SHT3X Sensor over I2C (Wemos)
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
class SHT3XI2CSensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SHT3XI2CSensor(): I2CSensor() {
_sensor_id = SENSOR_SHT3X_I2C_ID;
_count = 2;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
// I2C auto-discover
unsigned char addresses[] = {0x45};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[25];
snprintf(buffer, sizeof(buffer), "SHT3X @ I2C (0x%02X)", _address);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_OK;
unsigned char buffer[6];
i2c_write_uint8(_address, 0x2C, 0x06);
nice_delay(500);
i2c_read_buffer(_address, buffer, 6);
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
_temperature = ((((buffer[0] * 256.0) + buffer[1]) * 175) / 65535.0) - 45;
_humidity = ((((buffer[3] * 256.0) + buffer[4]) * 100) / 65535.0);
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
double _temperature = 0;
unsigned char _humidity = 0;
};
#endif // SENSOR_SUPPORT && SHT3X_I2C_SUPPORT

168
espurna/sensors/SI7021Sensor.h Executable file
View File

@ -0,0 +1,168 @@
// -----------------------------------------------------------------------------
// SI7021 / HTU21D Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && SI7021_SUPPORT
#pragma once
#include "Arduino.h"
#include "I2CSensor.h"
#define SI7021_SCL_FREQUENCY 200
#define SI7021_CHIP_SI7021 0x15
#define SI7021_CHIP_HTU21D 0x32
#define SI7021_CMD_TMP_HOLD 0xE3
#define SI7021_CMD_HUM_HOLD 0xE5
#define SI7021_CMD_TMP_NOHOLD 0xF3
#define SI7021_CMD_HUM_NOHOLD 0xF5
PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
class SI7021Sensor : public I2CSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
SI7021Sensor(): I2CSensor() {
_sensor_id = SENSOR_SI7021_ID;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
_init();
_dirty = !_ready;
}
// Descriptive name of the sensor
String description() {
char name[10];
strncpy_P(name,
_chip == SI7021_CHIP_SI7021 ?
si7021_chip_si7021_name :
si7021_chip_htu21d_name,
sizeof(name)
);
char buffer[25];
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_TEMPERATURE;
if (index == 1) return MAGNITUDE_HUMIDITY;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
_error = SENSOR_ERROR_UNKNOWN_ID;
if (_chip == 0) return;
_error = SENSOR_ERROR_OK;
double value;
value = _read(SI7021_CMD_TMP_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
_temperature = (175.72 * value / 65536) - 46.85;
value = _read(SI7021_CMD_HUM_NOHOLD);
if (_error != SENSOR_ERROR_OK) return;
value = (125.0 * value / 65536) - 6;
_humidity = constrain(value, 0, 100);
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _temperature;
if (index == 1) return _humidity;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _init() {
// I2C auto-discover
unsigned char addresses[] = {0x40};
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (_address == 0) return;
// Check device
i2c_write_uint8(_address, 0xFC, 0xC9);
_chip = i2c_read_uint8(_address);
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
_count = 0;
i2cReleaseLock(_address);
_previous_address = 0;
_error = SENSOR_ERROR_UNKNOWN_ID;
// Setting _address to 0 forces auto-discover
// This might be necessary at this stage if there is a
// different sensor in the hardcoded address
_address = 0;
} else {
_count = 2;
}
_ready = true;
}
unsigned int _read(uint8_t command) {
// Request measurement
i2c_write_uint8(_address, command);
// When not using clock stretching (*_NOHOLD commands) delay here
// is needed to wait for the measurement.
// According to datasheet the max. conversion time is ~22ms
unsigned long start = millis();
nice_delay(50);
// Clear the last to bits of LSB to 00.
// According to datasheet LSB of RH is always xxxxxx10
unsigned int value = i2c_read_uint16(_address) & 0xFFFC;
// We should be checking there are no pending bytes in the buffer
// and raise a CRC error if there are
_error = SENSOR_ERROR_OK;
return value;
}
unsigned char _chip;
double _temperature = 0;
double _humidity = 0;
};
#endif // SENSOR_SUPPORT && SI7021_SUPPORT

259
espurna/sensors/V9261FSensor.h Executable file
View File

@ -0,0 +1,259 @@
// -----------------------------------------------------------------------------
// V9261F based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && V9261F_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
#include <SoftwareSerial.h>
class V9261FSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
V9261FSensor(): BaseSensor(), _data() {
_count = 6;
_sensor_id = SENSOR_V9261F_ID;
}
~V9261FSensor() {
if (_serial) delete _serial;
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
bool getInverted() {
return _inverted;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_serial) delete _serial;
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(V9261F_BAUDRATE);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _reactive;
if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
return 0;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void _read() {
static unsigned char state = 0;
static unsigned long last = 0;
static bool found = false;
static unsigned char index = 0;
if (state == 0) {
while (_serial->available()) {
_serial->flush();
found = true;
last = millis();
}
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
_serial->flush();
index = 0;
state = 1;
}
} else if (state == 1) {
while (_serial->available()) {
_serial->read();
if (index++ >= 7) {
_serial->flush();
index = 0;
state = 2;
}
}
} else if (state == 2) {
while (_serial->available()) {
_data[index] = _serial->read();
if (index++ >= 19) {
_serial->flush();
last = millis();
state = 3;
}
}
} else if (state == 3) {
if (_checksum()) {
_active = (double) (
(_data[3]) +
(_data[4] << 8) +
(_data[5] << 16) +
(_data[6] << 24)
) / _ratioP;
_reactive = (double) (
(_data[7]) +
(_data[8] << 8) +
(_data[9] << 16) +
(_data[10] << 24)
) / _ratioR;
_voltage = (double) (
(_data[11]) +
(_data[12] << 8) +
(_data[13] << 16) +
(_data[14] << 24)
) / _ratioV;
_current = (double) (
(_data[15]) +
(_data[16] << 8) +
(_data[17] << 16) +
(_data[18] << 24)
) / _ratioC;
if (_active < 0) _active = 0;
if (_reactive < 0) _reactive = 0;
if (_voltage < 0) _voltage = 0;
if (_current < 0) _current = 0;
_apparent = sqrt(_reactive * _reactive + _active * _active);
}
last = millis();
index = 0;
state = 4;
} else if (state == 4) {
while (_serial->available()) {
_serial->flush();
last = millis();
}
if (millis() - last > V9261F_SYNC_INTERVAL) {
state = 1;
}
}
}
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 0; i < 19; i++) {
checksum = checksum + _data[i];
}
checksum = ~checksum + 0x33;
return checksum == _data[19];
}
// ---------------------------------------------------------------------
unsigned int _pin_rx = V9261F_PIN;
bool _inverted = V9261F_PIN_INVERSE;
SoftwareSerial * _serial = NULL;
double _active = 0;
double _reactive = 0;
double _voltage = 0;
double _current = 0;
double _apparent = 0;
double _ratioP = V9261F_POWER_FACTOR;
double _ratioC = V9261F_CURRENT_FACTOR;
double _ratioV = V9261F_VOLTAGE_FACTOR;
double _ratioR = V9261F_RPOWER_FACTOR;
unsigned char _data[24];
};
#endif // SENSOR_SUPPORT && V9261F_SUPPORT