building an ot/ics lab with raspi 5 and esp32

ID

building an ot/ics lab with raspi 5 and esp32

touch-ot logo

haven’t touched microcontrollers since 2021.

now i needed a test environment for a scanner that detects ot/ics devices on a network, but real ot gear is expensive.

so i simulated them with a raspi 5 + esp32.

raspi as a multi-protocol ot server, esp32 as a real iot device with sensors and actuators.

spent about 314k idr on esp32 + components, already had the raspi.

esp32 and w5500 ethernet connected with jumper wires on desk

shopping list

from a local electronics store in indonesia:

itemqtyprice
esp32 devkitc v4 wroom-32d + micro usb181k
w5500 ethernet lan tcp/ip module165k
relay module 5v 4ch 30a optocoupler1110k
dht22 am2302 temp & humidity sensor122k
lcd 1602 i2c green backlight127k
led rgb 5mm 3-color53k
breadboard 830p112k
jumper dupont wires 15cm pack136k

subtotal 359k idr, after discounts + shipping: 314k idr

raspi 5

raspberry pi 5 as multi-protocol ot simulator

install os, ssh in, install all services.

created 3 server scripts:

modbus tcp (port 502) - simulates a plc, holding registers + coils.

siemens s7 (port 102) - simulates siemens cpu 315-2 pn/dp using snap7.

bacnet/ip (port 47808) - simulates building automation controller using bac0.

plus mosquitto for mqtt broker (port 1883) and snmpd (port 161).

all running as systemd services.

port 22    - ssh
port 102   - siemens s7
port 161   - snmp
port 502   - modbus tcp
port 1883  - mqtt
port 5353  - mdns
port 47808 - bacnet/ip

esp32

haven’t done wiring in years, was a bit rusty looking at a breadboard again.

esp32 to w5500 via spi, dht22 for sensing, relay 4ch for actuation.

all female-to-female jumpers, breadboard only for power distribution.

esp32 devkitc is wide enough to cover all rows a-j on a breadboard. ended up not mounting it, just set it beside.

firmware

initially used the arduino ethernet library for w5500.

couldn’t run 2 servers (http + modbus) together. kept crashing.

turns out the arduino ethernet library isn’t thread-safe on esp32 (freertos). spi bus collision.

switched to eth.h (esp-idf native w5500 driver) with lwip stack. worked immediately.

features:

  • modbus tcp server (port 1234)
  • http status page (port 1111)
  • dht22 sensor reading
  • relay control via modbus or http
  • mei (fc 0x2b) + report slave id (fc 0x11)
  • exception responses for unsupported function codes

esp32 ot simulator web interface showing sensor data and relay control

flashing

used arduino ide. upload speed 115200.

brltty on ubuntu hijacks cp210x usb serial:

sudo systemctl stop brltty-udev
sudo systemctl disable brltty-udev
sudo systemctl mask brltty-udev

testing

modbus to esp32

from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.0.243', port=1234)
client.connect()
result = client.read_holding_registers(address=0, count=3)
print(f'temp: {result.registers[0] / 10.0} C')
# 31.9 C - real data from dht22

s7 to raspi

import snap7
client = snap7.Client()
client.connect('192.168.0.74', 0, 1)
info = client.get_cpu_info()
# CPU 315-2 PN/DP

bacnet to raspi

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2)
packet = bytes([0x81, 0x0a, 0x00, 0x08, 0x01, 0x00, 0x10, 0x08])
sock.sendto(packet, ('192.168.0.74', 47808))
data, addr = sock.recvfrom(1024)
# 21 bytes - bacnet iam response

problems along the way

scanner couldn’t detect esp32

built a modbus scanner that enumerates devices via mei, report slave id, holding registers, coils.

initially failed on everything.

esp32 didn’t respond to unsupported function codes. no response at all, connection dropped.

scanner tried mei -> timeout -> connection reset -> every request after that was a broken pipe.

scanner fix: added reconnect logic. connection lost -> auto reconnect -> continue.

firmware fix: added exception responses (funccode | 0x80) for unsupported fc. no more hanging.

after fixing:

esp32:
  method: report_slave_id
  slave_id: ESP32-IoT-Controller-v1.0.0

raspi:
  method: mei
  manufacturer: RaspberryPi-OT-Sim
  label: RPi5-PLC
  firmware_version: 1.0.0

dual server crash

esp32 crashed when running http + modbus together with the arduino ethernet library.

assert failed: xQueueSemaphoreTake queue.c:1709

cause: spi bus not mutex-protected, freertos preemption caused race conditions.

fix: switched to esp-idf native eth.h driver.

wireshark

all traffic visible in wireshark.

for modbus on port 1234, need to decode as: analyze -> decode as -> tcp port 1234 -> modbus/tcp.

modbus  - modbus tcp
s7comm  - siemens s7
bacnet  - bacnet/ip
mqtt    - mqtt

repo

github.com/zsbahtiar/touch-ot


← Back to blog