{"id":15197218,"url":"https://github.com/strolch-li/strolch-plc","last_synced_at":"2025-10-28T08:31:47.904Z","repository":{"id":84854024,"uuid":"236310892","full_name":"strolch-li/strolch-plc","owner":"strolch-li","description":"A software PLC based on Strolch's runtime","archived":false,"fork":false,"pushed_at":"2024-07-30T11:46:49.000Z","size":645,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2024-09-29T00:45:30.696Z","etag":null,"topics":["electronics","java","plc","plc-controller","plc-programming","raspberry-pi","raspberry-pi-3","strolch","webapp"],"latest_commit_sha":null,"homepage":"https://strolch.li/plc.html","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/strolch-li.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-26T12:43:57.000Z","updated_at":"2024-07-30T11:46:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"7b148856-5865-42e1-b632-902d80207379","html_url":"https://github.com/strolch-li/strolch-plc","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strolch-li%2Fstrolch-plc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strolch-li%2Fstrolch-plc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strolch-li%2Fstrolch-plc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strolch-li%2Fstrolch-plc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strolch-li","download_url":"https://codeload.github.com/strolch-li/strolch-plc/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219859904,"owners_count":16556031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["electronics","java","plc","plc-controller","plc-programming","raspberry-pi","raspberry-pi-3","strolch","webapp"],"created_at":"2024-09-28T00:45:48.541Z","updated_at":"2025-10-28T08:31:42.540Z","avatar_url":"https://github.com/strolch-li.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Strolch as a software PLC\n\n[![Contributors](https://img.shields.io/github/contributors/strolch-li/strolch-plc)](https://github.com/strolch-li/strolch-plc/graphs/contributors)\n[![License](https://img.shields.io/github/license/strolch-li/strolch-plc)](https://github.com/strolch-li/strolch-plc/blob/master/LICENSE)\n\nA soft real time PLC written in Strolch running on Strolch\n\nCheckout the documentation at https://strolch.li/plc/\n\n## Features\nStrolch PLC supports the following features:\n* Notification model\n* Raspberry Pi GPIO Input and Output addresses\n* I2C Input and Output addresses over PCF8574 chips\n* DataLogic Scanner connection\n* Virtual addresses\n* WebUI to observer and manipulate the addresses\n* WebSocket connection to Strolch Agent for notifying of changes\n* Simple two key addressing of hardware addresses to store semantics, e.g. `Convey01 - MotorOn`, instead of `i2cInput.dev01.0.0`\n\n## PlcService\nPlcServices are sub classes of `PlcService`. You can register for notifications, send keys and delay actions:\n\n    import java.util.concurrent.ScheduledFuture;\n    import java.util.concurrent.TimeUnit;\n    \n    import li.strolch.plc.core.PlcHandler;\n    import li.strolch.plc.core.PlcService;\n    import li.strolch.plc.model.PlcAddress;\n    \n    public class ConveyorPlcService extends PlcService {\n    \n      public static final int BOX_TRANSFER_DURATION = 30;\n    \n      private static final String R_CONVEYOR_01 = \"Conveyor01\";\n      private static final String A_START_BUTTON = \"StartButton\";\n      private static final String T_MOTOR_ON = \"MotorOn\";\n      private static final String T_MOTOR_OFF = \"MotorOff\";\n      private static final String A_BOX_DETECTED = \"BoxDetected\";\n    \n      private boolean motorOn;\n      private ScheduledFuture\u003c?\u003e motorStopTask;\n    \n      public ConveyorPlcService(PlcHandler plcHandler) {\n        super(plcHandler);\n      }\n    \n      @Override\n      public void handleNotification(PlcAddress address, Object value) {\n        String action = address.action;\n\n        boolean active = (boolean) value;\n    \n        if (action.equals(A_START_BUTTON)) {\n    \n          if (active) {\n            logger.info(\"Start button pressed. Starting motors...\");\n            send(R_CONVEYOR_01, T_MOTOR_ON);\n            this.motorOn = true;\n            scheduleStopTask();\n          }\n    \n        } else if (action.equals(A_BOX_DETECTED)) {\n    \n          if (active \u0026\u0026 this.motorOn) {\n            logger.info(\"Container detected, refreshing stop task...\");\n            scheduleStopTask();\n          }\n    \n        } else {\n          logger.info(\"Unhandled notification \" + address.toKeyAddress());\n        }\n      }\n    \n      private void scheduleStopTask() {\n        if (this.motorStopTask != null)\n          this.motorStopTask.cancel(false);\n        this.motorStopTask = schedule(this::stopMotor, BOX_TRANSFER_DURATION, TimeUnit.SECONDS);\n      }\n    \n      private void stopMotor() {\n        send(R_CONVEYOR_01, T_MOTOR_OFF);\n      }\n    \n      @Override\n      public void register() {\n        this.plcHandler.register(R_CONVEYOR_01, A_START_BUTTON, this);\n        this.plcHandler.register(R_CONVEYOR_01, A_BOX_DETECTED, this);\n        super.register();\n      }\n    \n      @Override\n      public void unregister() {\n        this.plcHandler.unregister(R_CONVEYOR_01, A_START_BUTTON, this);\n        this.plcHandler.unregister(R_CONVEYOR_01, A_BOX_DETECTED, this);\n        super.unregister();\n      }\n\nThis example registered for changes to the keys `Conveyor01 - StartButton` and \n`Conveyor01 - BoxDetected`. When the start button is pressed, then it sends the \nkeys `Conveyor01 - MotorOn` and schedules the motor off action with a delay of \n30s.\n\nThis class should then be registered in the `PlcServiceInitializer`:\n\n    public class CustomPlcServiceInitializer extends PlcServiceInitializer {\n    \n      public CustomPlcServiceInitializer(ComponentContainer container, String componentName) {\n        super(container, componentName);\n      }\n    \n      @Override\n      protected List\u003cPlcService\u003e getPlcServices(PlcHandler plcHandler) {\n        ArrayList\u003cPlcService\u003e plcServices = new ArrayList\u003c\u003e();\n    \n        StartupPlcService startupPlcService = new StartupPlcService(plcHandler);\n        ConveyorPlcService conveyorPlcService = new ConveyorPlcService(plcHandler);\n    \n        plcServices.add(conveyorPlcService);\n        plcServices.add(startupPlcService);\n    \n        return plcServices;\n      }\n\nAs one can see, there is little fuss for writing business logic, state is\npersisted automatically so it is visible in the UI, and the PlcServices can \nhandle their state as needed.\n\n## PlcAddress\nPlcAddresses store the value of a hardware address: \n\n      \u003cResource Id=\"A_Conveyor01-Occupied\" Name=\"Conveyor01 - Occupied\" Type=\"PlcAddress\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n          \u003cParameter Id=\"description\" Name=\"Description\" Type=\"String\" Index=\"5\" Value=\"Conveyor 1\"/\u003e\n          \u003cParameter Id=\"address\" Name=\"HW Address\" Type=\"String\" Interpretation=\"PlcConnection\" Index=\"10\" Value=\"i2cInput.dev01.0.0\"/\u003e\n          \u003cParameter Id=\"resource\" Name=\"Resource ID for PlcAddress\" Type=\"String\" Index=\"20\" Value=\"Conveyor01\"/\u003e\n          \u003cParameter Id=\"action\" Name=\"Action ID for PlcAddress\" Type=\"String\" Index=\"30\" Value=\"Occupied\"/\u003e\n          \u003cParameter Id=\"index\" Name=\"Index\" Type=\"Integer\" Index=\"40\" Value=\"10\"/\u003e\n          \u003cParameter Id=\"value\" Name=\"Value\" Type=\"Boolean\" Index=\"100\" Value=\"false\"/\u003e\n        \u003c/ParameterBag\u003e\n      \u003c/Resource\u003e\n\nThe two parameters `resource` and `action` are the local keys for the address, \nwhile `address` is used to find the actual connection and the address on that \nconnection.\n\n## PlcTelegram\nPlcTelegrams are used to store keys with a default value. For example it is \neasier to understand `Conveyor01 - MotorOn` instead of `Conveyor01 - Motor` with the value true or false.\n\n      \u003cResource Id=\"T_Conveyor01-MotorOn\" Name=\"Conveyor01 - MotorOn\" Type=\"PlcTelegram\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n          \u003cParameter Id=\"description\" Name=\"Description\" Type=\"String\" Index=\"5\" Value=\"Conveyor 1\"/\u003e\n          \u003cParameter Id=\"address\" Name=\"HW Address\" Type=\"String\" Interpretation=\"PlcConnection\" Index=\"10\" Value=\"i2cOutput.dev01.0.0\"/\u003e\n          \u003cParameter Id=\"resource\" Name=\"Resource ID for PlcAddress\" Type=\"String\" Index=\"20\" Value=\"Conveyor01\"/\u003e\n          \u003cParameter Id=\"action\" Name=\"Action ID for PlcAddress\" Type=\"String\" Index=\"30\" Value=\"MotorOn\"/\u003e\n          \u003cParameter Id=\"index\" Name=\"Index\" Type=\"Integer\" Index=\"40\" Value=\"10\"/\u003e\n          \u003cParameter Id=\"value\" Name=\"Value\" Type=\"Boolean\" Index=\"100\" Value=\"true\"/\u003e\n        \u003c/ParameterBag\u003e\n      \u003c/Resource\u003e\n\n## PlcLogicalDevice\nMultiple PlcAddresses can be grouped together into a PlcLogicalDevice for \nvisualization on the UI. In logistics a single conveyor might have multiple \nsensors and actors, e.g. the conveyor's motor, a light barrier and a scanner, \ngrouping them together makes it easier for debugging.\n\n      \u003cResource Id=\"D_MaterialFlow\" Name=\"MaterialFlow\" Type=\"PlcLogicalDevice\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n          \u003cParameter Id=\"description\" Name=\"Description\" Type=\"String\" Index=\"5\" Value=\"Material Flow\"/\u003e\n          \u003cParameter Id=\"group\" Name=\"Group\" Type=\"String\" Index=\"20\" Value=\"01 Material Flow\"/\u003e\n          \u003cParameter Id=\"index\" Name=\"Index\" Type=\"Integer\" Index=\"30\" Value=\"10\"/\u003e\n        \u003c/ParameterBag\u003e\n        \u003cParameterBag Id=\"relations\" Name=\"Relations\" Type=\"Relations\"\u003e\n          \u003cParameter Id=\"addresses\" Name=\"Addresses\" Type=\"StringList\" Interpretation=\"Resource-Ref\" Uom=\"PlcAddress\" Index=\"10\" Value=\"A_Conveyor01-Occupied, A_Conveyor02-Occupied, A_Conveyor03-Occupied, A_Conveyor04-Occupied, A_Conveyor01-MotorOn, A_Conveyor02-MotorOn, A_Conveyor03-MotorOn, A_Conveyor04-MotorOn, A_Conveyor03-Barcode, A_Conveyor03-On\"/\u003e\n          \u003cParameter Id=\"telegrams\" Name=\"Telegrams\" Type=\"StringList\" Interpretation=\"Resource-Ref\" Uom=\"PlcTelegram\" Index=\"20\" Value=\"T_Conveyor01-MotorOn, T_Conveyor01-MotorOff, T_Conveyor02-MotorOn, T_Conveyor02-MotorOff, T_Conveyor03-MotorOn, T_Conveyor03-MotorOff, T_Conveyor04-MotorOn, T_Conveyor04-MotorOff, T_Conveyor03-On, T_Conveyor03-Off\"/\u003e\n        \u003c/ParameterBag\u003e\n      \u003c/Resource\u003e\n\n## PlcConnections\nPlcConnections are used to model the actual hardware connections and define with\nwhich class the connection is to be established. The following shows some of the implementations:\n\n    \u003c!--\n        Raspberry GPIO BCM Address connection\n    --\u003e\n    \u003cResource Id=\"raspiBcmGpioOutput\" Name=\"Raspi BCM GPIO Output\" Type=\"PlcConnection\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"className\" Name=\"Connection Class\" Type=\"String\" Value=\"li.strolch.plc.core.hw.gpio.RaspiBcmGpioOutputConnection\"/\u003e\n            \u003cParameter Id=\"state\" Name=\"Connection State\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\" Value=\"Disconnected\"/\u003e\n            \u003cParameter Id=\"stateMsg\" Name=\"Connection State Msg\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\"\n                       Value=\"\"/\u003e\n            \u003cParameter Id=\"inverted\" Name=\"Inverted\" Type=\"Boolean\" Value=\"false\"/\u003e\n            \u003cParameter Id=\"bcmOutputPins\" Name=\"BCM Output Pins\" Type=\"IntegerList\" Value=\"27\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n    \u003cResource Id=\"raspiBcmGpioInput\" Name=\"Raspi BCM GPIO Input\" Type=\"PlcConnection\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"className\" Name=\"Connection Class\" Type=\"String\" Value=\"li.strolch.plc.core.hw.gpio.RaspiBcmGpioInputConnection\"/\u003e\n            \u003cParameter Id=\"state\" Name=\"Connection State\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\" Value=\"Disconnected\"/\u003e\n            \u003cParameter Id=\"stateMsg\" Name=\"Connection State Msg\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\"\n                       Value=\"\"/\u003e\n            \u003cParameter Id=\"inverted\" Name=\"Inverted\" Type=\"Boolean\" Value=\"true\"/\u003e\n            \u003cParameter Id=\"bcmInputPins\" Name=\"BCM Input Pins\" Type=\"IntegerList\" Value=\"4\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n\n    \u003c!--\n        I2C input connections\n    --\u003e\n    \u003cResource Id=\"i2cInput.dev01\" Name=\"PCF8574 Input 0x38\" Type=\"PlcConnection\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"className\" Name=\"Connection Class\" Type=\"String\" Value=\"li.strolch.plc.core.hw.i2c.PCF8574InputConnection\"/\u003e\n            \u003cParameter Id=\"state\" Name=\"Connection State\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\" Value=\"Disconnected\"/\u003e\n            \u003cParameter Id=\"stateMsg\" Name=\"Connection State Msg\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\"\n                       Value=\"\"/\u003e\n            \u003cParameter Id=\"i2cBus\" Name=\"I2C Bus\" Type=\"Integer\" Value=\"1\"/\u003e\n            \u003cParameter Id=\"addresses\" Name=\"Addresses\" Type=\"IntegerList\" Value=\"0x38\"/\u003e\n            \u003cParameter Id=\"verbose\" Name=\"Verbose\" Type=\"Boolean\" Value=\"true\"/\u003e\n            \u003cParameter Id=\"inverted\" Name=\"Inverted\" Type=\"Boolean\" Value=\"true\"/\u003e\n            \u003cParameter Id=\"interruptPinPullResistance\" Name=\"Raspi Interrupt PinPullResistance\" Type=\"String\" Value=\"PULL_DOWN\"/\u003e\n            \u003cParameter Id=\"interruptChangeState\" Name=\"Raspi Interrupt Change State\" Type=\"String\" Value=\"HIGH\"/\u003e\n            \u003cParameter Id=\"interruptBcmPinAddress\" Name=\"Raspi BCM Interrupt Pin\" Type=\"Integer\" Value=\"17\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n\n    \u003c!--\n        I2C output connections\n    --\u003e\n    \u003cResource Id=\"i2cOutput.dev01\" Name=\"PCF8574 Output 0x21\" Type=\"PlcConnection\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"className\" Name=\"Connection Class\" Type=\"String\" Value=\"li.strolch.plc.core.hw.i2c.PCF8574OutputConnection\"/\u003e\n            \u003cParameter Id=\"state\" Name=\"Connection State\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\" Value=\"Disconnected\"/\u003e\n            \u003cParameter Id=\"stateMsg\" Name=\"Connection State Msg\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\"\n                       Value=\"\"/\u003e\n            \u003cParameter Id=\"i2cBus\" Name=\"I2C Bus\" Type=\"Integer\" Value=\"1\"/\u003e\n            \u003cParameter Id=\"addresses\" Name=\"Addresses\" Type=\"IntegerList\" Value=\"0x21\"/\u003e\n            \u003cParameter Id=\"inverted\" Name=\"Inverted\" Type=\"Boolean\" Value=\"false\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n\n    \u003c!--\n        Barcode reader connection, currently place holder with RandomString\n    --\u003e\n    \u003cResource Id=\"dataLogicScanner\" Name=\"DataLogic Scanner Connection\" Type=\"PlcConnection\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"className\" Name=\"Connection Class\" Type=\"String\" Value=\"li.strolch.plc.core.hw.connections.DataLogicScannerConnection\"/\u003e\n            \u003cParameter Id=\"address\" Name=\"Scanner IP Address\" Type=\"String\" Value=\"192.168.1.249:51236\"/\u003e\n            \u003cParameter Id=\"readTimeout\" Name=\"Read Timeout (s)\" Type=\"Integer\" Value=\"60\"/\u003e\n            \u003cParameter Id=\"state\" Name=\"Connection State\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\" Value=\"Disconnected\"/\u003e\n            \u003cParameter Id=\"stateMsg\" Name=\"Connection State Msg\" Type=\"String\" Interpretation=\"Enumeration\" Uom=\"ConnectionState\"\n                       Value=\"\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n\n## Virtual Addresses\nIn some cases, especially in conjunction with a Strolch Agent as the main \nserver, it is necessary to also have virtual addresses, with which to perform\nnotifications. The following shows examples:\n\n    \u003cResource Id=\"addrPlcConnected\" Name=\"PLC - Connected\" Type=\"PlcAddress\"\u003e\n        \u003cParameterBag Id=\"parameters\" Name=\"Parameters\" Type=\"Parameters\"\u003e\n            \u003cParameter Id=\"address\" Name=\"HW Address\" Type=\"String\" Interpretation=\"PlcConnection\" Value=\"virtualString.plcServerConnected\"/\u003e\n            \u003cParameter Id=\"resource\" Name=\"Resource ID for PlcAddress\" Type=\"String\" Value=\"PLC\"/\u003e\n            \u003cParameter Id=\"action\" Name=\"Action ID for PlcAddress\" Type=\"String\" Value=\"ServerConnected\"/\u003e\n            \u003cParameter Id=\"valueType\" Name=\"Value Type\" Type=\"String\" Interpretation=\"Interpretation\" Uom=\"PlcValueType\" Value=\"String\"/\u003e\n            \u003cParameter Id=\"value\" Name=\"Value\" Type=\"String\" Value=\"false\"/\u003e\n            \u003cParameter Id=\"index\" Name=\"Index\" Type=\"Integer\" Value=\"5\"/\u003e\n        \u003c/ParameterBag\u003e\n    \u003c/Resource\u003e\n\n## More Information\n\nFind more to Strolch PLC at our website: https://strolch.li/plc.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrolch-li%2Fstrolch-plc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrolch-li%2Fstrolch-plc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrolch-li%2Fstrolch-plc/lists"}