mirror of
https://github.com/GrKoR/esphome_aux_ac_component.git
synced 2026-01-27 12:50:43 +03:00
new send packet action. It for engineers only
This commit is contained in:
@@ -17,7 +17,7 @@ namespace aux_ac {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
AirCon *ac_;
|
AirCon *ac_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
class AirConDisplayOnAction : public Action<Ts...>
|
class AirConDisplayOnAction : public Action<Ts...>
|
||||||
@@ -29,7 +29,37 @@ namespace aux_ac {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
AirCon *ac_;
|
AirCon *ac_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
class AirConSendTestPacketAction : public Action<Ts...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {}
|
||||||
|
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||||
|
this->data_func_ = func;
|
||||||
|
this->static_ = false;
|
||||||
|
}
|
||||||
|
void set_data_static(const std::vector<uint8_t> &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<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||||
|
std::vector<uint8_t> data_static_{};
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace aux_ac
|
} // namespace aux_ac
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
@@ -513,6 +513,9 @@ class AirCon : public esphome::Component, public esphome::climate::Climate {
|
|||||||
packet_t _inPacket;
|
packet_t _inPacket;
|
||||||
packet_t _outPacket;
|
packet_t _outPacket;
|
||||||
|
|
||||||
|
// пакет для тестирования всякой фигни
|
||||||
|
packet_t _outTestPacket;
|
||||||
|
|
||||||
// последовательность пакетов текущий шаг в последовательности
|
// последовательность пакетов текущий шаг в последовательности
|
||||||
sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN];
|
sequence_item_t _sequence[AC_SEQUENCE_MAX_LEN];
|
||||||
uint8_t _sequence_current_step;
|
uint8_t _sequence_current_step;
|
||||||
@@ -1576,6 +1579,24 @@ class AirCon : public esphome::Component, public esphome::climate::Climate {
|
|||||||
return relevant;
|
return relevant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// отправка запроса с тестовым пакетом
|
||||||
|
bool sq_requestTestPacket(){
|
||||||
|
// если исходящий пакет не пуст, то выходим и ждем освобождения
|
||||||
|
if (_outPacket.bytesLoaded > 0) return true;
|
||||||
|
|
||||||
|
_copyPacket(&_outPacket, &_outTestPacket);
|
||||||
|
_copyPacket(&_sequence[_sequence_current_step].packet, &_outTestPacket);
|
||||||
|
_sequence[_sequence_current_step].packet_type = AC_SPT_SENT_PACKET;
|
||||||
|
|
||||||
|
// Отчитываемся в лог
|
||||||
|
_debugMsg(F("Sequence [step %u]: Test Packet request generated:"), ESPHOME_LOG_LEVEL_VERBOSE, __LINE__, _sequence_current_step);
|
||||||
|
_debugPrintPacket(&_outPacket, ESPHOME_LOG_LEVEL_VERBOSE, __LINE__);
|
||||||
|
|
||||||
|
// увеличиваем текущий шаг
|
||||||
|
_sequence_current_step++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// сенсоры, отображающие параметры сплита
|
// сенсоры, отображающие параметры сплита
|
||||||
//esphome::sensor::Sensor *sensor_indoor_temperature = new esphome::sensor::Sensor();
|
//esphome::sensor::Sensor *sensor_indoor_temperature = new esphome::sensor::Sensor();
|
||||||
esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr;
|
esphome::sensor::Sensor *sensor_indoor_temperature_ = nullptr;
|
||||||
@@ -1613,6 +1634,10 @@ class AirCon : public esphome::Component, public esphome::climate::Climate {
|
|||||||
_clearInPacket();
|
_clearInPacket();
|
||||||
_clearOutPacket();
|
_clearOutPacket();
|
||||||
|
|
||||||
|
_clearPacket(&_outTestPacket);
|
||||||
|
_outTestPacket.header->start_byte = AC_PACKET_START_BYTE;
|
||||||
|
_outTestPacket.header->wifi = AC_PACKET_ANSWER;
|
||||||
|
|
||||||
_setStateMachineState(ACSM_IDLE);
|
_setStateMachineState(ACSM_IDLE);
|
||||||
_ac_serial = parent;
|
_ac_serial = parent;
|
||||||
_hw_initialized = (_ac_serial != nullptr);
|
_hw_initialized = (_ac_serial != nullptr);
|
||||||
@@ -2435,6 +2460,67 @@ class AirCon : public esphome::Component, public esphome::climate::Climate {
|
|||||||
return _displaySequence(dsp);
|
return _displaySequence(dsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// отправляет сплиту заданный набор байт
|
||||||
|
// Перед отправкой проверяет пакет на корректность структуры. CRC16 рассчитывает самостоятельно и перезаписывает.
|
||||||
|
bool sendTestPacket(const std::vector<uint8_t> &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; }
|
void set_period(uint32_t ms) { this->_update_period = ms; }
|
||||||
uint32_t get_period() { return this->_update_period; }
|
uint32_t get_period() { return this->_update_period; }
|
||||||
|
|
||||||
@@ -2485,7 +2571,7 @@ class AirCon : public esphome::Component, public esphome::climate::Climate {
|
|||||||
|
|
||||||
// обычный wifi-модуль запрашивает маленький пакет статуса
|
// обычный wifi-модуль запрашивает маленький пакет статуса
|
||||||
// но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру
|
// но нам никто не мешает запрашивать и большой и маленький, чтобы чаще обновлять комнатную температуру
|
||||||
// делаем этот запросом только в случае, если есть коннект с кондиционером
|
// делаем этот запрос только в случае, если есть коннект с кондиционером
|
||||||
if (get_has_connection()) getStatusBigAndSmall();
|
if (get_has_connection()) getStatusBigAndSmall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from esphome.const import (
|
|||||||
CONF_CUSTOM_FAN_MODES,
|
CONF_CUSTOM_FAN_MODES,
|
||||||
CONF_CUSTOM_PRESETS,
|
CONF_CUSTOM_PRESETS,
|
||||||
CONF_INTERNAL,
|
CONF_INTERNAL,
|
||||||
|
CONF_DATA,
|
||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
ICON_THERMOMETER,
|
ICON_THERMOMETER,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
@@ -44,6 +45,7 @@ Capabilities = aux_ac_ns.namespace("Constants")
|
|||||||
|
|
||||||
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action)
|
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action)
|
||||||
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action)
|
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action)
|
||||||
|
AirConSendTestPacketAction = aux_ac_ns.class_("AirConSendTestPacketAction", automation.Action)
|
||||||
|
|
||||||
ALLOWED_CLIMATE_MODES = {
|
ALLOWED_CLIMATE_MODES = {
|
||||||
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
|
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
|
||||||
@@ -80,6 +82,15 @@ CUSTOM_PRESETS = {
|
|||||||
}
|
}
|
||||||
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
|
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):
|
def output_info(config):
|
||||||
"""_LOGGER.info(config)"""
|
"""_LOGGER.info(config)"""
|
||||||
return config
|
return config
|
||||||
@@ -164,11 +175,47 @@ DISPLAY_ACTION_SCHEMA = maybe_simple_id(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA)
|
@automation.register_action("aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA)
|
||||||
async def switch_toggle_to_code(config, action_id, template_arg, args):
|
async def display_off_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA)
|
@automation.register_action("aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA)
|
||||||
async def switch_toggle_to_code(config, action_id, template_arg, args):
|
async def display_on_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
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),
|
||||||
|
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# *********************************************************************************************************
|
||||||
|
# ВАЖНО! Только для инженеров!
|
||||||
|
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
|
||||||
|
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
|
||||||
|
# Вы действуете на свой страх и риск.
|
||||||
|
# *********************************************************************************************************
|
||||||
|
@automation.register_action(
|
||||||
|
"aux_ac.send_packet",
|
||||||
|
AirConSendTestPacketAction,
|
||||||
|
SEND_TEST_PACKET_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def send_packet_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
data = config[CONF_DATA]
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
data = list(data)
|
||||||
|
|
||||||
|
if cg.is_template(data):
|
||||||
|
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||||
|
cg.add(var.set_data_template(templ))
|
||||||
|
else:
|
||||||
|
cg.add(var.set_data_static(data))
|
||||||
|
|
||||||
|
return var
|
||||||
134
tests/ac_send_packet_for_engineer.py
Normal file
134
tests/ac_send_packet_for_engineer.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import time
|
||||||
|
import aioesphomeapi
|
||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from aioesphomeapi.api_pb2 import (LOG_LEVEL_NONE,
|
||||||
|
LOG_LEVEL_ERROR,
|
||||||
|
LOG_LEVEL_WARN,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_DEBUG,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
LOG_LEVEL_VERY_VERBOSE)
|
||||||
|
|
||||||
|
def createParser ():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='''This script is used for collecting logs from ac_aux ESPHome component.
|
||||||
|
For more info, see https://github.com/GrKoR/ac_python_logger''',
|
||||||
|
add_help = False)
|
||||||
|
parent_group = parser.add_argument_group (title='Params')
|
||||||
|
parent_group.add_argument ('--help', '-h', action='help', help='show this help message and exit')
|
||||||
|
parent_group.add_argument ('-i', '--ip', nargs=1, required=True, help='IP address of the esphome device')
|
||||||
|
parent_group.add_argument ('-p', '--pwd', nargs=1, required=True, help='native API password for the esphome device')
|
||||||
|
parent_group.add_argument ('-n', '--name', nargs=1, default=['noname'], help='name of this devace in the log')
|
||||||
|
parent_group.add_argument ('-l', '--logfile', nargs=1, default=['%4d-%02d-%02d %02d-%02d-%02d log.csv' % time.localtime()[0:6]], help='log file name')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""Connect to an ESPHome device and wait for state changes."""
|
||||||
|
api = aioesphomeapi.APIClient(namespace.ip[0], 6053, namespace.pwd[0])
|
||||||
|
|
||||||
|
try:
|
||||||
|
await api.connect(login=True)
|
||||||
|
except aioesphomeapi.InvalidAuthAPIError as e:
|
||||||
|
return print(e)
|
||||||
|
|
||||||
|
print(api.api_version)
|
||||||
|
|
||||||
|
def log_AC(isAirConLog):
|
||||||
|
parts = re.search("(\d{10}): (\[\S{2}\]) \[([0-9A-F ]{23})\]\s?((?:[0-9A-F]{2}\s*)*) \[([0-9A-F ]{5})\]", isAirConLog.group(1))
|
||||||
|
packString = '\n' + namespace.name[0]
|
||||||
|
packString += ";" + "%4d-%02d-%02d %02d:%02d:%02d" % time.localtime()[0:6]
|
||||||
|
"""millis of message"""
|
||||||
|
packString += ";" + parts.group(1)
|
||||||
|
"""direction"""
|
||||||
|
packString += ";" + parts.group(2)
|
||||||
|
"""header"""
|
||||||
|
packString += ";" + ';'.join(parts.group(3).split(" "))
|
||||||
|
"""body (may be void)"""
|
||||||
|
if len(parts.group(4)) > 0:
|
||||||
|
packString += ";" + ';'.join(parts.group(4).split(" "))
|
||||||
|
"""crc"""
|
||||||
|
packString += ";" + ';'.join(parts.group(5).split(" "))
|
||||||
|
print(packString)
|
||||||
|
with open(namespace.logfile[0], 'a+') as file:
|
||||||
|
file.write( packString )
|
||||||
|
|
||||||
|
def log_Dallas(isDallasLog):
|
||||||
|
parts = re.search("'([\w ]+)': Got Temperature=([-]?\d+\.\d+)°C", isDallasLog.group(1))
|
||||||
|
packString = '\n' + parts.group(1)
|
||||||
|
packString += ";" + "%4d-%02d-%02d %02d:%02d:%02d" % time.localtime()[0:6]
|
||||||
|
"""millis of message always empty"""
|
||||||
|
packString += ";"
|
||||||
|
"""direction"""
|
||||||
|
packString += ";[<=]"
|
||||||
|
"""additional data flag"""
|
||||||
|
packString += ";AA"
|
||||||
|
"""dallas temperature"""
|
||||||
|
packString += ";" + parts.group(2)
|
||||||
|
print(packString)
|
||||||
|
with open(namespace.logfile[0], 'a+') as file:
|
||||||
|
file.write( packString )
|
||||||
|
|
||||||
|
def log_callback(log):
|
||||||
|
"""Print the log for AirCon"""
|
||||||
|
isAirConLog = re.search("\[AirCon:\d+\]: (.+\])", log.message.decode('utf-8'))
|
||||||
|
if isAirConLog:
|
||||||
|
log_AC(isAirConLog)
|
||||||
|
if namespace.logdallas:
|
||||||
|
isDallasLog = re.search("\[dallas.sensor:\d+\]: (.+C)", log.message.decode('utf-8'))
|
||||||
|
if isDallasLog:
|
||||||
|
log_Dallas(isDallasLog)
|
||||||
|
|
||||||
|
|
||||||
|
# Subscribe to the log
|
||||||
|
# await api.subscribe_logs(log_callback, LOG_LEVEL_DEBUG)
|
||||||
|
|
||||||
|
# print(await api.device_info())
|
||||||
|
print(f"%s" % (await api.list_entities_services(),))
|
||||||
|
|
||||||
|
# key надо искать в выводе list_entities_services
|
||||||
|
service = aioesphomeapi.UserService(
|
||||||
|
name="send_data",
|
||||||
|
key=311254518,
|
||||||
|
args=[
|
||||||
|
aioesphomeapi.UserServiceArg(name="data_buf", type=aioesphomeapi.UserServiceArgType.INT_ARRAY),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.execute_service(
|
||||||
|
service,
|
||||||
|
data={
|
||||||
|
# display off
|
||||||
|
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x7F, 0xE0, 0x00, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
await api.execute_service(
|
||||||
|
service,
|
||||||
|
data={
|
||||||
|
# display on
|
||||||
|
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x7F, 0xE0, 0x00, 0x20, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = createParser()
|
||||||
|
namespace = parser.parse_args()
|
||||||
|
print(namespace.name[0], namespace.ip[0])
|
||||||
|
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
try:
|
||||||
|
#asyncio.ensure_future(main())
|
||||||
|
#loop.run_forever()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
except aioesphomeapi.InvalidAuthAPIError as e:
|
||||||
|
print(e)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
loop.close()
|
||||||
|
pass
|
||||||
@@ -29,6 +29,24 @@ logger:
|
|||||||
api:
|
api:
|
||||||
password: !secret api_pass
|
password: !secret api_pass
|
||||||
reboot_timeout: 0s
|
reboot_timeout: 0s
|
||||||
|
services:
|
||||||
|
# этот сервис можно вызвать из Home Assistant или Python. Он отправляет полученные байты в кондиционер
|
||||||
|
- service: send_data
|
||||||
|
variables:
|
||||||
|
data_buf: int[]
|
||||||
|
then:
|
||||||
|
# ВАЖНО! Только для инженеров!
|
||||||
|
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
|
||||||
|
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
|
||||||
|
# Вы действуете на свой страх и риск.
|
||||||
|
- aux_ac.send_packet:
|
||||||
|
id: aux_id
|
||||||
|
data: !lambda |-
|
||||||
|
std::vector<uint8_t> data{};
|
||||||
|
for (int n : data_buf) {
|
||||||
|
data.push_back( (uint8_t) n );
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
|
||||||
ota:
|
ota:
|
||||||
password: !secret ota_pass
|
password: !secret ota_pass
|
||||||
Reference in New Issue
Block a user