Initial
This commit is contained in:
193
espurna/sensors/AM2320Sensor.h
Executable file
193
espurna/sensors/AM2320Sensor.h
Executable file
@ -0,0 +1,193 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// AM2320 Humidity & Temperature sensor over I2C
|
||||
// Copyright (C) 2018 by Mustafa Tufan
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && AM2320_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf
|
||||
#define AM2320_I2C_READ_REGISTER_DATA 0x03 // Read one or more data registers
|
||||
#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS 0x10 // Multiple sets of binary data to write multiple registers
|
||||
/*
|
||||
Register | Address | Register | Address | Register | Address | Register | Address
|
||||
-----------------+---------+--------------------+---------+-------------------------+---------+-----------+--------
|
||||
High humidity | 0x00 | Model High | 0x08 | Users register a high | 0x10 | Retention | 0x18
|
||||
Low humidity | 0x01 | Model Low | 0x09 | Users register a low | 0x11 | Retention | 0x19
|
||||
High temperature | 0x02 | The version number | 0x0A | Users register 2 high | 0x12 | Retention | 0x1A
|
||||
Low temperature | 0x03 | Device ID(24-31)Bit| 0x0B | Users register 2 low | 0x13 | Retention | 0x1B
|
||||
Retention | 0x04 | Device ID(24-31)Bit| 0x0C | Retention | 0x14 | Retention | 0x1C
|
||||
Retention | 0x05 | Device ID(24-31)Bit| 0x0D | Retention | 0x15 | Retention | 0x1D
|
||||
Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention | 0x16 | Retention | 0x1E
|
||||
Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F
|
||||
*/
|
||||
|
||||
class AM2320Sensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
AM2320Sensor(): I2CSensor() {
|
||||
_count = 2;
|
||||
_sensor_id = SENSOR_AM2320_ID;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
if (!_dirty) return;
|
||||
|
||||
// I2C auto-discover
|
||||
unsigned char addresses[] = {0x23, 0x5C, 0xB8};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_TEMPERATURE;
|
||||
if (index == 1) return MAGNITUDE_HUMIDITY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
_read();
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _temperature;
|
||||
if (index == 1) return _humidity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
// Get device model, version, device_id
|
||||
|
||||
void _init() {
|
||||
i2c_wakeup();
|
||||
delayMicroseconds(800);
|
||||
|
||||
unsigned char _buffer[11];
|
||||
|
||||
// 0x08 = read address
|
||||
// 7 = number of bytes to read
|
||||
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) {
|
||||
_error = SENSOR_ERROR_TIMEOUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t model = (_buffer[2] << 8) | _buffer[3];
|
||||
uint8_t version = _buffer[4];
|
||||
uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5];
|
||||
}
|
||||
*/
|
||||
|
||||
void _read() {
|
||||
i2c_wakeup();
|
||||
// waiting time of at least 800 μs, the maximum 3000 μs
|
||||
delayMicroseconds(800); // just to be on safe side
|
||||
|
||||
// 0x00 = read address
|
||||
// 4 = number of bytes to read
|
||||
if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) {
|
||||
_error = SENSOR_ERROR_TIMEOUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char _buffer[8];
|
||||
|
||||
// waiting time of at least 800 μs, the maximum 3000 μs
|
||||
delayMicroseconds(800 + ((3000-800)/2) );
|
||||
i2c_read_buffer(_address, _buffer, 8);
|
||||
|
||||
// Humidity : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 %
|
||||
// 0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 %
|
||||
// Temperature: 00FA = (F×16)+A = 250 => temperature = 250÷10 = 25.0 C
|
||||
// 0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C
|
||||
// Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature
|
||||
|
||||
// _buffer 0 = function code
|
||||
// _buffer 1 = number of bytes
|
||||
// _buffer 2-3 = high/low humidity
|
||||
// _buffer 4-5 = high/low temperature
|
||||
// _buffer 6-7 = CRC low/high
|
||||
|
||||
unsigned int responseCRC = 0;
|
||||
responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]);
|
||||
|
||||
if (responseCRC == _CRC16(_buffer)) {
|
||||
int foo = (_buffer[2] << 8) | _buffer[3];
|
||||
_humidity = foo / 10.0;
|
||||
|
||||
foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5]; // clean bit 15 and merge
|
||||
_temperature = foo / 10.0;
|
||||
|
||||
if (_buffer[4] & 0x80) { // is bit 15 == 1
|
||||
_temperature = _temperature * -1; // negative temperature
|
||||
}
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
} else {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int _CRC16(unsigned char buffer[]) {
|
||||
unsigned int crc16 = 0xFFFF;
|
||||
|
||||
for (unsigned int i = 0; i < 6; i++) {
|
||||
crc16 ^= buffer[i];
|
||||
|
||||
for (unsigned int b = 8; b != 0; b--) {
|
||||
if (crc16 & 0x01) { // is lsb set
|
||||
crc16 >>= 1;
|
||||
crc16 ^= 0xA001;
|
||||
} else {
|
||||
crc16 >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc16;
|
||||
}
|
||||
|
||||
double _temperature = 0;
|
||||
double _humidity = 0;
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && AM2320_SUPPORT
|
66
espurna/sensors/AnalogSensor.h
Executable file
66
espurna/sensors/AnalogSensor.h
Executable file
@ -0,0 +1,66 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Analog Sensor (maps to an analogRead)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && ANALOG_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
class AnalogSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
AnalogSensor(): BaseSensor() {
|
||||
_count = 1;
|
||||
_sensor_id = SENSOR_ANALOG_ID;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
pinMode(0, INPUT);
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
return String("ANALOG @ TOUT");
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String("0");
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_ANALOG;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return analogRead(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && ANALOG_SUPPORT
|
135
espurna/sensors/BH1750Sensor.h
Executable file
135
espurna/sensors/BH1750Sensor.h
Executable file
@ -0,0 +1,135 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BH1750 Liminosity sensor over I2C
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && BH1750_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
|
||||
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
|
||||
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms.
|
||||
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
|
||||
// Device is automatically set to Power Down after measurement.
|
||||
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
|
||||
// Device is automatically set to Power Down after measurement.
|
||||
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
|
||||
// Device is automatically set to Power Down after measurement.
|
||||
|
||||
class BH1750Sensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
BH1750Sensor(): I2CSensor() {
|
||||
_sensor_id = SENSOR_BH1750_ID;
|
||||
_count = 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setMode(unsigned char mode) {
|
||||
if (_mode == mode) return;
|
||||
_mode = mode;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getMode() {
|
||||
return _mode;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
// I2C auto-discover
|
||||
unsigned char addresses[] = {0x23, 0x5C};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
// Run configuration on next update
|
||||
_run_configure = true;
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "BH1750 @ I2C (0x%02X)", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_LUX;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
_lux = _read();
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _lux;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
double _read() {
|
||||
|
||||
// For one-shot modes reconfigure sensor & wait for conversion
|
||||
if (_run_configure) {
|
||||
|
||||
// Configure mode
|
||||
i2c_write_uint8(_address, _mode);
|
||||
|
||||
// According to datasheet
|
||||
// conversion time is ~16ms for low resolution
|
||||
// and ~120 for high resolution
|
||||
// but more time is needed
|
||||
unsigned long wait = (_mode & 0x02) ? 24 : 180;
|
||||
unsigned long start = millis();
|
||||
while (millis() - start < wait) delay(1);
|
||||
|
||||
// Keep on running configure each time if one-shot mode
|
||||
_run_configure = _mode & 0x20;
|
||||
|
||||
}
|
||||
|
||||
double level = (double) i2c_read_uint16(_address);
|
||||
if (level == 0xFFFF) {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
_run_configure = true;
|
||||
return 0;
|
||||
}
|
||||
return level / 1.2;
|
||||
|
||||
}
|
||||
|
||||
unsigned char _mode;
|
||||
bool _run_configure = false;
|
||||
double _lux = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && BH1750_SUPPORT
|
438
espurna/sensors/BMX280Sensor.h
Executable file
438
espurna/sensors/BMX280Sensor.h
Executable file
@ -0,0 +1,438 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BME280/BMP280 Sensor over I2C
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && BMX280_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
#define BMX280_CHIP_BMP280 0x58
|
||||
#define BMX280_CHIP_BME280 0x60
|
||||
|
||||
#define BMX280_REGISTER_DIG_T1 0x88
|
||||
#define BMX280_REGISTER_DIG_T2 0x8A
|
||||
#define BMX280_REGISTER_DIG_T3 0x8C
|
||||
|
||||
#define BMX280_REGISTER_DIG_P1 0x8E
|
||||
#define BMX280_REGISTER_DIG_P2 0x90
|
||||
#define BMX280_REGISTER_DIG_P3 0x92
|
||||
#define BMX280_REGISTER_DIG_P4 0x94
|
||||
#define BMX280_REGISTER_DIG_P5 0x96
|
||||
#define BMX280_REGISTER_DIG_P6 0x98
|
||||
#define BMX280_REGISTER_DIG_P7 0x9A
|
||||
#define BMX280_REGISTER_DIG_P8 0x9C
|
||||
#define BMX280_REGISTER_DIG_P9 0x9E
|
||||
|
||||
#define BMX280_REGISTER_DIG_H1 0xA1
|
||||
#define BMX280_REGISTER_DIG_H2 0xE1
|
||||
#define BMX280_REGISTER_DIG_H3 0xE3
|
||||
#define BMX280_REGISTER_DIG_H4 0xE4
|
||||
#define BMX280_REGISTER_DIG_H5 0xE5
|
||||
#define BMX280_REGISTER_DIG_H6 0xE7
|
||||
|
||||
#define BMX280_REGISTER_CHIPID 0xD0
|
||||
#define BMX280_REGISTER_VERSION 0xD1
|
||||
#define BMX280_REGISTER_SOFTRESET 0xE0
|
||||
|
||||
#define BMX280_REGISTER_CAL26 0xE1
|
||||
|
||||
#define BMX280_REGISTER_CONTROLHUMID 0xF2
|
||||
#define BMX280_REGISTER_CONTROL 0xF4
|
||||
#define BMX280_REGISTER_CONFIG 0xF5
|
||||
#define BMX280_REGISTER_PRESSUREDATA 0xF7
|
||||
#define BMX280_REGISTER_TEMPDATA 0xFA
|
||||
#define BMX280_REGISTER_HUMIDDATA 0xFD
|
||||
|
||||
class BMX280Sensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
static unsigned char addresses[2];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
BMX280Sensor(): I2CSensor() {
|
||||
_sensor_id = SENSOR_BMX280_ID;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
if (!_dirty) return;
|
||||
_init();
|
||||
_dirty = !_ready;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
unsigned char i = 0;
|
||||
#if BMX280_TEMPERATURE > 0
|
||||
if (index == i++) return MAGNITUDE_TEMPERATURE;
|
||||
#endif
|
||||
#if BMX280_PRESSURE > 0
|
||||
if (index == i++) return MAGNITUDE_PRESSURE;
|
||||
#endif
|
||||
#if BMX280_HUMIDITY > 0
|
||||
if (_chip == BMX280_CHIP_BME280) {
|
||||
if (index == i) return MAGNITUDE_HUMIDITY;
|
||||
}
|
||||
#endif
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
virtual void pre() {
|
||||
|
||||
if (_run_init) {
|
||||
i2cClearBus();
|
||||
_init();
|
||||
}
|
||||
|
||||
if (_chip == 0) {
|
||||
_error = SENSOR_ERROR_UNKNOWN_ID;
|
||||
return;
|
||||
}
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
#if BMX280_MODE == 1
|
||||
_forceRead();
|
||||
#endif
|
||||
|
||||
_error = _read();
|
||||
|
||||
if (_error != SENSOR_ERROR_OK) {
|
||||
_run_init = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
unsigned char i = 0;
|
||||
#if BMX280_TEMPERATURE > 0
|
||||
if (index == i++) return _temperature;
|
||||
#endif
|
||||
#if BMX280_PRESSURE > 0
|
||||
if (index == i++) return _pressure / 100;
|
||||
#endif
|
||||
#if BMX280_HUMIDITY > 0
|
||||
if (_chip == BMX280_CHIP_BME280) {
|
||||
if (index == i) return _humidity;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Load the configuration manifest
|
||||
static void manifest(JsonArray& sensors) {
|
||||
|
||||
char buffer[10];
|
||||
|
||||
JsonObject& sensor = sensors.createNestedObject();
|
||||
sensor["sensor_id"] = SENSOR_BMX280_ID;
|
||||
JsonArray& fields = sensor.createNestedArray("fields");
|
||||
|
||||
{
|
||||
JsonObject& field = fields.createNestedObject();
|
||||
field["tag"] = UI_TAG_SELECT;
|
||||
field["name"] = "address";
|
||||
field["label"] = "Address";
|
||||
JsonArray& options = field.createNestedArray("options");
|
||||
{
|
||||
JsonObject& option = options.createNestedObject();
|
||||
option["name"] = "auto";
|
||||
option["value"] = 0;
|
||||
}
|
||||
for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) {
|
||||
JsonObject& option = options.createNestedObject();
|
||||
snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]);
|
||||
option["name"] = String(buffer);
|
||||
option["value"] = BMX280Sensor::addresses[i];
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void getConfig(JsonObject& root) {
|
||||
root["sensor_id"] = _sensor_id;
|
||||
root["address"] = _address;
|
||||
};
|
||||
|
||||
void setConfig(JsonObject& root) {
|
||||
if (root.containsKey("address")) setAddress(root["address"]);
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
void _init() {
|
||||
|
||||
// Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up
|
||||
nice_delay(10);
|
||||
|
||||
// No chip ID by default
|
||||
_chip = 0;
|
||||
|
||||
// I2C auto-discover
|
||||
_address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
// Check sensor correctly initialized
|
||||
_chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
|
||||
if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {
|
||||
|
||||
_chip = 0;
|
||||
i2cReleaseLock(_address);
|
||||
_previous_address = 0;
|
||||
_error = SENSOR_ERROR_UNKNOWN_ID;
|
||||
|
||||
// Setting _address to 0 forces auto-discover
|
||||
// This might be necessary at this stage if there is a
|
||||
// different sensor in the hardcoded address
|
||||
_address = 0;
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
#if BMX280_TEMPERATURE > 0
|
||||
++_count;
|
||||
#endif
|
||||
#if BMX280_PRESSURE > 0
|
||||
++_count;
|
||||
#endif
|
||||
#if BMX280_HUMIDITY > 0
|
||||
if (_chip == BMX280_CHIP_BME280) ++_count;
|
||||
#endif
|
||||
|
||||
_readCoefficients();
|
||||
|
||||
unsigned char data = 0;
|
||||
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
|
||||
|
||||
data = (BMX280_STANDBY << 0x5) & 0xE0;
|
||||
data |= (BMX280_FILTER << 0x02) & 0x1C;
|
||||
i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data);
|
||||
|
||||
data = (BMX280_HUMIDITY) & 0x07;
|
||||
i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data);
|
||||
|
||||
data = (BMX280_TEMPERATURE << 5) & 0xE0;
|
||||
data |= (BMX280_PRESSURE << 2) & 0x1C;
|
||||
data |= (BMX280_MODE) & 0x03;
|
||||
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);
|
||||
|
||||
_measurement_delay = _measurementTime();
|
||||
_run_init = false;
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
void _readCoefficients() {
|
||||
_bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1);
|
||||
_bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2);
|
||||
_bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3);
|
||||
|
||||
_bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1);
|
||||
_bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2);
|
||||
_bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3);
|
||||
_bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4);
|
||||
_bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5);
|
||||
_bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6);
|
||||
_bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7);
|
||||
_bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8);
|
||||
_bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9);
|
||||
|
||||
_bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1);
|
||||
_bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2);
|
||||
_bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3);
|
||||
_bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF);
|
||||
_bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4);
|
||||
_bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6);
|
||||
}
|
||||
|
||||
unsigned long _measurementTime() {
|
||||
|
||||
// Measurement Time (as per BMX280 datasheet section 9.1)
|
||||
// T_max(ms) = 1.25
|
||||
// + (2.3 * T_oversampling)
|
||||
// + (2.3 * P_oversampling + 0.575)
|
||||
// + (2.4 * H_oversampling + 0.575)
|
||||
// ~ 9.3ms for current settings
|
||||
|
||||
double t = 1.25;
|
||||
#if BMX280_TEMPERATURE > 0
|
||||
t += (2.3 * BMX280_TEMPERATURE);
|
||||
#endif
|
||||
#if BMX280_PRESSURE > 0
|
||||
t += (2.3 * BMX280_PRESSURE + 0.575);
|
||||
#endif
|
||||
#if BMX280_HUMIDITY > 0
|
||||
if (_chip == BMX280_CHIP_BME280) {
|
||||
t += (2.4 * BMX280_HUMIDITY + 0.575);
|
||||
}
|
||||
#endif
|
||||
|
||||
return round(t + 1); // round up
|
||||
|
||||
}
|
||||
|
||||
void _forceRead() {
|
||||
|
||||
// We set the sensor in "forced mode" to force a reading.
|
||||
// After the reading the sensor will go back to sleep mode.
|
||||
uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL);
|
||||
value = (value & 0xFC) + 0x01;
|
||||
i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);
|
||||
|
||||
nice_delay(_measurement_delay);
|
||||
|
||||
}
|
||||
|
||||
unsigned char _read() {
|
||||
|
||||
#if BMX280_TEMPERATURE > 0
|
||||
int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA);
|
||||
if (0xFFFF == adc_T) return SENSOR_ERROR_I2C;
|
||||
adc_T <<= 8;
|
||||
adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2);
|
||||
adc_T >>= 4;
|
||||
|
||||
int32_t var1t = ((((adc_T>>3) -
|
||||
((int32_t)_bmx280_calib.dig_T1 <<1))) *
|
||||
((int32_t)_bmx280_calib.dig_T2)) >> 11;
|
||||
|
||||
int32_t var2t = (((((adc_T>>4) -
|
||||
((int32_t)_bmx280_calib.dig_T1)) *
|
||||
((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) *
|
||||
((int32_t)_bmx280_calib.dig_T3)) >> 14;
|
||||
|
||||
int32_t t_fine = var1t + var2t;
|
||||
|
||||
double T = (t_fine * 5 + 128) >> 8;
|
||||
_temperature = T / 100;
|
||||
#else
|
||||
int32_t t_fine = 102374; // ~20ºC
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#if BMX280_PRESSURE > 0
|
||||
int64_t var1, var2, p;
|
||||
|
||||
int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA);
|
||||
if (0xFFFF == adc_P) return SENSOR_ERROR_I2C;
|
||||
adc_P <<= 8;
|
||||
adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2);
|
||||
adc_P >>= 4;
|
||||
|
||||
var1 = ((int64_t)t_fine) - 128000;
|
||||
var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6;
|
||||
var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17);
|
||||
var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35);
|
||||
var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
|
||||
((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
|
||||
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
|
||||
if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero
|
||||
|
||||
p = 1048576 - adc_P;
|
||||
p = (((p<<31) - var2)*3125) / var1;
|
||||
var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25;
|
||||
var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19;
|
||||
|
||||
p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4);
|
||||
_pressure = (double) p / 256;
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#if BMX280_HUMIDITY > 0
|
||||
if (_chip == BMX280_CHIP_BME280) {
|
||||
|
||||
int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA);
|
||||
if (0xFFFF == adc_H) return SENSOR_ERROR_I2C;
|
||||
|
||||
int32_t v_x1_u32r;
|
||||
|
||||
v_x1_u32r = (t_fine - ((int32_t)76800));
|
||||
|
||||
v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) -
|
||||
(((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
|
||||
(((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) *
|
||||
(((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
|
||||
((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14));
|
||||
|
||||
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
|
||||
((int32_t)_bmx280_calib.dig_H1)) >> 4));
|
||||
|
||||
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
|
||||
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
|
||||
double h = (v_x1_u32r >> 12);
|
||||
_humidity = h / 1024.0;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
return SENSOR_ERROR_OK;
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char _chip;
|
||||
unsigned long _measurement_delay;
|
||||
bool _run_init = false;
|
||||
double _temperature = 0;
|
||||
double _pressure = 0;
|
||||
double _humidity = 0;
|
||||
|
||||
typedef struct {
|
||||
|
||||
uint16_t dig_T1;
|
||||
int16_t dig_T2;
|
||||
int16_t dig_T3;
|
||||
|
||||
uint16_t dig_P1;
|
||||
int16_t dig_P2;
|
||||
int16_t dig_P3;
|
||||
int16_t dig_P4;
|
||||
int16_t dig_P5;
|
||||
int16_t dig_P6;
|
||||
int16_t dig_P7;
|
||||
int16_t dig_P8;
|
||||
int16_t dig_P9;
|
||||
|
||||
uint8_t dig_H1;
|
||||
int16_t dig_H2;
|
||||
uint8_t dig_H3;
|
||||
int16_t dig_H4;
|
||||
int16_t dig_H5;
|
||||
int8_t dig_H6;
|
||||
|
||||
} bmx280_calib_t;
|
||||
|
||||
bmx280_calib_t _bmx280_calib;
|
||||
|
||||
};
|
||||
|
||||
// Static inizializations
|
||||
|
||||
unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77};
|
||||
|
||||
#endif // SENSOR_SUPPORT && BMX280_SUPPORT
|
101
espurna/sensors/BaseSensor.h
Executable file
101
espurna/sensors/BaseSensor.h
Executable file
@ -0,0 +1,101 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Abstract sensor class (other sensor classes extend this class)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#define SENSOR_ERROR_OK 0 // No error
|
||||
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
|
||||
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
|
||||
#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out
|
||||
#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID
|
||||
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
|
||||
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
|
||||
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
|
||||
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
|
||||
#define SENSOR_ERROR_OTHER 99 // Any other error
|
||||
|
||||
typedef std::function<void(unsigned char, const char *)> TSensorCallback;
|
||||
|
||||
class BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// Constructor
|
||||
BaseSensor() {}
|
||||
|
||||
// Destructor
|
||||
~BaseSensor() {}
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
virtual void begin() {}
|
||||
|
||||
// Loop-like method, call it in your main loop
|
||||
virtual void tick() {}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
virtual void pre() {}
|
||||
|
||||
// Post-read hook (usually to reset things)
|
||||
virtual void post() {}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
virtual String description() {}
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
virtual String address(unsigned char index) {}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
virtual String slot(unsigned char index) {};
|
||||
|
||||
// Type for slot # index
|
||||
virtual unsigned char type(unsigned char index) {}
|
||||
|
||||
// Current value for slot # index
|
||||
virtual double value(unsigned char index) {}
|
||||
|
||||
// Retrieve current instance configuration
|
||||
virtual void getConfig(JsonObject& root) {};
|
||||
|
||||
// Save current instance configuration
|
||||
virtual void setConfig(JsonObject& root) {};
|
||||
|
||||
// Load the configuration manifest
|
||||
static void manifest(JsonArray& root) {};
|
||||
|
||||
// Sensor ID
|
||||
unsigned char getID() { return _sensor_id; };
|
||||
|
||||
// Return status (true if no errors)
|
||||
bool status() { return 0 == _error; }
|
||||
|
||||
// Return ready status (true for ready)
|
||||
bool ready() { return _ready; }
|
||||
|
||||
// Return sensor last internal error
|
||||
int error() { return _error; }
|
||||
|
||||
// Number of available slots
|
||||
unsigned char count() { return _count; }
|
||||
|
||||
// Hook for event callback
|
||||
void onEvent(TSensorCallback fn) { _callback = fn; };
|
||||
|
||||
protected:
|
||||
|
||||
TSensorCallback _callback = NULL;
|
||||
unsigned char _sensor_id = 0x00;
|
||||
int _error = 0;
|
||||
bool _dirty = true;
|
||||
unsigned char _count = 0;
|
||||
bool _ready = false;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
372
espurna/sensors/CSE7766Sensor.h
Executable file
372
espurna/sensors/CSE7766Sensor.h
Executable file
@ -0,0 +1,372 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// CSE7766 based power monitor
|
||||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && CSE7766_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
class CSE7766Sensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
CSE7766Sensor(): BaseSensor(), _data() {
|
||||
_count = 4;
|
||||
_sensor_id = SENSOR_CSE7766_ID;
|
||||
}
|
||||
|
||||
~CSE7766Sensor() {
|
||||
if (_serial) delete _serial;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setRX(unsigned char pin_rx) {
|
||||
if (_pin_rx == pin_rx) return;
|
||||
_pin_rx = pin_rx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setInverted(bool inverted) {
|
||||
if (_inverted == inverted) return;
|
||||
_inverted = inverted;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getRX() {
|
||||
return _pin_rx;
|
||||
}
|
||||
|
||||
bool getInverted() {
|
||||
return _inverted;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void expectedCurrent(double expected) {
|
||||
if ((expected > 0) && (_current > 0)) {
|
||||
_ratioC = _ratioC * (expected / _current);
|
||||
}
|
||||
}
|
||||
|
||||
void expectedVoltage(unsigned int expected) {
|
||||
if ((expected > 0) && (_voltage > 0)) {
|
||||
_ratioV = _ratioV * (expected / _voltage);
|
||||
}
|
||||
}
|
||||
|
||||
void expectedPower(unsigned int expected) {
|
||||
if ((expected > 0) && (_active > 0)) {
|
||||
_ratioP = _ratioP * (expected / _active);
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentRatio(double value) {
|
||||
_ratioC = value;
|
||||
};
|
||||
|
||||
void setVoltageRatio(double value) {
|
||||
_ratioV = value;
|
||||
};
|
||||
|
||||
void setPowerRatio(double value) {
|
||||
_ratioP = value;
|
||||
};
|
||||
|
||||
double getCurrentRatio() {
|
||||
return _ratioC;
|
||||
};
|
||||
|
||||
double getVoltageRatio() {
|
||||
return _ratioV;
|
||||
};
|
||||
|
||||
double getPowerRatio() {
|
||||
return _ratioP;
|
||||
};
|
||||
|
||||
void resetRatios() {
|
||||
_ratioC = _ratioV = _ratioP = 1.0;
|
||||
}
|
||||
|
||||
void resetEnergy() {
|
||||
_energy = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
if (_serial) delete _serial;
|
||||
|
||||
if (1 == _pin_rx) {
|
||||
Serial.begin(CSE7766_BAUDRATE);
|
||||
} else {
|
||||
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
|
||||
_serial->enableIntTx(false);
|
||||
_serial->begin(CSE7766_BAUDRATE);
|
||||
}
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[28];
|
||||
if (1 == _pin_rx) {
|
||||
snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial");
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_pin_rx);
|
||||
}
|
||||
|
||||
// Loop-like method, call it in your main loop
|
||||
void tick() {
|
||||
_read();
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CURRENT;
|
||||
if (index == 1) return MAGNITUDE_VOLTAGE;
|
||||
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
|
||||
if (index == 3) return MAGNITUDE_ENERGY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _current;
|
||||
if (index == 1) return _voltage;
|
||||
if (index == 2) return _active;
|
||||
if (index == 3) return _energy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* "
|
||||
* Checksum is the sum of all data
|
||||
* except for packet header and packet tail lowering by 8bit (...)
|
||||
* "
|
||||
* @return bool
|
||||
*/
|
||||
bool _checksum() {
|
||||
unsigned char checksum = 0;
|
||||
for (unsigned char i = 2; i < 23; i++) {
|
||||
checksum += _data[i];
|
||||
}
|
||||
return checksum == _data[23];
|
||||
}
|
||||
|
||||
void _process() {
|
||||
|
||||
// Sample data:
|
||||
// 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load)
|
||||
// F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load)
|
||||
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG("[SENSOR] CSE7766: _process: ");
|
||||
for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]);
|
||||
DEBUG_MSG("\n");
|
||||
#endif
|
||||
|
||||
// Checksum
|
||||
if (!_checksum()) {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Calibration
|
||||
if (0xAA == _data[0]) {
|
||||
_error = SENSOR_ERROR_CALIBRATION;
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_data[0] & 0xFC) > 0xF0) {
|
||||
_error = SENSOR_ERROR_OTHER;
|
||||
#if SENSOR_DEBUG
|
||||
if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n");
|
||||
if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n");
|
||||
if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n");
|
||||
if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Calibration coefficients
|
||||
unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770
|
||||
unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030
|
||||
unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000
|
||||
|
||||
// Adj: this looks like a sampling report
|
||||
uint8_t adj = _data[20]; // F1 11110001
|
||||
|
||||
// Calculate voltage
|
||||
_voltage = 0;
|
||||
if ((adj & 0x40) == 0x40) {
|
||||
unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817
|
||||
_voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41
|
||||
}
|
||||
|
||||
// Calculate power
|
||||
_active = 0;
|
||||
if ((adj & 0x10) == 0x10) {
|
||||
if ((_data[0] & 0xF2) != 0xF2) {
|
||||
unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709
|
||||
_active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate current
|
||||
_current = 0;
|
||||
if ((adj & 0x20) == 0x20) {
|
||||
if (_active > 0) {
|
||||
unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376
|
||||
_current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate energy
|
||||
static unsigned long cf_pulses_last = 0;
|
||||
unsigned long cf_pulses = _data[21] << 8 | _data[22];
|
||||
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
|
||||
_energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0;
|
||||
cf_pulses_last = cf_pulses;
|
||||
|
||||
}
|
||||
|
||||
void _read() {
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
static unsigned char index = 0;
|
||||
static unsigned long last = millis();
|
||||
|
||||
while (_serial_available()) {
|
||||
|
||||
// A 24 bytes message takes ~55ms to go through at 4800 bps
|
||||
// Reset counter if more than 1000ms have passed since last byte.
|
||||
if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
|
||||
last = millis();
|
||||
|
||||
uint8_t byte = _serial_read();
|
||||
|
||||
// first byte must be 0x55 or 0xF?
|
||||
if (0 == index) {
|
||||
if ((0x55 != byte) && (byte < 0xF0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// second byte must be 0x5A
|
||||
} else if (1 == index) {
|
||||
if (0x5A != byte) {
|
||||
index = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_data[index++] = byte;
|
||||
if (index > 23) {
|
||||
_serial_flush();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Process packet
|
||||
if (24 == index) {
|
||||
_process();
|
||||
index = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
bool _serial_available() {
|
||||
if (1 == _pin_rx) {
|
||||
return Serial.available();
|
||||
} else {
|
||||
return _serial->available();
|
||||
}
|
||||
}
|
||||
|
||||
void _serial_flush() {
|
||||
if (1 == _pin_rx) {
|
||||
return Serial.flush();
|
||||
} else {
|
||||
return _serial->flush();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t _serial_read() {
|
||||
if (1 == _pin_rx) {
|
||||
return Serial.read();
|
||||
} else {
|
||||
return _serial->read();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned int _pin_rx = CSE7766_PIN;
|
||||
bool _inverted = CSE7766_PIN_INVERSE;
|
||||
SoftwareSerial * _serial = NULL;
|
||||
|
||||
double _active = 0;
|
||||
double _voltage = 0;
|
||||
double _current = 0;
|
||||
double _energy = 0;
|
||||
|
||||
double _ratioV = 1.0;
|
||||
double _ratioC = 1.0;
|
||||
double _ratioP = 1.0;
|
||||
|
||||
unsigned char _data[24];
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && CSE7766_SUPPORT
|
245
espurna/sensors/DHTSensor.h
Executable file
245
espurna/sensors/DHTSensor.h
Executable file
@ -0,0 +1,245 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// DHTXX Sensor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && DHT_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#define DHT_MAX_DATA 5
|
||||
#define DHT_MAX_ERRORS 5
|
||||
#define DHT_MIN_INTERVAL 2000
|
||||
|
||||
#define DHT_CHIP_DHT11 11
|
||||
#define DHT_CHIP_DHT22 22
|
||||
#define DHT_CHIP_DHT21 21
|
||||
#define DHT_CHIP_AM2301 21
|
||||
|
||||
class DHTSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
DHTSensor(): BaseSensor() {
|
||||
_count = 2;
|
||||
_sensor_id = SENSOR_DHTXX_ID;
|
||||
}
|
||||
|
||||
~DHTSensor() {
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setGPIO(unsigned char gpio) {
|
||||
_gpio = gpio;
|
||||
}
|
||||
|
||||
void setType(unsigned char type) {
|
||||
_type = type;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getGPIO() {
|
||||
return _gpio;
|
||||
}
|
||||
|
||||
unsigned char getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
_count = 0;
|
||||
|
||||
// Manage GPIO lock
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
_previous = GPIO_NONE;
|
||||
if (!gpioGetLock(_gpio)) {
|
||||
_error = SENSOR_ERROR_GPIO_USED;
|
||||
return;
|
||||
}
|
||||
_previous = _gpio;
|
||||
|
||||
_count = 2;
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
_read();
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", _type, _gpio);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_gpio);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_TEMPERATURE;
|
||||
if (index == 1) return MAGNITUDE_HUMIDITY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _temperature;
|
||||
if (index == 1) return _humidity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _read() {
|
||||
|
||||
if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long low = 0;
|
||||
unsigned long high = 0;
|
||||
|
||||
unsigned char dhtData[DHT_MAX_DATA] = {0};
|
||||
unsigned char byteInx = 0;
|
||||
unsigned char bitInx = 7;
|
||||
|
||||
// Send start signal to DHT sensor
|
||||
if (++_errors > DHT_MAX_ERRORS) {
|
||||
_errors = 0;
|
||||
digitalWrite(_gpio, HIGH);
|
||||
nice_delay(250);
|
||||
}
|
||||
pinMode(_gpio, OUTPUT);
|
||||
noInterrupts();
|
||||
digitalWrite(_gpio, LOW);
|
||||
if (_type == DHT_CHIP_DHT11) {
|
||||
nice_delay(20);
|
||||
} else {
|
||||
delayMicroseconds(500);
|
||||
}
|
||||
digitalWrite(_gpio, HIGH);
|
||||
delayMicroseconds(40);
|
||||
pinMode(_gpio, INPUT_PULLUP);
|
||||
delayMicroseconds(10);
|
||||
|
||||
// No errors, read the 40 data bits
|
||||
for( int k = 0; k < 41; k++ ) {
|
||||
|
||||
// Starts new data transmission with >50us low signal
|
||||
low = _signal(100, LOW);
|
||||
if (low == 0) {
|
||||
_error = SENSOR_ERROR_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if after >70us rx data is a 0 or a 1
|
||||
high = _signal(100, HIGH);
|
||||
if (high == 0) {
|
||||
_error = SENSOR_ERROR_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip the first bit
|
||||
if (k == 0) continue;
|
||||
|
||||
// add the current read to the output data
|
||||
// since all dhtData array where set to 0 at the start,
|
||||
// only look for "1" (>28us us)
|
||||
if (high > low) dhtData[byteInx] |= (1 << bitInx);
|
||||
|
||||
// index to next byte
|
||||
if (bitInx == 0) {
|
||||
bitInx = 7;
|
||||
++byteInx;
|
||||
} else {
|
||||
--bitInx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interrupts();
|
||||
|
||||
// Verify checksum
|
||||
if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get humidity from Data[0] and Data[1]
|
||||
if (_type == DHT_CHIP_DHT11) {
|
||||
_humidity = dhtData[0];
|
||||
} else {
|
||||
_humidity = dhtData[0] * 256 + dhtData[1];
|
||||
_humidity /= 10;
|
||||
}
|
||||
|
||||
// Get temp from Data[2] and Data[3]
|
||||
if (_type == DHT_CHIP_DHT11) {
|
||||
_temperature = dhtData[2];
|
||||
} else {
|
||||
_temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
|
||||
_temperature /= 10;
|
||||
if (dhtData[2] & 0x80) _temperature *= -1;
|
||||
}
|
||||
|
||||
_last_ok = millis();
|
||||
_errors = 0;
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
}
|
||||
|
||||
unsigned long _signal(int usTimeOut, bool state) {
|
||||
unsigned long uSec = 1;
|
||||
while (digitalRead(_gpio) == state) {
|
||||
if (++uSec > usTimeOut) return 0;
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
return uSec;
|
||||
}
|
||||
|
||||
unsigned char _gpio = GPIO_NONE;
|
||||
unsigned char _previous = GPIO_NONE;
|
||||
unsigned char _type = DHT_CHIP_DHT22;
|
||||
|
||||
unsigned long _last_ok = 0;
|
||||
unsigned char _errors = 0;
|
||||
|
||||
double _temperature = 0;
|
||||
unsigned int _humidity = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && DHT_SUPPORT
|
319
espurna/sensors/DallasSensor.h
Executable file
319
espurna/sensors/DallasSensor.h
Executable file
@ -0,0 +1,319 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dallas OneWire Sensor
|
||||
// Uses OneWire library
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && DALLAS_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
#include <vector>
|
||||
#include <OneWire.h>
|
||||
|
||||
#define DS_CHIP_DS18S20 0x10
|
||||
#define DS_CHIP_DS1822 0x22
|
||||
#define DS_CHIP_DS18B20 0x28
|
||||
#define DS_CHIP_DS1825 0x3B
|
||||
|
||||
#define DS_DATA_SIZE 9
|
||||
#define DS_PARASITE 1
|
||||
#define DS_DISCONNECTED -127
|
||||
|
||||
#define DS_CMD_START_CONVERSION 0x44
|
||||
#define DS_CMD_READ_SCRATCHPAD 0xBE
|
||||
|
||||
class DallasSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
DallasSensor(): BaseSensor() {
|
||||
_sensor_id = SENSOR_DALLAS_ID;
|
||||
}
|
||||
|
||||
~DallasSensor() {
|
||||
if (_wire) delete _wire;
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setGPIO(unsigned char gpio) {
|
||||
if (_gpio == gpio) return;
|
||||
_gpio = gpio;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getGPIO() {
|
||||
return _gpio;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
// Manage GPIO lock
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
_previous = GPIO_NONE;
|
||||
if (!gpioGetLock(_gpio)) {
|
||||
_error = SENSOR_ERROR_GPIO_USED;
|
||||
return;
|
||||
}
|
||||
|
||||
// OneWire
|
||||
if (_wire) delete _wire;
|
||||
_wire = new OneWire(_gpio);
|
||||
|
||||
// Search devices
|
||||
loadDevices();
|
||||
|
||||
// If no devices found check again pulling up the line
|
||||
if (_count == 0) {
|
||||
pinMode(_gpio, INPUT_PULLUP);
|
||||
loadDevices();
|
||||
}
|
||||
|
||||
// Check connection
|
||||
if (_count == 0) {
|
||||
gpioReleaseLock(_gpio);
|
||||
} else {
|
||||
_previous = _gpio;
|
||||
}
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Loop-like method, call it in your main loop
|
||||
void tick() {
|
||||
|
||||
static unsigned long last = 0;
|
||||
if (millis() - last < DALLAS_READ_INTERVAL) return;
|
||||
last = millis();
|
||||
|
||||
// Every second we either start a conversion or read the scratchpad
|
||||
static bool conversion = true;
|
||||
if (conversion) {
|
||||
|
||||
// Start conversion
|
||||
_wire->reset();
|
||||
_wire->skip();
|
||||
_wire->write(DS_CMD_START_CONVERSION, DS_PARASITE);
|
||||
|
||||
} else {
|
||||
|
||||
// Read scratchpads
|
||||
for (unsigned char index=0; index<_devices.size(); index++) {
|
||||
|
||||
// Read scratchpad
|
||||
if (_wire->reset() == 0) {
|
||||
// Force a CRC check error
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
_wire->select(_devices[index].address);
|
||||
_wire->write(DS_CMD_READ_SCRATCHPAD);
|
||||
|
||||
uint8_t data[DS_DATA_SIZE];
|
||||
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
|
||||
data[i] = _wire->read();
|
||||
}
|
||||
|
||||
#if false
|
||||
Serial.printf("[DS18B20] Data = ");
|
||||
for (unsigned char i = 0; i < DS_DATA_SIZE; i++) {
|
||||
Serial.printf("%02X ", data[i]);
|
||||
}
|
||||
Serial.printf(" CRC = %02X\n", OneWire::crc8(data, DS_DATA_SIZE-1));
|
||||
#endif
|
||||
|
||||
|
||||
if (_wire->reset() != 1) {
|
||||
// Force a CRC check error
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(_devices[index].data, data, DS_DATA_SIZE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
conversion = !conversion;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "Dallas @ GPIO%d", _gpio);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Address of the device
|
||||
String address(unsigned char index) {
|
||||
char buffer[20] = {0};
|
||||
if (index < _count) {
|
||||
uint8_t * address = _devices[index].address;
|
||||
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
address[0], address[1], address[2], address[3],
|
||||
address[4], address[5], address[6], address[7]
|
||||
);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
if (index < _count) {
|
||||
char buffer[40];
|
||||
uint8_t * address = _devices[index].address;
|
||||
snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d",
|
||||
chipAsString(index).c_str(),
|
||||
address[0], address[1], address[2], address[3],
|
||||
address[4], address[5], address[6], address[7],
|
||||
_gpio
|
||||
);
|
||||
return String(buffer);
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index < _count) return MAGNITUDE_TEMPERATURE;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
|
||||
if (index >= _count) return 0;
|
||||
|
||||
uint8_t * data = _devices[index].data;
|
||||
|
||||
if (OneWire::crc8(data, DS_DATA_SIZE-1) != data[DS_DATA_SIZE-1]) {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Registers
|
||||
// byte 0: temperature LSB
|
||||
// byte 1: temperature MSB
|
||||
// byte 2: high alarm temp
|
||||
// byte 3: low alarm temp
|
||||
// byte 4: DS18S20: store for crc
|
||||
// DS18B20 & DS1822: configuration register
|
||||
// byte 5: internal use & crc
|
||||
// byte 6: DS18S20: COUNT_REMAIN
|
||||
// DS18B20 & DS1822: store for crc
|
||||
// byte 7: DS18S20: COUNT_PER_C
|
||||
// DS18B20 & DS1822: store for crc
|
||||
// byte 8: SCRATCHPAD_CRC
|
||||
|
||||
int16_t raw = (data[1] << 8) | data[0];
|
||||
if (chip(index) == DS_CHIP_DS18S20) {
|
||||
raw = raw << 3; // 9 bit resolution default
|
||||
if (data[7] == 0x10) {
|
||||
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
|
||||
}
|
||||
} else {
|
||||
byte cfg = (data[4] & 0x60);
|
||||
if (cfg == 0x00) raw = raw & ~7; // 9 bit res, 93.75 ms
|
||||
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
|
||||
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
|
||||
// 12 bit res, 750 ms
|
||||
}
|
||||
|
||||
double value = (float) raw / 16.0;
|
||||
if (value == DS_DISCONNECTED) {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
bool validateID(unsigned char id) {
|
||||
return (id == DS_CHIP_DS18S20) || (id == DS_CHIP_DS18B20) || (id == DS_CHIP_DS1822) || (id == DS_CHIP_DS1825);
|
||||
}
|
||||
|
||||
unsigned char chip(unsigned char index) {
|
||||
if (index < _count) return _devices[index].address[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
String chipAsString(unsigned char index) {
|
||||
unsigned char chip_id = chip(index);
|
||||
if (chip_id == DS_CHIP_DS18S20) return String("DS18S20");
|
||||
if (chip_id == DS_CHIP_DS18B20) return String("DS18B20");
|
||||
if (chip_id == DS_CHIP_DS1822) return String("DS1822");
|
||||
if (chip_id == DS_CHIP_DS1825) return String("DS1825");
|
||||
return String("Unknown");
|
||||
}
|
||||
|
||||
void loadDevices() {
|
||||
|
||||
uint8_t address[8];
|
||||
_wire->reset();
|
||||
_wire->reset_search();
|
||||
while (_wire->search(address)) {
|
||||
|
||||
// Check CRC
|
||||
if (_wire->crc8(address, 7) == address[7]) {
|
||||
|
||||
// Check ID
|
||||
if (validateID(address[0])) {
|
||||
ds_device_t device;
|
||||
memcpy(device.address, address, 8);
|
||||
_devices.push_back(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
_count = _devices.size();
|
||||
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t address[8];
|
||||
uint8_t data[DS_DATA_SIZE];
|
||||
} ds_device_t;
|
||||
std::vector<ds_device_t> _devices;
|
||||
|
||||
unsigned char _gpio = GPIO_NONE;
|
||||
unsigned char _previous = GPIO_NONE;
|
||||
OneWire * _wire = NULL;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && DALLAS_SUPPORT
|
106
espurna/sensors/DigitalSensor.h
Executable file
106
espurna/sensors/DigitalSensor.h
Executable file
@ -0,0 +1,106 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Digital Sensor (maps to a digitalRead)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && DIGITAL_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
class DigitalSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
DigitalSensor(): BaseSensor() {
|
||||
_count = 1;
|
||||
_sensor_id = SENSOR_DIGITAL_ID;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setGPIO(unsigned char gpio) {
|
||||
_gpio = gpio;
|
||||
}
|
||||
|
||||
void setMode(unsigned char mode) {
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
void setDefault(bool value) {
|
||||
_default = value;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getGPIO() {
|
||||
return _gpio;
|
||||
}
|
||||
|
||||
unsigned char getMode() {
|
||||
return _mode;
|
||||
}
|
||||
|
||||
bool getDefault() {
|
||||
return _default;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
pinMode(_gpio, _mode);
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "DIGITAL @ GPIO%d", _gpio);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_gpio);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_DIGITAL;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return (digitalRead(_gpio) == _default) ? 0 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char _gpio;
|
||||
unsigned char _mode;
|
||||
bool _default = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && DIGITAL_SUPPORT
|
349
espurna/sensors/ECH1560Sensor.h
Executable file
349
espurna/sensors/ECH1560Sensor.h
Executable file
@ -0,0 +1,349 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ECH1560 based power monitor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && ECH1560_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
class ECH1560Sensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
ECH1560Sensor(): BaseSensor(), _data() {
|
||||
_count = 3;
|
||||
_sensor_id = SENSOR_ECH1560_ID;
|
||||
}
|
||||
|
||||
~ECH1560Sensor() {
|
||||
_enableInterrupts(false);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setCLK(unsigned char clk) {
|
||||
if (_clk == clk) return;
|
||||
_clk = clk;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setMISO(unsigned char miso) {
|
||||
if (_miso == miso) return;
|
||||
_miso = miso;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setInverted(bool inverted) {
|
||||
_inverted = inverted;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getCLK() {
|
||||
return _clk;
|
||||
}
|
||||
|
||||
unsigned char getMISO() {
|
||||
return _miso;
|
||||
}
|
||||
|
||||
bool getInverted() {
|
||||
return _inverted;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
pinMode(_clk, INPUT);
|
||||
pinMode(_miso, INPUT);
|
||||
_enableInterrupts(true);
|
||||
|
||||
_dirty = false;
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
// Loop-like method, call it in your main loop
|
||||
void tick() {
|
||||
if (_dosync) _sync();
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[35];
|
||||
snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[6];
|
||||
snprintf(buffer, sizeof(buffer), "%u:%u", _clk, _miso);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CURRENT;
|
||||
if (index == 1) return MAGNITUDE_VOLTAGE;
|
||||
if (index == 2) return MAGNITUDE_POWER_APPARENT;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _current;
|
||||
if (index == 1) return _voltage;
|
||||
if (index == 2) return _apparent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
|
||||
|
||||
(void) gpio;
|
||||
|
||||
// if we are trying to find the sync-time (CLK goes high for 1-2ms)
|
||||
if (_dosync == false) {
|
||||
|
||||
_clk_count = 0;
|
||||
|
||||
// register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms
|
||||
while (digitalRead(_clk) == HIGH) {
|
||||
_clk_count += 1;
|
||||
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
|
||||
}
|
||||
|
||||
// if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission
|
||||
if (_clk_count >= 33 && _clk_count <= 67) {
|
||||
_dosync = true;
|
||||
}
|
||||
|
||||
// we are in sync and logging CLK-highs
|
||||
} else {
|
||||
|
||||
// increment an integer to keep track of how many bits we have read.
|
||||
_bits_count += 1;
|
||||
_nextbit = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Interrupt management
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode);
|
||||
void _detach(unsigned char gpio);
|
||||
|
||||
void _enableInterrupts(bool value) {
|
||||
|
||||
static unsigned char _interrupt_clk = GPIO_NONE;
|
||||
|
||||
if (value) {
|
||||
if (_interrupt_clk != _clk) {
|
||||
if (_interrupt_clk != GPIO_NONE) _detach(_interrupt_clk);
|
||||
_attach(this, _clk, RISING);
|
||||
_interrupt_clk = _clk;
|
||||
}
|
||||
} else if (_interrupt_clk != GPIO_NONE) {
|
||||
_detach(_interrupt_clk);
|
||||
_interrupt_clk = GPIO_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _sync() {
|
||||
|
||||
unsigned int byte1 = 0;
|
||||
unsigned int byte2 = 0;
|
||||
unsigned int byte3 = 0;
|
||||
|
||||
_bits_count = 0;
|
||||
while (_bits_count < 40); // skip the uninteresting 5 first bytes
|
||||
_bits_count = 0;
|
||||
|
||||
while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2
|
||||
|
||||
if (_nextbit) {
|
||||
|
||||
if (_bits_count < 9) { // first Byte/8 bits in byte1
|
||||
|
||||
byte1 = byte1 << 1;
|
||||
if (digitalRead(_miso) == HIGH) byte1 |= 1;
|
||||
_nextbit = false;
|
||||
|
||||
} else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2
|
||||
|
||||
byte2 = byte2 << 1;
|
||||
if (digitalRead(_miso) == HIGH) byte2 |= 1;
|
||||
_nextbit = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power.
|
||||
|
||||
// voltage = 2 * (byte1 + byte2 / 255)
|
||||
_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
|
||||
|
||||
// power:
|
||||
_bits_count = 0;
|
||||
while (_bits_count < 40); // skip the uninteresting 5 first bytes
|
||||
_bits_count = 0;
|
||||
|
||||
byte1 = 0;
|
||||
byte2 = 0;
|
||||
byte3 = 0;
|
||||
|
||||
while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3.
|
||||
|
||||
if (_nextbit) {
|
||||
|
||||
if (_bits_count < 9) {
|
||||
|
||||
byte1 = byte1 << 1;
|
||||
if (digitalRead(_miso) == HIGH) byte1 |= 1;
|
||||
_nextbit = false;
|
||||
|
||||
} else if (_bits_count < 17) {
|
||||
|
||||
byte2 = byte2 << 1;
|
||||
if (digitalRead(_miso) == HIGH) byte2 |= 1;
|
||||
_nextbit = false;
|
||||
|
||||
} else {
|
||||
|
||||
byte3 = byte3 << 1;
|
||||
if (digitalRead(_miso) == HIGH) byte3 |= 1;
|
||||
_nextbit = false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_inverted) {
|
||||
byte1 = 255 - byte1;
|
||||
byte2 = 255 - byte2;
|
||||
byte3 = 255 - byte3;
|
||||
}
|
||||
|
||||
// power = (byte1*255+byte2+byte3/255)/2
|
||||
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
|
||||
_current = _apparent / _voltage;
|
||||
|
||||
_dosync = false;
|
||||
|
||||
}
|
||||
|
||||
// If byte2 is not 3 or something else than 0, something is wrong!
|
||||
if (byte2 == 0) {
|
||||
_dosync = false;
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n"));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char _clk = 0;
|
||||
unsigned char _miso = 0;
|
||||
bool _inverted = false;
|
||||
|
||||
volatile long _bits_count = 0;
|
||||
volatile long _clk_count = 0;
|
||||
volatile bool _dosync = false;
|
||||
volatile bool _nextbit = true;
|
||||
|
||||
double _apparent = 0;
|
||||
double _voltage = 0;
|
||||
double _current = 0;
|
||||
|
||||
unsigned char _data[24];
|
||||
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Interrupt helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
ECH1560Sensor * _ech1560_sensor_instance[10] = {NULL};
|
||||
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr(unsigned char gpio) {
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_ech1560_sensor_instance[index]) {
|
||||
_ech1560_sensor_instance[index]->handleInterrupt(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_0() { _ech1560_sensor_isr(0); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_1() { _ech1560_sensor_isr(1); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_2() { _ech1560_sensor_isr(2); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_3() { _ech1560_sensor_isr(3); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_4() { _ech1560_sensor_isr(4); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_5() { _ech1560_sensor_isr(5); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_12() { _ech1560_sensor_isr(12); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_13() { _ech1560_sensor_isr(13); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_14() { _ech1560_sensor_isr(14); }
|
||||
void ICACHE_RAM_ATTR _ech1560_sensor_isr_15() { _ech1560_sensor_isr(15); }
|
||||
|
||||
static void (*_ech1560_sensor_isr_list[10])() = {
|
||||
_ech1560_sensor_isr_0, _ech1560_sensor_isr_1, _ech1560_sensor_isr_2,
|
||||
_ech1560_sensor_isr_3, _ech1560_sensor_isr_4, _ech1560_sensor_isr_5,
|
||||
_ech1560_sensor_isr_12, _ech1560_sensor_isr_13, _ech1560_sensor_isr_14,
|
||||
_ech1560_sensor_isr_15
|
||||
};
|
||||
|
||||
void ECH1560Sensor::_attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
_detach(gpio);
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
_ech1560_sensor_instance[index] = instance;
|
||||
attachInterrupt(gpio, _ech1560_sensor_isr_list[index], mode);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void ECH1560Sensor::_detach(unsigned char gpio) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_ech1560_sensor_instance[index]) {
|
||||
detachInterrupt(gpio);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _ech1560_sensor_instance[index]->description().c_str());
|
||||
#endif
|
||||
_ech1560_sensor_instance[index] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SENSOR_SUPPORT && ECH1560_SUPPORT
|
150
espurna/sensors/EmonADC121Sensor.h
Executable file
150
espurna/sensors/EmonADC121Sensor.h
Executable file
@ -0,0 +1,150 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ADS121-based Energy Monitor Sensor over I2C
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "EmonSensor.h"
|
||||
|
||||
// ADC121 Registers
|
||||
#define ADC121_REG_RESULT 0x00
|
||||
#define ADC121_REG_ALERT 0x01
|
||||
#define ADC121_REG_CONFIG 0x02
|
||||
#define ADC121_REG_LIMITL 0x03
|
||||
#define ADC121_REG_LIMITH 0x04
|
||||
#define ADC121_REG_HYST 0x05
|
||||
#define ADC121_REG_CONVL 0x06
|
||||
#define ADC121_REG_CONVH 0x07
|
||||
|
||||
#define ADC121_RESOLUTION 12
|
||||
#define ADC121_CHANNELS 1
|
||||
|
||||
class EmonADC121Sensor : public EmonSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
EmonADC121Sensor(): EmonSensor() {
|
||||
_channels = ADC121_CHANNELS;
|
||||
_sensor_id = SENSOR_EMON_ADC121_ID;
|
||||
init();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
_dirty = false;
|
||||
|
||||
// Discover
|
||||
unsigned char addresses[] = {0x50, 0x51, 0x52, 0x54, 0x55, 0x56, 0x58, 0x59, 0x5A};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
// Init sensor
|
||||
_init();
|
||||
|
||||
// Just one channel
|
||||
_count = _magnitudes;
|
||||
|
||||
// Bit depth
|
||||
_resolution = ADC121_RESOLUTION;
|
||||
|
||||
// Call the parent class method
|
||||
EmonSensor::begin();
|
||||
|
||||
// warmup channel 0 (the only one)
|
||||
read(0);
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[30];
|
||||
snprintf(buffer, sizeof(buffer), "EMON @ ADC121 @ I2C (0x%02X)", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
|
||||
if (_address == 0) {
|
||||
_error = SENSOR_ERROR_UNKNOWN_ID;
|
||||
return;
|
||||
}
|
||||
|
||||
_current[0] = read(0);
|
||||
|
||||
#if EMON_REPORT_ENERGY
|
||||
static unsigned long last = 0;
|
||||
if (last > 0) {
|
||||
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
|
||||
}
|
||||
last = millis();
|
||||
#endif
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (index == i++) return MAGNITUDE_CURRENT;
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (index == i++) return MAGNITUDE_POWER_APPARENT;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (index == i) return MAGNITUDE_ENERGY;
|
||||
#endif
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
unsigned char channel = index / _magnitudes;
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (index == i++) return _current[channel];
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (index == i++) return _current[channel] * _voltage;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (index == i) return _energy[channel];
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _init() {
|
||||
i2c_write_uint8(_address, ADC121_REG_CONFIG, 0);
|
||||
}
|
||||
|
||||
unsigned int readADC(unsigned char channel) {
|
||||
(void) channel;
|
||||
unsigned int value = i2c_read_uint16(_address, ADC121_REG_RESULT) & 0x0FFF;
|
||||
return value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && EMON_ADC121_SUPPORT
|
347
espurna/sensors/EmonADS1X15Sensor.h
Executable file
347
espurna/sensors/EmonADS1X15Sensor.h
Executable file
@ -0,0 +1,347 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ADS1X15-based Energy Monitor Sensor over I2C
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "EmonSensor.h"
|
||||
|
||||
#define ADS1X15_CHANNELS (4)
|
||||
|
||||
#define ADS1X15_CHIP_ADS1015 (0)
|
||||
#define ADS1X15_CHIP_ADS1115 (1)
|
||||
|
||||
#define ADS1X15_RESOLUTION (16)
|
||||
|
||||
#define ADS1015_CONVERSIONDELAY (1)
|
||||
#define ADS1115_CONVERSIONDELAY (8)
|
||||
|
||||
#define ADS1015_BIT_SHIFT (4)
|
||||
#define ADS1115_BIT_SHIFT (0)
|
||||
|
||||
#define ADS1X15_REG_POINTER_MASK (0x03)
|
||||
#define ADS1X15_REG_POINTER_CONVERT (0x00)
|
||||
#define ADS1X15_REG_POINTER_CONFIG (0x01)
|
||||
#define ADS1X15_REG_POINTER_LOWTHRESH (0x02)
|
||||
#define ADS1X15_REG_POINTER_HITHRESH (0x03)
|
||||
|
||||
#define ADS1X15_REG_CONFIG_OS_MASK (0x8000)
|
||||
#define ADS1X15_REG_CONFIG_OS_SINGLE (0x8000) // Write: Set to start a single-conversion
|
||||
#define ADS1X15_REG_CONFIG_OS_BUSY (0x0000) // Read: Bit = 0 when conversion is in progress
|
||||
#define ADS1X15_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion
|
||||
|
||||
#define ADS1X15_REG_CONFIG_MUX_MASK (0x7000)
|
||||
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default)
|
||||
#define ADS1X15_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3
|
||||
#define ADS1X15_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3
|
||||
#define ADS1X15_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3
|
||||
#define ADS1X15_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0
|
||||
#define ADS1X15_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1
|
||||
#define ADS1X15_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2
|
||||
#define ADS1X15_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3
|
||||
|
||||
#define ADS1X15_REG_CONFIG_PGA_MASK (0x0E00)
|
||||
#define ADS1X15_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3
|
||||
#define ADS1X15_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1
|
||||
#define ADS1X15_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default)
|
||||
#define ADS1X15_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4
|
||||
#define ADS1X15_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8
|
||||
#define ADS1X15_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16
|
||||
|
||||
#define ADS1X15_REG_CONFIG_MODE_MASK (0x0100)
|
||||
#define ADS1X15_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode
|
||||
#define ADS1X15_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default)
|
||||
|
||||
#define ADS1X15_REG_CONFIG_DR_MASK (0x00E0)
|
||||
#define ADS1015_REG_CONFIG_DR_128SPS (0x0000) // 128 samples per second
|
||||
#define ADS1015_REG_CONFIG_DR_250SPS (0x0020) // 250 samples per second
|
||||
#define ADS1015_REG_CONFIG_DR_490SPS (0x0040) // 490 samples per second
|
||||
#define ADS1015_REG_CONFIG_DR_920SPS (0x0060) // 920 samples per second
|
||||
#define ADS1015_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default)
|
||||
#define ADS1015_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second
|
||||
#define ADS1015_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_8SPS (0x0000) // 8 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_16SPS (0x0020) // 16 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_32SPS (0x0040) // 32 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_64SPS (0x0060) // 64 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_128SPS (0x0080) // 128 samples per second (default)
|
||||
#define ADS1115_REG_CONFIG_DR_250SPS (0x00A0) // 250 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_475SPS (0x00C0) // 475 samples per second
|
||||
#define ADS1115_REG_CONFIG_DR_860SPS (0x00E0) // 860 samples per second
|
||||
|
||||
#define ADS1X15_REG_CONFIG_CMODE_MASK (0x0010)
|
||||
#define ADS1X15_REG_CONFIG_CMODE_TRAD (0x0000) // Traditional comparator with hysteresis (default)
|
||||
#define ADS1X15_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator
|
||||
|
||||
#define ADS1X15_REG_CONFIG_CPOL_MASK (0x0008)
|
||||
#define ADS1X15_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default)
|
||||
#define ADS1X15_REG_CONFIG_CPOL_ACTVHI (0x0008) // ALERT/RDY pin is high when active
|
||||
|
||||
#define ADS1X15_REG_CONFIG_CLAT_MASK (0x0004) // Determines if ALERT/RDY pin latches once asserted
|
||||
#define ADS1X15_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default)
|
||||
#define ADS1X15_REG_CONFIG_CLAT_LATCH (0x0004) // Latching comparator
|
||||
|
||||
#define ADS1X15_REG_CONFIG_CQUE_MASK (0x0003)
|
||||
#define ADS1X15_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions
|
||||
#define ADS1X15_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions
|
||||
#define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions
|
||||
#define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default)
|
||||
|
||||
class EmonADS1X15Sensor : public EmonSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
EmonADS1X15Sensor(): EmonSensor() {
|
||||
_channels = ADS1X15_CHANNELS;
|
||||
_sensor_id = SENSOR_EMON_ADS1X15_ID;
|
||||
init();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setType(unsigned char type) {
|
||||
if (_type == type) return;
|
||||
_type = type;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setMask(unsigned char mask) {
|
||||
if (_mask == mask) return;
|
||||
_mask = mask;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setGain(unsigned int gain) {
|
||||
if (_gain == gain) return;
|
||||
_gain = gain;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
unsigned char getMask() {
|
||||
return _mask;
|
||||
}
|
||||
|
||||
unsigned char getGain() {
|
||||
return _gain;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
// Discover
|
||||
unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
// Calculate ports
|
||||
_ports = 0;
|
||||
unsigned char mask = _mask;
|
||||
while (mask) {
|
||||
if (mask & 0x01) ++_ports;
|
||||
mask = mask >> 1;
|
||||
}
|
||||
_count = _ports * _magnitudes;
|
||||
|
||||
// Bit depth
|
||||
_resolution = ADS1X15_RESOLUTION;
|
||||
|
||||
// Reference based on gain
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_6_144V) _reference = 12.288;
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_4_096V) _reference = 8.192;
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_2_048V) _reference = 4.096;
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_1_024V) _reference = 2.048;
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_0_512V) _reference = 1.024;
|
||||
if (_gain == ADS1X15_REG_CONFIG_PGA_0_256V) _reference = 0.512;
|
||||
|
||||
// Call the parent class method
|
||||
EmonSensor::begin();
|
||||
|
||||
// warmup all channels
|
||||
warmup();
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[30];
|
||||
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
char buffer[35];
|
||||
unsigned char channel = getChannel(index % _ports);
|
||||
snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 (A%d) @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, channel, _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[10];
|
||||
unsigned char channel = getChannel(index % _ports);
|
||||
snprintf(buffer, sizeof(buffer), "0x%02X:%u", _address, channel);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
unsigned char magnitude = index / _ports;
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (magnitude == i++) return MAGNITUDE_CURRENT;
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (magnitude == i++) return MAGNITUDE_POWER_APPARENT;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (magnitude == i) return MAGNITUDE_ENERGY;
|
||||
#endif
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
void pre() {
|
||||
static unsigned long last = 0;
|
||||
for (unsigned char port=0; port<_ports; port++) {
|
||||
unsigned char channel = getChannel(port);
|
||||
_current[port] = getCurrent(channel);
|
||||
#if EMON_REPORT_ENERGY
|
||||
_energy[port] += (_current[port] * _voltage * (millis() - last) / 1000);
|
||||
#endif
|
||||
}
|
||||
last = millis();
|
||||
_error = SENSOR_ERROR_OK;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
unsigned char port = index % _ports;
|
||||
unsigned char magnitude = index / _ports;
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (magnitude == i++) return _current[port];
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (magnitude == i++) return _current[port] * _voltage;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (magnitude == i) return _energy[port];
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Protected
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
unsigned char getChannel(unsigned char port) {
|
||||
unsigned char count = 0;
|
||||
unsigned char bit = 1;
|
||||
for (unsigned char channel=0; channel<ADS1X15_CHANNELS; channel++) {
|
||||
if ((_mask & bit) == bit) {
|
||||
if (count == port) return channel;
|
||||
++count;
|
||||
}
|
||||
bit <<= 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void warmup() {
|
||||
for (unsigned char port=0; port<_ports; port++) {
|
||||
unsigned char channel = getChannel(port);
|
||||
_pivot[channel] = _adc_counts >> 1;
|
||||
getCurrent(channel);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// I2C
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void setConfigRegistry(unsigned char channel, bool continuous, bool start) {
|
||||
|
||||
// Start with default values
|
||||
uint16_t config = 0;
|
||||
config |= _gain; // Set PGA/voltage range (0x0200)
|
||||
config |= ADS1X15_REG_CONFIG_DR_MASK; // Always at max speed (0x00E0)
|
||||
//config |= ADS1X15_REG_CONFIG_CMODE_TRAD; // Traditional comparator (default val) (0x0000)
|
||||
//config |= ADS1X15_REG_CONFIG_CPOL_ACTVLOW; // Alert/Rdy active low (default val) (0x0000)
|
||||
//config |= ADS1X15_REG_CONFIG_CLAT_NONLAT; // Non-latching (default val) (0x0000)
|
||||
config |= ADS1X15_REG_CONFIG_CQUE_NONE; // Disable the comparator (default val) (0x0003)
|
||||
if (start) {
|
||||
config |= ADS1X15_REG_CONFIG_OS_SINGLE; // Start a single-conversion (0x8000)
|
||||
}
|
||||
if (continuous) {
|
||||
//config |= ADS1X15_REG_CONFIG_MODE_CONTIN; // Continuous mode (default) (0x0000)
|
||||
} else {
|
||||
config |= ADS1X15_REG_CONFIG_MODE_SINGLE; // Single-shot mode (0x0100)
|
||||
}
|
||||
config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000)
|
||||
|
||||
#if SENSOR_DEBUG
|
||||
//Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config);
|
||||
#endif
|
||||
|
||||
// Write config register to the ADC
|
||||
i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config);
|
||||
|
||||
}
|
||||
|
||||
double getCurrent(unsigned char channel) {
|
||||
|
||||
// Force stop by setting single mode and back to continuous
|
||||
static unsigned char previous = 9;
|
||||
if (previous != channel) {
|
||||
setConfigRegistry(channel, true, false);
|
||||
setConfigRegistry(channel, false, false);
|
||||
setConfigRegistry(channel, false, true);
|
||||
nice_delay(10);
|
||||
readADC(channel);
|
||||
previous = channel;
|
||||
}
|
||||
setConfigRegistry(channel, true, true);
|
||||
|
||||
return read(channel);
|
||||
|
||||
}
|
||||
|
||||
unsigned int readADC(unsigned char channel) {
|
||||
(void) channel;
|
||||
unsigned int value = i2c_read_uint16(_address, ADS1X15_REG_POINTER_CONVERT);
|
||||
if (_type = ADS1X15_CHIP_ADS1015) value >>= ADS1015_BIT_SHIFT;
|
||||
delayMicroseconds(500);
|
||||
return value;
|
||||
}
|
||||
|
||||
unsigned char _type = ADS1X15_CHIP_ADS1115;
|
||||
unsigned char _mask = 0x0F;
|
||||
unsigned int _gain = ADS1X15_REG_CONFIG_PGA_4_096V;
|
||||
unsigned char _ports;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT
|
124
espurna/sensors/EmonAnalogSensor.h
Executable file
124
espurna/sensors/EmonAnalogSensor.h
Executable file
@ -0,0 +1,124 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Energy Monitor Sensor using builtin ADC
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "EmonSensor.h"
|
||||
|
||||
#define EMON_ANALOG_RESOLUTION 10
|
||||
#define EMON_ANALOG_CHANNELS 1
|
||||
|
||||
class EmonAnalogSensor : public EmonSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
EmonAnalogSensor(): EmonSensor() {
|
||||
_channels = EMON_ANALOG_CHANNELS;
|
||||
_sensor_id = SENSOR_EMON_ANALOG_ID;
|
||||
init();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
_dirty = false;
|
||||
|
||||
// Number of slots
|
||||
_count = _magnitudes;
|
||||
|
||||
// Bit depth
|
||||
_resolution = EMON_ANALOG_RESOLUTION;
|
||||
|
||||
// Init analog PIN)
|
||||
pinMode(0, INPUT);
|
||||
|
||||
// Call the parent class method
|
||||
EmonSensor::begin();
|
||||
|
||||
// warmup channel 0 (the only one)
|
||||
read(0);
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
return String("EMON @ ANALOG @ GPIO0");
|
||||
}
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String("0");
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (index == i++) return MAGNITUDE_CURRENT;
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (index == i++) return MAGNITUDE_POWER_APPARENT;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (index == i) return MAGNITUDE_ENERGY;
|
||||
#endif
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
|
||||
_current[0] = read(0);
|
||||
|
||||
#if EMON_REPORT_ENERGY
|
||||
static unsigned long last = 0;
|
||||
if (last > 0) {
|
||||
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
|
||||
}
|
||||
last = millis();
|
||||
#endif
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
unsigned char channel = index / _magnitudes;
|
||||
unsigned char i=0;
|
||||
#if EMON_REPORT_CURRENT
|
||||
if (index == i++) return _current[channel];
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
if (index == i++) return _current[channel] * _voltage;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
if (index == i) return _energy[channel];
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
unsigned int readADC(unsigned char channel) {
|
||||
(void) channel;
|
||||
return analogRead(0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && EMON_ANALOG_SUPPORT
|
242
espurna/sensors/EmonSensor.h
Executable file
242
espurna/sensors/EmonSensor.h
Executable file
@ -0,0 +1,242 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
class EmonSensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
EmonSensor(): I2CSensor() {
|
||||
|
||||
// Calculate # of magnitudes
|
||||
#if EMON_REPORT_CURRENT
|
||||
++_magnitudes;
|
||||
#endif
|
||||
#if EMON_REPORT_POWER
|
||||
++_magnitudes;
|
||||
#endif
|
||||
#if EMON_REPORT_ENERGY
|
||||
++_magnitudes;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void expectedPower(unsigned char channel, unsigned int expected) {
|
||||
if (channel >= _channels) return;
|
||||
unsigned int actual = _current[channel] * _voltage;
|
||||
if (actual == 0) return;
|
||||
if (expected == actual) return;
|
||||
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void resetEnergy() {
|
||||
for (unsigned char i=0; i<_channels; i++) {
|
||||
_energy[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setVoltage(double voltage) {
|
||||
if (_voltage == voltage) return;
|
||||
_voltage = voltage;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setReference(double reference) {
|
||||
if (_reference == reference) return;
|
||||
_reference = reference;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setCurrentRatio(unsigned char channel, double current_ratio) {
|
||||
if (channel >= _channels) return;
|
||||
if (_current_ratio[channel] == current_ratio) return;
|
||||
_current_ratio[channel] = current_ratio;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
double getVoltage() {
|
||||
return _voltage;
|
||||
}
|
||||
|
||||
double getReference() {
|
||||
return _reference;
|
||||
}
|
||||
|
||||
double getCurrentRatio(unsigned char channel) {
|
||||
if (channel >= _channels) return 0;
|
||||
return _current_ratio[channel];
|
||||
}
|
||||
|
||||
unsigned char getChannels() {
|
||||
return _channels;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void begin() {
|
||||
|
||||
// Resolution
|
||||
_adc_counts = 1 << _resolution;
|
||||
|
||||
// Calculations
|
||||
for (unsigned char i=0; i<_channels; i++) {
|
||||
_energy[i] = _current[i] = 0;
|
||||
_pivot[i] = _adc_counts >> 1;
|
||||
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
|
||||
_multiplier[i] = calculateMultiplier(_current_factor[i]);
|
||||
}
|
||||
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG("[EMON] Reference (mV): %d\n", int(1000 * _reference));
|
||||
DEBUG_MSG("[EMON] ADC counts: %d\n", _adc_counts);
|
||||
for (unsigned char i=0; i<_channels; i++) {
|
||||
DEBUG_MSG("[EMON] Channel #%d current ratio (mA/V): %d\n", i, int(1000 * _current_ratio[i]));
|
||||
DEBUG_MSG("[EMON] Channel #%d current factor (mA/bit): %d\n", i, int(1000 * _current_factor[i]));
|
||||
DEBUG_MSG("[EMON] Channel #%d Multiplier: %d\n", i, int(_multiplier[i]));
|
||||
}
|
||||
#endif
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initializes internal variables
|
||||
void init() {
|
||||
_current_ratio = new double[_channels];
|
||||
_current_factor = new double[_channels];
|
||||
_multiplier = new uint16_t[_channels];
|
||||
_pivot = new double[_channels];
|
||||
_current = new double[_channels];
|
||||
#if EMON_REPORT_ENERGY
|
||||
_energy = new uint32_t[_channels];
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual unsigned int readADC(unsigned char channel) {}
|
||||
|
||||
unsigned int calculateMultiplier(double current_factor) {
|
||||
unsigned int s = 1;
|
||||
unsigned int i = 1;
|
||||
unsigned int m = s * i;
|
||||
unsigned int multiplier;
|
||||
while (m * current_factor < 1) {
|
||||
multiplier = m;
|
||||
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
|
||||
if (i == 1) s *= 10;
|
||||
m = s * i;
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
double read(unsigned char channel) {
|
||||
|
||||
int max = 0;
|
||||
int min = _adc_counts;
|
||||
double sum = 0;
|
||||
|
||||
unsigned long time_span = millis();
|
||||
for (unsigned long i=0; i<_samples; i++) {
|
||||
|
||||
int sample;
|
||||
double filtered;
|
||||
|
||||
// Read analog value
|
||||
sample = readADC(channel);
|
||||
if (sample > max) max = sample;
|
||||
if (sample < min) min = sample;
|
||||
|
||||
// Digital low pass filter extracts the VDC offset
|
||||
_pivot[channel] = (_pivot[channel] + (sample - _pivot[channel]) / EMON_FILTER_SPEED);
|
||||
filtered = sample - _pivot[channel];
|
||||
|
||||
// Root-mean-square method
|
||||
sum += (filtered * filtered);
|
||||
|
||||
}
|
||||
time_span = millis() - time_span;
|
||||
|
||||
// Quick fix
|
||||
if (_pivot[channel] < min || max < _pivot[channel]) {
|
||||
_pivot[channel] = (max + min) / 2.0;
|
||||
}
|
||||
|
||||
// Calculate current
|
||||
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
|
||||
double current = _current_factor[channel] * rms;
|
||||
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
|
||||
if (current < 0) current = 0;
|
||||
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG("[EMON] Channel: %d\n", channel);
|
||||
DEBUG_MSG("[EMON] Total samples: %d\n", _samples);
|
||||
DEBUG_MSG("[EMON] Total time (ms): %d\n", time_span);
|
||||
DEBUG_MSG("[EMON] Sample frequency (Hz): %d\n", int(1000 * _samples / time_span));
|
||||
DEBUG_MSG("[EMON] Max value: %d\n", max);
|
||||
DEBUG_MSG("[EMON] Min value: %d\n", min);
|
||||
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
|
||||
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
|
||||
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
|
||||
#endif
|
||||
|
||||
// Check timing
|
||||
if ((time_span > EMON_MAX_TIME)
|
||||
|| ((time_span < EMON_MAX_TIME) && (_samples < EMON_MAX_SAMPLES))) {
|
||||
_samples = (_samples * EMON_MAX_TIME) / time_span;
|
||||
}
|
||||
|
||||
return current;
|
||||
|
||||
}
|
||||
|
||||
unsigned char _channels = 0; // Number of ADC channels available
|
||||
unsigned char _magnitudes = 0; // Number of magnitudes per channel
|
||||
unsigned long _samples = EMON_MAX_SAMPLES; // Samples (dynamically modificable)
|
||||
|
||||
unsigned char _resolution = 10; // ADC resolution in bits
|
||||
unsigned long _adc_counts; // Max count
|
||||
|
||||
double _voltage = EMON_MAINS_VOLTAGE; // Mains voltage
|
||||
double _reference = EMON_REFERENCE_VOLTAGE; // ADC reference voltage (100%)
|
||||
|
||||
double * _current_ratio; // Ratio ampers in main loop to voltage in secondary (per channel)
|
||||
double * _current_factor; // Calculated, reads (RMS) to current (per channel)
|
||||
uint16_t * _multiplier; // Calculated, error (per channel)
|
||||
|
||||
double * _pivot; // Moving average mid point (per channel)
|
||||
double * _current; // Last current reading (per channel)
|
||||
#if EMON_REPORT_ENERGY
|
||||
uint32_t * _energy; // Aggregated energy (per channel)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT
|
211
espurna/sensors/EventSensor.h
Executable file
211
espurna/sensors/EventSensor.h
Executable file
@ -0,0 +1,211 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Event Counter Sensor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && EVENTS_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
class EventSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
EventSensor(): BaseSensor() {
|
||||
_count = 1;
|
||||
_sensor_id = SENSOR_EVENTS_ID;
|
||||
}
|
||||
|
||||
~EventSensor() {
|
||||
_enableInterrupts(false);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setGPIO(unsigned char gpio) {
|
||||
_gpio = gpio;
|
||||
}
|
||||
|
||||
void setMode(unsigned char mode) {
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
void setInterruptMode(unsigned char mode) {
|
||||
_interrupt_mode = mode;
|
||||
}
|
||||
|
||||
void setDebounceTime(unsigned long debounce) {
|
||||
_debounce = debounce;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getGPIO() {
|
||||
return _gpio;
|
||||
}
|
||||
|
||||
unsigned char getMode() {
|
||||
return _mode;
|
||||
}
|
||||
|
||||
unsigned char getInterruptMode() {
|
||||
return _interrupt_mode;
|
||||
}
|
||||
|
||||
unsigned long getDebounceTime() {
|
||||
return _debounce;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensors API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
// Defined outside the class body
|
||||
void begin() {
|
||||
pinMode(_gpio, _mode);
|
||||
_enableInterrupts(true);
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "INTERRUPT @ GPIO%d", _gpio);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_gpio);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_EVENTS;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) {
|
||||
double value = _events;
|
||||
_events = 0;
|
||||
return value;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle interrupt calls
|
||||
void handleInterrupt(unsigned char gpio) {
|
||||
(void) gpio;
|
||||
static unsigned long last = 0;
|
||||
if (millis() - last > _debounce) {
|
||||
_events = _events + 1;
|
||||
last = millis();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Interrupt management
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _attach(EventSensor * instance, unsigned char gpio, unsigned char mode);
|
||||
void _detach(unsigned char gpio);
|
||||
|
||||
void _enableInterrupts(bool value) {
|
||||
|
||||
static unsigned char _interrupt_gpio = GPIO_NONE;
|
||||
|
||||
if (value) {
|
||||
if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio);
|
||||
_attach(this, _gpio, _interrupt_mode);
|
||||
_interrupt_gpio = _gpio;
|
||||
} else if (_interrupt_gpio != GPIO_NONE) {
|
||||
_detach(_interrupt_gpio);
|
||||
_interrupt_gpio = GPIO_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
volatile unsigned long _events = 0;
|
||||
unsigned long _debounce = EVENTS_DEBOUNCE;
|
||||
unsigned char _gpio;
|
||||
unsigned char _mode;
|
||||
unsigned char _interrupt_mode;
|
||||
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Interrupt helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
EventSensor * _event_sensor_instance[10] = {NULL};
|
||||
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr(unsigned char gpio) {
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_event_sensor_instance[index]) {
|
||||
_event_sensor_instance[index]->handleInterrupt(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_0() { _event_sensor_isr(0); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_1() { _event_sensor_isr(1); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_2() { _event_sensor_isr(2); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_3() { _event_sensor_isr(3); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_4() { _event_sensor_isr(4); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_5() { _event_sensor_isr(5); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_12() { _event_sensor_isr(12); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_13() { _event_sensor_isr(13); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_14() { _event_sensor_isr(14); }
|
||||
void ICACHE_RAM_ATTR _event_sensor_isr_15() { _event_sensor_isr(15); }
|
||||
|
||||
static void (*_event_sensor_isr_list[10])() = {
|
||||
_event_sensor_isr_0, _event_sensor_isr_1, _event_sensor_isr_2,
|
||||
_event_sensor_isr_3, _event_sensor_isr_4, _event_sensor_isr_5,
|
||||
_event_sensor_isr_12, _event_sensor_isr_13, _event_sensor_isr_14,
|
||||
_event_sensor_isr_15
|
||||
};
|
||||
|
||||
void EventSensor::_attach(EventSensor * instance, unsigned char gpio, unsigned char mode) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
_detach(gpio);
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
_event_sensor_instance[index] = instance;
|
||||
attachInterrupt(gpio, _event_sensor_isr_list[index], mode);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void EventSensor::_detach(unsigned char gpio) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_event_sensor_instance[index]) {
|
||||
detachInterrupt(gpio);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _event_sensor_instance[index]->description().c_str());
|
||||
#endif
|
||||
_event_sensor_instance[index] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SENSOR_SUPPORT && EVENTS_SUPPORT
|
170
espurna/sensors/GUVAS12SDSensor.h
Executable file
170
espurna/sensors/GUVAS12SDSensor.h
Executable file
@ -0,0 +1,170 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// GUVA-S12SD UV Sensor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// by Mustafa Tufan
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && GUVAS12SD_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
// http://www.eoc-inc.com/genicom/GUVA-S12SD.pdf
|
||||
//
|
||||
// GUVA-S12D has a wide spectral range of 200nm-400nm
|
||||
// The output voltage and the UV index is linear, illumination intensity = 307 * Vsig where: Vsig is the value of voltage measured from the SIG pin of the interface, unit V.
|
||||
// illumination intensity unit: mW/m2 for the combination strength of UV light with wavelength range: 200nm-400nm
|
||||
// UV Index = illumination intensity / 200
|
||||
//
|
||||
// UV Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10+
|
||||
// -----------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+--------
|
||||
// mV | <50 | 227 | 318 | 408 | 503 | 606 | 696 | 795 | 881 | 976 | 1079 | 1170+
|
||||
// analog val | <10 | 46 | 65 | 83 | 103 | 124 | 142 | 162 | 180 | 200 | 221 | 240+
|
||||
//
|
||||
|
||||
#define UV_SAMPLE_RATE 1
|
||||
|
||||
class GUVAS12SDSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
GUVAS12SDSensor(): BaseSensor() {
|
||||
_count = 1;
|
||||
_sensor_id = SENSOR_GUVAS12SD_ID;
|
||||
}
|
||||
|
||||
~GUVAS12SDSensor() {
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setGPIO(unsigned char gpio) {
|
||||
_gpio = gpio;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getGPIO() {
|
||||
return _gpio;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
// Manage GPIO lock
|
||||
if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
|
||||
_previous = GPIO_NONE;
|
||||
if (!gpioGetLock(_gpio)) {
|
||||
_error = SENSOR_ERROR_GPIO_USED;
|
||||
return;
|
||||
}
|
||||
_previous = _gpio;
|
||||
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
_error = SENSOR_ERROR_OK;
|
||||
_read();
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[18];
|
||||
snprintf(buffer, sizeof(buffer), "GUVAS12SD @ GPIO%d", _gpio);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_gpio);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_UV;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _uvindex;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _read() {
|
||||
int _average = 0;
|
||||
|
||||
#if UV_SAMPLE_RATE == 1
|
||||
_average = analogRead(0);
|
||||
#else
|
||||
for (unsigned int i=0; i < UV_SAMPLE_RATE; i++) {
|
||||
_average += analogRead(0);
|
||||
nice_delay(2);
|
||||
}
|
||||
_average = (_average / UV_SAMPLE_RATE);
|
||||
#endif
|
||||
// _sensormV = _average / 1023*3.3;
|
||||
|
||||
if (_average < 10) {
|
||||
_uvindex = 0;
|
||||
} else if (_average < 46) {
|
||||
_uvindex = (_average - 10) / (46-10);
|
||||
} else if (_average < 65) {
|
||||
_uvindex = 1 + ((_average - 46) / (65-46));
|
||||
} else if (_average < 83) {
|
||||
_uvindex = 2 + ((_average - 65) / (83-65));
|
||||
} else if (_average < 103) {
|
||||
_uvindex = 3 + ((_average - 83) / (103- 83));
|
||||
} else if (_average < 124) {
|
||||
_uvindex = 4 + ((_average - 103) / (124-103));
|
||||
} else if (_average < 142) {
|
||||
_uvindex = 5 + ((_average - 124) / (142-124));
|
||||
} else if (_average < 162) {
|
||||
_uvindex = 6 + ((_average - 142) / (162-142));
|
||||
} else if (_average < 180) {
|
||||
_uvindex = 7 + ((_average - 162) / (180-162));
|
||||
} else if (_average < 200) {
|
||||
_uvindex = 8 + ((_average - 180) / (200-180));
|
||||
} else if (_average < 221) {
|
||||
_uvindex = 9 + ((_average - 200) / (221-200));
|
||||
} else {
|
||||
_uvindex = 10;
|
||||
}
|
||||
|
||||
return _uvindex;
|
||||
}
|
||||
|
||||
unsigned char _gpio = GPIO_NONE;
|
||||
unsigned char _previous = GPIO_NONE;
|
||||
|
||||
double _uvindex = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT
|
328
espurna/sensors/HLW8012Sensor.h
Executable file
328
espurna/sensors/HLW8012Sensor.h
Executable file
@ -0,0 +1,328 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Event Counter Sensor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && HLW8012_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <HLW8012.h>
|
||||
|
||||
class HLW8012Sensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
HLW8012Sensor(): BaseSensor() {
|
||||
_count = 7;
|
||||
_sensor_id = SENSOR_HLW8012_ID;
|
||||
_hlw8012 = new HLW8012();
|
||||
}
|
||||
|
||||
~HLW8012Sensor() {
|
||||
_enableInterrupts(false);
|
||||
delete _hlw8012;
|
||||
}
|
||||
|
||||
void expectedCurrent(double expected) {
|
||||
_hlw8012->expectedCurrent(expected);
|
||||
}
|
||||
|
||||
void expectedVoltage(unsigned int expected) {
|
||||
_hlw8012->expectedVoltage(expected);
|
||||
}
|
||||
|
||||
void expectedPower(unsigned int expected) {
|
||||
_hlw8012->expectedActivePower(expected);
|
||||
}
|
||||
|
||||
void resetRatios() {
|
||||
_hlw8012->resetMultipliers();
|
||||
}
|
||||
|
||||
void resetEnergy() {
|
||||
_hlw8012->resetEnergy();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setSEL(unsigned char sel) {
|
||||
if (_sel == sel) return;
|
||||
_sel = sel;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setCF(unsigned char cf) {
|
||||
if (_cf == cf) return;
|
||||
_cf = cf;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setCF1(unsigned char cf1) {
|
||||
if (_cf1 == cf1) return;
|
||||
_cf1 = cf1;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setSELCurrent(bool value) {
|
||||
_sel_current = value;
|
||||
}
|
||||
|
||||
void setCurrentRatio(double value) {
|
||||
_hlw8012->setCurrentMultiplier(value);
|
||||
};
|
||||
|
||||
void setVoltageRatio(double value) {
|
||||
_hlw8012->setVoltageMultiplier(value);
|
||||
};
|
||||
|
||||
void setPowerRatio(double value) {
|
||||
_hlw8012->setPowerMultiplier(value);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getSEL() {
|
||||
return _sel;
|
||||
}
|
||||
|
||||
unsigned char getCF() {
|
||||
return _cf;
|
||||
}
|
||||
|
||||
unsigned char getCF1() {
|
||||
return _cf1;
|
||||
}
|
||||
|
||||
unsigned char getSELCurrent() {
|
||||
return _sel_current;
|
||||
}
|
||||
|
||||
double getCurrentRatio() {
|
||||
return _hlw8012->getCurrentMultiplier();
|
||||
};
|
||||
|
||||
double getVoltageRatio() {
|
||||
return _hlw8012->getVoltageMultiplier();
|
||||
};
|
||||
|
||||
double getPowerRatio() {
|
||||
return _hlw8012->getPowerMultiplier();
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensors API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
// Defined outside the class body
|
||||
void begin() {
|
||||
|
||||
// Initialize HLW8012
|
||||
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
|
||||
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
|
||||
// * currentWhen is the value in sel_pin to select current sampling
|
||||
// * set use_interrupts to true to use interrupts to monitor pulse widths
|
||||
// * leave pulse_timeout to the default value, recommended when using interrupts
|
||||
#if HLW8012_USE_INTERRUPTS
|
||||
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, true);
|
||||
#else
|
||||
_hlw8012->begin(_cf, _cf1, _sel, _sel_current, false, 1000000);
|
||||
#endif
|
||||
|
||||
// These values are used to calculate current, voltage and power factors as per datasheet formula
|
||||
// These are the nominal values for the Sonoff POW resistors:
|
||||
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
|
||||
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
|
||||
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
|
||||
_hlw8012->setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
|
||||
|
||||
// Handle interrupts
|
||||
#if HLW8012_USE_INTERRUPTS
|
||||
_enableInterrupts(true);
|
||||
#else
|
||||
_onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) {
|
||||
_enableInterrupts(true);
|
||||
});
|
||||
_ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) {
|
||||
_enableInterrupts(false);
|
||||
});
|
||||
#endif
|
||||
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%u,%u,%u)", _sel, _cf, _cf1);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[10];
|
||||
snprintf(buffer, sizeof(buffer), "%u:%u:%u", _sel, _cf, _cf1);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CURRENT;
|
||||
if (index == 1) return MAGNITUDE_VOLTAGE;
|
||||
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
|
||||
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
|
||||
if (index == 4) return MAGNITUDE_POWER_APPARENT;
|
||||
if (index == 5) return MAGNITUDE_POWER_FACTOR;
|
||||
if (index == 6) return MAGNITUDE_ENERGY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _hlw8012->getCurrent();
|
||||
if (index == 1) return _hlw8012->getVoltage();
|
||||
if (index == 2) return _hlw8012->getActivePower();
|
||||
if (index == 3) return _hlw8012->getReactivePower();
|
||||
if (index == 4) return _hlw8012->getApparentPower();
|
||||
if (index == 5) return 100 * _hlw8012->getPowerFactor();
|
||||
if (index == 6) return _hlw8012->getEnergy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Toggle between current and voltage monitoring
|
||||
#if HLW8012_USE_INTERRUPTS == 0
|
||||
// Post-read hook (usually to reset things)
|
||||
void post() { _hlw8012->toggleMode(); }
|
||||
#endif // HLW8012_USE_INTERRUPTS == 0
|
||||
|
||||
// Handle interrupt calls
|
||||
void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) {
|
||||
if (gpio == _cf) _hlw8012->cf_interrupt();
|
||||
if (gpio == _cf1) _hlw8012->cf1_interrupt();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Interrupt management
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode);
|
||||
void _detach(unsigned char gpio);
|
||||
|
||||
void _enableInterrupts(bool value) {
|
||||
|
||||
static unsigned char _interrupt_cf = GPIO_NONE;
|
||||
static unsigned char _interrupt_cf1 = GPIO_NONE;
|
||||
|
||||
if (value) {
|
||||
|
||||
if (_interrupt_cf != _cf) {
|
||||
if (_interrupt_cf != GPIO_NONE) _detach(_interrupt_cf);
|
||||
_attach(this, _cf, CHANGE);
|
||||
_interrupt_cf = _cf;
|
||||
}
|
||||
|
||||
if (_interrupt_cf1 != _cf1) {
|
||||
if (_interrupt_cf1 != GPIO_NONE) _detach(_interrupt_cf1);
|
||||
_attach(this, _cf1, CHANGE);
|
||||
_interrupt_cf1 = _cf1;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
_detach(_cf);
|
||||
_detach(_cf1);
|
||||
_interrupt_cf = GPIO_NONE;
|
||||
_interrupt_cf1 = GPIO_NONE;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char _sel = GPIO_NONE;
|
||||
unsigned char _cf = GPIO_NONE;
|
||||
unsigned char _cf1 = GPIO_NONE;
|
||||
bool _sel_current = true;
|
||||
|
||||
HLW8012 * _hlw8012 = NULL;
|
||||
|
||||
#if HLW8012_USE_INTERRUPTS == 0
|
||||
WiFiEventHandler _onconnect_handler;
|
||||
WiFiEventHandler _ondisconnect_handler;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Interrupt helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
HLW8012Sensor * _hlw8012_sensor_instance[10] = {NULL};
|
||||
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr(unsigned char gpio) {
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_hlw8012_sensor_instance[index]) {
|
||||
_hlw8012_sensor_instance[index]->handleInterrupt(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_0() { _hlw8012_sensor_isr(0); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_1() { _hlw8012_sensor_isr(1); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_2() { _hlw8012_sensor_isr(2); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_3() { _hlw8012_sensor_isr(3); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_4() { _hlw8012_sensor_isr(4); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_5() { _hlw8012_sensor_isr(5); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_12() { _hlw8012_sensor_isr(12); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_13() { _hlw8012_sensor_isr(13); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_14() { _hlw8012_sensor_isr(14); }
|
||||
void ICACHE_RAM_ATTR _hlw8012_sensor_isr_15() { _hlw8012_sensor_isr(15); }
|
||||
|
||||
static void (*_hlw8012_sensor_isr_list[10])() = {
|
||||
_hlw8012_sensor_isr_0, _hlw8012_sensor_isr_1, _hlw8012_sensor_isr_2,
|
||||
_hlw8012_sensor_isr_3, _hlw8012_sensor_isr_4, _hlw8012_sensor_isr_5,
|
||||
_hlw8012_sensor_isr_12, _hlw8012_sensor_isr_13, _hlw8012_sensor_isr_14,
|
||||
_hlw8012_sensor_isr_15
|
||||
};
|
||||
|
||||
void HLW8012Sensor::_attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
_detach(gpio);
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
_hlw8012_sensor_instance[index] = instance;
|
||||
attachInterrupt(gpio, _hlw8012_sensor_isr_list[index], mode);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void HLW8012Sensor::_detach(unsigned char gpio) {
|
||||
if (!gpioValid(gpio)) return;
|
||||
unsigned char index = gpio > 5 ? gpio-6 : gpio;
|
||||
if (_hlw8012_sensor_instance[index]) {
|
||||
detachInterrupt(gpio);
|
||||
#if SENSOR_DEBUG
|
||||
DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str());
|
||||
#endif
|
||||
_hlw8012_sensor_instance[index] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SENSOR_SUPPORT && HLW8012_SUPPORT
|
95
espurna/sensors/I2CSensor.h
Executable file
95
espurna/sensors/I2CSensor.h
Executable file
@ -0,0 +1,95 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Abstract I2C sensor class (other sensor classes extend this class)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT )
|
||||
|
||||
#if I2C_USE_BRZO
|
||||
#define I2C_TRANS_SUCCESS 0 // All i2c commands were executed without errors
|
||||
#define I2C_TRANS_ERROR_BUS_NOT_FREE 1 // Bus not free, i.e. either SDA or SCL is low
|
||||
#define I2C_TRANS_ERROR_NACK_WRITE 2 // Not ACK ("NACK") by slave during write:
|
||||
// Either the slave did not respond to the given slave address; or the slave did not ACK a byte transferred by the master.
|
||||
#define I2C_TRANS_ERROR_NACK_READ 4 // Not ACK ("NACK") by slave during read,
|
||||
// i.e. slave did not respond to the given slave address
|
||||
#define I2C_TRANS_ERROR_CLOCK 8 // Clock Stretching by slave exceeded maximum clock stretching time. Most probably, there is a bus stall now!
|
||||
#define I2C_TRANS_ERROR_READ_NULL 16 // Read was called with 0 bytes to be read by the master. Command not sent to the slave, since this could yield to a bus stall
|
||||
#define I2C_TRANS_ERROR_TIMEOUT 32 // ACK Polling timeout exceeded
|
||||
#else // Wire
|
||||
#define I2C_TRANS_SUCCESS 0 // success
|
||||
#define I2C_TRANS_ERROR_BUFFER_OVERLOW 1 // data too long to fit in transmit buffer
|
||||
#define I2C_TRANS_ERROR_NACK_ADDRESS 2 // received NACK on transmit of address
|
||||
#define I2C_TRANS_ERROR_NACK_DATA 3 // received NACK on transmit of data
|
||||
#define I2C_TRANS_ERROR_OTHER 4 // other error
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseSensor.h"
|
||||
|
||||
class I2CSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
void setAddress(unsigned char address) {
|
||||
if (_address == address) return;
|
||||
_address = address;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
unsigned char getAddress() {
|
||||
return _address;
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[5];
|
||||
snprintf(buffer, sizeof(buffer), "0x%02X", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Specific for I2C sensors
|
||||
unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) {
|
||||
|
||||
// If we have already locked this address for this sensor quit
|
||||
if ((address > 0) && (address == _previous_address)) {
|
||||
return _previous_address;
|
||||
}
|
||||
|
||||
// Check if we should release a previously locked address
|
||||
if ((_previous_address > 0) && (_previous_address != address)) {
|
||||
i2cReleaseLock(_previous_address);
|
||||
_previous_address = 0;
|
||||
}
|
||||
|
||||
// If requesting a specific address, try to ger a lock to it
|
||||
if ((0 < address) && i2cGetLock(address)) {
|
||||
_previous_address = address;
|
||||
return _previous_address;
|
||||
}
|
||||
|
||||
// If everything else fails, perform an auto-discover
|
||||
_previous_address = i2cFindAndLock(size, addresses);
|
||||
|
||||
// Flag error
|
||||
if (0 == _previous_address) {
|
||||
_error = SENSOR_ERROR_I2C;
|
||||
}
|
||||
|
||||
return _previous_address;
|
||||
|
||||
}
|
||||
|
||||
unsigned char _previous_address = 0;
|
||||
unsigned char _address = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && I2C_SUPPORT
|
221
espurna/sensors/MHZ19Sensor.h
Executable file
221
espurna/sensors/MHZ19Sensor.h
Executable file
@ -0,0 +1,221 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// MHZ19 CO2 sensor
|
||||
// Based on: https://github.com/nara256/mhz19_uart
|
||||
// http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
|
||||
// Uses SoftwareSerial library
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && MHZ19_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
#define MHZ19_REQUEST_LEN 8
|
||||
#define MHZ19_RESPONSE_LEN 9
|
||||
#define MHZ19_TIMEOUT 1000
|
||||
#define MHZ19_GETPPM 0x8600
|
||||
#define MHZ19_ZEROCALIB 0x8700
|
||||
#define MHZ19_SPANCALIB 0x8800
|
||||
#define MHZ19_AUTOCALIB_ON 0x79A0
|
||||
#define MHZ19_AUTOCALIB_OFF 0x7900
|
||||
|
||||
class MHZ19Sensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
MHZ19Sensor(): BaseSensor() {
|
||||
_count = 1;
|
||||
_sensor_id = SENSOR_MHZ19_ID;
|
||||
}
|
||||
|
||||
~MHZ19Sensor() {
|
||||
if (_serial) delete _serial;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setRX(unsigned char pin_rx) {
|
||||
if (_pin_rx == pin_rx) return;
|
||||
_pin_rx = pin_rx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setTX(unsigned char pin_tx) {
|
||||
if (_pin_tx == pin_tx) return;
|
||||
_pin_tx = pin_tx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getRX() {
|
||||
return _pin_rx;
|
||||
}
|
||||
|
||||
unsigned char getTX() {
|
||||
return _pin_tx;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
if (_serial) delete _serial;
|
||||
|
||||
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
|
||||
_serial->enableIntTx(false);
|
||||
_serial->begin(9600);
|
||||
calibrateAuto(false);
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[28];
|
||||
snprintf(buffer, sizeof(buffer), "MHZ19 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[6];
|
||||
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CO2;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
void pre() {
|
||||
_read();
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _co2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void calibrateAuto(boolean state){
|
||||
_write(state ? MHZ19_AUTOCALIB_ON : MHZ19_AUTOCALIB_OFF);
|
||||
}
|
||||
|
||||
void calibrateZero() {
|
||||
_write(MHZ19_ZEROCALIB);
|
||||
}
|
||||
|
||||
void calibrateSpan(unsigned int ppm) {
|
||||
if( ppm < 1000 ) return;
|
||||
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
|
||||
buffer[0] = 0xFF;
|
||||
buffer[1] = 0x01;
|
||||
buffer[2] = MHZ19_SPANCALIB >> 8;
|
||||
buffer[3] = ppm >> 8;
|
||||
buffer[4] = ppm & 0xFF;
|
||||
_write(buffer);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _write(unsigned char * command) {
|
||||
_serial->write(command, MHZ19_REQUEST_LEN);
|
||||
_serial->write(_checksum(command));
|
||||
_serial->flush();
|
||||
}
|
||||
|
||||
void _write(unsigned int command, unsigned char * response) {
|
||||
|
||||
unsigned char buffer[MHZ19_REQUEST_LEN] = {0};
|
||||
buffer[0] = 0xFF;
|
||||
buffer[1] = 0x01;
|
||||
buffer[2] = command >> 8;
|
||||
buffer[3] = command & 0xFF;
|
||||
_write(buffer);
|
||||
|
||||
if (response != NULL) {
|
||||
unsigned long start = millis();
|
||||
while (_serial->available() == 0) {
|
||||
if (millis() - start > MHZ19_TIMEOUT) {
|
||||
_error = SENSOR_ERROR_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
_serial->readBytes(response, MHZ19_RESPONSE_LEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _write(unsigned int command) {
|
||||
_write(command, NULL);
|
||||
}
|
||||
|
||||
void _read() {
|
||||
|
||||
unsigned char buffer[MHZ19_RESPONSE_LEN] = {0};
|
||||
_write(MHZ19_GETPPM, buffer);
|
||||
|
||||
// Check response
|
||||
if ((buffer[0] == 0xFF)
|
||||
&& (buffer[1] == 0x86)
|
||||
&& (_checksum(buffer) == buffer[MHZ19_RESPONSE_LEN-1])) {
|
||||
|
||||
unsigned int value = buffer[2] * 256 + buffer[3];
|
||||
if (0 <= value && value <= 5000) {
|
||||
_co2 = value;
|
||||
_error = SENSOR_ERROR_OK;
|
||||
} else {
|
||||
_error = SENSOR_ERROR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
} else {
|
||||
_error = SENSOR_ERROR_CRC;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint8_t _checksum(uint8_t * command) {
|
||||
uint8_t sum = 0x00;
|
||||
for (unsigned char i = 1; i < MHZ19_REQUEST_LEN-1; i++) {
|
||||
sum += command[i];
|
||||
}
|
||||
sum = 0xFF - sum + 0x01;
|
||||
return sum;
|
||||
}
|
||||
|
||||
double _co2 = 0;
|
||||
unsigned int _pin_rx;
|
||||
unsigned int _pin_tx;
|
||||
SoftwareSerial * _serial = NULL;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && MHZ19_SUPPORT
|
152
espurna/sensors/PMSX003Sensor.h
Executable file
152
espurna/sensors/PMSX003Sensor.h
Executable file
@ -0,0 +1,152 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// PMSX003 Dust Sensor
|
||||
// Uses SoftwareSerial library
|
||||
// Contribution by Òscar Rovira López
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && PMSX003_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#include <PMS.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
class PMSX003Sensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
PMSX003Sensor(): BaseSensor() {
|
||||
_count = 3;
|
||||
_sensor_id = SENSOR_PMSX003_ID;
|
||||
}
|
||||
|
||||
~PMSX003Sensor() {
|
||||
if (_serial) delete _serial;
|
||||
if (_pms) delete _pms;
|
||||
}
|
||||
|
||||
void setRX(unsigned char pin_rx) {
|
||||
if (_pin_rx == pin_rx) return;
|
||||
_pin_rx = pin_rx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setTX(unsigned char pin_tx) {
|
||||
if (_pin_tx == pin_tx) return;
|
||||
_pin_tx = pin_tx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getRX() {
|
||||
return _pin_rx;
|
||||
}
|
||||
|
||||
unsigned char getTX() {
|
||||
return _pin_tx;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
if (_serial) delete _serial;
|
||||
if (_pms) delete _pms;
|
||||
|
||||
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
|
||||
_serial->enableIntTx(false);
|
||||
_serial->begin(9600);
|
||||
_pms = new PMS(* _serial);
|
||||
_pms->passiveMode();
|
||||
|
||||
_startTime = millis();
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[28];
|
||||
snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
char buffer[36] = {0};
|
||||
if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
char buffer[6];
|
||||
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_PM1dot0;
|
||||
if (index == 1) return MAGNITUDE_PM2dot5;
|
||||
if (index == 2) return MAGNITUDE_PM10;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
void pre() {
|
||||
|
||||
if (millis() - _startTime < 30000) {
|
||||
_error = SENSOR_ERROR_WARM_UP;
|
||||
return;
|
||||
}
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
if(_pms->read(_data)) {
|
||||
_pm1dot0 = _data.PM_AE_UG_1_0;
|
||||
_pm2dot5 = _data.PM_AE_UG_2_5;
|
||||
_pm10 = _data.PM_AE_UG_10_0;
|
||||
}
|
||||
|
||||
_pms->requestRead();
|
||||
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if(index == 0) return _pm1dot0;
|
||||
if(index == 1) return _pm2dot5;
|
||||
if(index == 2) return _pm10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
unsigned int _pm1dot0;
|
||||
unsigned int _pm2dot5;
|
||||
unsigned int _pm10;
|
||||
unsigned int _pin_rx;
|
||||
unsigned int _pin_tx;
|
||||
unsigned long _startTime;
|
||||
SoftwareSerial * _serial = NULL;
|
||||
PMS * _pms = NULL;
|
||||
PMS::DATA _data;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && PMSX003_SUPPORT
|
136
espurna/sensors/PZEM004TSensor.h
Executable file
136
espurna/sensors/PZEM004TSensor.h
Executable file
@ -0,0 +1,136 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// PZEM004T based power monitor
|
||||
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#include <PZEM004T.h>
|
||||
|
||||
class PZEM004TSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
PZEM004TSensor(): BaseSensor(), _data() {
|
||||
_count = 4;
|
||||
_sensor_id = SENSOR_PZEM004T_ID;
|
||||
}
|
||||
|
||||
~PZEM004TSensor() {
|
||||
if (_pzem) delete _pzem;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setRX(unsigned char pin_rx) {
|
||||
if (_pin_rx == pin_rx) return;
|
||||
_pin_rx = pin_rx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setTX(unsigned char pin_tx) {
|
||||
if (_pin_tx == pin_tx) return;
|
||||
_pin_tx = pin_tx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setSerial(Stream & serial) {
|
||||
_serial = serial;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getRX() {
|
||||
return _pin_rx;
|
||||
}
|
||||
|
||||
unsigned char getTX() {
|
||||
return _pin_tx;
|
||||
}
|
||||
|
||||
Stream & getSerial() {
|
||||
return _serial;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
if (_pzem) delete _pzem;
|
||||
if (_serial == NULL) {
|
||||
_pzem = PZEM004T(_pin_rx, _pin_tx);
|
||||
} else {
|
||||
_pzem = PZEM004T(_serial);
|
||||
}
|
||||
_pzem->setAddress(_ip);
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[28];
|
||||
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return _ip.toString();
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CURRENT;
|
||||
if (index == 1) return MAGNITUDE_VOLTAGE;
|
||||
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
|
||||
if (index == 3) return MAGNITUDE_ENERGY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _pzem->current(_ip);
|
||||
if (index == 1) return _pzem->voltage(_ip);
|
||||
if (index == 2) return _pzem->power(_ip);
|
||||
if (index == 3) return _pzem->energy(_ip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned int _pin_rx = PZEM004T_RX_PIN;
|
||||
unsigned int _pin_tx = PZEM004T_TX_PIN;
|
||||
Stream & _serial = NULL;
|
||||
IPAddress _ip(192,168,1,1);
|
||||
PZEM004T * _pzem = NULL;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT
|
89
espurna/sensors/SHT3XI2CSensor.h
Executable file
89
espurna/sensors/SHT3XI2CSensor.h
Executable file
@ -0,0 +1,89 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SHT3X Sensor over I2C (Wemos)
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
class SHT3XI2CSensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
SHT3XI2CSensor(): I2CSensor() {
|
||||
_sensor_id = SENSOR_SHT3X_I2C_ID;
|
||||
_count = 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
// I2C auto-discover
|
||||
unsigned char addresses[] = {0x45};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "SHT3X @ I2C (0x%02X)", _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_TEMPERATURE;
|
||||
if (index == 1) return MAGNITUDE_HUMIDITY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
unsigned char buffer[6];
|
||||
i2c_write_uint8(_address, 0x2C, 0x06);
|
||||
nice_delay(500);
|
||||
i2c_read_buffer(_address, buffer, 6);
|
||||
|
||||
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
|
||||
_temperature = ((((buffer[0] * 256.0) + buffer[1]) * 175) / 65535.0) - 45;
|
||||
_humidity = ((((buffer[3] * 256.0) + buffer[4]) * 100) / 65535.0);
|
||||
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _temperature;
|
||||
if (index == 1) return _humidity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
double _temperature = 0;
|
||||
unsigned char _humidity = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && SHT3X_I2C_SUPPORT
|
168
espurna/sensors/SI7021Sensor.h
Executable file
168
espurna/sensors/SI7021Sensor.h
Executable file
@ -0,0 +1,168 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SI7021 / HTU21D Sensor over I2C
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && SI7021_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2CSensor.h"
|
||||
|
||||
#define SI7021_SCL_FREQUENCY 200
|
||||
|
||||
#define SI7021_CHIP_SI7021 0x15
|
||||
#define SI7021_CHIP_HTU21D 0x32
|
||||
|
||||
#define SI7021_CMD_TMP_HOLD 0xE3
|
||||
#define SI7021_CMD_HUM_HOLD 0xE5
|
||||
#define SI7021_CMD_TMP_NOHOLD 0xF3
|
||||
#define SI7021_CMD_HUM_NOHOLD 0xF5
|
||||
|
||||
PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
|
||||
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
|
||||
|
||||
class SI7021Sensor : public I2CSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
SI7021Sensor(): I2CSensor() {
|
||||
_sensor_id = SENSOR_SI7021_ID;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
if (!_dirty) return;
|
||||
_init();
|
||||
_dirty = !_ready;
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char name[10];
|
||||
strncpy_P(name,
|
||||
_chip == SI7021_CHIP_SI7021 ?
|
||||
si7021_chip_si7021_name :
|
||||
si7021_chip_htu21d_name,
|
||||
sizeof(name)
|
||||
);
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_TEMPERATURE;
|
||||
if (index == 1) return MAGNITUDE_HUMIDITY;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Pre-read hook (usually to populate registers with up-to-date data)
|
||||
void pre() {
|
||||
|
||||
_error = SENSOR_ERROR_UNKNOWN_ID;
|
||||
if (_chip == 0) return;
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
double value;
|
||||
|
||||
value = _read(SI7021_CMD_TMP_NOHOLD);
|
||||
if (_error != SENSOR_ERROR_OK) return;
|
||||
_temperature = (175.72 * value / 65536) - 46.85;
|
||||
|
||||
value = _read(SI7021_CMD_HUM_NOHOLD);
|
||||
if (_error != SENSOR_ERROR_OK) return;
|
||||
value = (125.0 * value / 65536) - 6;
|
||||
_humidity = constrain(value, 0, 100);
|
||||
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _temperature;
|
||||
if (index == 1) return _humidity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _init() {
|
||||
|
||||
// I2C auto-discover
|
||||
unsigned char addresses[] = {0x40};
|
||||
_address = _begin_i2c(_address, sizeof(addresses), addresses);
|
||||
if (_address == 0) return;
|
||||
|
||||
// Check device
|
||||
i2c_write_uint8(_address, 0xFC, 0xC9);
|
||||
_chip = i2c_read_uint8(_address);
|
||||
|
||||
if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) {
|
||||
|
||||
_count = 0;
|
||||
i2cReleaseLock(_address);
|
||||
_previous_address = 0;
|
||||
_error = SENSOR_ERROR_UNKNOWN_ID;
|
||||
|
||||
// Setting _address to 0 forces auto-discover
|
||||
// This might be necessary at this stage if there is a
|
||||
// different sensor in the hardcoded address
|
||||
_address = 0;
|
||||
|
||||
} else {
|
||||
_count = 2;
|
||||
}
|
||||
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
unsigned int _read(uint8_t command) {
|
||||
|
||||
// Request measurement
|
||||
i2c_write_uint8(_address, command);
|
||||
|
||||
// When not using clock stretching (*_NOHOLD commands) delay here
|
||||
// is needed to wait for the measurement.
|
||||
// According to datasheet the max. conversion time is ~22ms
|
||||
unsigned long start = millis();
|
||||
nice_delay(50);
|
||||
|
||||
// Clear the last to bits of LSB to 00.
|
||||
// According to datasheet LSB of RH is always xxxxxx10
|
||||
unsigned int value = i2c_read_uint16(_address) & 0xFFFC;
|
||||
|
||||
// We should be checking there are no pending bytes in the buffer
|
||||
// and raise a CRC error if there are
|
||||
_error = SENSOR_ERROR_OK;
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
unsigned char _chip;
|
||||
double _temperature = 0;
|
||||
double _humidity = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && SI7021_SUPPORT
|
259
espurna/sensors/V9261FSensor.h
Executable file
259
espurna/sensors/V9261FSensor.h
Executable file
@ -0,0 +1,259 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// V9261F based power monitor
|
||||
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if SENSOR_SUPPORT && V9261F_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "BaseSensor.h"
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
class V9261FSensor : public BaseSensor {
|
||||
|
||||
public:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
V9261FSensor(): BaseSensor(), _data() {
|
||||
_count = 6;
|
||||
_sensor_id = SENSOR_V9261F_ID;
|
||||
}
|
||||
|
||||
~V9261FSensor() {
|
||||
if (_serial) delete _serial;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void setRX(unsigned char pin_rx) {
|
||||
if (_pin_rx == pin_rx) return;
|
||||
_pin_rx = pin_rx;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void setInverted(bool inverted) {
|
||||
if (_inverted == inverted) return;
|
||||
_inverted = inverted;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned char getRX() {
|
||||
return _pin_rx;
|
||||
}
|
||||
|
||||
bool getInverted() {
|
||||
return _inverted;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sensor API
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Initialization method, must be idempotent
|
||||
void begin() {
|
||||
|
||||
if (!_dirty) return;
|
||||
|
||||
if (_serial) delete _serial;
|
||||
|
||||
_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
|
||||
_serial->enableIntTx(false);
|
||||
_serial->begin(V9261F_BAUDRATE);
|
||||
|
||||
_ready = true;
|
||||
_dirty = false;
|
||||
|
||||
}
|
||||
|
||||
// Descriptive name of the sensor
|
||||
String description() {
|
||||
char buffer[28];
|
||||
snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
// Descriptive name of the slot # index
|
||||
String slot(unsigned char index) {
|
||||
return description();
|
||||
};
|
||||
|
||||
// Address of the sensor (it could be the GPIO or I2C address)
|
||||
String address(unsigned char index) {
|
||||
return String(_pin_rx);
|
||||
}
|
||||
|
||||
// Loop-like method, call it in your main loop
|
||||
void tick() {
|
||||
_read();
|
||||
}
|
||||
|
||||
// Type for slot # index
|
||||
unsigned char type(unsigned char index) {
|
||||
if (index == 0) return MAGNITUDE_CURRENT;
|
||||
if (index == 1) return MAGNITUDE_VOLTAGE;
|
||||
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
|
||||
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
|
||||
if (index == 4) return MAGNITUDE_POWER_APPARENT;
|
||||
if (index == 5) return MAGNITUDE_POWER_FACTOR;
|
||||
return MAGNITUDE_NONE;
|
||||
}
|
||||
|
||||
// Current value for slot # index
|
||||
double value(unsigned char index) {
|
||||
if (index == 0) return _current;
|
||||
if (index == 1) return _voltage;
|
||||
if (index == 2) return _active;
|
||||
if (index == 3) return _reactive;
|
||||
if (index == 4) return _apparent;
|
||||
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
void _read() {
|
||||
|
||||
static unsigned char state = 0;
|
||||
static unsigned long last = 0;
|
||||
static bool found = false;
|
||||
static unsigned char index = 0;
|
||||
|
||||
if (state == 0) {
|
||||
|
||||
while (_serial->available()) {
|
||||
_serial->flush();
|
||||
found = true;
|
||||
last = millis();
|
||||
}
|
||||
|
||||
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
|
||||
_serial->flush();
|
||||
index = 0;
|
||||
state = 1;
|
||||
}
|
||||
|
||||
} else if (state == 1) {
|
||||
|
||||
while (_serial->available()) {
|
||||
_serial->read();
|
||||
if (index++ >= 7) {
|
||||
_serial->flush();
|
||||
index = 0;
|
||||
state = 2;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state == 2) {
|
||||
|
||||
while (_serial->available()) {
|
||||
_data[index] = _serial->read();
|
||||
if (index++ >= 19) {
|
||||
_serial->flush();
|
||||
last = millis();
|
||||
state = 3;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state == 3) {
|
||||
|
||||
if (_checksum()) {
|
||||
|
||||
_active = (double) (
|
||||
(_data[3]) +
|
||||
(_data[4] << 8) +
|
||||
(_data[5] << 16) +
|
||||
(_data[6] << 24)
|
||||
) / _ratioP;
|
||||
|
||||
_reactive = (double) (
|
||||
(_data[7]) +
|
||||
(_data[8] << 8) +
|
||||
(_data[9] << 16) +
|
||||
(_data[10] << 24)
|
||||
) / _ratioR;
|
||||
|
||||
_voltage = (double) (
|
||||
(_data[11]) +
|
||||
(_data[12] << 8) +
|
||||
(_data[13] << 16) +
|
||||
(_data[14] << 24)
|
||||
) / _ratioV;
|
||||
|
||||
_current = (double) (
|
||||
(_data[15]) +
|
||||
(_data[16] << 8) +
|
||||
(_data[17] << 16) +
|
||||
(_data[18] << 24)
|
||||
) / _ratioC;
|
||||
|
||||
if (_active < 0) _active = 0;
|
||||
if (_reactive < 0) _reactive = 0;
|
||||
if (_voltage < 0) _voltage = 0;
|
||||
if (_current < 0) _current = 0;
|
||||
|
||||
_apparent = sqrt(_reactive * _reactive + _active * _active);
|
||||
|
||||
}
|
||||
|
||||
last = millis();
|
||||
index = 0;
|
||||
state = 4;
|
||||
|
||||
} else if (state == 4) {
|
||||
|
||||
while (_serial->available()) {
|
||||
_serial->flush();
|
||||
last = millis();
|
||||
}
|
||||
|
||||
if (millis() - last > V9261F_SYNC_INTERVAL) {
|
||||
state = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool _checksum() {
|
||||
unsigned char checksum = 0;
|
||||
for (unsigned char i = 0; i < 19; i++) {
|
||||
checksum = checksum + _data[i];
|
||||
}
|
||||
checksum = ~checksum + 0x33;
|
||||
return checksum == _data[19];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
unsigned int _pin_rx = V9261F_PIN;
|
||||
bool _inverted = V9261F_PIN_INVERSE;
|
||||
SoftwareSerial * _serial = NULL;
|
||||
|
||||
double _active = 0;
|
||||
double _reactive = 0;
|
||||
double _voltage = 0;
|
||||
double _current = 0;
|
||||
double _apparent = 0;
|
||||
|
||||
double _ratioP = V9261F_POWER_FACTOR;
|
||||
double _ratioC = V9261F_CURRENT_FACTOR;
|
||||
double _ratioV = V9261F_VOLTAGE_FACTOR;
|
||||
double _ratioR = V9261F_RPOWER_FACTOR;
|
||||
|
||||
unsigned char _data[24];
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSOR_SUPPORT && V9261F_SUPPORT
|
Reference in New Issue
Block a user