/* * Copyright (c) 2020 PHYTEC Messtechnik GmbH * Copyright (c) 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include LOG_MODULE_REGISTER(tcp_modbus, LOG_LEVEL_INF); #define MODBUS_TCP_PORT 502 static uint16_t holding_reg[8]; static uint8_t coils_state; static const struct gpio_dt_spec led_dev[] = { GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios), GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios), GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios), }; static int custom_read_count; static bool custom_handler(const int iface, const struct modbus_adu *rx_adu, struct modbus_adu *tx_adu, uint8_t *const excep_code, void *const user_data) { const uint8_t request_len = 2; const uint8_t response_len = 6; int *read_counter = (int *)user_data; uint8_t subfunc; uint8_t data_len; LOG_INF("Custom Modbus handler called"); if (rx_adu->length != request_len) { LOG_WRN("Custom request length doesn't match"); *excep_code = MODBUS_EXC_ILLEGAL_DATA_VAL; return true; } subfunc = rx_adu->data[0]; data_len = rx_adu->data[1]; LOG_INF("Custom function called with subfunc=%u, data_len=%u", subfunc, data_len); (*read_counter)++; sys_put_be16(0x5555, tx_adu->data); sys_put_be16(0xAAAA, &tx_adu->data[2]); sys_put_be16(*read_counter, &tx_adu->data[4]); tx_adu->length = response_len; return true; } MODBUS_CUSTOM_FC_DEFINE(custom, custom_handler, 101, &custom_read_count); static int init_leds(void) { int err; for (int i = 0; i < ARRAY_SIZE(led_dev); i++) { if (!gpio_is_ready_dt(&led_dev[i])) { LOG_ERR("LED%u GPIO device not ready", i); return -ENODEV; } err = gpio_pin_configure_dt(&led_dev[i], GPIO_OUTPUT_INACTIVE); if (err != 0) { LOG_ERR("Failed to configure LED%u pin", i); return err; } } return 0; } static int coil_rd(uint16_t addr, bool *state) { if (addr >= ARRAY_SIZE(led_dev)) { return -ENOTSUP; } if (coils_state & BIT(addr)) { *state = true; } else { *state = false; } LOG_INF("Coil read, addr %u, %d", addr, (int)*state); return 0; } static int coil_wr(uint16_t addr, bool state) { bool on; if (addr >= ARRAY_SIZE(led_dev)) { return -ENOTSUP; } if (state == true) { coils_state |= BIT(addr); on = true; } else { coils_state &= ~BIT(addr); on = false; } gpio_pin_set(led_dev[addr].port, led_dev[addr].pin, (int)on); LOG_INF("Coil write, addr %u, %d", addr, (int)state); return 0; } static int holding_reg_rd(uint16_t addr, uint16_t *reg) { if (addr >= ARRAY_SIZE(holding_reg)) { return -ENOTSUP; } *reg = holding_reg[addr]; LOG_INF("Holding register read, addr %u", addr); return 0; } static int holding_reg_wr(uint16_t addr, uint16_t reg) { if (addr >= ARRAY_SIZE(holding_reg)) { return -ENOTSUP; } holding_reg[addr] = reg; LOG_INF("Holding register write, addr %u", addr); return 0; } static struct modbus_user_callbacks mbs_cbs = { .coil_rd = coil_rd, .coil_wr = coil_wr, .holding_reg_rd = holding_reg_rd, .holding_reg_wr = holding_reg_wr, }; static struct modbus_adu tmp_adu; K_SEM_DEFINE(received, 0, 1); static int server_iface; static int server_raw_cb(const int iface, const struct modbus_adu *adu, void *user_data) { LOG_DBG("Server raw callback from interface %d", iface); tmp_adu.trans_id = adu->trans_id; tmp_adu.proto_id = adu->proto_id; tmp_adu.length = adu->length; tmp_adu.unit_id = adu->unit_id; tmp_adu.fc = adu->fc; memcpy(tmp_adu.data, adu->data, MIN(adu->length, CONFIG_MODBUS_BUFFER_SIZE)); LOG_HEXDUMP_DBG(tmp_adu.data, tmp_adu.length, "resp"); k_sem_give(&received); return 0; } const static struct modbus_iface_param server_param = { .mode = MODBUS_MODE_RAW, .server = { .user_cb = &mbs_cbs, .unit_id = 1, }, .rawcb.raw_tx_cb = server_raw_cb, .rawcb.user_data = NULL }; static int init_modbus_server(void) { char iface_name[] = "RAW_0"; int err; server_iface = modbus_iface_get_by_name(iface_name); if (server_iface < 0) { LOG_ERR("Failed to get iface index for %s", iface_name); return -ENODEV; } err = modbus_init_server(server_iface, server_param); if (err < 0) { return err; } return modbus_register_user_fc(server_iface, &modbus_cfg_custom); } static int modbus_tcp_reply(int client, struct modbus_adu *adu) { uint8_t header[MODBUS_MBAP_AND_FC_LENGTH]; modbus_raw_put_header(adu, header); if (send(client, header, sizeof(header), 0) < 0) { return -errno; } if (send(client, adu->data, adu->length, 0) < 0) { return -errno; } return 0; } static int modbus_tcp_connection(int client) { uint8_t header[MODBUS_MBAP_AND_FC_LENGTH]; int rc; int data_len; rc = recv(client, header, sizeof(header), MSG_WAITALL); if (rc <= 0) { return rc == 0 ? -ENOTCONN : -errno; } LOG_HEXDUMP_DBG(header, sizeof(header), "h:>"); modbus_raw_get_header(&tmp_adu, header); data_len = tmp_adu.length; rc = recv(client, tmp_adu.data, data_len, MSG_WAITALL); if (rc <= 0) { return rc == 0 ? -ENOTCONN : -errno; } LOG_HEXDUMP_DBG(tmp_adu.data, tmp_adu.length, "d:>"); if (modbus_raw_submit_rx(server_iface, &tmp_adu)) { LOG_ERR("Failed to submit raw ADU"); return -EIO; } if (k_sem_take(&received, K_MSEC(1000)) != 0) { LOG_ERR("MODBUS RAW wait time expired"); modbus_raw_set_server_failure(&tmp_adu); } return modbus_tcp_reply(client, &tmp_adu); } int main(void) { int serv; struct sockaddr_in bind_addr; static int counter; if (init_modbus_server()) { LOG_ERR("Modbus TCP server initialization failed"); return 0; } if (init_leds()) { LOG_ERR("Modbus TCP server initialization failed"); return 0; } serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serv < 0) { LOG_ERR("error: socket: %d", errno); return 0; } bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind_addr.sin_port = htons(MODBUS_TCP_PORT); if (bind(serv, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { LOG_ERR("error: bind: %d", errno); return 0; } if (listen(serv, 5) < 0) { LOG_ERR("error: listen: %d", errno); return 0; } LOG_INF("Started MODBUS TCP server example on port %d", MODBUS_TCP_PORT); while (1) { struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); char addr_str[INET_ADDRSTRLEN]; int client; int rc; client = accept(serv, (struct sockaddr *)&client_addr, &client_addr_len); if (client < 0) { LOG_ERR("error: accept: %d", errno); continue; } inet_ntop(client_addr.sin_family, &client_addr.sin_addr, addr_str, sizeof(addr_str)); LOG_INF("Connection #%d from %s", counter++, addr_str); do { rc = modbus_tcp_connection(client); } while (!rc); close(client); LOG_INF("Connection from %s closed, errno %d", addr_str, rc); } return 0; }