UMT Modelinden C++ Koduna: Bir Kapı Kontrolcüsü 30 Dakikada
Durum makinesi tabanlı gömülü yazılım geliştirirken çoğu ekip aynı soruyla karşılaşır: modeli kim yazacak, kodu kim yazacak ve ikisi nasıl senkronize kalacak?
Bu yazıda UMTSM'in bu soruya verdiği cevabı somut bir örnekle gösteriyorum. Bir kapı kontrolcüsünü .umt dosyasından başlayarak üretime hazır C++ koduna dönüştürüyoruz — adım adım.
Problem: Otomatik ve Manuel Modda Çalışan Bir Kapı
Sistem şu gereksinimleri karşılamalı:
- Kapı otomatik modda açıldığında belirli bir süre bekledikten sonra kendiliğinden kapanır.
- Manuel modda kapı yalnızca butona basıldığında hareket eder.
- İki mod arasında çalışma sırasında geçiş yapılabilir.
- Kapı açılırken ya da kapanırken mod değişikliği hareketi kesmez.
- Sistem kapandıktan sonra son durumunu hatırlar — cihaz yeniden başlatıldığında kaldığı yerden devam eder.
Bunlar gerçek bir ürün gereksinimi. Elle yazılmış bir FSM'de bu beş maddeyi temiz tutmak haftalar sürebilir. Eğer sistemi kabaca bir UML diagramı olarak ifade ecek olursak
Bu durum makinesini UMTSM'de ifade edersek, .umt dosyası şöyle olur:
Adım 1: Durum Makinesini Modellemek
type sm Engine;
sm Door
{
persistent deep history -> ManualMode_Open;
state ManualMode
{
Open:
ButtonPressed -> Closing / engineRunACCW;
Closing:
entry / engineRunACCW;
DoorClosed / engineStop -> Close;
ButtonPressed / engineStop -> Opening;
Close:
ButtonPressed -> Opening;
Opening:
entry / engineRunCCW;
DoorOpen / engineStop -> Open;
ButtonPressed / engineStop -> Closing;
Automatic -> AutomaticMode;
}
state AutomaticMode
{
Open:
entry / resetWaitingTime;
do / wait;
ButtonPressed / resetWaitingTime;
-> Closing;
Closing:
entry / engineRunACCW;
DoorClosed / engineStop -> Close;
ButtonPressed / engineStop -> Opening;
Close:
ButtonPressed -> Opening;
Opening:
entry / engineRunCCW;
DoorOpen / engineStop -> Open;
Manual -> ManualMode;
}
}
Birkaç satırda neler tanımladık:
- İki üst seviye bileşik durum:
ManualModeveAutomaticMode - Her birinde dört alt durum:
Open,Closing,Close,Opening - Otomatik modda
do / waitile zamanlayıcı tabanlı kapanma persistent deep historyile güç kesintisinden kurtarmatype sm Engineile Engine durum makinesine bağımlılık bildirimi
Adım 2: Kod Üretimi
UMTSM modeli parse edip hiyerarşik bir iç temsile (IR) dönüştürür. CppGen bu IR'dan aşağıdaki dosyaları üretir:

| Dosya | Amacı |
|---|---|
| Door.hh | Durum Makinesi sınıf bildirimi |
| Door.cpp | Durum Mekinesi |
| Door_DataType.hh | Duurum Makinesi veri yapısı |
| Door_Types.hh | Türetilmiş tip bildirimleri |
| Door_Auxilary.cpp.template | Kullanıcı yazacağı kısıt ve eylem fonksiyonları için şablonlar |
| Door_DataType.cpp.template | Veri yapılarının devreye alınma ve devre dışı bırakılması için şablon |
| Door_UserTypes.hh.template | Kullanıcı tanımladığı harici veri tip tanımları dosyası için şablon |
Üretilen dosyalar dokunulmaz — bir sonraki üretimde yeniden yazılır. Kullanıcıya ait dosyalar src/door/ altında yaşar ve üretimden etkilenmez.
Adım 3: Aksiyonları İmplemente Etmek
Üretilen şablona bakarak src/door/Door_Auxilary.cpp dosyasını oluşturuyoruz:
void Door::engineRunCCW([[maybe_unused]] Door_DataType const& input)
{
instanceData.doorActionTimeStart = std::chrono::system_clock::now();
instanceData.pEngine->trigger_runCCW();
}
void Door::engineRunACCW([[maybe_unused]] Door_DataType const& input)
{
instanceData.doorActionTimeStart = std::chrono::system_clock::now();
instanceData.pEngine->trigger_runACCW();
}
void Door::engineStop([[maybe_unused]] Door_DataType const& input)
{
instanceData.pEngine->trigger_stop();
}
void Door::resetWaitingTime([[maybe_unused]] Door_DataType const& input)
{
instanceData.waitUntil =
std::chrono::system_clock::now() + DOOR_AUTO_CLOSE_DELAY;
}
void Door::wait([[maybe_unused]] Door_DataType const& input)
{
std::this_thread::sleep_until(instanceData.waitUntil);
}
Aksiyonlar virtual metodlardır — SM iskeletini değiştirmeden farklı donanım için farklı implementasyon sağlanabilir. Simülasyonda motor thread'e uyuyarak taklit edilir; gerçek donanımda GPIO çıkışı sürer.
Adım 4: Sistemi Birleştirmek
src/main/main.cpp içinde tüm durum makineleri örneklenir ve birbirine bağlanır:
Door door;
Engine engine;
Button button;
// ...
door.instanceData.pEngine = &engine;
button.instanceData.pDoor = &door;
engine.start();
door.start(); // persistent history burada yüklenir
button.start();
// ...
door.start() çağrıldığında load_Deep_Main() kalıcı depolamadan son durumu okur. İlk açılışta varsayılan olarak ManualMode::Open'dan başlar. Sonraki açılışlarda son aktif durum geri yüklenir.
Adım 5: Çalıştırmak
cmake -S . -B build/Release -DCMAKE_BUILD_TYPE=Release
cmake --build build/Release
./build/Release/src/main/door
Uygulama tam ekran ncurses arayüzüyle açılır:

A tuşuna basıldığında sistem otomatik moda geçer. Kapı açılır, zamanlayıcı başlar, süre dolunca kapanma başlar. Bu arada M tuşuna basıldığında kapı mevcut hareketini sürdürür — motor durmaz — ama artık manuel modun kuralları geçerlidir.
Ne Elde Ettik?
| Gereksinim | Yaklaşım |
|---|---|
| Otomatik kapanma | do / wait + completion transition |
| Manuel mod | Buton eventi ile hareket |
| Mod geçişi | Manual / Automatic eventi, simetrik geçişler |
| Hareket sırasında mod geçişi | Geçiş entry action'ı yok, motor devam eder |
| Güç kesintisi sonrası kurtarma | persistent deep history + store_Deep_Main |
Toplam elle yazılan kod: ~150 satır aksiyon implementasyonu ve ~50 satır main.cpp. Geri kalan her şey üretildi.
Sonuç
UMTSM'in bu örnekte sağladığı şeyin özü şu: modeli ve kodu aynı kaynak dosyasından türetmek. .umt dosyası değiştiğinde üretilen iskelet güncellenir, kullanıcı implementasyonları korunur. Model ile kod hiçbir zaman senkronizasyonu kaybetmez.
Örnek kodun tamamı GitHub'da: https://github.com/demiralp/umtsm-examples-cpp
UMTSM ile ilgili sorularınız veya projeleriniz için iletişime geçin.
