mirror of
https://github.com/esphome/esphome.git
synced 2025-11-07 18:41:53 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b4a166ec | ||
|
|
c27fd0f01a | ||
|
|
dcb4a0a81e | ||
|
|
17da9fddc3 | ||
|
|
31aa3c55ca | ||
|
|
eca3685ea0 | ||
|
|
bd216c5c63 | ||
|
|
31ff76427c | ||
|
|
2229aa6ccc | ||
|
|
872b468415 | ||
|
|
9f022a7433 | ||
|
|
85a958e300 | ||
|
|
0ee56195ae | ||
|
|
48f52db1d9 | ||
|
|
d2c7afeef0 | ||
|
|
644aec791e | ||
|
|
b70a0325c5 | ||
|
|
268387f829 | ||
|
|
b975caef1e | ||
|
|
54fe1c7d55 | ||
|
|
89c1274d56 | ||
|
|
f9ca3f1c27 | ||
|
|
26dbc30279 | ||
|
|
4bee316425 |
@@ -3,7 +3,7 @@
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
BASE_VERSION: '2.0.0'
|
||||
BASE_VERSION: '2.0.1'
|
||||
TZ: UTC
|
||||
|
||||
stages:
|
||||
|
||||
@@ -3,3 +3,4 @@ include README.md
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.0
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.1
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM esphome/esphome-base-amd64:2.0.0
|
||||
FROM esphome/esphome-base-amd64:2.0.1
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
@@ -14,5 +14,7 @@ RUN \
|
||||
COPY requirements_test.txt /requirements_test.txt
|
||||
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
|
||||
|
||||
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
||||
@@ -509,6 +509,11 @@ def run_esphome(argv):
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
|
||||
if IS_PY2:
|
||||
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated "
|
||||
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
|
||||
"or higher.")
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
|
||||
@@ -406,6 +406,7 @@ message ListEntitiesSensorResponse {
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
|
||||
@@ -1359,6 +1359,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->accuracy_decimals = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->force_update = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1407,6 +1411,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_string(6, this->unit_of_measurement);
|
||||
buffer.encode_int32(7, this->accuracy_decimals);
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
}
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -1440,6 +1445,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
sprintf(buffer, "%d", this->accuracy_decimals);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" force_update: ");
|
||||
out.append(YESNO(this->force_update));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool SensorStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
|
||||
@@ -364,6 +364,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ void CSE7766Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
this->check_uart_settings(4800);
|
||||
}
|
||||
|
||||
} // namespace cse7766
|
||||
|
||||
221
esphome/components/dfplayer/__init__.py
Normal file
221
esphome/components/dfplayer/__init__.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
dfplayer_ns = cg.esphome_ns.namespace('dfplayer')
|
||||
DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component)
|
||||
DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_('DFPlayerFinishedPlaybackTrigger',
|
||||
automation.Trigger.template())
|
||||
DFPlayerIsPlayingCondition = dfplayer_ns.class_('DFPlayerIsPlayingCondition', automation.Condition)
|
||||
|
||||
MULTI_CONF = True
|
||||
CONF_FOLDER = 'folder'
|
||||
CONF_FILE = 'file'
|
||||
CONF_LOOP = 'loop'
|
||||
CONF_VOLUME = 'volume'
|
||||
CONF_DEVICE = 'device'
|
||||
CONF_EQ_PRESET = 'eq_preset'
|
||||
CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback'
|
||||
|
||||
EqPreset = dfplayer_ns.enum("EqPreset")
|
||||
EQ_PRESET = {
|
||||
'NORMAL': EqPreset.NORMAL,
|
||||
'POP': EqPreset.POP,
|
||||
'ROCK': EqPreset.ROCK,
|
||||
'JAZZ': EqPreset.JAZZ,
|
||||
'CLASSIC': EqPreset.CLASSIC,
|
||||
'BASS': EqPreset.BASS,
|
||||
}
|
||||
Device = dfplayer_ns.enum("Device")
|
||||
DEVICE = {
|
||||
'USB': Device.USB,
|
||||
'TF_CARD': Device.TF_CARD,
|
||||
}
|
||||
|
||||
NextAction = dfplayer_ns.class_('NextAction', automation.Action)
|
||||
PreviousAction = dfplayer_ns.class_('PreviousAction', automation.Action)
|
||||
PlayFileAction = dfplayer_ns.class_('PlayFileAction', automation.Action)
|
||||
PlayFolderAction = dfplayer_ns.class_('PlayFolderAction', automation.Action)
|
||||
SetVolumeAction = dfplayer_ns.class_('SetVolumeAction', automation.Action)
|
||||
SetEqAction = dfplayer_ns.class_('SetEqAction', automation.Action)
|
||||
SleepAction = dfplayer_ns.class_('SleepAction', automation.Action)
|
||||
ResetAction = dfplayer_ns.class_('ResetAction', automation.Action)
|
||||
StartAction = dfplayer_ns.class_('StartAction', automation.Action)
|
||||
PauseAction = dfplayer_ns.class_('PauseAction', automation.Action)
|
||||
StopAction = dfplayer_ns.class_('StopAction', automation.Action)
|
||||
RandomAction = dfplayer_ns.class_('RandomAction', automation.Action)
|
||||
SetDeviceAction = dfplayer_ns.class_('SetDeviceAction', automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(DFPlayer),
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DFPlayerFinishedPlaybackTrigger),
|
||||
}),
|
||||
}).extend(uart.UART_DEVICE_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.play_next', NextAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_next_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.play_previous', PreviousAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.play', PlayFileAction, cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
}, key=CONF_FILE))
|
||||
def dfplayer_play_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
template_ = yield cg.templatable(config[CONF_FILE], args, float)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = yield cg.templatable(config[CONF_LOOP], args, float)
|
||||
cg.add(var.set_loop(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.play_folder', PlayFolderAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FOLDER): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_FILE): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
|
||||
}))
|
||||
def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
template_ = yield cg.templatable(config[CONF_FOLDER], args, float)
|
||||
cg.add(var.set_folder(template_))
|
||||
if CONF_FILE in config:
|
||||
template_ = yield cg.templatable(config[CONF_FILE], args, float)
|
||||
cg.add(var.set_file(template_))
|
||||
if CONF_LOOP in config:
|
||||
template_ = yield cg.templatable(config[CONF_LOOP], args, float)
|
||||
cg.add(var.set_loop(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.set_device', SetDeviceAction, cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True),
|
||||
}, key=CONF_DEVICE))
|
||||
def dfplayer_set_device_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
template_ = yield cg.templatable(config[CONF_DEVICE], args, Device)
|
||||
cg.add(var.set_device(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.set_volume', SetVolumeAction, cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_VOLUME): cv.templatable(cv.int_),
|
||||
}, key=CONF_VOLUME))
|
||||
def dfplayer_set_volume_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
template_ = yield cg.templatable(config[CONF_VOLUME], args, float)
|
||||
cg.add(var.set_volume(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.set_eq', SetEqAction, cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)),
|
||||
}, key=CONF_EQ_PRESET))
|
||||
def dfplayer_set_eq_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
template_ = yield cg.templatable(config[CONF_EQ_PRESET], args, EqPreset)
|
||||
cg.add(var.set_eq(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.sleep', SleepAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_sleep_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.reset', ResetAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_reset_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.start', StartAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_start_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.pause', PauseAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_pause_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.stop', StopAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_stop_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_action('dfplayer.random', RandomAction, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplayer_random_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition('dfplayer.is_playing', DFPlayerIsPlayingCondition, cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}))
|
||||
def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args):
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_ID])
|
||||
yield var
|
||||
120
esphome/components/dfplayer/dfplayer.cpp
Normal file
120
esphome/components/dfplayer/dfplayer.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "dfplayer.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfplayer {
|
||||
|
||||
static const char* TAG = "dfplayer";
|
||||
|
||||
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
|
||||
if (folder < 100 && file < 256) {
|
||||
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
|
||||
} else if (folder <= 10 && file <= 1000) {
|
||||
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Cannot play folder %d file %d.", folder, file);
|
||||
}
|
||||
}
|
||||
|
||||
void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
|
||||
uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t)(argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef};
|
||||
uint16_t checksum = 0;
|
||||
for (uint8_t i = 1; i < 7; i++)
|
||||
checksum += buffer[i];
|
||||
checksum = -checksum;
|
||||
buffer[7] = checksum >> 8;
|
||||
buffer[8] = (uint8_t) checksum;
|
||||
|
||||
this->sent_cmd_ = cmd;
|
||||
|
||||
ESP_LOGD(TAG, "Send Command %#02x arg %#04x", cmd, argument);
|
||||
this->write_array(buffer, 10);
|
||||
}
|
||||
|
||||
void DFPlayer::loop() {
|
||||
// Read message
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
this->read_byte(&byte);
|
||||
|
||||
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
|
||||
this->read_pos_ = 0;
|
||||
|
||||
switch (this->read_pos_) {
|
||||
case 0: // Start mark
|
||||
if (byte != 0x7E)
|
||||
continue;
|
||||
break;
|
||||
case 1: // Version
|
||||
if (byte != 0xFF) {
|
||||
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 2: // Buffer length
|
||||
if (byte != 0x06) {
|
||||
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 9: // End byte
|
||||
if (byte != 0xEF) {
|
||||
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
// Parse valid received command
|
||||
uint8_t cmd = this->read_buffer_[3];
|
||||
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
|
||||
|
||||
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
|
||||
|
||||
switch (cmd) {
|
||||
case 0x3A:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB loaded");
|
||||
} else if (argument == 2)
|
||||
ESP_LOGI(TAG, "TF Card loaded");
|
||||
break;
|
||||
case 0x3B:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB unloaded");
|
||||
} else if (argument == 2)
|
||||
ESP_LOGI(TAG, "TF Card unloaded");
|
||||
break;
|
||||
case 0x3F:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB available");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card available");
|
||||
} else if (argument == 3) {
|
||||
ESP_LOGI(TAG, "USB, TF Card available");
|
||||
}
|
||||
break;
|
||||
case 0x41:
|
||||
ESP_LOGV(TAG, "Ack ok");
|
||||
this->is_playing_ |= this->ack_set_is_playing_;
|
||||
this->is_playing_ &= !this->ack_reset_is_playing_;
|
||||
this->ack_set_is_playing_ = false;
|
||||
this->ack_reset_is_playing_ = false;
|
||||
break;
|
||||
case 0x3D: // Playback finished
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Command %#02x arg %#04x", cmd, argument);
|
||||
}
|
||||
this->sent_cmd_ = 0;
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
this->read_buffer_[this->read_pos_] = byte;
|
||||
this->read_pos_++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dfplayer
|
||||
} // namespace esphome
|
||||
165
esphome/components/dfplayer/dfplayer.h
Normal file
165
esphome/components/dfplayer/dfplayer.h
Normal file
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
const size_t DFPLAYER_READ_BUFFER_LENGTH = 25; // two messages + some extra
|
||||
|
||||
namespace esphome {
|
||||
namespace dfplayer {
|
||||
|
||||
enum EqPreset {
|
||||
NORMAL = 0,
|
||||
POP = 1,
|
||||
ROCK = 2,
|
||||
JAZZ = 3,
|
||||
CLASSIC = 4,
|
||||
BASS = 5,
|
||||
};
|
||||
|
||||
enum Device {
|
||||
USB = 1,
|
||||
TF_CARD = 2,
|
||||
};
|
||||
|
||||
class DFPlayer : public uart::UARTDevice, public Component {
|
||||
public:
|
||||
void loop() override;
|
||||
|
||||
void next() { this->send_cmd_(0x01); }
|
||||
void previous() { this->send_cmd_(0x02); }
|
||||
void play_file(uint16_t file) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x03, file);
|
||||
}
|
||||
void play_file_loop(uint16_t file) { this->send_cmd_(0x08, file); }
|
||||
void play_folder(uint16_t folder, uint16_t file);
|
||||
void play_folder_loop(uint16_t folder) { this->send_cmd_(0x17, folder); }
|
||||
void volume_up() { this->send_cmd_(0x04); }
|
||||
void volume_down() { this->send_cmd_(0x05); }
|
||||
void set_device(Device device) { this->send_cmd_(0x09, device); }
|
||||
void set_volume(uint8_t volume) { this->send_cmd_(0x06, volume); }
|
||||
void set_eq(EqPreset preset) { this->send_cmd_(0x07, preset); }
|
||||
void sleep() { this->send_cmd_(0x0A); }
|
||||
void reset() { this->send_cmd_(0x0C); }
|
||||
void start() { this->send_cmd_(0x0D); }
|
||||
void pause() {
|
||||
this->ack_reset_is_playing_ = true;
|
||||
this->send_cmd_(0x0E);
|
||||
}
|
||||
void stop() { this->send_cmd_(0x16); }
|
||||
void random() { this->send_cmd_(0x18); }
|
||||
|
||||
bool is_playing() { return is_playing_; }
|
||||
|
||||
void add_on_finished_playback_callback(std::function<void()> callback) {
|
||||
this->on_finished_playback_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
void send_cmd_(uint8_t cmd, uint16_t argument = 0);
|
||||
void send_cmd_(uint8_t cmd, uint16_t high, uint16_t low) {
|
||||
this->send_cmd_(cmd, ((high & 0xFF) << 8) | (low & 0xFF));
|
||||
}
|
||||
uint8_t sent_cmd_{0};
|
||||
|
||||
char read_buffer_[DFPLAYER_READ_BUFFER_LENGTH];
|
||||
size_t read_pos_{0};
|
||||
|
||||
bool is_playing_{false};
|
||||
bool ack_set_is_playing_{false};
|
||||
bool ack_reset_is_playing_{false};
|
||||
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
};
|
||||
|
||||
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
||||
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \
|
||||
public: \
|
||||
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
|
||||
|
||||
template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(boolean, loop)
|
||||
void play(Ts... x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_file_loop(file);
|
||||
} else {
|
||||
this->parent_->play_file(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, folder)
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(boolean, loop)
|
||||
void play(Ts... x) override {
|
||||
auto folder = this->folder_.value(x...);
|
||||
auto file = this->file_.value(x...);
|
||||
auto loop = this->loop_.value(x...);
|
||||
if (loop) {
|
||||
this->parent_->play_folder_loop(folder);
|
||||
} else {
|
||||
this->parent_->play_folder(folder, file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Device, device)
|
||||
void play(Ts... x) override {
|
||||
auto device = this->device_.value(x...);
|
||||
this->parent_->set_device(device);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||
void play(Ts... x) override {
|
||||
auto volume = this->volume_.value(x...);
|
||||
this->parent_->set_volume(volume);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||
void play(Ts... x) override {
|
||||
auto eq = this->eq_.value(x...);
|
||||
this->parent_->set_eq(eq);
|
||||
}
|
||||
};
|
||||
|
||||
DFPLAYER_SIMPLE_ACTION(SleepAction, sleep)
|
||||
DFPLAYER_SIMPLE_ACTION(ResetAction, reset)
|
||||
DFPLAYER_SIMPLE_ACTION(StartAction, start)
|
||||
DFPLAYER_SIMPLE_ACTION(PauseAction, pause)
|
||||
DFPLAYER_SIMPLE_ACTION(StopAction, stop)
|
||||
DFPLAYER_SIMPLE_ACTION(RandomAction, random)
|
||||
|
||||
template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_playing(); }
|
||||
};
|
||||
|
||||
class DFPlayerFinishedPlaybackTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) {
|
||||
parent->add_on_finished_playback_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dfplayer
|
||||
} // namespace esphome
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "esp32_ble_tracker.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
@@ -202,20 +203,8 @@ void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_res
|
||||
}
|
||||
}
|
||||
|
||||
std::string hexencode(const std::string &raw_data) {
|
||||
char buf[20];
|
||||
std::string res;
|
||||
for (size_t i = 0; i < raw_data.size(); i++) {
|
||||
if (i + 1 != raw_data.size()) {
|
||||
sprintf(buf, "0x%02X.", static_cast<uint8_t>(raw_data[i]));
|
||||
} else {
|
||||
sprintf(buf, "0x%02X ", static_cast<uint8_t>(raw_data[i]));
|
||||
}
|
||||
res += buf;
|
||||
}
|
||||
sprintf(buf, "(%zu)", raw_data.size());
|
||||
res += buf;
|
||||
return res;
|
||||
std::string hexencode_string(const std::string &raw_data) {
|
||||
return hexencode(reinterpret_cast<const uint8_t *>(raw_data.c_str()), raw_data.size());
|
||||
}
|
||||
|
||||
ESPBTUUID::ESPBTUUID() : uuid_() {}
|
||||
@@ -327,15 +316,15 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||
for (auto uuid : this->service_uuids_) {
|
||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
||||
}
|
||||
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(this->manufacturer_data_).c_str());
|
||||
ESP_LOGVV(TAG, " Service data: %s", hexencode(this->service_data_).c_str());
|
||||
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode_string(this->manufacturer_data_).c_str());
|
||||
ESP_LOGVV(TAG, " Service data: %s", hexencode_string(this->service_data_).c_str());
|
||||
|
||||
if (this->service_data_uuid_.has_value()) {
|
||||
ESP_LOGVV(TAG, " Service Data UUID: %s", this->service_data_uuid_->to_string().c_str());
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Adv data: %s",
|
||||
hexencode(std::string(reinterpret_cast<const char *>(param.ble_adv), param.adv_data_len)).c_str());
|
||||
hexencode_string(std::string(reinterpret_cast<const char *>(param.ble_adv), param.adv_data_len)).c_str());
|
||||
#endif
|
||||
}
|
||||
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
|
||||
@@ -15,12 +15,14 @@ RESTORE_MODES = {
|
||||
'ALWAYS_ON': GPIOSwitchRestoreMode.GPIO_SWITCH_ALWAYS_ON,
|
||||
}
|
||||
|
||||
CONF_INTERLOCK_WAIT_TIME = 'interlock_wait_time'
|
||||
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(GPIOSwitch),
|
||||
cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_RESTORE_MODE, default='RESTORE_DEFAULT_OFF'):
|
||||
cv.enum(RESTORE_MODES, upper=True, space='_'),
|
||||
cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)),
|
||||
cv.Optional(CONF_INTERLOCK_WAIT_TIME, default='0ms'): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@@ -40,3 +42,4 @@ def to_code(config):
|
||||
lock = yield cg.get_variable(it)
|
||||
interlock.append(lock)
|
||||
cg.add(var.set_interlock(interlock))
|
||||
cg.add(var.set_interlock_wait_time(config[CONF_INTERLOCK_WAIT_TIME]))
|
||||
|
||||
@@ -69,13 +69,29 @@ void GPIOSwitch::dump_config() {
|
||||
void GPIOSwitch::write_state(bool state) {
|
||||
if (state != this->inverted_) {
|
||||
// Turning ON, check interlocking
|
||||
|
||||
bool found = false;
|
||||
for (auto *lock : this->interlock_) {
|
||||
if (lock == this)
|
||||
continue;
|
||||
|
||||
if (lock->state)
|
||||
if (lock->state) {
|
||||
lock->turn_off();
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found && this->interlock_wait_time_ != 0) {
|
||||
this->set_timeout("interlock", this->interlock_wait_time_, [this, state] {
|
||||
// Don't write directly, call the function again
|
||||
// (some other switch may have changed state while we were waiting)
|
||||
this->write_state(state);
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (this->interlock_wait_time_ != 0) {
|
||||
// If we are switched off during the interlock wait time, cancel any pending
|
||||
// re-activations
|
||||
this->cancel_timeout("interlock");
|
||||
}
|
||||
|
||||
this->pin_->digital_write(state);
|
||||
|
||||
@@ -26,6 +26,7 @@ class GPIOSwitch : public switch_::Switch, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_interlock(const std::vector<Switch *> &interlock);
|
||||
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
@@ -33,6 +34,7 @@ class GPIOSwitch : public switch_::Switch, public Component {
|
||||
GPIOPin *pin_;
|
||||
GPIOSwitchRestoreMode restore_mode_{GPIO_SWITCH_RESTORE_DEFAULT_OFF};
|
||||
std::vector<Switch *> interlock_;
|
||||
uint32_t interlock_wait_time_{0};
|
||||
};
|
||||
|
||||
} // namespace gpio
|
||||
|
||||
@@ -208,5 +208,30 @@ void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; }
|
||||
uint8_t next_i2c_bus_num_ = 0;
|
||||
#endif
|
||||
|
||||
I2CRegister &I2CRegister::operator=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
I2CRegister &I2CRegister::operator&=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, this->get() & value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
I2CRegister &I2CRegister::operator|=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, this->get() | value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t I2CRegister::get() {
|
||||
uint8_t value = 0x00;
|
||||
this->parent_->read_byte(this->register_, &value);
|
||||
return value;
|
||||
}
|
||||
I2CRegister &I2CRegister::operator=(const std::vector<uint8_t> &value) {
|
||||
this->parent_->write_bytes(this->register_, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
@@ -134,6 +134,24 @@ class I2CComponent : public Component {
|
||||
extern uint8_t next_i2c_bus_num_;
|
||||
#endif
|
||||
|
||||
class I2CDevice;
|
||||
|
||||
class I2CRegister {
|
||||
public:
|
||||
I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {}
|
||||
|
||||
I2CRegister &operator=(uint8_t value);
|
||||
I2CRegister &operator=(const std::vector<uint8_t> &value);
|
||||
I2CRegister &operator&=(uint8_t value);
|
||||
I2CRegister &operator|=(uint8_t value);
|
||||
|
||||
uint8_t get();
|
||||
|
||||
protected:
|
||||
I2CDevice *parent_;
|
||||
uint8_t register_;
|
||||
};
|
||||
|
||||
/** All components doing communication on the I2C bus should subclass I2CDevice.
|
||||
*
|
||||
* This class stores 1. the address of the i2c device and has a helper function to allow
|
||||
@@ -153,6 +171,8 @@ class I2CDevice {
|
||||
/// Manually set the parent i2c bus for this device.
|
||||
void set_i2c_parent(I2CComponent *parent);
|
||||
|
||||
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
|
||||
|
||||
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
|
||||
* writing the register value to the bus.
|
||||
*
|
||||
|
||||
@@ -148,6 +148,11 @@ void LCDDisplay::printf(const char *format, ...) {
|
||||
if (ret > 0)
|
||||
this->print(0, 0, buffer);
|
||||
}
|
||||
void LCDDisplay::clear() {
|
||||
// clear display, also sets DDRAM address to 0 (home)
|
||||
this->command_(LCD_DISPLAY_COMMAND_CLEAR_DISPLAY);
|
||||
delay(2);
|
||||
}
|
||||
#ifdef USE_TIME
|
||||
void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) {
|
||||
char buffer[64];
|
||||
|
||||
@@ -23,6 +23,8 @@ class LCDDisplay : public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void display();
|
||||
//// Clear LCD display
|
||||
void clear();
|
||||
|
||||
/// Print the given text at the specified column and row.
|
||||
void print(uint8_t column, uint8_t row, const char *str);
|
||||
|
||||
@@ -42,8 +42,8 @@ float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
|
||||
}
|
||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
for (int i = 20; i >= 1; i--) {
|
||||
const float min_frequency = ledc_min_frequency_for_bit_depth(frequency);
|
||||
const float max_frequency = ledc_max_frequency_for_bit_depth(frequency);
|
||||
const float min_frequency = ledc_min_frequency_for_bit_depth(i);
|
||||
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
|
||||
if (min_frequency <= frequency && frequency <= max_frequency)
|
||||
return i;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ void LEDCOutput::apply_frequency(float frequency) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
this->status_set_warning();
|
||||
}
|
||||
this->bit_depth_ = *bit_depth_opt;
|
||||
this->bit_depth_ = bit_depth_opt.value_or(8);
|
||||
this->frequency_ = frequency;
|
||||
ledcSetup(this->channel_, frequency, this->bit_depth_);
|
||||
// re-apply duty
|
||||
|
||||
@@ -94,6 +94,7 @@ void MHZ19Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MH-Z19:");
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
|
||||
if (this->abc_boot_logic_ == MHZ19_ABC_ENABLED) {
|
||||
ESP_LOGCONFIG(TAG, " Automatic baseline calibration enabled on boot");
|
||||
|
||||
43
esphome/components/modbus/__init__.py
Normal file
43
esphome/components/modbus/__init__.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||
from esphome.core import coroutine
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
modbus_ns = cg.esphome_ns.namespace('modbus')
|
||||
Modbus = modbus_ns.class_('Modbus', cg.Component, uart.UARTDevice)
|
||||
ModbusDevice = modbus_ns.class_('ModbusDevice')
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_MODBUS_ID = 'modbus_id'
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(Modbus),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cg.add_global(modbus_ns.using)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
yield uart.register_uart_device(var, config)
|
||||
|
||||
|
||||
def modbus_device_schema(default_address):
|
||||
schema = {
|
||||
cv.GenerateID(CONF_MODBUS_ID): cv.use_id(Modbus),
|
||||
}
|
||||
if default_address is None:
|
||||
schema[cv.Required(CONF_ADDRESS)] = cv.hex_uint8_t
|
||||
else:
|
||||
schema[cv.Optional(CONF_ADDRESS, default=default_address)] = cv.hex_uint8_t
|
||||
return cv.Schema(schema)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_modbus_device(var, config):
|
||||
parent = yield cg.get_variable(config[CONF_MODBUS_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
119
esphome/components/modbus/modbus.cpp
Normal file
119
esphome/components/modbus/modbus.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "modbus.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus {
|
||||
|
||||
static const char *TAG = "modbus";
|
||||
|
||||
void Modbus::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_modbus_byte_ > 50) {
|
||||
this->rx_buffer_.clear();
|
||||
this->last_modbus_byte_ = now;
|
||||
}
|
||||
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
this->read_byte(&byte);
|
||||
if (this->parse_modbus_byte_(byte)) {
|
||||
this->last_modbus_byte_ = now;
|
||||
} else {
|
||||
this->rx_buffer_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t crc16(const uint8_t *data, uint8_t len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
while (len--) {
|
||||
crc ^= *data++;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if ((crc & 0x01) != 0) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
||||
size_t at = this->rx_buffer_.size();
|
||||
this->rx_buffer_.push_back(byte);
|
||||
const uint8_t *raw = &this->rx_buffer_[0];
|
||||
|
||||
// Byte 0: modbus address (match all)
|
||||
if (at == 0)
|
||||
return true;
|
||||
uint8_t address = raw[0];
|
||||
|
||||
// Byte 1: Function (msb indicates error)
|
||||
if (at == 1)
|
||||
return (byte & 0x80) != 0x80;
|
||||
|
||||
// Byte 2: Size (with modbus rtu function code 4/3)
|
||||
// See also https://en.wikipedia.org/wiki/Modbus
|
||||
if (at == 2)
|
||||
return true;
|
||||
|
||||
uint8_t data_len = raw[2];
|
||||
// Byte 3..3+data_len-1: Data
|
||||
if (at < 3 + data_len)
|
||||
return true;
|
||||
|
||||
// Byte 3+data_len: CRC_LO (over all bytes)
|
||||
if (at == 3 + data_len)
|
||||
return true;
|
||||
// Byte 3+len+1: CRC_HI (over all bytes)
|
||||
uint16_t computed_crc = crc16(raw, 3 + data_len);
|
||||
uint16_t remote_crc = uint16_t(raw[3 + data_len]) | (uint16_t(raw[3 + data_len + 1]) << 8);
|
||||
if (computed_crc != remote_crc) {
|
||||
ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data(this->rx_buffer_.begin() + 3, this->rx_buffer_.begin() + 3 + data_len);
|
||||
|
||||
bool found = false;
|
||||
for (auto *device : this->devices_) {
|
||||
if (device->address_ == address) {
|
||||
device->on_modbus_data(data);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X!", address);
|
||||
}
|
||||
|
||||
// return false to reset buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
void Modbus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Modbus:");
|
||||
this->check_uart_settings(9600, 2);
|
||||
}
|
||||
float Modbus::get_setup_priority() const {
|
||||
// After UART bus
|
||||
return setup_priority::BUS - 1.0f;
|
||||
}
|
||||
void Modbus::send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count) {
|
||||
uint8_t frame[8];
|
||||
frame[0] = address;
|
||||
frame[1] = function;
|
||||
frame[2] = start_address >> 8;
|
||||
frame[3] = start_address >> 0;
|
||||
frame[4] = register_count >> 8;
|
||||
frame[5] = register_count >> 0;
|
||||
auto crc = crc16(frame, 6);
|
||||
frame[6] = crc >> 0;
|
||||
frame[7] = crc >> 8;
|
||||
|
||||
this->write_array(frame, 8);
|
||||
}
|
||||
|
||||
} // namespace modbus
|
||||
} // namespace esphome
|
||||
51
esphome/components/modbus/modbus.h
Normal file
51
esphome/components/modbus/modbus.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus {
|
||||
|
||||
class ModbusDevice;
|
||||
|
||||
class Modbus : public uart::UARTDevice, public Component {
|
||||
public:
|
||||
Modbus() = default;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void register_device(ModbusDevice *device) { this->devices_.push_back(device); }
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count);
|
||||
|
||||
protected:
|
||||
bool parse_modbus_byte_(uint8_t byte);
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
uint32_t last_modbus_byte_{0};
|
||||
std::vector<ModbusDevice *> devices_;
|
||||
};
|
||||
|
||||
class ModbusDevice {
|
||||
public:
|
||||
void set_parent(Modbus *parent) { parent_ = parent; }
|
||||
void set_address(uint8_t address) { address_ = address; }
|
||||
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
|
||||
|
||||
void send(uint8_t function, uint16_t start_address, uint16_t register_count) {
|
||||
this->parent_->send(this->address_, function, start_address, register_count);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend Modbus;
|
||||
|
||||
Modbus *parent_;
|
||||
uint8_t address_;
|
||||
};
|
||||
|
||||
} // namespace modbus
|
||||
} // namespace esphome
|
||||
@@ -154,8 +154,8 @@ def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
# https://github.com/marvinroger/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library('AsyncMqttClient-esphome', '0.8.2')
|
||||
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library('AsyncMqttClient-esphome', '0.8.3')
|
||||
cg.add_define('USE_MQTT')
|
||||
cg.add_global(mqtt_ns.using)
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo
|
||||
if (!this->sensor_->get_icon().empty())
|
||||
root["icon"] = this->sensor_->get_icon();
|
||||
|
||||
if (this->sensor_->get_force_update())
|
||||
root["force_update"] = true;
|
||||
|
||||
config.command_topic = false;
|
||||
}
|
||||
bool MQTTSensorComponent::send_initial_state() {
|
||||
|
||||
@@ -169,6 +169,7 @@ void PMSX003Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
} // namespace pmsx003
|
||||
|
||||
0
esphome/components/pzem004t/__init__.py
Normal file
0
esphome/components/pzem004t/__init__.py
Normal file
103
esphome/components/pzem004t/pzem004t.cpp
Normal file
103
esphome/components/pzem004t/pzem004t.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "pzem004t.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzem004t {
|
||||
|
||||
static const char *TAG = "pzem004t";
|
||||
|
||||
void PZEM004T::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_read_ > 500 && this->available()) {
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->last_read_ = now;
|
||||
}
|
||||
|
||||
// PZEM004T packet size is 7 byte
|
||||
while (this->available() >= 7) {
|
||||
auto resp = *this->read_array<7>();
|
||||
// packet format:
|
||||
// 0: packet type
|
||||
// 1-5: data
|
||||
// 6: checksum (sum of other bytes)
|
||||
// see https://github.com/olehs/PZEM004T
|
||||
uint8_t sum = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
sum += resp[i];
|
||||
|
||||
if (sum != resp[6]) {
|
||||
ESP_LOGV(TAG, "PZEM004T invalid checksum! 0x%02X != 0x%02X", sum, resp[6]);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (resp[0]) {
|
||||
case 0xA4: { // Set Module Address Response
|
||||
this->write_state_(READ_VOLTAGE);
|
||||
break;
|
||||
}
|
||||
case 0xA0: { // Voltage Response
|
||||
uint16_t int_voltage = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
|
||||
float voltage = int_voltage + (resp[3] / 10.0f);
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
ESP_LOGD(TAG, "Got Voltage %.1f V", voltage);
|
||||
this->write_state_(READ_CURRENT);
|
||||
break;
|
||||
}
|
||||
case 0xA1: { // Current Response
|
||||
uint16_t int_current = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
|
||||
float current = int_current + (resp[3] / 100.0f);
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
ESP_LOGD(TAG, "Got Current %.2f A", current);
|
||||
this->write_state_(READ_POWER);
|
||||
break;
|
||||
}
|
||||
case 0xA2: { // Active Power Response
|
||||
uint16_t power = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(power);
|
||||
ESP_LOGD(TAG, "Got Power %u W", power);
|
||||
this->write_state_(DONE);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xA3: // Energy Response
|
||||
case 0xA5: // Set Power Alarm Response
|
||||
case 0xB0: // Voltage Request
|
||||
case 0xB1: // Current Request
|
||||
case 0xB2: // Active Power Response
|
||||
case 0xB3: // Energy Request
|
||||
case 0xB4: // Set Module Address Request
|
||||
case 0xB5: // Set Power Alarm Request
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->last_read_ = now;
|
||||
}
|
||||
}
|
||||
void PZEM004T::update() { this->write_state_(SET_ADDRESS); }
|
||||
void PZEM004T::write_state_(PZEM004T::PZEM004TReadState state) {
|
||||
if (state == DONE) {
|
||||
this->read_state_ = state;
|
||||
return;
|
||||
}
|
||||
std::array<uint8_t, 7> data{};
|
||||
data[0] = state;
|
||||
data[1] = 192;
|
||||
data[2] = 168;
|
||||
data[3] = 1;
|
||||
data[4] = 1;
|
||||
data[5] = 0;
|
||||
data[6] = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
data[6] += data[i];
|
||||
|
||||
this->write_array(data);
|
||||
this->read_state_ = state;
|
||||
}
|
||||
|
||||
} // namespace pzem004t
|
||||
} // namespace esphome
|
||||
39
esphome/components/pzem004t/pzem004t.h
Normal file
39
esphome/components/pzem004t/pzem004t.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzem004t {
|
||||
|
||||
class PZEM004T : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
|
||||
void loop() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
sensor::Sensor *power_sensor_;
|
||||
|
||||
enum PZEM004TReadState {
|
||||
SET_ADDRESS = 0xB4,
|
||||
READ_VOLTAGE = 0xB0,
|
||||
READ_CURRENT = 0xB1,
|
||||
READ_POWER = 0xB2,
|
||||
DONE = 0x00,
|
||||
} read_state_{DONE};
|
||||
|
||||
void write_state_(PZEM004TReadState state);
|
||||
|
||||
uint32_t last_read_{0};
|
||||
};
|
||||
|
||||
} // namespace pzem004t
|
||||
} // namespace esphome
|
||||
37
esphome/components/pzem004t/sensor.py
Normal file
37
esphome/components/pzem004t/sensor.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
pzem004t_ns = cg.esphome_ns.namespace('pzem004t')
|
||||
PZEM004T = pzem004t_ns.class_('PZEM004T', cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(PZEM004T),
|
||||
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 0),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
0
esphome/components/pzemac/__init__.py
Normal file
0
esphome/components/pzemac/__init__.py
Normal file
62
esphome/components/pzemac/pzemac.cpp
Normal file
62
esphome/components/pzemac/pzemac.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "pzemac.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzemac {
|
||||
|
||||
static const char *TAG = "pzemac";
|
||||
|
||||
static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers
|
||||
|
||||
void PZEMAC::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
if (data.size() < 20) {
|
||||
ESP_LOGW(TAG, "Invalid size for PZEM AC!");
|
||||
return;
|
||||
}
|
||||
|
||||
// See https://github.com/esphome/feature-requests/issues/49#issuecomment-538636809
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||
// 01 04 14 08 D1 00 6C 00 00 00 F4 00 00 00 26 00 00 01 F4 00 64 00 00 51 34
|
||||
// Id Cc Sz Volt- Current---- Power------ Energy----- Frequ PFact Alarm Crc--
|
||||
|
||||
auto pzem_get_16bit = [&](size_t i) -> uint16_t {
|
||||
return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
|
||||
};
|
||||
auto pzem_get_32bit = [&](size_t i) -> uint32_t {
|
||||
return (uint32_t(pzem_get_16bit(i + 2)) << 16) | (uint32_t(pzem_get_16bit(i + 0)) << 0);
|
||||
};
|
||||
|
||||
uint16_t raw_voltage = pzem_get_16bit(0);
|
||||
float voltage = raw_voltage / 10.0f; // max 6553.5 V
|
||||
|
||||
uint32_t raw_current = pzem_get_32bit(2);
|
||||
float current = raw_current / 1000.0f; // max 4294967.295 A
|
||||
|
||||
uint32_t raw_active_power = pzem_get_32bit(6);
|
||||
float active_power = raw_active_power / 10.0f; // max 429496729.5 W
|
||||
|
||||
uint16_t raw_frequency = pzem_get_16bit(14);
|
||||
float frequency = raw_frequency / 10.0f;
|
||||
|
||||
uint16_t raw_power_factor = pzem_get_16bit(16);
|
||||
float power_factor = raw_power_factor / 100.0f;
|
||||
|
||||
ESP_LOGD(TAG, "PZEM AC: V=%.1f V, I=%.3f A, P=%.1f W, F=%.1f Hz, PF=%.2f", voltage, current, active_power, frequency,
|
||||
power_factor);
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(active_power);
|
||||
if (this->frequency_sensor_ != nullptr)
|
||||
this->frequency_sensor_->publish_state(frequency);
|
||||
if (this->power_factor_sensor_ != nullptr)
|
||||
this->power_factor_sensor_->publish_state(power_factor);
|
||||
}
|
||||
|
||||
void PZEMAC::update() { this->send(PZEM_CMD_READ_IN_REGISTERS, 0, PZEM_REGISTER_COUNT); }
|
||||
|
||||
} // namespace pzemac
|
||||
} // namespace esphome
|
||||
31
esphome/components/pzemac/pzemac.h
Normal file
31
esphome/components/pzemac/pzemac.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzemac {
|
||||
|
||||
class PZEMAC : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
|
||||
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
|
||||
|
||||
void update() override;
|
||||
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
sensor::Sensor *power_sensor_;
|
||||
sensor::Sensor *frequency_sensor_;
|
||||
sensor::Sensor *power_factor_sensor_;
|
||||
};
|
||||
|
||||
} // namespace pzemac
|
||||
} // namespace esphome
|
||||
47
esphome/components/pzemac/sensor.py
Normal file
47
esphome/components/pzemac/sensor.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, modbus
|
||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
|
||||
CONF_FREQUENCY, UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, \
|
||||
ICON_POWER, CONF_POWER_FACTOR, ICON_CURRENT_AC
|
||||
|
||||
AUTO_LOAD = ['modbus']
|
||||
|
||||
pzemac_ns = cg.esphome_ns.namespace('pzemac')
|
||||
PZEMAC = pzemac_ns.class_('PZEMAC', cg.PollingComponent, modbus.ModbusDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(PZEMAC),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_EMPTY, ICON_CURRENT_AC, 1),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield modbus.register_modbus_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if CONF_FREQUENCY in config:
|
||||
conf = config[CONF_FREQUENCY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_frequency_sensor(sens))
|
||||
if CONF_POWER_FACTOR in config:
|
||||
conf = config[CONF_POWER_FACTOR]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_factor_sensor(sens))
|
||||
0
esphome/components/pzemdc/__init__.py
Normal file
0
esphome/components/pzemdc/__init__.py
Normal file
52
esphome/components/pzemdc/pzemdc.cpp
Normal file
52
esphome/components/pzemdc/pzemdc.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "pzemdc.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzemdc {
|
||||
|
||||
static const char *TAG = "pzemdc";
|
||||
|
||||
static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers
|
||||
|
||||
void PZEMDC::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
if (data.size() < 16) {
|
||||
ESP_LOGW(TAG, "Invalid size for PZEM DC!");
|
||||
return;
|
||||
}
|
||||
|
||||
// See https://github.com/esphome/feature-requests/issues/49#issuecomment-538636809
|
||||
// 0 1 2 3 4 5 6 7 = ModBus register
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 = Buffer index
|
||||
// 01 04 10 05 40 00 0A 00 0D 00 00 00 02 00 00 00 00 00 00 D6 29
|
||||
// Id Cc Sz Volt- Curre Power------ Energy----- HiAlm LoAlm Crc--
|
||||
|
||||
auto pzem_get_16bit = [&](size_t i) -> uint16_t {
|
||||
return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
|
||||
};
|
||||
auto pzem_get_32bit = [&](size_t i) -> uint32_t {
|
||||
return (uint32_t(pzem_get_16bit(i + 2)) << 16) | (uint32_t(pzem_get_16bit(i + 0)) << 0);
|
||||
};
|
||||
|
||||
uint16_t raw_voltage = pzem_get_16bit(0);
|
||||
float voltage = raw_voltage / 100.0f; // max 655.35 V
|
||||
|
||||
uint16_t raw_current = pzem_get_16bit(2);
|
||||
float current = raw_current / 100.0f; // max 655.35 A
|
||||
|
||||
uint32_t raw_power = pzem_get_32bit(4);
|
||||
float power = raw_power / 10.0f; // max 429496729.5 W
|
||||
|
||||
ESP_LOGD(TAG, "PZEM DC: V=%.1f V, I=%.3f A, P=%.1f W", voltage, current, power);
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
|
||||
void PZEMDC::update() { this->send(PZEM_CMD_READ_IN_REGISTERS, 0, 8); }
|
||||
|
||||
} // namespace pzemdc
|
||||
} // namespace esphome
|
||||
31
esphome/components/pzemdc/pzemdc.h
Normal file
31
esphome/components/pzemdc/pzemdc.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pzemdc {
|
||||
|
||||
class PZEMDC : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
|
||||
void set_powerfactor_sensor(sensor::Sensor *powerfactor_sensor) { power_factor_sensor_ = powerfactor_sensor; }
|
||||
|
||||
void update() override;
|
||||
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
sensor::Sensor *power_sensor_;
|
||||
sensor::Sensor *frequency_sensor_;
|
||||
sensor::Sensor *power_factor_sensor_;
|
||||
};
|
||||
|
||||
} // namespace pzemdc
|
||||
} // namespace esphome
|
||||
36
esphome/components/pzemdc/sensor.py
Normal file
36
esphome/components/pzemdc/sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, modbus
|
||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, ICON_POWER, ICON_CURRENT_AC
|
||||
|
||||
AUTO_LOAD = ['modbus']
|
||||
|
||||
pzemdc_ns = cg.esphome_ns.namespace('pzemdc')
|
||||
PZEMDC = pzemdc_ns.class_('PZEMDC', cg.PollingComponent, modbus.ModbusDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(PZEMDC),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield modbus.register_modbus_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
@@ -8,6 +8,7 @@ script_ns = cg.esphome_ns.namespace('script')
|
||||
Script = script_ns.class_('Script', automation.Trigger.template())
|
||||
ScriptExecuteAction = script_ns.class_('ScriptExecuteAction', automation.Action)
|
||||
ScriptStopAction = script_ns.class_('ScriptStopAction', automation.Action)
|
||||
ScriptWaitAction = script_ns.class_('ScriptWaitAction', automation.Action)
|
||||
IsRunningCondition = script_ns.class_('IsRunningCondition', automation.Condition)
|
||||
|
||||
CONFIG_SCHEMA = automation.validate_automation({
|
||||
@@ -42,6 +43,14 @@ def script_stop_action_to_code(config, action_id, template_arg, args):
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action('script.wait', ScriptWaitAction, maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(Script)
|
||||
}))
|
||||
def script_wait_action_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition('script.is_running', IsRunningCondition, automation.maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(Script)
|
||||
}))
|
||||
|
||||
@@ -49,5 +49,47 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
|
||||
Script *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
|
||||
public:
|
||||
ScriptWaitAction(Script *script) : script_(script) {}
|
||||
|
||||
void play(Ts... x) { /* ignore - see play_complex */
|
||||
}
|
||||
|
||||
void play_complex(Ts... x) override {
|
||||
// Check if we can continue immediately.
|
||||
if (!this->script_->is_running()) {
|
||||
this->triggered_ = false;
|
||||
this->play_next(x...);
|
||||
return;
|
||||
}
|
||||
this->var_ = std::make_tuple(x...);
|
||||
this->triggered_ = true;
|
||||
this->loop();
|
||||
}
|
||||
|
||||
void stop() override { this->triggered_ = false; }
|
||||
|
||||
void loop() override {
|
||||
if (!this->triggered_)
|
||||
return;
|
||||
|
||||
if (this->script_->is_running())
|
||||
return;
|
||||
|
||||
this->triggered_ = false;
|
||||
this->play_next_tuple(this->var_);
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
bool is_running() override { return this->triggered_ || this->is_running_next(); }
|
||||
|
||||
protected:
|
||||
Script *script_;
|
||||
bool triggered_{false};
|
||||
std::tuple<Ts...> var_{};
|
||||
};
|
||||
|
||||
} // namespace script
|
||||
} // namespace esphome
|
||||
|
||||
@@ -56,6 +56,7 @@ void SDS011Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " RX-only mode: %s", ONOFF(this->rx_mode_only_));
|
||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
void SDS011Component::loop() {
|
||||
|
||||
@@ -73,6 +73,7 @@ bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t
|
||||
void SenseAirComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SenseAir:");
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
} // namespace senseair
|
||||
|
||||
@@ -6,10 +6,9 @@ from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, \
|
||||
CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, CONF_INTERNAL, \
|
||||
CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, \
|
||||
CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_TO, CONF_TRIGGER_ID, \
|
||||
CONF_UNIT_OF_MEASUREMENT, \
|
||||
CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID
|
||||
CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, \
|
||||
CONF_TO, CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID, \
|
||||
CONF_FORCE_UPDATE
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.util import Registry
|
||||
|
||||
@@ -87,6 +86,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT): unit_of_measurement,
|
||||
cv.Optional(CONF_ICON): icon,
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals,
|
||||
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_EXPIRE_AFTER): cv.All(cv.requires_component('mqtt'),
|
||||
cv.Any(None, cv.positive_time_period_milliseconds)),
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
@@ -258,6 +258,7 @@ def setup_sensor_core_(var, config):
|
||||
cg.add(var.set_icon(config[CONF_ICON]))
|
||||
if CONF_ACCURACY_DECIMALS in config:
|
||||
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
||||
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
||||
if CONF_FILTERS in config:
|
||||
filters = yield build_filters(config[CONF_FILTERS])
|
||||
cg.add(var.set_filters(filters))
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace sensor {
|
||||
if (!obj->unique_id().empty()) { \
|
||||
ESP_LOGV(TAG, prefix " Unique ID: '%s'", obj->unique_id().c_str()); \
|
||||
} \
|
||||
if (obj->get_force_update()) { \
|
||||
ESP_LOGV(TAG, prefix " Force Update: YES"); \
|
||||
} \
|
||||
}
|
||||
|
||||
/** Base-class for all sensors.
|
||||
@@ -142,6 +145,15 @@ class Sensor : public Nameable {
|
||||
|
||||
void internal_send_state_to_frontend(float state);
|
||||
|
||||
bool get_force_update() const { return force_update_; }
|
||||
/** Set this sensor's force_update mode.
|
||||
*
|
||||
* If the sensor is in force_update mode, the frontend is required to save all
|
||||
* state changes to the database when they are published, even if the state is the
|
||||
* same as before.
|
||||
*/
|
||||
void set_force_update(bool force_update) { force_update_ = force_update; }
|
||||
|
||||
protected:
|
||||
/** Override this to set the Home Assistant unit of measurement for this sensor.
|
||||
*
|
||||
@@ -174,6 +186,7 @@ class Sensor : public Nameable {
|
||||
optional<int8_t> accuracy_decimals_;
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
bool has_state_{false};
|
||||
bool force_update_{false};
|
||||
};
|
||||
|
||||
class PollingSensorComponent : public PollingComponent, public Sensor {
|
||||
|
||||
0
esphome/components/sgp30/__init__.py
Normal file
0
esphome/components/sgp30/__init__.py
Normal file
54
esphome/components/sgp30/sensor.py
Normal file
54
esphome/components/sgp30/sensor.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
|
||||
UNIT_PARTS_PER_BILLION, ICON_PERIODIC_TABLE_CO2
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
sgp30_ns = cg.esphome_ns.namespace('sgp30')
|
||||
SGP30Component = sgp30_ns.class_('SGP30Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_ECO2 = 'eco2'
|
||||
CONF_TVOC = 'tvoc'
|
||||
CONF_BASELINE = 'baseline'
|
||||
CONF_UPTIME = 'uptime'
|
||||
CONF_COMPENSATION = 'compensation'
|
||||
CONF_COMPENSATION_HUMIDITY = 'humidity_source'
|
||||
CONF_COMPENSATION_TEMPERATURE = 'temperature_source'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(SGP30Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION,
|
||||
ICON_PERIODIC_TABLE_CO2, 0),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_COMPENSATION): cv.Schema({
|
||||
cv.Required(CONF_COMPENSATION_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_COMPENSATION_TEMPERATURE): cv.use_id(sensor.Sensor)
|
||||
}),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x58))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_ECO2 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_ECO2])
|
||||
cg.add(var.set_eco2_sensor(sens))
|
||||
|
||||
if CONF_TVOC in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc_sensor(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
cg.add(var.set_baseline(config[CONF_BASELINE]))
|
||||
|
||||
if CONF_COMPENSATION in config:
|
||||
compensation_config = config[CONF_COMPENSATION]
|
||||
sens = yield cg.get_variable(compensation_config[CONF_COMPENSATION_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sens = yield cg.get_variable(compensation_config[CONF_COMPENSATION_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
295
esphome/components/sgp30/sgp30.cpp
Normal file
295
esphome/components/sgp30/sgp30.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include "sgp30.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sgp30 {
|
||||
|
||||
static const char *TAG = "sgp30";
|
||||
|
||||
static const uint16_t SGP30_CMD_GET_SERIAL_ID = 0x3682;
|
||||
static const uint16_t SGP30_CMD_GET_FEATURESET = 0x202f;
|
||||
static const uint16_t SGP30_CMD_IAQ_INIT = 0x2003;
|
||||
static const uint16_t SGP30_CMD_MEASURE_IAQ = 0x2008;
|
||||
static const uint16_t SGP30_CMD_SET_ABSOLUTE_HUMIDITY = 0x2061;
|
||||
static const uint16_t SGP30_CMD_GET_IAQ_BASELINE = 0x2015;
|
||||
static const uint16_t SGP30_CMD_SET_IAQ_BASELINE = 0x201E;
|
||||
|
||||
// Sensor baseline should first be relied on after 1H of operation,
|
||||
// if the sensor starts with a baseline value provided
|
||||
const long IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED = 3600;
|
||||
|
||||
// Sensor baseline could first be relied on after 12H of operation,
|
||||
// if the sensor starts without any prior baseline value provided
|
||||
const long IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE = 43200;
|
||||
|
||||
void SGP30Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
||||
|
||||
// Serial Number identification
|
||||
if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_serial_number[3];
|
||||
|
||||
if (!this->read_data_(raw_serial_number, 3)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) |
|
||||
(uint64_t(raw_serial_number[2]));
|
||||
ESP_LOGD(TAG, "Serial Number: %llu", this->serial_number_);
|
||||
|
||||
// Featureset identification for future use
|
||||
if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_featureset[1];
|
||||
if (!this->read_data_(raw_featureset, 1)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->featureset_ = raw_featureset[0];
|
||||
if (uint16_t(this->featureset_ >> 12) != 0x0) {
|
||||
if (uint16_t(this->featureset_ >> 12) == 0x1) {
|
||||
// ID matching a different sensor: SGPC3
|
||||
this->error_code_ = UNSUPPORTED_ID;
|
||||
} else {
|
||||
// Unknown ID
|
||||
this->error_code_ = INVALID_ID;
|
||||
}
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
|
||||
|
||||
// Sensor initialization
|
||||
if (!this->write_command_(SGP30_CMD_IAQ_INIT)) {
|
||||
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sensor baseline reliability timer
|
||||
if (this->baseline_ > 0) {
|
||||
this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED;
|
||||
this->write_iaq_baseline_(this->baseline_);
|
||||
} else {
|
||||
this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE;
|
||||
}
|
||||
}
|
||||
|
||||
bool SGP30Component::is_sensor_baseline_reliable_() {
|
||||
if ((this->required_warm_up_time_ == 0) || (std::floor(millis() / 1000) >= this->required_warm_up_time_)) {
|
||||
// requirement for warm up is removed once the millis uptime surpasses the required warm_up_time
|
||||
// this avoids the repetitive warm up when the millis uptime is rolled over every ~40 days
|
||||
this->required_warm_up_time_ = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SGP30Component::read_iaq_baseline_() {
|
||||
if (this->is_sensor_baseline_reliable_()) {
|
||||
if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) {
|
||||
ESP_LOGD(TAG, "Error getting baseline");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t eco2baseline = (raw_data[0]);
|
||||
uint8_t tvocbaseline = (raw_data[1]);
|
||||
|
||||
ESP_LOGI(TAG, "Current eCO2 & TVOC baseline: 0x%04X", uint16_t((eco2baseline << 8) | (tvocbaseline & 0xFF)));
|
||||
this->status_clear_warning();
|
||||
});
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Baseline reading not available for: %.0fs",
|
||||
(this->required_warm_up_time_ - std::floor(millis() / 1000)));
|
||||
}
|
||||
}
|
||||
|
||||
void SGP30Component::send_env_data_() {
|
||||
if (this->humidity_sensor_ == nullptr && this->temperature_sensor_ == nullptr)
|
||||
return;
|
||||
float humidity = NAN;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
humidity = this->humidity_sensor_->state;
|
||||
if (isnan(humidity) || humidity < 0.0f || humidity > 100.0f) {
|
||||
ESP_LOGW(TAG, "Compensation not possible yet: bad humidity data.");
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "External compensation data received: Humidity %0.2f%%", humidity);
|
||||
}
|
||||
float temperature = NAN;
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
temperature = float(this->temperature_sensor_->state);
|
||||
}
|
||||
if (isnan(temperature) || temperature < -40.0f || temperature > 85.0f) {
|
||||
ESP_LOGW(TAG, "Compensation not possible yet: bad temperature value data.");
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "External compensation data received: Temperature %0.2f°C", temperature);
|
||||
}
|
||||
|
||||
float absolute_humidity =
|
||||
216.7f * (((humidity / 100) * 6.112f * std::exp((17.62f * temperature) / (243.12f + temperature))) /
|
||||
(273.15f + temperature));
|
||||
uint8_t humidity_full = uint8_t(std::floor(absolute_humidity));
|
||||
uint8_t humidity_dec = uint8_t(std::floor((absolute_humidity - std::floor(absolute_humidity)) * 256));
|
||||
ESP_LOGD(TAG, "Calculated Absolute humidity: %0.3f g/m³ (0x%04X)", absolute_humidity,
|
||||
uint16_t(uint16_t(humidity_full) << 8 | uint16_t(humidity_dec)));
|
||||
uint8_t crc = sht_crc_(humidity_full, humidity_dec);
|
||||
uint8_t data[4];
|
||||
data[0] = SGP30_CMD_SET_ABSOLUTE_HUMIDITY & 0xFF;
|
||||
data[1] = humidity_full;
|
||||
data[2] = humidity_dec;
|
||||
data[3] = crc;
|
||||
if (!this->write_bytes(SGP30_CMD_SET_ABSOLUTE_HUMIDITY >> 8, data, 4)) {
|
||||
ESP_LOGE(TAG, "Error sending compensation data.");
|
||||
}
|
||||
}
|
||||
|
||||
void SGP30Component::write_iaq_baseline_(uint16_t baseline) {
|
||||
uint8_t e_c_o2_baseline = baseline >> 8;
|
||||
uint8_t tvoc_baseline = baseline & 0xFF;
|
||||
uint8_t data[4];
|
||||
data[0] = SGP30_CMD_SET_IAQ_BASELINE & 0xFF;
|
||||
data[1] = e_c_o2_baseline;
|
||||
data[2] = tvoc_baseline;
|
||||
data[3] = sht_crc_(e_c_o2_baseline, tvoc_baseline);
|
||||
if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 4)) {
|
||||
ESP_LOGE(TAG, "Error applying baseline: 0x%04X", baseline);
|
||||
} else
|
||||
ESP_LOGI(TAG, "Initial baseline 0x%04X applied successfully!", baseline);
|
||||
}
|
||||
|
||||
void SGP30Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SGP30:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||
break;
|
||||
case MEASUREMENT_INIT_FAILED:
|
||||
ESP_LOGW(TAG, "Measurement Initialization failed!");
|
||||
break;
|
||||
case INVALID_ID:
|
||||
ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this an SGP30?");
|
||||
break;
|
||||
case UNSUPPORTED_ID:
|
||||
ESP_LOGW(TAG, "Sensor reported an unsupported ID (SGPC3).");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown setup error!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Serial number: %llu", this->serial_number_);
|
||||
ESP_LOGCONFIG(TAG, " Baseline: 0x%04X%s", this->baseline_,
|
||||
((this->baseline_ != 0x0000) ? " (enabled)" : " (disabled)"));
|
||||
ESP_LOGCONFIG(TAG, " Warm up time: %lds", this->required_warm_up_time_);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "eCO2", this->eco2_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Compensation:");
|
||||
LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Compensation: No source configured");
|
||||
}
|
||||
}
|
||||
|
||||
void SGP30Component::update() {
|
||||
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float eco2 = (raw_data[0]);
|
||||
float tvoc = (raw_data[1]);
|
||||
|
||||
ESP_LOGD(TAG, "Got eCO2=%.1fppm TVOC=%.1fppb", eco2, tvoc);
|
||||
if (this->eco2_sensor_ != nullptr)
|
||||
this->eco2_sensor_->publish_state(eco2);
|
||||
if (this->tvoc_sensor_ != nullptr)
|
||||
this->tvoc_sensor_->publish_state(tvoc);
|
||||
this->status_clear_warning();
|
||||
this->send_env_data_();
|
||||
this->read_iaq_baseline_();
|
||||
});
|
||||
}
|
||||
|
||||
bool SGP30Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SGP30Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
auto *buf = new uint8_t[num_bytes];
|
||||
|
||||
if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) {
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
delete[](buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sgp30
|
||||
} // namespace esphome
|
||||
54
esphome/components/sgp30/sgp30.h
Normal file
54
esphome/components/sgp30/sgp30.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace sgp30 {
|
||||
|
||||
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
|
||||
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
||||
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
void set_baseline(uint16_t baseline) { baseline_ = baseline; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
void send_env_data_();
|
||||
void read_iaq_baseline_();
|
||||
bool is_sensor_baseline_reliable_();
|
||||
void write_iaq_baseline_(uint16_t baseline);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
uint64_t serial_number_;
|
||||
uint16_t featureset_;
|
||||
long required_warm_up_time_;
|
||||
|
||||
enum ErrorCode {
|
||||
COMMUNICATION_FAILED,
|
||||
MEASUREMENT_INIT_FAILED,
|
||||
INVALID_ID,
|
||||
UNSUPPORTED_ID,
|
||||
UNKNOWN
|
||||
} error_code_{UNKNOWN};
|
||||
|
||||
sensor::Sensor *eco2_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
uint16_t baseline_{0x0000};
|
||||
/// Input sensor for humidity and temperature compensation.
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace sgp30
|
||||
} // namespace esphome
|
||||
0
esphome/components/shtcx/__init__.py
Normal file
0
esphome/components/shtcx/__init__.py
Normal file
32
esphome/components/shtcx/sensor.py
Normal file
32
esphome/components/shtcx/sensor.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, ICON_WATER_PERCENT, \
|
||||
ICON_THERMOMETER, UNIT_CELSIUS, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
shtcx_ns = cg.esphome_ns.namespace('shtcx')
|
||||
SHTCXComponent = shtcx_ns.class_('SHTCXComponent', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
SHTCXType = shtcx_ns.enum('SHTCXType')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(SHTCXComponent),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x70))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
166
esphome/components/shtcx/shtcx.cpp
Normal file
166
esphome/components/shtcx/shtcx.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "shtcx.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace shtcx {
|
||||
|
||||
static const char *TAG = "shtcx";
|
||||
|
||||
static const uint16_t SHTCX_COMMAND_SLEEP = 0xB098;
|
||||
static const uint16_t SHTCX_COMMAND_WAKEUP = 0x3517;
|
||||
static const uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8;
|
||||
static const uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D;
|
||||
static const uint16_t SHTCX_COMMAND_POLLING_H = 0x7866;
|
||||
|
||||
inline const char *to_string(SHTCXType type) {
|
||||
switch (type) {
|
||||
case SHTCX_TYPE_SHTC3:
|
||||
return "SHTC3";
|
||||
case SHTCX_TYPE_SHTC1:
|
||||
return "SHTC1";
|
||||
default:
|
||||
return "[Unknown model]";
|
||||
}
|
||||
}
|
||||
|
||||
void SHTCXComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SHTCx...");
|
||||
this->soft_reset();
|
||||
|
||||
if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) {
|
||||
ESP_LOGE(TAG, "Error requesting Device ID");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t device_id_register[1];
|
||||
if (!this->read_data_(device_id_register, 1)) {
|
||||
ESP_LOGE(TAG, "Error reading Device ID");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (((device_id_register[0] << 2) & 0x1C) == 0x1C) {
|
||||
if ((device_id_register[0] & 0x847) == 0x847) {
|
||||
this->type_ = SHTCX_TYPE_SHTC3;
|
||||
} else {
|
||||
this->type_ = SHTCX_TYPE_SHTC1;
|
||||
}
|
||||
} else {
|
||||
this->type_ = SHTCX_TYPE_UNKNOWN;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Device identified: %s", to_string(this->type_));
|
||||
}
|
||||
void SHTCXComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SHTCx:");
|
||||
ESP_LOGCONFIG(TAG, " Model: %s", to_string(this->type_));
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with SHTCx failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
float SHTCXComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void SHTCXComponent::update() {
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGW(TAG, "Retrying to reconnect the sensor.");
|
||||
this->soft_reset();
|
||||
}
|
||||
if (this->type_ != SHTCX_TYPE_SHTC1) {
|
||||
this->wake_up();
|
||||
}
|
||||
if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
|
||||
float humidity = 100.0f * float(raw_data[1]) / 65536.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
if (this->type_ != SHTCX_TYPE_SHTC1) {
|
||||
this->sleep();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool SHTCXComponent::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
auto *buf = new uint8_t[num_bytes];
|
||||
|
||||
if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) {
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
delete[](buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SHTCXComponent::soft_reset() {
|
||||
this->write_command_(SHTCX_COMMAND_SOFT_RESET);
|
||||
delayMicroseconds(200);
|
||||
}
|
||||
void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); }
|
||||
|
||||
void SHTCXComponent::wake_up() {
|
||||
this->write_command_(SHTCX_COMMAND_WAKEUP);
|
||||
delayMicroseconds(200);
|
||||
}
|
||||
|
||||
} // namespace shtcx
|
||||
} // namespace esphome
|
||||
35
esphome/components/shtcx/shtcx.h
Normal file
35
esphome/components/shtcx/shtcx.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace shtcx {
|
||||
|
||||
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
|
||||
|
||||
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
||||
class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void soft_reset();
|
||||
void sleep();
|
||||
void wake_up();
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
SHTCXType type_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
} // namespace shtcx
|
||||
} // namespace esphome
|
||||
42
esphome/components/ssd1325_base/__init__.py
Normal file
42
esphome/components/ssd1325_base/__init__.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import display
|
||||
from esphome.const import CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN
|
||||
from esphome.core import coroutine
|
||||
|
||||
ssd1325_base_ns = cg.esphome_ns.namespace('ssd1325_base')
|
||||
SSD1325 = ssd1325_base_ns.class_('SSD1325', cg.PollingComponent, display.DisplayBuffer)
|
||||
SSD1325Model = ssd1325_base_ns.enum('SSD1325Model')
|
||||
|
||||
MODELS = {
|
||||
'SSD1325_128X32': SSD1325Model.SSD1325_MODEL_128_32,
|
||||
'SSD1325_128X64': SSD1325Model.SSD1325_MODEL_128_64,
|
||||
'SSD1325_96X16': SSD1325Model.SSD1325_MODEL_96_16,
|
||||
'SSD1325_64X48': SSD1325Model.SSD1325_MODEL_64_48,
|
||||
}
|
||||
|
||||
SSD1325_MODEL = cv.enum(MODELS, upper=True, space="_")
|
||||
|
||||
SSD1325_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
|
||||
cv.Required(CONF_MODEL): SSD1325_MODEL,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_EXTERNAL_VCC): cv.boolean,
|
||||
}).extend(cv.polling_component_schema('1s'))
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_ssd1036(var, config):
|
||||
yield cg.register_component(var, config)
|
||||
yield display.register_display(var, config)
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
if CONF_RESET_PIN in config:
|
||||
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
if CONF_EXTERNAL_VCC in config:
|
||||
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
177
esphome/components/ssd1325_base/ssd1325_base.cpp
Normal file
177
esphome/components/ssd1325_base/ssd1325_base.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "ssd1325_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ssd1325_base {
|
||||
|
||||
static const char *TAG = "ssd1325";
|
||||
|
||||
static const uint8_t BLACK = 0;
|
||||
static const uint8_t WHITE = 1;
|
||||
|
||||
static const uint8_t SSD1325_SETCOLADDR = 0x15;
|
||||
static const uint8_t SSD1325_SETROWADDR = 0x75;
|
||||
static const uint8_t SSD1325_SETCONTRAST = 0x81;
|
||||
static const uint8_t SSD1325_SETCURRENT = 0x84;
|
||||
|
||||
static const uint8_t SSD1325_SETREMAP = 0xA0;
|
||||
static const uint8_t SSD1325_SETSTARTLINE = 0xA1;
|
||||
static const uint8_t SSD1325_SETOFFSET = 0xA2;
|
||||
static const uint8_t SSD1325_NORMALDISPLAY = 0xA4;
|
||||
static const uint8_t SSD1325_DISPLAYALLON = 0xA5;
|
||||
static const uint8_t SSD1325_DISPLAYALLOFF = 0xA6;
|
||||
static const uint8_t SSD1325_INVERTDISPLAY = 0xA7;
|
||||
static const uint8_t SSD1325_SETMULTIPLEX = 0xA8;
|
||||
static const uint8_t SSD1325_MASTERCONFIG = 0xAD;
|
||||
static const uint8_t SSD1325_DISPLAYOFF = 0xAE;
|
||||
static const uint8_t SSD1325_DISPLAYON = 0xAF;
|
||||
|
||||
static const uint8_t SSD1325_SETPRECHARGECOMPENABLE = 0xB0;
|
||||
static const uint8_t SSD1325_SETPHASELEN = 0xB1;
|
||||
static const uint8_t SSD1325_SETROWPERIOD = 0xB2;
|
||||
static const uint8_t SSD1325_SETCLOCK = 0xB3;
|
||||
static const uint8_t SSD1325_SETPRECHARGECOMP = 0xB4;
|
||||
static const uint8_t SSD1325_SETGRAYTABLE = 0xB8;
|
||||
static const uint8_t SSD1325_SETPRECHARGEVOLTAGE = 0xBC;
|
||||
static const uint8_t SSD1325_SETVCOMLEVEL = 0xBE;
|
||||
static const uint8_t SSD1325_SETVSL = 0xBF;
|
||||
|
||||
static const uint8_t SSD1325_GFXACCEL = 0x23;
|
||||
static const uint8_t SSD1325_DRAWRECT = 0x24;
|
||||
static const uint8_t SSD1325_COPY = 0x25;
|
||||
|
||||
void SSD1325::setup() {
|
||||
this->init_internal_(this->get_buffer_length_());
|
||||
|
||||
this->command(SSD1325_DISPLAYOFF); /* display off */
|
||||
this->command(SSD1325_SETCLOCK); /* set osc division */
|
||||
this->command(0xF1); /* 145 */
|
||||
this->command(SSD1325_SETMULTIPLEX); /* multiplex ratio */
|
||||
this->command(0x3f); /* duty = 1/64 */
|
||||
this->command(SSD1325_SETOFFSET); /* set display offset --- */
|
||||
this->command(0x4C); /* 76 */
|
||||
this->command(SSD1325_SETSTARTLINE); /*set start line */
|
||||
this->command(0x00); /* ------ */
|
||||
this->command(SSD1325_MASTERCONFIG); /*Set Master Config DC/DC Converter*/
|
||||
this->command(0x02);
|
||||
this->command(SSD1325_SETREMAP); /* set segment remap------ */
|
||||
this->command(0x56);
|
||||
this->command(SSD1325_SETCURRENT + 0x2); /* Set Full Current Range */
|
||||
this->command(SSD1325_SETGRAYTABLE);
|
||||
this->command(0x01);
|
||||
this->command(0x11);
|
||||
this->command(0x22);
|
||||
this->command(0x32);
|
||||
this->command(0x43);
|
||||
this->command(0x54);
|
||||
this->command(0x65);
|
||||
this->command(0x76);
|
||||
this->command(SSD1325_SETCONTRAST); /* set contrast current */
|
||||
this->command(0x7F); // max!
|
||||
this->command(SSD1325_SETROWPERIOD);
|
||||
this->command(0x51);
|
||||
this->command(SSD1325_SETPHASELEN);
|
||||
this->command(0x55);
|
||||
this->command(SSD1325_SETPRECHARGECOMP);
|
||||
this->command(0x02);
|
||||
this->command(SSD1325_SETPRECHARGECOMPENABLE);
|
||||
this->command(0x28);
|
||||
this->command(SSD1325_SETVCOMLEVEL); // Set High Voltage Level of COM Pin
|
||||
this->command(0x1C); //?
|
||||
this->command(SSD1325_SETVSL); // set Low Voltage Level of SEG Pin
|
||||
this->command(0x0D | 0x02);
|
||||
this->command(SSD1325_NORMALDISPLAY); /* set display mode */
|
||||
this->command(SSD1325_DISPLAYON); /* display ON */
|
||||
}
|
||||
void SSD1325::display() {
|
||||
this->command(SSD1325_SETCOLADDR); /* set column address */
|
||||
this->command(0x00); /* set column start address */
|
||||
this->command(0x3F); /* set column end address */
|
||||
this->command(SSD1325_SETROWADDR); /* set row address */
|
||||
this->command(0x00); /* set row start address */
|
||||
this->command(0x3F); /* set row end address */
|
||||
|
||||
this->write_display_data();
|
||||
}
|
||||
void SSD1325::update() {
|
||||
this->do_update_();
|
||||
this->display();
|
||||
}
|
||||
int SSD1325::get_height_internal() {
|
||||
switch (this->model_) {
|
||||
case SSD1325_MODEL_128_32:
|
||||
return 32;
|
||||
case SSD1325_MODEL_128_64:
|
||||
return 64;
|
||||
case SSD1325_MODEL_96_16:
|
||||
return 16;
|
||||
case SSD1325_MODEL_64_48:
|
||||
return 48;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int SSD1325::get_width_internal() {
|
||||
switch (this->model_) {
|
||||
case SSD1325_MODEL_128_32:
|
||||
case SSD1325_MODEL_128_64:
|
||||
return 128;
|
||||
case SSD1325_MODEL_96_16:
|
||||
return 96;
|
||||
case SSD1325_MODEL_64_48:
|
||||
return 64;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
size_t SSD1325::get_buffer_length_() {
|
||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
||||
}
|
||||
|
||||
void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
||||
return;
|
||||
|
||||
uint16_t pos = x + (y / 8) * this->get_width_internal();
|
||||
uint8_t subpos = y % 8;
|
||||
if (color) {
|
||||
this->buffer_[pos] |= (1 << subpos);
|
||||
} else {
|
||||
this->buffer_[pos] &= ~(1 << subpos);
|
||||
}
|
||||
}
|
||||
void SSD1325::fill(int color) {
|
||||
uint8_t fill = color ? 0xFF : 0x00;
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void SSD1325::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
const char *SSD1325::model_str_() {
|
||||
switch (this->model_) {
|
||||
case SSD1325_MODEL_128_32:
|
||||
return "SSD1325 128x32";
|
||||
case SSD1325_MODEL_128_64:
|
||||
return "SSD1325 128x64";
|
||||
case SSD1325_MODEL_96_16:
|
||||
return "SSD1325 96x16";
|
||||
case SSD1325_MODEL_64_48:
|
||||
return "SSD1325 64x48";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ssd1325_base
|
||||
} // namespace esphome
|
||||
50
esphome/components/ssd1325_base/ssd1325_base.h
Normal file
50
esphome/components/ssd1325_base/ssd1325_base.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ssd1325_base {
|
||||
|
||||
enum SSD1325Model {
|
||||
SSD1325_MODEL_128_32 = 0,
|
||||
SSD1325_MODEL_128_64,
|
||||
SSD1325_MODEL_96_16,
|
||||
SSD1325_MODEL_64_48,
|
||||
};
|
||||
|
||||
class SSD1325 : public PollingComponent, public display::DisplayBuffer {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void display();
|
||||
|
||||
void update() override;
|
||||
|
||||
void set_model(SSD1325Model model) { this->model_ = model; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
void fill(int color) override;
|
||||
|
||||
protected:
|
||||
virtual void command(uint8_t value) = 0;
|
||||
virtual void write_display_data() = 0;
|
||||
void init_reset_();
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
|
||||
int get_height_internal() override;
|
||||
int get_width_internal() override;
|
||||
size_t get_buffer_length_();
|
||||
const char *model_str_();
|
||||
|
||||
SSD1325Model model_{SSD1325_MODEL_128_64};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
bool external_vcc_{false};
|
||||
};
|
||||
|
||||
} // namespace ssd1325_base
|
||||
} // namespace esphome
|
||||
0
esphome/components/ssd1325_spi/__init__.py
Normal file
0
esphome/components/ssd1325_spi/__init__.py
Normal file
26
esphome/components/ssd1325_spi/display.py
Normal file
26
esphome/components/ssd1325_spi/display.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import spi, ssd1325_base
|
||||
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
|
||||
|
||||
AUTO_LOAD = ['ssd1325_base']
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
ssd1325_spi = cg.esphome_ns.namespace('ssd1325_spi')
|
||||
SPISSD1325 = ssd1325_spi.class_('SPISSD1325', ssd1325_base.SSD1325, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(ssd1325_base.SSD1325_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(SPISSD1325),
|
||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield ssd1325_base.setup_ssd1036(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
64
esphome/components/ssd1325_spi/ssd1325_spi.cpp
Normal file
64
esphome/components/ssd1325_spi/ssd1325_spi.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "ssd1325_spi.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ssd1325_spi {
|
||||
|
||||
static const char *TAG = "ssd1325_spi";
|
||||
|
||||
void SPISSD1325::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SPI SSD1325...");
|
||||
this->spi_setup();
|
||||
this->dc_pin_->setup(); // OUTPUT
|
||||
this->cs_->setup(); // OUTPUT
|
||||
|
||||
this->init_reset_();
|
||||
delay(500);
|
||||
SSD1325::setup();
|
||||
}
|
||||
void SPISSD1325::dump_config() {
|
||||
LOG_DISPLAY("", "SPI SSD1325", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
void SPISSD1325::command(uint8_t value) {
|
||||
this->cs_->digital_write(true);
|
||||
this->dc_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->enable();
|
||||
this->cs_->digital_write(false);
|
||||
this->write_byte(value);
|
||||
this->cs_->digital_write(true);
|
||||
this->disable();
|
||||
}
|
||||
void HOT SPISSD1325::write_display_data() {
|
||||
this->cs_->digital_write(true);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->cs_->digital_write(false);
|
||||
delay(1);
|
||||
this->enable();
|
||||
for (uint16_t x = 0; x < this->get_width_internal(); x += 2) {
|
||||
for (uint16_t y = 0; y < this->get_height_internal(); y += 8) { // we write 8 pixels at once
|
||||
uint8_t left8 = this->buffer_[y * 16 + x];
|
||||
uint8_t right8 = this->buffer_[y * 16 + x + 1];
|
||||
for (uint8_t p = 0; p < 8; p++) {
|
||||
uint8_t d = 0;
|
||||
if (left8 & (1 << p))
|
||||
d |= 0xF0;
|
||||
if (right8 & (1 << p))
|
||||
d |= 0x0F;
|
||||
this->write_byte(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->cs_->digital_write(true);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
} // namespace ssd1325_spi
|
||||
} // namespace esphome
|
||||
29
esphome/components/ssd1325_spi/ssd1325_spi.h
Normal file
29
esphome/components/ssd1325_spi/ssd1325_spi.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ssd1325_base/ssd1325_base.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ssd1325_spi {
|
||||
|
||||
class SPISSD1325 : public ssd1325_base::SSD1325,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void command(uint8_t value) override;
|
||||
|
||||
void write_display_data() override;
|
||||
|
||||
GPIOPin *dc_pin_;
|
||||
};
|
||||
|
||||
} // namespace ssd1325_spi
|
||||
} // namespace esphome
|
||||
0
esphome/components/sts3x/__init__.py
Normal file
0
esphome/components/sts3x/__init__.py
Normal file
22
esphome/components/sts3x/sensor.py
Normal file
22
esphome/components/sts3x/sensor.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
sts3x_ns = cg.esphome_ns.namespace('sts3x')
|
||||
|
||||
STS3XComponent = sts3x_ns.class_('STS3XComponent', sensor.Sensor,
|
||||
cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.GenerateID(): cv.declare_id(STS3XComponent),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x4A))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
123
esphome/components/sts3x/sts3x.cpp
Normal file
123
esphome/components/sts3x/sts3x.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "sts3x.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sts3x {
|
||||
|
||||
static const char *TAG = "sts3x";
|
||||
|
||||
static const uint16_t STS3X_COMMAND_READ_SERIAL_NUMBER = 0x3780;
|
||||
static const uint16_t STS3X_COMMAND_READ_STATUS = 0xF32D;
|
||||
static const uint16_t STS3X_COMMAND_SOFT_RESET = 0x30A2;
|
||||
static const uint16_t STS3X_COMMAND_POLLING_H = 0x2400;
|
||||
|
||||
/// Commands for future use
|
||||
static const uint16_t STS3X_COMMAND_CLEAR_STATUS = 0x3041;
|
||||
static const uint16_t STS3X_COMMAND_HEATER_ENABLE = 0x306D;
|
||||
static const uint16_t STS3X_COMMAND_HEATER_DISABLE = 0x3066;
|
||||
static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000;
|
||||
|
||||
void STS3XComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up STS3x...");
|
||||
if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[2];
|
||||
if (!this->read_data_(raw_serial_number, 1)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint32_t serial_number = (uint32_t(raw_serial_number[0]) << 16);
|
||||
ESP_LOGV(TAG, " Serial Number: 0x%08X", serial_number);
|
||||
}
|
||||
void STS3XComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "STS3x:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with ST3x failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "STS3x", this);
|
||||
}
|
||||
float STS3XComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void STS3XComponent::update() {
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
||||
this->write_command_(STS3X_COMMAND_SOFT_RESET);
|
||||
}
|
||||
if (!this->write_command_(STS3X_COMMAND_POLLING_H)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[1];
|
||||
if (!this->read_data_(raw_data, 1)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float temperature = 175.0f * float(raw_data[0]) / 65535.0f - 45.0f;
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C", temperature);
|
||||
this->publish_state(temperature);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
bool STS3XComponent::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t sts3x_crc(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
auto *buf = new uint8_t[num_bytes];
|
||||
|
||||
if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) {
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sts3x_crc(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
delete[](buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sts3x
|
||||
} // namespace esphome
|
||||
24
esphome/components/sts3x/sts3x.h
Normal file
24
esphome/components/sts3x/sts3x.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sts3x {
|
||||
|
||||
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
|
||||
class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
};
|
||||
|
||||
} // namespace sts3x
|
||||
} // namespace esphome
|
||||
20
esphome/components/tuya/__init__.py
Normal file
20
esphome/components/tuya/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
tuya_ns = cg.esphome_ns.namespace('tuya')
|
||||
Tuya = tuya_ns.class_('Tuya', cg.Component, uart.UARTDevice)
|
||||
|
||||
CONF_TUYA_ID = 'tuya_id'
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(Tuya),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
44
esphome/components/tuya/light/__init__.py
Normal file
44
esphome/components/tuya/light/__init__.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from esphome.components import light
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, CONF_GAMMA_CORRECT, \
|
||||
CONF_DEFAULT_TRANSITION_LENGTH
|
||||
from .. import tuya_ns, CONF_TUYA_ID, Tuya
|
||||
|
||||
DEPENDENCIES = ['tuya']
|
||||
|
||||
CONF_DIMMER_DATAPOINT = "dimmer_datapoint"
|
||||
CONF_SWITCH_DATAPOINT = "switch_datapoint"
|
||||
|
||||
TuyaLight = tuya_ns.class_('TuyaLight', light.LightOutput, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaLight),
|
||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||
cv.Required(CONF_DIMMER_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_MIN_VALUE): cv.int_,
|
||||
cv.Optional(CONF_MAX_VALUE): cv.int_,
|
||||
|
||||
# Change the default gamma_correct and default transition length settings.
|
||||
# The Tuya MCU handles transitions and gamma correction on its own.
|
||||
cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float,
|
||||
cv.Optional(CONF_DEFAULT_TRANSITION_LENGTH, default='0s'): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield light.register_light(var, config)
|
||||
|
||||
if CONF_DIMMER_DATAPOINT in config:
|
||||
cg.add(var.set_dimmer_id(config[CONF_DIMMER_DATAPOINT]))
|
||||
if CONF_SWITCH_DATAPOINT in config:
|
||||
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
|
||||
if CONF_MIN_VALUE in config:
|
||||
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
|
||||
if CONF_MAX_VALUE in config:
|
||||
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
|
||||
paren = yield cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(paren))
|
||||
85
esphome/components/tuya/light/tuya_light.cpp
Normal file
85
esphome/components/tuya/light/tuya_light.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "tuya_light.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
static const char *TAG = "tuya.light";
|
||||
|
||||
void TuyaLight::setup() {
|
||||
if (this->dimmer_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->dimmer_id_, [this](TuyaDatapoint datapoint) {
|
||||
auto call = this->state_->make_call();
|
||||
call.set_brightness(float(datapoint.value_uint) / this->max_value_);
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
if (switch_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->switch_id_, [this](TuyaDatapoint datapoint) {
|
||||
auto call = this->state_->make_call();
|
||||
call.set_state(datapoint.value_bool);
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaLight::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Tuya Dimmer:");
|
||||
if (this->dimmer_id_.has_value())
|
||||
ESP_LOGCONFIG(TAG, " Dimmer has datapoint ID %u", *this->dimmer_id_);
|
||||
if (this->switch_id_.has_value())
|
||||
ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_);
|
||||
}
|
||||
|
||||
light::LightTraits TuyaLight::get_traits() {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(this->dimmer_id_.has_value());
|
||||
return traits;
|
||||
}
|
||||
|
||||
void TuyaLight::setup_state(light::LightState *state) { state_ = state; }
|
||||
|
||||
void TuyaLight::write_state(light::LightState *state) {
|
||||
float brightness;
|
||||
state->current_values_as_brightness(&brightness);
|
||||
|
||||
if (brightness == 0.0f) {
|
||||
// turning off, first try via switch (if exists), then dimmer
|
||||
if (switch_id_.has_value()) {
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = *this->switch_id_;
|
||||
datapoint.type = TuyaDatapointType::BOOLEAN;
|
||||
datapoint.value_bool = false;
|
||||
|
||||
parent_->set_datapoint_value(datapoint);
|
||||
} else if (dimmer_id_.has_value()) {
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = *this->dimmer_id_;
|
||||
datapoint.type = TuyaDatapointType::INTEGER;
|
||||
datapoint.value_int = 0;
|
||||
parent_->set_datapoint_value(datapoint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto brightness_int = static_cast<uint32_t>(brightness * this->max_value_);
|
||||
brightness_int = std::max(brightness_int, this->min_value_);
|
||||
|
||||
if (this->dimmer_id_.has_value()) {
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = *this->dimmer_id_;
|
||||
datapoint.type = TuyaDatapointType::INTEGER;
|
||||
datapoint.value_int = brightness_int;
|
||||
parent_->set_datapoint_value(datapoint);
|
||||
}
|
||||
if (this->switch_id_.has_value()) {
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = *this->switch_id_;
|
||||
datapoint.type = TuyaDatapointType::BOOLEAN;
|
||||
datapoint.value_bool = true;
|
||||
parent_->set_datapoint_value(datapoint);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
36
esphome/components/tuya/light/tuya_light.h
Normal file
36
esphome/components/tuya/light/tuya_light.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/tuya/tuya.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
class TuyaLight : public Component, public light::LightOutput {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_dimmer_id(uint8_t dimmer_id) { this->dimmer_id_ = dimmer_id; }
|
||||
void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; }
|
||||
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
|
||||
void set_min_value(uint32_t min_value) { min_value_ = min_value; }
|
||||
void set_max_value(uint32_t max_value) { max_value_ = max_value; }
|
||||
light::LightTraits get_traits() override;
|
||||
void setup_state(light::LightState *state) override;
|
||||
void write_state(light::LightState *state) override;
|
||||
|
||||
protected:
|
||||
void update_dimmer_(uint32_t value);
|
||||
void update_switch_(uint32_t value);
|
||||
|
||||
Tuya *parent_;
|
||||
optional<uint8_t> dimmer_id_{};
|
||||
optional<uint8_t> switch_id_{};
|
||||
uint32_t min_value_ = 0;
|
||||
uint32_t max_value_ = 255;
|
||||
light::LightState *state_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
295
esphome/components/tuya/tuya.cpp
Normal file
295
esphome/components/tuya/tuya.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include "tuya.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
static const char *TAG = "tuya";
|
||||
|
||||
void Tuya::setup() {
|
||||
this->send_empty_command_(TuyaCommandType::MCU_CONF);
|
||||
this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||
}
|
||||
|
||||
void Tuya::loop() {
|
||||
while (this->available()) {
|
||||
uint8_t c;
|
||||
this->read_byte(&c);
|
||||
this->handle_char_(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Tuya:");
|
||||
if ((gpio_status_ != -1) || (gpio_reset_ != -1))
|
||||
ESP_LOGCONFIG(TAG, " GPIO MCU configuration not supported!");
|
||||
for (auto &info : this->datapoints_) {
|
||||
if (info.type == TuyaDatapointType::BOOLEAN)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %d: switch (value: %s)", info.id, ONOFF(info.value_bool));
|
||||
else if (info.type == TuyaDatapointType::INTEGER)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %d: int value (value: %d)", info.id, info.value_int);
|
||||
else if (info.type == TuyaDatapointType::ENUM)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %d: enum (value: %d)", info.id, info.value_enum);
|
||||
else if (info.type == TuyaDatapointType::BITMASK)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %d: bitmask (value: %x)", info.id, info.value_bitmask);
|
||||
else
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %d: unknown", info.id);
|
||||
}
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
bool Tuya::validate_message_() {
|
||||
uint32_t at = this->rx_message_.size() - 1;
|
||||
auto *data = &this->rx_message_[0];
|
||||
uint8_t new_byte = data[at];
|
||||
|
||||
// Byte 0: HEADER1 (always 0x55)
|
||||
if (at == 0)
|
||||
return new_byte == 0x55;
|
||||
// Byte 1: HEADER2 (always 0xAA)
|
||||
if (at == 1)
|
||||
return new_byte == 0xAA;
|
||||
|
||||
// Byte 2: VERSION
|
||||
// no validation for the following fields:
|
||||
uint8_t version = data[2];
|
||||
if (at == 2)
|
||||
return true;
|
||||
// Byte 3: COMMAND
|
||||
uint8_t command = data[3];
|
||||
if (at == 3)
|
||||
return true;
|
||||
|
||||
// Byte 4: LENGTH1
|
||||
// Byte 5: LENGTH2
|
||||
if (at <= 5)
|
||||
// no validation for these fields
|
||||
return true;
|
||||
|
||||
uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5]));
|
||||
|
||||
// wait until all data is read
|
||||
if (at - 6 < length)
|
||||
return true;
|
||||
|
||||
// Byte 6+LEN: CHECKSUM - sum of all bytes (including header) modulo 256
|
||||
uint8_t rx_checksum = new_byte;
|
||||
uint8_t calc_checksum = 0;
|
||||
for (uint32_t i = 0; i < 6 + length; i++)
|
||||
calc_checksum += data[i];
|
||||
|
||||
if (rx_checksum != calc_checksum) {
|
||||
ESP_LOGW(TAG, "Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
// valid message
|
||||
const uint8_t *message_data = data + 6;
|
||||
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s]", command, version,
|
||||
hexencode(message_data, length).c_str());
|
||||
this->handle_command_(command, version, message_data, length);
|
||||
|
||||
// return false to reset rx buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
void Tuya::handle_char_(uint8_t c) {
|
||||
this->rx_message_.push_back(c);
|
||||
if (!this->validate_message_()) {
|
||||
this->rx_message_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
||||
uint8_t c;
|
||||
switch ((TuyaCommandType) command) {
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
||||
if (buffer[0] == 0) {
|
||||
ESP_LOGI(TAG, "MCU restarted");
|
||||
this->send_empty_command_(TuyaCommandType::QUERY_STATE);
|
||||
}
|
||||
break;
|
||||
case TuyaCommandType::QUERY_PRODUCT: {
|
||||
// check it is a valid string
|
||||
bool valid = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (buffer[i] == 0x00) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
ESP_LOGD(TAG, "Tuya Product Code: %s", reinterpret_cast<const char *>(buffer));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TuyaCommandType::MCU_CONF:
|
||||
if (len >= 2) {
|
||||
gpio_status_ = buffer[0];
|
||||
gpio_reset_ = buffer[1];
|
||||
}
|
||||
// set wifi state LED to off or on depending on the MCU firmware
|
||||
// but it shouldn't be blinking
|
||||
c = 0x3;
|
||||
this->send_command_(TuyaCommandType::WIFI_STATE, &c, 1);
|
||||
this->send_empty_command_(TuyaCommandType::QUERY_STATE);
|
||||
break;
|
||||
case TuyaCommandType::WIFI_STATE:
|
||||
break;
|
||||
case TuyaCommandType::WIFI_RESET:
|
||||
ESP_LOGE(TAG, "TUYA_CMD_WIFI_RESET is not handled");
|
||||
break;
|
||||
case TuyaCommandType::WIFI_SELECT:
|
||||
ESP_LOGE(TAG, "TUYA_CMD_WIFI_SELECT is not handled");
|
||||
break;
|
||||
case TuyaCommandType::SET_DATAPOINT:
|
||||
break;
|
||||
case TuyaCommandType::STATE: {
|
||||
this->handle_datapoint_(buffer, len);
|
||||
break;
|
||||
}
|
||||
case TuyaCommandType::QUERY_STATE:
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "invalid command (%02x) received", command);
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
|
||||
if (len < 2)
|
||||
return;
|
||||
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = buffer[0];
|
||||
datapoint.type = (TuyaDatapointType) buffer[1];
|
||||
datapoint.value_uint = 0;
|
||||
|
||||
size_t data_size = (buffer[2] << 8) + buffer[3];
|
||||
const uint8_t *data = buffer + 4;
|
||||
size_t data_len = len - 4;
|
||||
if (data_size != data_len) {
|
||||
ESP_LOGW(TAG, "invalid datapoint update");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
if (data_len != 1)
|
||||
return;
|
||||
datapoint.value_bool = data[0];
|
||||
break;
|
||||
case TuyaDatapointType::INTEGER:
|
||||
if (data_len != 4)
|
||||
return;
|
||||
datapoint.value_uint =
|
||||
(uint32_t(data[0]) << 24) | (uint32_t(data[1]) << 16) | (uint32_t(data[2]) << 8) | (uint32_t(data[3]) << 0);
|
||||
break;
|
||||
case TuyaDatapointType::ENUM:
|
||||
if (data_len != 1)
|
||||
return;
|
||||
datapoint.value_enum = data[0];
|
||||
break;
|
||||
case TuyaDatapointType::BITMASK:
|
||||
if (data_len != 2)
|
||||
return;
|
||||
datapoint.value_bitmask = (uint16_t(data[0]) << 8) | (uint16_t(data[1]) << 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Datapoint %u update to %u", datapoint.id, datapoint.value_uint);
|
||||
|
||||
// Update internal datapoints
|
||||
bool found = false;
|
||||
for (auto &other : this->datapoints_) {
|
||||
if (other.id == datapoint.id) {
|
||||
other = datapoint;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
this->datapoints_.push_back(datapoint);
|
||||
// New datapoint found, reprint dump_config after a delay.
|
||||
this->set_timeout("datapoint_dump", 100, [this] { this->dump_config(); });
|
||||
}
|
||||
|
||||
// Run through listeners
|
||||
for (auto &listener : this->listeners_)
|
||||
if (listener.datapoint_id == datapoint.id)
|
||||
listener.on_datapoint(datapoint);
|
||||
}
|
||||
|
||||
void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) {
|
||||
uint8_t len_hi = len >> 8;
|
||||
uint8_t len_lo = len >> 0;
|
||||
this->write_array({0x55, 0xAA,
|
||||
0x00, // version
|
||||
(uint8_t) command, len_hi, len_lo});
|
||||
if (len != 0)
|
||||
this->write_array(buffer, len);
|
||||
|
||||
uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo;
|
||||
for (int i = 0; i < len; i++)
|
||||
checksum += buffer[i];
|
||||
this->write_byte(checksum);
|
||||
}
|
||||
|
||||
void Tuya::set_datapoint_value(TuyaDatapoint datapoint) {
|
||||
std::vector<uint8_t> buffer;
|
||||
ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint);
|
||||
for (auto &other : this->datapoints_) {
|
||||
if (other.id == datapoint.id) {
|
||||
if (other.value_uint == datapoint.value_uint) {
|
||||
ESP_LOGV(TAG, "Not sending unchanged value");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.push_back(datapoint.id);
|
||||
buffer.push_back(static_cast<uint8_t>(datapoint.type));
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
data.push_back(datapoint.value_bool);
|
||||
break;
|
||||
case TuyaDatapointType::INTEGER:
|
||||
data.push_back(datapoint.value_uint >> 24);
|
||||
data.push_back(datapoint.value_uint >> 16);
|
||||
data.push_back(datapoint.value_uint >> 8);
|
||||
data.push_back(datapoint.value_uint >> 0);
|
||||
break;
|
||||
case TuyaDatapointType::ENUM:
|
||||
data.push_back(datapoint.value_enum);
|
||||
break;
|
||||
case TuyaDatapointType::BITMASK:
|
||||
data.push_back(datapoint.value_bitmask >> 8);
|
||||
data.push_back(datapoint.value_bitmask >> 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.push_back(data.size() >> 8);
|
||||
buffer.push_back(data.size() >> 0);
|
||||
buffer.insert(buffer.end(), data.begin(), data.end());
|
||||
this->send_command_(TuyaCommandType::SET_DATAPOINT, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
|
||||
auto listener = TuyaDatapointListener{
|
||||
.datapoint_id = datapoint_id,
|
||||
.on_datapoint = func,
|
||||
};
|
||||
this->listeners_.push_back(listener);
|
||||
|
||||
// Run through existing datapoints
|
||||
for (auto &datapoint : this->datapoints_)
|
||||
if (datapoint.id == datapoint_id)
|
||||
func(datapoint);
|
||||
}
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
73
esphome/components/tuya/tuya.h
Normal file
73
esphome/components/tuya/tuya.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
enum class TuyaDatapointType : uint8_t {
|
||||
RAW = 0x00, // variable length
|
||||
BOOLEAN = 0x01, // 1 byte (0/1)
|
||||
INTEGER = 0x02, // 4 byte
|
||||
STRING = 0x03, // variable length
|
||||
ENUM = 0x04, // 1 byte
|
||||
BITMASK = 0x05, // 2 bytes
|
||||
};
|
||||
|
||||
struct TuyaDatapoint {
|
||||
uint8_t id;
|
||||
TuyaDatapointType type;
|
||||
union {
|
||||
bool value_bool;
|
||||
int value_int;
|
||||
uint32_t value_uint;
|
||||
uint8_t value_enum;
|
||||
uint16_t value_bitmask;
|
||||
};
|
||||
};
|
||||
|
||||
struct TuyaDatapointListener {
|
||||
uint8_t datapoint_id;
|
||||
std::function<void(TuyaDatapoint)> on_datapoint;
|
||||
};
|
||||
|
||||
enum class TuyaCommandType : uint8_t {
|
||||
HEARTBEAT = 0x00,
|
||||
QUERY_PRODUCT = 0x01,
|
||||
MCU_CONF = 0x02,
|
||||
WIFI_STATE = 0x03,
|
||||
WIFI_RESET = 0x04,
|
||||
WIFI_SELECT = 0x05,
|
||||
SET_DATAPOINT = 0x06,
|
||||
STATE = 0x07,
|
||||
QUERY_STATE = 0x08,
|
||||
};
|
||||
|
||||
class Tuya : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
||||
void set_datapoint_value(TuyaDatapoint datapoint);
|
||||
|
||||
protected:
|
||||
void handle_char_(uint8_t c);
|
||||
void handle_datapoint_(const uint8_t *buffer, size_t len);
|
||||
bool validate_message_();
|
||||
|
||||
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len);
|
||||
void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len);
|
||||
void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); }
|
||||
|
||||
int gpio_status_ = -1;
|
||||
int gpio_reset_ = -1;
|
||||
std::vector<TuyaDatapointListener> listeners_;
|
||||
std::vector<TuyaDatapoint> datapoints_;
|
||||
std::vector<uint8_t> rx_message_;
|
||||
};
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
@@ -29,11 +29,13 @@ def validate_rx_pin(value):
|
||||
return value
|
||||
|
||||
|
||||
CONF_STOP_BITS = 'stop_bits'
|
||||
CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(UARTComponent),
|
||||
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
|
||||
cv.Optional(CONF_TX_PIN): pins.output_pin,
|
||||
cv.Optional(CONF_RX_PIN): validate_rx_pin,
|
||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN))
|
||||
|
||||
|
||||
@@ -48,6 +50,7 @@ def to_code(config):
|
||||
cg.add(var.set_tx_pin(config[CONF_TX_PIN]))
|
||||
if CONF_RX_PIN in config:
|
||||
cg.add(var.set_rx_pin(config[CONF_RX_PIN]))
|
||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||
|
||||
|
||||
# A schema to use for all UART devices, all UART integrations must extend this!
|
||||
|
||||
@@ -25,7 +25,10 @@ void UARTComponent::setup() {
|
||||
}
|
||||
int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1;
|
||||
int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1;
|
||||
this->hw_serial_->begin(this->baud_rate_, SERIAL_8N1, rx, tx);
|
||||
uint32_t config = SERIAL_8N1;
|
||||
if (this->stop_bits_ == 2)
|
||||
config = SERIAL_8N2;
|
||||
this->hw_serial_->begin(this->baud_rate_, config, rx, tx);
|
||||
}
|
||||
|
||||
void UARTComponent::dump_config() {
|
||||
@@ -37,6 +40,7 @@ void UARTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
|
||||
}
|
||||
|
||||
void UARTComponent::write_byte(uint8_t data) {
|
||||
@@ -102,21 +106,27 @@ void UARTComponent::setup() {
|
||||
// Use Arduino HardwareSerial UARTs if all used pins match the ones
|
||||
// preconfigured by the platform. For example if RX disabled but TX pin
|
||||
// is 1 we still want to use Serial.
|
||||
uint32_t mode = UART_NB_BIT_8 | UART_PARITY_NONE;
|
||||
if (this->stop_bits_ == 1)
|
||||
mode |= UART_NB_STOP_BIT_1;
|
||||
else
|
||||
mode |= UART_NB_STOP_BIT_2;
|
||||
SerialConfig config = static_cast<SerialConfig>(mode);
|
||||
if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) {
|
||||
this->hw_serial_ = &Serial;
|
||||
this->hw_serial_->begin(this->baud_rate_);
|
||||
this->hw_serial_->begin(this->baud_rate_, config);
|
||||
} else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) {
|
||||
this->hw_serial_ = &Serial;
|
||||
this->hw_serial_->begin(this->baud_rate_);
|
||||
this->hw_serial_->begin(this->baud_rate_, config);
|
||||
this->hw_serial_->swap();
|
||||
} else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) {
|
||||
this->hw_serial_ = &Serial1;
|
||||
this->hw_serial_->begin(this->baud_rate_);
|
||||
this->hw_serial_->begin(this->baud_rate_, config);
|
||||
} else {
|
||||
this->sw_serial_ = new ESP8266SoftwareSerial();
|
||||
int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1;
|
||||
int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1;
|
||||
this->sw_serial_->setup(tx, rx, this->baud_rate_);
|
||||
this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +139,7 @@ void UARTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
|
||||
if (this->hw_serial_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Using hardware serial interface.");
|
||||
} else {
|
||||
@@ -231,7 +242,7 @@ void UARTComponent::flush() {
|
||||
}
|
||||
}
|
||||
|
||||
void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate) {
|
||||
void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits) {
|
||||
this->bit_time_ = F_CPU / baud_rate;
|
||||
if (tx_pin != -1) {
|
||||
auto pin = GPIOPin(tx_pin, OUTPUT);
|
||||
@@ -246,6 +257,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra
|
||||
this->rx_buffer_ = new uint8_t[this->rx_buffer_size_];
|
||||
pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING);
|
||||
}
|
||||
this->stop_bits_ = stop_bits;
|
||||
}
|
||||
void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) {
|
||||
uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500;
|
||||
@@ -262,6 +274,8 @@ void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg
|
||||
rec |= arg->read_bit_(&wait, start) << 7;
|
||||
// Stop bit
|
||||
arg->wait_(&wait, start);
|
||||
if (arg->stop_bits_ == 2)
|
||||
arg->wait_(&wait, start);
|
||||
|
||||
arg->rx_buffer_[arg->rx_in_pos_] = rec;
|
||||
arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_;
|
||||
@@ -289,6 +303,8 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
|
||||
this->write_bit_(data & (1 << 7), &wait, start);
|
||||
// Stop bit
|
||||
this->write_bit_(true, &wait, start);
|
||||
if (this->stop_bits_ == 2)
|
||||
this->wait_(&wait, start);
|
||||
enable_interrupts();
|
||||
}
|
||||
void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) {
|
||||
@@ -344,5 +360,16 @@ int UARTComponent::peek() {
|
||||
return data;
|
||||
}
|
||||
|
||||
void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits) {
|
||||
if (this->parent_->baud_rate_ != baud_rate) {
|
||||
ESP_LOGE(TAG, " Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate,
|
||||
this->parent_->baud_rate_);
|
||||
}
|
||||
if (this->parent_->stop_bits_ != stop_bits) {
|
||||
ESP_LOGE(TAG, " Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits,
|
||||
this->parent_->stop_bits_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uart
|
||||
} // namespace esphome
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace uart {
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
class ESP8266SoftwareSerial {
|
||||
public:
|
||||
void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate);
|
||||
void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits);
|
||||
|
||||
uint8_t read_byte();
|
||||
uint8_t peek_byte();
|
||||
@@ -33,6 +33,7 @@ class ESP8266SoftwareSerial {
|
||||
size_t rx_buffer_size_{512};
|
||||
volatile size_t rx_in_pos_{0};
|
||||
size_t rx_out_pos_{0};
|
||||
uint8_t stop_bits_;
|
||||
ISRInternalGPIOPin *tx_pin_{nullptr};
|
||||
ISRInternalGPIOPin *rx_pin_{nullptr};
|
||||
};
|
||||
@@ -72,9 +73,11 @@ class UARTComponent : public Component, public Stream {
|
||||
|
||||
void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; }
|
||||
void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; }
|
||||
void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; }
|
||||
|
||||
protected:
|
||||
bool check_read_timeout_(size_t len = 1);
|
||||
friend class UARTDevice;
|
||||
|
||||
HardwareSerial *hw_serial_{nullptr};
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
@@ -83,6 +86,7 @@ class UARTComponent : public Component, public Stream {
|
||||
optional<uint8_t> tx_pin_;
|
||||
optional<uint8_t> rx_pin_;
|
||||
uint32_t baud_rate_;
|
||||
uint8_t stop_bits_;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -100,6 +104,9 @@ class UARTDevice : public Stream {
|
||||
|
||||
void write_array(const uint8_t *data, size_t len) { this->parent_->write_array(data, len); }
|
||||
void write_array(const std::vector<uint8_t> &data) { this->parent_->write_array(data); }
|
||||
template<size_t N> void write_array(const std::array<uint8_t, N> &data) {
|
||||
this->parent_->write_array(data.data(), data.size());
|
||||
}
|
||||
|
||||
void write_str(const char *str) { this->parent_->write_str(str); }
|
||||
|
||||
@@ -107,6 +114,13 @@ class UARTDevice : public Stream {
|
||||
bool peek_byte(uint8_t *data) { return this->parent_->peek_byte(data); }
|
||||
|
||||
bool read_array(uint8_t *data, size_t len) { return this->parent_->read_array(data, len); }
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_array() { // NOLINT
|
||||
std::array<uint8_t, N> res;
|
||||
if (!this->read_array(res.data(), N)) {
|
||||
return {};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int available() override { return this->parent_->available(); }
|
||||
|
||||
@@ -116,6 +130,9 @@ class UARTDevice : public Stream {
|
||||
int read() override { return this->parent_->read(); }
|
||||
int peek() override { return this->parent_->peek(); }
|
||||
|
||||
/// Check that the configuration of the UART bus matches the provided values and otherwise print a warning
|
||||
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1);
|
||||
|
||||
protected:
|
||||
UARTComponent *parent_{nullptr};
|
||||
};
|
||||
|
||||
80
esphome/components/vl53l0x/LICENSE.txt
Normal file
80
esphome/components/vl53l0x/LICENSE.txt
Normal file
@@ -0,0 +1,80 @@
|
||||
Most of the code in this integration is based on the VL53L0x library
|
||||
by Pololu (Pololu Corporation), which in turn is based on the VL53L0X
|
||||
API from ST. The code has been adapted to work with ESPHome's i2c APIs.
|
||||
Please see the top-level LICENSE.txt for information about ESPHome's license.
|
||||
The licenses for Pololu's and ST's software are included below.
|
||||
Orignally taken from https://github.com/pololu/vl53l0x-arduino (accessed 20th october 2019).
|
||||
|
||||
=================================================================
|
||||
|
||||
Copyright (c) 2017 Pololu Corporation. For more information, see
|
||||
|
||||
https://www.pololu.com/
|
||||
https://forum.pololu.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
=================================================================
|
||||
|
||||
Most of the functionality of this library is based on the VL53L0X
|
||||
API provided by ST (STSW-IMG005), and some of the explanatory
|
||||
comments are quoted or paraphrased from the API source code, API
|
||||
user manual (UM2039), and the VL53L0X datasheet.
|
||||
|
||||
The following applies to source code reproduced or derived from
|
||||
the API:
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Copyright © 2016, STMicroelectronics International N.V. All
|
||||
rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of STMicroelectronics nor the
|
||||
names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
|
||||
NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
||||
-----------------------------------------------------------------
|
||||
0
esphome/components/vl53l0x/__init__.py
Normal file
0
esphome/components/vl53l0x/__init__.py
Normal file
24
esphome/components/vl53l0x/sensor.py
Normal file
24
esphome/components/vl53l0x/sensor.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
vl53l0x_ns = cg.esphome_ns.namespace('vl53l0x')
|
||||
VL53L0XSensor = vl53l0x_ns.class_('VL53L0XSensor', sensor.Sensor, cg.PollingComponent,
|
||||
i2c.I2CDevice)
|
||||
|
||||
CONF_SIGNAL_RATE_LIMIT = 'signal_rate_limit'
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2).extend({
|
||||
cv.GenerateID(): cv.declare_id(VL53L0XSensor),
|
||||
cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range(
|
||||
min=0.0, max=512.0, min_included=False, max_included=False)
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x29))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
249
esphome/components/vl53l0x/vl53l0x_sensor.cpp
Normal file
249
esphome/components/vl53l0x/vl53l0x_sensor.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "vl53l0x_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
/*
|
||||
* Most of the code in this integration is based on the VL53L0x library
|
||||
* by Pololu (Pololu Corporation), which in turn is based on the VL53L0X
|
||||
* API from ST.
|
||||
*
|
||||
* For more information about licensing, please view the included LICENSE.txt file
|
||||
* in the vl53l0x integration directory.
|
||||
*/
|
||||
|
||||
namespace esphome {
|
||||
namespace vl53l0x {
|
||||
|
||||
static const char *TAG = "vl53l0x";
|
||||
|
||||
void VL53L0XSensor::dump_config() {
|
||||
LOG_SENSOR("", "VL53L0X", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
void VL53L0XSensor::setup() {
|
||||
reg(0x89) |= 0x01;
|
||||
reg(0x88) = 0x00;
|
||||
|
||||
reg(0x80) = 0x01;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
stop_variable_ = reg(0x91).get();
|
||||
|
||||
reg(0x00) = 0x01;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x80) = 0x00;
|
||||
reg(0x60) |= 0x12;
|
||||
|
||||
auto rate_value = static_cast<uint16_t>(signal_rate_limit_ * 128);
|
||||
write_byte_16(0x44, rate_value);
|
||||
|
||||
reg(0x01) = 0xFF;
|
||||
|
||||
// getSpadInfo()
|
||||
reg(0x80) = 0x01;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
reg(0xFF) = 0x06;
|
||||
reg(0x83) |= 0x04;
|
||||
reg(0xFF) = 0x07;
|
||||
reg(0x81) = 0x01;
|
||||
reg(0x80) = 0x01;
|
||||
reg(0x94) = 0x6B;
|
||||
reg(0x83) = 0x00;
|
||||
|
||||
while (reg(0x83).get() == 0x00)
|
||||
yield();
|
||||
|
||||
reg(0x83) = 0x01;
|
||||
uint8_t tmp = reg(0x92).get();
|
||||
uint8_t spad_count = tmp & 0x7F;
|
||||
bool spad_type_is_aperture = tmp & 0x80;
|
||||
|
||||
reg(0x81) = 0x00;
|
||||
reg(0xFF) = 0x06;
|
||||
reg(0x83) &= ~0x04;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x00) = 0x01;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x80) = 0x00;
|
||||
|
||||
uint8_t ref_spad_map[6];
|
||||
this->read_bytes(0xB0, ref_spad_map, 6);
|
||||
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x4F) = 0x00;
|
||||
reg(0x4E) = 0x2C;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0xB6) = 0xB4;
|
||||
|
||||
uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0;
|
||||
uint8_t spads_enabled = 0;
|
||||
for (int i = 0; i < 48; i++) {
|
||||
uint8_t &val = ref_spad_map[i / 8];
|
||||
uint8_t mask = 1 << (i % 8);
|
||||
|
||||
if (i < first_spad_to_enable || spads_enabled == spad_count)
|
||||
val &= ~mask;
|
||||
else if (val & mask)
|
||||
spads_enabled += 1;
|
||||
}
|
||||
|
||||
this->write_bytes(0xB0, ref_spad_map, 6);
|
||||
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x09) = 0x00;
|
||||
reg(0x10) = 0x00;
|
||||
reg(0x11) = 0x00;
|
||||
reg(0x24) = 0x01;
|
||||
reg(0x25) = 0xFF;
|
||||
reg(0x75) = 0x00;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x4E) = 0x2C;
|
||||
reg(0x48) = 0x00;
|
||||
reg(0x30) = 0x20;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x30) = 0x09;
|
||||
reg(0x54) = 0x00;
|
||||
reg(0x31) = 0x04;
|
||||
reg(0x32) = 0x03;
|
||||
reg(0x40) = 0x83;
|
||||
reg(0x46) = 0x25;
|
||||
reg(0x60) = 0x00;
|
||||
reg(0x27) = 0x00;
|
||||
reg(0x50) = 0x06;
|
||||
reg(0x51) = 0x00;
|
||||
reg(0x52) = 0x96;
|
||||
reg(0x56) = 0x08;
|
||||
reg(0x57) = 0x30;
|
||||
reg(0x61) = 0x00;
|
||||
reg(0x62) = 0x00;
|
||||
reg(0x64) = 0x00;
|
||||
reg(0x65) = 0x00;
|
||||
reg(0x66) = 0xA0;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x22) = 0x32;
|
||||
reg(0x47) = 0x14;
|
||||
reg(0x49) = 0xFF;
|
||||
reg(0x4A) = 0x00;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x7A) = 0x0A;
|
||||
reg(0x7B) = 0x00;
|
||||
reg(0x78) = 0x21;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x23) = 0x34;
|
||||
reg(0x42) = 0x00;
|
||||
reg(0x44) = 0xFF;
|
||||
reg(0x45) = 0x26;
|
||||
reg(0x46) = 0x05;
|
||||
reg(0x40) = 0x40;
|
||||
reg(0x0E) = 0x06;
|
||||
reg(0x20) = 0x1A;
|
||||
reg(0x43) = 0x40;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x34) = 0x03;
|
||||
reg(0x35) = 0x44;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x31) = 0x04;
|
||||
reg(0x4B) = 0x09;
|
||||
reg(0x4C) = 0x05;
|
||||
reg(0x4D) = 0x04;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x44) = 0x00;
|
||||
reg(0x45) = 0x20;
|
||||
reg(0x47) = 0x08;
|
||||
reg(0x48) = 0x28;
|
||||
reg(0x67) = 0x00;
|
||||
reg(0x70) = 0x04;
|
||||
reg(0x71) = 0x01;
|
||||
reg(0x72) = 0xFE;
|
||||
reg(0x76) = 0x00;
|
||||
reg(0x77) = 0x00;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x0D) = 0x01;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x80) = 0x01;
|
||||
reg(0x01) = 0xF8;
|
||||
reg(0xFF) = 0x01;
|
||||
reg(0x8E) = 0x01;
|
||||
reg(0x00) = 0x01;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x80) = 0x00;
|
||||
|
||||
reg(0x0A) = 0x04;
|
||||
reg(0x84) &= ~0x10;
|
||||
reg(0x0B) = 0x01;
|
||||
|
||||
measurement_timing_budget_us_ = get_measurement_timing_budget_();
|
||||
reg(0x01) = 0xE8;
|
||||
set_measurement_timing_budget_(measurement_timing_budget_us_);
|
||||
reg(0x01) = 0x01;
|
||||
|
||||
if (!perform_single_ref_calibration_(0x40)) {
|
||||
ESP_LOGW(TAG, "1st reference calibration failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
reg(0x01) = 0x02;
|
||||
if (!perform_single_ref_calibration_(0x00)) {
|
||||
ESP_LOGW(TAG, "2nd reference calibration failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
reg(0x01) = 0xE8;
|
||||
}
|
||||
void VL53L0XSensor::update() {
|
||||
if (this->initiated_read_ || this->waiting_for_interrupt_) {
|
||||
this->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
// initiate single shot measurement
|
||||
reg(0x80) = 0x01;
|
||||
reg(0xFF) = 0x01;
|
||||
|
||||
reg(0x00) = 0x00;
|
||||
reg(0x91) = stop_variable_;
|
||||
reg(0x00) = 0x01;
|
||||
reg(0xFF) = 0x00;
|
||||
reg(0x80) = 0x00;
|
||||
|
||||
reg(0x00) = 0x01;
|
||||
this->waiting_for_interrupt_ = false;
|
||||
this->initiated_read_ = true;
|
||||
// wait for timeout
|
||||
}
|
||||
void VL53L0XSensor::loop() {
|
||||
if (this->initiated_read_) {
|
||||
if (reg(0x00).get() & 0x01) {
|
||||
// waiting
|
||||
} else {
|
||||
// done
|
||||
// wait until reg(0x13) & 0x07 is set
|
||||
this->initiated_read_ = false;
|
||||
this->waiting_for_interrupt_ = true;
|
||||
}
|
||||
}
|
||||
if (this->waiting_for_interrupt_) {
|
||||
if (reg(0x13).get() & 0x07) {
|
||||
uint16_t range_mm;
|
||||
this->read_byte_16(0x14 + 10, &range_mm);
|
||||
reg(0x0B) = 0x01;
|
||||
this->waiting_for_interrupt_ = false;
|
||||
|
||||
if (range_mm >= 8190) {
|
||||
ESP_LOGW(TAG, "'%s' - Distance is out of range, please move the target closer", this->name_.c_str());
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
float range_m = range_mm / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - Got distance %.3f m", this->name_.c_str(), range_m);
|
||||
this->publish_state(range_m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vl53l0x
|
||||
} // namespace esphome
|
||||
257
esphome/components/vl53l0x/vl53l0x_sensor.h
Normal file
257
esphome/components/vl53l0x/vl53l0x_sensor.h
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vl53l0x {
|
||||
|
||||
struct SequenceStepEnables {
|
||||
bool tcc, msrc, dss, pre_range, final_range;
|
||||
};
|
||||
|
||||
struct SequenceStepTimeouts {
|
||||
uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks;
|
||||
|
||||
uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks;
|
||||
uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us;
|
||||
};
|
||||
|
||||
class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void update() override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void set_signal_rate_limit(float signal_rate_limit) { signal_rate_limit_ = signal_rate_limit; }
|
||||
|
||||
protected:
|
||||
uint32_t get_measurement_timing_budget_() {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
|
||||
uint16_t start_overhead = 1910;
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
|
||||
// "Start and end overhead times always present"
|
||||
uint32_t budget_us = start_overhead + end_overhead;
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc)
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
|
||||
if (enables.dss)
|
||||
budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
else if (enables.msrc)
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
|
||||
if (enables.pre_range)
|
||||
budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
|
||||
if (enables.final_range)
|
||||
budget_us += (timeouts.final_range_us + final_range_overhead);
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
return budget_us;
|
||||
}
|
||||
|
||||
bool set_measurement_timing_budget_(uint32_t budget_us) {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
|
||||
uint16_t start_overhead = 1320; // note that this is different than the value in get_
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
|
||||
uint32_t min_timing_budget = 20000;
|
||||
|
||||
if (budget_us < min_timing_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t used_budget_us = start_overhead + end_overhead;
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
}
|
||||
|
||||
if (enables.dss) {
|
||||
used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
} else if (enables.msrc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range) {
|
||||
used_budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
}
|
||||
|
||||
if (enables.final_range) {
|
||||
used_budget_us += final_range_overhead;
|
||||
|
||||
// "Note that the final range timeout is determined by the timing
|
||||
// budget and the sum of all other timeouts within the sequence.
|
||||
// If there is no room for the final range timeout, then an error
|
||||
// will be set. Otherwise the remaining time will be applied to
|
||||
// the final range."
|
||||
|
||||
if (used_budget_us > budget_us) {
|
||||
// "Requested timeout too big."
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t final_range_timeout_us = budget_us - used_budget_us;
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
|
||||
|
||||
// "For the final range timeout, the pre-range timeout
|
||||
// must be added. To do this both final and pre-range
|
||||
// timeouts must be expressed in macro periods MClks
|
||||
// because they have different vcsel periods."
|
||||
|
||||
uint16_t final_range_timeout_mclks =
|
||||
timeout_microseconds_to_mclks_(final_range_timeout_us, timeouts.final_range_vcsel_period_pclks);
|
||||
|
||||
if (enables.pre_range) {
|
||||
final_range_timeout_mclks += timeouts.pre_range_mclks;
|
||||
}
|
||||
|
||||
write_byte_16(0x71, encode_timeout_(final_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void get_sequence_step_enables_(SequenceStepEnables *enables) {
|
||||
uint8_t sequence_config = reg(0x01).get();
|
||||
enables->tcc = (sequence_config >> 4) & 0x1;
|
||||
enables->dss = (sequence_config >> 3) & 0x1;
|
||||
enables->msrc = (sequence_config >> 2) & 0x1;
|
||||
enables->pre_range = (sequence_config >> 6) & 0x1;
|
||||
enables->final_range = (sequence_config >> 7) & 0x1;
|
||||
}
|
||||
|
||||
enum VcselPeriodType { VCSEL_PERIOD_PRE_RANGE, VCSEL_PERIOD_FINAL_RANGE };
|
||||
|
||||
void get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts) {
|
||||
timeouts->pre_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_PRE_RANGE);
|
||||
|
||||
timeouts->msrc_dss_tcc_mclks = reg(0x46).get() + 1;
|
||||
timeouts->msrc_dss_tcc_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->msrc_dss_tcc_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
uint16_t value;
|
||||
read_byte_16(0x51, &value);
|
||||
timeouts->pre_range_mclks = decode_timeout_(value);
|
||||
timeouts->pre_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->pre_range_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
timeouts->final_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_FINAL_RANGE);
|
||||
|
||||
read_byte_16(0x71, &value);
|
||||
timeouts->final_range_mclks = decode_timeout_(value);
|
||||
|
||||
if (enables->pre_range) {
|
||||
timeouts->final_range_mclks -= timeouts->pre_range_mclks;
|
||||
}
|
||||
|
||||
timeouts->final_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->final_range_mclks, timeouts->final_range_vcsel_period_pclks);
|
||||
}
|
||||
|
||||
uint8_t get_vcsel_pulse_period_(VcselPeriodType type) {
|
||||
uint8_t vcsel;
|
||||
if (type == VCSEL_PERIOD_PRE_RANGE)
|
||||
vcsel = reg(0x50).get();
|
||||
else if (type == VCSEL_PERIOD_FINAL_RANGE)
|
||||
vcsel = reg(0x70).get();
|
||||
else
|
||||
return 255;
|
||||
|
||||
return (vcsel + 1) << 1;
|
||||
}
|
||||
|
||||
uint32_t get_macro_period_(uint8_t vcsel_period_pclks) {
|
||||
return ((2304UL * vcsel_period_pclks * 1655UL) + 500UL) / 1000UL;
|
||||
}
|
||||
|
||||
uint32_t timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
|
||||
}
|
||||
uint32_t timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
|
||||
}
|
||||
|
||||
uint16_t decode_timeout_(uint16_t reg_val) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint8_t msb = (reg_val >> 8) & 0xFF;
|
||||
uint8_t lsb = (reg_val >> 0) & 0xFF;
|
||||
return (uint16_t(lsb) << msb) + 1;
|
||||
}
|
||||
uint16_t encode_timeout_(uint16_t timeout_mclks) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint32_t ls_byte = 0;
|
||||
uint16_t ms_byte = 0;
|
||||
|
||||
if (timeout_mclks <= 0)
|
||||
return 0;
|
||||
|
||||
ls_byte = timeout_mclks - 1;
|
||||
|
||||
while ((ls_byte & 0xFFFFFF00) > 0) {
|
||||
ls_byte >>= 1;
|
||||
ms_byte++;
|
||||
}
|
||||
|
||||
return (ms_byte << 8) | (ls_byte & 0xFF);
|
||||
}
|
||||
|
||||
bool perform_single_ref_calibration_(uint8_t vhv_init_byte) {
|
||||
reg(0x00) = 0x01 | vhv_init_byte; // VL53L0X_REG_SYSRANGE_MODE_START_STOP
|
||||
|
||||
uint32_t start = millis();
|
||||
while ((reg(0x13).get() & 0x07) == 0) {
|
||||
if (millis() - start > 1000)
|
||||
return false;
|
||||
yield();
|
||||
}
|
||||
|
||||
reg(0x0B) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float signal_rate_limit_;
|
||||
uint32_t measurement_timing_budget_us_;
|
||||
bool initiated_read_{false};
|
||||
bool waiting_for_interrupt_{false};
|
||||
uint8_t stop_variable_;
|
||||
};
|
||||
|
||||
} // namespace vl53l0x
|
||||
} // namespace esphome
|
||||
@@ -3,7 +3,8 @@ import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
DEPENDENCIES = ['network', 'async_tcp']
|
||||
DEPENDENCIES = ['network']
|
||||
AUTO_LOAD = ['async_tcp']
|
||||
|
||||
web_server_base_ns = cg.esphome_ns.namespace('web_server_base')
|
||||
WebServerBase = web_server_base_ns.class_('WebServerBase', cg.Component)
|
||||
@@ -22,4 +23,4 @@ def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
cg.add_library('FS', None)
|
||||
# https://github.com/OttoWinter/ESPAsyncWebServer/blob/master/library.json
|
||||
cg.add_library('ESPAsyncWebServer-esphome', '1.2.4')
|
||||
cg.add_library('ESPAsyncWebServer-esphome', '1.2.5')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
MAJOR_VERSION = 1
|
||||
MINOR_VERSION = 14
|
||||
PATCH_VERSION = '0b1'
|
||||
PATCH_VERSION = '0b3'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
|
||||
@@ -156,6 +156,7 @@ CONF_FILTERS = 'filters'
|
||||
CONF_FILTER_OUT = 'filter_out'
|
||||
CONF_FLASH_LENGTH = 'flash_length'
|
||||
CONF_FOR = 'for'
|
||||
CONF_FORCE_UPDATE = 'force_update'
|
||||
CONF_FORMALDEHYDE = 'formaldehyde'
|
||||
CONF_FORMAT = 'format'
|
||||
CONF_FREQUENCY = 'frequency'
|
||||
@@ -323,6 +324,7 @@ CONF_PM_2_5 = 'pm_2_5'
|
||||
CONF_PORT = 'port'
|
||||
CONF_POSITION = 'position'
|
||||
CONF_POWER = 'power'
|
||||
CONF_POWER_FACTOR = 'power_factor'
|
||||
CONF_POWER_ON_VALUE = 'power_on_value'
|
||||
CONF_POWER_SAVE_MODE = 'power_save_mode'
|
||||
CONF_POWER_SUPPLY = 'power_supply'
|
||||
@@ -479,6 +481,7 @@ ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download'
|
||||
ICON_BRIGHTNESS_5 = 'mdi:brightness-5'
|
||||
ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon'
|
||||
ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline'
|
||||
ICON_CURRENT_AC = 'mdi:current-ac'
|
||||
ICON_EMPTY = ''
|
||||
ICON_FLASH = 'mdi:flash'
|
||||
ICON_FLOWER = 'mdi:flower'
|
||||
|
||||
@@ -73,9 +73,8 @@ void Application::loop() {
|
||||
|
||||
const uint32_t end = millis();
|
||||
if (end - start > 200) {
|
||||
ESP_LOGV(TAG, "A component took a long time in a loop() cycle (%.1f s).", (end - start) / 1e3f);
|
||||
ESP_LOGV(TAG, "A component took a long time in a loop() cycle (%.2f s).", (end - start) / 1e3f);
|
||||
ESP_LOGV(TAG, "Components should block for at most 20-30ms in loop().");
|
||||
ESP_LOGV(TAG, "This will become a warning soon.");
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
|
||||
@@ -314,4 +314,20 @@ std::array<uint8_t, 2> decode_uint16(uint16_t value) {
|
||||
return {msb, lsb};
|
||||
}
|
||||
|
||||
std::string hexencode(const uint8_t *data, uint32_t len) {
|
||||
char buf[20];
|
||||
std::string res;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i + 1 != len) {
|
||||
sprintf(buf, "%02X.", data[i]);
|
||||
} else {
|
||||
sprintf(buf, "%02X ", data[i]);
|
||||
}
|
||||
res += buf;
|
||||
}
|
||||
sprintf(buf, "(%u)", len);
|
||||
res += buf;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -156,6 +156,9 @@ enum ParseOnOffState {
|
||||
|
||||
ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr);
|
||||
|
||||
// Encode raw data to a human-readable string (for debugging)
|
||||
std::string hexencode(const uint8_t *data, uint32_t len);
|
||||
|
||||
// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
|
||||
template<int...> struct seq {}; // NOLINT
|
||||
template<int N, int... S> struct gens : gens<N - 1, N - 1, S...> {}; // NOLINT
|
||||
|
||||
@@ -10,10 +10,10 @@ include_dir = include
|
||||
|
||||
[common]
|
||||
lib_deps =
|
||||
AsyncTCP@1.0.3
|
||||
AsyncMqttClient-esphome@0.8.2
|
||||
AsyncTCP@1.1.1
|
||||
AsyncMqttClient-esphome@0.8.3
|
||||
ArduinoJson-esphomelib@5.13.3
|
||||
ESPAsyncWebServer-esphome@1.2.3
|
||||
ESPAsyncWebServer-esphome@1.2.5
|
||||
FastLED@3.2.9
|
||||
NeoPixelBus-esphome@2.5.2
|
||||
ESPAsyncTCP-esphome@1.2.2
|
||||
|
||||
@@ -185,6 +185,7 @@ sensor:
|
||||
accuracy_decimals: 5
|
||||
expire_after: 120s
|
||||
setup_priority: -100
|
||||
force_update: true
|
||||
filters:
|
||||
- offset: 2.0
|
||||
- multiply: 1.2
|
||||
@@ -522,6 +523,9 @@ sensor:
|
||||
name: "Living Room Humidity 8"
|
||||
address: 0x44
|
||||
update_interval: 15s
|
||||
- platform: sts3x
|
||||
name: "Living Room Temperature 9"
|
||||
address: 0x4A
|
||||
- platform: scd30
|
||||
co2:
|
||||
name: "Living Room CO2 9"
|
||||
@@ -531,6 +535,22 @@ sensor:
|
||||
name: "Living Room Humidity 9"
|
||||
address: 0x61
|
||||
update_interval: 15s
|
||||
- platform: sgp30
|
||||
eco2:
|
||||
name: "Workshop eCO2"
|
||||
accuracy_decimals: 1
|
||||
tvoc:
|
||||
name: "Workshop TVOC"
|
||||
accuracy_decimals: 1
|
||||
address: 0x58
|
||||
update_interval: 5s
|
||||
- platform: shtcx
|
||||
temperature:
|
||||
name: "Living Room Temperature 10"
|
||||
humidity:
|
||||
name: "Living Room Humidity 10"
|
||||
address: 0x70
|
||||
update_interval: 15s
|
||||
- platform: template
|
||||
name: "Template Sensor"
|
||||
id: template_sensor
|
||||
@@ -1333,6 +1353,13 @@ display:
|
||||
reset_pin: GPIO23
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: ssd1325_spi
|
||||
model: "SSD1325 128x64"
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
reset_pin: GPIO23
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: waveshare_epaper
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
|
||||
114
tests/test3.yaml
114
tests/test3.yaml
@@ -60,6 +60,83 @@ api:
|
||||
- float_arr.size()
|
||||
- string_arr[0].c_str()
|
||||
- string_arr.size()
|
||||
- service: dfplayer_next
|
||||
then:
|
||||
- dfplayer.play_next:
|
||||
- service: dfplayer_previous
|
||||
then:
|
||||
- dfplayer.play_previous:
|
||||
- service: dfplayer_play
|
||||
variables:
|
||||
file: int
|
||||
then:
|
||||
- dfplayer.play: !lambda 'return file;'
|
||||
- service: dfplayer_play_loop
|
||||
variables:
|
||||
file: int
|
||||
loop_: bool
|
||||
then:
|
||||
- dfplayer.play:
|
||||
file: !lambda 'return file;'
|
||||
loop: !lambda 'return loop_;'
|
||||
- service: dfplayer_play_folder
|
||||
variables:
|
||||
folder: int
|
||||
file: int
|
||||
then:
|
||||
- dfplayer.play_folder:
|
||||
folder: !lambda 'return folder;'
|
||||
file: !lambda 'return file;'
|
||||
|
||||
- service: dfplayer_play_loo_folder
|
||||
variables:
|
||||
folder: int
|
||||
then:
|
||||
- dfplayer.play_folder:
|
||||
folder: !lambda 'return folder;'
|
||||
loop: True
|
||||
|
||||
- service: dfplayer_set_device
|
||||
variables:
|
||||
device: int
|
||||
then:
|
||||
- dfplayer.set_device:
|
||||
device: TF_CARD
|
||||
|
||||
- service: dfplayer_set_volume
|
||||
variables:
|
||||
volume: int
|
||||
then:
|
||||
- dfplayer.set_volume: !lambda 'return volume;'
|
||||
- service: dfplayer_set_eq
|
||||
variables:
|
||||
preset: int
|
||||
then:
|
||||
- dfplayer.set_eq: !lambda 'return static_cast<dfplayer::EqPreset>(preset);'
|
||||
|
||||
- service: dfplayer_sleep
|
||||
then:
|
||||
- dfplayer.sleep
|
||||
|
||||
- service: dfplayer_reset
|
||||
then:
|
||||
- dfplayer.reset
|
||||
|
||||
- service: dfplayer_start
|
||||
then:
|
||||
- dfplayer.start
|
||||
|
||||
- service: dfplayer_pause
|
||||
then:
|
||||
- dfplayer.pause
|
||||
|
||||
- service: dfplayer_stop
|
||||
then:
|
||||
- dfplayer.stop
|
||||
|
||||
- service: dfplayer_random
|
||||
then:
|
||||
- dfplayer.random
|
||||
|
||||
wifi:
|
||||
ssid: 'MySSID'
|
||||
@@ -214,6 +291,31 @@ sensor:
|
||||
name: ADE7953 Active Power A
|
||||
active_power_b:
|
||||
name: ADE7953 Active Power B
|
||||
- platform: pzem004t
|
||||
voltage:
|
||||
name: "PZEM00T Voltage"
|
||||
current:
|
||||
name: "PZEM004T Current"
|
||||
power:
|
||||
name: "PZEM004T Power"
|
||||
- platform: pzemac
|
||||
voltage:
|
||||
name: "PZEMAC Voltage"
|
||||
current:
|
||||
name: "PZEMAC Current"
|
||||
power:
|
||||
name: "PZEMAC Power"
|
||||
frequency:
|
||||
name: "PZEMAC Frequency"
|
||||
power_factor:
|
||||
name: "PZEMAC Power Factor"
|
||||
- platform: pzemdc
|
||||
voltage:
|
||||
name: "PZEMDC Voltage"
|
||||
current:
|
||||
name: "PZEMDC Current"
|
||||
power:
|
||||
name: "PZEMDC Power"
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
@@ -306,6 +408,8 @@ text_sensor:
|
||||
- lambda: !lambda |-
|
||||
ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str());
|
||||
- script.execute: my_script
|
||||
- script.wait: my_script
|
||||
- script.stop: my_script
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
@@ -532,3 +636,13 @@ sim800l:
|
||||
- sim800l.send_sms:
|
||||
message: 'hello you'
|
||||
recipient: '+1234'
|
||||
|
||||
dfplayer:
|
||||
on_finished_playback:
|
||||
then:
|
||||
if:
|
||||
condition:
|
||||
not:
|
||||
dfplayer.is_playing
|
||||
then:
|
||||
logger.log: 'Playback finished event'
|
||||
|
||||
Reference in New Issue
Block a user