1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 23:51:47 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
Otto Winter
929600d7f7 Fix merge 2019-10-24 22:16:09 +02:00
Otto Winter
163d0c55ab Bump version to v1.14.0b4 2019-10-24 22:02:40 +02:00
Otto Winter
327ccb241e Make file generation saving atomic (#792)
* Make file generation saving atomic

* Lint

* Python 2 Compat

* Fix

* Handle file not found error
2019-10-24 22:02:29 +02:00
Otto Winter
6b3c7b0854 Fix scheduler first execution (#798)
* Fix scheduler first execution not immediately

* Also update sensor filters
2019-10-24 22:02:29 +02:00
Otto Winter
681dcb51da Fix MQTT not showing logs with Python 3 (#797)
* Fix MQTT logging for Python 3

* Also fix captive portal PACKED
2019-10-24 22:02:29 +02:00
Chris Debenham
576d5021fd Add missing include - fixes missing GPIOPin definition (#794) 2019-10-24 22:02:29 +02:00
Otto Winter
6cd76f00ac Implement more dump_configs (#791) 2019-10-24 22:02:29 +02:00
Otto Winter
6f63a62a8d Add lint check for integer constants (#775) 2019-10-24 22:02:23 +02:00
Otto Winter
8867a0fcfb Add additional custom lint checks (#790) 2019-10-24 21:59:24 +02:00
Otto Winter
42b4a166ec Bump version to v1.14.0b3 2019-10-22 23:01:55 +02:00
Otto Winter
c27fd0f01a Fix modbus CRC calculation (#789)
* Fix modbus CRC calculation

Fixes https://github.com/esphome/feature-requests/issues/49#issuecomment-545045776

* Fix
2019-10-22 23:01:51 +02:00
Otto Winter
dcb4a0a81e Add Python 2 deprecation notice (#784)
* Add Python 2 deprecation notice

* Update __main__.py
2019-10-22 23:01:51 +02:00
Nils Schulte
17da9fddc3 web_server_base AUTO_LOAD includes ASYNC_TCP (#788)
* web_server_base AUTO_LOAD includes ASYNC_TCP

fix AUTO_LOAD of web_server_base to include ASYNC_TCP

* Remove from dependencies


Co-authored-by: Otto Winter <otto@otto-winter.com>
2019-10-22 23:01:51 +02:00
Otto Winter
31aa3c55ca Fix ledc can't find bit_depth (#786)
Fixes https://github.com/esphome/issues/issues/759
2019-10-22 23:01:51 +02:00
Otto Winter
eca3685ea0 Update docker base image to 2.0.1 (#785) 2019-10-22 23:01:50 +02:00
Otto Winter
bd216c5c63 Add sensor force_update option (#783)
* Add sensor force_update option

* Add test
2019-10-22 23:01:50 +02:00
amishv
31ff76427c Implementation of LCD Clear (#781)
* Implementation of LCD Clear

* Implementation of LCD Clear

* Implementation of LCD Clear

* Implementation of LCD Clear
2019-10-22 23:01:50 +02:00
Otto Winter
2229aa6ccc Change message 2019-10-20 19:31:16 +02:00
Otto Winter
872b468415 Lint 2019-10-20 19:31:00 +02:00
Otto Winter
9f022a7433 Tuya Set gamma correction and transition length defaults
See also https://github.com/esphome/esphome-docs/pull/353/files#r336751499
2019-10-20 19:31:00 +02:00
Otto Winter
85a958e300 Link pip&python in lint Dockerfile 2019-10-20 19:30:45 +02:00
Otto Winter
0ee56195ae Bump version to v1.14.0b2 2019-10-20 19:29:56 +02:00
Otto Winter
48f52db1d9 Update AsyncMQTTClient/ESPAsyncWebServer (#779) 2019-10-20 19:29:48 +02:00
Otto Winter
d2c7afeef0 Add PZEM004T/PZEMAC/PZEMDC Support (#587)
* Add PZEM004T Support

* Don't flush as much

* Update pzem004t.cpp

* Add generalized modbus

* Add PZEMAC

* Add PZEMDC

* Fix file modes

* Lint

* Fix

* Fix

* Add check_uart_settings
2019-10-20 19:29:48 +02:00
Otto Winter
644aec791e Add GPIO Switch interlock wait time (#777)
* Add interlock wait time to gpio switch

Fixes https://github.com/esphome/issues/issues/753

* Format

* Fix
2019-10-20 19:29:48 +02:00
Otto Winter
b70a0325c5 Vl53l0x (#644)
* VL530LX

* VL53L0X

* Updates

* License

* Lint
2019-10-20 19:29:47 +02:00
Otto Winter
268387f829 Add script.wait action (#778)
Fixes https://github.com/esphome/feature-requests/issues/416, fixes https://github.com/esphome/issues/issues/572
2019-10-20 19:29:47 +02:00
Samuel Sieb
b975caef1e Add new component for Tuya dimmers (#743)
* Add new component for Tuya dimmers

* Update code

* Class naming

* Log output

* Fixes

* Lint

* Format

* Fix test

* log setting datapoint values

* remove in_setup_ and fix datapoint handling


Co-authored-by: Samuel Sieb <samuel@sieb.net>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2019-10-20 19:29:47 +02:00
Guillermo Ruffino
54fe1c7d55 Add dfplayer mini component (#655)
* Add dfplayer mini component

* receiving some data

* implemented many actions

* lint

* undo homeassistant_time.h

* Update esphome/components/dfplayer/__init__.py

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Update esphome/components/dfplayer/dfplayer.cpp

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* add set device. fixes

* lint

* Fixes and sync with docs

* add test

* lint

* lint

* lint
2019-10-20 19:29:47 +02:00
Nad
89c1274d56 Add support for STS3x Temperature sensors (#669)
* Add support for Sensirion STS3x Temperature sensors

* Removed humidty reading from STS3x sensor

* Fixed line error and operand error

* Fixed syntax

* Add test snippet for STS3x sensor

* Clean up

* #550 Proactive fix for STS3x component reporting WARNING status and reinitialzing similar to SHT3xd

* Flattened config.

* Fixed missing temperature unit

* Code formatting

* Added marking for future commands

* Cleanup

* Removed whitespace

* Cleanup

* Cleanup
2019-10-20 19:29:47 +02:00
Nad
f9ca3f1c27 Add support for SHTCx Temperature sensors (#676)
* Add support for Sensirion STS3x Temperature sensors

* Removed humidty reading from STS3x sensor

* Fixed line error and operand error

* Fixed syntax

* Add test snippet for STS3x sensor

* Clean up

* Add support for Sensirion SHTC1 and SHTC3 Temperature sensors

* Fixed the test

* Fix lint issues

* Update esphome/components/shtcx/shtcx.cpp

Good point.

Co-Authored-By: Otto Winter <otto@otto-winter.com>

* Refactored device type identification and logging

* Refactoring and cleanup

* Remove sts3x


Co-authored-by: Otto Winter <otto@otto-winter.com>
2019-10-20 19:29:46 +02:00
Nad
26dbc30279 Add support for SGP30 eCO2 and TVOC sensors (#679)
* Add support for SGP30 eCO2 and TVOC sensors

* Added test for SGP30

* Lint issues fixed

* Lint fixes

* Fixed light lengths

* Cleanup

* Add support for Sensirion SCD30 CO2 sensors

* Fixed few lint issues

* Lint fixes

* Fixed line ending for lint

* Cleanup

* Refactored float conversion

* Refactor unnecessary return

* Refactoring and cleanup

* Updated uptime_sensor_ referencing and simplified checking on availability of copensation

* Temperature and Humidity source moved to a separate compensation block; Dependency for Uptime sensor removed.

* Both humidity_source and temperature_source are now mandatory if the compensation block is defined;

* Clean up

* Cleanup

* Cleanup in search of perfection

* Use correct comment style


Co-authored-by: Otto Winter <otto@otto-winter.com>
2019-10-20 19:29:46 +02:00
Evan Coleman
4bee316425 Add SSD1325 Display Component (#736)
* add ssd1325 component

* fix i2c

* remove ssd1325 i2c

* add test

* set max contrast

* No macros - see styleguide

* Remove invalid function

* Formatting


Co-authored-by: Otto Winter <otto@otto-winter.com>
2019-10-20 19:29:46 +02:00
141 changed files with 4374 additions and 260 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,6 +31,10 @@ uint8_t I2CAS3935Component::read_register(uint8_t reg) {
}
return value;
}
void I2CAS3935Component::dump_config() {
AS3935Component::dump_config();
LOG_I2C_DEVICE(this);
}
} // namespace as3935_i2c
} // namespace esphome

View File

@@ -10,6 +10,9 @@ namespace esphome {
namespace as3935_i2c {
class I2CAS3935Component : public as3935::AS3935Component, public i2c::I2CDevice {
public:
void dump_config() override;
protected:
void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) override;
uint8_t read_register(uint8_t reg) override;

View File

@@ -145,6 +145,14 @@ Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void BangBangClimate::dump_config() {
LOG_CLIMATE("", "Bang Bang Climate", this);
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_));
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low);
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high);
}
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig(float default_temperature_low,

View File

@@ -21,6 +21,7 @@ class BangBangClimate : public climate::Climate, public Component {
public:
BangBangClimate();
void setup() override;
void dump_config() override;
void set_sensor(sensor::Sensor *sensor);
Trigger<> *get_idle_trigger() const;

View File

@@ -10,9 +10,9 @@ namespace binary_sensor {
#define LOG_BINARY_SENSOR(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
if (!obj->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, prefix " Device Class: '%s'", obj->get_device_class().c_str()); \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); \
} \
}

View File

@@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome.components import sensor, binary_sensor
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP
DEPENDENCIES = ['binary_sensor']
@@ -11,7 +11,6 @@ binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
CONF_GROUP = 'group'
SENSOR_MAP_TYPES = {
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
}

View File

@@ -166,6 +166,7 @@ float CaptivePortal::get_setup_priority() const {
// Before WiFi
return setup_priority::WIFI + 1.0f;
}
void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
CaptivePortal *global_captive_portal = nullptr;

View File

@@ -2,6 +2,7 @@
#include <DNSServer.h>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/components/web_server_base/web_server_base.h"
@@ -18,6 +19,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
public:
CaptivePortal(web_server_base::WebServerBase *base);
void setup() override;
void dump_config() override;
void loop() override {
if (this->dns_server_ != nullptr)
this->dns_server_->processNextRequest();

View File

@@ -9,6 +9,11 @@
namespace esphome {
namespace climate {
#define LOG_CLIMATE(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
}
class Climate;
/** This class is used to encode all control actions on a climate device.

View File

@@ -1,8 +1,11 @@
#include "climate_ir.h"
#include "esphome/core/log.h"
namespace esphome {
namespace climate {
static const char *TAG = "climate_ir";
climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
@@ -52,6 +55,13 @@ void ClimateIR::control(const climate::ClimateCall &call) {
this->transmit_state();
this->publish_state();
}
void ClimateIR::dump_config() {
LOG_CLIMATE("", "IR Climate", this);
ESP_LOGCONFIG(TAG, " Min. Temperature: %.1f°C", this->minimum_temperature_);
ESP_LOGCONFIG(TAG, " Max. Temperature: %.1f°C", this->maximum_temperature_);
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
}
} // namespace climate
} // namespace esphome

View File

@@ -25,6 +25,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
}
void setup() override;
void dump_config() override;
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}

View File

@@ -1,18 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate, remote_transmitter, remote_receiver, sensor
from esphome.const import CONF_ID, CONF_SENSOR
from esphome.components.remote_base import CONF_TRANSMITTER_ID, CONF_RECEIVER_ID
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
AUTO_LOAD = ['sensor', 'climate_ir']
coolix_ns = cg.esphome_ns.namespace('coolix')
CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component)
CONF_TRANSMITTER_ID = 'transmitter_id'
CONF_RECEIVER_ID = 'receiver_id'
CONF_SUPPORTS_HEAT = 'supports_heat'
CONF_SUPPORTS_COOL = 'supports_cool'
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(CoolixClimate),
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),

View File

@@ -13,13 +13,13 @@ const extern float COVER_CLOSED;
#define LOG_COVER(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
auto traits_ = obj->get_traits(); \
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, prefix " Assumed State: YES"); \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!obj->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, prefix " Device Class: '%s'", obj->get_device_class().c_str()); \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); \
} \
}

View File

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

View File

@@ -1,13 +1,12 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY
from .. import custom_ns
CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor')
CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor')
CONF_BINARY = 'binary'
CONF_FLOAT = 'float'
CONFIG_SCHEMA = cv.typed_schema({

View File

@@ -0,0 +1,219 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
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_LOOP = 'loop'
CONF_VOLUME = 'volume'
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

View File

@@ -0,0 +1,124 @@
#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_++;
}
}
void DFPlayer::dump_config() {
ESP_LOGCONFIG(TAG, "DFPlayer:");
this->check_uart_settings(9600);
}
} // namespace dfplayer
} // namespace esphome

View File

@@ -0,0 +1,166 @@
#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 dump_config() override;
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

View File

@@ -84,8 +84,8 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type); \
ESP_LOGCONFIG(TAG, prefix " Rotations: %d °", obj->rotation_); \
ESP_LOGCONFIG(TAG, prefix " Dimensions: %dpx x %dpx", obj->get_width(), obj->get_height()); \
ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, obj->rotation_); \
ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, obj->get_width(), obj->get_height()); \
}
class DisplayBuffer {

View File

@@ -31,6 +31,11 @@ static esp_ble_adv_params_t ble_adv_params = {
static esp_ble_ibeacon_head_t ibeacon_common_head = {
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
void ESP32BLEBeacon::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:");
ESP_LOGCONFIG(TAG, " Major: %u, Minor: %u", this->major_, this->minor_);
}
void ESP32BLEBeacon::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 BLE beacon...");
global_esp32_ble_beacon = this;
@@ -50,7 +55,7 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
ble_setup();
while (true) {
delay(1000);
delay(1000); // NOLINT
}
}
void ESP32BLEBeacon::ble_setup() {

View File

@@ -34,6 +34,7 @@ class ESP32BLEBeacon : public Component {
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void set_major(uint16_t major) { this->major_ = major; }

View File

@@ -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
@@ -133,7 +134,7 @@ bool ESP32BLETracker::ble_setup() {
}
// BLE takes some time to be fully set up, 200ms should be more than enough
delay(200);
delay(200); // NOLINT
return true;
}
@@ -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 &param) {

View File

@@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, \
ESP_PLATFORM_ESP32
ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
DEPENDENCIES = ['api']
@@ -35,23 +35,19 @@ FRAME_SIZES = {
'UXGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200,
}
CONF_DATA_PINS = 'data_pins'
CONF_VSYNC_PIN = 'vsync_pin'
CONF_HREF_PIN = 'href_pin'
CONF_PIXEL_CLOCK_PIN = 'pixel_clock_pin'
CONF_EXTERNAL_CLOCK = 'external_clock'
CONF_I2C_PINS = 'i2c_pins'
CONF_RESET_PIN = 'reset_pin'
CONF_POWER_DOWN_PIN = 'power_down_pin'
CONF_MAX_FRAMERATE = 'max_framerate'
CONF_IDLE_FRAMERATE = 'idle_framerate'
CONF_RESOLUTION = 'resolution'
CONF_JPEG_QUALITY = 'jpeg_quality'
CONF_VERTICAL_FLIP = 'vertical_flip'
CONF_HORIZONTAL_MIRROR = 'horizontal_mirror'
CONF_CONTRAST = 'contrast'
CONF_BRIGHTNESS = 'brightness'
CONF_SATURATION = 'saturation'
CONF_TEST_PATTERN = 'test_pattern'

View File

@@ -108,6 +108,8 @@ void ESP32TouchComponent::dump_config() {
}
void ESP32TouchComponent::loop() {
const uint32_t now = millis();
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
for (auto *child : this->children_) {
uint16_t value;
if (this->iir_filter_enabled_()) {
@@ -119,14 +121,14 @@ void ESP32TouchComponent::loop() {
child->value_ = value;
child->publish_state(value < child->get_threshold());
if (this->setup_mode_) {
if (should_print) {
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
}
}
if (this->setup_mode_) {
if (should_print) {
// Avoid spamming logs
delay(250);
this->setup_mode_last_log_print_ = now;
}
}

View File

@@ -50,6 +50,7 @@ class ESP32TouchComponent : public Component {
touch_volt_atten_t voltage_attenuation_{};
std::vector<ESP32TouchBinarySensor *> children_;
bool setup_mode_{false};
uint32_t setup_mode_last_log_print_{};
uint32_t iir_filter_{0};
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");

View 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]))

View 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

View 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

View File

@@ -10,7 +10,7 @@ void MPR121Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MPR121...");
// soft reset device
this->write_byte(MPR121_SOFTRESET, 0x63);
delay(100);
delay(100); // NOLINT
if (!this->write_byte(MPR121_ECR, 0x0)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();

View File

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

View File

@@ -201,7 +201,7 @@ void MQTTClientComponent::check_connected() {
this->status_clear_warning();
ESP_LOGI(TAG, "MQTT Connected!");
// MQTT Client needs some time to be fully set up.
delay(100);
delay(100); // NOLINT
this->resubscribe_subscriptions_();

View File

@@ -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() {

View File

@@ -19,7 +19,7 @@ void MS5611Component::setup() {
this->mark_failed();
return;
}
delay(100);
delay(100); // NOLINT
for (uint8_t offset = 0; offset < 6; offset++) {
if (!this->read_byte_16(MS5611_CMD_READ_PROM + (offset * 2), &this->prom_[offset])) {
this->mark_failed();

View File

@@ -19,9 +19,9 @@ void NTC::process_(float value) {
return;
}
float lr = logf(value);
float v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
float temp = 1 / v - 273.15f;
double lr = log(double(value));
double v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
auto temp = float(1.0 / v - 273.15);
ESP_LOGD(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp);
this->publish_state(temp);

View File

@@ -9,9 +9,9 @@ namespace ntc {
class NTC : public Component, public sensor::Sensor {
public:
void set_sensor(Sensor *sensor) { sensor_ = sensor; }
void set_a(float a) { a_ = a; }
void set_b(float b) { b_ = b; }
void set_c(float c) { c_ = c; }
void set_a(double a) { a_ = a; }
void set_b(double b) { b_ = b; }
void set_c(double c) { c_ = c; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
@@ -20,9 +20,9 @@ class NTC : public Component, public sensor::Sensor {
void process_(float value);
sensor::Sensor *sensor_;
float a_;
float b_;
float c_;
double a_;
double b_;
double c_;
};
} // namespace ntc

View File

@@ -266,7 +266,7 @@ void OTAComponent::handle_() {
delay(10);
ESP_LOGI(TAG, "OTA update finished!");
this->status_clear_warning();
delay(100);
delay(100); // NOLINT
App.safe_reboot();
error:

View File

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

View File

@@ -12,24 +12,24 @@ pmsx003_ns = cg.esphome_ns.namespace('pmsx003')
PMSX003Component = pmsx003_ns.class_('PMSX003Component', uart.UARTDevice, cg.Component)
PMSX003Sensor = pmsx003_ns.class_('PMSX003Sensor', sensor.Sensor)
CONF_PMSX003 = 'PMSX003'
CONF_PMS5003T = 'PMS5003T'
CONF_PMS5003ST = 'PMS5003ST'
TYPE_PMSX003 = 'PMSX003'
TYPE_PMS5003T = 'PMS5003T'
TYPE_PMS5003ST = 'PMS5003ST'
PMSX003Type = pmsx003_ns.enum('PMSX003Type')
PMSX003_TYPES = {
CONF_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
CONF_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
CONF_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
}
SENSORS_TO_TYPE = {
CONF_PM_1_0: [CONF_PMSX003, CONF_PMS5003ST],
CONF_PM_2_5: [CONF_PMSX003, CONF_PMS5003T, CONF_PMS5003ST],
CONF_PM_10_0: [CONF_PMSX003, CONF_PMS5003ST],
CONF_TEMPERATURE: [CONF_PMS5003T, CONF_PMS5003ST],
CONF_HUMIDITY: [CONF_PMS5003T, CONF_PMS5003ST],
CONF_FORMALDEHYDE: [CONF_PMS5003ST],
CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST],
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST],
CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_FORMALDEHYDE: [TYPE_PMS5003ST],
}

View File

View File

@@ -0,0 +1,109 @@
#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;
}
void PZEM004T::dump_config() {
ESP_LOGCONFIG(TAG, "PZEM004T:");
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
}
} // namespace pzem004t
} // namespace esphome

View File

@@ -0,0 +1,41 @@
#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;
void dump_config() 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

View 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))

View File

View File

@@ -0,0 +1,71 @@
#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); }
void PZEMAC::dump_config() {
ESP_LOGCONFIG(TAG, "PZEMAC:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
LOG_SENSOR("", "Frequency", this->frequency_sensor_);
LOG_SENSOR("", "Power Factor", this->power_factor_sensor_);
}
} // namespace pzemac
} // namespace esphome

View File

@@ -0,0 +1,33 @@
#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;
void dump_config() 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

View 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))

View File

View File

@@ -0,0 +1,59 @@
#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); }
void PZEMDC::dump_config() {
ESP_LOGCONFIG(TAG, "PZEMDC:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
}
} // namespace pzemdc
} // namespace esphome

View File

@@ -0,0 +1,33 @@
#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;
void dump_config() 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

View 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))

View File

@@ -13,8 +13,8 @@ void RestartSwitch::write_state(bool state) {
if (state) {
ESP_LOGI(TAG, "Restarting device...");
// then execute
delay(100); // Let MQTT settle a bit
// Let MQTT settle a bit
delay(100); // NOLINT
App.safe_reboot();
}
}

View File

@@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import sensor
from esphome.const import CONF_ID, CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, UNIT_STEPS, \
ICON_ROTATE_RIGHT, CONF_VALUE
ICON_ROTATE_RIGHT, CONF_VALUE, CONF_PIN_A, CONF_PIN_B
rotary_encoder_ns = cg.esphome_ns.namespace('rotary_encoder')
RotaryEncoderResolution = rotary_encoder_ns.enum('RotaryEncoderResolution')
@@ -13,8 +13,6 @@ RESOLUTIONS = {
4: RotaryEncoderResolution.ROTARY_ENCODER_4_PULSES_PER_CYCLE,
}
CONF_PIN_A = 'pin_a'
CONF_PIN_B = 'pin_b'
CONF_PIN_RESET = 'pin_reset'
RotaryEncoderSensor = rotary_encoder_ns.class_('RotaryEncoderSensor', sensor.Sensor, cg.Component)

View File

@@ -3,15 +3,13 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import CONF_ID, UNIT_PARTS_PER_MILLION, \
CONF_HUMIDITY, CONF_TEMPERATURE, ICON_PERIODIC_TABLE_CO2, \
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT, CONF_CO2
DEPENDENCIES = ['i2c']
scd30_ns = cg.esphome_ns.namespace('scd30')
SCD30Component = scd30_ns.class_('SCD30Component', cg.PollingComponent, i2c.I2CDevice)
CONF_CO2 = 'co2'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(SCD30Component),
cv.Required(CONF_CO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION,

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

@@ -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,7 +258,8 @@ 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]))
if CONF_FILTERS in config:
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
if config.get(CONF_FILTERS): # must exist and not be empty
filters = yield build_filters(config[CONF_FILTERS])
cg.add(var.set_filters(filters))

View File

@@ -9,14 +9,17 @@ namespace sensor {
#define LOG_SENSOR(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
ESP_LOGCONFIG(TAG, prefix " Unit of Measurement: '%s'", obj->get_unit_of_measurement().c_str()); \
ESP_LOGCONFIG(TAG, prefix " Accuracy Decimals: %d", obj->get_accuracy_decimals()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, obj->get_unit_of_measurement().c_str()); \
ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, obj->get_accuracy_decimals()); \
if (!obj->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, prefix " Icon: '%s'", obj->get_icon().c_str()); \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); \
} \
if (!obj->unique_id().empty()) { \
ESP_LOGV(TAG, prefix " Unique ID: '%s'", obj->unique_id().c_str()); \
ESP_LOGV(TAG, "%s Unique ID: '%s'", prefix, obj->unique_id().c_str()); \
} \
if (obj->get_force_update()) { \
ESP_LOGV(TAG, "%s Force Update: YES", prefix); \
} \
}
@@ -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 {

View File

View 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_HUMIDITY_SOURCE = 'humidity_source'
CONF_TEMPERATURE_SOURCE = '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_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_TEMPERATURE_SOURCE): 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_HUMIDITY_SOURCE])
cg.add(var.set_humidity_sensor(sens))
sens = yield cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE])
cg.add(var.set_temperature_sensor(sens))

View 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

View 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

View File

View 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))

View 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

View 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

View File

@@ -14,7 +14,7 @@ void ShutdownSwitch::write_state(bool state) {
if (state) {
ESP_LOGI(TAG, "Shutting down...");
delay(100); // Let MQTT settle a bit
delay(100); // NOLINT
App.run_safe_shutdown_hooks();
#ifdef ARDUINO_ARCH_ESP8266

View File

@@ -255,6 +255,10 @@ void Sim800LComponent::send_sms(std::string recipient, std::string message) {
this->send_pending_ = true;
this->update();
}
void Sim800LComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SIM800L:");
ESP_LOGCONFIG(TAG, " RSSI: %d dB", this->rssi_);
}
} // namespace sim800l
} // namespace esphome

View File

@@ -4,11 +4,11 @@
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#define SIM800L_READ_BUFFER_LENGTH 255
namespace esphome {
namespace sim800l {
const uint8_t SIM800L_READ_BUFFER_LENGTH = 255;
enum State {
STATE_IDLE = 0,
STATE_INIT,
@@ -37,6 +37,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent {
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
void update() override;
void loop() override;
void dump_config() override;
void add_on_sms_received_callback(std::function<void(std::string, std::string)> callback) {
this->callback_.add(std::move(callback));
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/output/float_output.h"
namespace esphome {

View 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_))

View 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

View 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

View 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))

View 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); // NOLINT
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

View 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

View File

View 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)

View 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

View 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

View File

@@ -9,15 +9,15 @@ namespace switch_ {
#define LOG_SWITCH(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
if (!obj->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, prefix " Icon: '%s'", obj->get_icon().c_str()); \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); \
} \
if (obj->assumed_state()) { \
ESP_LOGCONFIG(TAG, prefix " Assumed State: YES"); \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (obj->is_inverted()) { \
ESP_LOGCONFIG(TAG, prefix " Inverted: YES"); \
ESP_LOGCONFIG(TAG, "%s Inverted: YES", prefix); \
} \
}

Some files were not shown because too many files have changed in this diff Show More