strolch-plc/README.md

275 lines
14 KiB
Markdown
Raw Permalink Normal View History

2020-03-31 08:40:08 +02:00
# Strolch as a software PLC
[![Contributors](https://img.shields.io/github/contributors/strolch-li/strolch-plc)](https://github.com/strolch-li/strolch-plc/graphs/contributors)
[![License](https://img.shields.io/github/license/strolch-li/strolch-plc)](https://github.com/strolch-li/strolch-plc/blob/master/LICENSE)
2020-03-31 08:40:08 +02:00
A soft real time PLC written in Strolch running on Strolch
2020-03-30 22:43:14 +02:00
2023-07-19 12:48:08 +02:00
Checkout the documentation at https://strolch.li/plc/
2020-03-31 08:40:08 +02:00
## Features
Strolch PLC supports the following features:
* Notification model
* Raspberry Pi GPIO Input and Output addresses
* I2C Input and Output addresses over PCF8574 chips
* DataLogic Scanner connection
* Virtual addresses
* WebUI to observer and manipulate the addresses
* WebSocket connection to Strolch Agent for notifying of changes
* Simple two key addressing of hardware addresses to store semantics, e.g. `Convey01 - MotorOn`, instead of `i2cInput.dev01.0.0`
## PlcService
PlcServices are sub classes of `PlcService`. You can register for notifications, send keys and delay actions:
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
import li.strolch.plc.model.PlcAddress;
public class ConveyorPlcService extends PlcService {
public static final int BOX_TRANSFER_DURATION = 30;
private static final String R_CONVEYOR_01 = "Conveyor01";
private static final String A_START_BUTTON = "StartButton";
private static final String T_MOTOR_ON = "MotorOn";
private static final String T_MOTOR_OFF = "MotorOff";
private static final String A_BOX_DETECTED = "BoxDetected";
private boolean motorOn;
private ScheduledFuture<?> motorStopTask;
public ConveyorPlcService(PlcHandler plcHandler) {
super(plcHandler);
}
@Override
public void handleNotification(PlcAddress address, Object value) {
String action = address.action;
boolean active = (boolean) value;
if (action.equals(A_START_BUTTON)) {
if (active) {
logger.info("Start button pressed. Starting motors...");
send(R_CONVEYOR_01, T_MOTOR_ON);
this.motorOn = true;
scheduleStopTask();
}
} else if (action.equals(A_BOX_DETECTED)) {
if (active && this.motorOn) {
logger.info("Container detected, refreshing stop task...");
scheduleStopTask();
}
} else {
logger.info("Unhandled notification " + address.toKeyAddress());
}
}
private void scheduleStopTask() {
if (this.motorStopTask != null)
this.motorStopTask.cancel(false);
this.motorStopTask = schedule(this::stopMotor, BOX_TRANSFER_DURATION, TimeUnit.SECONDS);
}
private void stopMotor() {
send(R_CONVEYOR_01, T_MOTOR_OFF);
}
@Override
public void register() {
this.plcHandler.register(R_CONVEYOR_01, A_START_BUTTON, this);
this.plcHandler.register(R_CONVEYOR_01, A_BOX_DETECTED, this);
super.register();
}
@Override
public void unregister() {
this.plcHandler.unregister(R_CONVEYOR_01, A_START_BUTTON, this);
this.plcHandler.unregister(R_CONVEYOR_01, A_BOX_DETECTED, this);
super.unregister();
}
This example registered for changes to the keys `Conveyor01 - StartButton` and
`Conveyor01 - BoxDetected`. When the start button is pressed, then it sends the
keys `Conveyor01 - MotorOn` and schedules the motor off action with a delay of
30s.
This class should then be registered in the `PlcServiceInitializer`:
public class CustomPlcServiceInitializer extends PlcServiceInitializer {
public CustomPlcServiceInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
protected List<PlcService> getPlcServices(PlcHandler plcHandler) {
ArrayList<PlcService> plcServices = new ArrayList<>();
StartupPlcService startupPlcService = new StartupPlcService(plcHandler);
ConveyorPlcService conveyorPlcService = new ConveyorPlcService(plcHandler);
plcServices.add(conveyorPlcService);
plcServices.add(startupPlcService);
return plcServices;
}
As one can see, there is little fuss for writing business logic, state is
persisted automatically so it is visible in the UI, and the PlcServices can
handle their state as needed.
2020-03-31 08:40:08 +02:00
## PlcAddress
PlcAddresses store the value of a hardware address:
<Resource Id="A_Conveyor01-Occupied" Name="Conveyor01 - Occupied" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Index="5" Value="Conveyor 1"/>
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Index="10" Value="i2cInput.dev01.0.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Index="20" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Index="30" Value="Occupied"/>
<Parameter Id="index" Name="Index" Type="Integer" Index="40" Value="10"/>
<Parameter Id="value" Name="Value" Type="Boolean" Index="100" Value="false"/>
</ParameterBag>
</Resource>
The two parameters `resource` and `action` are the local keys for the address,
while `address` is used to find the actual connection and the address on that
connection.
## PlcTelegram
PlcTelegrams are used to store keys with a default value. For example it is
easier to understand `Conveyor01 - MotorOn` instead of `Conveyor01 - Motor` with the value true or false.
<Resource Id="T_Conveyor01-MotorOn" Name="Conveyor01 - MotorOn" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Index="5" Value="Conveyor 1"/>
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Index="10" Value="i2cOutput.dev01.0.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Index="20" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Index="30" Value="MotorOn"/>
<Parameter Id="index" Name="Index" Type="Integer" Index="40" Value="10"/>
<Parameter Id="value" Name="Value" Type="Boolean" Index="100" Value="true"/>
</ParameterBag>
</Resource>
## PlcLogicalDevice
Multiple PlcAddresses can be grouped together into a PlcLogicalDevice for
visualization on the UI. In logistics a single conveyor might have multiple
sensors and actors, e.g. the conveyor's motor, a light barrier and a scanner,
grouping them together makes it easier for debugging.
<Resource Id="D_MaterialFlow" Name="MaterialFlow" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Index="5" Value="Material Flow"/>
<Parameter Id="group" Name="Group" Type="String" Index="20" Value="01 Material Flow"/>
<Parameter Id="index" Name="Index" Type="Integer" Index="30" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress" Index="10" Value="A_Conveyor01-Occupied, A_Conveyor02-Occupied, A_Conveyor03-Occupied, A_Conveyor04-Occupied, A_Conveyor01-MotorOn, A_Conveyor02-MotorOn, A_Conveyor03-MotorOn, A_Conveyor04-MotorOn, A_Conveyor03-Barcode, A_Conveyor03-On"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram" Index="20" Value="T_Conveyor01-MotorOn, T_Conveyor01-MotorOff, T_Conveyor02-MotorOn, T_Conveyor02-MotorOff, T_Conveyor03-MotorOn, T_Conveyor03-MotorOff, T_Conveyor04-MotorOn, T_Conveyor04-MotorOff, T_Conveyor03-On, T_Conveyor03-Off"/>
</ParameterBag>
</Resource>
## PlcConnections
PlcConnections are used to model the actual hardware connections and define with
which class the connection is to be established. The following shows some of the implementations:
<!--
Raspberry GPIO BCM Address connection
-->
<Resource Id="raspiBcmGpioOutput" Name="Raspi BCM GPIO Output" Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String" Value="li.strolch.plc.core.hw.gpio.RaspiBcmGpioOutputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String" Interpretation="Enumeration" Uom="ConnectionState" Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String" Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="false"/>
<Parameter Id="bcmOutputPins" Name="BCM Output Pins" Type="IntegerList" Value="27"/>
</ParameterBag>
</Resource>
<Resource Id="raspiBcmGpioInput" Name="Raspi BCM GPIO Input" Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String" Value="li.strolch.plc.core.hw.gpio.RaspiBcmGpioInputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String" Interpretation="Enumeration" Uom="ConnectionState" Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String" Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="bcmInputPins" Name="BCM Input Pins" Type="IntegerList" Value="4"/>
</ParameterBag>
</Resource>
<!--
I2C input connections
-->
<Resource Id="i2cInput.dev01" Name="PCF8574 Input 0x38" Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String" Value="li.strolch.plc.core.hw.i2c.PCF8574InputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String" Interpretation="Enumeration" Uom="ConnectionState" Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String" Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="i2cBus" Name="I2C Bus" Type="Integer" Value="1"/>
<Parameter Id="addresses" Name="Addresses" Type="IntegerList" Value="0x38"/>
<Parameter Id="verbose" Name="Verbose" Type="Boolean" Value="true"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="interruptPinPullResistance" Name="Raspi Interrupt PinPullResistance" Type="String" Value="PULL_DOWN"/>
<Parameter Id="interruptChangeState" Name="Raspi Interrupt Change State" Type="String" Value="HIGH"/>
<Parameter Id="interruptBcmPinAddress" Name="Raspi BCM Interrupt Pin" Type="Integer" Value="17"/>
</ParameterBag>
</Resource>
<!--
I2C output connections
-->
<Resource Id="i2cOutput.dev01" Name="PCF8574 Output 0x21" Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String" Value="li.strolch.plc.core.hw.i2c.PCF8574OutputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String" Interpretation="Enumeration" Uom="ConnectionState" Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String" Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="i2cBus" Name="I2C Bus" Type="Integer" Value="1"/>
<Parameter Id="addresses" Name="Addresses" Type="IntegerList" Value="0x21"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="false"/>
</ParameterBag>
</Resource>
<!--
Barcode reader connection, currently place holder with RandomString
-->
<Resource Id="dataLogicScanner" Name="DataLogic Scanner Connection" Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String" Value="li.strolch.plc.core.hw.connections.DataLogicScannerConnection"/>
<Parameter Id="address" Name="Scanner IP Address" Type="String" Value="192.168.1.249:51236"/>
<Parameter Id="readTimeout" Name="Read Timeout (s)" Type="Integer" Value="60"/>
<Parameter Id="state" Name="Connection State" Type="String" Interpretation="Enumeration" Uom="ConnectionState" Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String" Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
</ParameterBag>
</Resource>
## Virtual Addresses
In some cases, especially in conjunction with a Strolch Agent as the main
server, it is necessary to also have virtual addresses, with which to perform
notifications. The following shows examples:
<Resource Id="addrPlcConnected" Name="PLC - Connected" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="virtualString.plcServerConnected"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ServerConnected"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="String"/>
<Parameter Id="value" Name="Value" Type="String" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="5"/>
</ParameterBag>
</Resource>
2020-03-31 08:45:17 +02:00
## More Information
Find more to Strolch PLC at our website: https://strolch.li/plc.html