10static constexpr uint32_t UNLOCK_ID = 0;
12static constexpr uint32_t COMMAND_GAP_MS = 250;
14#ifdef USE_EMONTX_VERSION
15static constexpr uint32_t VERSION_REQUEST_ID = 1;
17static constexpr uint8_t VERSION_RETRY_FRAMES = 2;
18static constexpr uint8_t VERSION_MAX_ATTEMPTS = 3;
20static const char VERSION_PREFIX[] PROGMEM =
" - Version:";
22static const char MSG_JSON_PREFIX[] PROGMEM =
"{\"MSG\":";
23static const char MSG_TEXT_PREFIX[] PROGMEM =
"MSG:";
25static constexpr uint32_t LOCK_MIN_MAJOR = 1;
26static constexpr uint32_t LOCK_MIN_MINOR = 2;
29static bool starts_with(
const char *line,
size_t len,
const char *prefix,
size_t prefix_len) {
30 return len > prefix_len && ESPHOME_strncmp_P(line, prefix, prefix_len) == 0;
34static bool read_number(
const char *&
pos,
const char *
end,
uint32_t &value) {
35 const char *start =
pos;
37 while (
pos <
end &&
pos - start < 9 && *pos >=
'0' && *
pos <=
'9')
38 value = value * 10 + (*
pos++ -
'0');
43static bool supports_lock(
const char *version,
const char *
end) {
44 if (version <
end && (*version ==
'v' || *version ==
'V'))
47 if (!read_number(version,
end, major) || version ==
end || *version !=
'.')
51 if (!read_number(version,
end, minor))
53 return major > LOCK_MIN_MAJOR || (major == LOCK_MIN_MAJOR && minor >= LOCK_MIN_MINOR);
61 this->
set_timeout(UNLOCK_ID, COMMAND_GAP_MS, [
this]() {
63#ifdef USE_EMONTX_VERSION
70#ifdef USE_EMONTX_VERSION
78 ESP_LOGI(TAG,
"Unlocking emonTx");
79 this->send_command(
"emonunlock");
82void EmonTx::on_shutdown() {
84#ifdef USE_EMONTX_VERSION
89 if (this->is_paused()) {
90 ESP_LOGW(TAG,
"Not locking emonTx before shutdown, UART is paused");
93 ESP_LOGI(TAG,
"Locking emonTx before shutdown");
94 this->send_command(
"emonlock");
106 if (this->is_paused()) {
114 this->
read_available([&dropped](uint8_t
byte) { dropped = byte; });
115 if (dropped ==
'\n') {
120#ifdef USE_EMONTX_VERSION
132 if (line ==
nullptr ||
len == 0) {
135 ESP_LOGD(TAG,
"Received line: %s", line);
137 if (line[0] ==
'{') {
138 ESP_LOGV(TAG,
"Line is JSON, parsing...");
141#ifdef USE_EMONTX_VERSION
151 bool success =
json::parse_json(
reinterpret_cast<const uint8_t *
>(data),
len, [
this, data,
len](JsonObject root) {
153 for (
auto &sensor_pair : this->
sensors_) {
154 auto val = root[sensor_pair.first];
155 if (
val.is<JsonVariant>()) {
157 ESP_LOGV(TAG,
"Updating sensor '%s' with value: %.2f", sensor_pair.first, value);
158 sensor_pair.second->publish_state(value);
168 ESP_LOGW(TAG,
"Failed to parse JSON");
172#ifdef USE_EMONTX_VERSION
175 this->lock_supported_ =
false;
182 if (this->is_paused())
185 this->
set_timeout(VERSION_REQUEST_ID, COMMAND_GAP_MS, [
this]() { this->send_command(
"v"); });
190 if (starts_with(line,
len, MSG_JSON_PREFIX,
sizeof(MSG_JSON_PREFIX) - 1)) {
191 pos = line +
sizeof(MSG_JSON_PREFIX) - 1;
192 }
else if (starts_with(line,
len, MSG_TEXT_PREFIX,
sizeof(MSG_TEXT_PREFIX) - 1)) {
193 pos = line +
sizeof(MSG_TEXT_PREFIX) - 1;
198 if (!read_number(
pos, line +
len, msg_number))
206 ESP_LOGW(TAG,
"No firmware version from the emonTx, it may still be locked");
211 this->last_msg_number_ = msg_number;
215 constexpr size_t prefix_len =
sizeof(VERSION_PREFIX) - 1;
216 if (!starts_with(line,
len, VERSION_PREFIX, prefix_len))
218 const char *version = line + prefix_len;
219 const char *
end = line +
len;
220 while (version <
end && *version ==
' ')
224 ESP_LOGI(TAG,
"emonTx firmware version: %.*s",
static_cast<int>(
end - version), version);
226#ifdef USE_EMONTX_VERSION_TEXT_SENSOR
227 if (this->version_text_sensor_ !=
nullptr)
228 this->version_text_sensor_->publish_state(version,
end - version);
231 bool supported = supports_lock(version,
end);
234 this->
set_timeout(UNLOCK_ID, COMMAND_GAP_MS, [
this]() {
235 if (this->is_paused())
241 this->lock_supported_ = supported;
248void EmonTx::dump_config() {
249 ESP_LOGCONFIG(TAG,
"EmonTx:");
250 const char *
lock = LOG_STR_LITERAL(
"no");
252 lock = LOG_STR_LITERAL(
"yes");
254 lock = LOG_STR_LITERAL(
"auto");
256 ESP_LOGCONFIG(TAG,
" Lock on reboot: %s", lock);
259 ESP_LOGCONFIG(TAG,
" Registered sensors: %zu", this->
sensors_.size());
260 for (
const auto &sensor_pair : this->
sensors_) {
261 ESP_LOGCONFIG(TAG,
" Sensor: %s", sensor_pair.first);
264 ESP_LOGCONFIG(TAG,
" Sensor support: DISABLED");
266#ifdef USE_EMONTX_VERSION_TEXT_SENSOR
267 LOG_TEXT_SENSOR(
" ",
"Firmware version", this->version_text_sensor_);
276void EmonTx::send_command(
const char *command) {
277 if (this->is_paused()) {
278 ESP_LOGW(TAG,
"Not sending command, UART is paused: %s", command);
281 ESP_LOGD(TAG,
"Sending command to emonTx: %s", command);
293void EmonTx::register_sensor(
const char *tag_name, sensor::Sensor *sensor) {
294 ESP_LOGCONFIG(TAG,
"Registering sensor for tag: %s", tag_name);
295 this->
sensors_.emplace_back(tag_name, sensor);
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
char * feed(uint8_t c)
Feed one byte; returns the completed line (empty lines included), valid until the next call,...
void reset()
Forget the partial line and start a new one with the next byte.
void log_if_overflowed(const char *tag) const
Log the shared "line dropped" warning when the last feed() overflowed.
void discard_line()
Drop everything up to and including the next terminator.
StringRef is a reference to a string owned by something else.
FixedVector< std::pair< const char *, sensor::Sensor * > > sensors_
LineReader< MAX_LINE_LENGTH+1 > line_reader_
void send_version_request_()
LockOnReboot lock_on_reboot_
bool wants_version_() const
void parse_msg_number_(const char *line, size_t len)
LazyCallbackManager< void(JsonObject, StringRef)> json_callbacks_
uint32_t last_msg_number_
LazyCallbackManager< void(StringRef)> data_callbacks_
uint8_t version_frames_left_
void parse_json_(const char *data, size_t len)
void parse_version_line_(const char *line, size_t len)
void write_str(const char *str)
void write_byte(uint8_t data)
bool read_available(F &&on_byte)
Same as read_available_chunks(), calling on_byte(uint8_t) once per byte.
ESPHOME_LOG_TAG(TAG, "emontx")
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.