|
|
@ -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 |