├── Demo 1 - Basics ├── 1-0 Blank Program.adpro ├── 1-1 Simple Button.adpro ├── 1-2 Delayed Button.adpro ├── 1-3 Blinking.adpro └── README.md ├── Demo 2 - Pedestrian Button ├── README.md ├── Traffic Light.adpro ├── datascience │ └── __init__.py ├── modbus_client.py ├── modbus_server.py ├── requirements.txt └── socket_client.py ├── LICENSE └── README.md /Demo 1 - Basics/1-0 Blank Program.adpro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonemo/pycon2019-plc-demo/e3f7352f949828f7c793767ada80d44af39b0801/Demo 1 - Basics/1-0 Blank Program.adpro -------------------------------------------------------------------------------- /Demo 1 - Basics/1-1 Simple Button.adpro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonemo/pycon2019-plc-demo/e3f7352f949828f7c793767ada80d44af39b0801/Demo 1 - Basics/1-1 Simple Button.adpro -------------------------------------------------------------------------------- /Demo 1 - Basics/1-2 Delayed Button.adpro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonemo/pycon2019-plc-demo/e3f7352f949828f7c793767ada80d44af39b0801/Demo 1 - Basics/1-2 Delayed Button.adpro -------------------------------------------------------------------------------- /Demo 1 - Basics/1-3 Blinking.adpro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonemo/pycon2019-plc-demo/e3f7352f949828f7c793767ada80d44af39b0801/Demo 1 - Basics/1-3 Blinking.adpro -------------------------------------------------------------------------------- /Demo 1 - Basics/README.md: -------------------------------------------------------------------------------- 1 | # Demo 1: Basics 2 | 3 | ## How to open `.adpro` files 4 | 5 | To open the `.adpro` files in this folder you need the "Productivity Suite" software. 6 | Please see the README.md at the top level of this repo for details where to get this software. 7 | 8 | ## Related section of the presentation 9 | 10 | The examples in this folder appear in the PyCon talk video between [8:10](https://youtu.be/a0l29lgDf6k?t=490) and [13:14](https://youtu.be/a0l29lgDf6k?t=794). 11 | In the talk I live-code the files and explain the basics of the ladder logic language as I go along. 12 | I also run the programs on a PLC which is wired up to a traffic signal as a real-world example of controlling devices from PLC logic. 13 | 14 | ## How to use the examples when you don't have a traffic signal 15 | 16 | If you have the PLC but not a traffic signal, you can wire up some other device to the PLC output terminals. 17 | Or simply look at the built-in LEDs to see when the output is active (on) or off. 18 | If you have the PLC but aren't quite sure what LED to look for, just load the `1-3 Blinking.adpro` project onto the PLC, put the PLC into "Run" mode (using the physical switch and the software button), and look for the blinking light! 19 | 20 | ## What are these examples 21 | 22 | These `.adpro` files are project files that contain a single "task" (subroutine) written in ladder logic. 23 | In the presentation which this repo accompanies, they served as the very first introduction to the ladder logic language and the concept of PLCs in general. 24 | You can think of the examples as "Hello World" of PLC programming. 25 | You can watch a recording of the presentation [here](https://youtu.be/a0l29lgDf6k), or jump directly to the section where I use these examples [here](https://youtu.be/a0l29lgDf6k?t=490). 26 | 27 | If you watch the presentation video, you will see that I start with a "blank" file and then just keep editing this same file to go from one example to the next. 28 | The files in this folder are intermediate snapshots from this process. 29 | 30 | ### `1-0 Blank Program.adpro` 31 | 32 | This is the file that I start with at [8:15](https://youtu.be/a0l29lgDf6k?t=495) in the video. 33 | It looks like a blank file (all the ladder rungs are empty), but I did a little bit of prep work to make the demo flow quicker: 34 | * The project is configured with the type of PLC CPU (Productivity1000), the I/O modules I have connected to the PLC's CPU (digital input and digital output) and the IP address of the PLC. 35 | * The Tag Database (which you can find in the tree on the left side of the screen) contains the tags (variables) I will be using, that way I can rely on auto-complete when typing there names. 36 | 37 | ### `1-1 Simple Button.adpro` 38 | 39 | This file represents the state of the demo program that can be seen on screen at [9:51](https://youtu.be/a0l29lgDf6k?t=591) in the video. 40 | It's a single ladder rung implementing this rule: When the input named `DI1` is on, then enable the output called `DQ1`. 41 | The ladder logic elements used in this examples are "Normally Open Contact" and "Coil". 42 | Watch the video to see this program running. 43 | 44 | ### `1-2 Delayed Button.adpro` 45 | 46 | This file represents the state of the demo program that can be seen on screen at [11:40](https://youtu.be/a0l29lgDf6k?t=700) in the video. 47 | It adds a delay to the previous example: `DQ1` only switches on after `DI1` has been on for two seconds. 48 | On top of the "Normally Open Contact" and "Coil" from the previous example, this example uses the "Simple Timer" ladder logic element. 49 | Watch the video to see this program running. 50 | 51 | ### `1-3 Blinking.adpro` 52 | 53 | This file represents the state of the demo program that can be seen on screen at [12:38](https://youtu.be/a0l29lgDf6k?t=758) in the video. 54 | It no longer uses the `DI1` input for anything. 55 | Instead, it uses two different timers to yield a blinking light that switches state every two seconds. 56 | On top of the previously used ladder logic elements, this example also uses a "Normally Closed Contact", which I explain in the talk as similar to using `if not` in Python. 57 | Watch the video to see this program running. 58 | -------------------------------------------------------------------------------- /Demo 2 - Pedestrian Button/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2: Pedestrian Button 2 | 3 | ## How to open `.adpro` files 4 | 5 | To open the `.adpro` files in this folder you need the "Productivity Suite" software. 6 | Please see the README.md at the top level of this repo for details where to get this software. 7 | 8 | ## Related section of the presentation 9 | 10 | The `Traffic Light.adpro` file in this folder appears in the PyCon talk video between [16:02](https://youtu.be/a0l29lgDf6k?t=972) and [21:21](https://youtu.be/a0l29lgDf6k?t=1281). 11 | Not all of the `.py` files were part of the presentation because I had to trim the talk content quite aggressively to stay within the time limit. 12 | Nonetheless, the `Traffic Light.adpro` PLC program contains everything necessary for the Python scripts to work. 13 | 14 | If you have not read the README.md files at the top level of this repo and the one in the `Demo 1: Basics` folder, I suggest you read those first and then come back here. 15 | 16 | ## How to use the examples when you don't have a traffic signal 17 | 18 | If you have the PLC but not a traffic signal, you can wire up other devices to the PLC input and output terminals. 19 | Currently, I do not have a wiring diagram available for this setup; please send me an email if you are interested in using this example and need more details. 20 | 21 | ## What's in the `Traffic Light.adpro`? 22 | 23 | The purpose of this PLC project is to demonstrate different network interfaces and protocols for communicating with a PLC from Python scripts running on a PC. 24 | The presentation this was part of contains some discussion of the Modbus protocol as well as a review of other libraries and protocols that can be used with other brands of PLC. 25 | 26 | `Traffic Light.adpro` contains several tasks (subroutines) written in ladder logic (the only language available in Productivity Suite). 27 | 28 | ### `MainTask` 29 | 30 | `MainTask` calls all the other tasks at every scan of the program 31 | 32 | ### `PedestrianXing` 33 | 34 | `PedestrianXing` uses a sequence of timers to step through a traffic light cycle from green to yellow to red, then "walking" to "flashing" to "hand" for pedestrians, back to green. 35 | The trigger for the cycle is either `DI1` (wired to the pedestrian button) or one of the other tasks. 36 | This task is on screen at [16:41](https://youtu.be/a0l29lgDf6k?t=1001) in the presentation recording. 37 | 38 | ### `Demo2Part` 39 | 40 | `Demo2Part2` contains ladder logic elements to write the current state of all outputs (i.e. the state of the traffic signal) to a remote Modbus server. 41 | Use the `modbus_server.py` script below to run a Modbus server on your laptop. 42 | The PLC program assumes that your laptop has the IP address `192.168.1.99` (you can change this value in `Demo2Part2`) and runs the Modbus server on port `502` (the default normally used with Modbus over TCP). 43 | 44 | ### `Demo2Part3` 45 | 46 | `Demo2Part3` contains ladder logic elements to listen for incoming ASCII strings on port 50505 of the PLC (that port number is arbitrarily chosen and can be changed). 47 | See the `socket_client.py` script below for how to send commands to this "server". 48 | This task was not shown or used in the PyCon presentation. 49 | 50 | ### `Demo2Part4` 51 | 52 | `Demo2Part4` contains ladder logic that implement the "Second Mode" aka "Disco Mode" of the traffic signal. 53 | See the `socket_client.py` section below for how to trigger "Disco Mode" and what it is. 54 | The PLC tag names and program names are intentionally nondescript ("Second Mode" and "Demo2Part4") because this allows me to use the "Disco Mode" as a suprise/joke in live demonstrations. 55 | This task was not shown or used in the PyCon presentation. 56 | 57 | ### Project Configuration 58 | 59 | The `Traffic Light.adpro` also contains various configuration settings: 60 | * The setup with information like which PLC CPU is used and what I/O modules are connected. 61 | * Configuration for the Modbus server and which tags (variables) are exposed over the Modbus protocol. 62 | * Configuration for the "Custom Protocol over Ethernet" (CPoE) server used in the `Demo2Part3` task. 63 | 64 | ## Python Scripts 65 | 66 | ### `modbus_client.py` 67 | 68 | This file represents the state of the demo program that can be seen on screen at [18:17](https://youtu.be/a0l29lgDf6k?t=1097) in the video. 69 | It connect to the Modbus server running on port 502 of the PLC and writes to specific registers. 70 | To see which registers maps to which tag (variable) in the ladder logic code, open the "Tag Database" window in the Productivity Suite software. 71 | 72 | ### `modbus_server.py` 73 | 74 | This script was running in the background during my presentation at PyCon, but sadly I didn't have the time to show and explain it (i.e. you won't see it referenced in the talk video). 75 | The script uses the `pymodbus` package to run a Modbus server on port 502. 76 | The `Demo2Part2` of the `Traffic Light.adpro` PLC project contains the logic to publish the current value of the PLC's output pins (which map to the colors of the traffic signal) into the registers of the Modbus server. 77 | Whenever new values arrive, the `MyDataBlock.setValues()` method prints a nicely formatted line into the terminal that shows which lights of the traffic signals are currently on. 78 | 79 | Note that the PLC's logic hard-codes the IP address of the Modbus server it publishes the values to. 80 | In my presentation setup, the laptop had IP `192.168.1.99`. 81 | You can change that value in the `Demo2Part2` task of the `Traffic Light.adpro` PLC project. 82 | 83 | ### `socket_client.py` 84 | 85 | This script is an example for how you can send commands to the "Custom Protocol over Ethernet" server defined in the `Demo2Part3` task of the `Traffic Light.adpro` PLC project. 86 | If you read the PLC code carefully, you will notice that the PLC logic only looks at the first three characters of an incoming message. 87 | Therefore, sending `REQUEST` has the same effect as sending `REQ` and sending `DISCO` has the same effect as sending `DIS`. 88 | Note that `socket.sendall()` should get bytes and not Unicode strings. 89 | 90 | ```python 91 | import socket 92 | 93 | sock = socket.socket() 94 | sock.connect(("192.168.1.9", 50505)) 95 | sock.sendall(b"REQ") # request a pedestrian cycle 96 | sock.recv(1024) # if everything works, the PLC responds with "ACK" 97 | sock.sendall(b"DISCO") # the traffic signal flashes its lights until the pedestrian button is pressed 98 | sock.close() 99 | ``` 100 | 101 | ### `datascience/__init__.py` 102 | 103 | This file is used to support a joke about data science in the presentation. 104 | It's at approximately [20:47](https://youtu.be/a0l29lgDf6k?t=1247) in the video recording. 105 | Basically, it allows me to write the following code in a REPL and get back a (static) list of values that happens to be exactly what I want for the next on-stage demo: 106 | 107 | ```python 108 | from pymodbus.client.sync import ModbusTCPClient 109 | from datascience import machine_learning 110 | 111 | client = ModbusTCPClient("192.168.1.9") 112 | client.write_registers(0, machine_learning()) 113 | ``` 114 | -------------------------------------------------------------------------------- /Demo 2 - Pedestrian Button/Traffic Light.adpro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonemo/pycon2019-plc-demo/e3f7352f949828f7c793767ada80d44af39b0801/Demo 2 - Pedestrian Button/Traffic Light.adpro -------------------------------------------------------------------------------- /Demo 2 - Pedestrian Button/datascience/__init__.py: -------------------------------------------------------------------------------- 1 | def machine_learning(): 2 | return [11, 0, 37, 0, 85, 0, 51, 0, 50, 0, 86, 0] 3 | -------------------------------------------------------------------------------- /Demo 2 - Pedestrian Button/modbus_client.py: -------------------------------------------------------------------------------- 1 | from pymodbus.client.sync import ModbusTcpClient 2 | 3 | client = ModbusTcpClient('192.168.1.9') 4 | client.read_holding_registers(0, 12) 5 | 6 | # In [52]: client.read_holding_registers(0, 12).registers 7 | # Out[52]: [0, 500, 0, 300, 0, 100, 0, 300, 0, 700, 0, 50] 8 | 9 | # In [51]: client.write_registers(0, [0, 500]) 10 | # Out[51]: