diff --git a/content/development/maven-archetypes.md b/content/development/maven-archetypes.md index 930f0ef..140a582 100644 --- a/content/development/maven-archetypes.md +++ b/content/development/maven-archetypes.md @@ -6,8 +6,11 @@ weight: 40 ## Maven Archetypes Maven offers archetypes to generate new projects. Strolch offers the following archetypes, to create new projects: + * [li.strolch.mvn.archetype.main](/development/main-class-app) for Java main class applications -* [li.strolch.mvn.archetype.webapp](/development/web-app) for Java Web based applications using REST and Polymer 1.x as the frontend. +* [li.strolch.mvn.archetype.webapp](/development/web-app) for Java Web based applications using REST and Polymer 1.x as + the frontend. +* [li.strolch.mvn.archetype.plc](/plc/example-set-up) for Strolch PLC projects. To use the archetypes, clone the archetypes repository and install it locally: @@ -17,4 +20,4 @@ cd strolch-maven-archetypes mvn clean install ``` -Then follow one of the next steps to create the type of application you want. \ No newline at end of file +Then follow one of the next steps to create the type of application you want. diff --git a/content/plc/_index.md b/content/plc/_index.md index b4a3dc1..580c381 100644 --- a/content/plc/_index.md +++ b/content/plc/_index.md @@ -19,7 +19,7 @@ description in how to set up your own Strolch based PLC. Checkout the code at [GitHub](https://github.com/strolch-li/strolch-plc) -Strolch PLC is also deployed to Maven Central. Current version is 1.2.1. +Strolch PLC is also deployed to Maven Central. Current version is 1.2.2 and is using Strolch version 1.8.5. Currently, we have the following topics of discussion: diff --git a/content/plc/example-set-up.md b/content/plc/example-set-up.md index ec155eb..dd17c61 100644 --- a/content/plc/example-set-up.md +++ b/content/plc/example-set-up.md @@ -20,362 +20,60 @@ business logic and the PLC controls the I/Os. ![Strolch PLC Example](/assets/images/Strolch-Plc-Example.png) ## New Project -1. First create a new Strolch Web project using the Strolch Maven archetype -2. Now add the following Maven dependencies: -```xml - - - 1.8.4 - 1.2.2 - - - - - li.strolch - li.strolch.bom - pom - ${strolch.version} - import - - - li.strolch - strolch-plc-bom - pom - ${strolch.plc.version} - import - - - - - - - li.strolch - strolch-plc-core - - - li.strolch - strolch-plc-rest - - - li.strolch - strolch-plc-gw-client - - - +Create a new project using the PLC Strolch Maven Archetype: + +```shell +mvn archetype:generate \ + -DarchetypeGroupId=li.strolch \ + -DarchetypeArtifactId=li.strolch.mvn.archetype.plc \ + -DarchetypeVersion=0.1.0-SNAPSHOT \ + -DgroupId= \ + -DartifactId= \ + -Dversion= \ + -Dpackage= \ + -DappName="" + ``` -3. Add a bower dependency: `"strolch-wc-plc": "strolch-li/strolch-wc-plc#^0.3.20"` - to `src/main/webapp/bower.json` +This will create a multi-module project: - After adding the dependency, run `gulp` in the webapp directory. Gulp should - have been installed through the instructions from - the [development page](/development). +- The `-web` module contains the server code, which handles notifications from the PLC, and can send + telegrams to the PLC. +- The `-plc-web` module contains the PLC code, which connects to the server and communicates with the + low-level hardware. +- The `shared` modules contains classes shared by both projects (e.g. constants, etc.). -4. Now we need to add the PLC web views to our new project. This is added in - the `src/main/webapp/app/src/c-app.html` file. Add the following: +This project already contains a default PLC model in `-plc-web/runtime/data/`. -```html - - - +The following sections explains these files: - - +### strolch-plc-example-connections.xml - - - +This file defines the hardware connections. The following connections are implemented: -// add a new property to the WebSocket path for observing changes on the PLC -wsObserverPath: { - type: String, - value: function () { - return CustomWeb.baseWsPath + "/plc/observer"; - } -} -``` +* li.strolch.plc.core.hw.i2c.RSL366OverHorterI2c +* li.strolch.plc.core.hw.i2c.PCF8574InputConnection +* li.strolch.plc.core.hw.gpio.RaspiBcmGpioInputConnection +* li.strolch.plc.core.hw.gpio.RaspiBcmGpioOutputConnection +* li.strolch.plc.core.hw.i2c.Multi8BitI2cOutputConnection +* li.strolch.plc.core.hw.connections.DataLogicScannerConnection +* li.strolch.plc.core.hw.connections.LoggerOutConnection +* li.strolch.plc.core.hw.connections.RandomStringConnection -5. Don't forget to add the PLC Rest classes to your `ResourceConfig` +See their respective classes for details. -```java -@ApplicationPath("rest") -public class RestfulApplication extends ResourceConfig { +### strolch-plc-example.csv - public RestfulApplication() { +This file maps I/Os to Resources and Actions and is converted into Strolch `Resource` objects using +the `PlcAddressGenerator` class. - ... +In this example we will use simple Raspberry Pi GPIOs. For convenience, and also when sharing I/O definitions with +external partners, it is easier to use a CSV file to define the I/Os and then use the `PlcAddressGenerator` to generate +and validate the model. - // strolch plc services - packages(PlcConnectionsResource.class.getPackage().getName()); - - ... - } -} -``` - -6. Now we need to configure the PLC's runtime by - modifying `runtime/StrolchConfiguration.xml` and adding the following: - -```xml - - - - - - PlcHandler - li.strolch.plc.core.PlcHandler - li.strolch.plc.core.DefaultPlcHandler - RealmHandler - - - li.strolch.plc.core.hw.DefaultPlc - - - - - - PlcServiceInitializer - li.strolch.plc.core.PlcServiceInitializer - li.strolch.plc.example.CustomPlcServiceInitializer - PlcHandler - - - - - - - PlcGwClientHandler - li.strolch.plc.gw.client.PlcGwClientHandler - li.strolch.plc.gw.client.PlcGwClientHandler - PlcHandler - PlcServiceInitializer - - plc-01 - plc-01 - plc-01 - ws://localhost:8080/agent/websocket/strolch/plc - - - - - -``` - -7. Now we add the custom classes we just declared. - -**PlcServiceInitializer** - -```java -import java.util.ArrayList; -import java.util.List; - -import li.strolch.plc.example.services.*; -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 CustomPlcServiceInitializer extends PlcServiceInitializer { - - public CustomPlcServiceInitializer(ComponentContainer container, String componentName) { - super(container, componentName); - } - - @Override - protected List getPlcServices(PlcHandler plcHandler) { - ArrayList plcServices = new ArrayList<>(); - - StartupPlcService startupPlcService = new StartupPlcService(plcHandler); - ConveyorPlcService conveyorPlcService = new ConveyorPlcService(plcHandler); - - plcServices.add(conveyorPlcService); - plcServices.add(startupPlcService); - - return plcServices; - } -} -``` - -**PlcPostInitializer** -```java -import li.strolch.agent.api.ComponentContainer; -import li.strolch.plc.core.PlcPostInitializer; - -public class CustomPostInitializer extends PlcPostInitializer { - public CustomPostInitializer(ComponentContainer container, String componentName) { - super(container, componentName); - } - - // override the initialize(), start(), stop() and destroy() methods as needed -} -``` - -8. In the `CustomPlcServiceInitializer` we added two PlcServices, for which the - code is missing. The following are simple examples: - -**StartupPlcService** - -```java -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(PlcHandler plcHandler) { - super(plcHandler); - } - - @Override - public void start(StrolchTransaction tx) { - send(PLC, STARTED); - super.start(tx); - } - - @Override - public void stop() { - send(PLC, STOPPED); - super.stop(); - } -} -``` - -**ConveyorPlcService** - -```java - - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import li.strolch.plc.core.PlcHandler; -import li.strolch.plc.core.PlcService; -import li.strolch.plc.model.PlcAddress; - -public class ConveyorPlcService extends PlcService { - - public static final int BOX_TRANSFER_DURATION = 30; - - private static final String R_CONVEYOR_01 = "Conveyor01"; - private static final String A_START_BUTTON = "StartButton"; - private static final String T_MOTOR_ON = "MotorOn"; - private static final String T_MOTOR_OFF = "MotorOff"; - private static final String A_BOX_DETECTED = "BoxDetected"; - - private boolean motorOn; - private ScheduledFuture motorStopTask; - - public ConveyorPlcService(PlcHandler plcHandler) { - super(plcHandler); - } - - @Override - public void handleNotification(PlcAddress address, Object value) { - String resource = address.resource; - String action = address.action; - - if (!resource.equals("Conveyor01")) - throw new IllegalStateException("Unexpected resource " + resource); - - boolean active = (boolean) value; - - if (action.equals(A_START_BUTTON)) { - - if (active) { - logger.info("Start button pressed. Starting motors..."); - send(R_CONVEYOR_01, T_MOTOR_ON); - this.motorOn = true; - scheduleStopTask(); - } - - } else if (action.equals(A_BOX_DETECTED)) { - - if (active && this.motorOn) { - logger.info("Container detected, refreshing stop task..."); - scheduleStopTask(); - } - - } else { - logger.info("Unhandled notification " + address.toKeyAddress()); - } - } - - private void scheduleStopTask() { - if (this.motorStopTask != null) - this.motorStopTask.cancel(false); - this.motorStopTask = schedule(this::stopMotor, BOX_TRANSFER_DURATION, TimeUnit.SECONDS); - } - - private void stopMotor() { - send(R_CONVEYOR_01, T_MOTOR_OFF); - } - - @Override - public void register() { - this.plcHandler.register(R_CONVEYOR_01, A_START_BUTTON, this); - this.plcHandler.register(R_CONVEYOR_01, A_BOX_DETECTED, this); - super.register(); - } - - @Override - public void unregister() { - this.plcHandler.unregister(R_CONVEYOR_01, A_START_BUTTON, this); - this.plcHandler.unregister(R_CONVEYOR_01, A_BOX_DETECTED, this); - super.unregister(); - } -} -``` - -9. Now the last part is to add the model, i.e. PlcConnections, PlcAddresses and - PlcTelegrams. To have less configuration files and make it easier to - reconfigure at runtime, this data is stored in normal Strolch `Resources`. - - In this example we will use simple Raspberry Pi GPIOs. For convenience, and - also when sharing I/O definitions with external partners, it is easier to use - a CSV file to define the I/Os and then use the `PlcAddressGenerator` to - generate and validate the model. - - For this purpose in this example, we will use one conveyor with 2 inputs and - 1 output. The CSV file should have the following content: - -```csv -Description,Type,SubType,Device,Pin,Resource,Action1,Action2,Connection,DeviceId -Material Flow,Group,,,,,,,,MaterialFlow -Conveyor 1,Input,Pin,,4,Conveyor,Occupied,,raspiBcmGpioInput -Conveyor 1,Input,Pin,,17,Conveyor,BoxDetected,,raspiBcmGpioInput -Conveyor 1,Output,Pin,,18,Conveyor,MotorOn,MotorOff,raspiBcmGpioOutput -``` +For easier handling, use the following Google Sheet as a starting +point: https://docs.google.com/spreadsheets/d/10fgTfR3FZCVbQ5bbh0xB1u8rLIaw2KEyO45VMv7y5ho/edit?usp=sharing The CSV headers are as follows: @@ -392,13 +90,14 @@ The CSV headers are as follows: Scanner. The actions must be left empty as the keys Barcode (address), On and Off (telegrams) will be generated. * SubType → -* For Input and Output types → - * DevPin, DevPin0 → Generates the address as `..`. - DevPin0 decrements the Device and Pin values by one. - * Pin → Generates the address as `.`. -* For Virtual types → - * Boolean - * String + * For Input and Output types → + * DevPin, DevPin0 → Generates the address as `..`. + DevPin0 decrements the Device and Pin values by one for zero-indexed addressing. + * Pin → Generates the address as `.`. + * For Virtual types → + * Boolean + * String + * Integer * Device → Device number * Pin → The pin number on the device * Resource → The resource ID with which to notify the agent @@ -406,134 +105,53 @@ The CSV headers are as follows: * Action2 → The second action ID if required * Connection → The ID of the PlcConnection with which this I/O is attached * DeviceId → For type Group: Set the ID of this PlcLogicalDevice being generated +* Interted → For boolean inputs or outputs, if true, inverts the value +* Value → A default value, often used for virtual integer addresses +* Remote → if true, then the server will be notified of changes to this address When you use this file as input for the `PlcAddressGenerator`, then it will -generate PlcLogicalDevice, PlcAddress and PlcTelegram elements: +generate PlcLogicalDevice, PlcAddress and PlcTelegram elements. See the file `strolch-plc-example.xml` for an example. -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -The PlcLogicalDevice references the PlcAddress and PlcTelegram objects, and is +The `PlcLogicalDevice` references the `PlcAddress` and `PlcTelegram` objects, and is then used in the UI for grouping. -The PlcAddress is used to store the current value and defines the keys with +The `PlcAddress` is used to store the current value and defines the keys with which the agent will be notified -The PlcTelegram is used to store default values to send, for specific keys. E.g. +The `PlcTelegram` is used to store default values to send, for specific keys. E.g. The action `On` would send true, and `Off` would send false. This is semantics, and is defined in each project depending on the hardware. -10. Copy the - file [plc-state.xml](https://github.com/strolch-li/strolch-plc/blob/develop/example/plc-state.xml) - to your runtime and reference it by use of - a `` element. Modify the `PlcId` to be the - same as the one you defined in the `StrolchConfiguration.xml`. +### Running the example -11. Now that we have a model, the `PlcConnections` are to be defined. In the - previous example we used a Raspberry Pi's GPIOs. This needs to be defined as - a `PlcConnection`: +Once you have both the server and PLC instances running, you can login. The default username and password are `admin` +/ `admin`. -```xml +After logging in to the PLC you should be greeted with the following +screen: +![PLC UI](/assets/images/plc.png) - - - - - - - - - - - - - - - - - - - - -``` +And after logging in to the server you should be greeted with the following +screen: +![Server UI](/assets/images/plc-server.png) -See [strolch-plc-example-connections.xml](https://github.com/strolch-li/strolch-plc/blob/develop/example/strolch-plc-example-connections.xml) for further examples. +If the PLC could connect to the server, then the `PLC Control` icon should be activated. The actions Enable, Disable and +Stop All send telegrams to the PLC, thus showing how the server would communicate with the PLC. +### Customization +#### PLC + +Now that the server and PLC are running, we can customize the code. On the PLC side you will want to create +new `li.strolch.plc.core.PlcService` services by extending the class and then registering the service +in `CustomPlcServiceInitializer`. + +See the example `StartupPlcService`. + +#### Server + +On the server side, to register for notifications from the PLC, one would +implement `li.strolch.plc.gw.server.PlcGwService` services and register them on the `PlcHandler` in +the `PostInitializer` class. + +See the example `ModePlcSrvService`. \ No newline at end of file diff --git a/static/assets/images/plc-server.png b/static/assets/images/plc-server.png new file mode 100644 index 0000000..1061530 Binary files /dev/null and b/static/assets/images/plc-server.png differ diff --git a/static/assets/images/plc.png b/static/assets/images/plc.png new file mode 100644 index 0000000..afba5b6 Binary files /dev/null and b/static/assets/images/plc.png differ