add UDP command processing and sender for dynamic testing
This commit is contained in:
parent
8591e7433d
commit
3384c89475
@ -4,6 +4,14 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace truma_inetbox {
|
namespace truma_inetbox {
|
||||||
|
|
||||||
@ -49,6 +57,9 @@ void TrumaiNetBoxApp::update() {
|
|||||||
ESP_LOGI(TAG, "Master mode: Discovery sent - expecting heater response on PID 3D");
|
ESP_LOGI(TAG, "Master mode: Discovery sent - expecting heater response on PID 3D");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UDP command receiver
|
||||||
|
this->process_udp_commands();
|
||||||
|
|
||||||
// Master TX scheduler (throttle to ~20ms spacing)
|
// Master TX scheduler (throttle to ~20ms spacing)
|
||||||
if (this->master_mode_ && !this->master_tx_queue_.empty()) {
|
if (this->master_mode_ && !this->master_tx_queue_.empty()) {
|
||||||
uint32_t now = micros();
|
uint32_t now = micros();
|
||||||
@ -503,4 +514,90 @@ void TrumaiNetBoxApp::trigger_discovery() {
|
|||||||
ESP_LOGI(TAG, "=== DISCOVERY COMPLETED - HEATER SHOULD RESPOND ON PID 3D ===");
|
ESP_LOGI(TAG, "=== DISCOVERY COMPLETED - HEATER SHOULD RESPOND ON PID 3D ===");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrumaiNetBoxApp::process_udp_commands() {
|
||||||
|
if (!this->master_mode_) return;
|
||||||
|
|
||||||
|
// Initialize UDP command receiver socket on port 5556 (one port higher than stream)
|
||||||
|
if (this->udp_cmd_sock_ < 0 && !this->udp_cmd_init_attempted_) {
|
||||||
|
this->udp_cmd_init_attempted_ = true;
|
||||||
|
|
||||||
|
this->udp_cmd_sock_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (this->udp_cmd_sock_ < 0) {
|
||||||
|
ESP_LOGW(TAG, "UDP command socket creation failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set non-blocking
|
||||||
|
int flags = fcntl(this->udp_cmd_sock_, F_GETFL, 0);
|
||||||
|
fcntl(this->udp_cmd_sock_, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
// Bind to port 5556
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
addr.sin_port = htons(5556);
|
||||||
|
|
||||||
|
if (bind(this->udp_cmd_sock_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||||
|
ESP_LOGW(TAG, "UDP command socket bind failed");
|
||||||
|
close(this->udp_cmd_sock_);
|
||||||
|
this->udp_cmd_sock_ = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "UDP command receiver listening on port 5556");
|
||||||
|
ESP_LOGI(TAG, "Send commands like: 'CMD:B2 23 17 46 10 03 NAD:01'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for incoming commands
|
||||||
|
if (this->udp_cmd_sock_ >= 0) {
|
||||||
|
char buffer[256];
|
||||||
|
struct sockaddr_in sender_addr;
|
||||||
|
socklen_t addr_len = sizeof(sender_addr);
|
||||||
|
|
||||||
|
ssize_t len = recvfrom(this->udp_cmd_sock_, buffer, sizeof(buffer) - 1, 0,
|
||||||
|
(struct sockaddr*)&sender_addr, &addr_len);
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
buffer[len] = '\0';
|
||||||
|
std::string cmd(buffer);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "UDP Command received: %s", cmd.c_str());
|
||||||
|
|
||||||
|
// Parse command format: "CMD:B2 23 17 46 10 03 NAD:01"
|
||||||
|
size_t cmd_pos = cmd.find("CMD:");
|
||||||
|
size_t nad_pos = cmd.find("NAD:");
|
||||||
|
|
||||||
|
if (cmd_pos != std::string::npos && nad_pos != std::string::npos) {
|
||||||
|
std::string hex_data = cmd.substr(cmd_pos + 4, nad_pos - cmd_pos - 5);
|
||||||
|
std::string nad_str = cmd.substr(nad_pos + 4);
|
||||||
|
|
||||||
|
// Parse NAD
|
||||||
|
uint8_t nad = (uint8_t)strtoul(nad_str.c_str(), nullptr, 16);
|
||||||
|
|
||||||
|
// Parse hex bytes
|
||||||
|
std::vector<uint8_t> payload;
|
||||||
|
std::istringstream iss(hex_data);
|
||||||
|
std::string byte_str;
|
||||||
|
|
||||||
|
while (iss >> byte_str) {
|
||||||
|
if (byte_str.length() == 2) {
|
||||||
|
uint8_t byte_val = (uint8_t)strtoul(byte_str.c_str(), nullptr, 16);
|
||||||
|
payload.push_back(byte_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.empty()) {
|
||||||
|
ESP_LOGI(TAG, "Sending diagnostic command to NAD 0x%02X with %zu bytes", nad, payload.size());
|
||||||
|
this->master_send_diag_single(nad, payload);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Failed to parse command payload");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Invalid command format. Use: CMD:B2 23 17 46 10 03 NAD:01");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|||||||
@ -32,6 +32,7 @@ class TrumaiNetBoxApp : public LinBusProtocol {
|
|||||||
bool master_send_diag_single(uint8_t nad, const std::vector<uint8_t> &payload);
|
bool master_send_diag_single(uint8_t nad, const std::vector<uint8_t> &payload);
|
||||||
bool master_scan_b2(uint8_t nad, uint8_t ident_start, uint8_t ident_end );
|
bool master_scan_b2(uint8_t nad, uint8_t ident_start, uint8_t ident_end );
|
||||||
void trigger_discovery();
|
void trigger_discovery();
|
||||||
|
void process_udp_commands();
|
||||||
|
|
||||||
TRUMA_DEVICE get_heater_device() const { return this->heater_device_; }
|
TRUMA_DEVICE get_heater_device() const { return this->heater_device_; }
|
||||||
TRUMA_DEVICE get_aircon_device() const { return this->aircon_device_; }
|
TRUMA_DEVICE get_aircon_device() const { return this->aircon_device_; }
|
||||||
@ -96,6 +97,10 @@ class TrumaiNetBoxApp : public LinBusProtocol {
|
|||||||
uint32_t last_master_send_us_ = 0;
|
uint32_t last_master_send_us_ = 0;
|
||||||
bool master_discovery_started_ = false;
|
bool master_discovery_started_ = false;
|
||||||
|
|
||||||
|
// UDP command receiver socket
|
||||||
|
int udp_cmd_sock_ = -1;
|
||||||
|
bool udp_cmd_init_attempted_ = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace truma_inetbox
|
} // namespace truma_inetbox
|
||||||
|
|||||||
85
tools/udp_cmd_sender.py
Normal file
85
tools/udp_cmd_sender.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
UDP Command Sender for Truma ESP32 Master Mode
|
||||||
|
Sends diagnostic commands to ESP32 for dynamic testing
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
def send_command(esp_ip, command, nad):
|
||||||
|
"""Send a diagnostic command to ESP32"""
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
# Format: "CMD:B2 23 17 46 10 03 NAD:01"
|
||||||
|
message = f"CMD:{command} NAD:{nad:02X}"
|
||||||
|
|
||||||
|
print(f"Sending: {message}")
|
||||||
|
sock.sendto(message.encode(), (esp_ip, 5556))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage:")
|
||||||
|
print(" python udp_cmd_sender.py <ESP32_IP> [command] [nad]")
|
||||||
|
print("")
|
||||||
|
print("Examples:")
|
||||||
|
print(" python udp_cmd_sender.py 192.168.1.90")
|
||||||
|
print(" python udp_cmd_sender.py 192.168.1.90 'B2 23 17 46 10 03' 01")
|
||||||
|
print(" python udp_cmd_sender.py 192.168.1.90 'B2 00 17 46 10 03' 01")
|
||||||
|
print(" python udp_cmd_sender.py 192.168.1.90 'B2 00 17 46 40 03' 01")
|
||||||
|
print("")
|
||||||
|
print("Interactive mode if no command specified.")
|
||||||
|
return
|
||||||
|
|
||||||
|
esp_ip = sys.argv[1]
|
||||||
|
|
||||||
|
if len(sys.argv) >= 4:
|
||||||
|
# Single command mode
|
||||||
|
command = sys.argv[2]
|
||||||
|
nad = int(sys.argv[3], 16)
|
||||||
|
send_command(esp_ip, command, nad)
|
||||||
|
else:
|
||||||
|
# Interactive mode
|
||||||
|
print(f"UDP Command Sender - Connected to ESP32 at {esp_ip}:5556")
|
||||||
|
print("Commands will be sent to ESP32 and you should see responses on UDP port 5555")
|
||||||
|
print("")
|
||||||
|
print("Useful commands to try:")
|
||||||
|
print(" B2 23 17 46 10 03 (Query identifier 0x23 with CP Plus signature)")
|
||||||
|
print(" B2 00 17 46 10 03 (Query identifier 0x00 with CP Plus signature)")
|
||||||
|
print(" B2 00 17 46 40 03 (Query identifier 0x00 with heater signature)")
|
||||||
|
print(" B2 00 17 46 01 03 (Query identifier 0x00 with old signature)")
|
||||||
|
print("")
|
||||||
|
print("Enter 'quit' to exit")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
user_input = input("Enter command (hex bytes): ").strip()
|
||||||
|
if user_input.lower() in ['quit', 'exit', 'q']:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get NAD
|
||||||
|
nad_input = input("Enter NAD (hex, default 01): ").strip()
|
||||||
|
if not nad_input:
|
||||||
|
nad = 1
|
||||||
|
else:
|
||||||
|
nad = int(nad_input, 16)
|
||||||
|
|
||||||
|
send_command(esp_ip, user_input, nad)
|
||||||
|
print("Command sent! Check UDP receiver for responses.")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
print("Goodbye!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user