diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index aae4de7..493bffd 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -31,6 +31,7 @@ jobs: # Install the ArduinoModbus library from the local path - source-path: ./ - name: ArduinoRS485 + - name: Arduino_10BASE_T1S UNIVERSAL_SKETCH_PATHS: | - examples/RTU @@ -42,24 +43,38 @@ jobs: - fqbn: arduino:megaavr:uno2018:mode=off ethernet: true nina: true + spe: false artifact-name-suffix: arduino-megaavr-uno2018 - fqbn: arduino:samd:mkrwifi1010 ethernet: true nina: true + spe: false artifact-name-suffix: arduino-samd-mkrwifi1010 - fqbn: arduino:mbed_nano:nano33ble ethernet: false nina: false + spe: false artifact-name-suffix: arduino-mbed_nano-nano33ble - fqbn: arduino:mbed_portenta:envie_m7 ethernet: false nina: false + spe: false artifact-name-suffix: arduino-mbed_portenta-envie_m7 - fqbn: arduino:mbed_opta:opta ethernet: true nina: false + spe: false artifact-name-suffix: arduino-mbed_opta-opta - + - fqbn: arduino:renesas_uno:unor4wifi + ethernet: false + nina: false + spe: true + artifact-name-suffix: arduino-renesas_uno-unor4wifi + - fqbn: arduino:renesas_uno:minima + ethernet: false + nina: false + spe: true + artifact-name-suffix: arduino-renesas_uno-minima # Make board type-specific customizations to the matrix jobs include: - board: @@ -90,6 +105,21 @@ jobs: nina: false nina-libraries: "" nina-sketch-paths: "" + - board: + # Boards with T1S shield + spe: true + # Install these libraries in addition to the ones defined by env.UNIVERSAL_LIBRARIES + spe-libraries: "" + # Compile these sketches in addition to the ones defined by env.UNIVERSAL_SKETCH_PATHS + nina-sketch-paths: | + - examples/T1S + - board: + # Boards with T1S shield + spe: false + # Install these libraries in addition to the ones defined by env.UNIVERSAL_LIBRARIES + spe-libraries: "" + # Compile these sketches in addition to the ones defined by env.UNIVERSAL_SKETCH_PATHS + nina-sketch-paths: "" steps: - name: Checkout diff --git a/examples/RTU/ModbusRTUClientMD02TemperatureHumiditySensor/ModbusRTUClientMD02TemperatureHumiditySensor.ino b/examples/RTU/ModbusRTUClientMD02TemperatureHumiditySensor/ModbusRTUClientMD02TemperatureHumiditySensor.ino index 2f113be..8a66eb3 100644 --- a/examples/RTU/ModbusRTUClientMD02TemperatureHumiditySensor/ModbusRTUClientMD02TemperatureHumiditySensor.ino +++ b/examples/RTU/ModbusRTUClientMD02TemperatureHumiditySensor/ModbusRTUClientMD02TemperatureHumiditySensor.ino @@ -56,7 +56,7 @@ void loop() if (ModbusRTUClient.available()) { int16_t const temperature_raw = ModbusRTUClient.read(); - float const temperature_deg = temperature_raw / 100.f; + float const temperature_deg = temperature_raw / 10.f; Serial.println(temperature_deg); } @@ -68,7 +68,7 @@ void loop() if (ModbusRTUClient.available()) { int16_t const humidity_raw = ModbusRTUClient.read(); - float const humidity_per_cent = humidity_raw / 100.f; + float const humidity_per_cent = humidity_raw / 10.f; Serial.println(humidity_per_cent); } diff --git a/examples/T1S/ModbusT1SClient/ModbusT1SClient.ino b/examples/T1S/ModbusT1SClient/ModbusT1SClient.ino new file mode 100644 index 0000000..8f91f0a --- /dev/null +++ b/examples/T1S/ModbusT1SClient/ModbusT1SClient.ino @@ -0,0 +1,77 @@ +/* + Modbus T1S Client + + This sketch demonstrates how to send commands to a Modbus T1S server connected + via T1S Single Pair Ethernet. + + Circuit: + - T1S shield + - Uno WiFi R4 +*/ + +#include +#include + +Arduino_10BASE_T1S_UDP udp_client; +static uint8_t const T1S_PLCA_NODE_ID = 2; +static uint16_t const UDP_SERVER_PORT = 8889; +static uint16_t const UDP_CLIENT_PORT = 8888; +#define MODBUS_ID 42 + +void setup() { + Serial.begin(115200); + + ModbusT1SClient.setT1SClient(udp_client); + ModbusT1SClient.setT1SPort(UDP_CLIENT_PORT); + ModbusT1SClient.setServerPort(UDP_SERVER_PORT); + ModbusT1SClient.setModbusId(MODBUS_ID); + ModbusT1SClient.setCallback(OnPlcaStatus); + + if (!ModbusT1SClient.begin(T1S_PLCA_NODE_ID)) { + Serial.println("Failed to start Modbus T1S Client!"); + while (1); + } +} + +void loop() { + ModbusT1SClient.update(); + + int res = ModbusT1SClient.coilRead(0x00); + if (res == -1) { + Serial.println("Failed to read coil! "); + } else { + Serial.print("Coil value: "); + Serial.println(res); + } + + res = ModbusT1SClient.coilWrite(0, 0x01); + if (res == -1) { + Serial.println("Failed to write coil! "); + } else { + Serial.println("write done"); + } + + res = ModbusT1SClient.inputRegisterRead(0x00); + if (res == -1) { + Serial.println("Failed to read Input Register! "); + } else { + Serial.print("Input Register value: "); + Serial.println(res); + } +} + +static void OnPlcaStatus(bool success, bool plcaStatus) +{ + if (!success) + { + Serial.println("PLCA status register read failed"); + return; + } + + if (plcaStatus) { + Serial.println("PLCA Mode active"); + } else { + Serial.println("CSMA/CD fallback"); + tc6_inst->enablePlca(); + } +} \ No newline at end of file diff --git a/examples/T1S/ModbusT1SClientTemperatureHumiditySensor/ModbusT1SClientTemperatureHumiditySensor.ino b/examples/T1S/ModbusT1SClientTemperatureHumiditySensor/ModbusT1SClientTemperatureHumiditySensor.ino new file mode 100644 index 0000000..bfafef8 --- /dev/null +++ b/examples/T1S/ModbusT1SClientTemperatureHumiditySensor/ModbusT1SClientTemperatureHumiditySensor.ino @@ -0,0 +1,79 @@ +/* + Modbus T1S Client Temperature Humidity sensor + + This sketch creates a Modbus T1S Client and demonstrates + how to use the ModbusT1S API to communicate. + - Arduino Uno Wifi R4 + - T1S shield + - SPE ethernet connected to the client + - all the terminations placed on the hardware +*/ + +#include +#include + +static uint8_t const T1S_PLCA_NODE_ID = 2; +static uint16_t const UDP_SERVER_PORT = 8889; +static uint16_t const UDP_CLIENT_PORT = 8888; + +Arduino_10BASE_T1S_UDP udp_client; + +void setup() { + Serial.begin(115200); + + ModbusT1SClient.setT1SClient(udp_client); + ModbusT1SClient.setT1SPort(UDP_CLIENT_PORT); + ModbusT1SClient.setServerPort(UDP_SERVER_PORT); + ModbusT1SClient.setCallback(OnPlcaStatus); + + if (!ModbusT1SClient.begin(T1S_PLCA_NODE_ID)) { + Serial.println("Failed to start Modbus T1S Client!"); + while (1); + } + ModbusT1SClient.disablePOE(); +} + +unsigned long start = 0; +void loop() { + ModbusT1SClient.update(); + + if ((millis() - start) > 1000) + { + int res = ModbusT1SClient.inputRegisterRead(1, 0x01); + if (res == -1) { + Serial.println("Failed to read temperature! "); + } else { + int16_t const temperature_raw = res; + float const temperature_deg = temperature_raw / 10.f; + Serial.print("Temperature: "); + Serial.println(temperature_deg); + } + + res = ModbusT1SClient.inputRegisterRead(1, 0x02); + if (res == -1) { + Serial.println("Failed to read humidity! "); + } else { + int16_t const humidity_raw = res; + float const humidity_per_cent = humidity_raw / 10.f; + Serial.print("Humidity: "); + Serial.println(humidity_per_cent); + } + start = millis(); + } +} + +static void OnPlcaStatus(bool success, bool plcaStatus) +{ + if (!success) + { + Serial.println("PLCA status register read failed"); + return; + } + + if (plcaStatus) { + Serial.println("PLCA Mode active"); + } else { + Serial.println("CSMA/CD fallback"); + tc6_inst->enablePlca(); + } +} diff --git a/examples/T1S/ModbusT1SServer/ModbusT1SServer.ino b/examples/T1S/ModbusT1SServer/ModbusT1SServer.ino new file mode 100644 index 0000000..4bd5445 --- /dev/null +++ b/examples/T1S/ModbusT1SServer/ModbusT1SServer.ino @@ -0,0 +1,55 @@ +/* + Modbus T1S Server + + + This sketch demonstrates how to receive commands from a Modbus T1S Client connected + via T1S Single Pair Ethernet. + + Circuit: + - T1S shield + - Uno WiFi R4 +*/ + +#include +#include + +RS485Class serial485(RS485_SERIAL, RS485_TX_PIN, RS485_DE_PIN, RS485_RE_PIN); + +static uint8_t const T1S_PLCA_NODE_ID = 0; +static uint16_t const UDP_SERVER_PORT = 8889; + +Arduino_10BASE_T1S_UDP udp_server; + +void setup() { + Serial.begin(115200); + + ModbusT1SServer.setT1SServer(udp_server); + ModbusT1SServer.setT1SPort(UDP_SERVER_PORT); + ModbusT1SServer.setCallback(OnPlcaStatus); + + if (!ModbusT1SServer.begin(T1S_PLCA_NODE_ID, 9600, SERIAL_8N1, serial485)) { + Serial.println("Failed to start Modbus T1S Server!"); + while (1); + } + ModbusT1SServer.disablePOE(); +} + +void loop() { + ModbusT1SServer.update(); +} + +static void OnPlcaStatus(bool success, bool plcaStatus) +{ + if (!success) + { + Serial.println("PLCA status register read failed"); + return; + } + + if (plcaStatus) { + Serial.println("PLCA Mode active"); + } else { + Serial.println("CSMA/CD fallback"); + tc6_inst->enablePlca(); + } +} diff --git a/keywords.txt b/keywords.txt index 845bdb3..0fecedd 100644 --- a/keywords.txt +++ b/keywords.txt @@ -11,6 +11,8 @@ ModbusRTUClient KEYWORD1 ModbusRTUServer KEYWORD1 ModbusRTUClient KEYWORD1 ModbusTCPServer KEYWORD1 +ModbusT1SClient KEYWORD1 +ModbusT1SServer KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -44,6 +46,21 @@ configureInputRegisters KEYWORD2 discreteInputWrite KEYWORD2 inputRegisterWrite KEYWORD2 +setServerIp KEYWORD2 +setServerPort KEYWORD2 +setModbusId KEYWORD2 +setT1SServer KEYWORD2 +setT1SClient KEYWORD2 +setGateway KEYWORD2 +setRxTimeout KEYWORD2 +setT1SPort KEYWORD2 +update KEYWORD2 +setCallback KEYWORD2 +enablePOE KEYWORD2 +disablePOE KEYWORD2 +checkStatus KEYWORD2 +parsePacket KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/library.properties b/library.properties index 84aace6..8da0553 100644 --- a/library.properties +++ b/library.properties @@ -3,9 +3,9 @@ version=1.0.9 author=Arduino maintainer=Arduino sentence=Use Modbus equipment with your Arduino. -paragraph=Using TCP or RS485 shields, like the MKR 485 Shield. This library depends on the ArduinoRS485 library. +paragraph=Using TCP, RS485 or T1S shields, like the MKR 485 Shield. This library depends on the ArduinoRS485 library. category=Communication url=https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus -architectures=megaavr,samd,mbed_nano,mbed_portenta,mbed_opta +architectures=megaavr,samd,mbed_nano,mbed_portenta,mbed_opta,renesas_uno includes=ArduinoModbus.h depends=ArduinoRS485 diff --git a/src/ArduinoModbus.h b/src/ArduinoModbus.h index 41f1615..7690ef4 100644 --- a/src/ArduinoModbus.h +++ b/src/ArduinoModbus.h @@ -26,4 +26,8 @@ #include "ModbusTCPClient.h" #include "ModbusTCPServer.h" +#ifndef __AVR__ +#include "ModbusT1SClient.h" +#include "ModbusT1SServer.h" +#endif #endif diff --git a/src/ModbusT1SClient.cpp b/src/ModbusT1SClient.cpp new file mode 100644 index 0000000..5ba7bd3 --- /dev/null +++ b/src/ModbusT1SClient.cpp @@ -0,0 +1,559 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +extern "C" { +#include "libmodbus/modbus.h" +#include "libmodbus/modbus-rtu.h" +} + +#include "ModbusT1SClient.h" +#ifndef __AVR__ +/** + * @class ModbusT1SClientClass + * Class for Modbus T1S Client communication. + * + * This class provides functionalities to communicate with a Modbus T1S server. + */ + +/** + * Default constructor for ModbusT1SClientClass. + * + * Initializes the Modbus client with a default timeout of 1000 milliseconds. + */ +ModbusT1SClientClass::ModbusT1SClientClass() : + ModbusClient(1000), + callback(default_OnPlcaStatus) +{ +} + +/** + * Constructor for ModbusT1SClientClass with RS485 support. + * + * Initializes the Modbus client with a default timeout of 1000 milliseconds and sets up RS485 communication. + * + * @param rs485 Reference to an RS485Class object for RS485 communication. + */ +ModbusT1SClientClass::ModbusT1SClientClass(RS485Class& rs485) : + ModbusClient(1000), + _rs485(&rs485) +{ +} + +/** + * Destructor for ModbusT1SClientClass. + * + * Cleans up any resources used by the ModbusT1SClientClass. + */ +ModbusT1SClientClass::~ModbusT1SClientClass() +{ +} + +int ModbusT1SClientClass::begin(int node_id) +{ + _node_id = node_id; + pinMode(IRQ_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(IRQ_PIN), + + []() { + tc6_io->onInterrupt(); + }, + FALLING); + + /* Initialize IO module. */ + if (!tc6_io->begin()) + { + return 0; + } + + if(_gateway == IPAddress(0, 0, 0, 0)) { + _gateway = IPAddress(192, 168, 42, 100); + } + + IPAddress ip_addr = IPAddress(_gateway[0], _gateway[1], _gateway[2], _gateway[3] + _node_id); + IPAddress network_mask = IPAddress(255, 255, 255, 0); + IPAddress gateway = IPAddress(_gateway[0], _gateway[1], _gateway[2], _gateway[3]); + IPAddress server_addr = IPAddress(_gateway[0], _gateway[1], _gateway[2], _gateway[3]); + + if(_server_ip == IPAddress(0, 0, 0, 0)) { + _server_ip = server_addr; + } + + + T1SPlcaSettings t1s_plca_settings(_node_id); + + T1SMacSettings const t1s_default_mac_settings; + MacAddress const mac_addr = MacAddress::create_from_uid(); + + if (!tc6_inst->begin(ip_addr + , network_mask + , gateway + , mac_addr + , t1s_plca_settings + , t1s_default_mac_settings)) + { + return 0; + } + + if (!_client->begin(udp_port)) + { + return 0; + } + return 1; +} + +/** + * Sets the IP address of the Modbus server. + * + * This function sets the IP address of the Modbus server that the client will communicate with. + * + * @param server_ip The IP address of the Modbus server. + */ +void ModbusT1SClientClass::setServerIp(IPAddress server_ip) +{ + _server_ip = server_ip; +} + +/** + * Sets the port number of the Modbus server. + * + * This function sets the port number of the Modbus server that the client will communicate with. + * + * @param server_port The port number of the Modbus server. + */ +void ModbusT1SClientClass::setServerPort(uint16_t server_port) +{ + _server_port = server_port; +} + +/** + * Sets the Modbus ID for the client. + * + * This function sets the Modbus ID for the client to use when communicating with the Modbus server. + * + * @param id The Modbus ID to use. + */ +void ModbusT1SClientClass::setModbusId(uint16_t id) +{ + _modbus_id = id; +} + +/** + * Sets the timeout for receiving a response from the Modbus server. + * + * This function sets the timeout for receiving a response from the Modbus server. + * + * @param timeout The timeout value in milliseconds. + */ +void ModbusT1SClientClass::setRxTimeout(unsigned long timeout) +{ + _rx_timeout = timeout; +} +/** + * Reads the status of a coil from the Modbus server. + * + * This function sends a request to read the status of a coil at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the coil to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int The status of the coil (1 for ON, 0 for OFF) or -1 if an error occurs. + */ +int ModbusT1SClientClass::coilRead(int address, Arduino_10BASE_T1S_UDP * client) +{ + return coilRead(_modbus_id, address, client); +} + +/** + * Reads the status of a coil from the Modbus server with a specified ID. + * + * This function sends a request to read the status of a coil at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the coil to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int The status of the coil (1 for ON, 0 for OFF) or -1 if an error occurs. + */ +int ModbusT1SClientClass::coilRead(int id, int address, Arduino_10BASE_T1S_UDP * client) +{ + return receive(id, address, client, UDP_READ_COIL_PORT); +} + +/** + * Writes a value to a coil on the Modbus server. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server using the provided UDP client and port. + * + * @param address The address of the coil to write to. + * @param value The value to write to the coil (1 for ON, 0 for OFF). + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +int ModbusT1SClientClass::coilWrite(int address, uint16_t value, Arduino_10BASE_T1S_UDP * client) +{ + return coilWrite(_modbus_id, address, value, client); +} + +/** + * Writes a value to a coil on the Modbus server with a specified ID. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the coil to write to. + * @param value The value to write to the coil (1 for ON, 0 for OFF). + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +int ModbusT1SClientClass::coilWrite(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client) +{ + return send(id, address, value, client, UDP_WRITE_COIL_PORT); +} + +/** + * Reads the status of a discrete input from the Modbus server. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the discrete input to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ +int ModbusT1SClientClass::discreteInputRead(int address, Arduino_10BASE_T1S_UDP * client) +{ + return discreteInputRead(_modbus_id, address, client); +} + +/** + * Reads the status of a discrete input from the Modbus server with a specified ID. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the discrete input to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ +int ModbusT1SClientClass::discreteInputRead(int id, int address, Arduino_10BASE_T1S_UDP * client) +{ + return receive(id, address, client, UDP_READ_DI_PORT); +} + +/** + * Reads the value of an input register from the Modbus server. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the input register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return long The value of the input register or -1 if an error occurs. + */ +long ModbusT1SClientClass::inputRegisterRead(int address, Arduino_10BASE_T1S_UDP * client) +{ + return inputRegisterRead(_modbus_id, address, client); +} + +/** + * Reads the value of an input register from the Modbus server with a specified ID. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the input register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return long The value of the input register or -1 if an error occurs. + */ +long ModbusT1SClientClass::inputRegisterRead(int id, int address, Arduino_10BASE_T1S_UDP * client) +{ + return receive(id, address, client, UDP_READ_IR_PORT); +} + +/** + * Reads a value from a holding register on a Modbus server. + * + * This function sends a request to read a single holding register on a Modbus server + * using the specified client and port. + * + * @param address The address of the holding register to read from. + * @param client A pointer to an Arduino_10BASE_T1S_UDP client used for communication. + * @return Returns the value of the holding register on success, or -1 if the client is null. + */ +long ModbusT1SClientClass::holdingRegisterRead(int address, Arduino_10BASE_T1S_UDP * client) +{ + return holdingRegisterRead(_modbus_id, address, client); +} + +/** + * Writes a value to a holding register on a Modbus server. + * + * This function sends a request to write a single holding register on a Modbus server + * using the specified client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the holding register to write to. + * @param value The value to write to the holding register. + * @param client A pointer to an Arduino_10BASE_T1S_UDP client used for communication. + * @return Returns 1 on success, or -1 if the client is null. + */ +long ModbusT1SClientClass::holdingRegisterRead(int id, int address, Arduino_10BASE_T1S_UDP * client) +{ + return receive(id, address, client, UDP_READ_HR_PORT); +} + + +/** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the holding register to write to. + * @param value The value to write to the holding register. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +int ModbusT1SClientClass::holdingRegisterWrite(int address, uint16_t value, Arduino_10BASE_T1S_UDP * client) +{ + return holdingRegisterWrite(_modbus_id, address, value, client); +} + +/** + * Writes a value to a holding register on a Modbus server. + * + * This function sends a request to write a single holding register on a Modbus server + * using the specified client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the holding register to write to. + * @param value The value to write to the holding register. + * @param client A pointer to an Arduino_10BASE_T1S_UDP client used for communication. + * @return Returns 1 on success, or -1 if the client is null. + */ +int ModbusT1SClientClass::holdingRegisterWrite(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client) +{ + return send(id, address, value, client, UDP_WRITE_HR_PORT); +} + +/** + * Checks if the received packet matches the specified port, id, and address. + * + * This function compares the received packet's port, id, and address with the provided + * values to determine if they match. + * + * @param port The port number to check against the received packet. + * @param id The id to check against the received packet. + * @param address The address to check against the received packet. + * @return true if the received packet matches the specified port, id, and address; false otherwise. + */ +bool ModbusT1SClientClass::checkPacket(int port, uint16_t id, uint16_t address) +{ + int port_rec = udp_rx_buf[0] << 8 | udp_rx_buf[1]; + uint16_t id_rcv = udp_rx_buf[2] << 8 | udp_rx_buf[3]; + uint16_t add_rcv = udp_rx_buf[4] << 8 | udp_rx_buf[5]; + if(port_rec == port && add_rcv == address && id_rcv == id) { + return true; + } + return false; +} + +/** + * Sets the T1S client for the Modbus communication. + * + * This function assigns a 10BASE-T1S UDP client to be used for Modbus communication. + * + * @param client A reference to an Arduino_10BASE_T1S_UDP object that represents the T1S client. + */ +void ModbusT1SClientClass::setT1SClient(Arduino_10BASE_T1S_UDP & client) +{ + _client = &client; +} + +/** + * Sets the T1S port for the Modbus communication. + * + * This function sets the port number to be used for the T1S communication. + * + * @param port The port number to use for T1S communication. + */ +void ModbusT1SClientClass::setT1SPort(int port) +{ + udp_port = port; +} + +/** + * Polls the Modbus client for incoming data. + * + * This function polls the Modbus client for incoming data. + */ +void ModbusT1SClientClass::update() +{ + tc6_inst->service(); + + static unsigned long prev_beacon_check = 0; + static unsigned long prev_udp_packet_sent = 0; + + auto const now = millis(); + + if ((now - prev_beacon_check) > 1000) + { + prev_beacon_check = now; + tc6_inst->getPlcaStatus(callback); + } +} + +/** + * Sets the callback function to be used by the ModbusT1SClient. + * + * This function sets the callback function to be used by the ModbusT1SClient. + * + * @param cb The callback function to use. + */ +void ModbusT1SClientClass::setCallback(callback_f cb) { + if(cb != nullptr) { + callback = cb; + } +} + +/** + * Default callback function for PLCA status check. + * + * This function is the default callback function for PLCA status check. + * + * @param success The success status of the PLCA status check. + * @param plcaStatus The PLCA status. + */ +static void default_OnPlcaStatus(bool success, bool plcaStatus) +{ + if (!success) + { + return; + } + + if (!plcaStatus) { + tc6_inst->enablePlca(); + } +} + +/** + * Sets the gateway IP address for the Modbus client. + * + * This function sets the gateway IP address for the Modbus client. + * + * @param gateway The gateway IP address. + */ +void ModbusT1SClientClass::setGateway(IPAddress gateway) { + _gateway = gateway; +} + +/** + * Enables Power Over Ethernet (POE). + * + * This function enables Power Over Ethernet (POE) on the T1S client. + */ +void ModbusT1SClientClass::enablePOE() { + tc6_inst->digitalWrite(TC6::DIO::A0, true); + tc6_inst->digitalWrite(TC6::DIO::A1, true); +} + +/** + * Disables Power Over Ethernet (POE). + * + * This function disables Power Over Ethernet (POE) on the T1S client. + */ +void ModbusT1SClientClass::disablePOE() { + tc6_inst->digitalWrite(TC6::DIO::A0, false); + tc6_inst->digitalWrite(TC6::DIO::A1, true); +} + +long ModbusT1SClientClass::receive(int id, int address, Arduino_10BASE_T1S_UDP * client, int functionCode) { + + long res = -1; + if(client == nullptr) { + client = _client; + } + + uint8_t tx_buf[8] = {(uint8_t)(functionCode & 0xFF00) >> 8, (uint8_t)(functionCode & 0x00FF), + (uint8_t)((id & 0xFF00) >> 8), (uint8_t)id, + (uint8_t)((address & 0xFF00) >> 8), (uint8_t)address}; + int tx_packet_size = sizeof(tx_buf); + write(tx_buf, tx_packet_size, client); + unsigned long start = millis(); + while(millis() - start < _rx_timeout) { + if(read(client)) { + if(checkPacket(functionCode, id, address)) { + switch (functionCode) { + case UDP_READ_IR_PORT: + case UDP_READ_HR_PORT: + res = int(udp_rx_buf[6] << 8 | udp_rx_buf[7]); + return res; + case UDP_READ_COIL_PORT: + case UDP_READ_DI_PORT: + res = int(udp_rx_buf[7]); + return res; + default: + break; + } + } + } + } + return res; +} + +int ModbusT1SClientClass::send(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client, int functionCode) +{ + if(client == nullptr) { + client = _client; + } + + uint8_t tx_buf[8] = {(uint8_t)(functionCode & 0xFF00) >> 8, (uint8_t)(functionCode & 0x00FF), (uint8_t)((id & 0xFF00) >> 8), (uint8_t)id, + (uint8_t)((address & 0xFF00) >> 8), (uint8_t)address, (value & 0xFF00) >> 8, (uint8_t)(value & 0x00FF)}; + + int tx_packet_size = sizeof(tx_buf); + write(tx_buf, tx_packet_size, client); + return 1; +} + + +void ModbusT1SClientClass::write(uint8_t * buf, int len, Arduino_10BASE_T1S_UDP * client) +{ + client->beginPacket(_server_ip, _server_port); + client->write((const uint8_t *)buf, len); + client->endPacket(); +} + +int ModbusT1SClientClass::read(Arduino_10BASE_T1S_UDP * client) +{ + int const rx_packet_size = client->parsePacket(); + if (rx_packet_size == 8) + { + int bytes_read = client->read(udp_rx_buf, 8); + return rx_packet_size == bytes_read; + } + return 0; +} +ModbusT1SClientClass ModbusT1SClient; +#endif diff --git a/src/ModbusT1SClient.h b/src/ModbusT1SClient.h new file mode 100644 index 0000000..04d96c1 --- /dev/null +++ b/src/ModbusT1SClient.h @@ -0,0 +1,343 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _MODBUS_T1S_CLIENT_H_INCLUDED +#define _MODBUS_T1S_CLIENT_H_INCLUDED +#ifndef __AVR__ +#include "ModbusClient.h" +#include +#include +#include "ModbusT1SCommon.h" +#include + +#define RX_TIMEOUT 1000 + +using callback_f = void (*)(bool, bool); +class ModbusT1SClientClass : public ModbusClient { +public: + /** + * Default constructor for ModbusT1SClientClass. + * + * Initializes the Modbus client with a default timeout of 1000 milliseconds. + */ + ModbusT1SClientClass(); + + /** + * Constructor for ModbusT1SClientClass with RS485 support. + * + * Initializes the Modbus client with a default timeout of 1000 milliseconds and sets up RS485 communication. + * + * @param rs485 Reference to an RS485Class object for RS485 communication. + */ + ModbusT1SClientClass(RS485Class& rs485); + + /** + * Destructor for ModbusT1SClientClass. + */ + virtual ~ModbusT1SClientClass(); + + /** + * Start the Modbus T1S client with the specified parameters + * + * @param baudrate Baud rate to use + * @param config serial config. to use defaults to SERIAL_8N1 + * + * Return 1 on success, 0 on failure + */ + int begin(int node_id); + + /** + * Sets the IP address of the Modbus server. + * + * This function sets the IP address of the Modbus server that the client will communicate with. + * + * @param server_ip The IP address of the Modbus server. Default is IPAddress(0, 0, 0, 0). + */ + void setServerIp(IPAddress server_ip = IPAddress(0, 0, 0, 0)); + + /** + * Sets the port number of the Modbus server. + * + * This function sets the port number of the Modbus server that the client will communicate with. + * + * @param server_port The port number of the Modbus server. Default is 8889. + */ + void setServerPort(uint16_t server_port = 8889); + + /** + * Sets the Modbus ID of the client. + * + * This function sets the Modbus ID that the client will use for communication. + * + * @param id The Modbus ID. + */ + void setModbusId(uint16_t id); + + /** + * Reads the status of a coil from the Modbus server. + * + * This function sends a request to read the status of a coil at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the coil to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int The status of the coil (1 for ON, 0 for OFF) or -1 if an error occurs. + */ + int coilRead(int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the status of a coil from the Modbus server with a specified ID. + * + * This function sends a request to read the status of a coil at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the coil to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int The status of the coil (1 for ON, 0 for OFF) or -1 if an error occurs. + */ + int coilRead(int id, int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Writes a value to a coil on the Modbus server. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server using the provided UDP client and port. + * + * @param address The address of the coil to write to. + * @param value The value to write to the coil (1 for ON, 0 for OFF). + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int Returns 1 if the write operation is successful, 0 otherwise. + */ + int coilWrite(int address, uint16_t value, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Writes a value to a coil on the Modbus server with a specified ID. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the coil to write to. + * @param value The value to write to the coil (1 for ON, 0 for OFF). + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int Returns 1 if the write operation is successful, 0 otherwise. + */ + int coilWrite(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the status of a discrete input from the Modbus server. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the discrete input to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ + int discreteInputRead(int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the status of a discrete input from the Modbus server with a specified ID. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the discrete input to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ + int discreteInputRead(int id, int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the value of an input register from the Modbus server. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the input register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return long The value of the input register or -1 if an error occurs. + */ + long inputRegisterRead(int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the value of an input register from the Modbus server with a specified ID. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the input register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return long The value of the input register or -1 if an error occurs. + */ + long inputRegisterRead(int id, int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client and port. + * + * @param address The address of the holding register to write to. + * @param value The value to write to the holding register. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int Returns 1 if the write operation is successful, 0 otherwise. + */ + int holdingRegisterWrite(int address, uint16_t value, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Writes a value to a holding register on the Modbus server with a specified ID. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the holding register to write to. + * @param value The value to write to the holding register. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return int Returns 1 if the write operation is successful, 0 otherwise. + */ + int holdingRegisterWrite(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the value of a holding register from the Modbus server. + * + * This function sends a request to read the value of a holding register at the specified address + * from the Modbus server using the provided UDP client and port. + * + * @param address The address of the holding register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return long The value of the holding register or -1 if an error occurs. + */ + long holdingRegisterRead(int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Reads the value of a holding register from the Modbus server with a specified ID. + * + * This function sends a request to read the value of a holding register at the specified address + * from the Modbus server with the given ID using the provided UDP client and port. + * + * @param id The ID of the Modbus server. + * @param address The address of the holding register to read. + * @param client A pointer to the Arduino_10BASE_T1S_UDP client used for communication. Default is nullptr. + * @return long The value of the holding register or -1 if an error occurs. + */ + long holdingRegisterRead(int id, int address, Arduino_10BASE_T1S_UDP * client = nullptr); + + /** + * Sets the T1S client for the Modbus communication. + * + * This function assigns a 10BASE-T1S UDP client to be used for Modbus communication. + * + * @param client A reference to an Arduino_10BASE_T1S_UDP object that represents the T1S client. + */ + void setT1SClient(Arduino_10BASE_T1S_UDP & client); + + /** + * Sets the gateway IP address for the Modbus client. + * + * This function sets the gateway IP address for the Modbus client. + * + * @param gateway The gateway IP address. + */ + + void setGateway(IPAddress gateway = IPAddress(0, 0, 0, 0)); + + /** + * Sets the timeout for receiving a response from the Modbus server. + * + * This function sets the timeout for receiving a response from the Modbus server. + * + * @param timeout The timeout value in milliseconds. + */ + void setRxTimeout(unsigned long timeout = RX_TIMEOUT); + + /** + * Sets the port to use for communication. + * + * This function sets the port to use for communication. + * + * @param port The port to use. + */ + void setT1SPort(int port = 8889); + + /** + * Polls the Modbus client for incoming data. + * + * This function polls the Modbus client for incoming data. + * + * @return int The number of bytes read from the client. + */ + void update(); + + /** + * Sets the callback function to be used by the ModbusT1SClient. + * + * This function allows you to specify a callback function that will be called + * when certain events occur in the ModbusT1SClient. If no callback function is + * provided, the default value of nullptr will be used, meaning no callback + * will be executed. + * + * @param cb The callback function to be set. The default value is nullptr. + */ + void setCallback(callback_f cb = nullptr); + + /** + * Enables Power Over Ethernet (POE) on the Modbus client. + * + * This function enables Power Over Ethernet (POE) on the Modbus client and sets the device as the power source. + */ + void enablePOE(); + + /** + * Disables Power Over Ethernet (POE) on the Modbus client. + * + * This function disables Power Over Ethernet (POE) on the Modbus client and set the USB as power source. + */ + void disablePOE(); + + +private: + long receive(int id, int address, Arduino_10BASE_T1S_UDP * client, int functionCode); + int send(int id, int address, uint16_t value, Arduino_10BASE_T1S_UDP * client, int functionCode); + void write(uint8_t * buf, int len, Arduino_10BASE_T1S_UDP * client); + int read(Arduino_10BASE_T1S_UDP * client); + bool checkPacket(int port, uint16_t id, uint16_t address); + +private: + IPAddress _gateway = IPAddress(0, 0, 0, 0); + unsigned long _rx_timeout = RX_TIMEOUT; + callback_f callback = nullptr; + IPAddress _server_ip = IPAddress(0, 0, 0, 0); + uint16_t _server_port = 8889; + uint16_t _modbus_id = 0; + uint8_t udp_rx_buf[8] = {0}; + + RS485Class* _rs485 = &RS485; + Arduino_10BASE_T1S_UDP * _client = nullptr; + int _node_id = 1; + int udp_port = 0; +}; + +extern ModbusT1SClientClass ModbusT1SClient; +#endif +#endif diff --git a/src/ModbusT1SCommon.cpp b/src/ModbusT1SCommon.cpp new file mode 100644 index 0000000..4aab607 --- /dev/null +++ b/src/ModbusT1SCommon.cpp @@ -0,0 +1,4 @@ +#include "ModbusT1SCommon.h" +#ifndef __AVR__ +INIT_TC6(SPI, CS_PIN, RESET_PIN, IRQ_PIN); +#endif \ No newline at end of file diff --git a/src/ModbusT1SCommon.h b/src/ModbusT1SCommon.h new file mode 100644 index 0000000..1a75111 --- /dev/null +++ b/src/ModbusT1SCommon.h @@ -0,0 +1,37 @@ +#ifndef _MODBUS_T1S_COMMON_H_INCLUDED +#define _MODBUS_T1S_COMMON_H_INCLUDED + +#ifndef __AVR__ +#include + +#define INIT_TC6(_SPI, _CS_PIN, _RESET_PIN, _IRQ_PIN) \ + TC6::TC6_Io* tc6_io = new TC6::TC6_Io \ + ( _SPI \ + , _CS_PIN \ + , _RESET_PIN \ + , _IRQ_PIN); \ + TC6::TC6_Arduino_10BASE_T1S* tc6_inst = new TC6::TC6_Arduino_10BASE_T1S(tc6_io); + +extern TC6::TC6_Arduino_10BASE_T1S* tc6_inst; +extern TC6::TC6_Io* tc6_io; +static void default_OnPlcaStatus(bool success, bool plcaStatus); + +#if (defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_UNOR4_MINIMA)) +#define RS485_SERIAL Serial1 +#define RS485_TX_PIN 1 +#define RS485_RX_PIN 0 +#define RS485_DE_PIN 8 +#define RS485_RE_PIN 7 +#endif + +enum ModbusT1SFunctionCode { + UDP_READ_COIL_PORT = 1, + UDP_WRITE_COIL_PORT, + UDP_READ_DI_PORT, + UDP_READ_IR_PORT, + UDP_READ_HR_PORT, + UDP_WRITE_HR_PORT +}; + +#endif +#endif diff --git a/src/ModbusT1SServer.cpp b/src/ModbusT1SServer.cpp new file mode 100644 index 0000000..ebd2ffa --- /dev/null +++ b/src/ModbusT1SServer.cpp @@ -0,0 +1,504 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include + +extern "C" { +#include "libmodbus/modbus.h" +#include "libmodbus/modbus-rtu.h" +} + +#include "ModbusT1SServer.h" +#ifndef __AVR__ + +/** + * Default constructor for ModbusT1SServerClass. + * + * Initializes the Modbus server with RS485 communication. + */ +ModbusT1SServerClass::ModbusT1SServerClass(): + callback(default_OnPlcaStatus) +{ + +} + +/** + * Constructor for ModbusT1SServerClass with RS485 support. + * + * Initializes the Modbus server with RS485 communication. + * + * @param rs485 Reference to an RS485Class object for RS485 communication. + */ +ModbusT1SServerClass::ModbusT1SServerClass(RS485Class& rs485) : _rs485(&rs485) +{ + +} + +/** + * Destructor for ModbusT1SServerClass. + */ +ModbusT1SServerClass::~ModbusT1SServerClass() +{ +} + +/** + * Start the Modbus T1S server with the specified parameters and RS485 communication. + * + * @param node_id id of the server + * @param baudrate Baud rate to use + * @param config serial config. to use defaults to SERIAL_8N1 + * @param rs485 RS485 object to use + * + * Return 1 on success, 0 on failure + */ +int ModbusT1SServerClass::begin(int node_id, unsigned long baudrate, uint16_t config, RS485Class& rs485) +{ + _node_id = node_id; + if (&rs485 != nullptr) + { + _rs485 = &rs485; + } + + pinMode(IRQ_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(IRQ_PIN), + + []() { + tc6_io->onInterrupt(); + }, + FALLING); + + /* Initialize IO module. */ + if (!tc6_io->begin()) + { + return 0; + } + + if(_gateway == IPAddress(0, 0, 0, 0)) { + _gateway = IPAddress(192, 168, 42, 100); + } + + IPAddress ip_addr = IPAddress(_gateway[0], _gateway[1], _gateway[2], _gateway[3] + _node_id); + IPAddress network_mask = IPAddress(255, 255, 255, 0); + IPAddress gateway = IPAddress(_gateway[0], _gateway[1], _gateway[2], _gateway[3]); + + T1SPlcaSettings t1s_plca_settings(_node_id); + T1SMacSettings const t1s_default_mac_settings; + MacAddress const mac_addr = MacAddress::create_from_uid(); + + if (!tc6_inst->begin(ip_addr + , network_mask + , gateway + , mac_addr + , t1s_plca_settings + , t1s_default_mac_settings)) + { + return 0; + } + if (!_server->begin(udp_port)) + { + return 0; + } + + float MODBUS_BIT_DURATION = 1.f / _baudrate; + float MODBUS_PRE_DELAY_BR = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6; + float MODBUS_POST_DELAY_BR = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6; + + _rs485->setDelays(MODBUS_PRE_DELAY_BR, MODBUS_POST_DELAY_BR); + + if(!ModbusRTUClient.begin(*_rs485, (unsigned long) _baudrate, (uint16_t) SERIAL_8N1)) { + return 0; + } + + ModbusRTUClient.setTimeout(2*1000); + + return 1; +} + +/** + * Set the Modbus RTU client timeout. + * + * This function sets the Modbus RTU client timeout. + * + * @param timeout The timeout value in milliseconds. + */ +void ModbusT1SServerClass::setTimeout(unsigned long timeout) { + ModbusRTUClient.setTimeout(timeout); +} + +/** + * Sets the Arduino_10BASE_T1S_UDP server for communication. + * + * This function sets the Arduino_10BASE_T1S_UDP server that the Modbus server will communicate with. + * + * @param server A pointer to the Arduino_10BASE_T1S_UDP server. + */ +int ModbusT1SServerClass::poll() +{ + return 0; +} + +/** + * Sets the Arduino_10BASE_T1S_UDP server for communication. + * + * This function sets the Arduino_10BASE_T1S_UDP server that the Modbus server will communicate with. + * + * @param server A pointer to the Arduino_10BASE_T1S_UDP server. + */ +int ModbusT1SServerClass::coilRead(int address) +{ + return read(address, COILS); +} + +/** + * Writes a value to a coil on the Modbus server. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the coil to write to. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +int ModbusT1SServerClass::coilWrite(int address) +{ + return write(address, COILS); +} +/** + * Reads the status of a discrete input from the Modbus server. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server using the provided UDP client. + * + * @param address The address of the discrete input to read. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ +int ModbusT1SServerClass::discreteInputRead(int address) { + return read(address, DISCRETE_INPUTS); +} + +/** + * Reads the value of an input register from the Modbus server. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server using the provided UDP client. + * + * @param address The address of the input register to read. + * @return long The value of the input register or -1 if an error occurs. + */ +long ModbusT1SServerClass::inputRegisterRead(int address) +{ + return read(address, INPUT_REGISTERS); +} + +/** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the holding register to write to. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +long ModbusT1SServerClass::holdingRegisterRead(int address) { + return read(address, HOLDING_REGISTERS); +} + +/** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the holding register to write to. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ +int ModbusT1SServerClass::holdingRegisterWrite(int address) { + return write(address, HOLDING_REGISTERS); +} + +/** + * Parses the Modbus packet received from the server. + * + * This function parses the Modbus packet received from the server. + * + * @return int The parsed packet or -1 if an error occurs. + */ +int ModbusT1SServerClass::parsePacket() { + int res = -1; + if(_server == nullptr) { + return res; + } + + int rx_packet_size = _server->parsePacket(); + if (rx_packet_size == 8) + { + int bytes_read = _server->read(udp_rx_buf, 8); + res = udp_rx_buf[0] << 8 | udp_rx_buf[1]; + _last_ip = _server->remoteIP(); + _last_port = _server->remotePort(); + return rx_packet_size == bytes_read ? res : -1; + } + return res; +} + +/** + * Checks the PLCA status and Services the hardware and the protocol stack. + * + * This function checks the PLCA status and services the hardware and the + * protocol stack. + * + */ +void ModbusT1SServerClass::checkStatus() +{ + tc6_inst->service(); + + static unsigned long prev_beacon_check = 0; + static unsigned long prev_udp_packet_sent = 0; + + auto const now = millis(); + + if ((now - prev_beacon_check) > 1000) + { + prev_beacon_check = now; + tc6_inst->getPlcaStatus(callback); + } +} + +/** + * Manage the incoming T1S packets and make the appropriate Modbus request. + * + * This function manages the incoming T1S packets and makes the appropriate Modbus request. + */ +void ModbusT1SServerClass::update() { + + checkStatus(); + switch (parsePacket()) + { + case UDP_READ_COIL_PORT: + coilRead(); + break; + + case UDP_WRITE_COIL_PORT: + coilWrite(); + break; + + case UDP_READ_DI_PORT: + discreteInputRead(); + break; + + case UDP_READ_IR_PORT: + inputRegisterRead(); + break; + + case UDP_READ_HR_PORT: + holdingRegisterRead(); + break; + + case UDP_WRITE_HR_PORT: + holdingRegisterWrite(); + break; + + default: + break; + } +} + +/** + * Reads a value from the T1S client, sends it to the Modbus server and sends the response back to the client. + * + * This function reads a value from the T1S client, sends it to the Modbus server and sends the response back to the client. + * + * @param address The address of the value to read. + * @param functionCode The function code to use for the Modbus request. + * + * @return long The value read from the Modbus server. + */ +long ModbusT1SServerClass::read(int address, int functionCode) { + long res = -1; + if(_server == nullptr) { + return res; + } + + int modbus_id = udp_rx_buf[2] << 8 | udp_rx_buf[3]; + int address_mod = udp_rx_buf[4] << 8 | udp_rx_buf[5]; + if (ModbusRTUClient.requestFrom(modbus_id, functionCode, address_mod, 1)) { + if (ModbusRTUClient.available()) { + switch (functionCode) { + case INPUT_REGISTERS: + case HOLDING_REGISTERS: + res = ModbusRTUClient.read(); + break; + case COILS: + case DISCRETE_INPUTS: + res = (uint8_t) ModbusRTUClient.read(); + break; + default: + return res; + } + + if(res != -1) { + udp_rx_buf[6] = (uint8_t)((res & 0xFF00) >> 8); + udp_rx_buf[7] = (uint8_t)(res & 0x00FF); + _server->beginPacket(_last_ip, _last_port); + _server->write((uint8_t *)udp_rx_buf, sizeof(udp_rx_buf)); + _server->endPacket(); + } + } + } + return res; +} + +/** + * reads a value from the T1S client and sends it to the Modbus server. + * + * This function reads a value from the T1S client and sends it to the Modbus server. + * + * @param address The address of the value to read. + * @param functionCode The function code to use for the Modbus request. + * + * @return long The value read from the Modbus server. + */ +long ModbusT1SServerClass::write(int address, int functionCode) { + long res = -1; + if(_server == nullptr) { + return res; + } + + int modbus_id = udp_rx_buf[2] << 8 | udp_rx_buf[3]; + int address_mod = udp_rx_buf[4] << 8 | udp_rx_buf[5]; + int value = -1; + switch (functionCode) { + case HOLDING_REGISTERS: + value = udp_rx_buf[6] << 8 | udp_rx_buf[7]; + break; + case COILS: + value = udp_rx_buf[7]; + break; + default: + return res; + break; + } + + ModbusRTUClient.beginTransmission(modbus_id, functionCode, address_mod, 1); + res = ModbusRTUClient.write(value); + if (!ModbusRTUClient.endTransmission()) { + res = -1; + } + return res; +} + +/** + * Sets the Arduino_10BASE_T1S_UDP server for communication. + * + * This function sets the Arduino_10BASE_T1S_UDP server that the Modbus server will communicate with. + * + * @param server A pointer to the Arduino_10BASE_T1S_UDP server. + */ +void ModbusT1SServerClass::setT1SServer(Arduino_10BASE_T1S_UDP & server) { + _server = &server; +} + +/** + * Sets the port to use for communication. + * + * This function sets the port to use for communication. + * + * @param port The port to use. + */ +void ModbusT1SServerClass::setT1SPort(int port) { + udp_port = port; +} + +/** + * Sets the baud rate to use for communication. + * + * This function sets the baud rate to use for communication. + * + * @param baudrate The baud rate to use. + */ +void ModbusT1SServerClass::setBaudrate(int baudrate) { + _baudrate = baudrate; +} + +/** + * Sets the callback function to use for PLCA status check. + * + * This function sets the callback function to use for PLCA status check. + * + * @param cb The callback function to use. + */ +void ModbusT1SServerClass::setCallback(callback_f cb) { + if(cb != nullptr) { + callback = cb; + } +} + +/** + * Default callback function for PLCA status check. + * + * This function is the default callback function for PLCA status check. + * + * @param success The success status of the PLCA status check. + * @param plcaStatus The PLCA status. + */ +static void default_OnPlcaStatus(bool success, bool plcaStatus) +{ + if (!success) + { + return; + } + + if (!plcaStatus) { + tc6_inst->enablePlca(); + } +} + +/** + * Enables Power Over Ethernet (POE). + * + * This function enables Power Over Ethernet (POE) setting the device as power source. + */ +void ModbusT1SServerClass::enablePOE() { + tc6_inst->digitalWrite(TC6::DIO::A0, true); + tc6_inst->digitalWrite(TC6::DIO::A1, true); +} + +/** + * Disables Power Over Ethernet (POE). + * + * This function disables Power Over Ethernet (POE) and set the USB as power source. + */ +void ModbusT1SServerClass::disablePOE() { + tc6_inst->digitalWrite(TC6::DIO::A0, false); + tc6_inst->digitalWrite(TC6::DIO::A1, true); +} + +/** + * Sets the gateway IP address. + * + * This function sets the gateway IP address. + * + * @param ip The gateway IP address. + */ +void ModbusT1SServerClass::setGatwayIP(IPAddress ip) { + _gateway = ip; +} + +ModbusT1SServerClass ModbusT1SServer; +#endif diff --git a/src/ModbusT1SServer.h b/src/ModbusT1SServer.h new file mode 100644 index 0000000..8876d1e --- /dev/null +++ b/src/ModbusT1SServer.h @@ -0,0 +1,242 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _MODBUS_T1S_SERVER_H_INCLUDED +#define _MODBUS_T1S_SERVER_H_INCLUDED + +#ifndef __AVR__ +#include "ModbusServer.h" +#include +#include +#include "ModbusRTUClient.h" +#include "ModbusT1SCommon.h" +#include + +using callback_f = void (*)(bool, bool); +class ModbusT1SServerClass : public ModbusServer { +public: + /** + * Default constructor for ModbusT1SServerClass. + * + * Initializes the Modbus server with RS485 communication. + */ + ModbusT1SServerClass(); + ModbusT1SServerClass(RS485Class& rs485); + + /** + * Destructor for ModbusT1SServerClass. + */ + virtual ~ModbusT1SServerClass(); + + /** + * Start the Modbus T1S server with the specified parameters + * + * @param node_id id of the server + * @param baudrate Baud rate to use + * @param config serial config. to use defaults to SERIAL_8N1 + * @param rs485 RS485 object to use + * + * Return 1 on success, 0 on failure + */ + + int begin(int node_id, unsigned long baudrate, uint16_t config = SERIAL_8N1, RS485Class& rs485 = RS485); + + /** + * Reads a coil from the Modbus server. + * + * This function sends a request to read a coil at the specified address + * from the Modbus server using the provided UDP client. + * + * @param address The address of the coil to read. + * @return int The value of the coil or -1 if an error occurs. + */ + int coilRead(int address = -1); + + /** + * Writes a value to a coil on the Modbus server. + * + * This function sends a request to write a value to a coil at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the coil to write to. + * @return int Returns 1 if the write operation is successful, 0 otherwise. + */ + int coilWrite(int address = -1); + + /** + * Reads the status of a discrete input from the Modbus server. + * + * This function sends a request to read the status of a discrete input at the specified address + * from the Modbus server using the provided UDP client. + * + * @param address The address of the discrete input to read. + * @return int The status of the discrete input (1 for ON, 0 for OFF) or -1 if an error occurs. + */ + int discreteInputRead(int address = -1); + + /** + * Reads the value of an input register from the Modbus server. + * + * This function sends a request to read the value of an input register at the specified address + * from the Modbus server using the provided UDP client. + * + * @param address The address of the input register to read. + * @return long The value of the input register or -1 if an error occurs. + */ + long inputRegisterRead(int address = -1); + + /** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the holding register to write to. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ + long holdingRegisterRead(int address = -1); + + /** + * Writes a value to a holding register on the Modbus server. + * + * This function sends a request to write a value to a holding register at the specified address + * on the Modbus server using the provided UDP client. + * + * @param address The address of the holding register to write to. + * @return int 1 if the write operation is successful, -1 if an error occurs. + */ + int holdingRegisterWrite(int address = -1); + + /** + * Parses the Modbus packet received from the server. + * + * This function parses the Modbus packet received from the server. + * + * @return int The parsed packet or -1 if an error occurs. + */ + int parsePacket(); + + /** + * Set the T1S server to use for communication + * + * @param server The T1S server to use + */ + void setT1SServer(Arduino_10BASE_T1S_UDP & server); + + /** + * Set the port to use for communication + * + * @param port The port to use + */ + void setT1SPort(int port = 8889); + + /** + * Update the Modbus server. + * + * This function updates the Modbus server. + */ + void update(); + + /** + * Set the baudrate to use for communication + * + * @param baudrate The baudrate to use + */ + void setBaudrate(int baudrate); + +/** + * Sets the gateway IP address. + * + * This function sets the gateway IP address. + * + * @param ip The gateway IP address. + */ + void setGatwayIP(IPAddress ip); + + /** + * Poll the Modbus server. + * + * This function polls the Modbus server. + * + * @return int The status of the poll. + */ + virtual int poll(); + + /** + * Checks the PLCA status and Services the hardware and the protocol stack. + * + * This function checks the PLCA status and services the hardware and the + * protocol stack. + * + */ + void checkStatus(); + + /** + * Set the callback function to use for PLCA status check. + * + * This function sets the callback function to use for PLCA status check. + * + * @param cb The callback function to use. + */ + void setCallback(callback_f cb = nullptr); + + /** + * Enables Power Over Ethernet (POE). + * + * This function enables Power Over Ethernet (POE) setting the device as power source. + */ + void enablePOE(); + + /** + * Disables Power Over Ethernet (POE). + * + * This function disables Power Over Ethernet (POE) and set the USB as power source. + */ + void disablePOE(); + + /** + * Set the Modbus RTU client timeout. + * + * This function sets the Modbus RTU client timeout. + * + * @param timeout The timeout value in milliseconds. + */ + void setTimeout(unsigned long timeout); + +private: + long read(int address, int functionCode); + long write(int address, int functionCode); + +private: + IPAddress _gateway = IPAddress(0, 0, 0, 0); + uint8_t udp_rx_buf[8] = {0}; + callback_f callback = nullptr; + RS485Class* _rs485 = &RS485; + Arduino_10BASE_T1S_UDP * _server = nullptr; + IPAddress _last_ip; + uint16_t _last_port; + int _baudrate = 9600; + int _node_id = 1; + int udp_port = 0; +}; + +extern ModbusT1SServerClass ModbusT1SServer; + +#endif +#endif diff --git a/src/libmodbus/modbus-private.h b/src/libmodbus/modbus-private.h index 79e7b93..ecd2f94 100644 --- a/src/libmodbus/modbus-private.h +++ b/src/libmodbus/modbus-private.h @@ -10,7 +10,7 @@ #ifndef _MSC_VER # include -#if defined(ARDUINO) && defined(__AVR__) +#if defined(ARDUINO) && (defined(__AVR__) || defined(ARDUINO_ARCH_RENESAS)) #define ssize_t unsigned long #define fd_set void*