This commit is contained in:
Alexander Pohl 2020-10-11 11:30:04 +02:00
commit cf6205f9b8
22 changed files with 160 additions and 44 deletions

View file

@ -18,6 +18,7 @@ jobs:
name: Build docker containers
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]

View file

@ -44,6 +44,7 @@ jobs:
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
@ -107,6 +108,7 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1

View file

@ -41,6 +41,7 @@ jobs:
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
@ -104,6 +105,7 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1

View file

@ -40,6 +40,7 @@ jobs:
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
@ -103,6 +104,7 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1

View file

@ -12,8 +12,10 @@ void DaikinClimate::transmit_state() {
0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00};
remote_state[21] = this->operation_mode_();
remote_state[24] = this->fan_speed_();
remote_state[22] = this->temperature_();
uint16_t fan_speed = this->fan_speed_();
remote_state[24] = fan_speed >> 8;
remote_state[25] = fan_speed & 0xff;
// Calculate checksum
for (int i = 16; i < 34; i++) {
@ -90,25 +92,38 @@ uint8_t DaikinClimate::operation_mode_() {
return operating_mode;
}
uint8_t DaikinClimate::fan_speed_() {
uint8_t fan_speed;
uint16_t DaikinClimate::fan_speed_() {
uint16_t fan_speed;
switch (this->fan_mode) {
case climate::CLIMATE_FAN_LOW:
fan_speed = DAIKIN_FAN_1;
fan_speed = DAIKIN_FAN_1 << 8;
break;
case climate::CLIMATE_FAN_MEDIUM:
fan_speed = DAIKIN_FAN_3;
fan_speed = DAIKIN_FAN_3 << 8;
break;
case climate::CLIMATE_FAN_HIGH:
fan_speed = DAIKIN_FAN_5;
fan_speed = DAIKIN_FAN_5 << 8;
break;
case climate::CLIMATE_FAN_AUTO:
default:
fan_speed = DAIKIN_FAN_AUTO;
fan_speed = DAIKIN_FAN_AUTO << 8;
}
// If swing is enabled switch first 4 bits to 1111
return this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? fan_speed | 0xF : fan_speed;
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
fan_speed |= 0x0F00;
break;
case climate::CLIMATE_SWING_HORIZONTAL:
fan_speed |= 0x000F;
break;
case climate::CLIMATE_SWING_BOTH:
fan_speed |= 0x0F0F;
break;
default:
break;
}
return fan_speed;
}
uint8_t DaikinClimate::temperature_() {
@ -159,13 +174,19 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
this->target_temperature = temperature >> 1;
}
uint8_t fan_mode = frame[8];
if (fan_mode & 0xF)
uint8_t swing_mode = frame[9];
if (fan_mode & 0xF && swing_mode & 0xF)
this->swing_mode = climate::CLIMATE_SWING_BOTH;
else if (fan_mode & 0xF)
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
else if (swing_mode & 0xF)
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
else
this->swing_mode = climate::CLIMATE_SWING_OFF;
switch (fan_mode & 0xF0) {
case DAIKIN_FAN_1:
case DAIKIN_FAN_2:
case DAIKIN_FAN_SILENT:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case DAIKIN_FAN_3:

View file

@ -21,6 +21,7 @@ const uint8_t DAIKIN_MODE_ON = 0x01;
// Fan Speed
const uint8_t DAIKIN_FAN_AUTO = 0xA0;
const uint8_t DAIKIN_FAN_SILENT = 0xB0;
const uint8_t DAIKIN_FAN_1 = 0x30;
const uint8_t DAIKIN_FAN_2 = 0x40;
const uint8_t DAIKIN_FAN_3 = 0x50;
@ -46,13 +47,14 @@ class DaikinClimate : public climate_ir::ClimateIR {
DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
protected:
// Transmit via IR the state of this climate controller.
void transmit_state() override;
uint8_t operation_mode_();
uint8_t fan_speed_();
uint16_t fan_speed_();
uint8_t temperature_();
// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;

View file

@ -299,7 +299,7 @@ void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char
void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::CENTER_LEFT, format, arg);
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }

View file

@ -36,7 +36,10 @@ const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01;
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02;
const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03;
const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04;
const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000;
const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00;
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01;
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02;
const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03;
const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00;
const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00;
@ -74,7 +77,12 @@ const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {}
FujitsuGeneralClimate::FujitsuGeneralClimate()
: ClimateIR(
FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
climate::CLIMATE_SWING_BOTH}) {}
void FujitsuGeneralClimate::transmit_state() {
if (this->mode == climate::CLIMATE_MODE_OFF) {
@ -101,8 +109,8 @@ void FujitsuGeneralClimate::transmit_state() {
remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15;
// Set temperature
uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN);
safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX);
auto safecelsius =
(uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
remote_state[8] = (byte) safecelsius - 16;
remote_state[8] = remote_state[8] << 4;
@ -119,18 +127,52 @@ void FujitsuGeneralClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9;
break;
case climate::CLIMATE_MODE_DRY:
remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9;
break;
case climate::CLIMATE_MODE_AUTO:
default:
remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9;
break;
// TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome
// TODO: CLIMATE_MODE_10C are missing in esphome
}
// TODO: missing support for fan speed
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
// Set fan
switch (this->fan_mode) {
case climate::CLIMATE_FAN_HIGH:
remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10;
break;
case climate::CLIMATE_FAN_MEDIUM:
remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10;
break;
case climate::CLIMATE_FAN_LOW:
remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10;
break;
case climate::CLIMATE_FAN_AUTO:
default:
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
break;
}
// TODO: missing support for swing
// remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10;
// Set swing
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_HORIZONTAL:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_BOTH:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_OFF:
default:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4);
break;
}
// TODO: missing support for outdoor unit low noise
// remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;

View file

@ -17,7 +17,7 @@ class AQICalculator : public AbstractAQICalculator {
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 45}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, 604}};

View file

@ -102,17 +102,18 @@ class LightTurnOnTrigger : public Trigger<> {
public:
LightTurnOnTrigger(LightState *a_light) {
a_light->add_new_remote_values_callback([this, a_light]() {
auto is_on = a_light->current_values.is_on();
// using the remote value because of transitions we need to trigger as early as possible
auto is_on = a_light->remote_values.is_on();
// only trigger when going from off to on
auto should_trigger = is_on && !last_on_;
auto should_trigger = is_on && !this->last_on_;
// Set new state immediately so that trigger() doesn't devolve
// into infinite loop
last_on_ = is_on;
this->last_on_ = is_on;
if (should_trigger) {
this->trigger();
}
});
last_on_ = a_light->current_values.is_on();
this->last_on_ = a_light->current_values.is_on();
}
protected:
@ -122,22 +123,14 @@ class LightTurnOnTrigger : public Trigger<> {
class LightTurnOffTrigger : public Trigger<> {
public:
LightTurnOffTrigger(LightState *a_light) {
a_light->add_new_remote_values_callback([this, a_light]() {
a_light->add_new_target_state_reached_callback([this, a_light]() {
auto is_on = a_light->current_values.is_on();
// only trigger when going from on to off
auto should_trigger = !is_on && last_on_;
// Set new state immediately so that trigger() doesn't devolve
// into infinite loop
last_on_ = is_on;
if (should_trigger) {
if (!is_on) {
this->trigger();
}
});
last_on_ = a_light->current_values.is_on();
}
protected:
bool last_on_;
};
template<typename... Ts> class AddressableSet : public Action<Ts...> {

View file

@ -145,6 +145,7 @@ void LightState::loop() {
if (this->transformer_ != nullptr) {
if (this->transformer_->is_finished()) {
this->remote_values = this->current_values = this->transformer_->get_end_values();
this->target_state_reached_callback_.call();
if (this->transformer_->publish_at_end())
this->publish_state();
this->transformer_ = nullptr;
@ -336,6 +337,9 @@ void LightCall::perform() {
this->parent_->set_immediately_(v, this->publish_);
}
if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call();
}
if (this->publish_) {
this->parent_->publish_state();
}
@ -752,6 +756,10 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
this->remote_values_callback_.add(std::move(send_callback));
}
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
this->target_state_reached_callback_.add(std::move(send_callback));
}
LightEffect *LightState::get_active_effect_() {
if (this->active_effect_index_ == 0)
return nullptr;

View file

@ -242,6 +242,13 @@ class LightState : public Nameable, public Component {
*/
void add_new_remote_values_callback(std::function<void()> &&send_callback);
/**
* The callback is called once the state of current_values and remote_values are equal
*
* @param send_callback
*/
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
/// Return whether the light has any effects that meet the trait requirements.
bool supports_effects();
@ -318,6 +325,12 @@ class LightState : public Nameable, public Component {
* starting with the beginning of the transition.
*/
CallbackManager<void()> remote_values_callback_{};
/** Callback to call when the state of current_values and remote_values are equal
* This should be called once the state of current_values changed and equals the state of remote_values
*/
CallbackManager<void()> target_state_reached_callback_{};
LightOutput *output_; ///< Store the output to allow effects to have more access.
/// Whether the light value should be written in the next cycle.
bool next_write_{true};

View file

@ -11,6 +11,7 @@ CONF_SCROLL_DWELL = 'scroll_dwell'
CONF_SCROLL_DELAY = 'scroll_delay'
CONF_SCROLL_ENABLE = 'scroll_enable'
CONF_SCROLL_MODE = 'scroll_mode'
CONF_REVERSE_ENABLE = 'reverse_enable'
SCROLL_MODES = {
'CONTINUOUS': 0,
@ -39,6 +40,7 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({
cv.Optional(CONF_SCROLL_SPEED, default='250ms'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SCROLL_DELAY, default='1000ms'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SCROLL_DWELL, default='1000ms'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean,
}).extend(cv.polling_component_schema('500ms')).extend(spi.spi_device_schema(cs_pin_required=True))
@ -56,6 +58,7 @@ def to_code(config):
cg.add(var.set_scroll_delay(config[CONF_SCROLL_DELAY]))
cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
if CONF_LAMBDA in config:
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')],

View file

@ -108,7 +108,11 @@ void MAX7219Component::display() {
// Send the data to the chip
for (uint8_t i = 0; i < this->num_chips_; i++) {
for (uint8_t j = 0; j < 8; j++) {
pixels[j] = this->max_displaybuffer_[i * 8 + j];
if (this->reverse_) {
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
} else {
pixels[j] = this->max_displaybuffer_[i * 8 + j];
}
}
this->send64pixels(i, pixels);
}
@ -229,7 +233,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
b = pixels[col];
} else if (this->orientation_ == 2) {
for (uint8_t i = 0; i < 8; i++) {
b |= ((pixels[i] >> (7 - col)) << (7 - i));
b |= ((pixels[i] >> (7 - col)) & 1) << i;
}
} else {
b = pixels[7 - col];

View file

@ -52,6 +52,7 @@ class MAX7219Component : public PollingComponent,
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
void set_scroll(bool on_off) { this->scroll_ = on_off; };
void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
void set_reverse(bool on_off) { this->reverse_ = on_off; };
void send_char(byte chip, byte data);
void send64pixels(byte chip, const byte pixels[8]);
@ -87,6 +88,7 @@ class MAX7219Component : public PollingComponent,
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
uint8_t num_chips_;
bool scroll_;
bool reverse_;
bool update_{false};
uint16_t scroll_speed_;
uint16_t scroll_delay_;

View file

@ -29,10 +29,9 @@ void FloatOutput::set_level(float state) {
this->power_.unrequest();
}
#endif
float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_;
if (this->is_inverted())
adjusted_value = 1.0f - adjusted_value;
state = 1.0f - state;
float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_;
this->write_state(adjusted_value);
}

View file

@ -10,10 +10,11 @@ import tzlocal
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
from esphome.const import CONF_ID, CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
from esphome.core import coroutine, coroutine_with_priority
from esphome.automation import Condition
_LOGGER = logging.getLogger(__name__)
@ -24,6 +25,7 @@ time_ns = cg.esphome_ns.namespace('time')
RealTimeClock = time_ns.class_('RealTimeClock', cg.Component)
CronTrigger = time_ns.class_('CronTrigger', automation.Trigger.template(), cg.Component)
ESPTime = time_ns.struct('ESPTime')
TimeHasTimeCondition = time_ns.class_('TimeHasTimeCondition', Condition)
def _tz_timedelta(td):
@ -328,3 +330,11 @@ def register_time(time_var, config):
def to_code(config):
cg.add_define('USE_TIME')
cg.add_global(time_ns.using)
@automation.register_condition('time.has_time', TimeHasTimeCondition, cv.Schema({
cv.GenerateID(): cv.use_id(RealTimeClock),
}))
def time_has_time_to_code(config, condition_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(condition_id, template_arg, paren)

View file

@ -2,6 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include <stdlib.h>
#include <time.h>
#include <bitset>
@ -133,5 +134,14 @@ class RealTimeClock : public Component {
std::string timezone_{};
};
template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {
public:
TimeHasTimeCondition(RealTimeClock *parent) : parent_(parent) {}
bool check(Ts... x) override { return this->parent_->now().is_valid(); }
protected:
RealTimeClock *parent_;
};
} // namespace time
} // namespace esphome

View file

@ -31,6 +31,7 @@ void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &kl
stream->print("</td><td></td><td>");
stream->print(action.c_str());
stream->print("</td>");
stream->print("</tr>");
}
UrlMatch match_url(const std::string &url, bool only_domain = false) {

View file

@ -1,6 +1,6 @@
voluptuous==0.11.7
PyYAML==5.3.1
paho-mqtt==1.5.0
paho-mqtt==1.5.1
colorlog==4.2.1
tornado==6.0.4
protobuf==3.13.0

View file

@ -6,7 +6,7 @@ pexpect==4.8.0
# Unit tests
pytest==6.0.2
pytest-cov==2.10.0
pytest-cov==2.10.1
pytest-mock==3.3.1
asyncmock==0.4.2
hypothesis==5.21.0

View file

@ -8,6 +8,7 @@ esphome:
- wait_until:
- api.connected
- wifi.connected
- time.has_time
includes:
- custom.h