mirror of
https://github.com/esphome/esphome.git
synced 2025-01-07 13:21:44 +01:00
- add new trigger for receiving broadcast packet's
- add new action to send broadcast packet's - on the send and broadcast action 'dara' value is renamed to "payload" - peer value can now be se set as mac_address, uint64 value or 8digit alfanummeric peer code. - log message shows now only the peer code - espnow rejects all send messages before setup correctly. - the espnow protocal events should return true when they are overloaded.
This commit is contained in:
parent
4fcab8c82f
commit
f42fee8e62
4 changed files with 286 additions and 225 deletions
|
@ -1,7 +1,7 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COMMAND, CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_COMMAND, CONF_ID, CONF_PAYLOAD, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@nielsnl68", "@jesserockz"]
|
||||
|
@ -45,6 +45,9 @@ CONF_USE_SENT_CHECK = "use_sent_check"
|
|||
CONF_WIFI_CHANNEL = "wifi_channel"
|
||||
|
||||
|
||||
CONF_MAC_CHARS = "0123456789-AbCdEfGhIjKlMnOpQrStUvWxYz+aBcDeFgHiJkLmNoPqRsTuVwXyZ"
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode("utf-8")
|
||||
|
@ -55,18 +58,45 @@ def validate_raw_data(value):
|
|||
)
|
||||
|
||||
|
||||
def validate_espnow_peer():
|
||||
return PEER_SCHEMA
|
||||
def convert_mac_address(value):
|
||||
parts = value.split(":")
|
||||
if len(parts) != 6:
|
||||
raise cv.Invalid("MAC Address must consist of 6 : (colon) separated parts")
|
||||
parts_int = 0
|
||||
if any(len(part) != 2 for part in parts):
|
||||
raise cv.Invalid("MAC Address must be format XX:XX:XX:XX:XX:XX")
|
||||
for part in parts:
|
||||
try:
|
||||
parts_int = (parts_int << 8) + int(part, 16)
|
||||
except ValueError:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(
|
||||
"MAC Address parts must be hexadecimal values from 00 to FF"
|
||||
)
|
||||
return parts_int
|
||||
|
||||
|
||||
PEER_SCHEMA = cv.Any(
|
||||
{
|
||||
cv.templatable(validate_espnow_peer),
|
||||
cv.All(cv.string, cv.Length(min=8, max=8)),
|
||||
cv.mac_address,
|
||||
cv.uint64_t,
|
||||
}
|
||||
)
|
||||
def validate_peer(value):
|
||||
if isinstance(value, (int)):
|
||||
return value
|
||||
|
||||
if value.find(":") != -1:
|
||||
return convert_mac_address(value)
|
||||
|
||||
if len(value) == 8:
|
||||
value = cv.string_strict(value)
|
||||
|
||||
mac = 0
|
||||
for x in value:
|
||||
n = CONF_MAC_CHARS.find(x)
|
||||
if n == -1:
|
||||
raise cv.Invalid(f"peer code is invalid. ({value}|{x})")
|
||||
mac = (mac << 6) + n
|
||||
return mac
|
||||
|
||||
raise cv.Invalid(
|
||||
f"peer code '{value}' needs to be 8 characters width, or a valid Mac address or a hexidacimal value of 12 chars width starting with '0x'"
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
|
@ -97,7 +127,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_COMMAND): cv.Range(min=16, max=255),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address),
|
||||
cv.Optional(CONF_PEERS): cv.ensure_list(validate_peer),
|
||||
},
|
||||
cv.only_on_esp32,
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
@ -107,7 +137,7 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.is_esp32 and CORE.using_arduino:
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("WiFi", None)
|
||||
|
||||
cg.add_define("USE_ESPNOW")
|
||||
|
@ -115,9 +145,12 @@ async def to_code(config):
|
|||
cg.add(var.set_wifi_channel(config[CONF_WIFI_CHANNEL]))
|
||||
cg.add(var.set_auto_add_peer(config[CONF_AUTO_ADD_PEER]))
|
||||
cg.add(var.set_use_sent_check(config[CONF_USE_SENT_CHECK]))
|
||||
cg.add(var.set_convermation_timeout(config[CONF_CONFORMATION_TIMEOUT]))
|
||||
cg.add(var.set_conformation_timeout(config[CONF_CONFORMATION_TIMEOUT]))
|
||||
cg.add(var.set_retries(config[CONF_RETRIES]))
|
||||
|
||||
for conf in config.get(CONF_PEERS, []):
|
||||
cg.add(var.add_peer(conf))
|
||||
|
||||
for conf in config.get(CONF_ON_SENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if CONF_COMMAND in conf:
|
||||
|
@ -144,9 +177,6 @@ async def to_code(config):
|
|||
trigger, [(ESPNowPacketConst, "packet")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_PEERS, []):
|
||||
cg.add(var.add_peer(conf.as_hex))
|
||||
|
||||
|
||||
PROTOCOL_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
@ -167,46 +197,45 @@ async def register_protocol(var, config):
|
|||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESPNowComponent),
|
||||
cv.Optional(CONF_PEER, default=0xFFFFFFFFFFFF): cv.uint64_t,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
cv.Required(CONF_PAYLOAD): cv.templatable(validate_raw_data),
|
||||
cv.Optional(CONF_COMMAND): cv.templatable(cv.Range(min=16, max=255)),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
key=CONF_PAYLOAD,
|
||||
),
|
||||
)
|
||||
@automation.register_action(
|
||||
"espnow.send",
|
||||
SendAction,
|
||||
cv.maybe_simple_value(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESPNowComponent),
|
||||
cv.Required(CONF_PEER): validate_espnow_peer,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
cv.Optional(CONF_COMMAND): cv.templatable(cv.Range(min=16, max=255)),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
cv.Required(CONF_PEER): cv.templatable(validate_peer),
|
||||
cv.Required(CONF_PAYLOAD): cv.templatable(validate_raw_data),
|
||||
cv.Optional(CONF_COMMAND, default=0): cv.templatable(
|
||||
cv.Range(min=0, max=255)
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def send_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
if CONF_PEER in config:
|
||||
template_ = await cg.templatable(config[CONF_PEER].as_hex, args, cg.uint64)
|
||||
cg.add(var.set_peer(template_))
|
||||
peer = config.get(CONF_PEER, 0xFFFFFFFFFFFF)
|
||||
template_ = await cg.templatable(peer, args, cg.uint64)
|
||||
cg.add(var.set_peer(template_))
|
||||
|
||||
if CONF_COMMAND in config:
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
command = config.get(CONF_COMMAND, 0)
|
||||
template_ = await cg.templatable(command, args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
data = config.get(CONF_DATA, [])
|
||||
data = config.get(CONF_PAYLOAD, [])
|
||||
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))
|
||||
vec_ = cg.std_vector.template(cg.uint8)
|
||||
templ = await cg.templatable(data, args, vec_, vec_)
|
||||
cg.add(var.set_payload(templ))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
|
@ -216,7 +245,7 @@ async def send_action(config, action_id, template_arg, args):
|
|||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESPNowComponent),
|
||||
cv.Required(CONF_PEER): cv.templatable(cv.mac_address),
|
||||
cv.Required(CONF_PEER): cv.templatable(validate_peer),
|
||||
},
|
||||
key=CONF_PEER,
|
||||
),
|
||||
|
@ -227,13 +256,13 @@ async def send_action(config, action_id, template_arg, args):
|
|||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESPNowComponent),
|
||||
cv.Required(CONF_PEER): cv.templatable(cv.mac_address),
|
||||
cv.Required(CONF_PEER): cv.templatable(validate_peer),
|
||||
},
|
||||
key=CONF_PEER,
|
||||
),
|
||||
)
|
||||
async def del_peer_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
template_ = await cg.templatable(config[CONF_PEER].as_hex, args, cg.uint64)
|
||||
template_ = await cg.templatable(config[CONF_PEER], args, cg.uint64)
|
||||
cg.add(var.set_peer(template_))
|
||||
return var
|
||||
|
|
|
@ -29,6 +29,33 @@ static const size_t SEND_BUFFER_SIZE = 200;
|
|||
|
||||
ESPNowComponent *ESPNowComponent::static_{nullptr}; // NOLINT
|
||||
|
||||
std::string espnow_encode_peer(uint64_t peer) {
|
||||
std::string str1 = "";
|
||||
if (peer == FAILED) {
|
||||
return "[Not Set]";
|
||||
} else if (peer == ESPNOW_BROADCAST_ADDR)
|
||||
return "[BroadCast]";
|
||||
do {
|
||||
str1.push_back(chars[peer & 63]); // Add on the left
|
||||
peer = peer >> 6;
|
||||
} while (peer != 0);
|
||||
return str1;
|
||||
};
|
||||
|
||||
uint64_t espnow_decode_peer(std::string peer) {
|
||||
uint64_t mac = 0;
|
||||
if (peer.size() != 8)
|
||||
return FAILED;
|
||||
|
||||
for (int pos = peer.size(); pos > 0; pos--) {
|
||||
char *p = strchr(chars, peer[pos - 1]);
|
||||
if (p == nullptr)
|
||||
return FAILED;
|
||||
mac = (mac << 6) + (p - chars);
|
||||
}
|
||||
return mac;
|
||||
};
|
||||
|
||||
/* ESPNowComponent ********************************************************************** */
|
||||
|
||||
ESPNowComponent::ESPNowComponent() { ESPNowComponent::static_ = this; } // NOLINT
|
||||
|
@ -36,7 +63,7 @@ ESPNowComponent::ESPNowComponent() { ESPNowComponent::static_ = this; } // NOLI
|
|||
void ESPNowComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "esp_now:");
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Own Peer Address: %s.", decode_peer(this->own_peer_address_));
|
||||
ESP_LOGCONFIG(TAG, " Own Peer Address: %s.", espnow_encode_peer(this->own_peer_address_).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Wifi channel: %d.", this->wifi_channel_);
|
||||
ESP_LOGCONFIG(TAG, " Auto add new peers: %s.", this->auto_add_peer_ ? "Yes" : "No");
|
||||
|
||||
|
@ -46,9 +73,9 @@ void ESPNowComponent::dump_config() {
|
|||
}
|
||||
|
||||
void ESPNowComponent::show_packet(const std::string &title, const ESPNowPacket &packet) {
|
||||
ESP_LOGV(TAG, "%s packet. Peer: %s, Header: %c%c%c, Protocol:%c%c%c-%02x, Sequents: %d.%d, Size: %d, Valid: %s",
|
||||
title.c_str(), packet.peer.c_str(), packet.at(0), packet.at(1), packet.at(2), packet.at(3), packet.at(4),
|
||||
packet.at(5), packet.at(6), packet.at(7), packet.attempts, packet.content_size(),
|
||||
ESP_LOGV(TAG, "%s packet. Peer: '%s', Header: %c%c%c, Protocol:%c%c%c-%02x, Sequents: %d.%d, Size: %d, Valid: %s",
|
||||
title.c_str(), packet.get_peer_code().c_str(), packet.at(0), packet.at(1), packet.at(2), packet.at(3),
|
||||
packet.at(4), packet.at(5), packet.at(6), packet.at(7), packet.attempts, packet.content_size(),
|
||||
packet.is_valid() ? "Yes" : "No");
|
||||
}
|
||||
|
||||
|
@ -114,9 +141,8 @@ void ESPNowComponent::setup() {
|
|||
|
||||
esp_wifi_get_mac(WIFI_IF_STA, (uint8_t *) &this->own_peer_address_);
|
||||
|
||||
for (auto &peer : this->peers_) {
|
||||
ESP_LOGV(TAG, "Add peer '%s'.", peer.c_str());
|
||||
add_peer(peer);
|
||||
for (auto id : this->peers_) {
|
||||
add_peer(id);
|
||||
}
|
||||
|
||||
this->send_queue_ = xQueueCreate(SEND_BUFFER_SIZE, sizeof(ESPNowPacket));
|
||||
|
@ -146,25 +172,28 @@ void ESPNowComponent::espnow_task(void *param) {
|
|||
ESPNowPacket packet; // NOLINT
|
||||
for (;;) {
|
||||
if (xQueueReceive(this_espnow->receive_queue_, (void *) &packet, (TickType_t) 1) == pdTRUE) {
|
||||
uint8_t *mac = packet.peer.mac();
|
||||
if (esp_now_is_peer_exist(mac)) {
|
||||
this_espnow->call_on_receive_(packet);
|
||||
} else {
|
||||
if (this_espnow->auto_add_peer_) {
|
||||
this_espnow->add_peer(packet.peer);
|
||||
uint8_t *mac = packet.get_peer();
|
||||
if (!packet.is_broadcast || !this_espnow->call_on_broadcast_(packet)) {
|
||||
if (!esp_now_is_peer_exist(mac) && !this_espnow->call_on_new_peer_(packet)) {
|
||||
if (this_espnow->auto_add_peer_) {
|
||||
this_espnow->add_peer(packet.peer);
|
||||
}
|
||||
}
|
||||
if (esp_now_is_peer_exist(mac)) {
|
||||
this_espnow->call_on_receive_(packet);
|
||||
}
|
||||
this_espnow->call_on_new_peer_(packet);
|
||||
}
|
||||
}
|
||||
if (xQueueReceive(this_espnow->send_queue_, (void *) &packet, (TickType_t) 1) == pdTRUE) {
|
||||
if (packet.attempts > this_espnow->retries_) {
|
||||
ESP_LOGE(TAG, "Dropped '%s' (%d.%d). To many retries.", packet.peer.c_str(), packet.get_sequents(),
|
||||
ESP_LOGE(TAG, "Dropped '%s' (%d.%d). To many retries.", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts);
|
||||
this_espnow->unlock();
|
||||
continue;
|
||||
} else if (this_espnow->is_locked()) {
|
||||
if (packet.timestamp + this_espnow->conformation_timeout_ < millis()) {
|
||||
ESP_LOGW(TAG, "TimeOut '%s' (%d.%d).", packet.peer.c_str(), packet.get_sequents(), packet.attempts);
|
||||
ESP_LOGW(TAG, "TimeOut '%s' (%d.%d).", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts);
|
||||
this_espnow->unlock();
|
||||
}
|
||||
} else {
|
||||
|
@ -172,13 +201,13 @@ void ESPNowComponent::espnow_task(void *param) {
|
|||
packet.retry();
|
||||
packet.timestamp = millis();
|
||||
|
||||
esp_err_t err = esp_now_send(packet.peer.mac(), packet.get_content(), packet.content_size());
|
||||
esp_err_t err = esp_now_send(packet.get_peer(), packet.get_content(), packet.content_size());
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "Sended '%s' (%d.%d) from buffer. Wait for conformation.", packet.peer.c_str(),
|
||||
ESP_LOGD(TAG, "Sended '%s' (%d.%d) from buffer. Wait for conformation.", packet.get_peer_code().c_str(),
|
||||
packet.get_sequents(), packet.attempts);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Sending '%s' (%d.%d) FAILED. B: %d.", packet.peer.c_str(), packet.get_sequents(),
|
||||
ESP_LOGE(TAG, "Sending '%s' (%d.%d) FAILED. B: %d.", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts, this_espnow->send_queue_used());
|
||||
this_espnow->unlock();
|
||||
}
|
||||
|
@ -188,7 +217,7 @@ void ESPNowComponent::espnow_task(void *param) {
|
|||
}
|
||||
}
|
||||
|
||||
esp_err_t ESPNowComponent::add_peer(Peer peer) {
|
||||
esp_err_t ESPNowComponent::add_peer(uint64_t peer) {
|
||||
if (!this->is_ready()) {
|
||||
this->peers_.push_back(peer);
|
||||
return ESP_OK;
|
||||
|
@ -199,15 +228,15 @@ esp_err_t ESPNowComponent::add_peer(Peer peer) {
|
|||
memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
|
||||
peer_info.channel = this->wifi_channel_;
|
||||
peer_info.encrypt = false;
|
||||
memcpy((void *) peer_info.peer_addr, (void *) peer.mac(), 6);
|
||||
memcpy((void *) peer_info.peer_addr, (void *) &peer, 6);
|
||||
|
||||
return esp_now_add_peer(&peer_info);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t ESPNowComponent::del_peer(Peer peer) {
|
||||
if (esp_now_is_peer_exist((uint8_t *) peer.mac()))
|
||||
return esp_now_del_peer((uint8_t *) &peer.mac());
|
||||
esp_err_t ESPNowComponent::del_peer(uint64_t peer) {
|
||||
if (esp_now_is_peer_exist((uint8_t *) &peer))
|
||||
return esp_now_del_peer((uint8_t *) &peer);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
@ -226,25 +255,36 @@ ESPNowProtocol *ESPNowComponent::get_protocol_(uint32_t protocol) {
|
|||
return this->protocols_[protocol];
|
||||
}
|
||||
|
||||
void ESPNowComponent::call_on_receive_(ESPNowPacket &packet) {
|
||||
bool ESPNowComponent::call_on_receive_(ESPNowPacket &packet) {
|
||||
ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol());
|
||||
if (protocol != nullptr) {
|
||||
this->defer([protocol, packet]() { protocol->on_receive(packet); });
|
||||
return protocol->on_receive(packet);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ESPNowComponent::call_on_sent_(ESPNowPacket &packet, bool status) {
|
||||
bool ESPNowComponent::call_on_broadcast_(ESPNowPacket &packet) {
|
||||
ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol());
|
||||
if (protocol != nullptr) {
|
||||
this->defer([protocol, packet, status]() { protocol->on_sent(packet, status); });
|
||||
return protocol->on_broadcast(packet);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ESPNowComponent::call_on_new_peer_(ESPNowPacket &packet) {
|
||||
bool ESPNowComponent::call_on_sent_(ESPNowPacket &packet, bool status) {
|
||||
ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol());
|
||||
if (protocol != nullptr) {
|
||||
this->defer([protocol, packet]() { protocol->on_new_peer(packet); });
|
||||
return protocol->on_sent(packet, status);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ESPNowComponent::call_on_new_peer_(ESPNowPacket &packet) {
|
||||
ESPNowProtocol *protocol = this->get_protocol_(packet.get_protocol());
|
||||
if (protocol != nullptr) {
|
||||
return protocol->on_new_peer(packet);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**< callback function of receiving ESPNOW data */
|
||||
|
@ -270,6 +310,7 @@ void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data,
|
|||
(wifi_promiscuous_pkt_t *) (data - sizeof(wifi_pkt_rx_ctrl_t) - 39); // = sizeof (espnow_frame_format_t)
|
||||
rx_ctrl = &promiscuous_pkt->rx_ctrl;
|
||||
#endif
|
||||
|
||||
ESPNowPacket packet(addr, data, (uint8_t) size); // NOLINT
|
||||
packet.is_broadcast = broadcast;
|
||||
if (rx_ctrl != nullptr) {
|
||||
|
@ -288,23 +329,26 @@ void ESPNowComponent::on_data_received(const uint8_t *addr, const uint8_t *data,
|
|||
}
|
||||
|
||||
bool ESPNowComponent::send(ESPNowPacket packet) {
|
||||
if (this->is_failed()) {
|
||||
if (!this->is_ready()) {
|
||||
ESP_LOGE(TAG, "Cannot send espnow packet, espnow is not setup yet.");
|
||||
} else if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Cannot send espnow packet, espnow failed to setup");
|
||||
} else if (this->send_queue_full()) {
|
||||
ESP_LOGE(TAG, "Send Buffer Out of Memory.");
|
||||
} else if (!esp_now_is_peer_exist(packet.peer.mac())) {
|
||||
ESP_LOGE(TAG, "Peer not registered: %s.", packet.peer.c_str());
|
||||
} else if (!esp_now_is_peer_exist(packet.get_peer())) {
|
||||
ESP_LOGE(TAG, "Peer not registered: %s.", packet.get_peer_code().c_str());
|
||||
} else if (!packet.is_valid()) {
|
||||
ESP_LOGE(TAG, "This Packet is invalid: %s (%d.%d)", packet.c_str(), packet.get_sequents(), packet.attempts);
|
||||
ESP_LOGE(TAG, "This Packet is invalid: %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts);
|
||||
} else if (this->use_sent_check_ && packet.peer != ESPNOW_BROADCAST_ADDR) {
|
||||
ESP_LOGV(TAG, "Placing %s (%d.%d) into send buffer. Used: %d of %d", packet.peer.c_str(), packet.get_sequents(),
|
||||
packet.attempts, this->send_queue_used(), SEND_BUFFER_SIZE);
|
||||
ESP_LOGV(TAG, "Placing %s (%d.%d) into send buffer. Used: %d of %d", packet.get_peer_code().c_str(),
|
||||
packet.get_sequents(), packet.attempts, this->send_queue_used(), SEND_BUFFER_SIZE);
|
||||
xQueueSendToBack(this->send_queue_, (void *) &packet, 10);
|
||||
return true;
|
||||
} else {
|
||||
esp_err_t err = esp_now_send(packet.peer.mac(), packet.get_content(), packet.content_size());
|
||||
ESP_LOGV(TAG, "Sending to %s (%d.%d) directly%s.", packet.peer.c_str(), packet.get_sequents(), packet.attempts,
|
||||
(err == ESP_OK) ? "" : " FAILED");
|
||||
esp_err_t err = esp_now_send(packet.get_peer(), packet.get_content(), packet.content_size());
|
||||
ESP_LOGV(TAG, "Sending to %s (%d.%d) directly%s.", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts, (err == ESP_OK) ? "" : " FAILED");
|
||||
|
||||
this->call_on_sent_(packet, err == ESP_OK);
|
||||
return true;
|
||||
|
@ -315,16 +359,19 @@ bool ESPNowComponent::send(ESPNowPacket packet) {
|
|||
|
||||
void ESPNowComponent::on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
ESPNowPacket packet; // NOLINT
|
||||
Peer peer(mac_addr);
|
||||
uint64_t peer;
|
||||
memcpy((void *) &peer, mac_addr, 6);
|
||||
if (xQueuePeek(ESPNowComponent::static_->send_queue_, (void *) &packet, 10 / portTICK_PERIOD_MS) == pdTRUE) {
|
||||
if (packet.peer != peer) {
|
||||
ESP_LOGE(TAG, " Invalid mac address. Expected: %s (%d.%d); got: %s", packet.peer.c_str(), packet.get_sequents(),
|
||||
packet.attempts, peer.c_str());
|
||||
ESP_LOGE(TAG, " Invalid mac address. Expected: %s (%d.%d); got: %s", packet.get_peer_code().c_str(),
|
||||
packet.get_sequents(), packet.attempts, espnow_encode_peer(peer).c_str());
|
||||
return;
|
||||
} else if (status != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Sent packet failed for %s (%d.%d)", packet.peer.c_str(), packet.get_sequents(), packet.attempts);
|
||||
ESP_LOGE(TAG, "Sent packet failed for %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Confirm packet sent %s (%d.%d)", packet.peer.c_str(), packet.get_sequents(), packet.attempts);
|
||||
ESP_LOGV(TAG, "Confirm packet sent %s (%d.%d)", packet.get_peer_code().c_str(), packet.get_sequents(),
|
||||
packet.attempts);
|
||||
xQueueReceive(ESPNowComponent::static_->send_queue_, (void *) &packet, 10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
ESPNowComponent::static_->call_on_sent_(packet, status == ESP_OK);
|
||||
|
@ -334,10 +381,9 @@ void ESPNowComponent::on_data_sent(const uint8_t *mac_addr, esp_now_send_status_
|
|||
|
||||
/* ESPNowProtocol ********************************************************************** */
|
||||
|
||||
bool ESPNowProtocol::send(Peer peer, const uint8_t *data, uint8_t len, uint8_t command) {
|
||||
ESPNowPacket packet(peer, data, len, this->get_protocol_id()); // NOLINT
|
||||
bool ESPNowProtocol::send(uint64_t peer, const uint8_t *data, uint8_t len, uint8_t command) {
|
||||
ESPNowPacket packet(peer, data, len, this->get_protocol_id(), command); // NOLINT
|
||||
packet.set_sequents(this->get_next_sequents(packet.peer));
|
||||
packet.set_command(command);
|
||||
return this->parent_->send(packet);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_now.h>
|
||||
|
||||
#include <array>
|
||||
|
@ -31,93 +33,11 @@ static const uint8_t ESPNOW_COMMAND_RESEND = 0x05;
|
|||
static const char chars[] = "0123456789-AbCdEfGhIjKlMnOpQrStUvWxYz+aBcDeFgHiJkLmNoPqRsTuVwXyZ";
|
||||
static const uint64_t FAILED = 0;
|
||||
|
||||
std::string encode_peer(uint64_t peer) {
|
||||
std::string str1 = "";
|
||||
if (peer == FAILED) {
|
||||
return "[Not Set]"
|
||||
} else if (peer == ESPNOW_BROADCAST_ADDR)
|
||||
return "[BroadCast]";
|
||||
do {
|
||||
str1.push_back(chars[peer & 63]); // Add on the left
|
||||
peer = peer >> 6;
|
||||
} while (peer != 0);
|
||||
return str1;
|
||||
}
|
||||
|
||||
uint64_t decode_peer(std::string peer) {
|
||||
uint64_t mac = 0;
|
||||
if (peer.size() != 8)
|
||||
return FAILED;
|
||||
|
||||
for (int pos = peer.size(); pos > 0; pos--) {
|
||||
char *p = strchr(chars, peer[pos - 1]);
|
||||
if (p == nullptr)
|
||||
return FAILED;
|
||||
mac = (mac << 6) + (p - chars);
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
struct Peer {
|
||||
uint64_t id{0};
|
||||
|
||||
inline Peer() ESPHOME_ALWAYS_INLINE{};
|
||||
|
||||
inline Peer(const Peer &peer) ESPHOME_ALWAYS_INLINE { this.is = peer.id; }
|
||||
inline Peer(std::String peer) ESPHOME_ALWAYS_INLINE { this->id = decode_peer(peercode); }
|
||||
inline Peer(uint64_t peer) ESPHOME_ALWAYS_INLINE { this->id = peerid; }
|
||||
inline Peer(const uint8_t *peer) ESPHOME_ALWAYS_INLINE { this = peer; }
|
||||
|
||||
inline uint8_t *mac() const { return (uint8_t *) &(this->id); }
|
||||
inline std::string code() const { return encode_peer(this->id); }
|
||||
|
||||
inline char *c_str() const { return encode_peer(this->id).c_str(); }
|
||||
|
||||
inline bool operator=(const Peer &peer) { // NOLINT
|
||||
return this->id = peer.id;
|
||||
}
|
||||
inline Color &operator=(uint8_t *peer) ESPHOME_ALWAYS_INLINE {
|
||||
memcpy((void *) this->mac(), (const void *) peer, 6);
|
||||
return *this;
|
||||
}
|
||||
inline Color &operator=(std::String peer) ESPHOME_ALWAYS_INLINE {
|
||||
this->id = decode_peer(peercode);
|
||||
return *this;
|
||||
}
|
||||
inline Color &operator=(uint64_t peer) ESPHOME_ALWAYS_INLINE {
|
||||
this->id = peer;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator==(const Peer &peer) { // NOLINT
|
||||
return this->id == peer.id;
|
||||
}
|
||||
inline bool operator==(uint64_t peer) { // NOLINT
|
||||
return this->id == peer;
|
||||
}
|
||||
inline bool operator==(std::String peer) { // NOLINT
|
||||
return this->id == decode_peer(peer);
|
||||
}
|
||||
inline bool operator==(const uint8_t *peer) { // NOLINT
|
||||
return memcmp(peer, this->mac(), 6) == 0;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Peer &peer) { // NOLINT
|
||||
return this->id != peer.id;
|
||||
}
|
||||
inline bool operator!=(uint64_t peer) { // NOLINT
|
||||
return this->id != rhs.raw_32;
|
||||
}
|
||||
inline bool operator!=(std::String peer) { // NOLINT
|
||||
return this->id != decode_peer(peer);
|
||||
}
|
||||
inline bool operator!=(const uint8_t *peer) { // NOLINT
|
||||
return memcmp(peer, this->mac(), 6) != 0;
|
||||
}
|
||||
}
|
||||
std::string espnow_encode_peer(uint64_t peer);
|
||||
uint64_t espnow_decode_peer(std::string peer);
|
||||
|
||||
struct ESPNowPacket {
|
||||
Peer &peerid{0};
|
||||
uint64_t peer{0};
|
||||
uint8_t rssi{0};
|
||||
int8_t attempts{0};
|
||||
bool is_broadcast{false};
|
||||
|
@ -135,12 +55,20 @@ struct ESPNowPacket {
|
|||
inline ESPNowPacket() ESPHOME_ALWAYS_INLINE {}
|
||||
// Create packet to be send.
|
||||
|
||||
inline ESPNowPacket(Peer peer, const uint8_t *data, uint8_t size, uint32_t protocol,
|
||||
inline ESPNowPacket(uint64_t peer, const uint8_t *data, uint8_t size, uint32_t protocol,
|
||||
uint8_t command = 0) ESPHOME_ALWAYS_INLINE {
|
||||
assert(size <= MAX_ESPNOW_DATA_SIZE);
|
||||
assert(peer == 0);
|
||||
if (size > MAX_ESPNOW_DATA_SIZE) {
|
||||
ESP_LOGE("ESPNowPacket", "Payload size is to large. It should be less then %d instead it is %d",
|
||||
MAX_ESPNOW_DATA_SIZE, size);
|
||||
return;
|
||||
}
|
||||
if (peer == 0ull) {
|
||||
ESP_LOGE("ESPNowPacket", "No Peer defined.");
|
||||
return;
|
||||
}
|
||||
|
||||
this->peer = peer;
|
||||
this->is_broadcast = (peer == ESPNOW_BROADCAST_ADDR);
|
||||
|
||||
this->set_protocol(protocol);
|
||||
if (command != 0) {
|
||||
|
@ -150,10 +78,34 @@ struct ESPNowPacket {
|
|||
std::memcpy(this->get_payload(), data, size);
|
||||
}
|
||||
|
||||
inline ESPNowPacket(const uint8_t *peer, const uint8_t *data, uint8_t size) ESPHOME_ALWAYS_INLINE {
|
||||
if (size > MAX_ESPNOW_DATA_SIZE + this->prefix_size()) {
|
||||
ESP_LOGE("ESPNowPacket", "Received Payload size is to large. It should be less then %d instead it is %d",
|
||||
MAX_ESPNOW_DATA_SIZE + this->prefix_size(), size);
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_peer(peer);
|
||||
|
||||
this->size = size - this->prefix_size();
|
||||
std::memcpy(this->get_content(), data, size);
|
||||
}
|
||||
|
||||
uint8_t prefix_size() const { return sizeof(this->content.prefix); }
|
||||
|
||||
uint8_t content_size() const { return (this->prefix_size() + this->size); }
|
||||
|
||||
inline void set_peer(const uint8_t *peer) ESPHOME_ALWAYS_INLINE {
|
||||
if (*peer == 0) {
|
||||
peer = (uint8_t *) &ESPNOW_BROADCAST_ADDR;
|
||||
}
|
||||
memcpy((void *) this->get_peer(), (const void *) peer, 6);
|
||||
};
|
||||
inline bool is_peer(const uint8_t *peer) const { return memcmp(peer, this->get_peer(), 6) == 0; }
|
||||
|
||||
inline uint8_t *get_peer() const { return (uint8_t *) &(this->peer); }
|
||||
inline std::string get_peer_code() const { return espnow_encode_peer(this->peer); }
|
||||
|
||||
inline uint32_t get_protocol() const { return this->content.prefix.protocol & 0x00FFFFFF; }
|
||||
inline void set_protocol(uint32_t protocol) {
|
||||
this->content.prefix.protocol = (this->content.prefix.protocol & 0xFF000000) + (protocol & 0x00FFFFFF);
|
||||
|
@ -189,9 +141,11 @@ class ESPNowProtocol : public Parented<ESPNowComponent> {
|
|||
public:
|
||||
ESPNowProtocol(){};
|
||||
|
||||
virtual void on_receive(const ESPNowPacket &packet){};
|
||||
virtual void on_sent(const ESPNowPacket &packet, bool status){};
|
||||
virtual void on_new_peer(const ESPNowPacket &packet){};
|
||||
virtual bool on_receive(const ESPNowPacket &packet) { return false; };
|
||||
virtual bool on_broadcast(const ESPNowPacket &packet) { return false; };
|
||||
|
||||
virtual bool on_sent(const ESPNowPacket &packet, bool status) { return false; };
|
||||
virtual bool on_new_peer(const ESPNowPacket &packet) { return false; };
|
||||
|
||||
virtual uint32_t get_protocol_id() = 0;
|
||||
virtual std::string get_protocol_name() = 0;
|
||||
|
@ -214,7 +168,7 @@ class ESPNowProtocol : public Parented<ESPNowComponent> {
|
|||
return valid;
|
||||
}
|
||||
|
||||
bool send(peer peer, const uint8_t *data, uint8_t len, uint8_t command = 0);
|
||||
bool send(uint64_t peer, const uint8_t *data, uint8_t len, uint8_t command = 0);
|
||||
|
||||
protected:
|
||||
uint8_t next_sequents_{255};
|
||||
|
@ -228,22 +182,41 @@ class ESPNowDefaultProtocol : public ESPNowProtocol {
|
|||
void add_on_receive_callback(std::function<void(const ESPNowPacket)> &&callback) {
|
||||
this->on_receive_.add(std::move(callback));
|
||||
}
|
||||
void on_receive(const ESPNowPacket &packet) override { this->on_receive_.call(packet); };
|
||||
bool on_receive(const ESPNowPacket &packet) override {
|
||||
this->on_receive_.call(packet);
|
||||
return this->on_receive_.size() > 0;
|
||||
};
|
||||
|
||||
void add_on_broadcast_callback(std::function<void(const ESPNowPacket)> &&callback) {
|
||||
this->on_broadcast_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void on_broadcast(const ESPNowPacket &packet) override {
|
||||
this->on_broadcast_.call(packet);
|
||||
return this->on_broadcast_.size() > 0;
|
||||
};
|
||||
|
||||
void add_on_sent_callback(std::function<void(const ESPNowPacket, bool status)> &&callback) {
|
||||
this->on_sent_.add(std::move(callback));
|
||||
}
|
||||
void on_sent(const ESPNowPacket &packet, bool status) override { this->on_sent_.call(packet, status); };
|
||||
bool on_sent(const ESPNowPacket &packet, bool status) override {
|
||||
this->on_sent_.call(packet, status);
|
||||
return this->on_sent_.size() > 0;
|
||||
};
|
||||
|
||||
void add_on_peer_callback(std::function<void(const ESPNowPacket)> &&callback) {
|
||||
this->on_new_peer_.add(std::move(callback));
|
||||
}
|
||||
void on_new_peer(const ESPNowPacket &packet) override { this->on_new_peer_.call(packet); };
|
||||
void on_new_peer(const ESPNowPacket &packet) override {
|
||||
this->on_new_peer_.call(packet);
|
||||
return this->on_new_peer_.size() > 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const ESPNowPacket, bool)> on_sent_;
|
||||
CallbackManager<void(const ESPNowPacket)> on_receive_;
|
||||
CallbackManager<void(const ESPNowPacket)> on_new_peer_;
|
||||
CallbackManager<void(const ESPNowPacket)> on_broadcast_;
|
||||
};
|
||||
|
||||
class ESPNowComponent : public Component {
|
||||
|
@ -265,7 +238,7 @@ class ESPNowComponent : public Component {
|
|||
void set_wifi_channel(uint8_t channel) { this->wifi_channel_ = channel; }
|
||||
void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; }
|
||||
void set_use_sent_check(bool value) { this->use_sent_check_ = value; }
|
||||
void set_convermation_timeout(uint32_t timeout) { this->conformation_timeout_ = timeout; }
|
||||
void set_conformation_timeout(uint32_t timeout) { this->conformation_timeout_ = timeout; }
|
||||
void set_retries(uint8_t value) { this->retries_ = value; }
|
||||
|
||||
void setup() override;
|
||||
|
@ -278,8 +251,8 @@ class ESPNowComponent : public Component {
|
|||
this->protocols_[protocol->get_protocol_id()] = protocol;
|
||||
}
|
||||
|
||||
esp_err_t add_peer(Peer peer);
|
||||
esp_err_t del_peer(Peer peer);
|
||||
esp_err_t add_peer(uint64_t peer);
|
||||
esp_err_t del_peer(uint64_t peer);
|
||||
|
||||
bool send_queue_empty() { return uxQueueMessagesWaiting(this->send_queue_) == 0; }
|
||||
bool send_queue_full() { return uxQueueSpacesAvailable(this->send_queue_) == 0; }
|
||||
|
@ -311,9 +284,10 @@ class ESPNowComponent : public Component {
|
|||
|
||||
bool lock_{false};
|
||||
|
||||
void call_on_receive_(ESPNowPacket &packet);
|
||||
void call_on_sent_(ESPNowPacket &packet, bool status);
|
||||
void call_on_new_peer_(ESPNowPacket &packet);
|
||||
bool call_on_receive_(ESPNowPacket &packet);
|
||||
bool call_on_broadcast_(ESPNowPacket &packet);
|
||||
bool call_on_sent_(ESPNowPacket &packet, bool status);
|
||||
bool call_on_new_peer_(ESPNowPacket &packet);
|
||||
|
||||
QueueHandle_t receive_queue_{};
|
||||
QueueHandle_t send_queue_{};
|
||||
|
@ -326,34 +300,18 @@ class ESPNowComponent : public Component {
|
|||
|
||||
template<typename... Ts> class SendAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
public:
|
||||
template<typename V> void set_peer(V peer) { this->peer_ = peer; }
|
||||
template<typename V> void set_command(V command) { this->command_ = command; }
|
||||
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->dynamic_ = true;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) { this->data_static_ = data; }
|
||||
TEMPLATABLE_VALUE(uint64_t, peer);
|
||||
TEMPLATABLE_VALUE(uint8_t, command);
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, payload);
|
||||
|
||||
void play(Ts... x) override {
|
||||
Peer peer = this->peer_.value(x...);
|
||||
uint8_t command = 0;
|
||||
if (this->command_.has_value()) {
|
||||
command = this->mac_.value(x...);
|
||||
}
|
||||
uint64_t peer = this->peer_.value(x...);
|
||||
uint8_t command = this->command_.value(x...);
|
||||
std::vector<uint8_t> payload = this->payload_.value(x...);
|
||||
ESP_LOGVV("SendAction", "send to 0x%12llx, command %d, payload size: %d", peer, command, payload.size());
|
||||
|
||||
if (this->dynamic_) {
|
||||
this->data_static_ = this->data_func_(x...);
|
||||
}
|
||||
this->parent_->get_default_protocol()->send(peer, this->data_static_.data(), this->data_static_.size(), command);
|
||||
this->parent_->get_default_protocol()->send(peer, payload.data(), payload.size(), command);
|
||||
}
|
||||
|
||||
protected:
|
||||
TemplatableValue<uint8_t, Ts...> command_{};
|
||||
TemplatableValue<Peer, Ts...> peer_{};
|
||||
bool dynamic_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class NewPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
|
@ -365,19 +323,19 @@ template<typename... Ts> class NewPeerAction : public Action<Ts...>, public Pare
|
|||
}
|
||||
|
||||
protected:
|
||||
TemplatableValue<Peer, Ts...> mac_{};
|
||||
TemplatableValue<uint64_t, Ts...> mac_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class DelPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
public:
|
||||
template<typename V> void set_peer(V mac) { this->peer_ = peer; }
|
||||
template<typename V> void set_peer(V peer) { this->peer_ = peer; }
|
||||
void play(Ts... x) override {
|
||||
auto peer = this->peer_.value(x...);
|
||||
parent_->del_peer(mac);
|
||||
parent_->del_peer(peer);
|
||||
}
|
||||
|
||||
protected:
|
||||
TemplatableValue<Peer, Ts...> peer_{};
|
||||
TemplatableValue<uint64_t, Ts...> peer_{};
|
||||
};
|
||||
|
||||
class ESPNowSentTrigger : public Trigger<const ESPNowPacket, bool> {
|
||||
|
@ -410,6 +368,21 @@ class ESPNowReceiveTrigger : public Trigger<const ESPNowPacket> {
|
|||
uint8_t command_{0};
|
||||
};
|
||||
|
||||
class ESPNowBroadcaseTrigger : public Trigger<const ESPNowPacket> {
|
||||
public:
|
||||
explicit ESPNowBroadcaseTrigger(ESPNowComponent *parent) {
|
||||
parent->get_default_protocol()->add_on_broadcast_callback([this](const ESPNowPacket packet) {
|
||||
if ((this->command_ == 0) || this->command_ == packet.get_command()) {
|
||||
this->trigger(packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
void set_command(uint8_t command) { this->command_ = command; }
|
||||
|
||||
protected:
|
||||
uint8_t command_{0};
|
||||
};
|
||||
|
||||
class ESPNowNewPeerTrigger : public Trigger<const ESPNowPacket> {
|
||||
public:
|
||||
explicit ESPNowNewPeerTrigger(ESPNowComponent *parent) {
|
||||
|
|
|
@ -3,11 +3,6 @@ substitutions:
|
|||
name: "lum-iot-test"
|
||||
friendly_name: "Project Template"
|
||||
|
||||
# external_components:
|
||||
# - source: github://pr#7141
|
||||
# components: [ espnow ]
|
||||
# refresh: 1 sec
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
|
@ -35,11 +30,29 @@ espnow:
|
|||
auto_add_peer: true
|
||||
peers:
|
||||
- FF:FF:FF:FF:FF:FF
|
||||
- 0xFFFFFFFFFFFF
|
||||
- ZZZZZZZZ
|
||||
on_receive:
|
||||
- logger.log:
|
||||
format: "Received: %s RSSI: %d"
|
||||
args: [packet.get_payload(), packet.rssi]
|
||||
format: "Received: '%s' from '%s' command: %d RSSI: %d"
|
||||
args:
|
||||
[
|
||||
packet.get_payload(),
|
||||
packet.get_peer_code().c_str(),
|
||||
packet.command,
|
||||
packet.rssi,
|
||||
]
|
||||
|
||||
on_broadcast:
|
||||
- command: 123
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Broadcast Received from: '%s' RSSI: %d: %s"
|
||||
args: [packet.get_peer_code().c_str(), packet.rssi]
|
||||
|
||||
interval:
|
||||
- interval: 10sec
|
||||
then:
|
||||
- espnow.broatcast: "hallo everyone"
|
||||
- espnow.broatcast:
|
||||
payload: "hallo everyone"
|
||||
command: 123
|
||||
|
|
Loading…
Reference in a new issue