cat ~/notes/touch-ot.md
bikin lab ot/ics kecil pakai raspberry pi dan esp32
[en]bikin lab ot/ics kecil pakai raspberry pi dan esp32
Gw butuh satu network penuh device OT/ICS buat nguji security scanner. PLC, gateway, sama field device beneran cepat banget bikin mahal, jadi gw bikin lab paling kecil yg masih berguna pakai Raspberry Pi 5 sama esp32.
Raspi-nya pura-pura jadi beberapa sistem industri sekaligus. Board esp32 ngasih sisi fisiknya: sensor suhu, relay, dan Modbus TCP server sendiri lewat Ethernet.
Targetnya bukan niru satu pabrik. Targetnya bikin protocol discovery gagal dengan cara yg sama kayak device beneran.

Isi lab dalam satu tabel
Komponennya gw beli dari toko elektronik lokal di Bandung:
| barang | qty | harga |
|---|---|---|
| esp32 devkitc v4 wroom-32d + micro USB | 1 | 81k |
| w5500 ethernet LAN TCP/IP module | 1 | 65k |
| relay module 5V 4ch 30A optocoupler | 1 | 110k |
| dht22 am2302 sensor suhu dan kelembapan | 1 | 22k |
| LCD 1602 I2C green backlight | 1 | 27k |
| RGB LED 5mm 3-color | 5 | 3k |
| breadboard 830p | 1 | 12k |
| paket jumper dupont 15cm | 1 | 36k |
Subtotal-nya 359k. Setelah diskon sama ongkir, total akhirnya 314k. Raspi-nya udah ada.
Satu raspi, beberapa identity industri

Raspi-nya jalanin tiga protocol server kecil:
- Modbus TCP di port
502, lengkap dengan holding register dan coil PLC - Siemens S7 di port
102, simulasi Siemens CPU 315-2 PN/DP lewat snap7 - BACnet/IP di port
47808, simulasi building automation controller lewat bac0
Gw tambahin mosquitto sebagai MQTT broker di port 1883 dan snmpd di port 161.
Semua komponen jalan sebagai systemd service.
port 22 - SSH
port 102 - Siemens S7
port 161 - SNMP
port 502 - Modbus TCP
port 1883 - MQTT
port 5353 - mDNS
port 47808 - BACnet/IP
Dari sisi scanner, satu raspi sekarang keliatan kayak satu rak equipment industri. Ini penting karena scanner ga bisa berasumsi satu host cuma punya satu protokol atau satu identity.
Nambahin I/O fisik
Board esp32 connect ke module Ethernet w5500 lewat SPI. dht22 ngasih suhu dan kelembapan beneran, sementara relay empat channel jadi output fisik yg bisa didetect dan dikontrol scanner.
Wiring-nya pakai jumper female-to-female. Devkit esp32 cukup lebar buat nutup semua row breadboard dari A sampai J, jadi breadboard cuma dipakai buat distribusi power dan controller-nya ditaro di samping.
Dua server di satu microcontroller
Firmware pertama pakai Arduino Ethernet library buat w5500. HTTP server bisa jalan. Modbus server juga bisa jalan. Begitu keduanya dinyalain barengan, esp32 crash.
Library-nya ga thread-safe di bawah FreeRTOS, jadi dua task server bisa tabrakan di bus SPI.
Gw ganti ke ETH.h, native driver w5500 dari ESP-IDF yg pakai lwIP.
Dua server langsung jalan tanpa SPI race tadi.
Firmware-nya expose:
- Modbus TCP server di port
1234 - HTTP status page di port
1111 - value suhu dan kelembapan dari dht22
- kontrol relay lewat Modbus atau HTTP
- MEI dengan function code
0x2b - Report Slave ID dengan function code
0x11 - Modbus exception response buat function code yg ga didukung

Flash firmware di Ubuntu
Gw pakai Arduino IDE dengan upload speed 115200.
Di Ubuntu, brltty bisa ngambil device USB serial cp210x sebelum IDE connect. Stop dan mask udev service-nya bikin port itu bebas lagi:
sudo systemctl stop brltty-udev
sudo systemctl disable brltty-udev
sudo systemctl mask brltty-udev
Cek tiap protokol secara langsung
Contoh di bawah pakai IP khusus dokumentasi, bukan alamat dari lab gw.
Modbus ke esp32
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.0.2.10', 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 - data beneran dari dht22
S7 ke raspi
import snap7
client = snap7.Client()
client.connect('192.0.2.20', 0, 1)
info = client.get_cpu_info()
# CPU 315-2 PN/DP
BACnet ke 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.0.2.20', 47808))
data, addr = sock.recvfrom(1024)
# 21 bytes - BACnet I-Am response
Tiap protokol ngasih identity dan bentuk response yg beda ke scanner.
Scan Modbus pertama gagal total
Scanner nyoba MEI, Report Slave ID, holding register, sama coil ke esp32. Semua probe gagal.
Firmware-nya ga balikin Modbus exception buat function code yg ga didukung. Dia diem lalu nutup koneksi.
Scanner mulai dari MEI, nunggu sampai timeout, kehilangan socket, lalu kena broken pipe di semua request berikutnya. Satu optional probe yg ga didukung ngerusak seluruh fingerprint sequence.
Fix-nya perlu ada di dua sisi:
- firmware sekarang balikin exception dengan
function_code | 0x80 - scanner reconnect kalo device mutusin socket
Setelah itu, dua host-nya bisa ngenalin diri:
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
Lab ini ngebuka satu aturan penting buat scanner: optional probe yg gagal ga boleh ngeblok probe setelahnya.
Crash di bus SPI
Ethernet stack awalnya gagal dengan error:
assert failed: xQueueSemaphoreTake queue.c:1709
Preemption FreeRTOS bikin dua task server masuk ke jalur SPI yg ga dilindungi.
Pindah ke native driver ETH.h ngilangin race itu dan bikin HTTP sama Modbus bisa hidup barengan.
Liat paket di wire
Wireshark bisa decode semua protokol di lab.
Device esp32 jalanin Modbus di port 1234, bukan port standar, jadi decoder-nya harus di-map manual lewat Analyze, Decode As, TCP port 1234, lalu Modbus/TCP.
modbus - Modbus TCP
s7comm - Siemens S7
bacnet - BACnet/IP
mqtt - MQTT
Setup kecil ini nguji lebih dari sekadar happy-path handshake. Ada banyak protokol di satu host, I/O fisik lewat Ethernet, unsupported function code, koneksi putus, reconnect, dan sensor value yg beneran berubah.
Itu udah cukup realistis buat ngebongkar asumsi jelek di scanner sebelum ketemu network industri beneran.
Code-nya ada di github.com/zsbahtiar/touch-ot.