Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize write requests #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ nibegw:
# Optional port this device will listen to to receive write request. Defaults to 10000
write_port: 10000

# Optional command ports for specific requests.
# ports:
# - address: RMU40_S3
# token: RMU_WRITE
# port: 10001

acknowledge:
- MODBUS40
Expand Down
2 changes: 1 addition & 1 deletion components/nibegw/NibeGwClimate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ enum RmuWriteIndex {
RMU_WRITE_INDEX_END = RMU_WRITE_INDEX_SETPOINT_S4 + 1
};

#define RMU_WRITE_INDEX_SETPOINT_SX(index) (RMU_WRITE_INDEX_SETPOINT_S1 + (index) *2)
#define RMU_WRITE_INDEX_SETPOINT_SX(index) (RMU_WRITE_INDEX_SETPOINT_S1 + (index) * 2)

static const int RMU_DEVICE_VERSION = 0x0103;

Expand Down
31 changes: 22 additions & 9 deletions components/nibegw/NibeGwComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ NibeGwComponent::NibeGwComponent(esphome::GPIOPin *dir_pin) {
std::bind(&NibeGwComponent::callback_msg_received, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&NibeGwComponent::callback_msg_token_received, this, std::placeholders::_1, std::placeholders::_2));

udp_read_.onPacket([this](AsyncUDPPacket packet) { token_request_cache(packet, MODBUS40, READ_TOKEN); });
udp_write_.onPacket([this](AsyncUDPPacket packet) { token_request_cache(packet, MODBUS40, WRITE_TOKEN); });
for (auto &x : requests_sockets_) {
auto address = std::get<0>(x.first);
auto token = std::get<1>(x.first);

x.second.socket->onPacket(
[this, address, token](AsyncUDPPacket packet) { token_request_cache(packet, address, token); });
}
}

static request_data_type dedup(const byte *const data, int len, byte val) {
Expand Down Expand Up @@ -40,10 +45,15 @@ void NibeGwComponent::callback_msg_received(const byte *const data, int len) {
return;
}

/* always sending standard data from modbus read token */
auto &udp = requests_sockets_[request_key_type(MODBUS40, READ_TOKEN)].socket;
if (!udp)
return;

ESP_LOGD(TAG, "UDP Packet with %d bytes to send", len);
for (auto target = udp_targets_.begin(); target != udp_targets_.end(); target++) {
ip_addr_t address = (ip_addr_t) std::get<0>(*target);
if (!udp_read_.writeTo(data, len, &address, std::get<1>(*target))) {
if (!udp.writeTo(data, len, &address, std::get<1>(*target))) {
ESP_LOGW(TAG, "UDP Packet send failed to %s:%d", std::get<0>(*target).str().c_str(), std::get<1>(*target));
}
}
Expand Down Expand Up @@ -124,22 +134,25 @@ void NibeGwComponent::dump_config() {
for (auto address = udp_source_ip_.begin(); address != udp_source_ip_.end(); address++) {
ESP_LOGCONFIG(TAG, " Source: %s", address->str().c_str());
}
ESP_LOGCONFIG(TAG, " Read Port: %d", udp_read_port_);
ESP_LOGCONFIG(TAG, " Write Port: %d", udp_write_port_);
for (auto const &x : requests_sockets_) {
ESP_LOGCONFIG(TAG, " Handler %x:%x Port: %d", std::get<0>(x.first), std::get<1>(x.first), x.second.port);
}
}

void NibeGwComponent::loop() {
if (network::is_connected() && !is_connected_) {
ESP_LOGI(TAG, "Connecting network ports.");
udp_read_.listen(udp_read_port_);
udp_write_.listen(udp_write_port_);
for (auto &x : requests_sockets_) {
x.second.socket->listen(x.second.port);
}
is_connected_ = true;
}

if (!network::is_connected() && is_connected_) {
ESP_LOGI(TAG, "Disconnecting network ports.");
udp_read_.close();
udp_write_.close();
for (auto &x : requests_sockets_) {
x.second.socket->close();
}
is_connected_ = false;
}

Expand Down
24 changes: 19 additions & 5 deletions components/nibegw/NibeGwComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <vector>
#include <cstddef>
#include <map>
#include <memory>

#include "esphome.h"
#include "esphome/core/component.h"
Expand Down Expand Up @@ -38,22 +39,24 @@ class NibeGwComponent : public esphome::Component, public esphome::uart::UARTDev
}
const char *TAG = "nibegw";
const int requests_queue_max = 3;
int udp_read_port_ = 9999;
int udp_write_port_ = 10000;

std::vector<network::IPAddress> udp_source_ip_;
bool is_connected_ = false;

struct request_socket_type {
int port;
std::unique_ptr<AsyncUDP> socket;
};

std::vector<target_type> udp_targets_;
std::map<request_key_type, std::queue<request_data_type>> requests_;
std::map<request_key_type, request_provider_type> requests_provider_;
std::map<request_key_type, request_socket_type> requests_sockets_;
std::map<request_key_type, message_listener_type> message_listener_;
HighFrequencyLoopRequester high_freq_;

NibeGw *gw_;

AsyncUDP udp_read_;
AsyncUDP udp_write_;

void callback_msg_received(const byte *const data, int len);
int callback_msg_token_received(eTokenType token, byte *data);
void callback_debug(byte verbose, char *data);
Expand All @@ -68,6 +71,12 @@ class NibeGwComponent : public esphome::Component, public esphome::uart::UARTDev
udp_write_port_ = port;
};

void add_socket_request(int address, int token, int port) {
auto &handler = requests_sockets_[request_key_type(address, token)];
handler.port = port;
handler.socket = make_unique<AsyncUDP>();
}

void add_target(const network::IPAddress &ip, int port) {
auto target = target_type(ip, port);
udp_targets_.push_back(target);
Expand Down Expand Up @@ -97,6 +106,11 @@ class NibeGwComponent : public esphome::Component, public esphome::uart::UARTDev
queue.push(std::move(request));
}

void add_acknowledge(int address) {
gw_->setSendAcknowledge(1);
gw_->setAcknowledge(address, true);
}

NibeGw &gw() {
return *gw_;
}
Expand Down
35 changes: 24 additions & 11 deletions components/nibegw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID
from esphome.const import (
CONF_ID,
CONF_PORT,
)
from esphome import pins
from esphome.core import CORE
from esphome.components.network import IPAddress
Expand All @@ -28,6 +31,7 @@
CONF_ACKNOWLEDGE_SMS40 = "sms40"
CONF_READ_PORT = "read_port"
CONF_WRITE_PORT = "write_port"
CONF_PORTS = "ports"
CONF_SOURCE = "source"
CONF_ADDRESS = "address"
CONF_TOKEN = "token"
Expand Down Expand Up @@ -76,12 +80,21 @@ def real_enum(enum: Enum):
}
)

PORTS_SCHEMA = cv.Schema(
{
cv.Required(CONF_PORT): cv.port,
cv.Required(CONF_ADDRESS): cv.Any(real_enum(Addresses), int),
cv.Required(CONF_TOKEN): cv.Any(real_enum(Token), int),
}
)

UDP_SCHEMA = cv.Schema(
{
cv.Required(CONF_TARGET, []): cv.ensure_list(TARGET_SCHEMA),
cv.Optional(CONF_SOURCE, []): cv.ensure_list(cv.ipv4),
cv.Optional(CONF_READ_PORT, default=9999): cv.port,
cv.Optional(CONF_WRITE_PORT, default=10000): cv.port,
cv.Optional(CONF_SOURCE, []): cv.ensure_list(cv.ipv4)
cv.Optional(CONF_PORTS, []): cv.ensure_list(PORTS_SCHEMA)
}
)

Expand Down Expand Up @@ -120,19 +133,19 @@ async def to_code(config):
if udp := config.get(CONF_UDP):
for target in udp[CONF_TARGET]:
cg.add(var.add_target(IPAddress(*target[CONF_TARGET_IP].args), target[CONF_TARGET_PORT]))
cg.add(var.set_read_port(udp[CONF_READ_PORT]))
cg.add(var.set_write_port(udp[CONF_WRITE_PORT]))

for port in udp[CONF_PORTS]:
cg.add(var.add_socket_request(port[CONF_ADDRESS], port[CONF_TOKEN], port[CONF_PORT]))
if port_number := udp[CONF_READ_PORT]:
cg.add(var.add_socket_request(Addresses.MODBUS40.value, Token.MODBUS_READ.value, port_number))
if port_number := udp[CONF_WRITE_PORT]:
cg.add(var.add_socket_request(Addresses.MODBUS40.value, Token.MODBUS_WRITE.value, port_number))

for source in udp[CONF_SOURCE]:
cg.add(var.add_source_ip(IPAddress(*source.args)))

if config[CONF_ACKNOWLEDGE]:
cg.add(var.gw().setSendAcknowledge(1))
for address in config[CONF_ACKNOWLEDGE]:
cg.add(
var.gw().setAcknowledge(address, True)
)
else:
cg.add(var.gw().setSendAcknowledge(0))
cg.add(var.add_acknowledge(address))


def xor8(data: bytes) -> int:
Expand Down