Programowanie obiektowe w CAREL STone Kompletny przewodnik
Cytat:Niniejszy artykuł to zbiór zasad i konwencji programowania obiektowego w języku Structured Text (ST) w środowisku CAREL STone dla sterowników c.pCO nowej generacji. Wszystkie opisane wzorce i praktyki bazują na moim wieloletnim doświadczeniu z produkcyjnymi bibliotekami przemysłowymi. Jako przykład przewodni wybrałem bibliotekę sterowania pompownią przemysłową — system zarządzający różnymi typami pomp (VFD, DOL, soft-start) z regulacją ciśnienia, przepływu i poziomu cieczy. Zasady mają jednak zastosowanie uniwersalne.
1. Struktura projektu CAREL STone
1.1. Plik projektu/biblioteki ".stprj"
Każdy projekt/biblioteka CAREL STone posiada plik XML .stprj, który definiuje metadane i strukturę kompilacji.
Kod: text
1.2. Organizacja katalogów
Dobrą praktyką jest podział kodu na katalogi odzwierciedlające elementy OOP:
| Katalog | Zawartość | Przykłady plików | Classes/ | Klasy (abstrakcyjne i finalne) | BasePump.st, VFD_Pump.st, DOL_Pump.st | Interfaces/ | Interfejsy (kontrakty) | IPump.st, IVariableSpeedPump.st | Functions/ | Funkcje globalne | ValidateModbusResults.st, CheckControllerID.st | Libs/ | Zależności zewnętrzne (.stlib) | PumpCore_v.2.0.3.0.stlib |
Konwencja: każda klasa/interfejs = osobny plik .st o nazwie identycznej z nazwą klasy/interfejsu.
1.3. Wersjonowanie bibliotek — "LibVer.g.st"
CAREL STone stosuje mechanizm preprocesora do zarządzania wersjami i namespace'ami:
Kod: text
Kluczowe zasady:
• Wersja jest częścią namespace'a (Libs.PumpStation_Lib_v2_0_0) — pozwala na współistnienie wielu wersji biblioteki
• Include guard (__LIBRARYNAMESPACEANDVERSION__) zapobiega wielokrotnemu definiowaniu
• Każdy plik .st zaczyna się od {INCLUDE '../LibVer.g.st'}
• Makro LibraryNamespaceAndVersion jest używane jako nazwa NAMESPACE w każdym pliku
2. Namespace i dyrektywy preprocesora
2.1. Namespace
Każdy plik w bibliotece jest opakowany w NAMESPACE z makrem wersyjnym:
Kod: text
Konwencje:
• USING importuje inne namespace'y — zawsze na początku, po otwarciu NAMESPACE
• Zależne biblioteki importowane przez pełną ścieżkę z wersją: Libs.PumpCore_v2_0_3_0
• Typy systemowe przez System.IO, System.Math
2.2. Kompilacja warunkowa "{IF DEF ...}"
CAREL STone wspiera dyrektywy preprocesora do kompilacji warunkowej. W praktyce intensywnie je wykorzystuję do obsługi wariantów sprzętowych:
Kod: text
Kod: text
Praktyczne zastosowania:
| Define | Efekt | FEATURE_MODBUS_DRIVES | Włącza sterowanie VFD przez Modbus — wymaga rozszerzonego API komunikacyjnego | FEATURE_ADVANCED_MOTORS | Włącza pompy soft-start i DOL z rozszerzonym monitoringiem |
Cytat:Dzięki temu jeden codebase obsługuje wiele wariantów sprzętowych. Klasy mogą mieć dwie niezależne implementacje w tym samym pliku, przełączane w compile-time.
2.3. Regiony "{REGION ... ENDREGION}"
Regiony porządkują sekcje kodu wizualnie w IDE:
Kod: text
Zalecana konwencja regionów:
| Region | Zawartość | Setters | Metody ustawiające parametry | Getters | Metody zwracające wartości | Public / Protected | Podregiony wewnątrz Setters/Getters grupujące po widoczności | Default params | Stałe domyślne | NAMED VALUES | Typy wyliczeniowe z przypisanymi wartościami numerycznymi | ENUMERATORS | Standardowe enumeratory | CONFIGURATION | Struktury konfiguracyjne | INFORMATION | Struktury informacyjne/statusowe | Conditional API calls | Bloki wywołań warunkowanych na feature-flags sprzętu |
3. Typy danych
3.1. Named Values (enumeratory z wartościami)
CAREL STone pozwala na definiowanie enumeratorów z jawnie przypisanymi wartościami numerycznymi. Składnia:
Kod: text
Przykład — wybór pompy:
Kod: text
Przykład — stany maszyny sterującej:
Kod: text
Kluczowe cechy:
• Typ bazowy (USINT, UINT, DINT) determinuje rozmiar w pamięci — optymalizacja zasobów PLC
• Wartości nie muszą być ciągłe (np. PUMP_STATE ma luki: 12→18, 21→100)
• Modyfikator INTERNAL ogranicza widoczność: TYPE INTERNAL — typ niedostępny spoza biblioteki
• Dostęp przez kwalifikator: PUMP_STATE#Regulation, PUMP_SEL#Pump1
3.2. Enumeratory standardowe
Bez jawnych wartości — kompilator przypisuje je automatycznie od 0:
Kod: text
Cytat:Zwróć uwagę na przecinek przed DOL_Pump wewnątrz {IF DEF} — jest to konieczne, aby zachować poprawną składnię niezależnie od stanu kompilacji warunkowej.
3.3. Struktury ("STRUCT")
Struktury grupują powiązane dane. W praktyce stosuję intensywne zagnieżdżanie struktur:
Kod: text
Konwencje struktur:
• Każde pole ma wartość domyślną (:= ...)
• Zakresy definiowane wprost: UINT(0..100), UINT(0..18000)
• Zagnieżdżanie jako forma kompozycji — hierarchia danych
3.4. Tablice typowane
Kod: text
3.5. Atrybuty i metadane pól
CAREL STone wspiera atrybuty przypisywane do pól i metod:
Kod: text
| Atrybut | Znaczenie | {ATTRIBUTE UOM ...} | Jednostka miary (CELSIUS, BAR, PERCENT, AMPERE, OHM, RPM, SECOND, MINUTE, HERTZ, CUBICMETERPERHOUR) | {METADATA MIN_VAL ...} | Minimalna wartość parametru | {METADATA MAX_VAL ...} | Maksymalna wartość parametru |
Te atrybuty mogą być odczytywane przez narzędzia IDE (IntelliSense) i systemy supervisory.
3.6. Stałe globalne "VAR_GLOBAL CONSTANT"
Kod: text
Konwencje:
• INTERNAL — stałe widoczne tylko wewnątrz biblioteki
• Nazwy: UPPER_SNAKE_CASE z prefiksem tematycznym (np. DEFAULT_)
• Wartości domyślne są źródłem prawdy dla parametrów fabrycznych
4. Interfejsy ("INTERFACE")
4.1. Definicja interfejsu
Interfejs definiuje kontrakt — zestaw metod, które klasa musi zaimplementować:
Kod: text
4.2. Dziedziczenie interfejsów ("EXTENDS")
Interfejsy mogą rozszerzać inne interfejsy:
Kod: text
Konwencje interfejsów:
| Reguła | Przykład | Prefix I w nazwie | IPump, IVariableSpeedPump | Brak ciał metod | Tylko sygnatury | Grupowanie w regiony | {REGION Setters}, {REGION Getters} | Atrybuty na metodach | {ATTRIBUTE UOM BAR} przed METHOD | VAR_IN_OUT CONSTANT dla struktur | Przekazanie przez referencję bez kopii, read-only |
4.3. Wzorzec "VAR_IN_OUT CONSTANT"
Kluczowy wzorzec optymalizacji pamięci PLC:
Kod: text
• VAR_IN_OUT — przekazanie przez referencję (brak kopiowania dużych struktur)
• CONSTANT — zabezpieczenie przed modyfikacją wewnątrz metody
• Idealny do przekazywania dużych struktur konfiguracyjnych do setterów
5. Klasy ("CLASS")
5.1. Hierarchia klas
Kod: text
5.2. Klasa abstrakcyjna — "BasePump"
Kod: text
Kluczowe wzorce:
• ABSTRACT — nie można tworzyć instancji bezpośrednio
• INTERNAL — klasa widoczna tylko wewnątrz biblioteki
• IMPLEMENTS IPump — implementacja kontraktu interfejsu
• METHOD ABSTRACT PROTECTED OnRun — metoda abstrakcyjna, punkt rozszerzenia (Template Method Pattern)
• THIS. — jawne odwołanie do członków instancji
5.3. Modyfikatory widoczności zmiennych
Kod: text
| Modyfikator | Zakres widoczności | PRIVATE | Tylko w danej klasie | PROTECTED | W klasie i jej podklasach | PUBLIC | Wszędzie (domyślny dla metod interfejsu) | INTERNAL | Wewnątrz biblioteki |
Konwencja: zmienne instancji FB i referencje → PRIVATE; struktury współdzielone z podklasami → PROTECTED.
5.4. Klasa pośrednia — "BaseVariableSpeedPump"
Kod: text
Wzorce:
• EXTENDS BasePump IMPLEMENTS IVariableSpeedPump — dziedziczenie po jednej klasie + implementacja interfejsu
• OVERRIDE — nadpisanie metody klasy bazowej
• SUPER.Reset() — wywołanie implementacji rodzica
5.5. Klasa finalna — "VFD_Pump"
Kod: text
Kluczowe cechy klas finalnych:
• FINAL — klasa nie może być dalej dziedziczona
• PUBLIC — widoczna dla użytkowników biblioteki
• Implementuje OnRun() — konkretna logika sterowania
• OVERRIDE PROTECTED ApplyConfiguration — rozszerza bazową konfigurację, zawsze wywołuje SUPER.ApplyConfiguration()
6. Wzorce projektowe (Design Patterns)
6.1. Template Method Pattern
Najważniejszy wzorzec, który stosuję w bibliotekach sterowania. Klasa bazowa BasePump.Run() definiuje szkielet algorytmu, a klasy pochodne dostarczają implementację OnRun():
Kod: text
Każda klasa finalna implementuje OnRun() inaczej:
| Klasa | Logika [b]OnRun()[/b] | VFD_Pump | WriteMotorData → Driver(analog out) → ReadMotorData → CanGo | SoftStartPump | WriteMotorData → Driver(soft-start) → ReadMotorData → CanGo | VFD_PumpModbus | WriteMotorData → Modbus calls (WriteSpeed, ReadFeedback...) → ReadMotorData → Error check | ExternalController | Synchronizacja ze standalone-inverterem przez REF_TO T_MOTOR_DATA | DOL_Pump | WriteMotorData → ReadMotorData → CanGo (direct on/off, no speed regulation) |
6.2. Wzorzec detekcji zmiany konfiguracji
Każda klasa z konfiguracją implementuje wzorzec wykrywania zmian:
Kod: text
Logika: flaga IsConfigChanged jest akumulowana (OR) — raz ustawiona, nie zostanie zresetowana aż do Reset(). Sprawdzana w Run() → jeśli TRUE, wykonuje Reset() i RETURN.
6.3. Wzorzec domyślnej konfiguracji przez zmienną lokalną
Kod: text
Zmienna lokalna CfgDefault jest inicjalizowana wartościami domyślnymi ze struktury CFG_VFD_MOTOR. Nie trzeba ich ustawiać ręcznie — wystarczy przypisać świeżą instancję.
6.4. Wzorzec referencji dla dużych struktur
Kod: text
Zasada: duże struktury konfiguracyjne są przechowywane jako REF_TO — dane pozostają w jednym miejscu, klasa trzyma tylko wskaźnik. Walidacja NULL przed użyciem:
Kod: text
6.5. Wzorzec Getter/Setter
Konsekwentny wzorzec Get/Set:
Kod: text
Kluczowe: wartość zwracana przez przypisanie do nazwy metody: GetXxx := wartość;
6.6. Przeciążanie metod (Method Overloading)
CAREL STone wspiera przeciążanie — ta sama nazwa metody, różne sygnatury:
Kod: text
Oraz przeciążanie z różnymi typami parametrów (na przykładzie obsługi błędów):
Kod: text
7. Modyfikatory klas i metod
7.1. Modyfikatory klas
| Modyfikator | Znaczenie | Przykład | ABSTRACT | Nie można instancjonować; wymaga podklas | BasePump, BaseVariableSpeedPump | FINAL | Nie można dziedziczyć | VFD_Pump, SoftStartPump, DOL_Pump | INTERNAL | Widoczna tylko wewnątrz biblioteki | BasePump, BaseVariableSpeedPump | PUBLIC | Widoczna dla użytkowników biblioteki | VFD_Pump, ExternalController |
Stosowane kombinacje:
| Deklaracja | Rola | CLASS ABSTRACT INTERNAL | Klasa bazowa, ukryta przed użytkownikiem | CLASS FINAL PUBLIC | Klasa końcowa, API publiczne |
7.2. Modyfikatory metod
| Modyfikator | Znaczenie | ABSTRACT | Brak ciała — musi być zaimplementowana w podklasie | OVERRIDE | Nadpisuje metodę klasy bazowej | PUBLIC | Dostępna z zewnątrz | PROTECTED | Dostępna w klasie i podklasach | PRIVATE | Dostępna tylko w danej klasie |
Przykład łańcucha override:
Kod: text
8. Funkcje ("FUNCTION")
Funkcje są samodzielne, bezstanowe i operują na danych wejściowych:
Kod: text
Cechy:
• ARRAY [*] — tablica o dynamicznym rozmiarze (variable-length array)
• LOWER_BOUND() / UPPER_BOUND() — intrinsics do granic tablicy
• Wartość zwracana przez przypisanie do nazwy funkcji: ValidateModbusResults.ID := ...
• Dostęp do pól zwracanej struktury przez . na nazwie funkcji
• RETURN jako early exit
Kolejny przykład — sprawdzanie kompatybilności sprzętowej:
Kod: text
9. Komentarze i dokumentacja
9.1. Typy komentarzy
| Składnia | Cel | Widoczność | (*komentarz*) | Komentarz blokowy standardowy | Kod | (**komentarz*) | Komentarz dokumentacyjny (IntelliSense) | IDE + podpowiedzi | //komentarz | Komentarz liniowy | Kod |
9.2. Komentarze dokumentacyjne "(**...*)"
Każdy publiczny element powinien mieć komentarz (**...*) bezpośrednio przed deklaracją:
Kod: text
Kod: text
Kod: text
Konwencje dokumentacji:
• Klasy: opis roli i odpowiedzialności (multi-line, z * na początku kontynuacji)
• Metody: jeden wiersz opisujący akcję (zaczyna się od czasownika: Returns, Sets, Executes, Resets)
• Zmienne: krótki opis przeznaczenia
• Parametry VAR_INPUT / VAR_IN_OUT: opis każdego parametru
9.3. Komentarze operacyjne "(*...*)"
Wewnątrz ciał metod — wyjaśniają logikę:
Kod: text
10. Konwencje nazewnictwa
10.1. Podsumowanie
| Element | Konwencja | Przykłady | Interfejs | I + PascalCase | IPump, IVariableSpeedPump | Klasa bazowa | Base + PascalCase | BasePump, BaseVariableSpeedPump | Klasa finalna | PascalCase (bez prefixu) | VFD_Pump, SoftStartPump, ExternalController | Metoda publiczna | Get/Set + PascalCase | GetMotorSpeed, SetRegParams | Metoda chroniona | PascalCase | OnRun, ApplyConfiguration, UpdateAlarmStatus | Metoda prywatna | PascalCase | Driver, CoreProcessingPhase | Zmienna lokalna | PascalCase | CfgDefault, InternalMotorType, ErrCode | Zmienna instancji | PascalCase | PumpSel, IsConfigChanged, ReadyToCmd | Stała globalna | UPPER_SNAKE_CASE | DEFAULT_MAX_SPEED, TARGET_CONTROLLER_ID | Typ (struct) | UPPER_SNAKE_CASE | REG_PARAMS, CFG_VFD_MOTOR, PRESSURE_PROTECTION | Enumerator | UPPER_SNAKE_CASE | MOTOR_TYPE, REG_TYPE, MOTOR_CFG_VARIANT | Wartość enumeratora | PascalCase | VFD, ExternalController, Factory | Named value | PascalCase | Pump1, EmergencyStop, Regulation | Prefiks Bool | Is/En/Al | IsConfigChanged, En_Backup, Al_Present | Referencja | Ref + nazwa | RefRegParams, RefAdvRegParams |
10.2. Separatory w nazwach
W środowisku CAREL STone stosuję podkreślnik _ jako separator w złożonych nazwach technicznych:
• PumpSel — Pump Selection
• PID_Params — PID Parameters
• HighPress_Ti — High Pressure Integral Time
• AlarmStatus — Alarm Status
• IsConfigChanged — z prefixem Is
11. Wzorce konwersji typów
CAREL STone wymaga jawnych konwersji typów za pomocą funkcji TO_xxx:
Kod: text
12. Obsługa błędów
12.1. Wzorzec sprawdzania wyników komunikacji
Kod: text
12.2. Wzorzec kaskadowej walidacji
Kod: text
Wzorzec: seria kroków z RETURN po każdym potencjalnym błędzie — fail-fast bez zagnieżdżonych IF-ów.
12.3. Warunkowe wywołania API
Kod: text
Zasada: sprawdzaj feature-flags drivera/falownika przed wywołaniem opcjonalnych poleceń — dzięki temu kod działa z różnymi modelami i wersjami firmware.
13. Podsumowanie najlepszych praktyk
Architektura
1. Jeden plik = jeden element (klasa, interfejs, funkcja)
2. Interfejsy jako kontrakty — definiuj INTERFACE przed implementacją
3. Hierarchia: abstrakcyjna baza → klasy finalne — użytkownik widzi tylko FINAL PUBLIC
4. Ukrywaj implementację — klasy bazowe jako INTERNAL
5. Template Method Pattern — METHOD ABSTRACT PROTECTED OnRun jako metoda abstrakcyjna i punkt rozszerzenia
Pamięć i wydajność
6. VAR_IN_OUT CONSTANT dla dużych struktur przekazywanych do metod
7. REF_TO do przechowywania referencji — unikaj kopiowania
8. Najmniejszy typ bazowy — USINT zamiast UINT gdy wystarczy zakres 0-255
9. Zakresy typów — UINT(0..100) zamiast gołego UINT
Konwencje
10. Prefiksy: I (interfejs), Base (klasa abstrakcyjna), Get/Set (metody), Is/En (bool)
11. Komentarze (**...*) (IntelliSense) na każdym publicznym elemencie
12. {REGION} do organizacji kodu w IDE
13. Kompilacja warunkowa {IF DEF} do obsługi wariantów sprzętowych
14. Jawne konwersje typów — TO_BYTE(), TO_REAL(), TO_USINT()
Bezpieczeństwo
15. Walidacja NULL przed dereferencją REF_TO
16. Fail-fast z RETURN zamiast głębokiego zagnieżdżania
17. Detekcja zmiany konfiguracji — flaga IsConfigChanged + Reset()
18. Dzielenie przez zero — sprawdzenie <> 0 przed dzieleniem
Cytat:Autor: Hubert Lepiarczyk a.k.a lunakk | Artykuł oparty na doświadczeniu z produkcyjnymi bibliotekami w środowisku CAREL STone.
Artykuł pochodzi z bazy wiedzy IceLAB: https://icelab.pl/pl/portal/stone-oop-conventions/
Fajne? Ranking DIY