Centrala Sinum obsługuje komunikację w standardzie Modbus w dwóch trybach pracy:
Master – centrala komunikuje się z urządzeniami podrzędnymi, takimi jak falowniki, liczniki energii lub inne urządzenia automatyki.
Slave – urządzenie nadrzędne (np. system BMS) może odczytywać dane z centrali Sinum.
Centrala może pracować jednocześnie w obu trybach, z zastrzeżeniem, że nie jest możliwe uruchomienie obu trybów na tym samym extenderze.
Domyślnie tryb Modbus Slave jest aktywny na interfejsie TCP, na porcie 502.
Dodatkowo, tryb Slave można włączyć w komunikacji RTU – na extenderze Modbus lub na wbudowanym porcie Modbus centrali.
Należy jednak pamiętać, że dany port może pracować wyłącznie w jednym trybie – Master lub Slave (nigdy jednocześnie w obu).
Centrala Sinum nie posiada stałej listy rejestrów Modbus. Obsługa rejestrów realizowana jest poprzez skrypty Lua, co umożliwia pełne dostosowanie dostępnych rozkazów i struktur danych do wymagań systemu nadrzędnego lub klienta.
Centrala Sinum obsługuje komunikację jako Modbus slave za pomocą dwóch interfejsów:
Modbus TCP na porcie 502/tcp,
Modbus RTU, przed odpowiednio skofigurowany:
ekstender EX-MODBUS z oprogramowaniem w wersji co najmniej 1.4.2
Centrala nie rozróżnia żądań pochodzących z Modbus TCP i Modbus RTU.
Transceiver lub ekstender muszą mieć aktywny tryb slave, aby móc przyjmować rozkazy Modbus. Otwórz ustawienia centrali Sinum i przejdź do zakładki urządzenia, a następnie moduły systemowe. Otwórz ustawienia wybranego modułu.
Z listy wybierz tryb slave:
Ustaw parametry komunikacji i naciśnij zapisz.
Zostanie aktywowany tryb slave, a na urządzeniu pojawi się oznaczenie.
Utwórz nową automatyzację typu kod. W edytorze wpisz skrypt:
local slaveId = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
-- ustaw dane dla odpowiedzi
res:data(8)
end)
Zapisz automatyzację. Centrala Sinum będzie odpowiadać na żądanie odczytu rejestru input o dowolnym adresie wartością 8.
Zauważ, że jeśli wyślesz żądanie odczytu więcej niż jednego rejestru, centrala odpowie wyjątkiem 04 SERVER DEVICE FAILURE. Przyczyną jest skrypt, który zwraca zbyt mało wartości dla odpowiedzi.
Aby poprawnie obsłużyć żądanie o dowolnej liczbie rejestrów, należy edytować skrypt, aby przekazać odpowiednią liczbę wartości. Można to zrobić w następujący sposób:
local slaveId = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
local data = {}
-- req:size() zwraca ilość rejestrów, która została zażądana
for i = 1, req:size() do
data[i] = i
end
res:data(data)
end)
Zapisz zmiany. Centrala będzie teraz odpowiadać na żądanie odczytu dowolnej liczby rejestrów typu input, wysyłając w odpowiedzi kolejne liczby.
Dzięki temu każde żądanie zostanie poprawnie obsłużone, niezależnie od liczby żądanych rejestrów.
Utwórz nową automatyzację typu kod. W edytorze wpisz poniższy skrypt:
local slaveId = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
local sensor = wtp[req:address()]
local temp = sensor:getValue("temperature")
-- przelicz na kod uzupełnień do dwóch
if temp < 0 then
temp = temp + 65536
end
res:data(temp)
end)
Centrala będzie wysyłać w odpowiedzi temperaturę odczytaną przez czujnik radiowy, którego ID jest równe adresowi rejestru. To przykład minimalny — nie zadziała prawidłowo, gdy:
Zostanie zażądana wartość więcej niż jednego rejestru.
Urządzenie pod wybranym ID nie istnieje.
Urządzenie pod wybranym ID nie jest czujnikiem temperatury.
W przypadku wysłania niepoprawnego adresu (np. odpowiadającego niepoprawnemu ID urządzenia radiowego), centrala może odpowiedzieć wyjątkiem Modbus, sygnalizującym błąd odczytu.
local slaveId = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
local ok, sensor = pcall(function ()
local device = wtp[req:address()]
assert(device:getValue("type") == "temperature_sensor")
return device
end)
if not ok then
res:exception(0x2) -- ILLEGAL DATA ADDRESS
else
res:data(sensor:getValue("temperature"))
end
end)
Obsługę odczytu wielu rejestrów można dodać analogicznie do poprzednich przykładów:
local slaveId = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
local firstId = req:address()
local data = {}
for id = firstId, firstId + req:size() - 1 do
local ok, sensor = pcall(function ()
local device = wtp[id]
assert(device:getValue("type") == "temperature_sensor")
return device
end)
if not ok then
res:exception(0x2) -- ILLEGAL DATA ADDRESS
return
else
table.insert(data, sensor:getValue("temperature"))
end
end
res:data(data)
end)
Zadaną temperaturę, będącą ustawieniem regulatorów, można odczytywać i zmieniać, dlatego odpowiednie będą rejestry typu holding. Odczyt można obsłużyć podobnie jak w przypadku rejestrów typu input:
local slaveId = 1
modbus_slave:onHoldingRegisterRead(slaveId, function (req, res)
local regulator = wtp[req:address()]
res:data(regulator:getValue("target_temperature"))
end)
Obsługę zapisu można dodać następująco:
local slaveId = 1
modbus_slave:onHoldingRegisterRead(slaveId, function (req, res)
local regulator = wtp[req:address()]
res:data(regulator:getValue("target_temperature"))
end)
modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
local regulator = wtp[req:address()]
regulator:setValue("target_temperature", req:data())
res:success()
end)
Metoda setValue zgłosi błąd, gdy wartość będzie niepoprawna (np. wykraczająca poza dozwolony zakres). Można to obsłużyć, stosując konstrukcję pcall, która pozwala bezpiecznie wywołać funkcję i złapać ewentualny błąd. Przykład:
...
modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
local addrOk, regulator = pcall(function ()
local device = wtp[req:address()]
assert(device:getValue("type") == "temperature_regulator")
return device
end)
if not addrOk then
res:exception(0x2) -- ILLEGAL DATA ADDRESS
return
end
local setpointOk = pcall(function ()
regulator:setValue("target_temperature", req:data())
end)
if setpointOk then
res:success()
else
res:exception(0x3) -- ILLEGAL DATA VALUE
end
end)
Podobnie jak przy odczytach, można dodać obsługę zapisu wielu rejestrów jednocześnie, iterując po wartościach i ustawiając je kolejno w odpowiednich regulatorach. Przykładowy kod może wyglądać tak:
...
modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
local firstId = req:address()
local setpoints = req:data()
if req:size() == 1 then
setpoints = { setpoints }
end
for id = firstId, firstId + req:size() - 1 do
local addrOk, regulator = pcall(function ()
local device = wtp[id]
assert(device:getValue("type") == "temperature_regulator")
return device
end)
if not addrOk then
res:exception(0x2) -- ILLEGAL DATA ADDRESS
return
end
local setpointOk = pcall(function ()
regulator:setValue("target_temperature", req:data())
end)
if setpointOk then
res:success()
else
res:exception(0x3) -- ILLEGAL DATA VALUE
return
end
end
end)