├── .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 | [](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 |
--------------------------------------------------------------------------------