[Project] Initial Commit

This commit is contained in:
Robert von Burg 2020-01-27 16:17:00 +01:00
parent 7ec4e140e7
commit 1a19dad296
60 changed files with 4567 additions and 0 deletions

9
.gitignore vendored
View File

@ -21,3 +21,12 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# other
.DS_Store
.settings
.project
.idea/
target/
dependencies/
*.iml

447
example/exampleModel.xml Normal file
View File

@ -0,0 +1,447 @@
<?xml version="1.0" encoding="UTF-8"?>
<StrolchModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd"
xsi:schemaLocation="https://strolch.li/xsd/StrolchModel-1.6.xsd StrolchModel-1.6.xsd">
<!--
Simple logger output connection
-->
<Resource Id="loggerOutput" Name="Logger PLC 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.LoggerOutConnection"/>
<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>
<!--
Simple Boolean connection
-->
<Resource Id="booleanConnection" Name="Single Boolean PLC 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.InMemoryBooleanConnection"/>
<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>
<!--
Barcode reader connection, currently place holder with RandomString
-->
<Resource Id="barcodeReader" Name="Barcode Reader 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.RandomStringConnection"/>
<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>
<!--
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="bcmOutputPins" Name="BCM Output Pins" Type="IntegerList" Value="27"/>
</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="address" Name="Address" Type="String" Value="0x38"/>
<Parameter Id="interruptChangeState" Name="Raspi BCM 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="address" Name="Address" Type="String" Value="0x21"/>
</ParameterBag>
</Resource>
<!--
Simple toggler device
-->
<Resource Id="toggler" Name="Toggler 01" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Entry"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress" Value="addrTogglerOn"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telToggleTogglerOn, telToggleTogglerOff"/>
</ParameterBag>
</Resource>
<Resource Id="addrTogglerOn" Name="Toggler - On" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telToggleTogglerOn" Name="Toggler - On" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telToggleTogglerOff" Name="Toggler - Off" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Off"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<!--
PLC State
-->
<Resource Id="plc" Name="PLC" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Startup"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="999999"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress"
Value="addrPlcStarted"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telPlcStarted, telPlcStopped"/>
</ParameterBag>
</Resource>
<Resource Id="addrPlcStarted" Name="PLC - Started" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Started"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telPlcStarted" Name="PLC - Started" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Started"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telPlcStopped" Name="PLC - Stopped" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Stopped"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<!--
Simple conveyor
-->
<Resource Id="conveyor01" Name="Conveyor 01" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Entry"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress"
Value="addrConveyor01Occupied, addrConveyor01OnState"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telConveyor01On, telConveyor01Off"/>
</ParameterBag>
</Resource>
<Resource Id="addrConveyor01Occupied" Name="Conveyor01 - Occupied" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.7"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Occupied"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="addrConveyor01OnState" Name="Conveyor01 - On" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.7"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<Resource Id="telConveyor01On" Name="Conveyor01 - On" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.7"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telConveyor01Off" Name="Conveyor01 - Off" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.7"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Conveyor01"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Off"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<!--
BoxFillPosition logical device
-->
<Resource Id="BoxFillPosition" Name="Box Fill Position" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Filling"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress"
Value="addrEntryOccupied, addrBoxFillPositionOccupied, addrExitOccupied, addrEntryStopperOpened, addrEntryStopperClosed, addrEntryStopperOpen, addrExitStopperOpened, addrExitStopperClosed, addrExitStopperOpen, addrConveyorRunning, addrBarcodeReader"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telConveyorRun, telConveyorStop, telEntryStopperOpen, telEntryStopperClose, telExitStopperOpen, telExitStopperClose, telReadBarcode"/>
</ParameterBag>
</Resource>
<!-- FillPosition addresses -->
<Resource Id="addrEntryOccupied" Name="BoxFillPosition - EntryOccupied" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryOccupied"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="addrBoxFillPositionOccupied" Name="BoxFillPosition - Occupied" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.1"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Occupied"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<Resource Id="addrExitOccupied" Name="BoxFillPosition - ExitOccupied" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.2"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitOccupied"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="30"/>
</ParameterBag>
</Resource>
<Resource Id="addrEntryStopperOpened" Name="BoxFillPosition - EntryStopperOpened" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.3"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryStopperOpened"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="40"/>
</ParameterBag>
</Resource>
<Resource Id="addrEntryStopperClosed" Name="BoxFillPosition - EntryStopperClosed" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.4"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryStopperClosed"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="50"/>
</ParameterBag>
</Resource>
<Resource Id="addrEntryStopperOpen" Name="BoxFillPosition - EntryStopperOpen" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryStopperOpen"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="60"/>
</ParameterBag>
</Resource>
<Resource Id="addrExitStopperOpened" Name="BoxFillPosition - ExitStopperOpened" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.5"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitStopperOpened"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="70"/>
</ParameterBag>
</Resource>
<Resource Id="addrExitStopperClosed" Name="BoxFillPosition - ExitStopperClosed" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cInput.dev01.6"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitStopperClosed"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="80"/>
</ParameterBag>
</Resource>
<Resource Id="addrExitStopperOpen" Name="BoxFillPosition - ExitStopperOpen" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.1"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitStopperOpen"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="90"/>
</ParameterBag>
</Resource>
<Resource Id="addrConveyorRunning" Name="BoxFillPosition - ConveyorRunning" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.2"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ConveyorRunning"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="100"/>
</ParameterBag>
</Resource>
<Resource Id="addrBarcodeReader" Name="BoxFillPosition - Barcode" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="barcodeReader"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Barcode"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="String"/>
<Parameter Id="value" Name="Value" Type="String" Value=""/>
<Parameter Id="index" Name="Index" Type="Integer" Value="110"/>
</ParameterBag>
</Resource>
<!-- FillPosition telegrams -->
<Resource Id="telConveyorRun" Name="BoxFillPosition - ConveyorRun" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.2"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ConveyorRun"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telConveyorStop" Name="BoxFillPosition - ConveyorStop" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.2"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ConveyorStop"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<Resource Id="telEntryStopperOpen" Name="BoxFillPosition - EntryStopperOpen" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryStopperOpen"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="30"/>
</ParameterBag>
</Resource>
<Resource Id="telEntryStopperClose" Name="BoxFillPosition - EntryStopperClose" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.0"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="EntryStopperClose"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="40"/>
</ParameterBag>
</Resource>
<Resource Id="telExitStopperOpen" Name="BoxFillPosition - ExitStopperOpen" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.1"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitStopperOpen"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="50"/>
</ParameterBag>
</Resource>
<Resource Id="telExitStopperClose" Name="BoxFillPosition - ExitStopperClose" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="i2cOutput.dev01.1"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ExitStopperClose"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="60"/>
</ParameterBag>
</Resource>
<Resource Id="telReadBarcode" Name="BoxFillPosition - ReadBarcode" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="barcodeReader"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BoxFillPosition"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ReadBarcode"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="String"/>
<Parameter Id="value" Name="Value" Type="String" Value="DoRead"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="70"/>
</ParameterBag>
</Resource>
</StrolchModel>

425
pom.xml Normal file
View File

@ -0,0 +1,425 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc</artifactId>
<packaging>pom</packaging>
<name>strolch-plc</name>
<version>0.1.0-SNAPSHOT</version>
<description>Module build to build strolch-plc</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
<buildTimestamp>${maven.build.timestamp}</buildTimestamp>
<jdk.version>1.8</jdk.version>
<!-- compile time dependencies -->
<slf4j.version>1.7.28</slf4j.version>
<logback.version>1.2.3</logback.version>
<jersey.version>2.29</jersey.version>
<gson.version>2.8.5</gson.version>
<annotation.version>1.3.2</annotation.version>
<serverlet.version>4.0.1</serverlet.version>
<jaxrs.api.version>2.1.1</jaxrs.api.version>
<jaxrs.ri.version>2.29</jaxrs.ri.version>
<websocket.version>1.1</websocket.version>
<jaxb.api.version>2.4.0-b180830.0359</jaxb.api.version>
<csv.version>1.7</csv.version>
<tyrus.version>1.13</tyrus.version>
<pi4j.version>1.2</pi4j.version>
<jserialcomm.version>[2.0.0,3.0.0)</jserialcomm.version>
<javafx.version>11.0.2</javafx.version>
<strolch.version>1.6.0-SNAPSHOT</strolch.version>
<!-- test time dependencies -->
<junit.version>4.12</junit.version>
<hamcrest.version>2.1</hamcrest.version>
<!-- maven plug-in dependencies -->
<maven-scm-plugin.version>1.11.2</maven-scm-plugin.version>
<buildnumber-maven-plugin.version>1.4</buildnumber-maven-plugin.version>
<versions-maven-plugin.version>2.7</versions-maven-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-source-plugin.version>3.1.0</maven-source-plugin.version>
<maven-site-plugin.version>3.7.1</maven-site-plugin.version>
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
<maven-jar-plugin.version>3.1.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.3</maven-war-plugin.version>
<tomcat7-maven-plugin.version>2.2</tomcat7-maven-plugin.version>
<maven-javadoc-plugin.version>3.1.0</maven-javadoc-plugin.version>
<maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version>
<maven-resources-plugin.version>3.1.0</maven-resources-plugin.version>
<maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version>
<maven-assembly-plugin.version>3.1.1</maven-assembly-plugin.version>
<maven-project-info-reports-plugin.version>3.0.0</maven-project-info-reports-plugin.version>
<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
<nexus-staging-maven-plugin.version>1.6.8</nexus-staging-maven-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
<archetype-packaging.version>3.1.2</archetype-packaging.version>
</properties>
<modules>
<module>strolch-plc-core</module>
<module>strolch-plc-rest</module>
</modules>
<developers>
<developer>
<id>eitch</id>
<name>Robert von Burg</name>
<email>robert.vonburg@4trees.ch</email>
<url>http://4trees.ch</url>
<organization>4trees</organization>
<organizationUrl>http://4trees.ch</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>+1</timezone>
<properties>
<picUrl>http://localhost</picUrl>
</properties>
</developer>
<developer>
<id>rb</id>
<name>Reto Breitenmoser</name>
<email>reto.breitenmoser@4trees.ch</email>
<url>http://4trees.ch</url>
<organization>4trees</organization>
<organizationUrl>http://4trees.ch</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>+1</timezone>
<properties>
<picUrl>http://localhost</picUrl>
</properties>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:4treesCH/strolch-plc.git</connection>
<developerConnection>scm:git:git@github.com:4treesCH/strolch-plc.git</developerConnection>
<url>https://github.com/4treesCH/strolch-plc</url>
</scm>
<organization>
<name>4trees</name>
<url>http://4trees.ch</url>
</organization>
<dependencyManagement>
<dependencies>
<!-- base -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- strolch -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.bom</artifactId>
<version>${strolch.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- PLC -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-rest</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Pi4j -->
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-core</artifactId>
<version>${pi4j.version}</version>
</dependency>
<!-- jSerialComm -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>${jserialcomm.version}</version>
</dependency>
<!-- Restful API -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.api.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${serverlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${jaxrs.api.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
<version>${jaxrs.ri.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>${tyrus.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-client</artifactId>
<version>${tyrus.version}</version>
</dependency>
<!-- websocket -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>${websocket.version}</version>
<scope>provided</scope>
</dependency>
<!-- testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>${buildnumber-maven-plugin.version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<compilerArgument>-Xlint:all</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources-no-fork</id>
<phase>generate-sources</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>${maven-site-plugin.version}</version>
<configuration>
<outputEncoding>UTF-8</outputEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>${maven-eclipse-plugin.version}</version>
<configuration>
<downloadJavadocs>true</downloadJavadocs>
<downloadSources>true</downloadSources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>deploy</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,8 @@
4trees PLC Core
===================================
One should enable the performance governor on the raspberry:
sudo echo performance | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
sudo cat /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq

120
strolch-plc-core/pom.xml Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>strolch-plc-core</artifactId>
<name>strolch-plc-core</name>
<packaging>jar</packaging>
<url>https://github.com/4treesCH/strolch-plc</url>
<scm>
<connection>scm:git:git@github.com:4treesCH/strolch-plc.git</connection>
<developerConnection>scm:git:git@github.com:4treesCH/strolch-plc.git</developerConnection>
<url>https://github.com/4treesCH/strolch-plc</url>
</scm>
<properties>
<!-- properties -->
</properties>
<dependencies>
<!-- base -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- Strolch -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.utils</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.model</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.agent</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.service</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.rest</artifactId>
</dependency>
<!-- PI4j -->
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.testbase</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,7 @@
package li.strolch.plc.core;
public enum ConnectionState {
Connected,
Disconnected,
Failed;
}

View File

@ -0,0 +1,394 @@
package li.strolch.plc.core;
import static li.strolch.plc.core.PlcConstants.*;
import static java.lang.System.nanoTime;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.formatNanoDuration;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import li.strolch.plc.core.hw.*;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.model.Resource;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.parameter.StringParameter;
import li.strolch.model.visitor.SetParameterValueVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.utils.collections.MapOfMaps;
public class DefaultPlcHandler extends StrolchComponent implements PlcHandler, PlcConnectionStateChangeListener {
private PrivilegeContext ctx;
private Plc plc;
private PlcState plcState;
private String plcStateMsg;
private Map<PlcAddress, PlcListener> virtualListeners;
private MapOfMaps<String, String, PlcAddress> plcAddresses;
private MapOfMaps<String, String, PlcAddress> plcTelegrams;
private Map<PlcAddress, String> addressesToResourceId;
public DefaultPlcHandler(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
public Plc getPlc() {
return this.plc;
}
@Override
public PlcState getPlcState() {
return this.plcState;
}
@Override
public String getPlcStateMsg() {
return this.plcStateMsg;
}
@Override
public PlcAddress getPlcAddress(String resource, String action) {
PlcAddress plcAddress = this.plcAddresses.getElement(resource, action);
if (plcAddress == null)
throw new IllegalStateException("PlcAddress for " + resource + "-" + action + " does not exist!");
return plcAddress;
}
@Override
public String getPlcAddressId(String resource, String action) {
PlcAddress plcAddress = getPlcAddress(resource, action);
String addressId = this.addressesToResourceId.get(plcAddress);
if (addressId == null)
throw new IllegalStateException(
"PlcAddress mapping ID for " + resource + "-" + action + " does not exist!");
return addressId;
}
@Override
public void initialize(ComponentConfiguration configuration) throws Exception {
// validate Plc class name
String plcClassName = configuration.getString("plcClass", DefaultPlc.class.getName());
Class.forName(plcClassName);
this.plcState = PlcState.Initial;
this.plcStateMsg = PlcState.Initial.name();
this.virtualListeners = new HashMap<>();
this.plcAddresses = new MapOfMaps<>();
this.plcTelegrams = new MapOfMaps<>();
this.addressesToResourceId = new HashMap<>();
super.initialize(configuration);
}
@Override
public void start() throws Exception {
this.ctx = getContainer().getPrivilegeHandler().openAgentSystemUserContext();
if (reconfigurePlc())
startPlc();
super.start();
}
@Override
public void stop() throws Exception {
stopPlc();
if (this.ctx != null)
getContainer().getPrivilegeHandler().invalidate(this.ctx.getCertificate());
super.stop();
}
@Override
public void startPlc() {
if (this.plc == null)
throw new IllegalStateException("Can not start as not yet configured!");
this.plc.start();
this.plcState = PlcState.Started;
this.plcStateMsg = PlcState.Started.name();
logger.info("Started PLC");
}
@Override
public void stopPlc() {
if (this.plc != null)
this.plc.stop();
this.plcState = PlcState.Stopped;
this.plcStateMsg = PlcState.Stopped.name();
logger.info("Stopped PLC");
}
@Override
public boolean reconfigurePlc() {
if (this.plcState == PlcState.Started)
throw new IllegalStateException("Can not reconfigure if started!");
try {
MapOfMaps<String, String, PlcAddress> plcAddresses = new MapOfMaps<>();
MapOfMaps<String, String, PlcAddress> plcTelegrams = new MapOfMaps<>();
Map<PlcAddress, String> addressesToResourceId = new HashMap<>();
this.plc = configure(validateCtx(), plcAddresses, plcTelegrams, addressesToResourceId);
this.plcAddresses = plcAddresses;
this.plcTelegrams = plcTelegrams;
this.addressesToResourceId = addressesToResourceId;
this.plcState = PlcState.Configured;
this.plcStateMsg = PlcState.Configured.name();
logger.info("Reconfigured PLC with " + this.plcAddresses.size() + " addresses");
return true;
} catch (Exception e) {
logger.error("Failed to configure Plc", e);
this.plcState = PlcState.Failed;
this.plcStateMsg = "Configure failed: " + getExceptionMessageWithCauses(e);
return false;
}
}
private void updateConnectionState(StrolchTransaction tx, Resource connection, PlcConnection plcConnection) {
StringParameter stateP = connection.getParameter(BAG_PARAMETERS, PARAM_STATE, true);
StringParameter stateMsgP = connection.getParameter(BAG_PARAMETERS, PARAM_STATE_MSG, true);
logger.info("State for PlcConnection {} has changed from {} to {}", connection.getId(), stateP.getValue(),
plcConnection.getState().name());
stateP.setValue(plcConnection.getState().name());
stateMsgP.setValue(plcConnection.getStateMsg());
tx.update(connection);
}
private Plc configure(PrivilegeContext ctx, MapOfMaps<String, String, PlcAddress> plcAddresses,
MapOfMaps<String, String, PlcAddress> plcTelegrams, Map<PlcAddress, String> addressesToResourceId) {
Plc plc;
try (StrolchTransaction tx = openTx(ctx.getCertificate(), true)) {
String plcClassName = getConfiguration().getString("plcClass", DefaultPlc.class.getName());
plc = PlcConfigurator.configurePlc(tx, plcClassName, plcAddresses, plcTelegrams, addressesToResourceId);
plc.setConnectionStateChangeListener(this);
plcAddresses.values().stream().filter(a -> a.type == PlcAddressType.Notification)
.forEach(plcAddress -> plc.registerListener(plcAddress, this::asyncStateUpdate));
if (tx.needsCommit())
tx.commitOnClose();
}
return plc;
}
private void asyncStateUpdate(PlcAddress address, Object value) {
getExecutorService("PlcAddressUpdater").submit(() -> updatePlcAddress(address, value));
}
private void asyncStateUpdate(PlcConnection connection) {
getExecutorService("PlcConnectionUpdater").submit(() -> updateConnectionState(connection));
}
private void updatePlcAddress(PlcAddress address, Object value) {
long s = nanoTime();
String addressId = this.addressesToResourceId.get(address);
if (addressId == null) {
logger.error("No PlcAddress mapping for " + address);
return;
}
try {
try (StrolchTransaction tx = openTx(validateCtx().getCertificate(), "updatePlcAddress", false)) {
tx.lock(Resource.locatorFor(TYPE_PLC_ADDRESS, addressId));
Resource addressRes = tx.getResourceBy(TYPE_PLC_ADDRESS, addressId, true);
// see if we need to invert a boolean flag
if (address.valueType == PlcValueType.Boolean && address.inverted) {
value = !((boolean) value);
}
Parameter<?> valueP = addressRes.getParameter(PARAM_VALUE, true);
logger.info("PlcAddress {}-{} has changed from {} to {}", address.resource, address.action,
valueP.getValue(), value);
valueP.accept(new SetParameterValueVisitor(value));
tx.update(addressRes);
tx.commitOnClose();
}
} catch (Exception e) {
logger.error("Failed to update PlcAddress " + addressId + " with new value " + value, e);
}
logger.info("async update " + address.address + " took " + (formatNanoDuration(nanoTime() - s)));
}
private void updateConnectionState(PlcConnection plcConnection) {
long s = nanoTime();
try {
try (StrolchTransaction tx = openTx(validateCtx().getCertificate(), "updateConnectionState", false)) {
tx.lock(Resource.locatorFor(TYPE_PLC_CONNECTION, plcConnection.getId()));
Resource connection = tx.getResourceBy(TYPE_PLC_CONNECTION, plcConnection.getId());
updateConnectionState(tx, connection, plcConnection);
tx.update(connection);
tx.commitOnClose();
}
} catch (Exception e) {
logger.error("Failed to update state for connection " + plcConnection.getId(), e);
}
logger.info("updateConnectionState took " + (formatNanoDuration(nanoTime() - s)));
}
private PrivilegeContext validateCtx() {
if (this.ctx == null) {
this.ctx = getContainer().getPrivilegeHandler().openAgentSystemUserContext();
} else {
try {
getContainer().getPrivilegeHandler().validateSystemSession(this.ctx);
} catch (Exception e) {
logger.error("PrivilegeContext for session " + this.ctx.getCertificate().getSessionId()
+ " is not valid, reopening.", e);
this.ctx = getContainer().getPrivilegeHandler().openAgentSystemUserContext();
}
}
return this.ctx;
}
@Override
public Collection<PlcAddress> getVirtualAddresses() {
return this.virtualListeners.keySet();
}
@Override
public void registerVirtualListener(String resource, String action, PlcListener listener, PlcValueType valueType,
Object defaultValue) {
if (this.plcAddresses.containsElement(resource, action))
throw new IllegalStateException(
"There already is a virtual listener registered for key " + resource + "-" + action);
PlcAddress plcAddress = new PlcAddress(PlcAddressType.Notification, true, resource, action,
listener.getClass().getSimpleName(), valueType, defaultValue, false);
this.plcAddresses.addElement(resource, action, plcAddress);
this.virtualListeners.put(plcAddress, listener);
logger.info("Registered virtual listener for " + resource + "-" + action);
}
@Override
public void unregisterVirtualListener(String resource, String action) {
PlcAddress plcAddress = this.plcAddresses.getElement(resource, action);
if (plcAddress == null) {
logger.error("No PlcListener registered for " + resource + " " + action);
return;
}
PlcListener listener = this.virtualListeners.remove(plcAddress);
if (listener == null) {
logger.error("No PlcListener registered for " + resource + " " + action);
return;
}
this.plcAddresses.removeElement(resource, action);
logger.info("Unregistered listener " + resource + " " + action);
}
@Override
public void registerListener(String resource, String action, PlcListener listener) {
PlcAddress plcAddress = this.plcAddresses.getElement(resource, action);
if (plcAddress == null)
throw new IllegalStateException("No PlcAddress exists for " + resource + "-" + action);
this.plc.registerListener(plcAddress, listener);
}
@Override
public void unregisterListener(String resource, String action, PlcListener listener) {
PlcAddress plcAddress = this.plcAddresses.getElement(resource, action);
if (plcAddress == null) {
logger.warn("No PlcAddress exists for " + resource + "-" + action);
} else {
this.plc.unregisterListener(plcAddress, listener);
}
}
@Override
public void send(String resource, String action) {
PlcAddress plcAddress = this.plcTelegrams.getElement(resource, action);
if (plcAddress == null)
throw new IllegalStateException("No PlcTelegram exists for " + resource + "-" + action);
if (plcAddress.virtual) {
this.virtualListeners.get(plcAddress).handleNotification(plcAddress, plcAddress.defaultValue);
return;
}
if (plcAddress.defaultValue == null)
throw new IllegalStateException("Can not send PlcAddress as no default value set for " + plcAddress);
logger.info("Sending " + resource + "-" + action + ": " + plcAddress.defaultValue + " (default)");
PlcConnection connection = validateConnection(plcAddress);
connection.send(plcAddress.address, plcAddress.defaultValue);
asyncStateUpdate(plcAddress, plcAddress.defaultValue);
}
@Override
public void send(String resource, String action, Object value) {
PlcAddress plcAddress = this.plcTelegrams.getElement(resource, action);
if (plcAddress == null)
throw new IllegalStateException("No PlcTelegram exists for " + resource + "-" + action);
if (plcAddress.virtual) {
this.virtualListeners.get(plcAddress).handleNotification(plcAddress, value);
return;
}
logger.info("Sending " + resource + "-" + action + ": " + value);
PlcConnection connection = validateConnection(plcAddress);
connection.send(plcAddress.address, value);
asyncStateUpdate(plcAddress, value);
}
@Override
public void notify(String resource, String action, Object value) {
PlcAddress plcAddress = this.plcAddresses.getElement(resource, action);
if (plcAddress == null)
throw new IllegalStateException("No PlcAddress exists for " + resource + "-" + action);
if (plcAddress.type != PlcAddressType.Notification)
throw new IllegalStateException(
"Can not notify PlcAddress " + plcAddress + " as it is not a notification!");
if (plcAddress.virtual) {
this.virtualListeners.get(plcAddress).handleNotification(plcAddress, value);
return;
}
this.plc.notify(plcAddress.address, value);
}
private PlcConnection validateConnection(PlcAddress plcAddress) {
PlcConnection connection = this.plc.getConnection(plcAddress);
if (connection.getState() == ConnectionState.Connected)
return connection;
connection.connect();
if (connection.getState() == ConnectionState.Connected)
return connection;
throw new IllegalStateException("PlcConnection " + connection.getId() + " is disconnected for " + plcAddress);
}
@Override
public StrolchTransaction openTx(Certificate cert, boolean readOnly) {
return super.openTx(cert, readOnly);
}
@Override
public void notifyStateChange(PlcConnection connection) {
asyncStateUpdate(connection);
}
}

View File

@ -0,0 +1,139 @@
package li.strolch.plc.core;
import static li.strolch.plc.core.PlcConstants.*;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import li.strolch.plc.core.hw.*;
import li.strolch.model.Resource;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.search.ResourceSearch;
import li.strolch.utils.collections.MapOfMaps;
import li.strolch.utils.helper.ClassHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class PlcConfigurator {
private static final Logger logger = LoggerFactory.getLogger(PlcConfigurator.class);
static Plc configurePlc(StrolchTransaction tx, String plcClassName,
MapOfMaps<String, String, PlcAddress> plcAddresses, MapOfMaps<String, String, PlcAddress> plcTelegrams,
Map<PlcAddress, String> addressesToResourceId) {
// instantiate Plc
logger.info("Configuring PLC " + plcClassName + "...");
Plc plc = ClassHelper.instantiateClass(plcClassName);
// instantiate all PlcConnections
new ResourceSearch().types(TYPE_PLC_CONNECTION).search(tx)
.forEach(connection -> configureConnection(plc, connection));
Map<String, PlcAddress> plcAddressesByHwAddress = new HashMap<>();
// query PlcLogicalDevices
List<Resource> logicalDevices = new ResourceSearch().types(TYPE_PLC_LOGICAL_DEVICE).search(tx).toList();
// first all addresses
logicalDevices.forEach(logicalDevice -> {
logger.info("Configuring PlcAddresses for PlcLogicalDevice " + logicalDevice.getId() + "...");
tx.getResourcesByRelation(logicalDevice, PARAM_ADDRESSES, true).forEach(
addressRes -> buildPlcAddress(plc, plcAddresses, addressesToResourceId, plcAddressesByHwAddress,
addressRes));
});
// now telegrams
logicalDevices.forEach(logicalDevice -> {
logger.info("Configuring PlcTelegrams for PlcLogicalDevice " + logicalDevice.getId() + "...");
tx.getResourcesByRelation(logicalDevice, PARAM_TELEGRAMS, true).forEach(
telegramRes -> buildTelegramPlcAddress(plcAddresses, plcTelegrams, addressesToResourceId,
plcAddressesByHwAddress, telegramRes));
});
return plc;
}
private static void configureConnection(Plc plc, Resource connection) {
String className = connection.getParameter(BAG_PARAMETERS, PARAM_CLASS_NAME, true).getValue();
logger.info("Configuring PLC Connection " + className + "...");
PlcConnection plcConnection = ClassHelper
.instantiateClass(className, new Class<?>[] { Plc.class, String.class },
new Object[] { plc, connection.getId() });
plcConnection.initialize(connection.getParameterBag(BAG_PARAMETERS, true).toObjectMap());
plc.addConnection(plcConnection);
}
private static void buildPlcAddress(Plc plc, MapOfMaps<String, String, PlcAddress> plcAddresses,
Map<PlcAddress, String> addressesToResourceId, Map<String, PlcAddress> plcAddressesByHwAddress,
Resource addressRes) {
String address = addressRes.getParameter(PARAM_ADDRESS, true).getValue();
String resource = addressRes.getParameter(PARAM_RESOURCE, true).getValue();
String action = addressRes.getParameter(PARAM_ACTION, true).getValue();
PlcValueType valueType = PlcValueType.valueOf(addressRes.getParameter(PARAM_VALUE_TYPE, true).getValue());
Object value = addressRes.getParameter(PARAM_VALUE, true).getValue();
boolean inverted =
addressRes.hasParameter(PARAM_INVERTED) && ((boolean) addressRes.getParameter(PARAM_INVERTED, true)
.getValue());
PlcAddress plcAddress = new PlcAddress(PlcAddressType.Notification, false, resource, action, address, valueType,
value, inverted);
logger.info("Adding PlcAddress " + plcAddress + "...");
plc.registerNotificationMapping(plcAddress);
PlcAddress replaced = plcAddresses.addElement(resource, action, plcAddress);
if (replaced != null)
throw new IllegalStateException(
"Duplicate " + resource + "-" + action + ". Replaced: " + replaced + " with " + plcAddress);
addressesToResourceId.put(plcAddress, addressRes.getId());
plcAddressesByHwAddress.put(plcAddress.address, plcAddress);
}
private static void buildTelegramPlcAddress(MapOfMaps<String, String, PlcAddress> plcAddresses,
MapOfMaps<String, String, PlcAddress> plcTelegrams, Map<PlcAddress, String> addressesToResourceId,
Map<String, PlcAddress> plcAddressesByHwAddress, Resource telegramRes) {
String address = telegramRes.getParameter(PARAM_ADDRESS, true).getValue();
String resource = telegramRes.getParameter(PARAM_RESOURCE, true).getValue();
String action = telegramRes.getParameter(PARAM_ACTION, true).getValue();
PlcValueType valueType = PlcValueType.valueOf(telegramRes.getParameter(PARAM_VALUE_TYPE, true).getValue());
Object value = telegramRes.getParameter(PARAM_VALUE, true).getValue();
PlcAddress existingAddress = plcAddressesByHwAddress.get(address);
if (existingAddress == null)
throw new IllegalStateException(
telegramRes.getLocator() + " is referencing non-existing address " + address);
if (valueType != existingAddress.valueType) {
throw new IllegalStateException(
telegramRes.getLocator() + " has valueType " + valueType + " but address " + existingAddress.address
+ " has type " + existingAddress.valueType);
}
PlcAddress telegramAddress = new PlcAddress(PlcAddressType.Telegram, false, resource, action, address,
valueType, value, false);
logger.info("Adding PlcTelegram " + telegramAddress + "...");
PlcAddress replaced = plcTelegrams.addElement(resource, action, telegramAddress);
if (replaced != null)
throw new IllegalStateException(
"Duplicate " + resource + "-" + action + ". Replaced: " + replaced + " with " + telegramAddress);
if (!plcAddresses.containsElement(resource, action))
plcAddresses.addElement(resource, action, telegramAddress);
PlcAddress plcAddress = plcAddresses.getElement(existingAddress.resource, existingAddress.action);
if (plcAddress == null)
throw new IllegalStateException(
"PlcAddress for " + resource + "-" + action + " does not exist, so can not connect PlcTelegram "
+ telegramAddress);
String addressId = addressesToResourceId.get(plcAddress);
if (addressId == null)
throw new IllegalStateException(
"PlcAddress mapping ID for " + resource + "-" + action + " does not exist!");
addressesToResourceId.put(telegramAddress, addressId);
}
}

View File

@ -0,0 +1,32 @@
package li.strolch.plc.core;
public class PlcConstants {
public static final String TYPE_PLC_CONNECTION = "PlcConnection";
public static final String TYPE_PLC_LOGICAL_DEVICE = "PlcLogicalDevice";
public static final String TYPE_PLC_ADDRESS = "PlcAddress";
public static final String TYPE_PLC_TELEGRAM = "PlcTelegram";
public static final String BAG_NOTIFICATIONS = "notifications";
public static final String BAG_TELEGRAMS = "telegrams";
public static final String PARAM_ADDRESSES = "addresses";
public static final String PARAM_TELEGRAMS = "telegrams";
public static final String PARAM_STATE = "state";
public static final String PARAM_STATE_MSG = "stateMsg";
public static final String PARAM_CLASS_NAME = "className";
public static final String PARAM_ADDRESS = "address";
public static final String PARAM_RESOURCE = "resource";
public static final String PARAM_ACTION = "action";
public static final String PARAM_ADDRESS_TYPE = "addressType";
public static final String PARAM_VALUE_TYPE = "valueType";
public static final String PARAM_INTERRUPT_PIN_NAME = "interruptPinName";
public static final String PARAM_GROUP = "group";
public static final String PARAM_INDEX = "index";
public static final String PARAM_TYPE = "type";
public static final String PARAM_VALUE = "value";
public static final String PARAM_INVERTED = "inverted";
public static final String INTERPRETATION_NOTIFICATION = "Notification";
public static final String INTERPRETATION_TELEGRAM = "Telegram";
}

View File

@ -0,0 +1,49 @@
package li.strolch.plc.core;
import java.util.Collection;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcAddress;
import li.strolch.plc.core.hw.PlcListener;
import li.strolch.plc.core.hw.PlcValueType;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
public interface PlcHandler {
PlcState getPlcState();
String getPlcStateMsg();
boolean reconfigurePlc();
void startPlc();
void stopPlc();
Plc getPlc();
PlcAddress getPlcAddress(String resource, String action);
String getPlcAddressId(String resource, String action);
void registerListener(String resource, String action, PlcListener listener);
void unregisterListener(String resource, String action, PlcListener listener);
void registerVirtualListener(String resource, String action, PlcListener listener, PlcValueType valueType,
Object defaultValue);
void unregisterVirtualListener(String resource, String action);
Collection<PlcAddress> getVirtualAddresses();
void send(String resource, String action);
void send(String resource, String action, Object value);
void notify(String resource, String action, Object value);
StrolchTransaction openTx(Certificate cert, boolean readOnly);
}

View File

@ -0,0 +1,92 @@
package li.strolch.plc.core;
import java.util.concurrent.TimeUnit;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.agent.impl.SimplePostInitializer;
import li.strolch.execution.ArchiveExecutedActivitiesJob;
import li.strolch.execution.ExecutionHandler;
import li.strolch.handler.mail.MailHandler;
import li.strolch.job.JobMode;
import li.strolch.job.StrolchJobsHandler;
import li.strolch.policy.ReloadPoliciesJob;
import li.strolch.policy.ReloadPrivilegeHandlerJob;
import li.strolch.runtime.configuration.RuntimeConfiguration;
import li.strolch.utils.helper.ExceptionHelper;
public class PlcPostInitializer extends SimplePostInitializer {
public PlcPostInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
public void start() throws Exception {
registerJobs();
notifyStart();
super.start();
}
@Override
public void stop() throws Exception {
super.stop();
}
protected void registerJobs() throws Exception {
if (!getContainer().hasComponent(StrolchJobsHandler.class))
return;
StrolchJobsHandler jobsHandler = getComponent(StrolchJobsHandler.class);
// Manually triggered jobs to run once on startup
// jobsHandler.register(XXX.class).runNow();
// special jobs which are triggered by an admin, and not run at startup
jobsHandler.register(ReloadPoliciesJob.class);
jobsHandler.register(ReloadPrivilegeHandlerJob.class);
// recurring jobs
// jobsHandler.registerAndScheduleJob(XXX.class);
if (getContainer().hasComponent(ExecutionHandler.class)) {
StrolchAgent agent = getContainer().getAgent();
ArchiveExecutedActivitiesJob archiveExecutedActivitiesJob = new ArchiveExecutedActivitiesJob(agent,
JobMode.Recurring, 5, TimeUnit.MINUTES, 6, TimeUnit.HOURS);
jobsHandler.register(archiveExecutedActivitiesJob).runNow();
}
}
protected void notifyStart() {
if (!(getConfiguration().getBoolean("notifyStart", Boolean.FALSE) && getContainer()
.hasComponent(MailHandler.class)))
return;
String recipients = getConfiguration().getString("notifyStartRecipients", "");
if (recipients.isEmpty()) {
logger.error("config param notifyStartRecipients is empty, can not notify of boot!");
return;
}
StrolchAgent agent = getContainer().getAgent();
RuntimeConfiguration runtimeConfiguration = agent.getStrolchConfiguration().getRuntimeConfiguration();
String subject = runtimeConfiguration.getApplicationName() + ":" + runtimeConfiguration.getEnvironment()
+ " Startup Complete!";
String body = "Dear User\n\n" //
+ "The " + getConfiguration().getRuntimeConfiguration().getApplicationName()
+ " Server has just completed startup with version " //
+ agent.getVersion().getAppVersion().getArtifactVersion() //
+ "\n\n" //
+ "\tYour Server.";
try {
getContainer().getComponent(MailHandler.class).sendMail(subject, body, recipients);
} catch (Exception e) {
logger.error("Notifying of server startup failed: " + ExceptionHelper.getRootCause(e), e);
}
}
}

View File

@ -0,0 +1,127 @@
package li.strolch.plc.core;
import static li.strolch.plc.core.PlcConstants.PARAM_VALUE;
import static li.strolch.plc.core.PlcConstants.TYPE_PLC_ADDRESS;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import li.strolch.plc.core.hw.PlcAddress;
import li.strolch.plc.core.hw.PlcListener;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.model.Resource;
import li.strolch.model.parameter.Parameter;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.runtime.privilege.PrivilegedRunnable;
import li.strolch.runtime.privilege.PrivilegedRunnableWithResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PlcService implements PlcListener {
protected static final Logger logger = LoggerFactory.getLogger(PlcService.class);
protected final ComponentContainer container;
protected final PlcHandler plcHandler;
private PlcServiceState state;
public PlcService(ComponentContainer container, PlcHandler plcHandler) {
this.container = container;
this.plcHandler = plcHandler;
this.state = PlcServiceState.Unregistered;
}
public PlcServiceState getState() {
return this.state;
}
@Override
public void handleNotification(PlcAddress address, Object value) {
// no-op
}
public void start(StrolchTransaction tx) {
this.state = PlcServiceState.Started;
}
public void stop() {
this.state = PlcServiceState.Stopped;
}
public void register() {
this.state = PlcServiceState.Registered;
}
public void unregister() {
this.state = PlcServiceState.Unregistered;
}
protected Resource getPlcAddress(StrolchTransaction tx, String resource, String action) {
String plcAddressId = this.plcHandler.getPlcAddressId(resource, action);
return tx.getResourceBy(TYPE_PLC_ADDRESS, plcAddressId, true);
}
protected <T> T getAddressState(StrolchTransaction tx, String resource, String action) {
Parameter<T> addressParam = getPlcAddress(tx, resource, action).getParameter(PARAM_VALUE, true);
return addressParam.getValue();
}
protected void send(String resource, String action) {
this.plcHandler.send(resource, action);
}
protected void send(String resource, String action, Object value) {
this.plcHandler.send(resource, action, value);
}
protected StrolchTransaction openTx(PrivilegeContext ctx, boolean readOnly) {
return this.plcHandler.openTx(ctx.getCertificate(), readOnly);
}
protected void runAsAgent(PrivilegedRunnable runnable) throws Exception {
this.container.getPrivilegeHandler().runAsAgent(runnable);
}
protected <T> T runAsAgentWithResult(PrivilegedRunnableWithResult<T> runnable) throws Exception {
return this.container.getPrivilegeHandler().runAsAgentWithResult(runnable);
}
protected ScheduledFuture<?> schedule(PrivilegedRunnable runnable, long delay, TimeUnit delayUnit) {
return this.container.getAgent().getScheduledExecutor(PlcService.class.getSimpleName()).schedule(() -> {
try {
this.container.getPrivilegeHandler().runAsAgent(runnable);
} catch (Exception e) {
handleFailedSchedule(e);
}
}, delay, delayUnit);
}
protected ScheduledFuture<?> scheduleAtFixedRate(PrivilegedRunnable runnable, long initialDelay, long period,
TimeUnit delayUnit) {
return this.container.getAgent().getScheduledExecutor(PlcService.class.getSimpleName())
.scheduleAtFixedRate(() -> {
try {
this.container.getPrivilegeHandler().runAsAgent(runnable);
} catch (Exception e) {
handleFailedSchedule(e);
}
}, initialDelay, period, delayUnit);
}
protected ScheduledFuture<?> scheduleWithFixedDelay(PrivilegedRunnable runnable, long initialDelay, long period,
TimeUnit delayUnit) {
return this.container.getAgent().getScheduledExecutor(PlcService.class.getSimpleName())
.scheduleWithFixedDelay(() -> {
try {
this.container.getPrivilegeHandler().runAsAgent(runnable);
} catch (Exception e) {
handleFailedSchedule(e);
}
}, initialDelay, period, delayUnit);
}
protected void handleFailedSchedule(Exception e) {
logger.error("Failed to execute " + getClass().getSimpleName(), e);
}
}

View File

@ -0,0 +1,75 @@
package li.strolch.plc.core;
import java.util.List;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.persistence.api.StrolchTransaction;
public abstract class PlcServiceInitializer extends StrolchComponent {
private List<PlcService> plcServices;
public PlcServiceInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
public void start() throws Exception {
startPlcServices();
super.start();
}
@Override
public void stop() throws Exception {
if (this.plcServices != null)
this.plcServices.forEach(plcService -> {
try {
plcService.stop();
} catch (Exception e) {
logger.error("Failed to stop PlcService " + plcService.getClass().getName(), e);
}
try {
plcService.unregister();
} catch (Exception e) {
logger.error("Failed to unregister PlcService " + plcService.getClass().getName(), e);
}
});
super.stop();
}
protected void startPlcServices() {
PlcHandler plcHandler = getComponent(PlcHandler.class);
this.plcServices = getPlcServices(plcHandler);
for (PlcService plcService : this.plcServices) {
try {
plcService.register();
} catch (Exception e) {
logger.error("Failed to register PlcService " + plcService.getClass().getName(), e);
}
}
try {
runAsAgent(ctx -> {
try (StrolchTransaction tx = openTx(ctx.getCertificate(), getClass().getSimpleName(), true)) {
for (PlcService plcService : this.plcServices) {
if (plcService.getState() != PlcServiceState.Registered)
continue;
try {
plcService.start(tx);
} catch (Exception e) {
logger.error("Failed to register PlcService " + plcService.getClass().getName(), e);
}
}
}
});
} catch (Exception e) {
throw new IllegalStateException("Failed to start PlcServices", e);
}
}
protected abstract List<PlcService> getPlcServices(PlcHandler plcHandler);
}

View File

@ -0,0 +1,8 @@
package li.strolch.plc.core;
public enum PlcServiceState {
Registered,
Started,
Stopped,
Unregistered
}

View File

@ -0,0 +1,9 @@
package li.strolch.plc.core;
public enum PlcState {
Initial,
Configured,
Started,
Stopped,
Failed;
}

View File

@ -0,0 +1,152 @@
package li.strolch.plc.core.hw;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import li.strolch.utils.collections.MapOfLists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultPlc implements Plc {
private static final Logger logger = LoggerFactory.getLogger(DefaultPlc.class);
private Map<String, PlcAddress> notificationMappings;
private Map<String, PlcConnection> connections;
private Map<String, PlcConnection> connectionsByAddress;
private MapOfLists<PlcAddress, PlcListener> listeners;
private PlcConnectionStateChangeListener connectionStateChangeListener;
public DefaultPlc() {
this.notificationMappings = new HashMap<>();
this.listeners = new MapOfLists<>();
this.connections = new HashMap<>();
this.connectionsByAddress = new HashMap<>();
}
@Override
public void registerListener(PlcAddress address, PlcListener listener) {
this.listeners.addElement(address, listener);
logger.info("Registered listener " + listener + " with key " + address);
}
@Override
public void unregisterListener(PlcAddress address, PlcListener listener) {
if (this.listeners.removeElement(address, listener)) {
logger.info("Unregistered listener " + listener + " with key " + address);
} else {
logger.warn("Listener " + listener + " not registered with key " + address);
}
}
@Override
public void notify(String address, Object value) {
logger.info("Update for " + address + " with value " + value);
PlcAddress key = this.notificationMappings.get(address);
if (key == null) {
logger.warn("No mapping to PlcAddress for hwAddress " + address);
return;
}
List<PlcListener> listeners = this.listeners.getList(key);
if (listeners == null || listeners.isEmpty()) {
logger.warn("No listeners for key " + key);
return;
}
for (PlcListener listener : listeners) {
try {
listener.handleNotification(key, value);
} catch (Exception e) {
logger.error("Failed to notify listener " + listener + " for key " + key, e);
}
}
}
@Override
public void send(PlcAddress address) {
PlcConnection connection = this.connectionsByAddress.get(address.address);
if (connection == null)
throw new IllegalStateException(
"No PlcConnection exists for key " + address + " with address " + address.address);
connection.send(address.address, address.defaultValue);
}
@Override
public void send(PlcAddress address, Object value) {
PlcConnection connection = this.connectionsByAddress.get(address.address);
if (connection == null)
throw new IllegalStateException(
"No PlcConnection exists for key " + address + " with address " + address.address);
connection.send(address.address, value);
}
@Override
public void addConnection(PlcConnection connection) {
this.connections.put(connection.getId(), connection);
Set<String> addresses = connection.getAddresses();
logger.info("Adding connection " + connection + " with " + addresses.size() + " addresses...");
for (String address : addresses) {
logger.info(" Adding address " + address + "...");
this.connectionsByAddress.put(address, connection);
}
}
@Override
public void start() {
this.connections.values().forEach(PlcConnection::connect);
}
@Override
public void stop() {
this.connections.values().forEach(PlcConnection::disconnect);
}
@Override
public void notifyConnectionStateChanged(PlcConnection connection) {
if (this.connectionStateChangeListener != null)
this.connectionStateChangeListener.notifyStateChange(connection);
}
public void setConnectionStateChangeListener(PlcConnectionStateChangeListener listener) {
this.connectionStateChangeListener = listener;
}
@Override
public PlcConnection getConnection(PlcAddress address) {
PlcConnection plcConnection = this.connectionsByAddress.get(address.address);
if (plcConnection == null)
throw new IllegalStateException("No PlcConnection exists for address " + address.address);
return plcConnection;
}
@Override
public PlcConnection getConnection(String id) {
PlcConnection plcConnection = this.connections.get(id);
if (plcConnection == null)
throw new IllegalStateException("No PlcConnection exists with id " + id);
return plcConnection;
}
@Override
public void registerNotificationMapping(PlcAddress address) {
if (!this.connectionsByAddress.containsKey(address.address))
throw new IllegalStateException(
"There is no connection registered for address " + address.address + " for key " + address);
logger.info("Registered address mapping for " + address);
if (address.type != PlcAddressType.Notification)
throw new IllegalArgumentException("Key must be of type " + PlcAddressType.Notification + ": " + address);
PlcAddress replaced = this.notificationMappings.put(address.address, address);
if (replaced != null) {
throw new IllegalArgumentException(
"Replaced mapping for address " + address.address + " for key " + replaced + " with " + address);
}
}
}

View File

@ -0,0 +1,30 @@
package li.strolch.plc.core.hw;
public interface Plc {
void start();
void stop();
void registerListener(PlcAddress address, PlcListener listener);
void unregisterListener(PlcAddress address, PlcListener listener);
void notify(String address, Object value);
void send(PlcAddress address);
void send(PlcAddress address, Object value);
void addConnection(PlcConnection connection);
PlcConnection getConnection(String id);
PlcConnection getConnection(PlcAddress address);
void registerNotificationMapping(PlcAddress address);
void notifyConnectionStateChanged(PlcConnection connection);
void setConnectionStateChangeListener(PlcConnectionStateChangeListener listener);
}

View File

@ -0,0 +1,56 @@
package li.strolch.plc.core.hw;
import java.util.Objects;
public class PlcAddress {
public final PlcAddressType type;
public final boolean virtual;
public final String resource;
public final String action;
public final String address;
public final PlcValueType valueType;
public final Object defaultValue;
public final boolean inverted;
public PlcAddress(PlcAddressType type, boolean virtual, String resource, String action, String address,
PlcValueType valueType, Object defaultValue, boolean inverted) {
this.type = type;
this.virtual = virtual;
this.resource = resource.intern();
this.action = action.intern();
this.address = address.intern();
this.valueType = valueType;
this.defaultValue = defaultValue;
this.inverted = inverted;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PlcAddress that = (PlcAddress) o;
if (!Objects.equals(resource, that.resource))
return false;
return Objects.equals(action, that.action);
}
@Override
public int hashCode() {
int result = resource != null ? resource.hashCode() : 0;
result = 31 * result + (action != null ? action.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "PlcAddress [" + "type='" + type + '\'' + ", resource='" + resource + '\'' + ", action='" + action + '\''
+ ", hwAddress='" + address + '\'' + ", valueType=" + valueType + ']';
}
}

View File

@ -0,0 +1,6 @@
package li.strolch.plc.core.hw;
public enum PlcAddressType {
Telegram,
Notification;
}

View File

@ -0,0 +1,51 @@
package li.strolch.plc.core.hw;
import java.util.Map;
import java.util.Set;
import li.strolch.plc.core.ConnectionState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PlcConnection {
protected static final Logger logger = LoggerFactory.getLogger(PlcConnection.class);
protected final Plc plc;
protected final String id;
protected ConnectionState connectionState;
protected String connectionStateMsg;
public PlcConnection(Plc plc, String id) {
this.plc = plc;
this.id = id;
this.connectionState = ConnectionState.Disconnected;
}
public String getId() {
return this.id;
}
public ConnectionState getState() {
return this.connectionState;
}
public String getStateMsg() {
return this.connectionStateMsg;
}
public abstract void initialize(Map<String, Object> parameters);
public abstract void connect();
public abstract void disconnect();
public abstract void send(String address, Object value);
public abstract Set<String> getAddresses();
protected void assertConnected() {
if (this.connectionState != ConnectionState.Connected)
throw new IllegalStateException("PlcConnection " + this.id + " is not yet connected!");
}
}

View File

@ -0,0 +1,6 @@
package li.strolch.plc.core.hw;
public interface PlcConnectionStateChangeListener {
void notifyStateChange(PlcConnection connection);
}

View File

@ -0,0 +1,6 @@
package li.strolch.plc.core.hw;
public interface PlcListener {
void handleNotification(PlcAddress address, Object value);
}

View File

@ -0,0 +1,119 @@
package li.strolch.plc.core.hw;
import com.google.gson.JsonPrimitive;
import li.strolch.model.StrolchValueType;
import li.strolch.utils.helper.StringHelper;
public enum PlcValueType {
Boolean() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(((Boolean) value).toString());
}
@Override
public Object parseStringValue(String value) {
return StringHelper.parseBoolean(value);
}
},
Short() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(((Short) value).toString());
}
@Override
public Object parseStringValue(String value) {
return java.lang.Short.parseShort(value);
}
},
Integer() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(((Integer) value).toString());
}
@Override
public Object parseStringValue(String value) {
return java.lang.Integer.parseInt(value);
}
},
Long() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(value.toString());
}
@Override
public Object parseStringValue(String value) {
return java.lang.Long.parseLong(value);
}
},
Float() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(value.toString());
}
@Override
public Object parseStringValue(String value) {
return java.lang.Float.parseFloat(value);
}
},
Double() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(value.toString());
}
@Override
public Object parseStringValue(String value) {
return java.lang.Double.parseDouble(value);
}
},
String() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive((String) value);
}
@Override
public Object parseStringValue(String value) {
return value;
}
},
ByteArray() {
@Override
public JsonPrimitive valueToJson(Object value) {
return new JsonPrimitive(StringHelper.toHexString((byte[]) value));
}
@Override
public Object parseStringValue(String value) {
return StringHelper.fromHexString(value);
}
};
public abstract JsonPrimitive valueToJson(Object value);
public abstract Object parseStringValue(String value);
public static PlcValueType fromStrolchValueType(StrolchValueType valueType) {
switch (valueType) {
case BOOLEAN:
return Boolean;
case INTEGER:
return Integer;
case FLOAT:
return Float;
case LONG:
return Long;
case STRING:
case TEXT:
return String;
default:
throw new IllegalStateException("Unhandled strolch value type " + valueType);
}
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.plc.core.hw.connections;
import li.strolch.plc.core.hw.Plc;
public class InMemoryBooleanConnection extends SimplePlcConnection {
public InMemoryBooleanConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void send(String address, Object value) {
boolean bool = (boolean) value;
logger.info("Setting address " + this.id + " to " + bool);
this.plc.notify(this.id, bool);
}
}

View File

@ -0,0 +1,16 @@
package li.strolch.plc.core.hw.connections;
import li.strolch.plc.core.hw.Plc;
public class LoggerOutConnection extends SimplePlcConnection {
public LoggerOutConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void send(String address, Object value) {
assertConnected();
logger.info(address + " -> " + value);
}
}

View File

@ -0,0 +1,25 @@
package li.strolch.plc.core.hw.connections;
import java.security.SecureRandom;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcConnection;
import li.strolch.utils.helper.StringHelper;
public class RandomStringConnection extends SimplePlcConnection {
public RandomStringConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void send(String address, Object value) {
assertConnected();
PlcConnection.logger.info("Sending " + address + " => " + value);
byte[] data = new byte[8];
new SecureRandom().nextBytes(data);
String newValue = StringHelper.toHexString(data);
PlcConnection.logger.info("Generated random value " + newValue);
this.plc.notify(address, newValue);
}
}

View File

@ -0,0 +1,44 @@
package li.strolch.plc.core.hw.connections;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import li.strolch.plc.core.ConnectionState;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcConnection;
public abstract class SimplePlcConnection extends PlcConnection {
public SimplePlcConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void initialize(Map<String, Object> parameters) {
logger.info("Configured " + getClass().getSimpleName() + this.id);
}
@Override
public void connect() {
logger.info(this.id + ": Is now connected.");
this.connectionState = ConnectionState.Connected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
}
@Override
public void disconnect() {
logger.info(this.id + ": Is now disconnected.");
this.connectionState = ConnectionState.Disconnected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
}
@Override
public Set<String> getAddresses() {
TreeSet<String> addresses = new TreeSet<>();
addresses.add(this.id);
return Collections.unmodifiableSet(addresses);
}
}

View File

@ -0,0 +1,21 @@
package li.strolch.plc.core.hw.gpio;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.RaspiGpioProvider;
import com.pi4j.io.gpio.RaspiPinNumberingScheme;
public class PlcGpioController {
private static GpioController controller;
public static GpioController getInstance() {
if (controller != null)
return controller;
GpioFactory.setDefaultProvider(new RaspiGpioProvider(RaspiPinNumberingScheme.BROADCOM_PIN_NUMBERING));
controller = GpioFactory.getInstance();
return controller;
}
}

View File

@ -0,0 +1,98 @@
package li.strolch.plc.core.hw.gpio;
import static java.util.stream.Collectors.joining;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import java.util.*;
import li.strolch.plc.core.ConnectionState;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcConnection;
import com.pi4j.io.gpio.*;
public class RaspiBcmGpioOutputConnection extends PlcConnection {
private List<Integer> outputBcmAddresses;
private Map<String, GpioPinDigitalOutput> pinsByAddress;
public RaspiBcmGpioOutputConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void initialize(Map<String, Object> parameters) {
@SuppressWarnings("unchecked")
List<Integer> bcmOutputPins = (List<Integer>) parameters.get("bcmOutputPins");
this.outputBcmAddresses = bcmOutputPins;
logger.info(
"Configured Raspi BCM GPIO Output for Pins " + this.outputBcmAddresses.stream().map(Object::toString)
.collect(joining(", ")));
}
@Override
public void connect() {
try {
GpioController gpioController = PlcGpioController.getInstance();
this.pinsByAddress = new HashMap<>();
for (Integer address : this.outputBcmAddresses) {
Pin pin = RaspiBcmPin.getPinByAddress(address);
if (pin == null)
throw new IllegalArgumentException("RaspiBcmPin " + address + " does not exist!");
GpioPinDigitalOutput outputPin = gpioController.provisionDigitalOutputPin(pin);
String key = this.id + "." + address;
this.pinsByAddress.put(key, outputPin);
logger.info("Registered address " + key + " for RaspiBcmPin " + outputPin);
}
logger.info(this.id + ": Is now connected.");
this.connectionState = ConnectionState.Connected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
} catch (Error e) {
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg = "Failed to connect to GpioController: " + getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
}
}
@Override
public void disconnect() {
try {
GpioController gpioController = PlcGpioController.getInstance();
for (GpioPinDigitalOutput outputPin : this.pinsByAddress.values()) {
gpioController.unprovisionPin(outputPin);
}
this.pinsByAddress.clear();
} catch (Error e) {
logger.error("Failed to disconnect " + this.id, e);
}
this.connectionState = ConnectionState.Disconnected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
}
@Override
public void send(String address, Object value) {
boolean high = (boolean) value;
GpioPinDigitalOutput outputPin = this.pinsByAddress.get(address);
if (outputPin == null)
throw new IllegalArgumentException("Output pin with address " + address + " does not exist!");
if (high)
outputPin.setState(PinState.HIGH);
else
outputPin.setState(PinState.LOW);
}
@Override
public Set<String> getAddresses() {
Set<String> addresses = new HashSet<>();
for (Integer address : this.outputBcmAddresses) {
addresses.add(this.id + "." + address);
}
return addresses;
}
}

View File

@ -0,0 +1,207 @@
package li.strolch.plc.core.hw.i2c;
import static li.strolch.utils.helper.ByteHelper.asBinary;
import static li.strolch.utils.helper.ByteHelper.isBitSet;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.toHexString;
import java.io.IOException;
import java.util.*;
import li.strolch.plc.core.ConnectionState;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcConnection;
import li.strolch.plc.core.hw.gpio.PlcGpioController;
import com.pi4j.io.gpio.*;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PCF8574InputConnection extends PlcConnection {
private static final Logger logger = LoggerFactory.getLogger(PCF8574InputConnection.class);
private int i2cBusNr;
private byte address;
private int interruptBcmPinAddress;
private PinState interruptChangeState;
private Map<String, Integer> positionsByAddress;
private I2CDevice inputDev;
private GpioPinDigitalInput interruptGpioPin;
private boolean[] states;
public PCF8574InputConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void initialize(Map<String, Object> parameters) {
if (!parameters.containsKey("i2cBus"))
throw new IllegalArgumentException("Missing param i2cBus");
if (!parameters.containsKey("address"))
throw new IllegalArgumentException("Missing param address");
if (!parameters.containsKey("interruptBcmPinAddress"))
throw new IllegalArgumentException("Missing param interruptBcmPinAddress");
this.i2cBusNr = (int) parameters.get("i2cBus");
String addressS = (String) parameters.get("address");
this.address = Integer.decode(addressS).byteValue();
this.interruptBcmPinAddress = (Integer) parameters.get("interruptBcmPinAddress");
this.interruptChangeState = PinState.valueOf((String) parameters.get("interruptChangeState"));
Map<String, Integer> positionsByAddress = new HashMap<>();
for (int i = 0; i < 8; i++)
positionsByAddress.put(this.id + "." + i, i);
this.positionsByAddress = Collections.unmodifiableMap(positionsByAddress);
logger.info("Configured PCF8574 Input on I2C address " + addressS + " on BCM Pin interrupt trigger "
+ this.interruptBcmPinAddress);
}
@Override
public void connect() {
if (this.connectionState == ConnectionState.Connected) {
logger.warn(this.id + ": Already connected");
return;
}
logger.info(this.id + ": Connecting...");
// initialize
try {
I2CBus i2cBus = I2CFactory.getInstance(this.i2cBusNr);
this.inputDev = i2cBus.getDevice(this.address);
logger.info("Connected to I2C Device at address 0x" + toHexString(this.address) + " on I2C Bus "
+ this.i2cBusNr);
} catch (Exception e) {
logger.error("Failed to connect to I2C Bus " + this.i2cBusNr + " and address " + toHexString(this.address),
e);
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg =
"Failed to connect to I2C Bus " + this.i2cBusNr + " and address " + toHexString(this.address) + ": "
+ getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
return;
}
try {
readInitialState();
} catch (Exception e) {
logger.error("Failed to read initial values from I2C Bus " + this.i2cBusNr + " and address " + toHexString(
this.address), e);
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg =
"Failed to read initial values from I2C Bus " + this.i2cBusNr + " and address " + toHexString(
this.address) + ": " + getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
return;
}
// register interrupt listener
try {
GpioController gpioController = PlcGpioController.getInstance();
Pin interruptPin = RaspiBcmPin.getPinByAddress(this.interruptBcmPinAddress);
if (interruptPin == null)
throw new IllegalStateException(
"RaspiBcmPin with address " + this.interruptBcmPinAddress + " does not exist!");
if (gpioController.getProvisionedPins().stream().map(GpioPin::getPin).anyMatch(interruptPin::equals))
throw new IllegalStateException("Pin " + interruptPin + " is already provisioned!");
this.interruptGpioPin = gpioController.provisionDigitalInputPin(interruptPin);
this.interruptGpioPin.removeAllListeners();
this.interruptGpioPin.addListener((GpioPinListenerDigital) this::handleInterrupt);
logger.info("Registered GPIO interrupt handler for BCM " + interruptPin);
logger.info(this.id + ": Is now connected.");
this.connectionState = ConnectionState.Connected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
} catch (Exception e) {
logger.error("Failed to register GPIO listener for BCM pin " + this.interruptBcmPinAddress, e);
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg =
"Failed to register GPIO listener for BCM pin " + this.interruptBcmPinAddress + ": "
+ getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
}
}
@Override
public void disconnect() {
if (this.interruptGpioPin != null) {
this.interruptGpioPin.removeAllListeners();
PlcGpioController.getInstance().unprovisionPin(this.interruptGpioPin);
}
this.inputDev = null;
this.connectionState = ConnectionState.Disconnected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
}
private void handleInterrupt(GpioPinDigitalStateChangeEvent event) {
try {
if (event.getState() == this.interruptChangeState)
handleNewState();
} catch (Exception e) {
logger.error("Failed to read new state for " + this.id, e);
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg = "Failed to read new state: " + getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
}
}
private void handleNewState() throws IOException {
byte data = (byte) this.inputDev.read();
boolean newState;
for (int i = 0; i < this.states.length; i++) {
newState = isBitSet(data, i);
if (this.states[i] != newState) {
this.states[i] = newState;
this.plc.notify(this.id + "." + i, newState);
}
}
}
private void readInitialState() throws IOException {
byte data = (byte) this.inputDev.read();
logger.info("Initial Value: " + asBinary(data));
this.states = new boolean[8];
for (int i = 0; i < this.states.length; i++) {
this.states[i] = isBitSet(data, i);
this.plc.notify(this.id + "." + i, this.states[i]);
}
}
@Override
public Set<String> getAddresses() {
return new TreeSet<>(this.positionsByAddress.keySet());
}
@Override
public void send(String address, Object value) {
throw new UnsupportedOperationException(getClass() + " does not support output!");
}
@Override
public String toString() {
return this.id;
}
}

View File

@ -0,0 +1,139 @@
package li.strolch.plc.core.hw.i2c;
import static li.strolch.utils.helper.ByteHelper.clearBit;
import static li.strolch.utils.helper.ByteHelper.setBit;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.toHexString;
import java.util.*;
import li.strolch.plc.core.ConnectionState;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.PlcConnection;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PCF8574OutputConnection extends PlcConnection {
private static final Logger logger = LoggerFactory.getLogger(PCF8574OutputConnection.class);
private int i2cBusNr;
private byte state;
private byte address;
private I2CDevice outputDev;
private Map<String, Integer> positionsByAddress;
public PCF8574OutputConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void initialize(Map<String, Object> parameters) {
if (!parameters.containsKey("i2cBus"))
throw new IllegalArgumentException("Missing param i2cBus");
if (!parameters.containsKey("address"))
throw new IllegalArgumentException("Missing param address");
this.i2cBusNr = (int) parameters.get("i2cBus");
String addressS = (String) parameters.get("address");
this.address = Integer.decode(addressS).byteValue();
Map<String, Integer> positionsByAddress = new HashMap<>();
for (int i = 0; i < 8; i++)
positionsByAddress.put(this.id + "." + i, i);
this.positionsByAddress = Collections.unmodifiableMap(positionsByAddress);
logger.info("Configured PCF8574 Output on I2C address " + addressS);
}
@Override
public void connect() {
if (this.connectionState == ConnectionState.Connected) {
logger.warn(this.id + ": Already connected");
return;
}
logger.info(this.id + ": Connecting...");
// initialize
try {
I2CBus i2cBus = I2CFactory.getInstance(i2cBusNr);
this.outputDev = i2cBus.getDevice(address);
// default is all outputs off, i.e. 1
this.state = (byte) 0xff;
this.outputDev.write(this.state);
logger.info("Connected to I2C Device at address 0x" + toHexString(this.address) + " on I2C Bus "
+ this.i2cBusNr);
logger.info(this.id + ": Is now connected.");
this.connectionState = ConnectionState.Connected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
} catch (Exception e) {
logger.error("Failed to connect to I2C Bus " + this.i2cBusNr + " and address " + toHexString(this.address),
e);
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg =
"Failed to connect to I2C Bus " + this.i2cBusNr + " and address " + toHexString(this.address) + ": "
+ getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
}
}
@Override
public void disconnect() {
this.outputDev = null;
this.connectionState = ConnectionState.Disconnected;
this.connectionStateMsg = "-";
this.plc.notifyConnectionStateChanged(this);
}
@Override
public Set<String> getAddresses() {
return new TreeSet<>(this.positionsByAddress.keySet());
}
@Override
public void send(String address, Object value) {
assertConnected();
Integer pos = this.positionsByAddress.get(address);
if (pos == null)
throw new IllegalStateException("Address is illegal " + address);
boolean high = (boolean) value;
try {
byte newState;
if (high)
newState = clearBit(this.state, pos);
else
newState = setBit(this.state, pos);
this.outputDev.write(newState);
this.state = newState;
} catch (Exception e) {
this.connectionState = ConnectionState.Failed;
this.connectionStateMsg =
"Failed to write to I2C address: " + address + ": " + getExceptionMessageWithCauses(e);
this.plc.notifyConnectionStateChanged(this);
throw new IllegalStateException("Failed to write to I2C address " + address, e);
}
}
@Override
public String toString() {
return this.id;
}
}

View File

@ -0,0 +1,25 @@
package li.strolch.plc.core.search;
import static li.strolch.utils.helper.StringHelper.isEmpty;
import li.strolch.plc.core.PlcConstants;
import li.strolch.search.ResourceSearch;
public class PlcConnectionSearch extends ResourceSearch {
public PlcConnectionSearch() {
types(PlcConstants.TYPE_PLC_CONNECTION);
}
public PlcConnectionSearch stringQuery(String value) {
if (isEmpty(value))
return this;
value = value.trim();
String[] values = value.split(" ");
where(id().containsIgnoreCase(values) //
.or(name().containsIgnoreCase(values)));
return this;
}
}

View File

@ -0,0 +1,25 @@
package li.strolch.plc.core.search;
import static li.strolch.utils.helper.StringHelper.isEmpty;
import li.strolch.plc.core.PlcConstants;
import li.strolch.search.ResourceSearch;
public class PlcLogicalDeviceSearch extends ResourceSearch {
public PlcLogicalDeviceSearch() {
types(PlcConstants.TYPE_PLC_LOGICAL_DEVICE);
}
public PlcLogicalDeviceSearch stringQuery(String value) {
if (isEmpty(value))
return this;
value = value.trim();
String[] values = value.split(" ");
where(id().containsIgnoreCase(values) //
.or(name().containsIgnoreCase(values)));
return this;
}
}

View File

@ -0,0 +1,18 @@
package li.strolch.plc.core.search;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.hw.PlcAddress;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.search.SearchResult;
import li.strolch.search.StrolchValueSearch;
public class PlcVirtualAddressesSearch extends StrolchValueSearch<PlcAddress> {
public SearchResult<PlcAddress> search(ComponentContainer container, PrivilegeContext ctx) {
assertHasPrivilege(ctx);
PlcHandler plcHandler = container.getComponent(PlcHandler.class);
return new SearchResult<>(plcHandler.getVirtualAddresses().stream());
}
}

View File

@ -0,0 +1,55 @@
package li.strolch.plc.core.service;
import static li.strolch.plc.core.PlcConstants.*;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.hw.PlcAddress;
import li.strolch.plc.core.hw.PlcAddressType;
import com.google.gson.JsonObject;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.service.JsonServiceArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
public class SendPlcAddressActionService extends AbstractService<JsonServiceArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public JsonServiceArgument getArgumentInstance() {
return new JsonServiceArgument();
}
@Override
protected ServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
JsonObject jsonObject = arg.jsonElement.getAsJsonObject();
PlcAddressType addressType = PlcAddressType.valueOf(jsonObject.get(PARAM_TYPE).getAsString());
String resource = jsonObject.get(PARAM_RESOURCE).getAsString();
String action = jsonObject.get(PARAM_ACTION).getAsString();
String valueS = jsonObject.get(PARAM_VALUE).getAsString();
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
PlcHandler plcHandler = getComponent(PlcHandler.class);
PlcAddress plcAddress = plcHandler.getPlcAddress(resource, action);
Object value = plcAddress.valueType.parseStringValue(valueS);
if (addressType == PlcAddressType.Telegram) {
plcHandler.send(resource, action, value);
} else if (addressType == PlcAddressType.Notification) {
plcHandler.notify(resource, action, value);
} else {
throw new UnsupportedOperationException("Unhandled address type " + addressType);
}
if (tx.needsCommit())
tx.commitOnClose();
}
return ServiceResult.success();
}
}

View File

@ -0,0 +1,58 @@
package li.strolch.plc.core.service;
import static li.strolch.plc.core.PlcConstants.TYPE_PLC_CONNECTION;
import li.strolch.plc.core.ConnectionState;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.hw.PlcConnection;
import li.strolch.model.Resource;
import li.strolch.model.Tags;
import li.strolch.persistence.api.Operation;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.service.StringMapArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
import li.strolch.utils.dbc.DBC;
public class SetPlcConnectionStateService extends AbstractService<StringMapArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public StringMapArgument getArgumentInstance() {
return new StringMapArgument();
}
@Override
protected ServiceResult internalDoService(StringMapArgument arg) throws Exception {
String id = arg.map.get(Tags.Json.ID);
String stateS = arg.map.get(Tags.Json.STATE);
DBC.PRE.assertNotEmpty("id must be set!", id);
DBC.PRE.assertNotEmpty("state must be set!", stateS);
ConnectionState state = ConnectionState.valueOf(stateS);
if (state != ConnectionState.Connected && state != ConnectionState.Disconnected)
throw new IllegalArgumentException(
"Only " + ConnectionState.Connected + " and " + ConnectionState.Disconnected + " states allowed!");
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
Resource connectionRes = tx.getResourceBy(TYPE_PLC_CONNECTION, id, true);
tx.assertHasPrivilege(Operation.UPDATE, connectionRes);
PlcHandler plcHandler = getComponent(PlcHandler.class);
PlcConnection plcConnection = plcHandler.getPlc().getConnection(connectionRes.getId());
if (state == ConnectionState.Connected)
plcConnection.connect();
else
plcConnection.disconnect();
return ServiceResult.success();
}
}
}

View File

@ -0,0 +1,55 @@
package li.strolch.plc.core.service;
import static li.strolch.plc.core.PlcConstants.PARAM_STATE;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcServiceInitializer;
import li.strolch.plc.core.PlcState;
import li.strolch.service.StringMapArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
public class SetPlcStateService extends AbstractService<StringMapArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public StringMapArgument getArgumentInstance() {
return new StringMapArgument();
}
@Override
protected ServiceResult internalDoService(StringMapArgument arg) throws Exception {
PlcState newState = PlcState.valueOf(arg.map.get(PARAM_STATE));
PlcHandler plcHandler = getComponent(PlcHandler.class);
PlcServiceInitializer plcServiceInitializer = getComponent(PlcServiceInitializer.class);
switch (newState) {
case Stopped:
if (plcHandler.getPlcState() == PlcState.Stopped)
return ServiceResult.error("Already stopped");
plcServiceInitializer.stop();
plcHandler.stopPlc();
break;
case Started:
if (plcHandler.getPlcState() == PlcState.Started)
return ServiceResult.error("Already started");
plcHandler.startPlc();
plcServiceInitializer.start();
break;
case Configured:
if (!plcHandler.reconfigurePlc())
return ServiceResult.error(plcHandler.getPlcStateMsg());
break;
default:
throw new IllegalArgumentException("Can not switch to state " + newState);
}
return ServiceResult.success();
}
}

View File

@ -0,0 +1,6 @@
groupId=${project.groupId}
artifactId=${project.artifactId}
artifactVersion=${project.version}
scmRevision=${buildNumber}
scmBranch=${scmBranch}
buildTimestamp=${buildTimestamp}

View File

@ -0,0 +1,77 @@
package li.strolch.plc.core;
import static li.strolch.plc.core.PlcConstants.PARAM_VALUE;
import static li.strolch.plc.core.PlcConstants.TYPE_PLC_ADDRESS;
import java.util.concurrent.atomic.AtomicReference;
import li.strolch.model.Resource;
import li.strolch.model.parameter.BooleanParameter;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.hw.PlcConnection;
import li.strolch.privilege.model.Certificate;
import li.strolch.testbase.runtime.RuntimeMock;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PlcHandlerTest {
private static final Logger logger = LoggerFactory.getLogger(PlcHandlerTest.class);
private static final String SRC_RUNTIME = "src/test/resources/test-runtime";
private static final String TARGET_PATH = "target/" + PlcHandlerTest.class.getSimpleName();
private static RuntimeMock runtimeMock;
private static Certificate cert;
@BeforeClass
public static void beforeClass() throws Exception {
runtimeMock = new RuntimeMock().mockRuntime(TARGET_PATH, SRC_RUNTIME);
runtimeMock.startContainer();
cert = runtimeMock.loginAdmin();
}
@AfterClass
public static void afterClass() throws InterruptedException {
if (cert != null)
runtimeMock.logout(cert);
if (runtimeMock != null)
runtimeMock.destroyRuntime();
// wait for PLC's async updates to complete
Thread.sleep(100L);
}
@Test
public void shouldStartPlcHandler() throws InterruptedException {
PlcHandler plcHandler = runtimeMock.getComponent(PlcHandler.class);
Assert.assertEquals(PlcState.Started, plcHandler.getPlcState());
PlcConnection loggerOutput = plcHandler.getPlc().getConnection("loggerOutput");
Assert.assertEquals(ConnectionState.Connected, loggerOutput.getState());
PlcConnection barcodeReader = plcHandler.getPlc().getConnection("barcodeReader");
Assert.assertEquals(ConnectionState.Connected, barcodeReader.getState());
String plcAddressId = plcHandler.getPlcAddressId("PLC", "Started");
Assert.assertEquals("addrPlcStarted", plcAddressId);
try (StrolchTransaction tx = runtimeMock.openUserTx(cert, true)) {
Resource plcStartedAddr = tx.getResourceBy(TYPE_PLC_ADDRESS, plcAddressId, true);
BooleanParameter valueP = plcStartedAddr.getParameter(PARAM_VALUE, true);
Assert.assertEquals(true, valueP.getValue());
}
}
@Test
public void shouldNotifyPlcService() throws InterruptedException {
PlcHandler plcHandler = runtimeMock.getComponent(PlcHandler.class);
AtomicReference<String> value = new AtomicReference<>("");
plcHandler.registerListener("BarcodeReader", "Barcode", (address, v) -> value.set((String) v));
plcHandler.send("BarcodeReader", "ReadBarcode", "DoRead");
Assert.assertNotEquals("", value.get());
}
}

View File

@ -0,0 +1,113 @@
package li.strolch.plc.core.hw;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import li.strolch.plc.core.ConnectionState;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PlcTest {
private static final Logger logger = LoggerFactory.getLogger(PlcTest.class);
private static Plc plc;
// conveyor: on/off and occupied
private static TestPlcConnection conveyorCon;
private static AtomicBoolean stateOnOff;
private static PlcAddress positionOn;
private static PlcAddress positionOff;
private static PlcAddress positionOccupied;
@BeforeClass
public static void beforeClass() {
// plc
plc = new DefaultPlc();
// conveyor connection with keys
stateOnOff = new AtomicBoolean();
conveyorCon = new TestPlcConnection(plc, "Connection.Conveyor01",
new HashSet<>(asList("Conveyor.Occupied", "Conveyor.OnOff")), e -> stateOnOff.set((Boolean) e));
conveyorCon.initialize(Collections.emptyMap());
plc.addConnection(conveyorCon);
positionOn = new PlcAddress(PlcAddressType.Telegram, false, "Conveyor", "On", "Conveyor.OnOff",
PlcValueType.Boolean, true, false);
positionOff = new PlcAddress(PlcAddressType.Telegram, false, "Conveyor", "Off", "Conveyor.OnOff",
PlcValueType.Boolean, false, false);
positionOccupied = new PlcAddress(PlcAddressType.Notification, false, "Conveyor", "Occupied",
"Conveyor.Occupied", PlcValueType.Boolean, null, false);
plc.registerNotificationMapping(positionOccupied);
}
@Test
public void shouldTestConveyorOnOff() {
assertFalse(stateOnOff.get());
plc.send(positionOn);
assertTrue(stateOnOff.get());
plc.send(positionOff);
assertFalse(stateOnOff.get());
}
@Test
public void shouldTestConveyorOccupied() {
AtomicBoolean state = new AtomicBoolean(false);
plc.registerListener(positionOccupied, (key, value) -> state.set((Boolean) value));
conveyorCon.notify("Conveyor.Occupied", true);
assertTrue(state.get());
conveyorCon.notify("Conveyor.Occupied", false);
assertFalse(state.get());
}
static class TestPlcConnection extends PlcConnection {
private final Set<String> addresses;
private final Consumer<Object> sendHandler;
public TestPlcConnection(Plc plc, String id, Set<String> addresses, Consumer<Object> sendHandler) {
super(plc, id);
this.addresses = addresses;
this.sendHandler = sendHandler;
}
@Override
public void initialize(Map<String, Object> parameters) {
// no-op
}
@Override
public void connect() {
this.connectionState = ConnectionState.Connected;
}
@Override
public void disconnect() {
//
}
@Override
public Set<String> getAddresses() {
return this.addresses;
}
@Override
public void send(String address, Object value) {
this.sendHandler.accept(value);
}
public void notify(String address, Object value) {
this.plc.notify(address, value);
}
}
}

View File

@ -0,0 +1,29 @@
package li.strolch.plc.core.test;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
public class StartupPlcService extends PlcService {
public static final String PLC = "PLC";
public static final String STARTED = "Started";
public static final String STOPPED = "Stopped";
public StartupPlcService(ComponentContainer container, PlcHandler plcHandler) {
super(container, plcHandler);
}
@Override
public void start(StrolchTransaction tx) {
send(PLC, STARTED);
super.start(tx);
}
@Override
public void stop() {
send(PLC, STOPPED);
super.stop();
}
}

View File

@ -0,0 +1,24 @@
package li.strolch.plc.core.test;
import java.util.ArrayList;
import java.util.List;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
import li.strolch.plc.core.PlcServiceInitializer;
public class TestPlcServiceInitializer extends PlcServiceInitializer {
public TestPlcServiceInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
protected List<PlcService> getPlcServices(PlcHandler plcHandler) {
ArrayList<PlcService> plcServices = new ArrayList<>();
plcServices.add(new StartupPlcService(getContainer(), plcHandler));
plcServices.add(new TogglePlcService(getContainer(), plcHandler));
return plcServices;
}
}

View File

@ -0,0 +1,57 @@
package li.strolch.plc.core.test;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
import li.strolch.privilege.model.PrivilegeContext;
public class TogglePlcService extends PlcService {
public static final String TOGGLER = "Toggler";
public static final String ON = "On";
public static final String OFF = "Off";
private boolean on;
private ScheduledFuture<?> toggler;
public TogglePlcService(ComponentContainer container, PlcHandler plcHandler) {
super(container, plcHandler);
}
@Override
public void start(StrolchTransaction tx) {
this.on = getAddressState(tx, TOGGLER, ON);
this.toggler = this.scheduleAtFixedRate(this::toggle, 10, 10, TimeUnit.SECONDS);
}
@Override
public void stop() {
if (this.toggler != null)
this.toggler.cancel(true);
this.toggler = null;
}
private void toggle(PrivilegeContext ctx) {
if (this.on) {
logger.info("Toggling Toggle to off!");
send(TOGGLER, OFF);
this.on = false;
} else {
logger.info("Toggling Toggle to on!");
send(TOGGLER, ON);
this.on = true;
}
}
@Override
protected void handleFailedSchedule(Exception e) {
if (this.toggler != null)
this.toggler.cancel(true);
logger.error("Execution of Toggler failed", e);
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d [%thread] %-5level %class{36}:%line %method - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<Privilege>
<Container>
<Parameters>
<!-- parameters for the container itself -->
<Parameter name="secretKey" value="changeMe"/>
<Parameter name="secretSalt" value="changeMe"/>
<Parameter name="persistSessions" value="true"/>
<Parameter name="autoPersistOnUserChangesData" value="false"/>
<Parameter name="privilegeConflictResolution" value="MERGE"/>
</Parameters>
<EncryptionHandler class="li.strolch.privilege.handler.DefaultEncryptionHandler">
<Parameters>
<!-- WARNING: If you change iterations or keyLength, then all passwords are invalid -->
<!-- default algorithm is: PBKDF2WithHmacSHA512 -->
<Parameter name="hashAlgorithm" value="PBKDF2WithHmacSHA512" />
<!-- default iterations: 200000 -->
<Parameter name="hashIterations" value="10000" />
<!-- default key length: 256 -->
<Parameter name="hashKeyLength" value="256" />
</Parameters>
</EncryptionHandler>
<PersistenceHandler class="li.strolch.privilege.handler.XmlPersistenceHandler">
<Parameters>
<Parameter name="usersXmlFile" value="PrivilegeUsers.xml"/>
<Parameter name="rolesXmlFile" value="PrivilegeRoles.xml"/>
</Parameters>
</PersistenceHandler>
<UserChallengeHandler class="li.strolch.privilege.handler.MailUserChallengeHandler">
</UserChallengeHandler>
</Container>
<Policies>
<Policy name="DefaultPrivilege" class="li.strolch.privilege.policy.DefaultPrivilege"/>
<Policy name="ModelPrivilege" class="li.strolch.runtime.privilege.ModelPrivilege" />
<Policy name="RoleAccessPrivilege" class="li.strolch.privilege.policy.RoleAccessPrivilege"/>
<Policy name="UserAccessPrivilege" class="li.strolch.privilege.policy.UserAccessPrivilege"/>
<Policy name="UserSessionAccessPrivilege" class="li.strolch.privilege.policy.UsernameFromCertificatePrivilege"/>
</Policies>
</Privilege>

View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<Roles>
<!--
Internal
-->
<Role name="Admin">
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
<Role name="agent">
<Privilege name="li.strolch.privilege.handler.SystemAction" policy="DefaultPrivilege">
<Allow>li.strolch.runtime.privilege.StrolchSystemAction</Allow>
<Allow>li.strolch.runtime.privilege.StrolchSystemActionWithResult</Allow>
</Privilege>
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeAction" policy="DefaultPrivilege">
<Allow>Persist</Allow>
<Allow>PersistSessions</Allow>
<Allow>GetCertificates</Allow>
</Privilege>
<Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeModifyUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeGetUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
</Roles>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<Users>
<User userId="U10" username="admin" password="cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344" salt="61646d696e">
<Firstname>Admin</Firstname>
<Lastname>Admin</Lastname>
<State>ENABLED</State>
<Locale>en-GB</Locale>
<Roles>
<Role>Admin</Role>
</Roles>
</User>
<User userId="S01" username="agent">
<State>SYSTEM</State>
<Roles>
<Role>agent</Role>
</Roles>
</User>
</Users>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<StrolchConfiguration>
<env id="dev">
<Runtime>
<applicationName>Strolch PLC</applicationName>
<Properties>
<locale>en</locale>
<verbose>false</verbose>
<timezone>Europe/Zurich</timezone>
</Properties>
</Runtime>
<Component>
<name>PrivilegeHandler</name>
<api>li.strolch.runtime.privilege.PrivilegeHandler</api>
<impl>li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler</impl>
<Properties>
<privilegeConfigFile>PrivilegeConfig.xml</privilegeConfigFile>
</Properties>
</Component>
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<realms>defaultRealm</realms>
<dataStoreMode>TRANSIENT</dataStoreMode>
<dataStoreFile>defaultModel.xml</dataStoreFile>
<enableObserverUpdates>true</enableObserverUpdates>
</Properties>
</Component>
<Component>
<name>ServiceHandler</name>
<api>li.strolch.service.api.ServiceHandler</api>
<impl>li.strolch.service.api.DefaultServiceHandler</impl>
<depends>RealmHandler</depends>
<depends>PrivilegeHandler</depends>
<Properties>
<verbose>false</verbose>
</Properties>
</Component>
<Component>
<name>PolicyHandler</name>
<api>li.strolch.policy.PolicyHandler</api>
<impl>li.strolch.policy.DefaultPolicyHandler</impl>
<Properties>
<readPolicyFile>true</readPolicyFile>
</Properties>
</Component>
<Component>
<name>SessionHandler</name>
<api>li.strolch.rest.StrolchSessionHandler</api>
<impl>li.strolch.rest.DefaultStrolchSessionHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<session.ttl.minutes>30</session.ttl.minutes>
<session.reload>true</session.reload>
</Properties>
</Component>
<Component>
<name>PlcHandler</name>
<api>li.strolch.plc.core.PlcHandler</api>
<impl>li.strolch.plc.core.DefaultPlcHandler</impl>
<depends>RealmHandler</depends>
<Properties>
<plcClass>li.strolch.plc.core.hw.DefaultPlc</plcClass>
</Properties>
</Component>
<Component>
<name>PlcServiceInitializer</name>
<api>li.strolch.plc.core.PlcServiceInitializer</api>
<impl>li.strolch.plc.core.test.TestPlcServiceInitializer</impl>
<depends>PlcHandler</depends>
<Properties>
</Properties>
</Component>
<Component>
<name>PostInitializer</name>
<api>li.strolch.agent.api.PostInitializer</api>
<impl>li.strolch.plc.core.PlcPostInitializer</impl>
<depends>PlcServiceInitializer</depends>
<Properties>
</Properties>
</Component>
</env>
</StrolchConfiguration>

View File

@ -0,0 +1,3 @@
<StrolchPolicies>
</StrolchPolicies>

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<StrolchModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd"
xsi:schemaLocation="https://strolch.li/xsd/StrolchModel-1.6.xsd StrolchModel-1.6.xsd">
<!--
Simple logger output connection
-->
<Resource Id="loggerOutput" Name="Logger PLC 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.LoggerOutConnection"/>
<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>
<!--
Simple Boolean connection
-->
<Resource Id="booleanConnection" Name="Single Boolean PLC 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.InMemoryBooleanConnection"/>
<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>
<!--
Barcode reader connection, currently place holder with RandomString
-->
<Resource Id="barcodeReader" Name="Barcode Reader 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.RandomStringConnection"/>
<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>
<!--
Simple toggler device
-->
<Resource Id="toggler" Name="Toggler 01" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Entry"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress" Value="addrTogglerOn"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telToggleTogglerOn, telToggleTogglerOff"/>
</ParameterBag>
</Resource>
<Resource Id="addrTogglerOn" Name="Toggler - On" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telToggleTogglerOn" Name="Toggler - On" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="On"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telToggleTogglerOff" Name="Toggler - Off" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="loggerOutput"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="Toggler"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Off"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<!--
PLC State
-->
<Resource Id="plc" Name="PLC" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="Startup"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="999999"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress"
Value="addrPlcStarted"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telPlcStarted, telPlcStopped"/>
</ParameterBag>
</Resource>
<Resource Id="addrPlcStarted" Name="PLC - Started" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Started"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telPlcStarted" Name="PLC - Started" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Started"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="true"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
</Resource>
<Resource Id="telPlcStopped" Name="PLC - Stopped" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="booleanConnection"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="PLC"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Stopped"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="Boolean"/>
<Parameter Id="value" Name="Value" Type="Boolean" Value="false"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="20"/>
</ParameterBag>
</Resource>
<!--
BarcodeReader
-->
<Resource Id="BarcodeReader" Name="BarcodeReader" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="group" Name="Group" Type="String" Value="BarcodeReader"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList" Interpretation="Resource-Ref" Uom="PlcAddress"
Value="addrBarcodeReader"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList" Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="telReadBarcode"/>
</ParameterBag>
</Resource>
<Resource Id="addrBarcodeReader" Name="BarcodeReader - Barcode" Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="barcodeReader"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BarcodeReader"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="Barcode"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="String"/>
<Parameter Id="value" Name="Value" Type="String" Value=""/>
<Parameter Id="index" Name="Index" Type="Integer" Value="110"/>
</ParameterBag>
</Resource>
<Resource Id="telReadBarcode" Name="BarcodeReader - ReadBarcode" Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="address" Name="HW Address" Type="String" Interpretation="PlcConnection" Value="barcodeReader"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress" Type="String" Value="BarcodeReader"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String" Value="ReadBarcode"/>
<Parameter Id="valueType" Name="Value Type" Type="String" Interpretation="Interpretation" Uom="PlcValueType" Value="String"/>
<Parameter Id="value" Name="Value" Type="String" Value="DoRead"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="70"/>
</ParameterBag>
</Resource>
</StrolchModel>

View File

@ -0,0 +1,2 @@
*.dat
*.log

141
strolch-plc-rest/pom.xml Normal file
View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>strolch-plc-rest</artifactId>
<name>strolch-plc-rest</name>
<packaging>jar</packaging>
<url>https://github.com/4treesCH/strolch-plc</url>
<scm>
<connection>scm:git:git@github.com:4treesCH/strolch-plc.git</connection>
<developerConnection>scm:git:git@github.com:4treesCH/strolch-plc.git</developerConnection>
<url>https://github.com/4treesCH/strolch-plc</url>
</scm>
<properties>
<!-- properties -->
</properties>
<dependencies>
<!-- base -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Strolch -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.utils</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.model</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.agent</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.service</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.rest</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.websocket</artifactId>
</dependency>
<!-- PLC -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-core</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-client</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/componentVersion.properties</exclude>
</excludes>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/componentVersion.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,64 @@
package li.strolch.plc.rest;
import static java.util.Comparator.comparing;
import static li.strolch.rest.StrolchRestfulConstants.DATA;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import li.strolch.plc.core.hw.PlcAddress;
import li.strolch.plc.core.search.PlcVirtualAddressesSearch;
import li.strolch.plc.core.service.SendPlcAddressActionService;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants;
import li.strolch.rest.helper.ResponseUtil;
import li.strolch.service.JsonServiceArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
@Path("plc/addresses")
public class PlcAddresses {
@GET
@Path("virtual")
@Produces(MediaType.APPLICATION_JSON)
public Response getVirtualAddresses(@Context HttpServletRequest request, @QueryParam("query") String query) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
PrivilegeContext ctx = RestfulStrolchComponent.getInstance().getPrivilegeHandler().validate(cert);
JsonArray result = new PlcVirtualAddressesSearch()
.search(RestfulStrolchComponent.getInstance().getAgent().getContainer(), ctx)
.orderBy(comparing((PlcAddress p) -> p.resource).thenComparing(p -> p.action))
.map(PlcModelVisitor::plcAddressToJson).asStream()
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
return ResponseUtil.toResponse(DATA, result);
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response sendAddressAction(@Context HttpServletRequest request, String data) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
JsonObject jsonObject = new JsonParser().parse(data).getAsJsonObject();
SendPlcAddressActionService svc = new SendPlcAddressActionService();
JsonServiceArgument arg = svc.getArgumentInstance();
arg.jsonElement = jsonObject;
// call service
ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
ServiceResult svcResult = svcHandler.doService(cert, svc, arg);
return ResponseUtil.toResponse(svcResult);
}
}

View File

@ -0,0 +1,72 @@
package li.strolch.plc.rest;
import static li.strolch.plc.rest.PlcModelVisitor.plcConnectionToJson;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.google.gson.JsonObject;
import li.strolch.model.Tags;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.search.PlcConnectionSearch;
import li.strolch.plc.core.service.SetPlcConnectionStateService;
import li.strolch.privilege.model.Certificate;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants;
import li.strolch.rest.helper.ResponseUtil;
import li.strolch.service.StringMapArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
import li.strolch.utils.collections.Paging;
@Path("plc/connections")
public class PlcConnectionsResource {
private static String getContext() {
StackTraceElement element = new Throwable().getStackTrace()[1];
return element.getClassName() + "." + element.getMethodName();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getConnections(@Context HttpServletRequest request, @QueryParam("query") String query,
@QueryParam("offset") @DefaultValue("0") int offset, @QueryParam("limit") @DefaultValue("20") int limit) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
Paging<JsonObject> paging;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getContext())) {
paging = new PlcConnectionSearch() //
.stringQuery(query) //
.search(tx) //
.orderByName() //
.visitor(plcConnectionToJson()) //
.toPaging(offset, limit);
}
return ResponseUtil.toResponse(paging);
}
@PUT
@Path("{id}/state/{state}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response setState(@Context HttpServletRequest request, @PathParam("id") String id,
@PathParam("state") String state) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
SetPlcConnectionStateService svc = new SetPlcConnectionStateService();
StringMapArgument arg = svc.getArgumentInstance();
arg.map.put(Tags.Json.ID, id);
arg.map.put(Tags.Json.STATE, state);
// call service
ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
ServiceResult svcResult = svcHandler.doService(cert, svc, arg);
return ResponseUtil.toResponse(svcResult);
}
}

View File

@ -0,0 +1,124 @@
package li.strolch.plc.rest;
import static java.util.Comparator.comparing;
import static li.strolch.plc.core.PlcConstants.*;
import static li.strolch.plc.rest.PlcModelVisitor.*;
import static li.strolch.rest.StrolchRestfulConstants.DATA;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import li.strolch.model.Resource;
import li.strolch.model.Tags;
import li.strolch.persistence.api.Operation;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.search.PlcLogicalDeviceSearch;
import li.strolch.privilege.model.Certificate;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants;
import li.strolch.rest.helper.ResponseUtil;
import li.strolch.utils.collections.MapOfLists;
@Path("plc/logicalDevices")
public class PlcLogicalDevicesResource {
private static String getContext() {
StackTraceElement element = new Throwable().getStackTrace()[1];
return element.getClassName() + "." + element.getMethodName();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getLogicalDevices(@Context HttpServletRequest request, @QueryParam("query") String query) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
JsonArray dataJ = new JsonArray();
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getContext())) {
MapOfLists<String, Resource> devicesByGroup = new PlcLogicalDeviceSearch() //
.stringQuery(query) //
.search(tx) //
.orderBy(comparing((Resource o) -> o.getParameter(PARAM_GROUP).getValue())
.thenComparing(o -> o.getParameter(PARAM_INDEX).getValue())) //
.toMapOfLists(r -> r.hasParameter(PARAM_GROUP) ?
r.getParameter(PARAM_GROUP).getValueAsString() :
"default");
for (String group : devicesByGroup.keySet()) {
List<Resource> devices = devicesByGroup.getList(group);
JsonObject groupJ = new JsonObject();
groupJ.addProperty(Tags.Json.NAME, group);
groupJ.add(DATA, devices.stream().map(e -> e.accept(plcLogicalDeviceToJson()))
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll));
dataJ.add(groupJ);
}
}
return ResponseUtil.toResponse(DATA, dataJ);
}
@GET
@Path("{id}/addresses")
@Produces(MediaType.APPLICATION_JSON)
public Response getAddresses(@Context HttpServletRequest request, @PathParam("id") String id) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
JsonArray dataJ;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getContext())) {
Resource plcLogicalDevice = tx.getResourceBy(TYPE_PLC_LOGICAL_DEVICE, id, true);
tx.assertHasPrivilege(Operation.GET, plcLogicalDevice);
dataJ = tx.getResourcesByRelation(plcLogicalDevice, PARAM_ADDRESSES, true).stream()
.map(e -> e.accept(plcAddressToJson())).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
}
return ResponseUtil.toResponse(DATA, dataJ);
}
@GET
@Path("{id}/notifications")
@Produces(MediaType.APPLICATION_JSON)
public Response getNotifications(@Context HttpServletRequest request, @PathParam("id") String id) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
JsonArray dataJ;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getContext())) {
Resource plcLogicalDevice = tx.getResourceBy(TYPE_PLC_LOGICAL_DEVICE, id, true);
tx.assertHasPrivilege(Operation.GET, plcLogicalDevice);
dataJ = tx.getResourcesByRelation(plcLogicalDevice, PARAM_ADDRESSES, true).stream()
.map(e -> e.accept(plcAddressToJson())).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
}
return ResponseUtil.toResponse(DATA, dataJ);
}
@GET
@Path("{id}/telegrams")
@Produces(MediaType.APPLICATION_JSON)
public Response getTelegrams(@Context HttpServletRequest request, @PathParam("id") String id) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
JsonArray dataJ;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getContext())) {
Resource plcLogicalDevice = tx.getResourceBy(TYPE_PLC_LOGICAL_DEVICE, id, true);
tx.assertHasPrivilege(Operation.GET, plcLogicalDevice);
dataJ = tx.getResourcesByRelation(plcLogicalDevice, PARAM_TELEGRAMS, true).stream()
.map(e -> e.accept(plcTelegramToJson())).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
}
return ResponseUtil.toResponse(DATA, dataJ);
}
}

View File

@ -0,0 +1,68 @@
package li.strolch.plc.rest;
import static li.strolch.plc.core.PlcConstants.*;
import static java.util.Comparator.comparing;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import li.strolch.plc.core.hw.PlcAddress;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import li.strolch.model.Tags;
import li.strolch.model.json.StrolchRootElementToJsonVisitor;
import li.strolch.model.parameter.Parameter;
public class PlcModelVisitor {
public static StrolchRootElementToJsonVisitor toJson() {
return new StrolchRootElementToJsonVisitor().withoutPolicies();
}
public static StrolchRootElementToJsonVisitor toJsonFlat() {
return toJson().flat();
}
public static StrolchRootElementToJsonVisitor plcConnectionToJson() {
return toJsonFlat().resourceHook((connectionR, connectionJ) -> {
// add the custom parameters with keys for the id, name and value, so we can show them on the UI
JsonArray parametersJ = new JsonArray();
connectionR.getParameterBag(BAG_PARAMETERS, true).getParameters().stream()
.sorted(comparing(Parameter::getIndex))
.filter(p -> !(p.getId().equals(PARAM_STATE) || p.getId().equals(PARAM_STATE_MSG) || p.getId()
.equals(PARAM_CLASS_NAME))).forEach(parameter -> {
JsonObject paramJ = new JsonObject();
paramJ.addProperty(Tags.Json.ID, parameter.getId());
paramJ.addProperty(Tags.Json.NAME, parameter.getName());
paramJ.addProperty(Tags.Json.VALUE, parameter.getValueAsString());
parametersJ.add(paramJ);
});
connectionJ.add(BAG_PARAMETERS, parametersJ);
});
}
public static StrolchRootElementToJsonVisitor plcLogicalDeviceToJson() {
return toJsonFlat();
}
public static StrolchRootElementToJsonVisitor plcAddressToJson() {
return toJsonFlat();
}
public static StrolchRootElementToJsonVisitor plcTelegramToJson() {
return toJsonFlat();
}
public static JsonObject plcAddressToJson(PlcAddress plcAddress) {
JsonObject addressJ = new JsonObject();
addressJ.addProperty(PARAM_ADDRESS, plcAddress.address);
addressJ.addProperty(PARAM_RESOURCE, plcAddress.resource);
addressJ.addProperty(PARAM_ACTION, plcAddress.action);
addressJ.addProperty(PARAM_ADDRESS_TYPE, plcAddress.type.name());
addressJ.addProperty(Tags.Json.ID, plcAddress.address);
addressJ.add(Tags.Json.VALUE, plcAddress.valueType.valueToJson(plcAddress.defaultValue));
addressJ.addProperty(Tags.Json.TYPE, plcAddress.valueType.name());
return addressJ;
}
}

View File

@ -0,0 +1,59 @@
package li.strolch.plc.rest;
import static li.strolch.plc.core.PlcConstants.*;
import static li.strolch.rest.StrolchRestfulConstants.DATA;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.service.SetPlcStateService;
import com.google.gson.JsonObject;
import li.strolch.privilege.model.Certificate;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants;
import li.strolch.rest.helper.ResponseUtil;
import li.strolch.service.StringMapArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
@Path("plc")
public class PlcResource {
@GET
@Path("state")
@Produces(MediaType.APPLICATION_JSON)
public Response getState(@Context HttpServletRequest request) {
PlcHandler plcHandler = RestfulStrolchComponent.getInstance().getComponent(PlcHandler.class);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(PARAM_STATE, plcHandler.getPlcState().name());
jsonObject.addProperty(PARAM_STATE_MSG, plcHandler.getPlcStateMsg());
if (plcHandler.getPlc() != null)
jsonObject.addProperty(PARAM_CLASS_NAME, plcHandler.getPlc().getClass().getName());
else
jsonObject.addProperty(PARAM_CLASS_NAME, "unknown");
return ResponseUtil.toResponse(DATA, jsonObject);
}
@PUT
@Path("state/{state}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response setState(@Context HttpServletRequest request, @PathParam("state") String state) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
SetPlcStateService svc = new SetPlcStateService();
StringMapArgument arg = svc.getArgumentInstance();
arg.map.put(PARAM_STATE, state);
// call service
ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
ServiceResult svcResult = svcHandler.doService(cert, svc, arg);
return ResponseUtil.toResponse(svcResult);
}
}

View File

@ -0,0 +1,21 @@
package li.strolch.plc.rest.ws;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.ObserverHandler;
import li.strolch.websocket.WebSocketClient;
import li.strolch.websocket.WebSocketObserverHandler;
public class PlcWebSocketClient extends WebSocketClient {
public PlcWebSocketClient(ComponentContainer container, Session session, EndpointConfig config) {
super(container, session, config);
}
@Override
protected WebSocketObserverHandler getWebSocketObserverHandler(ObserverHandler observerHandler) {
return new PlcWebSocketObserverHandler(observerHandler, this);
}
}

View File

@ -0,0 +1,31 @@
package li.strolch.plc.rest.ws;
import static li.strolch.plc.core.PlcConstants.TYPE_PLC_ADDRESS;
import static li.strolch.plc.rest.PlcModelVisitor.plcAddressToJson;
import java.util.Set;
import com.google.gson.JsonObject;
import li.strolch.agent.api.ObserverHandler;
import li.strolch.model.StrolchRootElement;
import li.strolch.websocket.WebSocketClient;
import li.strolch.websocket.WebSocketObserverHandler;
public class PlcWebSocketObserverHandler extends WebSocketObserverHandler {
public PlcWebSocketObserverHandler(ObserverHandler observerHandler, WebSocketClient client) {
super(observerHandler, client);
}
@Override
protected boolean filter(Set<String> observedTypesSet, StrolchRootElement e) {
return e.isResource() && e.getType().equals(TYPE_PLC_ADDRESS);
}
@Override
protected JsonObject toJson(StrolchRootElement e) {
if (e.isResource() && e.getType().equals(TYPE_PLC_ADDRESS))
return e.accept(plcAddressToJson());
return super.toJson(e);
}
}

View File

@ -0,0 +1,36 @@
package li.strolch.plc.rest.ws;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.rest.RestfulStrolchComponent;
@ServerEndpoint("/websocket/plc/observer")
public class WebSocketEndpoint {
private ConcurrentHashMap<Session, PlcWebSocketClient> clientMap = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
ComponentContainer container = RestfulStrolchComponent.getInstance().getContainer();
PlcWebSocketClient updateClient = new PlcWebSocketClient(container, session, config);
this.clientMap.put(session, updateClient);
session.addMessageHandler(updateClient);
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
PlcWebSocketClient webSocketClient = this.clientMap.remove(session);
if (webSocketClient != null)
webSocketClient.close(closeReason);
}
@OnError
public void onError(Session session, Throwable t) {
PlcWebSocketClient webSocketClient = this.clientMap.get(session);
if (webSocketClient != null)
webSocketClient.onError(t);
}
}