zam@zsbahtiar:~$

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

touch-ot logo

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.

esp32 dan w5500 ethernet terhubung dengan jumper wire di meja kerja

Isi lab dalam satu tabel

Komponennya gw beli dari toko elektronik lokal di Bandung:

barangqtyharga
esp32 devkitc v4 wroom-32d + micro USB181k
w5500 ethernet LAN TCP/IP module165k
relay module 5V 4ch 30A optocoupler1110k
dht22 am2302 sensor suhu dan kelembapan122k
LCD 1602 I2C green backlight127k
RGB LED 5mm 3-color53k
breadboard 830p112k
paket jumper dupont 15cm136k

Subtotal-nya 359k. Setelah diskon sama ongkir, total akhirnya 314k. Raspi-nya udah ada.

Satu raspi, beberapa identity industri

raspberry pi 5 menjalankan beberapa protokol OT

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

web interface esp32 OT simulator yg nampilin sensor data dan relay control

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.


← kembali ke blog