Postępująca transformacja cyfrowa dotyka wiele dziedzin życia oraz gospodarki. Nie omija to również przemysłu poprzez realizację założeń czwartej rewolucji przemysłowej, opierającej się na głębokiej integracji systemów sterowania oraz nadzoru z zaawansowanymi systemami informatycznymi.
Mają one za zadanie podnosić wskaźniki wydajności, jakości oraz bezpieczeństwa prowadzonych procesów produkcyjnych. Pomimo sporych inwestycji przeznaczanych przez przedsiębiorstwa na modernizację aparatury sterująco-kontrolnej w obiegu nadal funkcjonują urządzenia nieprzystosowane do funkcjonowania w aktualnych systemach zarządzania obiektem sterowania. W związku z tym, częstym problemem podczas realizacji idei przemysłu 4.0 jest również starzejąca się infrastruktura sieciowa. Istnieje natomiast możliwość obniżenia kosztów takiej transformacji poprzez wykorzystanie zewnętrznych bibliotek wraz z REST API. W niniejszym przykładzie opisana zostanie implementacja REST API z wykorzystaniem sieci polowej MODBUS TCP. W przykładzie wykorzystano środowisko CoDeSys oraz REST API napisane z wykorzystaniem .NET WEB API w języku C#.
MODBUS TCP jest wersją otwartego protokołu MODBUS wykorzystującego ethernet jako medium komunikacyjne zarówno przewodowo jak i bezprzewodowo. Wykorzystuje on w komunikacji port 502. Działa on w architekturze klient-serwer (master-slave). W przeciwieństwie do MODBUS RTU daje to przewagę w możliwości zawarcia wielu klientów, aniżeli jednego w zakresie sieci.
PLC + MODBUS
Schemat obiektu wygląda następująco:
Na wstępie przygotowujemy strukturę projektu PLC wraz z przygotowaniem infrastruktury sieciowej dla MODBUS TCP. Device Ethernet konfigurujemy zgodnie z aktualną dla nas siecią Ethernet.
Kolejnym krokiem jest przygotowanie mapowania zmiennych sieciowych do wykorzystania w programie.
Należy koniecznie pamiętać o ustawieniu odpowiedniego Taska dla działania naszej sieci polowej. W przykładzie jest to główny Task wywoływany cyklicznie.
Na tym etapie przygotowany projekt PLC jest gotowy do pracy w trybie slave (server), do którego będą przysyłane zapytania o dane.
REST API + NMODBUS
Kolejnym etapem jest przygotowanie REST API w środowisku .NET. W projekcie wykorzystano bibliotekę NModbus, która pozwala na dostęp do urządzeń wspierających sieć polową MODBUS zarówno w wersji RTU jak i TCP. Na wstępie wymagana jest konfiguracja klienta sieci MODBUS – w tym przypadku master’a.
public bool Connect()
{
if (_client == null)
return false;
var retriesTimeSpan = Backoff.LinearBackoff(TimeSpan.FromSeconds(1), 5);
var policy = Policy.Handle<SocketException>().WaitAndRetry(retriesTimeSpan);
var connectionResult = policy.ExecuteAndCapture(() => _client.Connect(_hostname, _port));
if (connectionResult.Outcome == OutcomeType.Failure)
{
var validationResult = new TcpClientValidationResult
{
HasErrors = true,
ExceptionType = connectionResult.FinalException.InnerException
};
validationResult.ErrorMessage = validationResult.GetErrorMessageByExceptionType();
ValidationResult = validationResult;
}
else
ValidationResult = new TcpClientValidationResult();
return _client.Connected;
}
Każdy endpoint wysyła zapytanie do service’ów, które to poprzez Gateway mają dostęp do sieci MODBUS.
[HttpGet("feedMotorSpeed")]
public async Task<IActionResult> GetFeddMotorSpeed()
{
var serviceResponse = await _packagingCellService.GetFeedMotorSpeed();
var actionResponse =_actionResponse.GetResponseFromService(serviceResponse);
return serviceResponse.ValidationResult.HasErrors ? BadRequest(actionResponse) : Ok(actionResponse);
}
public async Task<IServiceResponse<ushort>> GetFeedMotorSpeed()
{
var targetAddresses = _addressProvider.GetAddress(CompressorPackagingCellAddresses.FEED_MOTOR_SPEED);
if (HasError(_addressProvider.ValidationResult))
{
SetResponseError(_addressProvider.ValidationResult);
return GetResponse(_packagingCell.Model.FeedMotorSpeed);
}
_packagingCell.Model.FeedMotorSpeed = await _modbusGateway.Reader.ReadNumberValue(targetAddresses);
if (HasError(_modbusGateway.Reader.ValidationResult))
SetResponseError(_modbusGateway.Reader.ValidationResult);
return GetResponse(_packagingCell.Model.FeedMotorSpeed);
}
Adresy poszczególnych rejestrów sieci MODBUS są przechowywane w stałych. Walidacja następuje przy każdym ich wykorzystaniu.
public int GetAddress(string address)
{
var addressModel = _addressParser.Parse(address);
var addressValidationResult = _validator.Validate(addressModel);
if (!addressValidationResult.HasErrors)
_address = addressModel.ByteAddress + addressModel.BitAddress;
ValidationResult = addressValidationResult;
return _address;
}
Biblioteka Polly zajmuje się realizacją podejmowania prób ponownego połączenia oraz ponownego odczytu danych z sieci polowej. Dzięki niej łatwo można skonfigurować politykę obsługi wyjątków oraz podejmowania ponownych prób wykonania akcji.
public async Task<ushort> ReadNumberValue(int address)
{
var response = 0;
using (var client = _client.GetClient())
{
var timeSpan = Backoff.LinearBackoff(TimeSpan.FromSeconds(1), 4);
var modbusMasterPolicy = Policy.Handle<Exception>().WaitAndRetry(timeSpan);
var policyExecutionResult = modbusMasterPolicy.ExecuteAndCapture(() => SetModbusMaster(client));
if(policyExecutionResult.Outcome == OutcomeType.Failure)
{
var validationResult = new ModbusReadingValidationResult();
validationResult.GetFromSlaveException(policyExecutionResult.FinalException, (int)EModbusTransportErrorCode.ConnectionError);
ValidationResult = validationResult;
return (ushort)response;
}
var modbusReadingPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(timeSpan);
var modbusReadingPolicyResult = modbusReadingPolicy.ExecuteAndCaptureAsync(async () =>
await _modbusMaster.ReadInputRegistersAsync(0, (ushort)address, 1).ConfigureAwait(false));
if(modbusReadingPolicyResult.Result.Outcome == OutcomeType.Failure)
{
var validationResult = new ModbusReadingValidationResult();
validationResult.GetFromSlaveException(modbusReadingPolicyResult.Result.FinalException,
(int)EModbusTransportErrorCode.ReadingError);
ValidationResult = validationResult;
return (ushort)response;
}
response = modbusReadingPolicyResult.Result.Result[0];
}
return (ushort)response;
}
Następnie przechwycona odpowiedź lub błąd wynikający z niepowodzenia operacji przekazywane są do metody formatującej odpowiedź końcową.
public ServiceResponseBase GetResponseFromService<T>(IServiceResponse<T> serviceResponse)
{
var responseModel = new ModbusActionResponseModel
{
ResponseMessage = serviceResponse.ValidationResult.HasErrors ?
SetErrorMessage(serviceResponse.ValidationResult) :
SetPayloadMessage(serviceResponse.Payload),
};
responseModel.ResponseMessage.ResponseIssued = DateTime.Now;
return responseModel.ResponseMessage;
}
Te same kroki odpowiadają również ustawianiu rejestrów MODBUS. W tym celu należy wykorzystać inne (podać jakie) metody przygotowane w ramach biblioteki NModbus. Metoda „WriteSingleRegister” wpisuje wartość do całego rejestru – jest nią najczęściej wartość liczbowa. Natomiast metoda „WriteSingleCoil” pozwala na zmianę stanu pojedynczego bitu w rejestrze.
void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value);
void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);
Jak widać, niewątpliwą zaletą takiego rozwiązania jest jego niski koszt implementacji. O ile sama sieć MODBUS nie jest przystosowana do transmisji dużych pakietów danych (253 bajty), o tyle przy wymianie niewielkich ilości informacji jest w stanie zaspokoić potrzeby monitorowania podstawowych lub wrażliwych danych o obiekcie. Otwiera to drogę do archiwizowania i wizualizowania danych z obiektu oraz możliwość zadawania sygnałów sterujących zdalnie. Możliwości opisanej wyżej konfiguracji mogą również służyć do prototypowania różnych rozwiązań przemysłowych – od niewielkich instalacji sterowania i nadzoru do systemów archiwizacji oraz agregacji i prezentacji danych w systemach informatycznych. Istotnym elementem jest również zabezpieczanie takiej instalacji przed niepożądanym dostępem. W takim przypadku może on nie tylko narazić obiekt na utratę danych, ale również doprowadzić obiekt do stanu niebezpiecznego.
Zostaw komentarz