# @wz2b/node-red-modbus-dynamic-server

### Advanced Modbus server nodes for Node-RED

## License

MIT © Christopher Piggott

## Description

This package provides a set of Node-RED nodes for building flexible, general-purpose Modbus servers.

Unlike the server functionality in `node-red-contrib-modbus`, these nodes allow incoming Modbus requests to be intercepted before a response is sent. This makes it possible to examine, modify, filter, forward, or fulfill requests dynamically in a flow.

That enables use cases such as Modbus proxies, gateways, protocol conversion, request filtering, and other advanced server-side behaviors that are difficult or impossible with a conventional static register map.

---

## Included Nodes

This package contains the following nodes:

### Runtime nodes

#### Core request/response

- **modbus-dynamic-server**  
  Receives incoming Modbus requests from the server and emits them into a flow for dynamic handling.

- **modbus-dynamic-server-response**  
  Sends a dynamic response back to the waiting Modbus client request.
  See [FUNCTIONS.md](FUNCTIONS.md) for function examples and data encoding patterns.

#### Routing and control

- **modbus-source-router**  
  Routes incoming Modbus requests to different flow outputs based on the client's source IP address.  
  Rules are expressed as IPv4 CIDR blocks and evaluated top-to-bottom; the first matching rule wins.  
  If no rule matches, a configurable Modbus exception response is sent and the message is not emitted.

- **modbus-fc-filter**  
  Allows or blocks requests by Modbus function code (`payload.fc`).  
  Allowed requests continue unchanged; blocked requests are terminated with a configurable Modbus exception.

- **modbus-server-exception**  
  Convenience terminator node for custom reject paths.  
  Sends a Modbus exception response for the current pending request and emits no output.

#### Data handling

- **modbus-registers-read**  
  Reads and decodes values from the shared register map and emits them as a normal flow message.

- **modbus-registers-write**  
  Writes values into a local register store.

- **modbus-registers-respond**  
  Responds to Modbus requests using values from the local register store.

#### Integration / proxy

- **modbus-dynamic-proxy**  
  Forwards requests dynamically to another Modbus target and returns the result.

### Configuration nodes

- **modbus-dynamic-server-config**  
  Configures and owns the Modbus TCP server instance.

- **modbus-registers-config**  
  Defines local register storage used by register-based nodes.

- **modbus-proxy-target**  
  Defines a downstream Modbus target used for proxying or forwarding requests.

---

## How the pieces fit together

A typical dynamic flow looks like this:

1. A Modbus client sends a request to the configured server.
2. `modbus-dynamic-server` emits a friendly request object into the flow.
3. *(Optional)* `modbus-source-router` filters or routes the request based on the client's IP address.
4. The flow decides what to do with the request:
    - answer from local registers
    - generate a value dynamically
    - forward to another Modbus device
    - reject or filter the request
5. `modbus-dynamic-server-response` sends the final response back to the client.

This separation makes it possible to implement dynamic server-side logic entirely in Node-RED flows.

---

## Message Contract

Messages emitted by `modbus-dynamic-server` include internal request context in `msg._modbus`.

This context is required by downstream nodes such as:

- `modbus-dynamic-server-response`
- `modbus-source-router`
- `modbus-fc-filter`
- `modbus-server-exception`
- `modbus-dynamic-proxy`
- `modbus-registers-respond`

These nodes use `msg._modbus` to send a response back to the original Modbus client request.

Flows must preserve this property. This is especially important when you use other nodes, including
`function` nodes or custom nodes, to inspect requests, apply custom logic, transform payloads,
or generate custom responses.


Preferred:

```js
msg.payload = newValue;
return msg;
```

Also valid (safe alternative):

```js
msg = { ...msg, payload: newValue }
return msg
```

Not allowed:

```js
msg = { payload: newValue };
return msg;
```

If msg._modbus is missing, downstream response/termination nodes will be unable to complete the original Modbus request and will raise an error.

In general, modify the existing msg object rather than replacing it entirely.



---

## Use Cases

### Protocol Conversion

Requests can be intercepted, decoded, and translated into requests for another protocol. This is useful for protocol mediation and conversion involving systems such as OPC UA, BACnet, MQTT, Sparkplug, or custom APIs.

### Modbus Gateway

These nodes can be used to build a Modbus server that answers some requests from a local register cache while forwarding other requests to external devices, including Modbus TCP or Modbus serial targets.

This makes it possible to build gateways, aggregators, and hybrid local/remote data services.

### Security, Data Diode, and Request Filtering

These nodes can also be used to build a Modbus TCP gateway that inspects requests and applies filtering rules before allowing them to proceed.

For example, requests can be filtered based on client IP address, function code, unit ID, register address, or other request attributes. This makes it possible to implement read-only gateways, request whitelisting, and "data diode"-style patterns that prevent write operations or other side effects.

---

## Getting Started

A minimal dynamic server setup usually includes:

- one **modbus-dynamic-server-config** node
- one **modbus-dynamic-server** node
- one or more flow nodes such as `function`, `switch`, or `change`
- one **modbus-dynamic-server-response** node

More advanced flows may also use:

- **modbus-source-router** to split traffic by client IP before any processing logic
- **modbus-fc-filter** to allow only selected function codes (for example, read-only gateways)
- **modbus-server-exception** to terminate requests from custom `switch`/`function` logic
- **modbus-registers-read** to expose cached register values to other flows or dashboards
- **modbus-registers-config** with **modbus-registers-write** and **modbus-registers-respond**
- **modbus-proxy-target** with **modbus-dynamic-proxy**


## Examples

This package includes example flows in the `examples/` directory. After installation, they should be available from the Node-RED import dialog under the examples list for this module.



