mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
components: teleinfo: electrical counter information. (#1108)
Signed-off-by: 0hax <0hax@protonmail.com> Co-authored-by: Otto Winter <otto@otto-winter.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
f2d218e5ad
commit
5a2b14cfa4
6 changed files with 290 additions and 0 deletions
|
@ -61,6 +61,7 @@ esphome/components/substitutions/* @esphome/core
|
|||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
|
|
1
esphome/components/teleinfo/__init__.py
Normal file
1
esphome/components/teleinfo/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ['@0hax']
|
34
esphome/components/teleinfo/sensor.py
Normal file
34
esphome/components/teleinfo/sensor.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import CONF_ID, CONF_SENSOR, ICON_FLASH, UNIT_WATT_HOURS
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
teleinfo_ns = cg.esphome_ns.namespace('teleinfo')
|
||||
TeleInfo = teleinfo_ns.class_('TeleInfo', cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONF_TAG_NAME = "tag_name"
|
||||
TELEINFO_TAG_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_TAG_NAME): cv.string,
|
||||
cv.Required(CONF_SENSOR): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 0)
|
||||
})
|
||||
|
||||
CONF_TAGS = "tags"
|
||||
CONF_HISTORICAL_MODE = "historical_mode"
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(TeleInfo),
|
||||
cv.Optional(CONF_HISTORICAL_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TAGS): cv.ensure_list(TELEINFO_TAG_SCHEMA),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_HISTORICAL_MODE])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_TAGS in config:
|
||||
for tag in config[CONF_TAGS]:
|
||||
sens = yield sensor.new_sensor(tag[CONF_SENSOR])
|
||||
cg.add(var.register_teleinfo_sensor(tag[CONF_TAG_NAME], sens))
|
184
esphome/components/teleinfo/teleinfo.cpp
Normal file
184
esphome/components/teleinfo/teleinfo.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#include "teleinfo.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace teleinfo {
|
||||
|
||||
static const char *TAG = "teleinfo";
|
||||
|
||||
/* Helpers */
|
||||
static int get_field(char *dest, char *buf_start, char *buf_end, int sep) {
|
||||
char *field_end;
|
||||
int len;
|
||||
|
||||
field_end = static_cast<char *>(memchr(buf_start, sep, buf_end - buf_start));
|
||||
if (!field_end)
|
||||
return 0;
|
||||
len = field_end - buf_start;
|
||||
strncpy(dest, buf_start, len);
|
||||
dest[len] = '\0';
|
||||
|
||||
return len;
|
||||
}
|
||||
/* TeleInfo methods */
|
||||
bool TeleInfo::check_crc_(const char *grp, const char *grp_end) {
|
||||
int grp_len = grp_end - grp;
|
||||
uint8_t raw_crc = grp[grp_len - 1];
|
||||
uint8_t crc_tmp = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < grp_len - checksum_area_end_; i++)
|
||||
crc_tmp += grp[i];
|
||||
|
||||
crc_tmp &= 0x3F;
|
||||
crc_tmp += 0x20;
|
||||
if (raw_crc != crc_tmp) {
|
||||
ESP_LOGE(TAG, "bad crc: got %d except %d", raw_crc, crc_tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool TeleInfo::read_chars_until_(bool drop, uint8_t c) {
|
||||
uint8_t received;
|
||||
int j = 0;
|
||||
|
||||
while (available() > 0 && j < 128) {
|
||||
j++;
|
||||
received = read();
|
||||
if (received == c)
|
||||
return true;
|
||||
if (drop)
|
||||
continue;
|
||||
/*
|
||||
* Internal buffer is full, switch to OFF mode.
|
||||
* Data will be retrieved on next update.
|
||||
*/
|
||||
if (buf_index_ >= (MAX_BUF_SIZE - 1)) {
|
||||
ESP_LOGW(TAG, "Internal buffer full");
|
||||
state_ = OFF;
|
||||
return false;
|
||||
}
|
||||
buf_[buf_index_++] = received;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
void TeleInfo::setup() { state_ = OFF; }
|
||||
void TeleInfo::update() {
|
||||
if (state_ == OFF) {
|
||||
buf_index_ = 0;
|
||||
state_ = ON;
|
||||
}
|
||||
}
|
||||
void TeleInfo::loop() {
|
||||
switch (state_) {
|
||||
case OFF:
|
||||
break;
|
||||
case ON:
|
||||
/* Dequeue chars until start frame (0x2) */
|
||||
if (read_chars_until_(true, 0x2))
|
||||
state_ = START_FRAME_RECEIVED;
|
||||
break;
|
||||
case START_FRAME_RECEIVED:
|
||||
/* Dequeue chars until end frame (0x3) */
|
||||
if (read_chars_until_(false, 0x3))
|
||||
state_ = END_FRAME_RECEIVED;
|
||||
break;
|
||||
case END_FRAME_RECEIVED:
|
||||
char *buf_finger;
|
||||
char *grp_end;
|
||||
char *buf_end;
|
||||
int field_len;
|
||||
|
||||
buf_finger = buf_;
|
||||
buf_end = buf_ + buf_index_;
|
||||
|
||||
/* Each frame is composed of multiple groups starting by 0xa(Line Feed) and ending by
|
||||
* 0xd ('\r').
|
||||
*
|
||||
* Historical mode: each group contains tag, data and a CRC separated by 0x20 (Space)
|
||||
* 0xa | Tag | 0x20 | Data | 0x20 | CRC | 0xd
|
||||
* ^^^^^^^^^^^^^^^^^^^^
|
||||
* Checksum is computed on the above in historical mode.
|
||||
*
|
||||
* Standard mode: each group contains tag, data and a CRC separated by 0x9 (\t)
|
||||
* 0xa | Tag | 0x9 | Data | 0x9 | CRC | 0xd
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* Checksum is computed on the above in standard mode.
|
||||
*/
|
||||
while ((buf_finger = static_cast<char *>(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) &&
|
||||
((buf_finger - buf_) < buf_index_)) {
|
||||
/* Point to the first char of the group after 0xa */
|
||||
buf_finger += 1;
|
||||
|
||||
/* Group len */
|
||||
grp_end = static_cast<char *>(memchr(buf_finger, 0xd, buf_end - buf_finger));
|
||||
if (!grp_end) {
|
||||
ESP_LOGE(TAG, "No group found");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!check_crc_(buf_finger, grp_end))
|
||||
break;
|
||||
|
||||
/* Get tag */
|
||||
field_len = get_field(tag_, buf_finger, grp_end, separator_);
|
||||
if (!field_len || field_len >= MAX_TAG_SIZE) {
|
||||
ESP_LOGE(TAG, "Invalid tag.");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance buf_finger to after the tag and the separator. */
|
||||
buf_finger += field_len + 1;
|
||||
|
||||
/* Get value (after next separator) */
|
||||
field_len = get_field(val_, buf_finger, grp_end, separator_);
|
||||
if (!field_len || field_len >= MAX_VAL_SIZE) {
|
||||
ESP_LOGE(TAG, "Invalid Value");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance buf_finger to end of group */
|
||||
buf_finger += field_len + 1 + 1 + 1;
|
||||
|
||||
publish_value_(std::string(tag_), std::string(val_));
|
||||
}
|
||||
state_ = OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void TeleInfo::publish_value_(std::string tag, std::string val) {
|
||||
/* It will return 0 if tag is not a float. */
|
||||
auto newval = parse_float(val);
|
||||
for (auto element : teleinfo_sensors_)
|
||||
if (tag == element->tag)
|
||||
element->sensor->publish_state(*newval);
|
||||
}
|
||||
void TeleInfo::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "TeleInfo:");
|
||||
for (auto element : teleinfo_sensors_)
|
||||
LOG_SENSOR(" ", element->tag, element->sensor);
|
||||
this->check_uart_settings(baud_rate_, 1, uart::UART_CONFIG_PARITY_EVEN, 7);
|
||||
}
|
||||
TeleInfo::TeleInfo(bool historical_mode) {
|
||||
if (historical_mode) {
|
||||
/*
|
||||
* Historical mode doesn't contain last separator between checksum and data.
|
||||
*/
|
||||
checksum_area_end_ = 2;
|
||||
separator_ = 0x20;
|
||||
baud_rate_ = 1200;
|
||||
} else {
|
||||
checksum_area_end_ = 1;
|
||||
separator_ = 0x9;
|
||||
baud_rate_ = 9600;
|
||||
}
|
||||
}
|
||||
void TeleInfo::register_teleinfo_sensor(const char *tag, sensor::Sensor *sensor) {
|
||||
const TeleinfoSensorElement *teleinfo_sensor = new TeleinfoSensorElement{tag, sensor};
|
||||
teleinfo_sensors_.push_back(teleinfo_sensor);
|
||||
}
|
||||
|
||||
} // namespace teleinfo
|
||||
} // namespace esphome
|
51
esphome/components/teleinfo/teleinfo.h
Normal file
51
esphome/components/teleinfo/teleinfo.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace teleinfo {
|
||||
/*
|
||||
* 198 bytes should be enough to contain a full session in historical mode with
|
||||
* three phases. But go with 1024 just to be sure.
|
||||
*/
|
||||
static const uint8_t MAX_TAG_SIZE = 64;
|
||||
static const uint16_t MAX_VAL_SIZE = 256;
|
||||
static const uint16_t MAX_BUF_SIZE = 1024;
|
||||
|
||||
struct TeleinfoSensorElement {
|
||||
const char *tag;
|
||||
sensor::Sensor *sensor;
|
||||
};
|
||||
|
||||
class TeleInfo : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
TeleInfo(bool historical_mode);
|
||||
void register_teleinfo_sensor(const char *tag, sensor::Sensor *sensors);
|
||||
void loop() override;
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
std::vector<const TeleinfoSensorElement *> teleinfo_sensors_{};
|
||||
|
||||
protected:
|
||||
uint32_t baud_rate_;
|
||||
int checksum_area_end_;
|
||||
int separator_;
|
||||
char buf_[MAX_BUF_SIZE];
|
||||
uint32_t buf_index_{0};
|
||||
char tag_[MAX_TAG_SIZE];
|
||||
char val_[MAX_VAL_SIZE];
|
||||
enum State {
|
||||
OFF,
|
||||
ON,
|
||||
START_FRAME_RECEIVED,
|
||||
END_FRAME_RECEIVED,
|
||||
} state_{OFF};
|
||||
bool read_chars_until_(bool drop, uint8_t c);
|
||||
bool check_crc_(const char *grp, const char *grp_end);
|
||||
void publish_value_(std::string tag, std::string val);
|
||||
};
|
||||
} // namespace teleinfo
|
||||
} // namespace esphome
|
|
@ -762,6 +762,25 @@ sensor:
|
|||
aqi:
|
||||
name: "AQI"
|
||||
calculation_type: "CAQI"
|
||||
- platform: teleinfo
|
||||
tags:
|
||||
- tag_name: "HCHC"
|
||||
sensor:
|
||||
name: "hchc"
|
||||
unit_of_measurement: "Wh"
|
||||
icon: mdi:flash
|
||||
- tag_name: "HCHP"
|
||||
sensor:
|
||||
name: "hchp"
|
||||
unit_of_measurement: "Wh"
|
||||
icon: mdi:flash
|
||||
- tag_name: "PAPP"
|
||||
sensor:
|
||||
name: "papp"
|
||||
unit_of_measurement: "VA"
|
||||
icon: mdi:flash
|
||||
update_interval: 60s
|
||||
historical_mode: true
|
||||
- platform: mcp9808
|
||||
name: "MCP9808 Temperature"
|
||||
update_interval: 15s
|
||||
|
|
Loading…
Reference in a new issue