diff --git a/components/aux_ac/automation.h b/components/aux_ac/automation.h index aa46168..a62fb27 100644 --- a/components/aux_ac/automation.h +++ b/components/aux_ac/automation.h @@ -17,6 +17,7 @@ namespace aux_ac { protected: AirCon *ac_; + }; template class AirConDisplayOnAction : public Action @@ -28,7 +29,37 @@ namespace aux_ac { protected: AirCon *ac_; - }; + }; + + template + class AirConSendTestPacketAction : public Action + { + public: + explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} + void set_data_template(std::function(Ts...)> func) { + this->data_func_ = func; + this->static_ = false; + } + void set_data_static(const std::vector &data) { + this->data_static_ = data; + this->static_ = true; + } + + void play(Ts... x) override { + if (this->static_) { + this->ac_->sendTestPacket(this->data_static_); + } else { + auto val = this->data_func_(x...); + this->ac_->sendTestPacket(val); + } + } + + protected: + AirCon *ac_; + bool static_{false}; + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; + }; } // namespace aux_ac } // namespace esphome \ No newline at end of file diff --git a/components/aux_ac/aux_ac.h b/components/aux_ac/aux_ac.h index 3b470e0..c8a53c3 100644 --- a/components/aux_ac/aux_ac.h +++ b/components/aux_ac/aux_ac.h @@ -410,7 +410,7 @@ struct packet_small_info_body_t { // биты 0..5 растут на 1 каждую минуту, возможно внутренний таймер для включения/выключения по времени uint8_t fan_speed; // три старших бита - скорость вентилятора, остальные биты не известны // AUTO = 0xA0, LOW = 0x60, MEDIUM = 0x40, HIGH = 0x20 - uint8_t fan_turbo_and_mute; // бит 7 = режим TURBO, бит 6 - режим MUTE; остальные не известны + uint8_t fan_turbo_and_mute; // бит 7 = режим MUTE, бит 6 - режим TURBO; остальные не известны uint8_t mode; // режим работы сплита: // AUTO : bits[7, 6, 5] = [0, 0, 0] // COOL : bits[7, 6, 5] = [0, 0, 1] @@ -757,6 +757,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { packet_t _inPacket; packet_t _outPacket; + // пакет для тестирования всякой фигни + packet_t _outTestPacket; + // последовательность пакетов текущий шаг в последовательности sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN]; uint8_t _sequence_current_step; @@ -912,7 +915,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { break; } } - + // заполняет структуру команды нейтральными значениями void _clearCommand(ac_command_t * cmd){ cmd->clean = AC_CLEAN_UNTOUCHED; @@ -921,7 +924,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->fanSpeed = AC_FANSPEED_UNTOUCHED; cmd->fanTurbo = AC_FANTURBO_UNTOUCHED; cmd->health = AC_HEALTH_UNTOUCHED; - cmd->health_error = AC_HEALTH_ERROR_UNTOUCHED; + cmd->health_status = AC_HEALTH_STATUS_UNTOUCHED; cmd->iFeel = AC_IFEEL_UNTOUCHED; cmd->louver.louver_h = AC_LOUVERH_UNTOUCHED; cmd->louver.louver_v = AC_LOUVERV_UNTOUCHED; @@ -941,7 +944,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { cmd->temp_strange = 0; cmd->realFanSpeed = AC_REAL_FAN_UNTOUCHED; }; - + // очистка буфера размером AC_BUFFER_SIZE void _clearBuffer(uint8_t * buf){ memset(buf, 0, AC_BUFFER_SIZE); @@ -1269,9 +1272,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { stateChangedFlag = stateChangedFlag || (_current_ac_state.health != (ac_health)stateByte); _current_ac_state.health = (ac_health)stateByte; - stateByte = small_info_body->status & AC_HEALTH_ERROR_MASK; - stateChangedFlag = stateChangedFlag || (_current_ac_state.health_error != (ac_health_error)stateByte); - _current_ac_state.health_error = (ac_health_error)stateByte; + stateByte = small_info_body->status & AC_HEALTH_STATUS_MASK; + stateChangedFlag = stateChangedFlag || (_current_ac_state.health_status != (ac_health_status)stateByte); + _current_ac_state.health_status = (ac_health_status)stateByte; stateByte = small_info_body->status & AC_CLEAN_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.clean != (ac_clean)stateByte); @@ -1284,7 +1287,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { stateByte = small_info_body->display_and_mildew & AC_MILDEW_MASK; stateChangedFlag = stateChangedFlag || (_current_ac_state.mildew != (ac_mildew)stateByte); _current_ac_state.mildew = (ac_mildew)stateByte; - + // уведомляем об изменении статуса сплита if (stateChangedFlag) stateChanged(); @@ -1995,6 +1998,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { _clearInPacket(); _clearOutPacket(); + _clearPacket(&_outTestPacket); + _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; + _outTestPacket.header->wifi = AC_PACKET_ANSWER; + _setStateMachineState(ACSM_IDLE); _ac_serial = parent; _hw_initialized = (_ac_serial != nullptr); @@ -2802,7 +2809,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { hasCommand = true; cmd.health = AC_HEALTH_ON; cmd.health_status = AC_HEALTH_STATUS_ON; - cmd.iFeel = AC_IFEEL_ON; // зависимость от health + cmd.iFeel = AC_IFEEL_ON; // зависимость от health cmd.fanTurbo = AC_FANTURBO_OFF; // зависимость от health cmd.fanMute = AC_FANMUTE_OFF; // зависимость от health cmd.sleep = AC_SLEEP_OFF; // для логики пресетов @@ -3110,6 +3117,67 @@ class AirCon : public esphome::Component, public esphome::climate::Climate { return _displaySequence(dsp); } + // отправляет сплиту заданный набор байт + // Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает. + bool sendTestPacket(const std::vector &data){ + //bool sendTestPacket(uint8_t *data = nullptr, uitn8_t data_length = 0){ + //if (data == nullptr) return false; + //if (data_length == 0) return false; + if (data.size() == 0) return false; + //if (data_length > AC_BUFFER_SIZE) return false; + if (data.size() > AC_BUFFER_SIZE) return false; + + // нет смысла в отправке, если нет коннекта с кондиционером + if (!get_has_connection()) { + _debugMsg(F("sendTestPacket: no pings from HVAC. It seems like no AC connected."), ESPHOME_LOG_LEVEL_ERROR, __LINE__); + return false; + } + // очищаем пакет + _clearPacket(&_outTestPacket); + + // копируем данные в пакет + //memcpy(_outTestPacket.data, data, data_length); + uint8_t i = 0; + for (uint8_t n : data) { + _outTestPacket.data[i] = n; + i++; + } + + // на всякий случай указываем правильные некоторые байты + _outTestPacket.header->start_byte = AC_PACKET_START_BYTE; + //_outTestPacket.header->wifi = AC_PACKET_ANSWER; + + _outTestPacket.msec = millis(); + _outTestPacket.body = &(_outTestPacket.data[AC_HEADER_SIZE]); + _outTestPacket.bytesLoaded = AC_HEADER_SIZE + _outTestPacket.header->body_length + 2; + + // рассчитываем и записываем в пакет CRC + _outTestPacket.crc = (packet_crc_t *) &(_outTestPacket.data[AC_HEADER_SIZE + _outTestPacket.header->body_length]); + _setCRC16(&_outTestPacket); + + _debugMsg(F("sendTestPacket: test packet loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + _debugPrintPacket(&_outTestPacket, ESPHOME_LOG_LEVEL_WARN, __LINE__); + + // ниже блок добавления отправки пакета в последовательность команд + //***************************************************************** + // есть ли место на запрос в последовательности команд? + if (_getFreeSequenceSpace() < 1) { + _debugMsg(F("sendTestPacket: not enough space in command sequence. Sequence steps doesn't loaded."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + + /*************************************** sendTestPacket request ***********************************************/ + if (!_addSequenceFuncStep(&AirCon::sq_requestTestPacket)) { + _debugMsg(F("sendTestPacket: sendTestPacket request sequence step fail."), ESPHOME_LOG_LEVEL_WARN, __LINE__); + return false; + } + /**************************************************************************************/ + + _debugMsg(F("sendTestPacket: loaded to sequence"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__); + + return true; + } + void set_period(uint32_t ms) { this->_update_period = ms; } uint32_t get_period() { return this->_update_period; } diff --git a/components/aux_ac/climate.py b/components/aux_ac/climate.py index dbd52f8..414d17a 100644 --- a/components/aux_ac/climate.py +++ b/components/aux_ac/climate.py @@ -39,6 +39,9 @@ CODEOWNERS = ["@GrKoR"] DEPENDENCIES = ["climate", "uart"] AUTO_LOAD = ["sensor", "binary_sensor"] +CONF_SUPPORTED_MODES = 'supported_modes' +CONF_SUPPORTED_SWING_MODES = 'supported_swing_modes' +CONF_SUPPORTED_PRESETS = 'supported_presets' CONF_SHOW_ACTION = 'show_action' CONF_INDOOR_TEMPERATURE = 'indoor_temperature' CONF_OUTDOOR_TEMPERATURE = 'outdoor_temperature' @@ -57,6 +60,7 @@ CONF_DISPLAY_INVERTED = 'display_inverted' ICON_DISPLAY = "mdi:clock-digital" CONF_STORE_SETTINGS = 'store_settings' + aux_ac_ns = cg.esphome_ns.namespace("aux_ac") AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) Capabilities = aux_ac_ns.namespace("Constants") @@ -100,14 +104,15 @@ CUSTOM_PRESETS = { } validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) + def validate_raw_data(value): if isinstance(value, list): return cv.Schema([cv.hex_uint8_t])(value) raise cv.Invalid( "data must be a list of bytes" ) - - + + def output_info(config): """_LOGGER.info(config)""" return config @@ -282,6 +287,8 @@ async def to_code(config): if CONF_CUSTOM_FAN_MODES in config: cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) + + DISPLAY_ACTION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(AirCon), @@ -299,7 +306,6 @@ async def display_on_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg, paren) - SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(AirCon),