prettyprint

2025年2月2日 星期日

[Raspberry Pi Pico W] MODBUS EP 3: Modbus TCP/RTU Gateway || Node-RED Modbus

 本文章介紹如何使用 nanoMODBUS 函式庫在 Raspberry Pi Pico W上實作 MODBUS TCP/RTU Gateway。使用 MODBUS TCP Master透過 Node-RED UI 控制兩個 Modbus RTU Slaves。

架構如下圖所示:


MODBUS TCP/RTU Gateway收到modbus tcp master(client) request 根據UintID轉送到相對應的MODUBS RTU Slave(msater),Response依相反順序進行。


對應的code如下:

成果展示:


程式碼:


  • 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_gateway 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_gateway picow_modbus_gateway.c 
                                    nanomodbus/nanomodbus.c
                                    picow_tcp_server/picow_tcp_server.c
                                    modbus_rtu_master/modbus_rtu_master.c
                                    modbus_tcp_client/modbus_tcp_client.c)

pico_set_program_name(picow_modbus_gateway "picow_modbus_gateway")
pico_set_program_version(picow_modbus_gateway "0.1")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(picow_modbus_gateway 0)
pico_enable_stdio_usb(picow_modbus_gateway 1)

# Add the standard library to the build
target_link_libraries(picow_modbus_gateway
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_modbus_gateway PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
)

# Add any user requested libraries
target_link_libraries(picow_modbus_gateway 
        pico_cyw43_arch_lwip_threadsafe_background
        pico_lwip_mdns
        )

pico_add_extra_outputs(picow_modbus_gateway)


  

  • picow_modbus_gateway.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mdns.h"
#include "picow_tcp_server/picow_tcp_server.h"
#include "nanomodbus/nanomodbus.h"
#include "lwip/pbuf.h"
#include "lwip/tcp.h"

#include "modbus_tcp_client/modbus_tcp_client.h"
#include "modbus_rtu_master/modbus_rtu_master.h"


/*
    MODBUS GATEWAY
    modbus tcp client <--> modbus tcp server
                                ^
                                |
                                v
                            modbus rtu client  <--> modbus rtu server
*/

int main()
{
    stdio_init_all();

    uart_setup(UART_ID);

//========== modbus rtu master(client) init ==========
    // nmbs_uart_read() and nmbs_uart_write() are implemented by the user 
    nmbs_platform_conf rtu_platform_conf;
    nmbs_platform_conf_create(&rtu_platform_conf);
    rtu_platform_conf.transport = NMBS_TRANSPORT_RTU;
    rtu_platform_conf.read = nmbs_uart_read;
    rtu_platform_conf.write = nmbs_uart_write;
 
    nmbs_error err = nmbs_client_create(&nmbs_rtu_master, &rtu_platform_conf);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "Error creating modbus client\n");
        return 1;
    }
    // RTU: set read timeout and byte timeout
    nmbs_set_read_timeout(&nmbs_rtu_master, 2000);
    nmbs_set_byte_timeout(&nmbs_rtu_master, 2000);
// ============ modbus rtu master(client) init =============

// ====== 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();

    printf("Connecting to Wi-Fi...\n");
    if (cyw43_arch_wifi_connect_timeout_ms(SSID, PWD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
        printf("failed to connect.\n");
        return 1;
    } else {
        printf("Connected.\n");
        // Read the ip address in a human readable way
        uint8_t *ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr);
        printf("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[] = "picow_mb_gateway";
    if (mdns_resp_add_netif(netif_default, host_name)== ERR_OK)
    {
        printf("mDNS add successfully\n");
    } else {
        printf("mDNS failure\n");
    }
#endif
    picow_tcp_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 MB_SERVER_T *modbus_tcp_server;
    platform_conf.arg = modbus_tcp_server;
    
    nmbs_callbacks callbacks;
    nmbs_callbacks_create(&callbacks);
    callbacks.read_coils = handle_tcp_read_coils;
    callbacks.read_discrete_inputs = handle_tcp_read_discrete_inputs;
    callbacks.write_single_coil = handle_tcp_write_single_coil;
    callbacks.write_multiple_coils = handle_tcp_write_multiple_coils;
    callbacks.read_holding_registers = handler_tcp_read_holding_registers;
    callbacks.write_multiple_registers = handle_tcp_write_multiple_registers;
    callbacks.write_single_register = handle_tcp_write_single_register;
    callbacks.read_file_record = handle_tcp_read_file_record;
    callbacks.write_file_record = handle_tcp_write_file_record;
    callbacks.arg = modbus_tcp_server;
    
    err = nmbs_server_create(&nmbs_gateway_tcp, 0, &platform_conf, &callbacks);
    if (err != NMBS_ERROR_NONE) {
        fprintf(stderr, "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_gateway_tcp, 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_gateway_tcp);
        
#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_tcp_server:

  • picow_tcp_server.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"

#include "nanomodbus/nanomodbus.h"

#include "lwip/pbuf.h"
#include "lwip/tcp.h"

#include "picow_tcp_server.h"
#include "modbus_tcp_client/modbus_tcp_client.h"

MB_SERVER_T *modbus_tcp_server;


static err_t tcp_client_close(struct tcp_pcb *client_pcb) {
    err_t err = ERR_OK;
    if (client_pcb != NULL) {
        tcp_arg(client_pcb, NULL);
        tcp_poll(client_pcb, NULL, 0);
        tcp_sent(client_pcb, NULL);
        tcp_recv(client_pcb, NULL);
        tcp_err(client_pcb, NULL);
        err = tcp_close(client_pcb);
        if (err != ERR_OK) {
            DEBUG_printf("close failed %d, calling abort\n", err);
            tcp_abort(client_pcb);
            err = ERR_ABRT;
        }
        client_pcb = NULL;
    }

    return err;
}

static err_t tcp_server_close(void *arg) {
    MB_SERVER_T *state = (MB_SERVER_T*)arg;
    err_t err = ERR_OK;

    if (state->client_pcb != NULL) {
        tcp_arg(state->client_pcb, NULL);
        tcp_poll(state->client_pcb, NULL, 0);
        tcp_sent(state->client_pcb, NULL);
        tcp_recv(state->client_pcb, NULL);
        tcp_err(state->client_pcb, NULL);
        err = tcp_close(state->client_pcb);
        if (err != ERR_OK) {
            DEBUG_printf("close failed %d, calling abort\n", err);
            tcp_abort(state->client_pcb);
            err = ERR_ABRT;
        }
        state->client_pcb = NULL;
    }
    if (state->server_pcb) {
        tcp_arg(state->server_pcb, NULL);
        tcp_close(state->server_pcb);
        state->server_pcb = NULL;
    }
    return err;
}

static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) {
    MB_SERVER_T *state = (MB_SERVER_T*)arg;
    return ERR_OK;
}

err_t tcp_server_send_data(void *arg, struct tcp_pcb *tpcb)
{
    MB_SERVER_T *state = (MB_SERVER_T*)arg;

    return ERR_OK;
}

err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
    MB_SERVER_T *state = (MB_SERVER_T*)arg;
    state->client_pcb = tpcb;
    static modbus_queue_t temp_recv;
    if (!p) {
        tcp_client_close(tpcb);
        tcp_abort(tpcb);
        return ERR_ABRT;
    }

    if (p->tot_len > 0) {
        //DEBUG_printf("tcp_client_recv %d err %d\n", p->tot_len, err);

        // 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)) {
        printf("\n=======\n\nbusy\n-------\n");
        return ERR_INPROGRESS;
    }

        tcp_recved(tpcb, recv_len);
//Debug
        printf("---------recv---------\n");
        for (int i =0; i < recv_len; i++) {
            printf("%02x ",temp_recv.buffer[i]);
        }
        printf("\n-------------\n");
        
    }
    pbuf_free(p);

    nmbs_server_poll(&nmbs_gateway_tcp); // process modbus request

    return ERR_OK;


}

static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) {

    //nmbs_error err = nmbs_server_poll(&nmbs);   // process modbus request while tpc idle
    //if (err != NMBS_ERROR_NONE) {
    //    printf("Error on modbus connection - %s\n", nmbs_strerror(err));
        // In a more complete example, we would handle this error by checking its nmbs_error value
    //}
       
    return ERR_OK;
}

static void tcp_server_err(void *arg, err_t err) {
    if (err != ERR_ABRT) {
        DEBUG_printf("tcp_client_err_fn %d\n", err);
    }
}

static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) {
    MB_SERVER_T *state = (MB_SERVER_T*)arg;
    if (err != ERR_OK || client_pcb == NULL) {
        DEBUG_printf("Failure in accept\n");
        return err;
    }
    DEBUG_printf("Client connected\n");

    state->client_pcb = client_pcb;
    
    tcp_arg(state->client_pcb, state);
    tcp_sent(state->client_pcb, tcp_server_sent);
    tcp_recv(state->client_pcb, tcp_server_recv);
    tcp_poll(state->client_pcb, tcp_server_poll, POLL_TIME_S * 2);
    tcp_err(state->client_pcb, tcp_server_err);

    return ERR_OK;
}

static bool tcp_server_open(void *arg) {
    MB_SERVER_T *state = (MB_SERVER_T*)arg;
    DEBUG_printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), MODBUS_TCP_PORT);

    struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
    if (!pcb) {
        DEBUG_printf("failed to create pcb\n");
        return false;
    }

    err_t err = tcp_bind(pcb, NULL, MODBUS_TCP_PORT);
    //err_t err = tcp_bind(pcb, netif_ip4_addr(netif_list), MODBUS_TCP_PORT);
    if (err) {
        DEBUG_printf("failed to bind to port %u\n", MODBUS_TCP_PORT);
        return false;
    }

    state->server_pcb = tcp_listen_with_backlog(pcb, 1);
    if (!state->server_pcb) {
        DEBUG_printf("failed to listen\n");
        if (pcb) {
            tcp_close(pcb);
        }
        return false;
    }

    tcp_arg(state->server_pcb, state);
    tcp_accept(state->server_pcb, tcp_server_accept);

    return true;
}

void picow_tcp_server_init(void) {
    /* modbus TCP server  init*/
    modbus_tcp_server = calloc(1, sizeof(MB_SERVER_T));
    if (!modbus_tcp_server) {
        return;
    }

    queue_init(&modbus_tcp_server->recv_queue, sizeof(modbus_queue_t), 4);

    if (!tcp_server_open(modbus_tcp_server)) {
        DEBUG_printf("tcp server open error\n");
        return;
    }

    return;

}


  

  • picow_tcp_server.h


#define MODBUS_TCP_PORT 502            //MODBUS default port
#define DEBUG_printf printf
#define POLL_TIME_S 1


#define SSID "your-SSID"
#define PWD "your-PASSWORD"

#include "pico/util/queue.h"

typedef struct __modbus_queue_t{
    uint8_t buffer[260];
    uint16_t buffer_len;
} modbus_queue_t;

typedef struct TCP_CLIENT_T_ {
    struct tcp_pcb *server_pcb;
    struct tcp_pcb *client_pcb;
    bool connected;
    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;
    modbus_queue_t temp_recv;
} MB_SERVER_T;

void picow_tcp_server_init(void);
  

modbus_tcp_client:

  • modbus_tcp_client.c

#include "stdio.h"
#include "pico/stdlib.h"
#include "modbus_tcp_client.h"


#include "lwip/pbuf.h"
#include "lwip/tcp.h"
#include "picow_tcp_server/picow_tcp_server.h"
#include "modbus_rtu_master/modbus_rtu_master.h"

//#define _DEBUG
#ifdef _DEBUG
#define DEBUG_PRINT(...) printf(__VA_ARGS__)
#else
#define DEBUG_PRINT(...) (void) (0)
#endif

nmbs_t nmbs_gateway_tcp;

// A single nmbs_bitfield variable can keep 2000 coils
bool terminate = false;
nmbs_bitfield server_coils[MAX_CLIENT_DEVICES] = {0};
uint16_t server_registers[MAX_CLIENT_DEVICES][REGS_ADDR_MAX] = {0};
uint16_t server_file[MAX_CLIENT_DEVICES][FILE_SIZE_MAX];


#define UNUSED_PARAM(x) ((x) = (x))

void sighandler(int s) {
    UNUSED_PARAM(s);
    terminate = true;
}

nmbs_error handle_tcp_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);
    MB_SERVER_T * mb = (MB_SERVER_T*) arg;


    if (address + quantity > COILS_ADDR_MAX + 1)
        return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_read_coils(&nmbs_rtu_master, address, quantity, coils_out); 
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(read coils): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }    
     
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils[unit_id], address + i, nmbs_bitfield_read(coils_out, i));
    }
  
    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_read_discrete_inputs(&nmbs_rtu_master, address, quantity, coils_out);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(read discrete coils): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }
     
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils[unit_id], address + i, nmbs_bitfield_read(coils_out, i));
    }

    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_write_single_coil(&nmbs_rtu_master, address, value);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(write single coil): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }

    // Write coils values to our server_coils
    nmbs_bitfield_write(server_coils[unit_id], address, value);

    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_write_multiple_coils(&nmbs_rtu_master, address, quantity, coils);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(write mulitple coils): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        uart_reset(UART_ID);
        return err;
    }

    // Write coils values to our server_coils
    for (int i = 0; i < quantity; i++) {
        nmbs_bitfield_write(server_coils[unit_id], 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;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_read_holding_registers(&nmbs_rtu_master, address, quantity, registers_out);
    // Read our registers values into registers_out
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(read holding regiseters): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }

    for (int i = 0; i < quantity; i++)
        server_registers[unit_id][address + i] = registers_out[i];

    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;
    
    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_write_single_register(&nmbs_rtu_master, address, value);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(write single register): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }
    // Write registers values to our server_registers

        server_registers[unit_id][address] = value;

    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;
    
    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_write_multiple_registers(&nmbs_rtu_master, address, quantity, registers);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info(write multiple registers): unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }

    // Write registers values to our server_registers
    for (int i = 0; i < quantity; i++)
        server_registers[unit_id][address + i] = registers[i];

    return NMBS_ERROR_NONE;
}

nmbs_error handle_tcp_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;
    
    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_read_file_record(&nmbs_rtu_master, file_number, record_number, registers, count);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info: unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }

    memcpy(registers, server_file[unit_id] + record_number, count * sizeof(uint16_t));

    return NMBS_ERROR_NONE;
}


nmbs_error handle_tcp_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;

    nmbs_set_destination_rtu_address(&nmbs_rtu_master, unit_id);
    nmbs_error err = nmbs_write_file_record(&nmbs_rtu_master, file_number, record_number, registers, count);
    if (err != NMBS_ERROR_NONE) {
        DEBUG_printf("Error info: unit_id:%d, %s\n", unit_id, 
                    nmbs_strerror(err));
        return err;
    }

    memcpy(server_file[unit_id] + 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) {
    MB_SERVER_T *state=(MB_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) {
    MB_SERVER_T *state = (MB_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 = tcp_write(state->client_pcb, buf, count, 0 /*TCP_WRITE_FLAG_COPY*/);
    if (err != ERR_OK) 
    {
        //tcp_abort(state->tcp_pcb);
        DEBUG_PRINT("tcp_write error:%d:%d\n", err, tcp_sndbuf(state->client_pcb)); 
        return 0;
    }

    err = tcp_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_client.h

#ifndef __MODBUS_GATEWAY_TCP_CLIENT__
#define __MODBUS_GATEWAY_TCP_CLIENT__

#include "nanomodbus/nanomodbus.h"

extern nmbs_t nmbs_gateway_tcp;

nmbs_error handle_tcp_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_tcp_read_discrete_inputs(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
nmbs_error handle_tcp_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg);
nmbs_error handle_tcp_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_tcp_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg);
nmbs_error handle_tcp_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
                                           uint8_t unit_id, void* arg);
nmbs_error handle_tcp_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_tcp_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 
  

modbus_rtu_master:

  • modbus_rtu_master.c

#include "stdio.h"
#include "pico/stdlib.h"
#include "modbus_rtu_master.h"

nmbs_t nmbs_rtu_master;

int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg){
    uint64_t start_time = time_us_64();
    int32_t bytes_read = 0;
    uint64_t timeout_us = (uint64_t) byte_timeout_ms * 1000;
    while (time_us_64() - start_time < timeout_us && bytes_read < count) {
        if (uart_is_readable(UART_ID)) {
            buf[bytes_read++] = uart_getc(UART_ID);
            start_time = time_us_64();    // Reset start time after a successful read
        }
    }
    
    return bytes_read;    
}

int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
    uart_write_blocking(UART_ID, buf, count);

    return count;
}

void uart_reset(uart_inst_t* uart) {
    uart_deinit(uart);
    uart_setup(uart);
}

void uart_setup(uart_inst_t* uart) {
    // Set up our UART
    uart_init(uart, BAUD_RATE);

    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

    int __unused actual = uart_set_baudrate(UART_ID, BAUD_RATE);
    uart_set_hw_flow(UART_ID, false, false);
    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
    uart_set_fifo_enabled(UART_ID, false);
}
  

  • modbus_rtu_master.h

#ifndef __MODBUS_GATEWAY_RTU_MASTER__
#define __MODBUS_GATEWAY_RTU_MASTER__

#include "nanomodbus/nanomodbus.h"

// UART defines
#define UART_ID uart1
#define UART_TX_PIN 4
#define UART_RX_PIN 5

#define BAUD_RATE       115200
#define DATA_BITS       8
#define STOP_BITS       1
#define PARITY          UART_PARITY_EVEN


// The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
#define COILS_ADDR_MAX      100
#define REGS_ADDR_MAX       32
#define FILE_SIZE_MAX       32
#define MAX_CLIENT_DEVICES  64

extern nmbs_t nmbs_rtu_master;


int32_t nmbs_uart_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
int32_t nmbs_uart_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
void uart_reset(uart_inst_t* uart);
void uart_setup(uart_inst_t* uart);
#endif
  


nanoMODBUS:

 原始library起參閱https://github.com/debevv/nanoMODBUS

在本文章專案中作以下修改部份程式碼:


Node-RED Modbus client:
  • flow:


  • flow code:
[
    {
        "id": "48fad0c0f57cea35",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "67287decfe7536cf",
        "type": "ui_colour_picker",
        "z": "48fad0c0f57cea35",
        "name": "",
        "label": "Color of LED",
        "group": "fc04be05304743a6",
        "format": "rgb",
        "outformat": "object",
        "showSwatch": true,
        "showPicker": false,
        "showValue": false,
        "showHue": true,
        "showAlpha": false,
        "showLightness": false,
        "square": "true",
        "dynOutput": "false",
        "order": 2,
        "width": 0,
        "height": 0,
        "passthru": false,
        "topic": "color",
        "topicType": "str",
        "className": "",
        "x": 510,
        "y": 40,
        "wires": [
            [
                "e50739a98c836bc2"
            ]
        ]
    },
    {
        "id": "730e5141782e4353",
        "type": "ui_slider",
        "z": "48fad0c0f57cea35",
        "name": "",
        "label": "num 0f Leds",
        "tooltip": "",
        "group": "fc04be05304743a6",
        "order": 3,
        "width": 0,
        "height": 0,
        "passthru": false,
        "outs": "all",
        "topic": "leds",
        "topicType": "str",
        "min": 0,
        "max": 10,
        "step": 1,
        "className": "",
        "x": 530,
        "y": 140,
        "wires": [
            [
                "945a43328cae473b"
            ]
        ]
    },
    {
        "id": "850f63e35b7c0dd6",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "Modbus Uint 2",
        "func": "var regs = [4];\nregs[0] = flow.get('color_red');\nregs[1] = flow.get('color_green');\nregs[2] = flow.get('color_blue');\nregs[3] = flow.get('num_leds');\nmsg.payload = {value: regs, 'fc': 16, 'unitid': 2, 'address': 0, 'quantity': 4 };\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 80,
        "wires": [
            [
                "0b897cabd78f207b"
            ]
        ]
    },
    {
        "id": "e50739a98c836bc2",
        "type": "change",
        "z": "48fad0c0f57cea35",
        "name": "set flow color",
        "rules": [
            {
                "t": "set",
                "p": "color_red",
                "pt": "flow",
                "to": "payload.r",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "color_green",
                "pt": "flow",
                "to": "payload.g",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "color_blue",
                "pt": "flow",
                "to": "payload.b",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 710,
        "y": 40,
        "wires": [
            [
                "850f63e35b7c0dd6"
            ]
        ]
    },
    {
        "id": "945a43328cae473b",
        "type": "change",
        "z": "48fad0c0f57cea35",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "num_leds",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 710,
        "y": 140,
        "wires": [
            [
                "850f63e35b7c0dd6"
            ]
        ]
    },
    {
        "id": "0b897cabd78f207b",
        "type": "modbus-flex-write",
        "z": "48fad0c0f57cea35",
        "name": "",
        "showStatusActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "server": "54922a4357ba259d",
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 1090,
        "y": 260,
        "wires": [
            [
                "24a8a58f01fcf5cc",
                "00a3c1cedc235bc8"
            ],
            []
        ]
    },
    {
        "id": "24a8a58f01fcf5cc",
        "type": "modbus-response",
        "z": "48fad0c0f57cea35",
        "name": "",
        "registerShowMax": 20,
        "x": 1130,
        "y": 160,
        "wires": []
    },
    {
        "id": "40e24a450a247c58",
        "type": "ui_button",
        "z": "48fad0c0f57cea35",
        "name": "Gate",
        "group": "fc04be05304743a6",
        "order": 8,
        "width": 2,
        "height": 1,
        "passthru": false,
        "label": "{{lbl}}",
        "tooltip": "",
        "color": "",
        "bgcolor": "{{background}}",
        "className": "",
        "icon": "{{myicon}}",
        "payload": "u1_coil0",
        "payloadType": "flow",
        "topic": "addr0",
        "topicType": "str",
        "x": 570,
        "y": 240,
        "wires": [
            [
                "c24f886f7213b046"
            ]
        ]
    },
    {
        "id": "5654c6c9dbd8c887",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "Modbus Uint 1",
        "func": "if (msg.address == 0)\n    msg.payload = {value: msg.payload, 'fc': 15, 'unitid': 1, 'address': msg.address, 'quantity': msg.quantity };\nif (msg.address == 1)\n    msg.payload = {value: msg.payload, 'fc': 5, 'unitid': 1, 'address': msg.address, 'quantity': msg.quantity };\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 320,
        "wires": [
            [
                "0b897cabd78f207b"
            ]
        ]
    },
    {
        "id": "c24f886f7213b046",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "Uint 1",
        "func": "var newmsg={};\n\nif (msg.payload == true) {\n    if (msg.topic == 'addr0') {\n        newmsg.lbl = 'CLOSE';\n        newmsg.background=\"magenta\";\n    }\n    else {\n        newmsg.lbl = \"OFF\";\n        newmsg.background=\"red\";\n    }\n    newmsg.myicon = 'fa-toggle-off';\n\n    \n} else { \n    if (msg.topic == 'addr0') {\n        newmsg.lbl='OPEN';\n        newmsg.background='lightblue';\n    }\n    else {\n        newmsg.lbl = 'ON';\n        newmsg.background='blue';\n    }\n    newmsg.myicon = 'fa-toggle-on';\n    \n}\nnewmsg.topic = msg.topic;\nlet address =2;\nif (newmsg.topic == 'addr0') {\n    address =0;\n    //newmsg.payload={};\n    flow.set('u1_coil0', !msg.payload);\n    flow.set('u1_coil1', !msg.payload);\n    newmsg.payload = [msg.payload, msg.payload];\n    newmsg.quantity = 2;\n}\nif (newmsg.topic == 'addr1') {\n    address =1;\n    flow.set('u1_coil1', !msg.payload);\n    newmsg.payload=msg.payload;\n    newmsg.quantity = 1;\n}\nnewmsg.address=address;\n\nreturn newmsg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 320,
        "wires": [
            [
                "5654c6c9dbd8c887",
                "b090ab4f179755b5"
            ]
        ]
    },
    {
        "id": "3009c8f5180b8b48",
        "type": "ui_button",
        "z": "48fad0c0f57cea35",
        "name": "Light",
        "group": "fc04be05304743a6",
        "order": 12,
        "width": 2,
        "height": 1,
        "passthru": false,
        "label": "{{lbl}}",
        "tooltip": "",
        "color": "",
        "bgcolor": "{{background}}",
        "className": "",
        "icon": "{{myicon}}",
        "payload": "u1_coil1",
        "payloadType": "flow",
        "topic": "addr1",
        "topicType": "str",
        "x": 570,
        "y": 420,
        "wires": [
            [
                "c24f886f7213b046"
            ]
        ]
    },
    {
        "id": "b090ab4f179755b5",
        "type": "switch",
        "z": "48fad0c0f57cea35",
        "name": "toggle",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "addr0",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "addr1",
                "vt": "str"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 430,
        "y": 360,
        "wires": [
            [
                "deb6e9852981cbe7"
            ],
            [
                "ea4d168a958d4731"
            ]
        ]
    },
    {
        "id": "7fcff809d9501142",
        "type": "modbus-getter",
        "z": "48fad0c0f57cea35",
        "name": "Uint1 FC:1",
        "showStatusActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "logIOActivities": false,
        "unitid": "1",
        "dataType": "Coil",
        "adr": "0",
        "quantity": "2",
        "server": "54922a4357ba259d",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 170,
        "y": 340,
        "wires": [
            [
                "78865a9a77ef7a00",
                "cf34a46dd051bf77"
            ],
            []
        ]
    },
    {
        "id": "78865a9a77ef7a00",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "get coil 0 init value",
        "func": "var newmsg={};\nflow.set('u1_coil0',!msg.payload[0]);\n\nnewmsg.payload=msg.payload[0];\n\nif (msg.payload[0] == false) {\n    newmsg.lbl = \"OPEN\";\n    newmsg.myicon='fa-toggle-on';\n    newmsg.background='lightblue';\n} else {\n    newmsg.lbl = 'CLOSE';\n    newmsg.myicon='fa-toggle-off';\n    newmsg.background='magenta';\n}\nreturn newmsg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 290,
        "y": 240,
        "wires": [
            [
                "deb6e9852981cbe7"
            ]
        ]
    },
    {
        "id": "cf34a46dd051bf77",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "get coil 1 init value",
        "func": "var newmsg={};\nflow.set('u1_coil1',!msg.payload[1]);\n\nnewmsg.payload=msg.payload[1];\n\nif (msg.payload[1] == false) {\n    newmsg.lbl = \"ON\";\n    newmsg.background='blue';\n    newmsg.myicon='fa-toggle-on';\n} else {\n    newmsg.lbl = 'OFF';\n    newmsg.myicon='fa-toggle-off';\n    newmsg.background='red';\n}\n//newmsg.label = newmsg.label_v;\nreturn newmsg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 290,
        "y": 460,
        "wires": [
            [
                "ea4d168a958d4731"
            ]
        ]
    },
    {
        "id": "714e1f0e2afa0885",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "color",
        "func": "var newmsg={};\nnewmsg.payload = {r:msg.payload[0],g:msg.payload[1],b:msg.payload[2], a:1};\nreturn newmsg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 330,
        "y": 40,
        "wires": [
            [
                "67287decfe7536cf"
            ]
        ]
    },
    {
        "id": "c1fc304462222840",
        "type": "function",
        "z": "48fad0c0f57cea35",
        "name": "num_of_leds",
        "func": "var newmsg = {};\nnewmsg.payload = msg.payload[3];\nreturn newmsg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 140,
        "wires": [
            [
                "730e5141782e4353"
            ]
        ]
    },
    {
        "id": "22c0fbed37498038",
        "type": "ui_text",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "order": 1,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Unit 1",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": true,
        "font": "Times New Roman,Times,serif",
        "fontSize": "22",
        "color": "#0000ff",
        "x": 890,
        "y": 280,
        "wires": []
    },
    {
        "id": "b3dc9565e22052fe",
        "type": "ui_text",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "order": 5,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Unit 2",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": true,
        "font": "Times New Roman,Times,serif",
        "fontSize": "22",
        "color": "#0000ff",
        "x": 890,
        "y": 40,
        "wires": []
    },
    {
        "id": "40896a542df60d8a",
        "type": "ui_text",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "order": 6,
        "width": 1,
        "height": 2,
        "name": "",
        "label": "Gate",
        "format": "{{msg.payload}}",
        "layout": "col-center",
        "className": "",
        "style": true,
        "font": "",
        "fontSize": "15",
        "color": "#8f0a0a",
        "x": 710,
        "y": 240,
        "wires": []
    },
    {
        "id": "4828163558cac23f",
        "type": "ui_text",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "order": 10,
        "width": 1,
        "height": 2,
        "name": "",
        "label": "Light:",
        "format": "{{msg.payload}}",
        "layout": "col-center",
        "className": "",
        "style": true,
        "font": "Verdana,Verdana,Geneva,sans-serif",
        "fontSize": "15",
        "color": "#133305",
        "x": 710,
        "y": 420,
        "wires": []
    },
    {
        "id": "7710bdc96bee5a64",
        "type": "link in",
        "z": "48fad0c0f57cea35",
        "name": "Unit 1:Read Coil 1",
        "links": [
            "336bf1d73e3c16f0"
        ],
        "x": 35,
        "y": 340,
        "wires": [
            [
                "7fcff809d9501142"
            ]
        ]
    },
    {
        "id": "336bf1d73e3c16f0",
        "type": "link out",
        "z": "48fad0c0f57cea35",
        "name": "Unit 1: Read Coil 1",
        "mode": "link",
        "links": [
            "7710bdc96bee5a64"
        ],
        "x": 1235,
        "y": 300,
        "wires": []
    },
    {
        "id": "fe478f2f1bf74258",
        "type": "ui_ui_control",
        "z": "48fad0c0f57cea35",
        "name": "",
        "events": "all",
        "x": 80,
        "y": 200,
        "wires": [
            [
                "4855c1d9cb3c5de3",
                "7fcff809d9501142"
            ]
        ]
    },
    {
        "id": "4855c1d9cb3c5de3",
        "type": "modbus-getter",
        "z": "48fad0c0f57cea35",
        "name": "Unit2 FC:3",
        "showStatusActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "logIOActivities": false,
        "unitid": "2",
        "dataType": "HoldingRegister",
        "adr": "0",
        "quantity": "4",
        "server": "54922a4357ba259d",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 170,
        "y": 100,
        "wires": [
            [
                "714e1f0e2afa0885",
                "c1fc304462222840"
            ],
            []
        ]
    },
    {
        "id": "ea4d168a958d4731",
        "type": "ui_template",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "name": "Light Status",
        "order": 11,
        "width": 2,
        "height": 2,
        "format": "<script>\n(function(scope) {\n    scope.$watch('msg', function(msg) {\n        if (msg) {\n            if (msg.payload) {\n                scope.light_icon=\"light_on.png\";\n            } else {\n                scope.light_icon=\"light_off.png\";\n            }\n        }\n\n\n    });\n\n})(scope);\n</script>\n\n\n<div>\n    <img src = {{light_icon}} width=60 height=60>\n    \n</div>\n",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 510,
        "y": 480,
        "wires": [
            [
                "3009c8f5180b8b48"
            ]
        ]
    },
    {
        "id": "deb6e9852981cbe7",
        "type": "ui_template",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "name": "Gate Status",
        "order": 7,
        "width": 2,
        "height": 2,
        "format": "<script>\n(function(scope) {\n    scope.$watch('msg', function(msg) {\n        if (msg) {\n            if (msg.payload) {\n                scope.light_icon=\"gate_open.png\";\n            } else {\n                scope.light_icon=\"gate_close.png\";\n            }\n        }\n\n\n    });\n\n})(scope);\n</script>\n\n\n<div>\n    <img src = {{light_icon}} width=60 height=60>\n    \n</div>\n",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 490,
        "y": 300,
        "wires": [
            [
                "40e24a450a247c58"
            ]
        ]
    },
    {
        "id": "1f2e434da8912dc8",
        "type": "ui_template",
        "z": "48fad0c0f57cea35",
        "group": "fc04be05304743a6",
        "name": "seprate line",
        "order": 4,
        "width": 0,
        "height": 0,
        "format": "<div>\n    <img src='line.png' width=250>\n</div>",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 630,
        "y": 180,
        "wires": [
            []
        ]
    },
    {
        "id": "d11918fa96d8a971",
        "type": "link in",
        "z": "48fad0c0f57cea35",
        "name": "Unit 2: read registers",
        "links": [
            "41175f479b349001"
        ],
        "x": 45,
        "y": 100,
        "wires": [
            [
                "4855c1d9cb3c5de3"
            ]
        ]
    },
    {
        "id": "41175f479b349001",
        "type": "link out",
        "z": "48fad0c0f57cea35",
        "name": "Unit 2: read registers",
        "mode": "link",
        "links": [
            "d11918fa96d8a971"
        ],
        "x": 1235,
        "y": 340,
        "wires": []
    },
    {
        "id": "00a3c1cedc235bc8",
        "type": "switch",
        "z": "48fad0c0f57cea35",
        "name": "link out",
        "property": "payload.unitid",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "1",
                "vt": "num"
            },
            {
                "t": "eq",
                "v": "2",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 1120,
        "y": 320,
        "wires": [
            [
                "336bf1d73e3c16f0"
            ],
            [
                "41175f479b349001"
            ]
        ]
    },
    {
        "id": "60796979c4948a75",
        "type": "ui_spacer",
        "z": "48fad0c0f57cea35",
        "name": "spacer",
        "group": "fc04be05304743a6",
        "order": 9,
        "width": 2,
        "height": 1
    },
    {
        "id": "136b2d0e7ac84aae",
        "type": "ui_spacer",
        "z": "48fad0c0f57cea35",
        "name": "spacer",
        "group": "fc04be05304743a6",
        "order": 13,
        "width": 2,
        "height": 1
    },
    {
        "id": "fc04be05304743a6",
        "type": "ui_group",
        "name": "Gateway",
        "tab": "cb1cfb3cbe2a4244",
        "order": 1,
        "disp": true,
        "width": 5,
        "collapse": false,
        "className": ""
    },
    {
        "id": "54922a4357ba259d",
        "type": "modbus-client",
        "name": "picow_mb_gateway",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "failureLogEnabled": true,
        "tcpHost": "picow_mb_gateway.local",
        "tcpPort": 502,
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB",
        "serialType": "RTU-BUFFERD",
        "serialBaudrate": 9600,
        "serialDatabits": 8,
        "serialStopbits": 1,
        "serialParity": "none",
        "serialConnectionDelay": 100,
        "serialAsciiResponseStartDelimiter": "0x3A",
        "unit_id": 1,
        "commandDelay": 1,
        "clientTimeout": 2000,
        "reconnectOnTimeout": false,
        "reconnectTimeout": 2000,
        "parallelUnitIdsAllowed": true,
        "showErrors": false,
        "showWarnings": true,
        "showLogs": true
    },
    {
        "id": "cb1cfb3cbe2a4244",
        "type": "ui_tab",
        "name": "MODBUS",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]
  


沒有留言:

張貼留言