- Published on
ARCHITECTURE_RULES - Production Firmware Discipline — ESP32 (Arduino Core)
- Authors
📜 ARCHITECTURE_RULES: Production Firmware Discipline — ESP32 (Arduino Core)
- 📜 ARCHITECTURE_RULES: Production Firmware Discipline — ESP32 (Arduino Core)
- 1️⃣ Dependency Discipline (Artikel 2 Freeze)
- 2️⃣ Memory Discipline (Artikel 3 Freeze)
- 3️⃣ Layering & Architecture Freeze (Artikel 4)
- 4️⃣ Concurrency Boundary (Artikel 5)
- 5️⃣ Communication Freeze (Artikel 6)
- 6️⃣ Reliability & Error Model (Artikel 7)
- 7️⃣ Reference Implementation (Minimal Project)
- 7.1 Struktur File (Arduino Flat Constraint)
- 7.2 Composition Root (R3, R5, R12)
- 7.3 Application Layer (app_)
- 7.4 Service Layer (svc_)
- 7.5 Driver Layer (drv_)
- 7.6 Task Implementation (Artikel 5 → R13–R16)
- 7.7 ISR Pattern (R13)
- 7.8 Inter-Task Communication (R15, R16)
- 7.9 Communication Boundary (Artikel 6 → R17–R21)
- 7.10 Execution Flow Final
- 7.11 Rule Coverage Mapping
- 7.12 Inti yang Harus Terlihat
- 🛡 Final Enforcement
⚠️ Status Dokumen
Dokumen ini adalah ringkasan aturan arsitektur yang dikunci pada Artikel 2–7.
Aturan di bawah ini tidak boleh dilanggar tanpa redesign formal.
Jika satu saja dilanggar, firmware berisiko kembali menjadi spaghetti.
1️⃣ Dependency Discipline (Artikel 2 Freeze)
🔒 R1 — No Global Mutable State
Dilarang:
bool globalMode;
int relayState;
Diperbolehkan:
constexpr- compile-time config
- immutable constant
Semua state runtime harus berada di dalam class.
🔒 R2 — No Singleton
Dilarang:
Logger::instance();
WiFiManager::getInstance();
Instance dibuat hanya di:
IndustrialNode.ino
🔒 R3 — Explicit Dependency Injection
Semua dependency diberikan melalui constructor.
Tidak boleh mengambil dependency dari global.
🔒 R4 — Composition > Inheritance
Inheritance hanya jika benar-benar diperlukan. Default approach adalah composition.
2️⃣ Memory Discipline (Artikel 3 Freeze)
🔒 R5 — Static-First Allocation
Semua object utama firmware harus:
- Dideklarasikan statik di
IndustrialNode.ino - Lifetime = lifetime firmware
Dilarang membuat object besar di dalam loop().
🔒 R6 — Heap Restricted Zone
Heap hanya boleh digunakan untuk:
- TLS internal
- MQTT internal buffer
- OTA buffer
Selain itu dilarang.
🔒 R7 — No Allocation in Control Loop
Dilarang:
newdeleteString- container growable
di path kontrol utama.
🔒 R8 — No Allocation in ISR
ISR hanya boleh:
- Set flag
- Notify task
- Push ke queue fixed-size
🔒 R9 — Destructor Must Be Trivial
Destructor tidak boleh:
- Blocking
- Publish
- Logging berat
- Network operation
3️⃣ Layering & Architecture Freeze (Artikel 4)
🔒 R10 — 3-Layer Model Final
Prefix wajib:
app_→ Applicationsvc_→ Servicedrv_→ Driversys_→ System
Arduino flat structure → prefix adalah boundary enforcement.
🔒 R11 — Dependency Direction Strict (DAG)
Allowed:
app_ → svc_ → drv_ → sys_
Forbidden:
- drv* include svc*
- svc* include app*
- circular include
🔒 R12 — Composition Root Only One
Semua instance dibuat di:
IndustrialNode.ino
Tidak boleh instansiasi tersebar.
4️⃣ Concurrency Boundary (Artikel 5)
🔒 R13 — ISR Only Notify
ISR tidak boleh:
- Call service
- Allocate
- Publish
- Logging berat
🔒 R14 — ControlTask Non-Blocking
ControlTask tidak boleh:
- Publish network blocking
- Logging blocking
- Delay panjang
🔒 R15 — Queue Must Be Bounded
Semua komunikasi antar task:
- Fixed-size
- Tidak growable
- Tidak dynamic
🔒 R16 — No Shared Mutable Without Owner
Jika state digunakan lintas task:
- Harus punya owner tunggal
- Task lain hanya kirim request
5️⃣ Communication Freeze (Artikel 6)
🔒 R17 — CommManager Mandatory
Semua komunikasi harus melalui:
svc_CommManager
Tidak boleh publish langsung dari App/Service.
🔒 R18 — Explicit State Machine Required
State minimal:
INIT
WIFI_CONNECTING
WIFI_CONNECTED
MQTT_CONNECTING
MQTT_CONNECTED
ERROR_BACKOFF
Reconnect tidak boleh implicit.
🔒 R19 — Offline-Safe Policy
Jika WAN down:
- Control tetap jalan
- Telemetry dibuffer bounded
- Tidak blocking ControlTask
🔒 R20 — Credential Encapsulation
Credential hanya di satu tempat (sys_Config.h atau secure storage). Tidak literal tersebar.
🔒 R21 — OTA Guarded
OTA hanya:
- Via CommManager
- Diverifikasi
- Tidak dieksekusi langsung dari callback
6️⃣ Reliability & Error Model (Artikel 7)
🔒 R22 — Explicit Error Propagation
Semua fungsi kritikal harus return:
enum class Status { ... };
Tidak boleh silent failure.
🔒 R23 — No Exception
Exception dilarang.
🔒 R24 — Watchdog Encapsulated
Watchdog hanya di-manage oleh:
svc_HealthService
Tidak boleh di-feed sembarang tempat.
🔒 R25 — Fail-Safe Mandatory
Jika error kritikal:
- System masuk state FAILSAFE
- Relay ke safe state
- Tidak fail-open
🔒 R26 — Logging via Interface
Tidak boleh Serial.println liar.
Logging harus:
- Via interface
- Non-blocking
- Bounded queue
🔒 R27 — Health Telemetry Mandatory
Minimal harus ada:
- reset_reason
- heap_min_free
- backlog_depth
- wifi_state
- error_code
7️⃣ Reference Implementation (Minimal Project)
Bagian ini menunjukkan bentuk minimal firmware yang mematuhi seluruh rule Artikel 2–6.
Tujuannya bukan fitur lengkap, tetapi:
melihat bagaimana rule diterapkan secara nyata
7.1 Struktur File (Arduino Flat Constraint)
IndustrialNode/
IndustrialNode.ino
app_control.cpp
app_control.h
svc_sensor.cpp
svc_sensor.h
svc_actuator.cpp
svc_actuator.h
svc_comm.cpp
svc_comm.h
drv_relay.cpp
drv_relay.h
sys_config.h
sys_types.h
Semua file mengikuti:
prefix = boundary layer
7.2 Composition Root (R3, R5, R12)
// IndustrialNode.ino
#include "app_control.h"
#include "svc_sensor.h"
#include "svc_actuator.h"
#include "svc_comm.h"
#include "drv_relay.h"
// Driver
RelayDriver relay(5);
// Service
ActuatorService actuator(relay);
SensorService sensor;
CommManager comm;
// Application
ControlApp app(sensor, actuator, comm);
// Task Wrapper
ControlTask controlTask(app);
CommTask commTask(comm);
void setup() {
controlTask.start();
commTask.start();
}
void loop() {
// tidak digunakan
}
Memenuhi:
R3 → dependency injection
R5 → static allocation
R12 → single composition root
7.3 Application Layer (app_)
// app_control.cpp
void ControlApp::run()
{
if (mode_ == AUTO)
{
float temp = sensor_.getTemperature();
if (temp > threshold_)
actuator_.on();
else
actuator_.off();
}
}
Ciri:
✔ decision system
✔ tidak tahu hardware
✔ tidak tahu MQTT
7.4 Service Layer (svc_)
// svc_actuator.cpp
void ActuatorService::on()
{
relay_.set(true);
}
void ActuatorService::off()
{
relay_.set(false);
}
Ciri:
✔ domain behavior
✔ tidak tahu mode AUTO/MANUAL
✔ tidak tahu task
7.5 Driver Layer (drv_)
// drv_relay.cpp
void RelayDriver::set(bool state)
{
digitalWrite(pin_, state ? HIGH : LOW);
}
Ciri:
✔ pure hardware
✔ tidak tahu logic
✔ tidak tahu service
7.6 Task Implementation (Artikel 5 → R13–R16)
ControlTask
void ControlTask::run(void* param)
{
while (true)
{
app_.run();
vTaskDelay(10);
}
}
CommTask
void CommTask::run(void* param)
{
while (true)
{
comm_.run();
vTaskDelay(10);
}
}
7.7 ISR Pattern (R13)
volatile bool buttonPressed = false;
void IRAM_ATTR buttonISR()
{
buttonPressed = true;
}
Diproses di ControlTask:
if (buttonPressed)
{
buttonPressed = false;
app.handleButton();
}
7.8 Inter-Task Communication (R15, R16)
struct Command {
bool relayOn;
};
QueueHandle_t commandQueue;
CommTask:
xQueueSend(commandQueue, &cmd, 0);
ControlTask:
xQueueReceive(commandQueue, &cmd, 0);
app.handleCommand(cmd);
7.9 Communication Boundary (Artikel 6 → R17–R21)
// svc_comm.cpp
void CommManager::run()
{
handleStateMachine();
if (mqttMessageAvailable())
{
Command cmd = parseMessage();
enqueueCommand(cmd);
}
}
Tidak pernah:
❌ actuator.setRelay()
❌ akses service lain langsung
7.10 Execution Flow Final
ControlTask → app_ → svc_ → drv_
CommTask → svc_CommManager → queue → ControlTask
7.11 Rule Coverage Mapping
| Rule | Implementasi |
|---|---|
| R1–R4 | dependency via constructor |
| R5–R9 | static object di .ino |
| R10–R12 | prefix + DAG |
| R13–R16 | ISR notify + queue |
| R17–R21 | CommManager |
7.12 Inti yang Harus Terlihat
Project ini menunjukkan:
tidak ada global mutable
tidak ada singleton
tidak ada task liar
tidak ada callback bypass layer
tidak ada dynamic allocation di control path
🛡 Final Enforcement
Jika salah satu aturan di atas dilanggar:
- Firmware berisiko kehilangan determinism.
- Refactor menjadi mahal.
- Debug menjadi sulit.
- Uptime jangka panjang menurun.
Aturan ini dibuat untuk menjaga firmware:
Stabil, deterministik, audit-able, dan maintainable selama 3–5 tahun.
Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.