├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── src ├── com └── sourceforge │ └── snap7 │ └── moka7 │ ├── IntByRef.java │ ├── S7.java │ ├── S7BlockInfo.java │ ├── S7Client.java │ ├── S7CpInfo.java │ ├── S7CpuInfo.java │ ├── S7OrderCode.java │ ├── S7Protection.java │ └── S7Szl.java └── si └── trina └── moka7 └── live ├── PLC.java ├── PLCListener.java └── PLCStatus.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | pom.xml.tag 18 | pom.xml.releaseBackup 19 | pom.xml.versionsBackup 20 | pom.xml.next 21 | release.properties 22 | dependency-reduced-pom.xml 23 | buildNumber.properties 24 | .mvn/timing.properties 25 | 26 | bin/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Davide Nardella. 2 | Copyright (C) 2017 David Delibasic. 3 | Copyright (C) 2017 Mojca Rojko. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/si.trina/moka7-live/badge.svg)](https://maven-badges.herokuapp.com/maven-central/si.trina/moka7-live) 2 | 3 | 4 | # moka7-live 5 | 6 | This is a library built around [moka7](http://snap7.sourceforge.net/moka7.html) created by Dave Nardella. Moka7 is is the Java port of Snap7 Client. It’s a pure Java implementation of the S7Protocol used to communicate with S7 PLCs. 7 | 8 | 9 | 10 | ## Installation 11 | 12 | Package can be installed via maven by adding the following to your pom.xml: 13 | 14 | 15 | si.trina 16 | moka7-live 17 | 0.0.11 18 | 19 | 20 | ## How to use 21 | 22 | **1. Create classes that implement interface PLCListener** 23 | 24 | **2. Create PLC class instances for every PLC you wish to receive bit changes / read integers, bits from** 25 | 26 | ``` 27 | import si.trina.moka7.live.PLC; 28 | import com.sourceforge.snap7.moka7.S7; 29 | 30 | /* 31 | args: 32 | ** name of PLC 33 | ** IP of PLC 34 | ** byte array with length of db PLC->PC 35 | ** byte array with length of PC->PLC 36 | ** db (DataBase) number PLC->PC 37 | ** db (DataBase) number PC->PLC 38 | ** array of addresses of booleans to listen to changes to 39 | */ 40 | PLC plc1 = new PLC("Test PLC1","10.10.21.10",new byte[32],new byte[36],112,114,new double[]{0.1,0.2}); 41 | 42 | 43 | /* 44 | args: 45 | ** name of PLC 46 | ** IP of PLC 47 | ** length of db PLC->PC 48 | ** length of PC->PLC 49 | ** db (DataBase) number PLC->PC 50 | ** db (DataBase) number PC->PLC 51 | ** array of addresses of booleans to listen to changes to 52 | ** rack number 53 | ** slot number 54 | ** area type of PLC->PC 55 | ** area type of PC->PLC 56 | */ 57 | PLC plc2 = new PLC("Test PLC2", "10.10.22.10", 18, 22, 45, 44, new double[]{0.1,0.2,0.3}, 0, 1, S7.AreaDB, S7AreaDB); 58 | ``` 59 | 60 | **3. Add classes that implement interface PLCListener to PLC's `ArrayList listener` array** 61 | 62 | ``` 63 | PLCListenerImplementation myListener = new PLCListenerImplementation(); 64 | plc1.listeners.add(myListener); 65 | plc2.listeners.add(myListener); 66 | ``` 67 | 68 | **4. Start a thread for each PLC instance** 69 | 70 | ``` 71 | Thread t1 = new Thread(plc1).start(); 72 | Thread t2 = new Thread(plc2).start(); 73 | ``` 74 | 75 | **5. Receive bit changes from bits at addresses from last argument of PLC constructor** 76 | 77 | ``` 78 | import si.trina.moka7.live.PLCListener; 79 | 80 | public class PLCListenerImplementation implements PLCListener { 81 | @Override 82 | public void PLCBitChanged(int address, int pos, boolean val, String plcName) { 83 | switch (address) { 84 | case 0: 85 | switch (pos) { 86 | case 1: 87 | System.out.println("Bit at address 0.1 of PLC " + plcName + " changed to: " + val); 88 | } 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | **6. Write shorts/integers/booleans to DB** 95 | 96 | ``` 97 | /* 98 | args: 99 | ** database to write to: from plc = true, from pc = false 100 | ** address to write to 101 | ** short/integer to write to db 102 | */ 103 | plc1.putInt(false, 12, (short)3); 104 | plc1.putDInt(false, 12, 3); 105 | 106 | /* 107 | args: 108 | ** database to write to: from plc = true, from pc = false 109 | ** address to write to 110 | ** bit offset at address to write to 111 | ** value to write 112 | */ 113 | plc1.putBool(false, 0, 1, true); 114 | plc1.signalBoolean(false, 0, 1, true); // resets to false after 300ms 115 | ``` 116 | 117 | **7. Read shorts/integers/booleans from DB** 118 | ``` 119 | try { 120 | short aShort = plc1.getInt(true, 8); // 2 bytes 121 | int anInteger = plc1.getDInt(true, 8); // 4 bytes 122 | boolean aBoolean = plc1.getBool(true, 0, 2); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | } 126 | ``` 127 | 128 | ## Optional 129 | 130 | **Check communication status** 131 | 132 | Communication status can optionally be continuously checked with the help of a 'live bit'. 133 | 134 | The following example settings set bit at address 0.0 in both DB's as the 'live bit', meaning it toggles it every 250ms and expects PLC to toggle it back every 500ms. Throws exception if it doesn't. 135 | 136 | ``` 137 | plc1.liveBitEnabled = true; 138 | plc1.liveBitAddress = 0; 139 | plc1.liveBitPosition = 0; 140 | plc1.liveBitPCDuration = 250; 141 | plc1.liveBitPLCDuration = 500; 142 | ``` 143 | 144 | ## Misc 145 | 146 | **PLC connection status** 147 | 148 | Boolean indicating whether a PLC is connected or not can be found in PLC class' public variable `connected`. 149 | 150 | ## Logging 151 | 152 | All logging with various priorities inside the library is done with slf4j. Meaning, you need a logger binding (for example slf4j-simple) to see logs. 153 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | si.trina 4 | moka7-live 5 | 0.0.14-SNAPSHOT 6 | jar 7 | moka7-live 8 | Communication with s7 PLCs helper classes 9 | https://github.com/xtrinch/moka7-live 10 | 11 | 12 | GNU General Public License, Version 3.0 13 | https://www.gnu.org/licenses/gpl-3.0.txt 14 | repo 15 | 16 | 17 | 18 | scm:git:git://github.com/xtrinch/moka7-live.git 19 | scm:git:git@github.com:xtrinch/moka7-live.git 20 | http://github.com/xtrinch/moka7-live 21 | moka7-live-0.0.8 22 | 23 | 24 | 25 | moka7 26 | Dave Nardella 27 | snap7.sourceforce.com 28 | 29 | 30 | david 31 | David Delibasic 32 | dac.si 33 | 34 | 35 | xtrinch 36 | Mojca Rojko 37 | trina.si 38 | 39 | 40 | 41 | src 42 | 43 | 44 | 45 | maven-compiler-plugin 46 | 3.6.1 47 | 48 | 1.8 49 | 1.8 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-javadoc-plugin 55 | 2.9 56 | 57 | 58 | attach-javadocs 59 | 60 | jar 61 | 62 | 63 | -Xdoclint:none 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-release-plugin 71 | 2.5.3 72 | 73 | 74 | 75 | 76 | 77 | release-sign-artifacts 78 | 79 | 80 | performRelease 81 | true 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-gpg-plugin 89 | 1.6 90 | 91 | 92 | sign-artifacts 93 | verify 94 | 95 | sign 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | sonatype-nexus-snapshots 107 | Sonatype Nexus snapshot repository 108 | https://oss.sonatype.org/content/repositories/snapshots 109 | 110 | 111 | nexus-releases 112 | Nexus Release Repository 113 | http://oss.sonatype.org/service/local/staging/deploy/maven2 114 | 115 | 116 | 117 | 118 | commons-codec 119 | commons-codec 120 | 1.9 121 | 122 | 123 | org.slf4j 124 | slf4j-api 125 | 1.8.0-beta0 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/IntByRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | 14 | public class IntByRef { 15 | 16 | public IntByRef(int Val) 17 | { 18 | this.Value=Val; 19 | } 20 | public IntByRef() 21 | { 22 | this.Value=0; 23 | } 24 | public int Value; 25 | } 26 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.Date; 11 | import java.util.Calendar; 12 | 13 | /** 14 | * 15 | * @author Dave Nardella 16 | */ 17 | 18 | 19 | // Step 7 Constants and Conversion helper class 20 | public class S7 { 21 | // S7 ID Area (Area that we want to read/write) 22 | public static final int S7AreaPE = 0x81; 23 | public static final int S7AreaPA = 0x82; 24 | public static final int S7AreaMK = 0x83; 25 | public static final int S7AreaDB = 0x84; 26 | public static final int S7AreaCT = 0x1C; 27 | public static final int S7AreaTM = 0x1D; 28 | // Connection types 29 | public static final byte PG = 0x01; 30 | public static final byte OP = 0x02; 31 | public static final byte S7_BASIC = 0x03; 32 | // Block type 33 | public static final int Block_OB = 0x38; 34 | public static final int Block_DB = 0x41; 35 | public static final int Block_SDB = 0x42; 36 | public static final int Block_FC = 0x43; 37 | public static final int Block_SFC = 0x44; 38 | public static final int Block_FB = 0x45; 39 | public static final int Block_SFB = 0x46; 40 | // Sub Block Type 41 | public static final int SubBlk_OB = 0x08; 42 | public static final int SubBlk_DB = 0x0A; 43 | public static final int SubBlk_SDB = 0x0B; 44 | public static final int SubBlk_FC = 0x0C; 45 | public static final int SubBlk_SFC = 0x0D; 46 | public static final int SubBlk_FB = 0x0E; 47 | public static final int SubBlk_SFB = 0x0F; 48 | // Block languages 49 | public static final int BlockLangAWL = 0x01; 50 | public static final int BlockLangKOP = 0x02; 51 | public static final int BlockLangFUP = 0x03; 52 | public static final int BlockLangSCL = 0x04; 53 | public static final int BlockLangDB = 0x05; 54 | public static final int BlockLangGRAPH = 0x06; 55 | // PLC Status 56 | public static final int S7CpuStatusUnknown = 0x00; 57 | public static final int S7CpuStatusRun = 0x08; 58 | public static final int S7CpuStatusStop = 0x04; 59 | // Type Var 60 | public static final int S7TypeBool = 1; 61 | public static final int S7TypeInt = 1; 62 | 63 | // Returns the bit at Pos.Bit 64 | public static boolean GetBitAt(byte[] Buffer, int Pos, int Bit) 65 | { 66 | int Value = Buffer[Pos] & 0x0FF; 67 | byte[] Mask = { 68 | (byte)0x01,(byte)0x02,(byte)0x04,(byte)0x08, 69 | (byte)0x10,(byte)0x20,(byte)0x40,(byte)0x80 70 | }; 71 | if (Bit<0) Bit=0; 72 | if (Bit>7) Bit=7; 73 | 74 | return (Value & Mask[Bit])!=0; 75 | } 76 | /** 77 | * Returns a 16 bit unsigned value : from 0 to 65535 (2^16-1) 78 | * @param Buffer 79 | * @param Pos start position 80 | * @return 81 | */ 82 | public static int GetWordAt(byte[] Buffer, int Pos) 83 | { 84 | int hi = (Buffer[Pos] & 0x00FF); 85 | int lo = (Buffer[Pos+1] & 0x00FF); 86 | return (hi<<8)+lo; 87 | } 88 | 89 | // Returns a 16 bit signed value : from -32768 to 32767 90 | public static int GetShortAt(byte[] Buffer, int Pos) 91 | { 92 | int hi = (Buffer[Pos]); 93 | int lo = (Buffer[Pos+1] & 0x00FF); 94 | return ((hi<<8)+lo); 95 | } 96 | 97 | // Returns a 32 bit unsigned value : from 0 to 4294967295 (2^32-1) 98 | public static long GetDWordAt(byte[] Buffer, int Pos) 99 | { 100 | long Result; 101 | Result=(long)(Buffer[Pos] & 0x0FF); 102 | Result<<=8; 103 | Result+=(long)(Buffer[Pos+1] & 0x0FF); 104 | Result<<=8; 105 | Result+=(long)(Buffer[Pos+2] & 0x0FF); 106 | Result<<=8; 107 | Result+=(long)(Buffer[Pos+3] & 0x0FF); 108 | return Result; 109 | } 110 | 111 | // Returns a 32 bit signed value : from 0 to 4294967295 (2^32-1) 112 | public static int GetDIntAt(byte[] Buffer, int Pos) 113 | { 114 | int Result; 115 | Result= Buffer[Pos]; 116 | Result<<=8; 117 | Result+=(Buffer[Pos+1] & 0x0FF); 118 | Result<<=8; 119 | Result+=(Buffer[Pos+2] & 0x0FF); 120 | Result<<=8; 121 | Result+=(Buffer[Pos+3] & 0x0FF); 122 | return Result; 123 | } 124 | 125 | // Returns a 32 bit floating point 126 | public static float GetFloatAt(byte[] Buffer, int Pos) 127 | { 128 | int IntFloat = GetDIntAt(Buffer, Pos); 129 | return Float.intBitsToFloat(IntFloat); 130 | } 131 | 132 | // Returns an ASCII string 133 | public static String GetStringAt(byte[] Buffer, int Pos, int MaxLen) 134 | { 135 | byte[] StrBuffer = new byte[MaxLen]; 136 | System.arraycopy(Buffer, Pos, StrBuffer, 0, MaxLen); 137 | String S; 138 | try { 139 | S = new String(StrBuffer, "UTF-8"); // the charset is UTF-8 140 | } catch (UnsupportedEncodingException ex) { 141 | S = ""; 142 | } 143 | return S; 144 | } 145 | 146 | public static String GetPrintableStringAt(byte[] Buffer, int Pos, int MaxLen) 147 | { 148 | byte[] StrBuffer = new byte[MaxLen]; 149 | System.arraycopy(Buffer, Pos, StrBuffer, 0, MaxLen); 150 | for (int c = 0; c < MaxLen; c++) 151 | { 152 | if ((StrBuffer[c]<31) || (StrBuffer[c]>126)) 153 | StrBuffer[c]=46; // '.' 154 | } 155 | String S; 156 | try { 157 | S = new String(StrBuffer, "UTF-8"); // the charset is UTF-8 158 | } catch (UnsupportedEncodingException ex) { 159 | S = ""; 160 | } 161 | return S; 162 | } 163 | 164 | public static Date GetDateAt(byte[] Buffer, int Pos) 165 | { 166 | int Year, Month, Day, Hour, Min, Sec; 167 | Calendar S7Date = Calendar.getInstance(); 168 | 169 | Year = S7.BCDtoByte(Buffer[Pos]); 170 | if (Year<90) 171 | Year+=2000; 172 | else 173 | Year+=1900; 174 | 175 | Month=S7.BCDtoByte(Buffer[Pos+1])-1; 176 | Day =S7.BCDtoByte(Buffer[Pos+2]); 177 | Hour =S7.BCDtoByte(Buffer[Pos+3]); 178 | Min =S7.BCDtoByte(Buffer[Pos+4]); 179 | Sec =S7.BCDtoByte(Buffer[Pos+5]); 180 | 181 | S7Date.set(Year, Month, Day, Hour, Min, Sec); 182 | 183 | return S7Date.getTime(); 184 | } 185 | 186 | public static void SetBitAt(byte[] Buffer, int Pos, int Bit, boolean Value) 187 | { 188 | byte[] Mask = { 189 | (byte)0x01,(byte)0x02,(byte)0x04,(byte)0x08, 190 | (byte)0x10,(byte)0x20,(byte)0x40,(byte)0x80 191 | }; 192 | if (Bit<0) Bit=0; 193 | if (Bit>7) Bit=7; 194 | 195 | if (Value) 196 | Buffer[Pos]= (byte) (Buffer[Pos] | Mask[Bit]); 197 | else 198 | Buffer[Pos]= (byte) (Buffer[Pos] & ~Mask[Bit]); 199 | } 200 | 201 | public static void SetWordAt(byte[] Buffer, int Pos, int Value) 202 | { 203 | int Word = Value & 0x0FFFF; 204 | Buffer[Pos] = (byte) (Word >> 8); 205 | Buffer[Pos+1] = (byte) (Word & 0x00FF); 206 | } 207 | 208 | public static void SetShortAt(byte[] Buffer, int Pos, int Value) 209 | { 210 | Buffer[Pos] = (byte) (Value >> 8); 211 | Buffer[Pos+1] = (byte) (Value & 0x00FF); 212 | } 213 | public static void SetDWordAt(byte[] Buffer, int Pos, long Value) 214 | { 215 | long DWord = Value &0x0FFFFFFFF; 216 | Buffer[Pos+3] = (byte) (DWord &0xFF); 217 | Buffer[Pos+2] = (byte) ((DWord >> 8) &0xFF); 218 | Buffer[Pos+1] = (byte) ((DWord >> 16) &0xFF); 219 | Buffer[Pos] = (byte) ((DWord >> 24) &0xFF); 220 | } 221 | 222 | public static void SetDIntAt(byte[] Buffer, int Pos, int Value) 223 | { 224 | Buffer[Pos+3] = (byte) (Value &0xFF); 225 | Buffer[Pos+2] = (byte) ((Value >> 8) &0xFF); 226 | Buffer[Pos+1] = (byte) ((Value >> 16) &0xFF); 227 | Buffer[Pos] = (byte) ((Value >> 24) &0xFF); 228 | } 229 | 230 | public static void SetFloatAt(byte[] Buffer, int Pos, float Value) 231 | { 232 | int DInt = Float.floatToIntBits(Value); 233 | SetDIntAt(Buffer, Pos, DInt); 234 | } 235 | 236 | public static void SetDateAt(byte[] Buffer, int Pos, Date DateTime) 237 | { 238 | int Year, Month, Day, Hour, Min, Sec, Dow; 239 | Calendar S7Date = Calendar.getInstance(); 240 | S7Date.setTime(DateTime); 241 | 242 | Year = S7Date.get(Calendar.YEAR); 243 | Month = S7Date.get(Calendar.MONTH)+1; 244 | Day = S7Date.get(Calendar.DAY_OF_MONTH); 245 | Hour = S7Date.get(Calendar.HOUR_OF_DAY); 246 | Min = S7Date.get(Calendar.MINUTE); 247 | Sec = S7Date.get(Calendar.SECOND); 248 | Dow = S7Date.get(Calendar.DAY_OF_WEEK); 249 | 250 | if (Year>1999) 251 | Year-=2000; 252 | 253 | Buffer[Pos] =ByteToBCD(Year); 254 | Buffer[Pos+1]=ByteToBCD(Month); 255 | Buffer[Pos+2]=ByteToBCD(Day); 256 | Buffer[Pos+3]=ByteToBCD(Hour); 257 | Buffer[Pos+4]=ByteToBCD(Min); 258 | Buffer[Pos+5]=ByteToBCD(Sec); 259 | Buffer[Pos+6]=0; 260 | Buffer[Pos+7]=ByteToBCD(Dow); 261 | } 262 | 263 | public static int BCDtoByte(byte B) 264 | { 265 | return ((B >> 4) * 10) + (B & 0x0F); 266 | } 267 | 268 | public static byte ByteToBCD(int Value) 269 | { 270 | return (byte) (((Value / 10) << 4) | (Value % 10)); 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7BlockInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | import java.util.Date; 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | public class S7BlockInfo { 14 | 15 | private final int BufSize = 96; 16 | // MilliSeconds between 1970/1/1 (Java time base) and 1984/1/1 (Siemens base) 17 | private final long DeltaMilliSecs = 441763200000L; 18 | protected byte[] Buffer = new byte[BufSize]; 19 | 20 | protected void Update(byte[] Src, int Pos) 21 | { 22 | System.arraycopy(Src, Pos, Buffer, 0, BufSize); 23 | } 24 | public int BlkType() 25 | { 26 | return Buffer[2]; 27 | } 28 | public int BlkNumber() 29 | { 30 | return S7.GetWordAt(Buffer, 3); 31 | } 32 | public int BlkLang() 33 | { 34 | return Buffer[1]; 35 | } 36 | public int BlkFlags() 37 | { 38 | return Buffer[0]; 39 | } 40 | public int MC7Size() // The real size in bytes 41 | { 42 | return S7.GetWordAt(Buffer, 31); 43 | } 44 | public int LoadSize() 45 | { 46 | return S7.GetDIntAt(Buffer, 5); 47 | } 48 | public int LocalData() 49 | { 50 | return S7.GetWordAt(Buffer, 29); 51 | } 52 | public int SBBLength() 53 | { 54 | return S7.GetWordAt(Buffer, 25); 55 | } 56 | public int Checksum() 57 | { 58 | return S7.GetWordAt(Buffer, 59); 59 | } 60 | public int Version() 61 | { 62 | return Buffer[57]; 63 | } 64 | public Date CodeDate() 65 | { 66 | long BlockDate = ((long)S7.GetWordAt(Buffer, 17))*86400000L+DeltaMilliSecs; 67 | return new Date(BlockDate); 68 | } 69 | public Date IntfDate() 70 | { 71 | long BlockDate = ((long)S7.GetWordAt(Buffer, 23))*86400000L+DeltaMilliSecs; 72 | return new Date(BlockDate); 73 | } 74 | public String Author() 75 | { 76 | return S7.GetStringAt(Buffer,33,8); 77 | } 78 | public String Family() 79 | { 80 | return S7.GetStringAt(Buffer,41,8); 81 | } 82 | public String Header() 83 | { 84 | return S7.GetStringAt(Buffer,49,8); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7Client.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | import java.io.DataInputStream; 10 | import java.io.DataOutputStream; 11 | import java.io.IOException; 12 | import java.io.UnsupportedEncodingException; 13 | import java.net.InetSocketAddress; 14 | import java.net.Socket; 15 | import java.net.SocketAddress; 16 | import java.util.Date; 17 | 18 | 19 | /** 20 | * 21 | * @author Dave Nardella 22 | */ 23 | public class S7Client 24 | { 25 | // WordLength 26 | private static final byte S7WLByte =0x02; 27 | private static final byte S7WLCounter =0x1C; 28 | private static final byte S7WLTimer =0x1D; 29 | // Error Codes 30 | public static final int errTCPConnectionFailed = 0x0001; 31 | public static final int errTCPDataSend = 0x0002; 32 | public static final int errTCPDataRecv = 0x0003; 33 | public static final int errTCPDataRecvTout = 0x0004; 34 | public static final int errTCPConnectionReset = 0x0005; 35 | public static final int errISOInvalidPDU = 0x0006; 36 | public static final int errISOConnectionFailed = 0x0007; 37 | public static final int errISONegotiatingPDU = 0x0008; 38 | public static final int errS7InvalidPDU = 0x0009; 39 | public static final int errS7DataRead = 0x000A; 40 | public static final int errS7DataWrite = 0x000B; 41 | public static final int errS7BufferTooSmall = 0x000C; 42 | public static final int errS7FunctionError = 0x000D; 43 | public static final int errS7InvalidParams = 0x000E; 44 | 45 | // Public fields 46 | public boolean Connected = false; 47 | public int LastError = 0; 48 | public int RecvTimeout = 2000; 49 | 50 | // Privates 51 | private static final int ISOTCP = 102; // ISOTCP Port 52 | private static final int MinPduSize = 16; 53 | private static final int DefaultPduSizeRequested = 480; 54 | private static final int IsoHSize = 7; // TPKT+COTP Header Size 55 | private static final int MaxPduSize = DefaultPduSizeRequested+IsoHSize; 56 | 57 | 58 | private Socket TCPSocket; 59 | private final byte[] PDU = new byte[2048]; 60 | 61 | private DataInputStream InStream = null; 62 | private DataOutputStream OutStream = null; 63 | 64 | private String IPAddress; 65 | private int TCPPort; 66 | 67 | private byte LocalTSAP_HI; 68 | private byte LocalTSAP_LO; 69 | private byte RemoteTSAP_HI; 70 | private byte RemoteTSAP_LO; 71 | private byte LastPDUType; 72 | 73 | private short ConnType = S7.PG; 74 | private int _PDULength = 0; 75 | 76 | // Telegrams 77 | // ISO Connection Request telegram (contains also ISO Header and COTP Header) 78 | private static final byte ISO_CR[] = { 79 | // TPKT (RFC1006 Header) 80 | (byte)0x03, // RFC 1006 ID (3) 81 | (byte)0x00, // Reserved, always 0 82 | (byte)0x00, // High part of packet lenght (entire frame, payload and TPDU included) 83 | (byte)0x16, // Low part of packet lenght (entire frame, payload and TPDU included) 84 | // COTP (ISO 8073 Header) 85 | (byte)0x11, // PDU Size Length 86 | (byte)0xE0, // CR - Connection Request ID 87 | (byte)0x00, // Dst Reference HI 88 | (byte)0x00, // Dst Reference LO 89 | (byte)0x00, // Src Reference HI 90 | (byte)0x01, // Src Reference LO 91 | (byte)0x00, // Class + Options Flags 92 | (byte)0xC0, // PDU Max Length ID 93 | (byte)0x01, // PDU Max Length HI 94 | (byte)0x0A, // PDU Max Length LO 95 | (byte)0xC1, // Src TSAP Identifier 96 | (byte)0x02, // Src TSAP Length (2 bytes) 97 | (byte)0x01, // Src TSAP HI (will be overwritten) 98 | (byte)0x00, // Src TSAP LO (will be overwritten) 99 | (byte)0xC2, // Dst TSAP Identifier 100 | (byte)0x02, // Dst TSAP Length (2 bytes) 101 | (byte)0x01, // Dst TSAP HI (will be overwritten) 102 | (byte)0x02 // Dst TSAP LO (will be overwritten) 103 | }; 104 | 105 | // S7 PDU Negotiation Telegram (contains also ISO Header and COTP Header) 106 | private static final byte S7_PN[] = { 107 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x19, 108 | (byte)0x02, (byte)0xf0, (byte)0x80, // TPKT + COTP (see above for info) 109 | (byte)0x32, (byte)0x01, (byte)0x00, (byte)0x00, 110 | (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x08, 111 | (byte)0x00, (byte)0x00, (byte)0xf0, (byte)0x00, 112 | (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x01, 113 | (byte)0x00, (byte)0x1e // PDU Length Requested = HI-LO 480 bytes 114 | }; 115 | 116 | // S7 Read/Write Request Header (contains also ISO Header and COTP Header) 117 | private static final byte S7_RW[] = { // 31-35 bytes 118 | (byte)0x03,(byte)0x00, 119 | (byte)0x00,(byte)0x1f, // Telegram Length (Data Size + 31 or 35) 120 | (byte)0x02,(byte)0xf0, (byte)0x80, // COTP (see above for info) 121 | (byte)0x32, // S7 Protocol ID 122 | (byte)0x01, // Job Type 123 | (byte)0x00,(byte)0x00, // Redundancy identification 124 | (byte)0x05,(byte)0x00, // PDU Reference 125 | (byte)0x00,(byte)0x0e, // Parameters Length 126 | (byte)0x00,(byte)0x00, // Data Length = Size(bytes) + 4 127 | (byte)0x04, // Function 4 Read Var, 5 Write Var 128 | (byte)0x01, // Items count 129 | (byte)0x12, // Var spec. 130 | (byte)0x0a, // Length of remaining bytes 131 | (byte)0x10, // Syntax ID 132 | S7WLByte, // Transport Size 133 | (byte)0x00,(byte)0x00, // Num Elements 134 | (byte)0x00,(byte)0x00, // DB Number (if any, else 0) 135 | (byte)0x84, // Area Type 136 | (byte)0x00,(byte)0x00,(byte)0x00, // Area Offset 137 | // WR area 138 | (byte)0x00, // Reserved 139 | (byte)0x04, // Transport size 140 | (byte)0x00,(byte)0x00, // Data Length * 8 (if not timer or counter) 141 | }; 142 | private static final int Size_RD = 31; 143 | private static final int Size_WR = 35; 144 | 145 | // S7 Get Block Info Request Header (contains also ISO Header and COTP Header) 146 | private static final byte S7_BI[] = { 147 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x25, 148 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 149 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x05, 150 | (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, 151 | (byte)0x0c, (byte)0x00, (byte)0x01, (byte)0x12, 152 | (byte)0x04, (byte)0x11, (byte)0x43, (byte)0x03, 153 | (byte)0x00, (byte)0xff, (byte)0x09, (byte)0x00, 154 | (byte)0x08, (byte)0x30, 155 | (byte)0x41, // Block Type 156 | (byte)0x30, (byte)0x30, (byte)0x30, (byte)0x30, (byte)0x30, // ASCII Block Number 157 | (byte)0x41 158 | }; 159 | 160 | // SZL First telegram request 161 | private static final byte S7_SZL_FIRST[] = { 162 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x21, 163 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 164 | (byte)0x07, (byte)0x00, (byte)0x00, 165 | (byte)0x05, (byte)0x00, // Sequence out 166 | (byte)0x00, (byte)0x08, (byte)0x00, 167 | (byte)0x08, (byte)0x00, (byte)0x01, (byte)0x12, 168 | (byte)0x04, (byte)0x11, (byte)0x44, (byte)0x01, 169 | (byte)0x00, (byte)0xff, (byte)0x09, (byte)0x00, 170 | (byte)0x04, 171 | (byte)0x00, (byte)0x00, // ID (29) 172 | (byte)0x00, (byte)0x00 // Index (31) 173 | }; 174 | 175 | // SZL Next telegram request 176 | private static final byte S7_SZL_NEXT[] = { 177 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x21, 178 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 179 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x06, 180 | (byte)0x00, (byte)0x00, (byte)0x0c, (byte)0x00, 181 | (byte)0x04, (byte)0x00, (byte)0x01, (byte)0x12, 182 | (byte)0x08, (byte)0x12, (byte)0x44, (byte)0x01, 183 | (byte)0x01, // Sequence 184 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 185 | (byte)0x0a, (byte)0x00, (byte)0x00, (byte)0x00 186 | }; 187 | 188 | // Get Date/Time request 189 | private static final byte S7_GET_DT[] = { 190 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x1d, 191 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 192 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x38, 193 | (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, 194 | (byte)0x04, (byte)0x00, (byte)0x01, (byte)0x12, 195 | (byte)0x04, (byte)0x11, (byte)0x47, (byte)0x01, 196 | (byte)0x00, (byte)0x0a, (byte)0x00, (byte)0x00, 197 | (byte)0x00 198 | }; 199 | 200 | // Set Date/Time command 201 | private static final byte S7_SET_DT[] = { 202 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x27, 203 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 204 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x89, 205 | (byte)0x03, (byte)0x00, (byte)0x08, (byte)0x00, 206 | (byte)0x0e, (byte)0x00, (byte)0x01, (byte)0x12, 207 | (byte)0x04, (byte)0x11, (byte)0x47, (byte)0x02, 208 | (byte)0x00, (byte)0xff, (byte)0x09, (byte)0x00, 209 | (byte)0x0a, (byte)0x00, (byte)0x19, // Hi part of Year 210 | (byte)0x13, // Lo part of Year 211 | (byte)0x12, // Month 212 | (byte)0x06, // Day 213 | (byte)0x17, // Hour 214 | (byte)0x37, // Min 215 | (byte)0x13, // Sec 216 | (byte)0x00, (byte)0x01 // ms + Day of week 217 | }; 218 | 219 | // S7 STOP request 220 | private static final byte S7_STOP[] = { 221 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x21, 222 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 223 | (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x0e, 224 | (byte)0x00, (byte)0x00, (byte)0x10, (byte)0x00, 225 | (byte)0x00, (byte)0x29, (byte)0x00, (byte)0x00, 226 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x09, 227 | (byte)0x50, (byte)0x5f, (byte)0x50, (byte)0x52, 228 | (byte)0x4f, (byte)0x47, (byte)0x52, (byte)0x41, 229 | (byte)0x4d 230 | }; 231 | 232 | // S7 HOT Start request 233 | private static final byte S7_HOT_START[] = { 234 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x25, 235 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 236 | (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x0c, 237 | (byte)0x00, (byte)0x00, (byte)0x14, (byte)0x00, 238 | (byte)0x00, (byte)0x28, (byte)0x00, (byte)0x00, 239 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 240 | (byte)0xfd, (byte)0x00, (byte)0x00, (byte)0x09, 241 | (byte)0x50, (byte)0x5f, (byte)0x50, (byte)0x52, 242 | (byte)0x4f, (byte)0x47, (byte)0x52, (byte)0x41, 243 | (byte)0x4d 244 | }; 245 | 246 | // S7 COLD Start request 247 | private static final byte S7_COLD_START[] = { 248 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x27, 249 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 250 | (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x0f, 251 | (byte)0x00, (byte)0x00, (byte)0x16, (byte)0x00, 252 | (byte)0x00, (byte)0x28, (byte)0x00, (byte)0x00, 253 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 254 | (byte)0xfd, (byte)0x00, (byte)0x02, (byte)0x43, 255 | (byte)0x20, (byte)0x09, (byte)0x50, (byte)0x5f, 256 | (byte)0x50, (byte)0x52, (byte)0x4f, (byte)0x47, 257 | (byte)0x52, (byte)0x41, (byte)0x4d 258 | }; 259 | 260 | // S7 Get PLC Status 261 | private static final byte S7_GET_STAT[] = { 262 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x21, 263 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 264 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x2c, 265 | (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, 266 | (byte)0x08, (byte)0x00, (byte)0x01, (byte)0x12, 267 | (byte)0x04, (byte)0x11, (byte)0x44, (byte)0x01, 268 | (byte)0x00, (byte)0xff, (byte)0x09, (byte)0x00, 269 | (byte)0x04, (byte)0x04, (byte)0x24, (byte)0x00, 270 | (byte)0x00 271 | }; 272 | 273 | // S7 Set Session Password 274 | private static final byte S7_SET_PWD[] = { 275 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x25, 276 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 277 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x27, 278 | (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, 279 | (byte)0x0c, (byte)0x00, (byte)0x01, (byte)0x12, 280 | (byte)0x04, (byte)0x11, (byte)0x45, (byte)0x01, 281 | (byte)0x00, (byte)0xff, (byte)0x09, (byte)0x00, 282 | (byte)0x08, 283 | // 8 Char Encoded Password 284 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 285 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 286 | }; 287 | 288 | // S7 Clear Session Password 289 | private static final byte S7_CLR_PWD[] = { 290 | (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x1d, 291 | (byte)0x02, (byte)0xf0, (byte)0x80, (byte)0x32, 292 | (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x29, 293 | (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, 294 | (byte)0x04, (byte)0x00, (byte)0x01, (byte)0x12, 295 | (byte)0x04, (byte)0x11, (byte)0x45, (byte)0x02, 296 | (byte)0x00, (byte)0x0a, (byte)0x00, (byte)0x00, 297 | (byte)0x00 298 | }; 299 | 300 | public S7Client() 301 | { 302 | // Placeholder for future implementations 303 | } 304 | 305 | public static String ErrorText(int Error) 306 | { 307 | switch (Error) 308 | { 309 | case errTCPConnectionFailed : 310 | return "TCP Connection failed."; 311 | case errTCPDataSend : 312 | return "TCP Sending error."; 313 | case errTCPDataRecv : 314 | return "TCP Receiving error."; 315 | case errTCPDataRecvTout : 316 | return "Data Receiving timeout."; 317 | case errTCPConnectionReset : 318 | return "Connection reset by the peer."; 319 | case errISOInvalidPDU : 320 | return "Invalid ISO PDU received."; 321 | case errISOConnectionFailed : 322 | return "ISO connection refused by the CPU."; 323 | case errISONegotiatingPDU : 324 | return "ISO error negotiating the PDU length."; 325 | case errS7InvalidPDU : 326 | return "Invalid S7 PDU received."; 327 | case errS7DataRead : 328 | return "S7 Error reading data from the CPU."; 329 | case errS7DataWrite : 330 | return "S7 Error writing data to the CPU."; 331 | case errS7BufferTooSmall : 332 | return "The Buffer supplied to the function is too small."; 333 | case errS7FunctionError : 334 | return "S7 function refused by the CPU."; 335 | case errS7InvalidParams : 336 | return "Invalid parameters supplied to the function."; 337 | default : 338 | return "Unknown error : 0x"+Integer.toHexString(Error); 339 | } 340 | } 341 | 342 | private int TCPConnect() 343 | { 344 | SocketAddress sockaddr = new InetSocketAddress(IPAddress, TCPPort); 345 | LastError=0; 346 | try { 347 | TCPSocket = new Socket(); 348 | TCPSocket.connect(sockaddr ,5000); 349 | TCPSocket.setTcpNoDelay(true); 350 | InStream = new DataInputStream(TCPSocket.getInputStream()); 351 | OutStream = new DataOutputStream(TCPSocket.getOutputStream()); 352 | } 353 | catch (IOException e) { 354 | LastError=errTCPConnectionFailed; 355 | } 356 | return LastError; 357 | } 358 | 359 | private int WaitForData(int Size, int Timeout) 360 | { 361 | int cnt = 0; 362 | LastError=0; 363 | int SizeAvail; 364 | boolean Expired = false; 365 | try 366 | { 367 | SizeAvail=InStream.available(); 368 | while ((SizeAvailTimeout; 380 | // If timeout we clean the buffer 381 | if (Expired && (SizeAvail>0) && (LastError==0)) 382 | InStream.read(PDU, 0, SizeAvail); 383 | } 384 | } 385 | catch (IOException ex) 386 | { 387 | LastError=errTCPDataRecvTout; 388 | } 389 | if (cnt>=Timeout) 390 | { 391 | LastError=errTCPDataRecvTout; 392 | } 393 | return LastError; 394 | } 395 | 396 | private int RecvPacket(byte[] Buffer, int Start, int Size) 397 | { 398 | int BytesRead=0; 399 | LastError=WaitForData(Size,RecvTimeout); 400 | if (LastError==0) 401 | { 402 | try { 403 | BytesRead = InStream.read(Buffer, Start, Size); 404 | } catch (IOException ex) { 405 | LastError=errTCPDataRecv; 406 | } 407 | if (BytesRead==0) 408 | LastError=errTCPConnectionReset; 409 | } 410 | return LastError; 411 | } 412 | 413 | private void SendPacket(byte[] Buffer, int Len) 414 | { 415 | LastError = 0; 416 | try { 417 | OutStream.write(Buffer,0,Len); 418 | OutStream.flush(); 419 | } catch (IOException ex) { 420 | LastError = errTCPDataSend; 421 | } 422 | } 423 | private void SendPacket(byte[] Buffer) 424 | { 425 | SendPacket(Buffer,Buffer.length); 426 | } 427 | 428 | private int RecvIsoPacket() 429 | { 430 | Boolean Done = false; 431 | int Size = 0; 432 | while ((LastError==0) && !Done) 433 | { 434 | // Get TPKT (4 bytes) 435 | RecvPacket(PDU, 0, 4); 436 | if (LastError==0) 437 | { 438 | Size=S7.GetWordAt(PDU,2); 439 | // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) 440 | if (Size==IsoHSize) 441 | RecvPacket(PDU,4, 3); // Skip remaining 3 bytes and Done is still false 442 | else 443 | { 444 | if ((Size>MaxPduSize) || (Size16 && <247 448 | } 449 | } 450 | } 451 | if (LastError==0) 452 | { 453 | RecvPacket(PDU,4, 3); // Skip remaining 3 COTP bytes 454 | LastPDUType=PDU[5]; // Stores PDU Type, we need it 455 | // Receives the S7 Payload 456 | RecvPacket(PDU, 7, Size-IsoHSize); 457 | } 458 | if (LastError==0) 459 | return Size; 460 | else 461 | return 0; 462 | } 463 | 464 | private int ISOConnect() 465 | { 466 | int Size; 467 | ISO_CR[16]=LocalTSAP_HI; 468 | ISO_CR[17]=LocalTSAP_LO; 469 | ISO_CR[20]=RemoteTSAP_HI; 470 | ISO_CR[21]=RemoteTSAP_LO; 471 | 472 | // Sends the connection request telegram 473 | SendPacket(ISO_CR); 474 | if (LastError==0) 475 | { 476 | // Gets the reply (if any) 477 | Size=RecvIsoPacket(); 478 | if (LastError==0) 479 | { 480 | if (Size==22) 481 | { 482 | if (LastPDUType!=(byte)0xD0) // 0xD0 = CC Connection confirm 483 | LastError=errISOConnectionFailed; 484 | } 485 | else 486 | LastError=errISOInvalidPDU; 487 | } 488 | } 489 | return LastError; 490 | } 491 | 492 | private int NegotiatePduLength() 493 | { 494 | int Length; 495 | // Set PDU Size Requested 496 | S7.SetWordAt(S7_PN,23,DefaultPduSizeRequested); 497 | // Sends the connection request telegram 498 | SendPacket(S7_PN); 499 | if (LastError==0) 500 | { 501 | Length=RecvIsoPacket(); 502 | if (LastError==0) 503 | { 504 | // check S7 Error 505 | if ((Length==27) && (PDU[17]==0) && (PDU[18]==0)) // 20 = size of Negotiate Answer 506 | { 507 | // Get PDU Size Negotiated 508 | _PDULength = S7.GetWordAt(PDU,25); 509 | if (_PDULength>0) 510 | return 0; 511 | else 512 | LastError=errISONegotiatingPDU; 513 | } 514 | else 515 | LastError=errISONegotiatingPDU; 516 | } 517 | } 518 | return LastError; 519 | } 520 | 521 | public void SetConnectionType(short ConnectionType) 522 | { 523 | ConnType=ConnectionType; 524 | } 525 | 526 | public int Connect() 527 | { 528 | LastError=0; 529 | if (!Connected) 530 | { 531 | TCPConnect(); 532 | if (LastError==0) // First stage : TCP Connection 533 | { 534 | ISOConnect(); 535 | if (LastError==0) // Second stage : ISOTCP (ISO 8073) Connection 536 | { 537 | LastError=NegotiatePduLength(); // Third stage : S7 PDU negotiation 538 | } 539 | } 540 | } 541 | Connected=LastError==0; 542 | 543 | // In case the connection is not completely established (TCP connection + ISO connection + PDU negotiation) 544 | // we close the socket and its IO streams to revert the object back to pre-Connect() state 545 | if (!Connected) 546 | { 547 | if (TCPSocket != null) { 548 | try { 549 | TCPSocket.close(); 550 | } catch (IOException ex) { 551 | } 552 | } 553 | if (InStream != null) { 554 | try { 555 | InStream.close(); 556 | } catch (IOException ex) { 557 | } 558 | } 559 | if (OutStream != null) { 560 | try { 561 | OutStream.close(); 562 | } catch (IOException ex) { 563 | } 564 | } 565 | _PDULength = 0; 566 | } 567 | 568 | return LastError; 569 | } 570 | 571 | public void Disconnect() 572 | { 573 | if (Connected) 574 | { 575 | try { 576 | OutStream.close(); 577 | InStream.close(); 578 | TCPSocket.close(); 579 | _PDULength=0; 580 | } catch (IOException ex) { 581 | } 582 | Connected=false; 583 | } 584 | } 585 | 586 | public int ConnectTo(String Address, int Rack, int Slot) { 587 | //Use the default port 588 | return ConnectTo(Address, Rack, Slot, ISOTCP); 589 | } 590 | 591 | public int ConnectTo(String Address, int Rack, int Slot,int Port) 592 | { 593 | int RemoteTSAP=(ConnType<<8)+ (Rack * 0x20) + Slot; 594 | SetConnectionParams(Address, Port, 0x0100, RemoteTSAP); 595 | return Connect(); 596 | } 597 | 598 | public int PDULength() 599 | { 600 | return _PDULength; 601 | } 602 | 603 | public void SetConnectionParams(String Address, int LocalTSAP, int RemoteTSAP) { 604 | SetConnectionParams(Address, ISOTCP, LocalTSAP, RemoteTSAP); 605 | } 606 | public void SetConnectionParams(String Address, int Port, int LocalTSAP, int RemoteTSAP) 607 | { 608 | int LocTSAP = LocalTSAP & 0x0000FFFF; 609 | int RemTSAP = RemoteTSAP & 0x0000FFFF; 610 | IPAddress = Address; 611 | TCPPort = Port; 612 | LocalTSAP_HI = (byte) (LocTSAP>>8); 613 | LocalTSAP_LO = (byte) (LocTSAP & 0x00FF); 614 | RemoteTSAP_HI= (byte) (RemTSAP>>8); 615 | RemoteTSAP_LO= (byte) (RemTSAP & 0x00FF); 616 | } 617 | 618 | public int ReadArea(int Area, int DBNumber, int Start, int Amount, byte[] Data) 619 | { 620 | int Address; 621 | int NumElements; 622 | int MaxElements; 623 | int TotElements; 624 | int SizeRequested; 625 | int Length; 626 | int Offset = 0; 627 | int WordSize = 1; 628 | 629 | LastError=0; 630 | 631 | // If we are addressing Timers or counters the element size is 2 632 | if ((Area==S7.S7AreaCT) || (Area==S7.S7AreaTM)) 633 | WordSize = 2; 634 | 635 | MaxElements=(_PDULength-18) / WordSize; // 18 = Reply telegram header 636 | TotElements=Amount; 637 | 638 | while ((TotElements>0) && (LastError==0)) 639 | { 640 | NumElements=TotElements; 641 | if (NumElements>MaxElements) 642 | NumElements=MaxElements; 643 | 644 | SizeRequested = NumElements * WordSize; 645 | 646 | // Setup the telegram 647 | System.arraycopy(S7_RW, 0, PDU, 0, Size_RD); 648 | // Set DB Number 649 | PDU[27] = (byte) Area; 650 | // Set Area 651 | if (Area==S7.S7AreaDB) 652 | S7.SetWordAt(PDU,25,DBNumber); 653 | 654 | // Adjusts Start and word length 655 | if ((Area==S7.S7AreaCT) || (Area==S7.S7AreaTM)) 656 | { 657 | Address = Start; 658 | if (Area==S7.S7AreaCT) 659 | PDU[22]=S7WLCounter; 660 | else 661 | PDU[22]=S7WLTimer; 662 | } 663 | else 664 | Address = Start<<3; 665 | 666 | // Num elements 667 | S7.SetWordAt(PDU,23,NumElements); 668 | 669 | // Address into the PLC (only 3 bytes) 670 | PDU[30] = (byte) (Address & 0x0FF); 671 | Address = Address >> 8; 672 | PDU[29] = (byte) (Address & 0x0FF); 673 | Address = Address >> 8; 674 | PDU[28] = (byte) (Address & 0x0FF); 675 | 676 | SendPacket(PDU, Size_RD); 677 | if (LastError==0) 678 | { 679 | Length=RecvIsoPacket(); 680 | if (LastError==0) 681 | { 682 | if (Length>=25) 683 | { 684 | if ((Length-25==SizeRequested) && (PDU[21]==(byte)0xFF)) 685 | { 686 | System.arraycopy(PDU, 25, Data, Offset, SizeRequested); 687 | Offset+=SizeRequested; 688 | } 689 | else 690 | LastError = errS7DataRead; 691 | } 692 | else 693 | LastError = errS7InvalidPDU; 694 | } 695 | } 696 | 697 | TotElements -= NumElements; 698 | Start += NumElements*WordSize; 699 | } 700 | return LastError; 701 | } 702 | 703 | public int WriteArea(int Area, int DBNumber, int Start, int Amount, byte[] Data) 704 | { 705 | int Address; 706 | int NumElements; 707 | int MaxElements; 708 | int TotElements; 709 | int DataSize; 710 | int IsoSize; 711 | int Length; 712 | int Offset = 0; 713 | int WordSize = 1; 714 | 715 | LastError=0; 716 | 717 | // If we are addressing Timers or counters the element size is 2 718 | if ((Area==S7.S7AreaCT) || (Area==S7.S7AreaTM)) 719 | WordSize = 2; 720 | 721 | MaxElements=(_PDULength-35) / WordSize; // 18 = Reply telegram header 722 | TotElements=Amount; 723 | 724 | while ((TotElements>0) && (LastError==0)) 725 | { 726 | NumElements=TotElements; 727 | if (NumElements>MaxElements) 728 | NumElements=MaxElements; 729 | 730 | DataSize = NumElements * WordSize; 731 | IsoSize = Size_WR + DataSize; 732 | 733 | // Setup the telegram 734 | System.arraycopy(S7_RW, 0, PDU, 0, Size_WR); 735 | // Whole telegram Size 736 | S7.SetWordAt(PDU,2,IsoSize); 737 | // Data Length 738 | Length=DataSize+4; 739 | S7.SetWordAt(PDU,15,Length); 740 | // Function 741 | PDU[17]= (byte) 0x05; 742 | // Set DB Number 743 | PDU[27] = (byte) Area; 744 | if (Area==S7.S7AreaDB) 745 | S7.SetWordAt(PDU,25,DBNumber); 746 | 747 | // Adjusts Start and word length 748 | if ((Area==S7.S7AreaCT) || (Area==S7.S7AreaTM)) 749 | { 750 | Address = Start; 751 | Length = DataSize; 752 | if (Area==S7.S7AreaCT) 753 | PDU[22]=S7WLCounter; 754 | else 755 | PDU[22]=S7WLTimer; 756 | } 757 | else 758 | { 759 | Address = Start<<3; 760 | Length = DataSize<<3; 761 | } 762 | // Num elements 763 | S7.SetWordAt(PDU,23,NumElements); 764 | // Address into the PLC 765 | PDU[30] = (byte) (Address & 0x0FF); 766 | Address = Address >> 8; 767 | PDU[29] = (byte) (Address & 0x0FF); 768 | Address = Address >> 8; 769 | PDU[28] = (byte) (Address & 0x0FF); 770 | // Length 771 | S7.SetWordAt(PDU,33,Length); 772 | 773 | // Copies the Data 774 | System.arraycopy(Data, Offset, PDU, 35, DataSize); 775 | 776 | SendPacket(PDU, IsoSize); 777 | if (LastError==0) 778 | { 779 | Length=RecvIsoPacket(); 780 | if (LastError==0) 781 | { 782 | if (Length==22) 783 | { 784 | if ((S7.GetWordAt(PDU,17)!=0) || (PDU[21]!=(byte)0xFF)) 785 | LastError = errS7DataWrite; 786 | } 787 | else 788 | LastError = errS7InvalidPDU; 789 | } 790 | } 791 | 792 | Offset+=DataSize; 793 | TotElements -= NumElements; 794 | Start += NumElements*WordSize; 795 | } 796 | return LastError; 797 | } 798 | 799 | public int GetAgBlockInfo(int BlockType, int BlockNumber, S7BlockInfo Block) 800 | { 801 | int Length; 802 | LastError=0; 803 | // Block Type 804 | S7_BI[30] = (byte) BlockType; 805 | // Block Number 806 | S7_BI[31]=(byte) ((BlockNumber / 10000)+0x30); 807 | BlockNumber=BlockNumber % 10000; 808 | S7_BI[32]=(byte) ((BlockNumber / 1000)+0x30); 809 | BlockNumber=BlockNumber % 1000; 810 | S7_BI[33]=(byte) ((BlockNumber / 100)+0x30); 811 | BlockNumber=BlockNumber % 100; 812 | S7_BI[34]=(byte) ((BlockNumber / 10)+0x30); 813 | BlockNumber=BlockNumber % 10; 814 | S7_BI[35]=(byte) ((BlockNumber / 1)+0x30); 815 | 816 | SendPacket(S7_BI); 817 | if (LastError==0) 818 | { 819 | Length=RecvIsoPacket(); 820 | if (Length > 32) // the minimum expected 821 | { 822 | if ((S7.GetWordAt(PDU,27)==0) && (PDU[29]==(byte)0xFF)) 823 | { 824 | Block.Update(PDU, 42); 825 | } 826 | else 827 | LastError = errS7FunctionError; 828 | } 829 | else 830 | LastError = errS7InvalidPDU; 831 | } 832 | 833 | return LastError; 834 | } 835 | /** 836 | * 837 | * @param DBNumber DB Number 838 | * @param Buffer Destination buffer 839 | * @param SizeRead How many bytes were read 840 | * @return 841 | */ 842 | public int DBGet(int DBNumber, byte[] Buffer, IntByRef SizeRead) 843 | { 844 | S7BlockInfo Block = new S7BlockInfo(); 845 | // Query the DB Length 846 | LastError = GetAgBlockInfo(S7.Block_DB, DBNumber, Block); 847 | if (LastError==0) 848 | { 849 | int SizeToRead = Block.MC7Size(); 850 | // Checks the room 851 | if (SizeToRead<=Buffer.length) 852 | { 853 | LastError=ReadArea(S7.S7AreaDB, DBNumber, 0, SizeToRead, Buffer); 854 | if (LastError==0) 855 | SizeRead.Value=SizeToRead; 856 | } 857 | else 858 | LastError=errS7BufferTooSmall; 859 | } 860 | return LastError; 861 | } 862 | 863 | public int ReadSZL(int ID, int Index, S7Szl SZL) 864 | { 865 | int Length; 866 | int DataSZL; 867 | int Offset = 0; 868 | boolean Done = false; 869 | boolean First = true; 870 | byte Seq_in =0x00; 871 | int Seq_out =0x0000; 872 | 873 | LastError=0; 874 | SZL.DataSize=0; 875 | do 876 | { 877 | if (First) 878 | { 879 | S7.SetWordAt(S7_SZL_FIRST, 11, ++Seq_out); 880 | S7.SetWordAt(S7_SZL_FIRST, 29, ID); 881 | S7.SetWordAt(S7_SZL_FIRST, 31, Index); 882 | SendPacket(S7_SZL_FIRST); 883 | } 884 | else 885 | { 886 | S7.SetWordAt(S7_SZL_NEXT, 11, ++Seq_out); 887 | PDU[24] = (byte)Seq_in; 888 | SendPacket(S7_SZL_NEXT); 889 | } 890 | if (LastError!=0) 891 | return LastError; 892 | 893 | Length=RecvIsoPacket(); 894 | if (LastError==0) 895 | { 896 | if (First) 897 | { 898 | if (Length > 32) // the minimum expected 899 | { 900 | if ((S7.GetWordAt(PDU,27)==0) && (PDU[29]==(byte)0xFF)) 901 | { 902 | // Gets Amount of this slice 903 | DataSZL=S7.GetWordAt(PDU,31)-8; // Skips extra params (ID, Index ...) 904 | Done=PDU[26]==0x00; 905 | Seq_in=(byte)PDU[24]; // Slice sequence 906 | 907 | SZL.LENTHDR=S7.GetWordAt(PDU, 37); 908 | SZL.N_DR=S7.GetWordAt(PDU, 39); 909 | SZL.Copy(PDU, 41, Offset, DataSZL); 910 | Offset+=DataSZL; 911 | SZL.DataSize+=DataSZL; 912 | } 913 | else 914 | LastError = errS7FunctionError; 915 | } 916 | else 917 | LastError = errS7InvalidPDU; 918 | } 919 | else 920 | { 921 | if (Length > 32) // the minimum expected 922 | { 923 | if ((S7.GetWordAt(PDU,27)==0) && (PDU[29]==(byte)0xFF)) 924 | { 925 | // Gets Amount of this slice 926 | DataSZL=S7.GetWordAt(PDU,31); 927 | Done=PDU[26]==0x00; 928 | Seq_in=(byte)PDU[24]; // Slice sequence 929 | SZL.Copy(PDU, 37, Offset, DataSZL); 930 | Offset+=DataSZL; 931 | SZL.DataSize+=DataSZL; 932 | } 933 | else 934 | LastError = errS7FunctionError; 935 | } 936 | else 937 | LastError = errS7InvalidPDU; 938 | } 939 | } 940 | First=false; 941 | } 942 | while(!Done && (LastError==0)); 943 | 944 | return LastError; 945 | } 946 | 947 | 948 | public int GetCpuInfo(S7CpuInfo Info) 949 | { 950 | S7Szl SZL = new S7Szl(1024); 951 | 952 | LastError = ReadSZL(0x001C, 0x0000, SZL); 953 | if (LastError==0) 954 | { 955 | Info.Update(SZL.Data, 0); 956 | } 957 | return LastError; 958 | } 959 | 960 | public int GetCpInfo(S7CpInfo Info) 961 | { 962 | S7Szl SZL = new S7Szl(1024); 963 | 964 | LastError = ReadSZL(0x0131, 0x0001, SZL); 965 | if (LastError==0) 966 | { 967 | Info.Update(SZL.Data, 0); 968 | } 969 | return LastError; 970 | } 971 | 972 | public int GetOrderCode(S7OrderCode Code) 973 | { 974 | S7Szl SZL = new S7Szl(1024); 975 | 976 | LastError = ReadSZL(0x0011, 0x0000, SZL); 977 | if (LastError==0) 978 | { 979 | Code.Update(SZL.Data, 0, SZL.DataSize); 980 | } 981 | return LastError; 982 | } 983 | 984 | public int GetPlcDateTime(Date DateTime) 985 | { 986 | int Length; 987 | 988 | LastError = 0; 989 | SendPacket(S7_GET_DT); 990 | if (LastError==0) 991 | { 992 | Length=RecvIsoPacket(); 993 | if (Length > 30) // the minimum expected 994 | { 995 | if ((S7.GetWordAt(PDU,27)==0) && (PDU[29]==(byte)0xFF)) 996 | { 997 | DateTime=S7.GetDateAt(PDU, 34); 998 | } 999 | else 1000 | LastError = errS7FunctionError; 1001 | } 1002 | else 1003 | LastError = errS7InvalidPDU; 1004 | } 1005 | 1006 | return LastError; 1007 | } 1008 | 1009 | public int SetPlcDateTime(Date DateTime) 1010 | { 1011 | int Length; 1012 | 1013 | LastError = 0; 1014 | S7.SetDateAt(S7_SET_DT, 31, DateTime); 1015 | 1016 | SendPacket(S7_SET_DT); 1017 | if (LastError==0) 1018 | { 1019 | Length=RecvIsoPacket(); 1020 | if (Length > 30) // the minimum expected 1021 | { 1022 | if (S7.GetWordAt(PDU,27)!=0) 1023 | LastError = errS7FunctionError; 1024 | } 1025 | else 1026 | LastError = errS7InvalidPDU; 1027 | } 1028 | 1029 | return LastError; 1030 | } 1031 | 1032 | public int SetPlcSystemDateTime() 1033 | { 1034 | return SetPlcDateTime(new Date()); 1035 | } 1036 | 1037 | public int PlcStop() 1038 | { 1039 | int Length; 1040 | 1041 | LastError = 0; 1042 | SendPacket(S7_STOP); 1043 | if (LastError==0) 1044 | { 1045 | Length=RecvIsoPacket(); 1046 | if (Length > 18) // 18 is the minimum expected 1047 | { 1048 | if (S7.GetWordAt(PDU,17)!=0) 1049 | LastError = errS7FunctionError; 1050 | } 1051 | else 1052 | LastError = errS7InvalidPDU; 1053 | } 1054 | return LastError; 1055 | } 1056 | 1057 | public int PlcHotStart() 1058 | { 1059 | int Length; 1060 | 1061 | LastError = 0; 1062 | SendPacket(S7_HOT_START); 1063 | if (LastError==0) 1064 | { 1065 | Length=RecvIsoPacket(); 1066 | if (Length > 18) // the minimum expected 1067 | { 1068 | if (S7.GetWordAt(PDU,17)!=0) 1069 | LastError = errS7FunctionError; 1070 | } 1071 | else 1072 | LastError = errS7InvalidPDU; 1073 | } 1074 | return LastError; 1075 | } 1076 | 1077 | public int PlcColdStart() 1078 | { 1079 | int Length; 1080 | 1081 | LastError = 0; 1082 | SendPacket(S7_COLD_START); 1083 | if (LastError==0) 1084 | { 1085 | Length=RecvIsoPacket(); 1086 | if (Length > 18) // the minimum expected 1087 | { 1088 | if (S7.GetWordAt(PDU,17)!=0) 1089 | LastError = errS7FunctionError; 1090 | } 1091 | else 1092 | LastError = errS7InvalidPDU; 1093 | } 1094 | return LastError; 1095 | } 1096 | 1097 | public int GetPlcStatus(IntByRef Status) 1098 | { 1099 | int Length; 1100 | 1101 | LastError = 0; 1102 | SendPacket(S7_GET_STAT); 1103 | if (LastError==0) 1104 | { 1105 | Length=RecvIsoPacket(); 1106 | if (Length > 30) // the minimum expected 1107 | { 1108 | if (S7.GetWordAt(PDU,27)==0) 1109 | { 1110 | switch (PDU[44]) 1111 | { 1112 | case S7.S7CpuStatusUnknown : 1113 | case S7.S7CpuStatusRun : 1114 | case S7.S7CpuStatusStop : Status.Value=PDU[44]; 1115 | break; 1116 | default : 1117 | // Since RUN status is always 0x08 for all CPUs and CPs, STOP status 1118 | // sometime can be coded as 0x03 (especially for old cpu...) 1119 | Status.Value=S7.S7CpuStatusStop; 1120 | } 1121 | } 1122 | else 1123 | LastError = errS7FunctionError; 1124 | } 1125 | else 1126 | LastError = errS7InvalidPDU; 1127 | } 1128 | return LastError; 1129 | } 1130 | 1131 | public int SetSessionPassword(String Password) 1132 | { 1133 | byte[] pwd = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20}; 1134 | int Length; 1135 | 1136 | LastError = 0; 1137 | // Adjusts the Password length to 8 1138 | if (Password.length()>8) 1139 | Password=Password.substring(0, 8); 1140 | else 1141 | { 1142 | while (Password.length()<8) 1143 | Password=Password+" "; 1144 | } 1145 | 1146 | try { 1147 | pwd = Password.getBytes("UTF-8"); 1148 | } catch (UnsupportedEncodingException ex) { 1149 | LastError = errS7InvalidParams; 1150 | } 1151 | if (LastError==0) 1152 | { 1153 | // Encodes the password 1154 | pwd[0]=(byte) (pwd[0] ^ 0x55); 1155 | pwd[1]=(byte) (pwd[1] ^ 0x55); 1156 | for (int c = 2; c < 8; c++) 1157 | { 1158 | pwd[c]=(byte) (pwd[c] ^ 0x55 ^ pwd[c-2]); 1159 | } 1160 | System.arraycopy(pwd, 0, S7_SET_PWD, 29, 8); 1161 | // Sends the telegrem 1162 | SendPacket(S7_SET_PWD); 1163 | if (LastError==0) 1164 | { 1165 | Length=RecvIsoPacket(); 1166 | if (Length > 32) // the minimum expected 1167 | { 1168 | if (S7.GetWordAt(PDU,27)!=0) 1169 | LastError = errS7FunctionError; 1170 | } 1171 | else 1172 | LastError = errS7InvalidPDU; 1173 | } 1174 | } 1175 | return LastError; 1176 | } 1177 | 1178 | public int ClearSessionPassword() 1179 | { 1180 | int Length; 1181 | 1182 | LastError = 0; 1183 | SendPacket(S7_CLR_PWD); 1184 | if (LastError==0) 1185 | { 1186 | Length=RecvIsoPacket(); 1187 | if (Length > 30) // the minimum expected 1188 | { 1189 | if (S7.GetWordAt(PDU,27)!=0) 1190 | LastError = errS7FunctionError; 1191 | } 1192 | else 1193 | LastError = errS7InvalidPDU; 1194 | } 1195 | return LastError; 1196 | } 1197 | 1198 | public int GetProtection(S7Protection Protection) 1199 | { 1200 | S7Szl SZL = new S7Szl(256); 1201 | 1202 | LastError = ReadSZL(0x0232, 0x0004, SZL); 1203 | if (LastError==0) 1204 | { 1205 | Protection.Update(SZL.Data); 1206 | } 1207 | return LastError; 1208 | } 1209 | 1210 | } 1211 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7CpInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | public class S7CpInfo { 14 | 15 | public int MaxPduLength; 16 | public int MaxConnections; 17 | public int MaxMpiRate; 18 | public int MaxBusRate; 19 | 20 | protected void Update(byte[] Src, int Pos) 21 | { 22 | MaxPduLength = S7.GetShortAt(Src, 2); 23 | MaxConnections = S7.GetShortAt(Src, 4); 24 | MaxMpiRate = S7.GetDIntAt(Src, 6); 25 | MaxBusRate = S7.GetDIntAt(Src, 10); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7CpuInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | public class S7CpuInfo { 14 | 15 | private final int BufSize = 256; 16 | protected byte[] Buffer = new byte[BufSize]; 17 | 18 | protected void Update(byte[] Src, int Pos) 19 | { 20 | System.arraycopy(Src, Pos, Buffer, 0, BufSize); 21 | } 22 | 23 | public String ModuleTypeName() 24 | { 25 | return S7.GetStringAt(Buffer,172,32); 26 | } 27 | public String SerialNumber() 28 | { 29 | return S7.GetStringAt(Buffer,138,24); 30 | } 31 | public String ASName() 32 | { 33 | return S7.GetStringAt(Buffer,2,24); 34 | } 35 | public String Copyright() 36 | { 37 | return S7.GetStringAt(Buffer,104,26); 38 | } 39 | public String ModuleName() 40 | { 41 | return S7.GetStringAt(Buffer,36,24); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7OrderCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | public class S7OrderCode { 14 | 15 | public int V1; 16 | public int V2; 17 | public int V3; 18 | protected byte[] Buffer = new byte[1024]; 19 | 20 | protected void Update(byte[] Src, int Pos, int Size) 21 | { 22 | System.arraycopy(Src, Pos, Buffer, 0, Size); 23 | V1 = (byte) Src[Size-3]; 24 | V2 = (byte) Src[Size-2]; 25 | V3 = (byte) Src[Size-1]; 26 | } 27 | 28 | public String Code() 29 | { 30 | return S7.GetStringAt(Buffer, 2, 20); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7Protection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.sourceforge.snap7.moka7; 7 | 8 | /** 9 | * 10 | * @author Dave Nardella 11 | */ 12 | // See §33.19 of "System Software for S7-300/400 System and Standard Functions" 13 | public class S7Protection { 14 | public int sch_schal; 15 | public int sch_par; 16 | public int sch_rel; 17 | public int bart_sch; 18 | public int anl_sch; 19 | protected void Update(byte[] Src) 20 | { 21 | sch_schal = S7.GetWordAt(Src,2); 22 | sch_par = S7.GetWordAt(Src,4); 23 | sch_rel = S7.GetWordAt(Src,6); 24 | bart_sch = S7.GetWordAt(Src,8); 25 | anl_sch = S7.GetWordAt(Src,10); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/sourceforge/snap7/moka7/S7Szl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | package com.sourceforge.snap7.moka7; 8 | 9 | /** 10 | * 11 | * @author Dave Nardella 12 | */ 13 | public class S7Szl { 14 | 15 | public int LENTHDR; 16 | public int N_DR; 17 | public int DataSize; 18 | public byte Data[]; 19 | 20 | public S7Szl(int BufferSize) 21 | { 22 | Data = new byte[BufferSize]; 23 | } 24 | protected void Copy(byte[] Src, int SrcPos, int DestPos, int Size) 25 | { 26 | System.arraycopy(Src, SrcPos, Data, DestPos, Size); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/si/trina/moka7/live/PLC.java: -------------------------------------------------------------------------------- 1 | package si.trina.moka7.live; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Timer; 8 | import java.util.TimerTask; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.sourceforge.snap7.moka7.S7; 14 | import com.sourceforge.snap7.moka7.S7Client; 15 | 16 | public class PLC implements Runnable { 17 | 18 | final Logger logger = LoggerFactory.getLogger(PLC.class); 19 | 20 | public ArrayList listeners; 21 | public Object PLCSyncObj; 22 | 23 | private int plcToPcDb; 24 | private int pcToPlcDb; 25 | private int rack = 0; 26 | private int slot = 1; 27 | private int plcToPcAreaType = S7.S7AreaDB; // optionally read merker, eingang, ausgang area 28 | private int pcToPlcAreaType = S7.S7AreaDB; 29 | 30 | private long plcToPcLiveBit; 31 | private long pcToPlcLiveBit; 32 | private boolean plcToPcLiveBitState; 33 | 34 | public String PLCName; 35 | public String PLCIp; 36 | private S7Client moka; 37 | private Map boolBitChange; 38 | private double[] booleans; 39 | private Timer t = new Timer(); 40 | 41 | boolean firstConnect = true; 42 | 43 | private byte[] plcToPc,pcToPlc; 44 | private Object plcToPcLock,pcToPlcLock; 45 | 46 | public boolean connected = false; 47 | public boolean liveBitEnabled = false; 48 | public short liveBitAddress = 0; 49 | public short liveBitPosition = 0; 50 | public short liveBitPCDuration = 250; // in ms 51 | public short liveBitPLCDuration = 500; // in ms 52 | public int LastError = 0; 53 | 54 | public PLC(String name,String ip,byte[] plcToPc,byte[] pcToPlc,int plcToPcDb,int pcToPlcDb,double[] booleans) { 55 | this.plcToPc = plcToPc; 56 | this.pcToPlc = pcToPlc; 57 | this.PLCName = name; 58 | this.PLCIp = ip; 59 | this.moka = new S7Client(); 60 | this.moka.SetConnectionType(S7.OP); 61 | this.plcToPcDb = plcToPcDb; 62 | this.pcToPlcDb = pcToPlcDb; 63 | this.boolBitChange = new HashMap(); 64 | this.booleans = booleans; 65 | this.pcToPlcLock = new Object(); 66 | this.plcToPcLock = new Object(); 67 | this.PLCSyncObj = new Object(); 68 | this.listeners = new ArrayList(); 69 | } 70 | 71 | public PLC(String name, 72 | String ip, 73 | int plcToPcLength, 74 | int pcToPlcLength, 75 | int plcToPcDb, 76 | int pcToPlcDb, 77 | double[] booleans, 78 | int rack, 79 | int slot, 80 | int plcToPcAreaType, 81 | int pcToPlcAreaType) { 82 | this.plcToPc = new byte[plcToPcLength]; 83 | this.pcToPlc = new byte[pcToPlcLength]; 84 | this.PLCName = name; 85 | this.PLCIp = ip; 86 | this.moka = new S7Client(); 87 | this.moka.SetConnectionType(S7.OP); 88 | this.plcToPcDb = plcToPcDb; 89 | this.pcToPlcDb = pcToPlcDb; 90 | this.boolBitChange = new HashMap(); 91 | this.booleans = booleans; 92 | this.pcToPlcLock = new Object(); 93 | this.plcToPcLock = new Object(); 94 | this.PLCSyncObj = new Object(); 95 | this.listeners = new ArrayList(); 96 | this.rack = rack; 97 | this.slot = slot; 98 | this.pcToPlcAreaType = pcToPlcAreaType; 99 | this.plcToPcAreaType = plcToPcAreaType; 100 | } 101 | 102 | public void processPLCEvents() { 103 | this.getBoolChange(); 104 | } 105 | 106 | private void getBoolChange() { 107 | for (double b : this.booleans) { 108 | String s = ""+b; 109 | String[] nums = s.split("\\."); 110 | try { 111 | boolean state = this.getBool(true, Integer.parseInt(nums[0]), Integer.parseInt(nums[1])); 112 | boolean prevState; 113 | try { 114 | prevState = this.boolBitChange.get(b); 115 | } catch (NullPointerException e) { 116 | prevState = state; 117 | } 118 | if (prevState == false && state == true) { 119 | // Bit changed - signalize 120 | for(PLCListener m: this.listeners) { 121 | synchronized (this.PLCSyncObj) { 122 | m.PLCBitChanged(Integer.parseInt(nums[0]), Integer.parseInt(nums[1]), state, this.PLCName); 123 | } 124 | } 125 | } else if (prevState == true && state == false) { 126 | // Bit changed - signalize 127 | for(PLCListener m: this.listeners) { 128 | synchronized (this.PLCSyncObj) { 129 | m.PLCBitChanged(Integer.parseInt(nums[0]), Integer.parseInt(nums[1]), state, this.PLCName); 130 | } 131 | } 132 | } 133 | } catch (Exception e) { 134 | System.out.println(b); 135 | e.printStackTrace(); 136 | } 137 | } 138 | this.saveBoolStatus(); 139 | } 140 | 141 | private void saveBoolStatus() { 142 | for (double b : this.booleans) { 143 | String s = ""+b; 144 | String[] nums = s.split("\\."); 145 | try { 146 | this.boolBitChange.put(b,this.getBool(true, Integer.parseInt(nums[0]), Integer.parseInt(nums[1]))); 147 | } catch (Exception e) { 148 | } 149 | } 150 | } 151 | 152 | public PLCStatus getStatus() { 153 | PLCStatus st = new PLCStatus(); 154 | st.name = this.PLCName; 155 | st.ip = this.PLCIp; 156 | synchronized (this.plcToPcLock) { 157 | st.dataFromPLC = org.apache.commons.codec.binary.Base64.encodeBase64String(this.plcToPc); 158 | } 159 | synchronized (this.pcToPlcLock) { 160 | st.dataToPLC = org.apache.commons.codec.binary.Base64.encodeBase64String(this.pcToPlc); 161 | } 162 | return st; 163 | } 164 | 165 | public void refreshPLCStatus() { 166 | // Update incoming 167 | synchronized (this.plcToPcLock) { 168 | this.moka.ReadArea(this.plcToPcAreaType, this.plcToPcDb, 0, this.plcToPc.length, this.plcToPc); 169 | } 170 | synchronized (this.pcToPlcLock) { 171 | this.moka.WriteArea(this.pcToPlcAreaType, this.pcToPlcDb, 0, this.pcToPlc.length, this.pcToPlc); 172 | } 173 | this.processPLCEvents(); 174 | } 175 | 176 | public void inverseBit(boolean fromPLC,int address,int pos) throws Exception { 177 | byte[] source; 178 | if (fromPLC) { 179 | synchronized (this.plcToPcLock) { 180 | source = this.plcToPc; 181 | if (address >= source.length || pos > 7) { 182 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 183 | } else { 184 | if((((byte)source[address]) & (0x01 << pos)) > 0) { 185 | source[address] &= ~(1 << pos); 186 | } else { 187 | source[address] |= 1 << pos; 188 | } 189 | } 190 | } 191 | } else { 192 | synchronized (this.pcToPlcLock) { 193 | source = this.pcToPlc; 194 | if (address >= source.length || pos > 7) { 195 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 196 | } else { 197 | if((((byte)source[address]) & (0x01 << pos)) > 0) { 198 | source[address] &= ~(1 << pos); 199 | } else { 200 | source[address] |= 1 << pos; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | public boolean putBool(boolean fromPLC,int address,int pos,boolean val) { 208 | byte[] source; 209 | if (fromPLC) { 210 | synchronized (this.plcToPcLock) { 211 | source = this.plcToPc; 212 | if (address >= source.length || pos > 7) { 213 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 214 | return false; 215 | } else { 216 | if (val == true) { 217 | source[address] |= 1 << pos; 218 | } else { 219 | source[address] &= ~(1 << pos); 220 | } 221 | } 222 | } 223 | } else { 224 | synchronized (this.pcToPlcLock) { 225 | source = this.pcToPlc; 226 | if (address >= source.length || pos > 7) { 227 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 228 | return false; 229 | } else { 230 | if (val == true) { 231 | source[address] |= 1 << pos; 232 | } else { 233 | source[address] &= ~(1 << pos); 234 | } 235 | } 236 | } 237 | } 238 | return true; 239 | } 240 | 241 | public void signalBoolean(boolean fromPLC,int address,int pos,boolean val) { 242 | try { 243 | this.putBool(fromPLC, address, pos, val); 244 | TimerTask tt = new TimerTask() { 245 | 246 | @Override 247 | public void run() { 248 | // TODO Auto-generated method stub 249 | try { 250 | putBool(fromPLC, address, pos, false); 251 | } catch (Exception e) { 252 | // TODO Auto-generated catch block 253 | e.printStackTrace(); 254 | } 255 | } 256 | }; 257 | t.schedule(tt, 300); 258 | 259 | } catch (Exception e) { 260 | // TODO Auto-generated catch block 261 | e.printStackTrace(); 262 | } 263 | } 264 | 265 | public void putIntDecimal(boolean fromPLC,int address,double val) throws Exception { 266 | this.putInt(fromPLC, address, (short) (val*100)); 267 | } 268 | 269 | public boolean putInt(boolean fromPLC,int address,short val) { 270 | ByteBuffer b = ByteBuffer.allocate(2); 271 | b.putShort(val); 272 | byte[] spl = b.array(); 273 | byte[] source; 274 | if (fromPLC) { 275 | synchronized (this.plcToPcLock) { 276 | source = this.plcToPc; 277 | if (address >= source.length-1) { 278 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 279 | return false; 280 | } else { 281 | source[address] = spl[0]; 282 | source[address+1] = spl[1]; 283 | } 284 | } 285 | } else { 286 | synchronized (this.pcToPlcLock) { 287 | source = this.pcToPlc; 288 | if (address >= source.length-1) { 289 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 290 | return false; 291 | } else { 292 | source[address] = spl[0]; 293 | source[address+1] = spl[1]; 294 | } 295 | } 296 | } 297 | return true; 298 | } 299 | 300 | public boolean putIntToByte(boolean fromPLC,int address,short val) { 301 | byte[] source; 302 | if (val > 255 || val < 0) { 303 | System.out.println("Value out of boundaries"); 304 | return false; 305 | } 306 | if (fromPLC) { 307 | synchronized (this.plcToPcLock) { 308 | source = this.plcToPc; 309 | if (address >= source.length) { 310 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 311 | return false; 312 | } else { 313 | source[address] = (byte)val; 314 | } 315 | } 316 | } else { 317 | synchronized (this.pcToPlcLock) { 318 | source = this.pcToPlc; 319 | if (address >= source.length) { 320 | System.out.println("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 321 | return false; 322 | } else { 323 | source[address] = (byte)val; 324 | } 325 | } 326 | } 327 | return true; 328 | } 329 | 330 | public void putDIntDecimal(boolean fromPLC,int address, double val) throws Exception { 331 | this.putDInt(fromPLC, address, (int)val*100); 332 | } 333 | 334 | public void putDInt(boolean fromPLC,int address,int val) throws Exception { 335 | ByteBuffer b = ByteBuffer.allocate(4); 336 | b.putInt(val); 337 | byte[] spl = b.array(); 338 | byte[] source; 339 | if (fromPLC) { 340 | synchronized (this.plcToPcLock) { 341 | source = this.plcToPc; 342 | if (address >= source.length-3) { 343 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 344 | } else { 345 | source[address] = spl[0]; 346 | source[address+1] = spl[1]; 347 | source[address+2] = spl[2]; 348 | source[address+3] = spl[3]; 349 | } 350 | } 351 | } else { 352 | synchronized (this.plcToPcLock) { 353 | source = this.pcToPlc; 354 | if (address >= source.length-3) { 355 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 356 | } else { 357 | source[address] = spl[0]; 358 | source[address+1] = spl[1]; 359 | source[address+2] = spl[2]; 360 | source[address+3] = spl[3]; 361 | } 362 | } 363 | } 364 | } 365 | 366 | public boolean getBool(boolean fromPLC,int address,int pos) throws Exception { 367 | byte[] source; 368 | if (fromPLC) { 369 | synchronized (this.plcToPcLock) { 370 | source = this.plcToPc; 371 | if (address >= source.length || pos > 7) { 372 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 373 | } 374 | int q = ((byte)source[address]) & (0x01 << pos) ; 375 | if (q == 0) { 376 | return false; 377 | } else { 378 | return true; 379 | } 380 | } 381 | } else { 382 | synchronized (this.pcToPlcLock) { 383 | source = this.pcToPlc; 384 | if (address >= source.length || pos > 7) { 385 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 386 | } 387 | int q = ((byte)source[address]) & (0x01 << pos) ; 388 | if (q == 0) { 389 | return false; 390 | } else { 391 | return true; 392 | } 393 | } 394 | } 395 | } 396 | 397 | public int getInt(boolean fromPLC,int address) throws Exception { 398 | byte[] source; 399 | if (fromPLC) { 400 | synchronized (this.plcToPcLock) { 401 | source = this.plcToPc; 402 | if (address >= source.length-1) { 403 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 404 | } 405 | return ((source[address] & 0xff) << 8) | (source[address+1] & 0xff); 406 | } 407 | } else { 408 | synchronized (this.pcToPlcLock) { 409 | source = this.pcToPlc; 410 | if (address >= source.length-1) { 411 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 412 | } 413 | return ((source[address] & 0xff) << 8) | (source[address+1] & 0xff); 414 | } 415 | } 416 | } 417 | 418 | public int getIntFromByte(boolean fromPLC,int address) throws Exception { 419 | byte[] source; 420 | if (fromPLC) { 421 | synchronized (this.plcToPcLock) { 422 | source = this.plcToPc; 423 | if (address >= source.length) { 424 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 425 | } 426 | Byte b = new Byte(source[address]); 427 | return b.intValue(); 428 | } 429 | } else { 430 | synchronized (this.pcToPlcLock) { 431 | source = this.pcToPlc; 432 | if (address >= source.length) { 433 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 434 | } 435 | Byte b = new Byte(source[address]); 436 | return b.intValue(); 437 | } 438 | } 439 | } 440 | 441 | public int getDInt(boolean fromPLC,int address) throws Exception { 442 | byte[] source; 443 | if (fromPLC) { 444 | synchronized (this.plcToPcLock) { 445 | source = this.plcToPc; 446 | if (address >= source.length-3) { 447 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 448 | } 449 | ByteBuffer b = ByteBuffer.allocate(4); 450 | b.put(source, address, 4); 451 | b.rewind(); 452 | return b.getInt(); 453 | } 454 | } else { 455 | synchronized (this.pcToPlcLock) { 456 | source = this.pcToPlc; 457 | if (address >= source.length-3) { 458 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 459 | } 460 | ByteBuffer b = ByteBuffer.allocate(4); 461 | b.put(source, address, 4); 462 | b.rewind(); 463 | return b.getInt(); 464 | } 465 | } 466 | } 467 | 468 | public String getString(boolean fromPLC, int address, int len) throws Exception { 469 | byte[] StrBuffer = new byte[len]; 470 | byte[] source; 471 | 472 | if (fromPLC) { 473 | synchronized (this.plcToPcLock) { 474 | source = this.plcToPc; 475 | if (address >= source.length-3) { 476 | throw new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.plcToPcDb + " at address " + address); 477 | } 478 | System.arraycopy(source, address, StrBuffer, 0, len); 479 | return S7.GetStringAt(StrBuffer, address, len); 480 | } 481 | } else { 482 | synchronized (this.pcToPlcLock) { 483 | source = this.pcToPlc; 484 | if (address >= source.length-3) { 485 | new Exception("PLC out of boundaries: " + this.PLCName + " in DB " + this.pcToPlcDb + " at address " + address); 486 | } 487 | System.arraycopy(source, address, StrBuffer, 0, len); 488 | return S7.GetStringAt(StrBuffer, address, len); 489 | } 490 | } 491 | } 492 | 493 | public void checkSetLiveBit() { 494 | try { 495 | if ((System.nanoTime() - this.pcToPlcLiveBit) > this.liveBitPCDuration * 1000000) { 496 | this.inverseBit(false, this.liveBitAddress, this.liveBitPosition); 497 | this.pcToPlcLiveBit = System.nanoTime(); 498 | } 499 | if (this.plcToPcLiveBitState != this.getBool(true, this.liveBitAddress, this.liveBitPosition)) { 500 | // Live bit changed - reset the timer 501 | this.plcToPcLiveBitState = this.getBool(true, this.liveBitAddress, this.liveBitPosition); 502 | this.plcToPcLiveBit = System.nanoTime(); 503 | } 504 | if ((System.nanoTime() - this.plcToPcLiveBit) > this.liveBitPLCDuration * 1000000) { 505 | System.out.println(this.getBool(true, this.liveBitAddress, this.liveBitPosition)); 506 | System.out.println(System.nanoTime() - this.plcToPcLiveBit); 507 | this.moka.Disconnect(); 508 | this.moka.Connected = false; 509 | logger.error("No live bit from PLC: " + this.PLCName); 510 | } 511 | } catch (Exception e) { 512 | e.printStackTrace(); 513 | } 514 | } 515 | 516 | @Override 517 | public void run() { 518 | while(true) { 519 | if (this.moka.Connected == false) { 520 | this.connected = false; 521 | int error = this.moka.ConnectTo(this.PLCIp, this.rack, this.slot); 522 | if (error > 0) { 523 | logger.error(S7Client.ErrorText(error)); 524 | } 525 | } else { 526 | this.connected = true; 527 | this.LastError = this.moka.LastError; 528 | 529 | if (this.firstConnect == true) { 530 | logger.info("Connected to PLC " + this.PLCName); 531 | // read current db state, so we don't override it with zeroes 532 | this.moka.ReadArea(this.pcToPlcAreaType, this.pcToPlcDb, 0, this.pcToPlc.length, this.pcToPlc); 533 | this.firstConnect = false; 534 | if (this.liveBitEnabled) { 535 | this.pcToPlcLiveBit = System.nanoTime(); 536 | this.plcToPcLiveBit = System.nanoTime(); 537 | try { 538 | this.plcToPcLiveBitState = this.getBool(true, this.liveBitAddress, this.liveBitPosition); 539 | } catch (Exception e) { 540 | logger.error(e.getMessage()); 541 | } 542 | } 543 | } 544 | 545 | this.refreshPLCStatus(); 546 | if (this.liveBitEnabled) { 547 | this.checkSetLiveBit(); 548 | } 549 | } 550 | try { 551 | Thread.sleep(20); 552 | } catch (InterruptedException e) { 553 | logger.error(e.getMessage()); 554 | } 555 | } 556 | } 557 | 558 | } 559 | -------------------------------------------------------------------------------- /src/si/trina/moka7/live/PLCListener.java: -------------------------------------------------------------------------------- 1 | package si.trina.moka7.live; 2 | 3 | public interface PLCListener { 4 | abstract public void PLCBitChanged(int address, int pos, boolean val, String plcName); 5 | } 6 | -------------------------------------------------------------------------------- /src/si/trina/moka7/live/PLCStatus.java: -------------------------------------------------------------------------------- 1 | package si.trina.moka7.live; 2 | 3 | public class PLCStatus { 4 | public String name; 5 | public String ip; 6 | public String dataToPLC; 7 | public String dataFromPLC; 8 | } 9 | --------------------------------------------------------------------------------