MODBUS

https://en.wikipedia.org/wiki/Modbus

Implements MODBUS-RTU protocols.

Can use this directly, but generally preferable to create custom Device and Request classes. See R421A Relay Boards for an example.

Device properties include:

Address

The address of a modbus slave. Modbus docs. call this the slave ID.

Node

Represents something a slave device does. Modbus relay boards have one node for each output it controls.

Node ID

The channel a node lives on. For the R421Axx relay boards this is the address or channel number. In a modbus transaction this is the address field.

For efficiency, nodes aren’t implemented as objects, just identifiers used by a Device / Controller. Nodes can be controlled using generic commands such as ‘open’, ‘close’, ‘toggle’, etc.

namespace IO::Modbus

Debug print support, outputs contents of packet

size_t printRequest(Print &p, const PDU &pdu)
size_t printRequest(Print &p, const ADU &adu)
size_t printResponse(Print &p, const PDU &pdu)
size_t printResponse(Print &p, const ADU &adu)

Enums

enum Exception

Modbus exception codes returned in response packets.

Values:

enumerator Success

No exception, transaction completed normally.

enumerator IllegalFunction

Function not allowed/supported/implemented, or device in wrong state to process request.

For example, an unconfigured device may be unable to return register values.

enumerator IllegalDataAddress

Data address not allowed.

More specifically, the combination of reference number and transfer length is invalid. For a controller with 100 registers, the ADU addresses the first register as 0, and the last one as 99.

If a request is submitted with a starting register address of 96 and a quantity of registers of 4, then this request will successfully operate (address-wise at least) on registers 96, 97, 98, 99.

If a request is submitted with a starting register address of 96 and a quantity of registers of 5, then this request will fail with Exception Code 0x02 “Illegal Data Address” since it attempts to operate on registers 96, 97, 98, 99 and 100, and there is no register with address 100.

enumerator IllegalDataValue

Data value not allowed.

This indicates a fault in the structure of the remainder of a complex request, such as that the implied length is incorrect. It specifically does NOT mean that a data item submitted for storage in a register has a value outside the expectation of the application program, since the MODBUS protocol is unaware of the significance of any particular value of any particular register.

enumerator SlaveDeviceFailure

Protocol slave device failure exception.

An unrecoverable error occurred while the server (or slave) was attempting to perform the requested action.

enum Function

Values:

enumerator XX

Functions

inline bool operator!(Exception exception)
String toString(Exception exception)
String toString(Function function)
ErrorCode readRequest(RS485::Controller &controller, ADU &adu)
void sendResponse(RS485::Controller &controller, ADU &adu)
struct ADU
#include <ADU.h>

Prepare outgoing packet

The slaveAddress and pdu fields must be correctly initialised.

returns size_t

Size of ADU, 0 on error

Parse a received packet

param receivedSize

How much data was received

returns ErrorCode

Result of parsing

class Device : public IO::RS485::Device
#include <Device.h>

A virtual device, represents a modbus slave device.

Actual devices must implement:

  • createRequest()

  • callback()

  • fillRequestData()

Subclassed by IO::Modbus::R421A::Device

Public Functions

inline virtual void onBroadcast(const ADU &adu)

Handle a broadcast message.

inline virtual void onRequest(ADU &adu)

Handle a message specifically for this device.

virtual void handleEvent(IO::Request *request, Event event) override

Implementations may override this method to customise event handling.

struct PDU
#include <PDU.h>

Protocol Data Unit.

Content is independent of the communication layer. Structure does NOT represent over-the-wire format as packing issues make handling PDU::Data awkward. Therefore, the other fields are unaligned and adjusted as required when sent over the wire.

byteCount field

Some functions with a byteCount field are marked as calculated. This means it doesn’t need to be set when constructing requests or responses, and will be filled in later based on the values of other fields.

In other cases, a setCount method is provided to help ensure the byteCount field is set correctly.

Get PDU size based on content

Calculation uses byte count so doesn’t access any 16-bit fields

union Data
#include <PDU.h>

Public Members

uint8_t exceptionCode
ReadCoils readCoils
ReadDiscreteInputs readDiscreteInputs
ReadHoldingRegisters readHoldingRegisters
ReadInputRegisters readInputRegisters
WriteSingleCoil writeSingleCoil
WriteSingleRegister writeSingleRegister
ReadExceptionStatus readExceptionStatus
GetComEventCounter getComEventCounter
GetComEventLog getComEventLog
WriteMultipleCoils writeMultipleCoils
WriteMultipleRegisters writeMultipleRegisters
ReportServerId reportServerId
MaskWriteRegister maskWriteRegister
ReadWriteMultipleRegisters readWriteMultipleRegisters
union GetComEventCounter
#include <PDU.h>

Public Members

Response response
struct Response
#include <PDU.h>
union GetComEventLog
#include <PDU.h>

Public Members

Response response
struct Response
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

union MaskWriteRegister
#include <PDU.h>

Public Types

using Response = Request

Public Members

Request request
Response response
struct Request
#include <PDU.h>
union ReadCoils
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>
struct Response
#include <PDU.h>

Public Members

uint8_t byteCount

Use setCount()

uint8_t coilStatus[250]

Use setCoil()

union ReadDiscreteInputs
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>
struct Response
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

union ReadExceptionStatus
#include <PDU.h>

Public Members

Response response
struct Response
#include <PDU.h>
union ReadHoldingRegisters
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>
struct Response
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

union ReadInputRegisters
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>
struct Response
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

union ReadWriteMultipleRegisters
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>

Public Members

uint8_t writeByteCount

Calculated.

struct Response
#include <PDU.h>
union ReportServerId
#include <PDU.h>

Public Members

Response response
struct Response
#include <PDU.h>

Public Members

uint8_t data[248]

Content is specific to slave device.

union WriteMultipleCoils
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

struct Response
#include <PDU.h>
union WriteMultipleRegisters
#include <PDU.h>

Public Members

Request request
Response response
struct Request
#include <PDU.h>

Public Members

uint8_t byteCount

Calculated.

struct Response
#include <PDU.h>
union WriteSingleCoil
#include <PDU.h>

Public Types

using Response = Request

Public Members

Request request
Response response
struct Request
#include <PDU.h>
union WriteSingleRegister
#include <PDU.h>

Public Types

using Response = Request

Public Members

Request request
Response response
struct Request
#include <PDU.h>
class Request : public IO::Request
#include <Request.h>

Subclassed by IO::Modbus::R421A::Request

Public Functions

virtual ErrorCode callback(PDU &pdu) = 0

Process a received PDU.

Parameters

pdu

Returns

ErrorCode – If request is re-submitted, return Error::pending, otherwise request will be completed with given error.

namespace R421A
class Device : public IO::Modbus::Device
#include <Device.h>

Public Functions

virtual IO::Request *createRequest() override

Create a request object for this device.

Returns

Request* – Caller must destroy or submit the request

inline virtual DevNode::ID nodeIdMin() const override

Get minimum valid Node ID for this device.

Typically devices have a contiguous valid range of node IDs

inline virtual DevNode::ID nodeIdMax() const override

Get maximum valid Node ID for this device.

inline virtual uint16_t maxNodes() const override

Determine maximum number of nodes supported by the devicce.

Returns

uint16_t – 0 if device doesn’t support nodes

virtual DevNode::States getNodeStates(DevNode node) const override

Return the current set of states for all nodes controlled by this device.

Used to determine if, say, all nodes are ON, OFF or a combination.

virtual void handleEvent(IO::Request *request, Event event) override

Implementations may override this method to customise event handling.

struct Config
#include <Device.h>

R421A device configuration.

Public Members

Modbus::Device::Config modbus

Basic modbus configuration.

uint8_t channels

Number of channels (typically 4 or 8)

class Factory : public IO::Device::Factory
#include <Device.h>

Public Functions

inline virtual IO::Device *createDevice(IO::Controller &controller, const char *id) const override

Create a new device instance.

Called by DeviceManager::createDevice()

Parameters
  • controller – The owning controller

  • id – Unique identifier for the device

Returns

Device* – The constructed instance

inline virtual const FlashString &controllerClass() const override

Return the expected controller type for this device class, e.g. ‘rs485’.

The Device Manager uses this value to verify that devices are constructed using the correct controller.

inline virtual const FlashString &deviceClass() const override

Return the Device class name, e.g. ‘r421a’.

class Request : public IO::Modbus::Request
#include <Request.h>

Public Functions

virtual ErrorCode parseJson(JsonObjectConst json) override

Fill this request from a JSON description.

virtual void getJson(JsonObject json) const override

Get result of a completed request in JSON format.

virtual bool setNode(DevNode node) override

If nodes are supported, implemented this method.

virtual DevNode::States getNodeStates(DevNode node) override

Query node status from response.

virtual ErrorCode callback(PDU &pdu) override

Process a received PDU.

Parameters

pdu

Returns

ErrorCode – If request is re-submitted, return Error::pending, otherwise request will be completed with given error.