├── .gitignore ├── Hardware ├── PLCupdaterBoxLayout.svg ├── PLCupdaterClosedCase.png ├── PLCupdaterInsideCase.png ├── PLCupdaterLEDResistors.png ├── PLCupdaterRpiPinout.svg └── PLCupdaterWiring.svg ├── PLCupdater.sln ├── PLCupdater ├── App.config ├── DF1Comm.dll ├── PLCupdater ├── PLCupdater.cs ├── PLCupdater.csproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Settings.Designer.cs │ └── Settings.settings ├── archive │ └── DF1Comm.cs ├── idleshutdown.sh └── packages.config └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /Hardware/PLCupdaterBoxLayout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml9V Battery 446 | Raspberry 490 | Pi Zero 502 | MoPi 530 | MAX232 550 | -------------------------------------------------------------------------------- /Hardware/PLCupdaterClosedCase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertlarue/PLCupdater/e561c28b1bb375db89b5f2297276fb7174550fd8/Hardware/PLCupdaterClosedCase.png -------------------------------------------------------------------------------- /Hardware/PLCupdaterInsideCase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertlarue/PLCupdater/e561c28b1bb375db89b5f2297276fb7174550fd8/Hardware/PLCupdaterInsideCase.png -------------------------------------------------------------------------------- /Hardware/PLCupdaterLEDResistors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertlarue/PLCupdater/e561c28b1bb375db89b5f2297276fb7174550fd8/Hardware/PLCupdaterLEDResistors.png -------------------------------------------------------------------------------- /PLCupdater.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PLCupdater", "PLCupdater\PLCupdater.csproj", "{25942611-0501-450A-A162-B9FEAAB09C83}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {25942611-0501-450A-A162-B9FEAAB09C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {25942611-0501-450A-A162-B9FEAAB09C83}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {25942611-0501-450A-A162-B9FEAAB09C83}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {25942611-0501-450A-A162-B9FEAAB09C83}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /PLCupdater/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | A.txt 18 | 19 | 20 | B.txt 21 | 22 | 23 | /dev/serial0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 30000 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /PLCupdater/DF1Comm.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertlarue/PLCupdater/e561c28b1bb375db89b5f2297276fb7174550fd8/PLCupdater/DF1Comm.dll -------------------------------------------------------------------------------- /PLCupdater/PLCupdater: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # /etc/init.d/PLCupdater 4 | # Subsystem file for "PLCupdater" service 5 | ### BEGIN INIT INFO 6 | # Provides: PLCupdater 7 | # Required-Start: $remote_fs $syslog 8 | # Required-Stop: $remote_fs $syslog 9 | # Default-Start: 2 3 4 5 10 | # Default-Stop: 0 1 6 11 | # Description: PLCupdater 12 | ### END INIT INFO 13 | PIDFILE=/var/run/PLCupdater.pid 14 | RETVAL=0 15 | prog="PLCupdater" 16 | 17 | start() { 18 | if [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE ); then 19 | echo "Service already running" >&2 20 | return 1 21 | fi 22 | echo -n "Starting service" >&2 23 | su -c "start-stop-daemon -SbmCv -x /usr/bin/nohup -p \"$PIDFILE\" -- /usr/local/bin/mono /home/pi/PLCupdater/PLCupdater.exe" 24 | echo 'Service started' >&2 25 | } 26 | 27 | stop() { 28 | if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then 29 | echo 'Service not running' >&2 30 | return 1 31 | fi 32 | echo 'Stopping service...' >&2 33 | start-stop-daemon -K -p "$PIDFILE" 34 | rm -f "$PIDFILE" 35 | echo 'Service stopped' >&2 36 | } 37 | 38 | case "$1" in 39 | start) 40 | start 41 | ;; 42 | stop) 43 | stop 44 | ;; 45 | restart) 46 | stop 47 | start 48 | ;; 49 | *) 50 | echo "Usage: $0 {start|stop|restart}" 51 | RETVAL=1 52 | esac 53 | exit 54 | -------------------------------------------------------------------------------- /PLCupdater/PLCupdater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.IO; 5 | using Raspberry.IO.GeneralPurpose; 6 | 7 | namespace PLCupdater 8 | { 9 | class PLCupdater 10 | { 11 | public static Timer timer; 12 | public static int idleTimeout = 30000; 13 | public static System.IO.StreamReader fileReader; 14 | public static System.IO.StreamWriter fileWriter; 15 | static void Main(string[] args) 16 | { 17 | //create instance of settings object to get settings from config file 18 | Properties.Settings settings = new Properties.Settings(); 19 | 20 | //set idleTimout to value from PLCupdater.exe.config file 21 | idleTimeout = settings.idleTimeout; 22 | 23 | //Initialize timer to shutdown device when idle 24 | timer = new Timer(new TimerCallback(idleShutdown), null, idleTimeout, Timeout.Infinite); 25 | 26 | //flag to prevent triggering update while one is already in progress 27 | bool updating = false; 28 | 29 | //pin definitions 30 | 31 | //blue LED on pin 12, provision as a low-level pin 32 | ProcessorPin blueLED = ConnectorPin.P1Pin12.ToProcessor(); 33 | 34 | //red LED on pin 18, provision as a low-level pin 35 | ProcessorPin redLED = ConnectorPin.P1Pin18.ToProcessor(); 36 | 37 | //green LED on pin 16, provision as a managed output 38 | OutputPinConfiguration greenLED = ConnectorPin.P1Pin16.Output(); 39 | 40 | //create a low-level connection driver for red LED and blue LED 41 | IGpioConnectionDriver driver = GpioConnectionSettings.DefaultDriver; 42 | driver.Allocate(redLED, PinDirection.Output); 43 | driver.Allocate(blueLED, PinDirection.Output); 44 | 45 | //turn blue LED on to indicate program is ready 46 | driver.Write(blueLED, true); 47 | 48 | //create instance of DF1 protocol serial connection class 49 | DF1Comm.DF1Comm df1 = new DF1Comm.DF1Comm(); 50 | 51 | //create high-level connection for green LED and buttons 52 | //allows for blinking LEDs and OnStatusChanged events for buttons 53 | GpioConnection gpioConnection = new GpioConnection(greenLED); 54 | 55 | //Program A download on pin 15, reverse input so that it's normally open instead of normally closed 56 | //Download program A to PLC when pressed 57 | InputPinConfiguration A_Down = ConnectorPin.P1Pin15.Input() 58 | .Revert() 59 | .OnStatusChanged(a => 60 | { 61 | //if the button is pressed and update is not currently running, start update 62 | if (a && !updating) 63 | { 64 | //set updating flag to true 65 | updating = true; 66 | 67 | //start update to transfer program A to the PLC using serial port from the config 68 | DownloadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileA, settings.SerialPort); 69 | 70 | //set updating flag back to false 71 | updating = false; 72 | } 73 | }); 74 | 75 | //Program B download on pin 7, reverse input so that it's normally open instead of normally closed 76 | //Download program B to PLC when pressed 77 | InputPinConfiguration B_Down = ConnectorPin.P1Pin7.Input() 78 | .Revert() 79 | .OnStatusChanged(a => 80 | { 81 | //if the button is pressed and update is not currently running, start update 82 | if (a && !updating) 83 | { 84 | //set updating flag to true 85 | updating = true; 86 | 87 | //start update to transfer program A to the PLC using serial port from the config 88 | DownloadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileB, settings.SerialPort); 89 | 90 | //set updating flag back to false 91 | updating = false; 92 | } 93 | }); 94 | 95 | //Progam A upload on pin 13, reverse input so that it's normally open instead of normally closed 96 | //Upload program A from PLC when pressed 97 | var A_Up = ConnectorPin.P1Pin13.Input() 98 | .Revert() 99 | .OnStatusChanged(b => 100 | { 101 | //if the button is pressed and update is not currently running, start update 102 | if (b && !updating) 103 | { 104 | //set updating flag to true 105 | updating = true; 106 | 107 | //start update to transfer program B to the PLC using serial port from the config 108 | //DownloadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileB, settings.SerialPort); 109 | UploadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileA, settings.SerialPort); 110 | 111 | //set updating flag back to false 112 | updating = false; 113 | } 114 | }); 115 | 116 | //Progam B upload on pin 11, reverse input so that it's normally open instead of normally closed 117 | //Upload program B from PLC when pressed 118 | var B_Up = ConnectorPin.P1Pin11.Input() 119 | .Revert() 120 | .OnStatusChanged(b => 121 | { 122 | //if the button is pressed and update is not currently running, start update 123 | if (b && !updating) 124 | { 125 | //set updating flag to true 126 | updating = true; 127 | 128 | //start update to transfer program B to the PLC using serial port from the config 129 | //DownloadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileB, settings.SerialPort); 130 | UploadProgram(df1, driver, gpioConnection, redLED, greenLED, settings.FileB, settings.SerialPort); 131 | 132 | //set updating flag back to false 133 | updating = false; 134 | } 135 | }); 136 | 137 | //add the button configurations to the high-level connection 138 | gpioConnection.Add(A_Up); 139 | gpioConnection.Add(B_Up); 140 | gpioConnection.Add(A_Down); 141 | gpioConnection.Add(B_Down); 142 | 143 | //prevent program from exiting 144 | Console.ReadKey(); 145 | } 146 | 147 | //Downloads program to PLC using specified file name and serial port 148 | private static void DownloadProgram(DF1Comm.DF1Comm df1, IGpioConnectionDriver driver, GpioConnection gpioConnection, ProcessorPin redLED, OutputPinConfiguration greenLED, string filename, string serialPort) 149 | { 150 | startIdleTimer(); 151 | //turn on red LED while update is in progress 152 | driver.Write(redLED, true); 153 | 154 | //set serial port on DF1 class to serial port specified, e.g. "/dev/ttyUSB0" 155 | df1.ComPort = serialPort; 156 | 157 | //detectBaudRate(df1); 158 | 159 | //Create new PLCFileDetails object 160 | System.Collections.ObjectModel.Collection PLCFiles = new System.Collections.ObjectModel.Collection(); 161 | 162 | //Create new PLCFile with the defaults 163 | DF1Comm.DF1Comm.PLCFileDetails PLCFile = default(DF1Comm.DF1Comm.PLCFileDetails); 164 | 165 | 166 | //try reading the program file using the filename specified 167 | try 168 | { 169 | //create fileReader to read from file 170 | fileReader = new System.IO.StreamReader(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,filename)); 171 | 172 | //initialize string to hold contents of each line 173 | string line = null; 174 | 175 | //byte collection to hold raw data to send to PLC 176 | System.Collections.ObjectModel.Collection data = new System.Collections.ObjectModel.Collection(); 177 | 178 | //loop until the end of the file is reached 179 | //data is read in chunks of 3 lines 180 | //the first line is the FileType 181 | //the second line is the FileNumber 182 | //and the third line is the data 183 | //these are converted into a PLCFile and added to the PLCFiles collection 184 | while (!(fileReader.EndOfStream)) 185 | { 186 | //get the contents of the first line 187 | line = fileReader.ReadLine(); 188 | 189 | //convert hex ascii to byte for FileType 190 | PLCFile.FileType = Convert.ToByte(line, 16); 191 | 192 | //get the contents of the second line 193 | line = fileReader.ReadLine(); 194 | 195 | //convert hex ascii to byte for FileNumber 196 | PLCFile.FileNumber = Convert.ToByte(line, 16); 197 | 198 | //get the contents of the third line 199 | line = fileReader.ReadLine(); 200 | 201 | //clear the data collection 202 | data.Clear(); 203 | 204 | //loop through the entire line two characters at a time 205 | for (int i = 0; i <= line.Length / 2 - 1; i++) 206 | { 207 | //convert each two character ascii hex byte into a byte and add to data collection 208 | data.Add(Convert.ToByte(line.Substring(i * 2, 2), 16)); 209 | } 210 | 211 | //create byte array the same length as data collection 212 | byte[] dataC = new byte[data.Count]; 213 | 214 | //copy data collection to byte array 215 | data.CopyTo(dataC, 0); 216 | 217 | //assign byte array to PLCFile data property 218 | PLCFile.data = dataC; 219 | 220 | //add the PLCFile to the PLCFiles collection 221 | PLCFiles.Add(PLCFile); 222 | } 223 | 224 | //try to download the PLCfiles to the PLC 225 | try 226 | { 227 | df1.DownloadProgramData(PLCFiles); 228 | //set the PLC back to Run mode when download is complete 229 | df1.SetRunMode(); 230 | } 231 | 232 | //write the error to the console if an error occurs downloading the program 233 | catch (Exception ex) 234 | { 235 | Console.WriteLine("Error Downloading Program. " + ex.Message + " " + ex.StackTrace); 236 | rapidBlink(driver, redLED); 237 | startIdleTimer(); 238 | return; 239 | } 240 | 241 | //turn off red LED when update is complete 242 | driver.Write(redLED, false); 243 | 244 | //write a success message to the console if completed without errors 245 | Console.WriteLine("Successful Download"); 246 | 247 | //turn on green LED for 5 seconds when update is complete 248 | gpioConnection.Blink(greenLED, TimeSpan.FromSeconds(5)); 249 | 250 | //reset the idle shutdown timer 251 | startIdleTimer(); 252 | fileReader.Close(); 253 | return; 254 | } 255 | 256 | //catch errors reading the program file 257 | catch (Exception ex) 258 | { 259 | //write the error to the console if an error occurs reading the program file 260 | Console.WriteLine("Error Reading Program File for Download. " + ex.Message + " " + ex.StackTrace); 261 | 262 | //blink red LED to indicate problem with upload 263 | rapidBlink(driver, redLED); 264 | 265 | //reset the idle shutdown timer 266 | startIdleTimer(); 267 | if (fileReader != null) { fileReader.Close(); } 268 | return; 269 | } 270 | } 271 | 272 | private static void UploadProgram(DF1Comm.DF1Comm df1, IGpioConnectionDriver driver, GpioConnection gpioConnection, ProcessorPin redLED, OutputPinConfiguration greenLED, string filename, string serialPort) 273 | { 274 | startIdleTimer(); 275 | //turn on red LED while update is in progress 276 | driver.Write(redLED, true); 277 | 278 | //set serial port on DF1 class to serial port specified, e.g. "/dev/ttyUSB0" 279 | df1.ComPort = serialPort; 280 | 281 | //detectBaudRate(df1); 282 | 283 | //Create new PLCFileDetails object 284 | System.Collections.ObjectModel.Collection PLCFiles = new System.Collections.ObjectModel.Collection(); 285 | 286 | //byte collection to hold raw data to send to PLC 287 | System.Collections.ObjectModel.Collection data = new System.Collections.ObjectModel.Collection(); 288 | 289 | //try to upload the PLCfiles from the PLC 290 | try 291 | { 292 | PLCFiles = df1.UploadProgramData(); 293 | try 294 | { 295 | fileWriter = new System.IO.StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,filename)); 296 | for (int i = 0; i < PLCFiles.Count; i++) 297 | { 298 | fileWriter.WriteLine(String.Format("{0:x2}", PLCFiles[i].FileType)); 299 | fileWriter.WriteLine(String.Format("{0:x2}", PLCFiles[i].FileNumber)); 300 | for (int j = 0; j < PLCFiles[i].data.Length; j++) 301 | { 302 | fileWriter.Write(String.Format("{0:x2}", PLCFiles[i].data[j])); 303 | } 304 | fileWriter.WriteLine(); 305 | } 306 | fileWriter.Close(); 307 | } 308 | catch (Exception ex) 309 | { 310 | Console.WriteLine("Could not save PLC file. " + ex.Message + " " + ex.StackTrace); 311 | rapidBlink(driver, redLED); 312 | startIdleTimer(); 313 | if (fileWriter != null) { fileWriter.Close(); } 314 | return; 315 | } 316 | } 317 | 318 | //write the error to the console if an error occurs uploading the program 319 | catch (Exception ex) 320 | { 321 | Console.WriteLine("Could not upload PLC file. " + ex.Message + " " + ex.StackTrace); 322 | rapidBlink(driver, redLED); 323 | startIdleTimer(); 324 | return; 325 | } 326 | 327 | //turn off red LED when upload is complete 328 | driver.Write(redLED, false); 329 | 330 | //write a success message to the console if completed without errors 331 | Console.WriteLine("Successful Upload"); 332 | 333 | //turn on green LED for 5 seconds when update is complete 334 | gpioConnection.Blink(greenLED, TimeSpan.FromSeconds(5)); 335 | 336 | //reset the idle shutdown timer 337 | startIdleTimer(); 338 | return; 339 | } 340 | 341 | static void detectBaudRate(DF1Comm.DF1Comm df1) 342 | { 343 | try 344 | { 345 | int comDetect = df1.DetectCommSettings(); 346 | if (comDetect == 0) 347 | { 348 | Console.WriteLine("Detected baud rate is " + df1.BaudRate); 349 | return; 350 | } 351 | else 352 | { 353 | Console.WriteLine("Could not detect baud rate. Using 19200."); 354 | df1.BaudRate = 19200; 355 | return; 356 | } 357 | } 358 | catch (Exception ex) 359 | { 360 | Console.WriteLine("Could not detect baud rate. " + ex.Message + " " + ex.StackTrace); 361 | df1.BaudRate = 19200; 362 | return; 363 | } 364 | } 365 | 366 | static void rapidBlink(IGpioConnectionDriver driver, ProcessorPin led) 367 | { 368 | //blink the LED for 5 seconds, 4 times per second 369 | for (int i = 0; i < 20; i++) 370 | { 371 | driver.Write(led, true); 372 | System.Threading.Thread.Sleep(125); 373 | driver.Write(led, false); 374 | System.Threading.Thread.Sleep(125); 375 | } 376 | } 377 | 378 | //set the idle shutdown timer to the idleTimeout value 379 | static void startIdleTimer() 380 | { 381 | timer.Change(idleTimeout, Timeout.Infinite); 382 | Console.WriteLine("Resetting Timer"); 383 | } 384 | 385 | //run the idleshutdown.sh script when the idle shutdown timer has elapsed 386 | static void idleShutdown(object state) 387 | { 388 | Console.WriteLine("Shutting down because system is idle"); 389 | ProcessStartInfo psi = new ProcessStartInfo(); 390 | psi.FileName = "sh"; 391 | psi.Arguments = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "idleshutdown.sh"); 392 | psi.UseShellExecute = false; 393 | Process.Start(psi); 394 | } 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /PLCupdater/PLCupdater.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {25942611-0501-450A-A162-B9FEAAB09C83} 8 | Exe 9 | Properties 10 | PLCupdater 11 | PLCupdater 12 | v4.6 13 | 512 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | PLCupdater.PLCupdater 38 | 39 | 40 | 41 | False 42 | .\DF1Comm.dll 43 | 44 | 45 | ..\packages\Raspberry.IO.GeneralPurpose.2.4\lib\net40\Raspberry.IO.dll 46 | True 47 | 48 | 49 | ..\packages\Raspberry.IO.GeneralPurpose.2.4\lib\net40\Raspberry.IO.GeneralPurpose.dll 50 | True 51 | 52 | 53 | ..\packages\Raspberry.IO.GeneralPurpose.2.4\lib\net40\Raspberry.IO.Interop.dll 54 | True 55 | 56 | 57 | ..\packages\Raspberry.System.2.0.0\lib\net40\Raspberry.System.dll 58 | True 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | True 70 | True 71 | Settings.settings 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | SettingsSingleFileGenerator 80 | Settings.Designer.cs 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /PLCupdater/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Raspberry.IO.GeneralPurpose; 3 | 4 | namespace PLCupdater 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | bool updating = false; 11 | var redLED = ConnectorPin.P1Pin11.ToProcessor(); 12 | var greenLED = ConnectorPin.P1Pin07.Output(); 13 | var driver = GpioConnectionSettings.DefaultDriver; 14 | driver.Allocate(redLED, PinDirection.Output); 15 | DF1Comm.DF1Comm df1 = new DF1Comm.DF1Comm(); 16 | var gpioConnection = new GpioConnection(greenLED); 17 | var Abutton = ConnectorPin.P1Pin12.Input() 18 | .Revert() 19 | .OnStatusChanged(a => 20 | { 21 | if (a && !updating) 22 | { 23 | updating = true; 24 | driver.Write(redLED, true); 25 | DownloadProgram(df1, Properties.Settings.Default.FileA, Properties.Settings.Default.SerialPort); 26 | driver.Write(redLED, false); 27 | gpioConnection.Blink(greenLED, TimeSpan.FromSeconds(5)); 28 | updating = false; 29 | } 30 | }); 31 | var Bbutton = ConnectorPin.P1Pin16.Input() 32 | .Revert() 33 | .OnStatusChanged(b => 34 | { 35 | if (b && !updating) 36 | { 37 | updating = true; 38 | driver.Write(redLED, true); 39 | DownloadProgram(df1, Properties.Settings.Default.FileB, Properties.Settings.Default.SerialPort); 40 | driver.Write(redLED, false); 41 | gpioConnection.Blink(greenLED, TimeSpan.FromSeconds(5)); 42 | updating = false; 43 | } 44 | }); 45 | gpioConnection.Add(Abutton); 46 | gpioConnection.Add(Bbutton); 47 | Console.Read(); 48 | } 49 | 50 | private static void DownloadProgram(DF1Comm.DF1Comm df1, string filename, string serialPort) 51 | { 52 | 53 | df1.ComPort = serialPort; 54 | System.Collections.ObjectModel.Collection PLCFiles = new System.Collections.ObjectModel.Collection(); 55 | DF1Comm.DF1Comm.PLCFileDetails PLCFile = default(DF1Comm.DF1Comm.PLCFileDetails); 56 | try 57 | { 58 | System.IO.StreamReader FileReader = new System.IO.StreamReader(filename); 59 | 60 | string line = null; 61 | System.Collections.ObjectModel.Collection data = new System.Collections.ObjectModel.Collection(); 62 | 63 | int linesCount = 0; 64 | while (!(FileReader.EndOfStream)) 65 | { 66 | line = FileReader.ReadLine(); 67 | PLCFile.FileType = Convert.ToByte(line, 16); 68 | line = FileReader.ReadLine(); 69 | PLCFile.FileNumber = Convert.ToByte(line, 16); 70 | 71 | line = FileReader.ReadLine(); 72 | data.Clear(); 73 | for (int i = 0; i <= line.Length / 2 - 1; i++) 74 | { 75 | data.Add(Convert.ToByte(line.Substring(i * 2, 2), 16)); 76 | } 77 | byte[] dataC = new byte[data.Count]; 78 | data.CopyTo(dataC, 0); 79 | PLCFile.data = dataC; 80 | 81 | PLCFiles.Add(PLCFile); 82 | linesCount += 1; 83 | } 84 | 85 | try 86 | { 87 | df1.DownloadProgramData(PLCFiles); 88 | df1.SetRunMode(); 89 | } 90 | catch (Exception ex) 91 | { 92 | Console.WriteLine(ex.Message); 93 | return; 94 | } 95 | 96 | Console.WriteLine("Successful Download"); 97 | return; 98 | } 99 | catch (Exception ex) 100 | { 101 | Console.WriteLine(ex.Message); 102 | return; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /PLCupdater/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PLCupdater")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PLCupdater")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("25942611-0501-450a-a162-b9feaab09c83")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PLCupdater/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PLCupdater.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("A.txt")] 29 | public string FileA { 30 | get { 31 | return ((string)(this["FileA"])); 32 | } 33 | } 34 | 35 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 36 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 37 | [global::System.Configuration.DefaultSettingValueAttribute("B.txt")] 38 | public string FileB { 39 | get { 40 | return ((string)(this["FileB"])); 41 | } 42 | } 43 | 44 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 45 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 46 | [global::System.Configuration.DefaultSettingValueAttribute("/dev/serial0")] 47 | public string SerialPort { 48 | get { 49 | return ((string)(this["SerialPort"])); 50 | } 51 | } 52 | 53 | [global::System.Configuration.UserScopedSettingAttribute()] 54 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 55 | [global::System.Configuration.DefaultSettingValueAttribute("30000")] 56 | public int idleTimeout { 57 | get { 58 | return ((int)(this["idleTimeout"])); 59 | } 60 | set { 61 | this["idleTimeout"] = value; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PLCupdater/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | A.txt 7 | 8 | 9 | B.txt 10 | 11 | 12 | /dev/serial0 13 | 14 | 15 | 30000 16 | 17 | 18 | -------------------------------------------------------------------------------- /PLCupdater/idleshutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo mopicli -wsd 1 -------------------------------------------------------------------------------- /PLCupdater/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PLCupdater 2 | A portable programming tool for Allen Bradley MicroLogix PLCs. Software and instruction on building the hardware are included. 3 | Credits to Archie over at sourceforge for the Allen Bradley [DF1 Protocol library](https://sourceforge.net/projects/abdf1/) which I used as a starting place for this project. I forked his library to work with the Raspberry Pi and the modified version can be found in the [DF1Comm repo](https://github.com/robertlarue/DF1Comm) 4 | 5 | ## Features 6 | * Upload program from PLC to handheld device and store in one of two memory slots. 7 | * Download program from handheld device to PLC from one of two memory slots and set the PLC to run mode. 8 | 9 | # Software 10 | ## Installation 11 | You will need a network connection to your Raspberry Pi Zero before you can install packages from the internet. 12 | There are many ways of doing this. I set up mine to tunnel ethernet over usb and then share my laptop's connection. 13 | An easier way would be to get a [micro USB to ethernet adapter](https://www.amazon.com/Plugable-Micro-B-Ethernet-Raspberry-AX88772A/dp/B00RM3KXAU) 14 | Rasbian Jesse or greater is required and you have to install mono beforehand. 15 | 16 | `sudo apt-get install mono-complete` 17 | 18 | Then clone this repository 19 | 20 | `git clone https://github.com/robertlarue/PLCupdater.git ` 21 | 22 | # Hardware 23 | ## Components 24 | The following components are what I used to make my prototype but are by no means the only way you could make one of these devices. 25 | The most important components are a Raspberry Pi and a TTL to RS-232 adapter. 26 | 27 | | Name | Qty | Cost | Subtotal | Link | 28 | |-----------------------------------------|-----|-------|----------|-------------------------------------------------------------------------------------| 29 | | Raspberry Pi Zero | 1 | $10 | $10 | https://www.adafruit.com/product/3400 | 30 | | MoPi power converter | 1 | $34 | $34 | https://shop.pimoroni.com/products/mopi-mobile-pi-power | 31 | | MAX232 TTL to RS-232 Adapter | 1 | $7 | $7 | https://www.amazon.com/uxcell-MAX232CSE-Transfer-Converter-Module/dp/B00EJ9NAKA | 32 | | 9V Battery Clip | 1 | $5 | $5 | https://www.amazon.com/uxcell-Leather-Shell-Battery-Connector/dp/B00JR6FPVW | 33 | | 25 pack of Red, Green, Yellow, Blue LED | 1 | $6 | $6 | https://www.amazon.com/microtivity-IL081-Assorted-Resistors-Colors/dp/B004JO2PVA | 34 | | LED Holders 50 pack | 1 | $6 | $6 | https://www.amazon.com/5mm-Black-Plastic-LED-Holders/dp/B00AO1SF98 | 35 | | Cables with pin connectors | 1 | $7 | $7 | https://www.amazon.com/GeeBat-Multicolored-Rainbow-Breadboard-arduino/dp/B01MFCKASX | 36 | | Project Enclosure | 1 | $7 | $7 | https://www.amazon.com/Hammond-1591BSBK-ABS-Project-Black/dp/B0002BBQUA | 37 | | 9V Battery 4 pack | 1 | $8 | $8 | https://www.amazon.com/Duracell-MN-1604-Pack-MN1604/dp/B0164F986Q | 38 | | | | Total | $82 | | | 39 | 40 | ## Wiring 41 | Below is how I wired up my prototype. I used the MoPi circuit to handle power management and graceful shutdown. 42 | The table is layed out with the Raspberry Pi pinout down the center and the connections to other devices on each side. 43 | 44 | ![Raspberry Pi Pinout](https://rawgit.com/robertlarue/PLCupdater/master/Hardware/PLCupdaterRpiPinout.svg) 45 | 46 | | Func | BCM | wPi | Name | Mode | Phys | Phys | Mode | Name | wPi | BCM | Func | 47 | |--------------------|-----|-----|--------|------|------|------|------|--------|-----|-----|---------------------| 48 | | MAX232 3.3V | | | 3.3V | | 1 | 2 | | 5V | | | MoPi 5V | 49 | | MoPi SDA | 2 | 8 | SDA.1 | ALT0 | 3 | 4 | | 5V | | | | 50 | | MoPi SCL | 3 | 9 | SCL.1 | ALT0 | 5 | 6 | | 0V | | | MoPi 0V | 51 | | B Down | 4 | 7 | GPIO 7 | IN | 7 | 8 | ALT0 | TxD | 15 | 14 | MAX232 TTL Tx | 52 | | MAX232 0V | | | 0V | | 9 | 10 | ALT0 | RxD | 16 | 15 | MAX232 TTL Rx | 53 | | B Up | 17 | 0 | GPIO 0 | IN | 11 | 12 | OUT | GPIO 1 | 1 | 18 | Ready (Blue LED) | 54 | | A Up | 27 | 2 | GPIO 2 | IN | 13 | 14 | | 0V | | | Buttons & Lights 0V | 55 | | A Down | 22 | 3 | GPIO 3 | IN | 15 | 16 | OUT | GPIO 4 | 4 | 23 | Success (Green LED) | 56 | | Power (Yellow LED) | | | 3.3V | | 17 | 18 | OUT | GPIO 5 | 5 | 24 | Progress (Red LED) | 57 | 58 | ![PLCupdater Wiring](https://rawgit.com/robertlarue/PLCupdater/master/Hardware/PLCupdaterWiring.svg?raw=true) 59 | 60 | ![PLCupdater LED Resistors](/Hardware/PLCupdaterLEDResistors.png?raw=true) 61 | 62 | ![PLCupdater Inside Case](/Hardware/PLCupdaterInsideCase.png?raw=true) 63 | 64 | ![PLCupdater Closed Case](/Hardware/PLCupdaterClosedCase.png?raw=true) 65 | 66 | # Usage 67 | 68 | The video below shows how to use the handheld device. 69 | Hold down the power button for 3 seconds to starts up the device. When the device is ready, the blue light will turn on. 70 | To upload a program from a PLC to the device to save it, press either one of the top memory bank buttons. 71 | To download a saved program from the device to the PLC, press the down button from one of the memory banks. 72 | The device will automatically shutdown gracefully after 30 seconds or you can shut it down manually by pressing and holding the power button. 73 | 74 | [![PLCupdater Video](http://img.youtube.com/vi/zfGl7Mr_JKA/0.jpg)](http://www.youtube.com/watch?v=zfGl7Mr_JKA) 75 | --------------------------------------------------------------------------------