Merge pull request #10697 from s-hadinger/zigbee_prep_rgb

Zigbee prepare RGB command
This commit is contained in:
s-hadinger 2021-01-25 22:53:00 +01:00 committed by GitHub
commit d7de841a1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 109 additions and 92 deletions

View File

@ -107,6 +107,7 @@ public:
#ifdef USE_ZIGBEE_EZSP
uint32_t permit_end_time = 0; // timestamp when permit join ends
uint16_t ezsp_version = 0;
#elif defined(USE_ZIGBEE_ZNP)
bool permit_end_time = false; // in ZNP mode it's only a boolean
#endif

View File

@ -151,59 +151,94 @@ void ZigbeeHueGroups(String * lights) {
}
}
void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) {
zigbeeZCLSendCmd(ZigbeeZCLSendMessage({
shortaddr,
0 /* groupaddr */,
cluster /*cluster*/,
0 /* endpoint */,
cmd /* cmd */,
0, /* manuf */
true /* cluster specific */,
true /* response */,
false /* discover route */,
0, /* zcl transaction id */
(&s != nullptr) ? s.getBuffer() : nullptr,
(&s != nullptr) ? s.len() : 0
}));
}
// Send commands
// Power On/Off
void ZigbeeHuePower(uint16_t shortaddr, bool power) {
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, "");
ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, *(SBuffer*)nullptr);
// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, "");
zigbee_devices.getShortAddr(shortaddr).setPower(power, 0);
}
// Dimmer
void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) {
if (dimmer > 0xFE) { dimmer = 0xFE; }
char param[8];
snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer);
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param);
SBuffer s(4);
s.add8(dimmer);
s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0008, 0x04, s);
// char param[8];
// snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer);
// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param);
zigbee_devices.getLight(shortaddr).setDimmer(dimmer);
}
// CT
void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) {
if (ct > 0xFEFF) { ct = 0xFEFF; }
AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct);
char param[12];
snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8);
uint8_t colormode = 2; // "ct"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param);
// AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct);
SBuffer s(4);
s.add16(ct);
s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s);
// char param[12];
// snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8);
// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param);
Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(colormode);
light.setColorMode(2); // "ct"
light.setCT(ct);
}
// XY
void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) {
char param[16];
if (x > 0xFEFF) { x = 0xFEFF; }
if (y > 0xFEFF) { y = 0xFEFF; }
snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8);
uint8_t colormode = 1; // "xy"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param);
SBuffer s(8);
s.add16(x);
s.add16(y);
s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0300, 0x07, s);
// char param[16];
// snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8);
// uint8_t colormode = 1; // "xy"
// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param);
Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(colormode);
light.setColorMode(1); // "xy"
light.setX(x);
light.setY(y);
}
// HueSat
void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) {
char param[16];
uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254);
if (sat > 0xFE) { sat = 0xFE; }
snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat);
uint8_t colormode = 0; // "hs"
zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param);
SBuffer s(4);
s.add8(hue);
s.add8(sat);
s.add16(0);
ZigbeeSendHue(shortaddr, 0x0300, 0x06, s);
// char param[16];
// snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat);
// uint8_t colormode = 0; // "hs"
// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param);
Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(colormode);
light.setColorMode(0); // "hs"
light.setSat(sat);
light.setHue(hue);
}

View File

@ -436,9 +436,7 @@ void restoreDumpAllDevices(void) {
for (const auto & device : zigbee_devices.getDevices()) {
const SBuffer buf = hibernateDevicev2(device);
if (buf.len() > 0) {
char hex_char[buf.len()*2+2];
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_RESTORE "\":\"ZbRestore %s\"}"),
ToHex_P(buf.buf(0), buf.len(), hex_char, sizeof(hex_char)));
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_RESTORE "\":\"ZbRestore %_B\"}"), &buf);
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA));
}
}

View File

@ -20,7 +20,7 @@
#ifdef USE_ZIGBEE
#ifdef USE_ZIGBEE_EZSP
#define Z_EEPROM_DEBUG
// #define Z_EEPROM_DEBUG
// The EEPROM is 64KB in size with individually writable bytes.
// They are conveniently organized in pages of 128 bytes to accelerate
@ -291,7 +291,7 @@ bool ZFS::findFileEntry(uint32_t name, ZFS_File_Entry & entry, uint8_t * _entry_
#ifdef Z_EEPROM_DEBUG
// {
// char hex_char[(sizeof(ZFS_File_Entry) * 2) + 2];
// AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Read entry %d at address 0x%04X contains %s"), entry_idx, entry_addr, ToHex_P((uint8_t*)&entry, sizeof(entry), hex_char, sizeof(hex_char)));
// AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Read entry %d at address 0x%04X contains %*_H"), entry_idx, entry_addr, sizeof(entry), &entry);
// }
#endif
if (entry.name == name) {

View File

@ -89,8 +89,7 @@ int32_t hydrateSingleDevice(const SBuffer & buf, size_t start, size_t len) {
#ifdef Z_EEPROM_DEBUG
{
if (segment_len > 3) {
char hex_char[((segment_len+1) * 2) + 2];
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%s"), shortaddr, ToHex_P(buf.buf(start+3), segment_len+1-3, hex_char, sizeof(hex_char)));
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%*_H"), shortaddr, segment_len+1-3, buf.buf(start+3));
}
}
#endif
@ -182,16 +181,14 @@ SBuffer hibernateDeviceData(const struct Z_Device & device, bool mqtt = false) {
buf.set8(0, buf.len() - 1);
{
size_t buf_len = buf.len() - 3;
char hex[2*buf_len + 1];
// skip first 3 bytes
ToHex_P(buf.buf(3), buf_len, hex, sizeof(hex));
size_t buf_len = buf.len() - 3;
if (mqtt) {
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%s\"}"), device.shortaddr, hex);
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%*_H\"}"), device.shortaddr, buf_len, buf.buf(3));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA));
} else {
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%s"), device.shortaddr, hex);
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%*_H"), device.shortaddr, buf_len, buf.buf(3));
}
}
}

View File

@ -723,8 +723,6 @@ public:
void log(void) {
char hex_char[_payload.len()*2+2];
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
"\"groupid\":%d," "\"clusterid\":\"0x%04X\"," "\"srcaddr\":\"0x%04X\","
"\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d,"
@ -732,14 +730,14 @@ public:
"\"fc\":\"0x%02X\","
"\"frametype\":%d,\"direction\":%d,\"disableresp\":%d,"
"\"manuf\":\"0x%04X\",\"transact\":%d,"
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"),
"\"cmdid\":\"0x%02X\",\"payload\":\"%_B\"}}"),
_groupaddr, _cluster_id, _srcaddr,
_srcendpoint, _dstendpoint, _wasbroadcast,
_linkquality, _securityuse, _seqnumber,
_frame_control,
_frame_control.b.frame_type, _frame_control.b.direction, _frame_control.b.disable_def_resp,
_manuf_code, _transact_seq, _cmd_id,
hex_char);
&_payload);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
} else {

View File

@ -415,16 +415,16 @@ int32_t ZNP_ReceiveCheckVersion(int32_t res, SBuffer &buf) {
int32_t EZ_ReceiveCheckVersion(int32_t res, SBuffer &buf) {
uint8_t protocol_version = buf.get8(2);
uint8_t stack_type = buf.get8(3);
uint16_t stack_version = buf.get16(4);
zigbee.ezsp_version = buf.get16(4);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Version\":\"%d.%d.%d.%d\",\"Protocol\":%d"
",\"Stack\":%d}}"),
ZIGBEE_STATUS_EZ_VERSION,
(stack_version & 0xF000) >> 12,
(stack_version & 0x0F00) >> 8,
(stack_version & 0x00F0) >> 4,
stack_version & 0x000F,
(zigbee.ezsp_version & 0xF000) >> 12,
(zigbee.ezsp_version & 0x0F00) >> 8,
(zigbee.ezsp_version & 0x00F0) >> 4,
zigbee.ezsp_version & 0x000F,
protocol_version,
stack_type
);
@ -432,7 +432,7 @@ int32_t EZ_ReceiveCheckVersion(int32_t res, SBuffer &buf) {
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
if (0x08 == protocol_version) {
if ((stack_version & 0xFF00) == 0x6700) {
if ((zigbee.ezsp_version & 0xFF00) == 0x6700) {
// If v6.7 there is a bug so we need to change the response
ZBW(ZBR_SET_OK2, 0x00, 0x00 /*high*/, 0x00 /*ok*/)
}

View File

@ -222,9 +222,6 @@ void ZigbeeInputLoop(void) {
uint32_t frame_len = zigbee_buffer->len();
if (frame_complete || (frame_len && (millis() > (zigbee_polling_window + ZIGBEE_POLLING)))) {
char hex_char[frame_len * 2 + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
if ((frame_complete) && (frame_len >= 3)) {
// frame received and has at least 3 bytes (without EOF), checking CRC
@ -246,7 +243,7 @@ void ZigbeeInputLoop(void) {
// remove 2 last bytes
if (crc_received != crc) {
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %s"), crc_received, crc, hex_char);
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %_B"), crc_received, crc, &zigbee_buffer);
} else {
// copy buffer
SBuffer ezsp_buffer = zigbee_buffer->subBuffer(0, frame_len - 2); // CRC
@ -262,14 +259,13 @@ void ZigbeeInputLoop(void) {
}
}
ToHex_P((unsigned char*)ezsp_buffer.getBuffer(), ezsp_buffer.len(), hex_char, sizeof(hex_char));
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%s\"}"), hex_char);
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%_B\"}"), &ezsp_buffer);
// now process the message
ZigbeeProcessInputRaw(ezsp_buffer);
}
} else {
// the buffer timed-out, print error and discard
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %d"), hex_char);
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %_B"), &zigbee_buffer);
}
zigbee_buffer->setLen(0); // empty buffer
escape = false;
@ -352,9 +348,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
//AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %*_H"), len, msg);
}
//
@ -486,17 +480,13 @@ void ZigbeeEZSPSendRaw(const uint8_t *msg, size_t len, bool send_cancel) {
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %*_H"), len, msg);
}
// Send an EZSP command and data
// Ex: Version with min v8 = 000008
void ZigbeeEZSPSendCmd(const uint8_t *msg, size_t len) {
char hex_char[len*2 + 2];
ToHex_P(msg, len, hex_char, sizeof(hex_char));
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %s"), hex_char);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %*_H"), len, msg);
SBuffer cmd(len+3); // prefix with seq number (1 byte) and frame control bytes (2 bytes)
@ -567,11 +557,8 @@ void ZigbeeProcessInputEZSP(SBuffer &buf) {
}
buf.setLen(buf.len() - 3);
char hex_char[buf.len()*2 + 2];
// log message
ToHex_P((unsigned char*)buf.getBuffer(), buf.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%s\"}"), hex_char);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%_B\"}"), &buf);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
} else {

View File

@ -264,9 +264,7 @@ bool ZigbeeUploadBootloaderPrompt(void) {
}
if (buf_len) {
char hex_char[256];
ToHex_P(serial_buffer, buf_len, hex_char, 256);
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("XMD: Rcvd %s"), hex_char);
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("XMD: Rcvd %*_H"), buf_len, serial_buffer);
}
return ((4 == ZbUpload.byte_counter) && (millis() > XModem.flush_delay));

View File

@ -183,23 +183,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
}
}
if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) {
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
}
AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
shortaddr, groupaddr, cluster, endpoint, cmd, param);
if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { // endpoint null is ok for group address
AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return;
}
// everything is good, we can send the command
uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
zigbeeZCLSendCmd(ZigbeeZCLSendMessage({
shortaddr,
groupaddr,
cluster /*cluster*/,
@ -209,14 +193,37 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
clusterSpecific /* not cluster specific */,
true /* response */,
false /* discover route */,
seq, /* zcl transaction id */
0, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
}
void zigbeeZCLSendCmd(const class ZigbeeZCLSendMessage &msg_const) {
ZigbeeZCLSendMessage msg = msg_const; // copy to a modifiable variable
if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) {
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address
msg.endpoint = zigbee_devices.findFirstEndpoint(msg.shortaddr);
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
}
AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %*_H"),
msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint, msg.cmd, msg.len, msg.msg);
if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) { // endpoint null is ok for group address
AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return;
}
// everything is good, we can send the command
msg.transacId = zigbee_devices.getNextSeqNumber(msg.shortaddr);
ZigbeeZCLSend_Raw(msg);
// now set the timer, if any, to read back the state later
if (clusterSpecific) {
if (msg.clusterSpecific) {
if (!Settings.flag5.zb_disable_autoquery) {
// read back attribute value unless it is disabled
sendHueUpdate(shortaddr, groupaddr, cluster, endpoint);
sendHueUpdate(msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint);
}
}
}
@ -463,7 +470,7 @@ void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupad
const char *cmd_s = ""; // pointer to payload string
bool clusterSpecific = true;
static char delim[] = ", "; // delimiters for parameters
static const char delim[] = ", "; // delimiters for parameters
// probe the type of the argument
// If JSON object, it's high level commands
// If String, it's a low level command

View File

@ -90,9 +90,7 @@ void TCPLoop(void)
}
}
if (buf_len > 0) {
char hex_char[TCP_BRIDGE_BUF_SIZE+1];
ToHex_P(tcp_buf, buf_len, hex_char, 256);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "from MCU: %s"), hex_char);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "from MCU: %*_H"), buf_len, tcp_buf);
for (uint32_t i=0; i<ARRAY_SIZE(client_tcp); i++) {
WiFiClient &client = client_tcp[i];
@ -112,9 +110,7 @@ void TCPLoop(void)
}
}
if (buf_len > 0) {
char hex_char[TCP_BRIDGE_BUF_SIZE+1];
ToHex_P(tcp_buf, buf_len, hex_char, 256);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "to MCU/%d: %s"), i+1, hex_char);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "to MCU/%d: %*_H"), i+1, buf_len, tcp_buf);
TCPSerial->write(tcp_buf, buf_len);
}
}