prettyprint

2025年3月19日 星期三

[Raspberry Pi Pico W] MODBUS Ep 4. MODBUS/TCP Security || LWIP || MbedTLS || Node-RED

 本文章介紹MODBUS/TCP Security, 網路library使用LWIP altcp(application layer TCP),此library將SSL/TLS(MbedTLS)結合原來的TCP library。

Modbus server使用Raspberry Pi Pico W,Clinet端分使用Node-RED與Raspberry Pi Pico W來進行測試,在本專案中利用Wireshark擷取封包來比較未加密與透過Transport layer security加密的ADU封包。

MODBUS/TCP security在原來MODBUS TCP加上TLS Transport Layer Security, 如下圖比較:

LWIP網路API使用altcp,此組函式庫擴張原來TCP library,在原來使用tcp_*的呼叫流程中為呼叫altcp_*即可。另外須先初始或tls_config將Root CA, server primary key與server certification加入呼叫

server:  altcp_tls_create_config_server_privkey_cert(...)
client:  altcp_tls_create_config_client(...)
中。

若須使用self-signed certification可使用下列步驟建立:
  1. openssl genrsa -des3 -out ca.key 2048:
    建立Root CA key。
  2. openssl req -new -x509 -days 3650 -key ca.key -out ca.cert:
    建立self-signed root Certificate。
  3. openssl genrsa -out server.key 2048:
    建立server side private key。
  4. openssl req -new -out server.csr -key server.key
    使用server key製作server憑證需求(certificate request)
  5. openssl x509 -req -in server.csr -CA ca.cert -CAkey ca.key -CAcreateserial -out server.cert -days 365
    使用root CA ca.cert簽署server side certificate。
完整程式碼附於文章末尾。

成果影片:
程式碼:
Server:
  • CMakeLists.txt
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.0)
set(toolchainVersion 13_3_Rel1)
set(picotoolVersion 2.1.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico_w CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(picow_modbus_tcp_security_server C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(picow_modbus_tcp_security_server
picow_modbus_tcp_security_server.c
nanomodbus/nanomodbus.c
picow_tls_server/picow_tls_server.c
modbus_tcp_server/modbus_tcp_server.c)
pico_set_program_name(picow_modbus_tcp_security_server "picow_modbus_tcp_security_server")
pico_set_program_version(picow_modbus_tcp_security_server "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(picow_modbus_tcp_security_server 1)
pico_enable_stdio_usb(picow_modbus_tcp_security_server 0)
# Add the standard library to the build
target_link_libraries(picow_modbus_tcp_security_server
pico_stdlib)
# Add the standard include files to the build
target_include_directories(picow_modbus_tcp_security_server PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
add_subdirectory(GPIO_StepperMotor)
# Add any user requested libraries
target_link_libraries(picow_modbus_tcp_security_server
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_mbedtls
pico_mbedtls
pico_lwip_mdns
gpio_stepper_motor
)
pico_add_extra_outputs(picow_modbus_tcp_security_server)
# Ignore warnings from lwip code
set_source_files_properties(
${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
PROPERTIES
COMPILE_OPTIONS "-Wno-unused-result"
)
  • picow_modbus_tcp_security_server.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mdns.h"
#include "nanomodbus/nanomodbus.h"
#include "picow_tls_server/picow_tls_server.h"
#include "modbus_tcp_server/modbus_tcp_server.h"
#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/dns.h"
#include "GPIO_StepperMotor/steppermotor.h"
#define MODBUS_SERVER_HOST_NAME "modbus_tls_tcp"
int main()
{
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN,true);
stepperMotor_init(MOTOR_PIN1, MOTOR_PIN2, MOTOR_PIN3, MOTOR_PIN4);
stepperMotor_set_speed(5);
// ====== Initialise the Wi-Fi chip =====
if (cyw43_arch_init()) {
printf("Wi-Fi init failed\n");
return -1;
}
cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,false);
// Enable wifi station
cyw43_arch_enable_sta_mode();
DEBUG_print("Connecting to Wi-Fi...\n");
if (cyw43_arch_wifi_connect_timeout_ms(SSID, PWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
DEBUG_print("failed to connect.\n");
return 1;
} else {
DEBUG_print("Connected.\n");
// Read the ip address in a human readable way
uint8_t *ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr);
DEBUG_print("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
}
#if LWIP_MDNS_RESPONDER
mdns_resp_init();
uint8_t host_name[] = MODBUS_SERVER_HOST_NAME;
if (mdns_resp_add_netif(netif_default, host_name)== ERR_OK)
{
printf("mDNS add successfully\n");
} else {
printf("mDNS failure\n");
}
#endif
picow_tls_server_init();
//======= modbus tcp server(modbus server) init ======
nmbs_platform_conf platform_conf;
nmbs_platform_conf_create(&platform_conf);
platform_conf.transport = NMBS_TRANSPORT_TCP;
platform_conf.read = nmbs_transport_read;
platform_conf.write = nmbs_transport_write;
extern ALTCP_SERVER_T *altcp_tcp_server;
platform_conf.arg = altcp_tcp_server;
nmbs_callbacks callbacks;
nmbs_callbacks_create(&callbacks);
callbacks.read_coils = handle_altcp_read_coils;
callbacks.read_discrete_inputs = handle_altcp_read_discrete_inputs;
callbacks.write_single_coil = handle_altcp_write_single_coil;
callbacks.write_multiple_coils = handle_altcp_write_multiple_coils;
callbacks.read_holding_registers = handler_tcp_read_holding_registers;
callbacks.write_multiple_registers = handle_altcp_write_multiple_registers;
callbacks.write_single_register = handle_altcp_write_single_register;
callbacks.read_file_record = handle_altcp_read_file_record;
callbacks.write_file_record = handle_altcp_write_file_record;
callbacks.arg = altcp_tcp_server;
nmbs_error err = nmbs_server_create(&nmbs_tls, 0, &platform_conf, &callbacks);
if (err != NMBS_ERROR_NONE) {
printf("Error creating modbus server\n");
return 1;
}
// Set only the polling timeout. Byte timeout will be handled by the TCP connection
nmbs_set_read_timeout(&nmbs_tls, 2000);
//======= modbus tcp server(modbus server) init ======
printf("Modbus TCP server started\n");
cyw43_gpio_set(&cyw43_state, CYW43_WL_GPIO_LED_PIN,true);
//========================
while (true) {
nmbs_server_poll(&nmbs_tls);
#if PICO_CYW43_ARCH_POLL
cyw43_arch_poll();
cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000));
#else
tight_loop_contents();
#endif
}
}
  • picow_tls_server.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "nanomodbus/nanomodbus.h"
#include "lwip/pbuf.h"
#include "lwip/altcp_tls.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp.h"
#include "lwip/def.h"
#include "picow_tls_server.h"
#include "modbus_tcp_server/modbus_tcp_server.h"
#include "modbus_server_cert.h"
static int8_t conn_count=0;
ALTCP_SERVER_T *altcp_tcp_server;
static struct altcp_tls_config* tls_config;
static err_t altcp_client_close(struct altcp_pcb *client_pcb) {
err_t err = ERR_OK;
if (client_pcb != NULL) {
altcp_arg(client_pcb, NULL);
altcp_poll(client_pcb, NULL, 0);
altcp_sent(client_pcb, NULL);
altcp_recv(client_pcb, NULL);
altcp_err(client_pcb, NULL);
err = altcp_close(client_pcb);
if (err != ERR_OK) {
DEBUG_print("close failed %d, calling abort\n", err);
altcp_abort(client_pcb);
err = ERR_ABRT;
}
client_pcb = NULL;
altcp_tcp_server->connected = false;
}
DEBUG_print("connections after client_closed:%d\n", --conn_count);
return err;
}
static err_t altcp_server_close(void *arg) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
err_t err = ERR_OK;
if (state->client_pcb != NULL) {
altcp_arg(state->client_pcb, NULL);
altcp_poll(state->client_pcb, NULL, 0);
altcp_sent(state->client_pcb, NULL);
altcp_recv(state->client_pcb, NULL);
altcp_err(state->client_pcb, NULL);
err = altcp_close(state->client_pcb);
if (err != ERR_OK) {
DEBUG_print("close failed %d, calling abort\n", err);
altcp_abort(state->client_pcb);
err = ERR_ABRT;
}
state->client_pcb = NULL;
}
if (state->server_pcb) {
altcp_arg(state->server_pcb, NULL);
altcp_close(state->server_pcb);
state->server_pcb = NULL;
}
DEBUG_print("altcp server closed\n");
return err;
}
static err_t altcp_server_sent(void *arg, struct altcp_pcb *tpcb, u16_t len) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
return ERR_OK;
}
static err_t altcp_server_send_data(void *arg, struct altcp_pcb *tpcb)
{
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
return ERR_OK;
}
err_t altcp_server_recv(void *arg, struct altcp_pcb *tpcb, struct pbuf *p, err_t err) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
//state->client_pcb = tpcb;
static altcp_queue_t temp_recv;
if (!p) {
DEBUG_print("client connect close---------\n");
return altcp_client_close(tpcb);
}
if (p->tot_len > 0) {
// Receive the buffer
uint16_t recv_len = pbuf_copy_partial(p, temp_recv.buffer, p->tot_len, 0);
temp_recv.buffer_len = recv_len;
//queue_add_blocking(&state->recv_queue, &temp_recv);
if (!queue_try_add(&state->recv_queue, &temp_recv)) {
DEBUG_print("\n=======\n\nbusy\n-------\n");
return ERR_INPROGRESS;
}
altcp_recved(tpcb, recv_len);
#ifdef __DEBUG__
DEBUG_print("---------recv---------\n");
for (int i =0; i < recv_len; i++) {
DEBUG_print("%02x ",temp_recv.buffer[i]);
}
DEBUG_print("\n-------------\n");
#endif
}
pbuf_free(p);
return ERR_OK;
}
static err_t altcp_server_poll(void *arg, struct altcp_pcb *tpcb) {
return ERR_OK;
}
static void altcp_server_err(void *arg, err_t err) {
if (err != ERR_ABRT) {
DEBUG_print("tcp_client_err_fn %d\n", err);
}
}
static err_t altcp_server_accept(void *arg, struct altcp_pcb *client_pcb, err_t err) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
if (err != ERR_OK || client_pcb == NULL) {
DEBUG_print("Failure in accept\n");
return err;
}
//only allow one client(modbus master) to connect
if (state->connected) {
DEBUG_print("More than one connection!\n");
return ERR_CONN;
}
state->client_pcb = client_pcb;
altcp_arg(state->client_pcb, state);
altcp_sent(state->client_pcb, altcp_server_sent);
altcp_recv(state->client_pcb, altcp_server_recv);
altcp_poll(state->client_pcb, altcp_server_poll, POLL_TIME_S * 2);
altcp_err(state->client_pcb, altcp_server_err);
state->connected = true;
strcpy(state->client_ip_addr, ip4addr_ntoa(altcp_get_ip(client_pcb, false)));
DEBUG_print("Client connected:%d:%s\n", ++conn_count, state->client_ip_addr);
return ERR_OK;
}
static bool altcp_server_open(void *arg) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
DEBUG_print("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), MODBUS_ALTCP_PORT);
// set TLS connection
tls_config = altcp_tls_create_config_server_privkey_cert(MODBUS_SERVER_KEY, sizeof(MODBUS_SERVER_KEY), NULL, 0, MODBUS_SERVER_CERT, sizeof(MODBUS_SERVER_CERT));
//mbedtls_x509_crt_parse(tls_config->cert, CA_CERT, sizeof(CA_CERT));
struct altcp_pcb *alpcb = altcp_tls_new(tls_config, IPADDR_TYPE_ANY);
if (!alpcb) {
DEBUG_print("failed to create pcb\n");
return false;
}
err_t err = altcp_bind(alpcb, NULL, MODBUS_ALTCP_PORT);
if (err) {
DEBUG_print("failed to bind to port %u\n", MODBUS_ALTCP_PORT);
return false;
}
state->server_pcb = altcp_listen_with_backlog(alpcb,1);
if (!state->server_pcb) {
DEBUG_print("failed to listen\n");
if (alpcb) {
altcp_close(alpcb);
}
return false;
}
altcp_arg(state->server_pcb, state);
altcp_accept(state->server_pcb, altcp_server_accept);
return true;
}
bool picow_tls_server_init(void) {
/* modbus TCP server init*/
altcp_tcp_server = calloc(1, sizeof(ALTCP_SERVER_T));
if (!altcp_tcp_server) {
return false;
}
//altcp_tcp_server initial values;
queue_init(&altcp_tcp_server->recv_queue, sizeof(altcp_queue_t), 4);
altcp_tcp_server->connected = false;
altcp_tcp_server->queue_left=0;
altcp_tcp_server->recv_count=0;
altcp_tcp_server->start_index=0;
if (!altcp_server_open(altcp_tcp_server)) {
DEBUG_print("tcp server open error\n");
return false;
}
return true;
}
  • picow_tls_server.h
#define MODBUS_TCP_PORT 502 //MODBUS default port
#define MODBUS_ALTCP_PORT 802 //MODBUS default port
#define POLL_TIME_S 1
#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG_print(...) printf(__VA_ARGS__)
#else
#define DEBUG_print(...) (void) (0)
#endif
#define SSID "your-SSID"
#define PWD "your-PASSWD"
#define LED_PIN 15
#define MOTOR_PIN1 21
#define MOTOR_PIN2 20
#define MOTOR_PIN3 19
#define MOTOR_PIN4 18
#include "pico/util/queue.h"
#include "lwip/altcp.h"
typedef struct __altcp_queue_t{
uint8_t buffer[260];
uint16_t buffer_len;
} altcp_queue_t;
typedef struct ALTCP_SERVER_T_ {
struct altcp_pcb *server_pcb;
struct altcp_pcb *client_pcb;
unsigned char client_ip_addr[20];
bool connected;
queue_t recv_queue;
uint8_t server_host_name[126];
uint16_t recv_count;
uint16_t start_index;
uint16_t queue_left;
altcp_queue_t temp_recv;
} ALTCP_SERVER_T;
bool picow_tls_server_init(void);
  • modbus_tcp_server.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "modbus_tcp_server.h"
#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "picow_tls_server/picow_tls_server.h"
#include "GPIO_StepperMotor/steppermotor.h"
nmbs_t nmbs_tls;
#define COILS_ADDR_MAX 100
#define REGS_ADDR_MAX 32
#define FILE_SIZE_MAX 32
// A single nmbs_bitfield variable can keep 2000 coils
bool terminate = false;
nmbs_bitfield server_coils = {0};
uint16_t server_registers[REGS_ADDR_MAX] = {0};
uint16_t server_file[FILE_SIZE_MAX];
#define UNUSED_PARAM(x) ((x) = (x))
void sighandler(int s) {
UNUSED_PARAM(s);
terminate = true;
}
nmbs_error handle_altcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
ALTCP_SERVER_T * mb = (ALTCP_SERVER_T*) arg;
if (address + quantity > COILS_ADDR_MAX + 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
for (int i = 0; i < quantity; i++) {
nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils_out, i));
}
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (address + quantity > COILS_ADDR_MAX + 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
for (int i = 0; i < quantity; i++) {
nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils_out, i));
}
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
//if (address > COILS_ADDR_MAX )
// return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
if (address > 2 )
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
// Write coils values to our server_coils
nmbs_bitfield_write(server_coils, address, value);
if (address == 1) {
gpio_put(LED_PIN, value);
}
if (address == 0) {
stepperMotor_rotate_angle(90, value);
}
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (address + quantity > COILS_ADDR_MAX + 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
// Write coils values to our server_coils
for (int i = 0; i < quantity; i++) {
nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils, i));
}
return NMBS_ERROR_NONE;
}
nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (address + quantity > REGS_ADDR_MAX + 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
for (int i = 0; i < quantity; i++)
server_registers[address + i] = registers_out[i];
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (address > REGS_ADDR_MAX)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
// Write registers values to our server_registers
server_registers[address] = value;
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (address + quantity > REGS_ADDR_MAX + 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
// Write registers values to our server_registers
for (int i = 0; i < quantity; i++)
server_registers[address + i] = registers[i];
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (file_number != 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
if ((record_number + count) > FILE_SIZE_MAX)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
memcpy(registers, server_file + record_number, count * sizeof(uint16_t));
return NMBS_ERROR_NONE;
}
nmbs_error handle_altcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
uint16_t count, uint8_t unit_id, void* arg) {
UNUSED_PARAM(arg);
UNUSED_PARAM(unit_id);
if (file_number != 1)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
if ((record_number + count) > FILE_SIZE_MAX)
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
memcpy(server_file + record_number, registers, count * sizeof(uint16_t));
return NMBS_ERROR_NONE;
}
int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_SERVER_T *state=(ALTCP_SERVER_T*) arg;
uint16_t loop_count = 0;
if (state->queue_left == 0) {
while (queue_is_empty(&state->recv_queue) && loop_count < 20) {
loop_count++;
busy_wait_ms(50);
}
if (!queue_try_remove(&(state->recv_queue), &state->temp_recv)) {
return 0;
} else {
DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
}
state->queue_left = state->temp_recv.buffer_len;
}
state->recv_count = state->queue_left <= count ? state->queue_left : count;
state->start_index = state->temp_recv.buffer_len-state->queue_left;
for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];
state->queue_left = state->queue_left-state->recv_count;
//Debug
#ifdef __DEBUG__
DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
for (int i=0; i < state->recv_count; i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n==\n");
#endif
return state->recv_count;
}
int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_SERVER_T *state = (ALTCP_SERVER_T*)arg;
// Debug
#ifdef __DEBUG__
DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
for (int i=0; i < count;i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n====\n");
#endif
err_t err = altcp_write(state->client_pcb, buf, count, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
//tcp_abort(state->tcp_pcb);
DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->client_pcb));
return 0;
}
err = altcp_output(state->client_pcb);
if (err != ERR_OK)
{
DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err);
return 0;
}
return count;
}
  • modbus_tcp_server.h
#ifndef __MODBUS_GATEWAY_TCP_SERVER__
#define __MODBUS_GATEWAY_TCP_SERVER__
#include "nanomodbus/nanomodbus.h"
extern nmbs_t nmbs_tls;
nmbs_error handle_altcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id, void* arg);
nmbs_error handler_tcp_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
uint8_t unit_id, void* arg);
nmbs_error handle_altcp_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
uint8_t unit_id, void* arg);
nmbs_error handle_altcp_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
uint16_t count, uint8_t unit_id, void* arg);
int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,void* arg);
#endif

client:

  • CMakeLists.txt
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.0)
set(toolchainVersion 13_3_Rel1)
set(picotoolVersion 2.1.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico_w CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(picow_modbus_tcp_security_client C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(picow_modbus_tcp_security_client
picow_modbus_tcp_security_client.c
picow_altcp_client/picow_altcp_client.c
nanomodbus/nanomodbus.c)
pico_set_program_name(picow_modbus_tcp_security_client "picow_modbus_tcp_security_client")
pico_set_program_version(picow_modbus_tcp_security_client "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(picow_modbus_tcp_security_client 1)
pico_enable_stdio_usb(picow_modbus_tcp_security_client 0)
# Add the standard library to the build
target_link_libraries(picow_modbus_tcp_security_client
pico_stdlib)
# Add the standard include files to the build
target_include_directories(picow_modbus_tcp_security_client PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Add any user requested libraries
target_link_libraries(picow_modbus_tcp_security_client
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_mbedtls
pico_mbedtls
pico_lwip_mdns
)
pico_add_extra_outputs(picow_modbus_tcp_security_client)
# Ignore warnings from lwip code
set_source_files_properties(
${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
PROPERTIES
COMPILE_OPTIONS "-Wno-unused-result"
)
  • picow_modbus_tcp_security_client.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/pbuf.h"
#include "picow_altcp_client/picow_altcp_client.h"
#include "nanomodbus/nanomodbus.h"
#include "modbus_server_cert.h"
#define MODBUS_SERVER_HOST_NAME "modbus_server.local"
#define GATE_BUTTON_PIN 16
#define LIGHT_BUTTON_PIN 17
nmbs_t nmbs;
ALTCP_CLIENT_T *modbus_client_addr1;
int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_CLIENT_T *state=(ALTCP_CLIENT_T*) arg;
uint16_t loop_count = 0;
if (state->queue_left == 0) {
while (queue_is_empty(&state->recv_queue) && loop_count < 20) {
loop_count++;
busy_wait_ms(50);
}
if (!queue_try_remove(&(state->recv_queue), &state->temp_recv)) {
return 0;
} else {
DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
}
state->queue_left = state->temp_recv.buffer_len;
}
state->recv_count = state->queue_left <= count ? state->queue_left : count;
state->start_index = state->temp_recv.buffer_len-state->queue_left;
for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];
state->queue_left = state->queue_left-state->recv_count;
//Debug
DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
for (int i=0; i < state->recv_count; i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n==\n");
return state->recv_count;
}
int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_CLIENT_T *state = (ALTCP_CLIENT_T*)arg;
// Debug
DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
for (int i=0; i < count;i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n====\n");
err_t err = altcp_write(state->tcp_pcb, buf, count, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
//tcp_abort(state->tcp_pcb);
DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->tcp_pcb));
return 0;
}
err = altcp_output(state->tcp_pcb);
if (err != ERR_OK)
{
DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err);
return 0;
}
return count;
}
void gpio_button_irq_cb(uint gpio, uint32_t event_mask){
static bool addr0_value=false, addr1_value=false;
if ((gpio == GATE_BUTTON_PIN || gpio == LIGHT_BUTTON_PIN)&&event_mask == GPIO_IRQ_EDGE_FALL)
{
gpio_acknowledge_irq(gpio, GPIO_IRQ_EDGE_FALL);
if (modbus_client_addr1->conn_state == TCP_CONNECTED) {
if (gpio == GATE_BUTTON_PIN) {
addr0_value = !addr0_value;
nmbs_write_single_coil(&nmbs,0, addr0_value);
}
if (gpio == LIGHT_BUTTON_PIN) {
addr1_value = !addr1_value;
nmbs_write_single_coil(&nmbs,1, addr1_value);
}
}
}
}
int main()
{
stdio_init_all();
gpio_init(GATE_BUTTON_PIN);
gpio_init(LIGHT_BUTTON_PIN);
gpio_set_irq_enabled_with_callback(GATE_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
gpio_set_irq_enabled_with_callback(LIGHT_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
// Initialise the Wi-Fi chip
if (cyw43_arch_init()) {
DEBUG_print("Wi-Fi init failed\n");
return -1;
}
if (!picow_sta_connect(WIFI_SSID, WIFI_PASSWD)) {
return 0;
}
if (!picow_altcp_client_init(&modbus_client_addr1, MODBUS_SERVER_HOST_NAME, MODBUS_ALTCP_PORT, MODBUS_SERVER_CERT)) return 0;
nmbs_platform_conf platform_conf;
nmbs_platform_conf_create(&platform_conf);
platform_conf.transport = NMBS_TRANSPORT_TCP;
platform_conf.read = nmbs_transport_read;
platform_conf.write = nmbs_transport_write;
// Create the modbus client
nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
if (err != NMBS_ERROR_NONE) {
fprintf(stderr, "Error creating modbus client\n");
if (!nmbs_error_is_exception(err))
return 1;
}
// Set only the response timeout. Byte timeout will be handled by the TCP connection
nmbs_set_read_timeout(&nmbs, 1000);
nmbs_set_platform_arg(&nmbs, modbus_client_addr1);
bool value = true;
uint count=0;
while (true) {
tight_loop_contents();
}
}
  • picow_altcp_client.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/pbuf.h"
#include "picow_altcp_client/picow_altcp_client.h"
#include "nanomodbus/nanomodbus.h"
#include "modbus_server_cert.h"
#define MODBUS_SERVER_HOST_NAME "modbus_server.local"
#define GATE_BUTTON_PIN 16
#define LIGHT_BUTTON_PIN 17
nmbs_t nmbs;
ALTCP_CLIENT_T *modbus_client_addr1;
int32_t nmbs_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_CLIENT_T *state=(ALTCP_CLIENT_T*) arg;
uint16_t loop_count = 0;
if (state->queue_left == 0) {
while (queue_is_empty(&state->recv_queue) && loop_count < 20) {
loop_count++;
busy_wait_ms(50);
}
if (!queue_try_remove(&(state->recv_queue), &state->temp_recv)) {
return 0;
} else {
DEBUG_print("\t\t\t\t---loop count:%d\n", loop_count);
}
state->queue_left = state->temp_recv.buffer_len;
}
state->recv_count = state->queue_left <= count ? state->queue_left : count;
state->start_index = state->temp_recv.buffer_len-state->queue_left;
for (int i=0; i < state->recv_count; i++) buf[i] = state->temp_recv.buffer[state->start_index+i];
state->queue_left = state->queue_left-state->recv_count;
//Debug
DEBUG_print("\n=====transport read:%d:%d\n", count, state->temp_recv.buffer_len);
for (int i=0; i < state->recv_count; i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n==\n");
return state->recv_count;
}
int32_t nmbs_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
ALTCP_CLIENT_T *state = (ALTCP_CLIENT_T*)arg;
// Debug
DEBUG_print("====\nnmbs_transport_write count:%d\n", count);
for (int i=0; i < count;i++) {
DEBUG_print("%02x ", buf[i]);
}
DEBUG_print("\n====\n");
err_t err = altcp_write(state->tcp_pcb, buf, count, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
//tcp_abort(state->tcp_pcb);
DEBUG_print("tcp_write error:%d:%d\n", err, altcp_sndbuf(state->tcp_pcb));
return 0;
}
err = altcp_output(state->tcp_pcb);
if (err != ERR_OK)
{
DEBUG_print("%s:tcp_output:%d\n", state->server_host_name, err);
return 0;
}
return count;
}
void gpio_button_irq_cb(uint gpio, uint32_t event_mask){
static bool addr0_value=false, addr1_value=false;
if ((gpio == GATE_BUTTON_PIN || gpio == LIGHT_BUTTON_PIN)&&event_mask == GPIO_IRQ_EDGE_FALL)
{
gpio_acknowledge_irq(gpio, GPIO_IRQ_EDGE_FALL);
if (modbus_client_addr1->conn_state == TCP_CONNECTED) {
if (gpio == GATE_BUTTON_PIN) {
addr0_value = !addr0_value;
nmbs_write_single_coil(&nmbs,0, addr0_value);
}
if (gpio == LIGHT_BUTTON_PIN) {
addr1_value = !addr1_value;
nmbs_write_single_coil(&nmbs,1, addr1_value);
}
}
}
}
int main()
{
stdio_init_all();
gpio_init(GATE_BUTTON_PIN);
gpio_init(LIGHT_BUTTON_PIN);
gpio_set_irq_enabled_with_callback(GATE_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
gpio_set_irq_enabled_with_callback(LIGHT_BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, gpio_button_irq_cb);
// Initialise the Wi-Fi chip
if (cyw43_arch_init()) {
DEBUG_print("Wi-Fi init failed\n");
return -1;
}
if (!picow_sta_connect(WIFI_SSID, WIFI_PASSWD)) {
return 0;
}
if (!picow_altcp_client_init(&modbus_client_addr1, MODBUS_SERVER_HOST_NAME, MODBUS_ALTCP_PORT, MODBUS_SERVER_CERT)) return 0;
nmbs_platform_conf platform_conf;
nmbs_platform_conf_create(&platform_conf);
platform_conf.transport = NMBS_TRANSPORT_TCP;
platform_conf.read = nmbs_transport_read;
platform_conf.write = nmbs_transport_write;
// Create the modbus client
nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
if (err != NMBS_ERROR_NONE) {
fprintf(stderr, "Error creating modbus client\n");
if (!nmbs_error_is_exception(err))
return 1;
}
// Set only the response timeout. Byte timeout will be handled by the TCP connection
nmbs_set_read_timeout(&nmbs, 1000);
nmbs_set_platform_arg(&nmbs, modbus_client_addr1);
bool value = true;
uint count=0;
while (true) {
tight_loop_contents();
}
}
  • picow_altcp_client.h
#ifndef __PICOW_ALTCP_CLIENT__
#define __PCIOW_ALTCP_CLIENT__
#include "pico/util/queue.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#define WIFI_SSID "your-SSID"
#define WIFI_PASSWD "your-PASSWD"
#define MODBUS_TCP_PORT 502
#define MODBUS_ALTCP_PORT 802
#define POLL_TIME_S 1
#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG_print(...) printf(__VA_ARGS__)
#else
#define DEBUG_print(...) (void) (0)
#endif
enum _TCP_STATE{
TCP_CONNECTING = 1,
TCP_CONNECTED = 2,
TCP_CLOSING = 3,
TCP_CLOSED = 4,
};
typedef struct __recv_queue_t{
uint8_t buffer[260];
uint16_t buffer_len;
} recv_queue_t;
typedef struct ALTCP_CLIENT_T_ {
struct altcp_pcb *tcp_pcb;
struct altcp_tls_config *tls_config;
ip_addr_t remote_addr;
uint remote_port;
uint8_t conn_state;
bool dns_found;
queue_t recv_queue;
uint8_t server_host_name[256];
uint16_t recv_count;
uint16_t start_index;
uint16_t queue_left;
recv_queue_t temp_recv;
} ALTCP_CLIENT_T;
bool picow_altcp_client_init(ALTCP_CLIENT_T* *modbus_altcp_client, const char* hostname, uint port, const u8_t *cert);
err_t picow_altcp_client_close(void *arg);
err_t picow_altcp_client_conn(void *arg);
err_t picow_altcp_client_disconn(void *arg);
bool picow_altcp_client_open(ALTCP_CLIENT_T* state, const u8_t *cert);
bool picow_sta_connect(uint8_t* ssid, uint8_t* pwd);
#endif

  • nanoMODBUS library:






沒有留言:

張貼留言