├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md ├── build └── omron-fins.jar ├── example └── com │ └── delmesoft │ └── cp1l │ └── fins │ └── Cp1lFinsExample.java └── src └── com └── delmesoft └── cp1l └── fins ├── CIO.java ├── ControllerData.java ├── Cp1lFins.java ├── Cp1lFinsImpl.java ├── Hashcode.java └── InputListener.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | omron-fins 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.release=disabled 12 | org.eclipse.jdt.core.compiler.source=1.8 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sergio Soriano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omron-fins 2 | Java implementation of Omron FINS communication protocol 3 | 4 | # Documentation 5 | 6 | ## `public interface Cp1lFins` 7 | 8 | ## `void connect() throws Exception` 9 | 10 | Establishes connection with the device 11 | 12 | * **Exceptions:** `Exception` — Error opening connection 13 | 14 | ## `boolean isConnected()` 15 | 16 | Connection status 17 | 18 | * **Returns:** true connected 19 | 20 | ## `void disconnect()` 21 | 22 | Close current connection 23 | 24 | ## `ControllerData getControllerData() throws Exception` 25 | 26 | Controller information data 27 | 28 | * **Returns:** ControllerData 29 | * **Exceptions:** `Exception` — Error getting ControllerData 30 | 31 | ## `boolean read(int position, int bit) throws Exception` 32 | 33 | Read the state of the indicated bit 34 | 35 | * **Parameters:** 36 | * `position` — Memory position 37 | * `bit` — Bit position 38 | * **Returns:** bit status 39 | * **Exceptions:** `Exception` — Error reading 40 | 41 | ## `boolean read(CIO cio) throws Exception` 42 | 43 | Read the state of the indicated bit 44 | 45 | * **Parameters:** `cio` — Channel Input Output 46 | * **Returns:** bit status 47 | * **Exceptions:** `Exception` — Error reading 48 | 49 | ## `void write(int position, int bit, boolean value) throws Exception` 50 | 51 | Write the status of the indicated input 52 | 53 | * **Parameters:** 54 | * `position` — Memory position 55 | * `bit` — Bit position 56 | * `value` — New state 57 | * **Exceptions:** `Exception` — Error writing 58 | 59 | ## `void write(CIO cio, boolean value) throws Exception` 60 | 61 | Write the status of the indicated input 62 | 63 | * **Parameters:** 64 | * `cio` — Channel Input Output 65 | * `value` — new state 66 | * **Exceptions:** `Exception` — Exception Error writing 67 | 68 | ## `void setInputListener(InputListener listener)` 69 | 70 | * **Parameters:** `listener` — Interface to notify subscription events 71 | 72 | ## `InputListener getInputListener()` 73 | 74 | * **Returns:** Interface where subscription events are notified 75 | 76 | ## `void subscribe(CIO cio)` 77 | 78 | Adds a subscription to the state change events of the indicated bit 79 | 80 | * **Parameters:** `cio` — Channel Input Output 81 | 82 | ## `boolean unsuscribe(CIO cio)` 83 | 84 | Remove bit subscription 85 | 86 | * **Parameters:** `cio` — Channel Input Output 87 | * **Returns:** true if there was a subscription 88 | 89 | ## `void subscribe(int position, int bit)` 90 | 91 | Adds a subscription to the state change events of the indicated bit 92 | 93 | * **Parameters:** 94 | * `position` — Memory position 95 | * `bit` — Bit position 96 | 97 | ## `void unsuscribe(int position, int bit)` 98 | 99 | Remove bit subscription 100 | 101 | * **Parameters:** 102 | * `position` — Memory position 103 | * `bit` — Bit position 104 | 105 | ## `void unsuscribeAll()` 106 | 107 | Remove all bit subscriptions 108 | 109 | 2021 Sergio Soriano - www.sergiosoriano.com 110 | -------------------------------------------------------------------------------- /build/omron-fins.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/omron-fins/1b5ae2017ac0d455803d8a1a44f93f6d64e454fa/build/omron-fins.jar -------------------------------------------------------------------------------- /example/com/delmesoft/cp1l/fins/Cp1lFinsExample.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | public class Cp1lFinsExample { 4 | 5 | public static void main(String[] args) throws Exception { 6 | 7 | String host = "169.254.0.10"; 8 | int port = 9600; 9 | 10 | Cp1lFins cp1lFins = new Cp1lFinsImpl(host, port); 11 | cp1lFins.setInputListener(new InputListener() { 12 | @Override 13 | public void onEvent(int ch, int pin, boolean state) { 14 | System.out.printf("onEvent: ch=%d, pin=%d, state=%b\n", ch, pin, state); 15 | } 16 | 17 | @Override 18 | public void onError(Exception e) { 19 | e.printStackTrace(); 20 | } 21 | }); 22 | 23 | // Subscribe to input 24 | cp1lFins.subscribe(new CIO(0, 0)); // channel 0 pin 0 (0.00) 25 | cp1lFins.subscribe(new CIO(1, 0)); // channel 1 pin 0 (1.00) 26 | 27 | cp1lFins.subscribe(new CIO(102, 02)); // Custom 28 | 29 | cp1lFins.connect(); 30 | System.out.printf("Connected (host: '%s', port: %d)\n", host, port); 31 | System.out.println(cp1lFins.getControllerData()); 32 | 33 | // Write Output 34 | cp1lFins.write(101, 00, true); // channel 100 pin 0 35 | 36 | boolean state = false; 37 | for(int i = 0; i < 10; ++i) { // blink 38 | Thread.sleep(500); 39 | state = !state; 40 | cp1lFins.write(100, 01, state); // channel 100 pin 1 41 | } 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/CIO.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | /** 4 | * 2021 Sergio S. - sergi.ss4@gmail.com 5 | * @author Sergio S. 6 | * 7 | */ 8 | public class CIO { // Channel Input Output 9 | 10 | private int position; 11 | private int bit; 12 | 13 | transient boolean state; 14 | 15 | public CIO() {} 16 | 17 | public CIO(int position, int bit) { 18 | this.position = position; 19 | this.bit = bit; 20 | } 21 | 22 | public int getPosition() { 23 | return position; 24 | } 25 | 26 | public void setPosition(int position) { 27 | this.position = position; 28 | } 29 | 30 | public int getBit() { 31 | return bit; 32 | } 33 | 34 | public void setBit(int bit) { 35 | this.bit = bit; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | final int prime = 31; 41 | int result = 1; 42 | result = prime * result + bit; 43 | result = prime * result + position; 44 | return result; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object obj) { 49 | if (this == obj) 50 | return true; 51 | if (obj == null) 52 | return false; 53 | if (getClass() != obj.getClass()) 54 | return false; 55 | CIO other = (CIO) obj; 56 | if (bit != other.bit) 57 | return false; 58 | if (position != other.position) 59 | return false; 60 | return true; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | StringBuilder builder = new StringBuilder(); 66 | builder.append("CIO [position="); 67 | builder.append(position); 68 | builder.append(", bit="); 69 | builder.append(bit); 70 | builder.append("]"); 71 | return builder.toString(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/ControllerData.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | /** 4 | * 2021 Sergio S. - sergi.ss4@gmail.com 5 | * @author Sergio 6 | * 7 | */ 8 | public class ControllerData { 9 | 10 | private String model; 11 | 12 | private String version; 13 | 14 | public ControllerData() {} 15 | 16 | public ControllerData(String model, String version) { 17 | this.model = model; 18 | this.version = version; 19 | } 20 | 21 | public String getModel() { 22 | return model; 23 | } 24 | 25 | public void setModel(String model) { 26 | this.model = model; 27 | } 28 | 29 | public String getVersion() { 30 | return version; 31 | } 32 | 33 | public void setVersion(String version) { 34 | this.version = version; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | StringBuilder builder = new StringBuilder(); 40 | builder.append("ControllerData [model="); 41 | builder.append(model); 42 | builder.append(", version="); 43 | builder.append(version); 44 | builder.append("]"); 45 | return builder.toString(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/Cp1lFins.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | /** 4 | * 2021 Sergio S. - sergi.ss4@gmail.com 5 | * @author Sergio S. 6 | * 7 | */ 8 | public interface Cp1lFins { 9 | 10 | /** 11 | * Establishes connection with the device 12 | * @throws Exception Error opening connection 13 | */ 14 | void connect() throws Exception; 15 | 16 | /** 17 | * Connection status 18 | * @return true connected, false disconnected 19 | */ 20 | boolean isConnected(); 21 | 22 | /** 23 | * Close current connection 24 | */ 25 | void disconnect(); 26 | 27 | /** 28 | * Controller information data 29 | * @return ControllerData 30 | * @throws Exception Error getting ControllerData 31 | */ 32 | ControllerData getControllerData() throws Exception; 33 | 34 | /** 35 | * Read the state of the indicated bit 36 | * @param position Memory position 37 | * @param bit Bit position 38 | * @return bit status 39 | * @throws Exception Error reading 40 | */ 41 | boolean read(int position, int bit) throws Exception; 42 | 43 | /** 44 | * Read the state of the indicated bit 45 | * @param cio Channel Input Output 46 | * @return bit status 47 | * @throws Exception Error reading 48 | */ 49 | boolean read(CIO cio) throws Exception; 50 | 51 | /** 52 | * Write the status of the indicated input 53 | * @param position Memory position 54 | * @param bit Bit position 55 | * @param value New state 56 | * @throws Exception Error writing 57 | */ 58 | void write(int position, int bit, boolean value) throws Exception; 59 | 60 | /** 61 | * Write the status of the indicated input 62 | * @param cio Channel Input Output 63 | * @param value 64 | * @throws Exception Exception Error writing 65 | */ 66 | void write(CIO cio, boolean value) throws Exception; 67 | 68 | /** 69 | * 70 | * @param listener 71 | */ 72 | void setInputListener(InputListener listener); 73 | 74 | /** 75 | * 76 | * @return 77 | */ 78 | InputListener getInputListener(); 79 | 80 | /** 81 | * Adds a subscription to the state change events of the indicated bit 82 | * @param cio Channel Input Output 83 | */ 84 | void subscribe(CIO cio); 85 | 86 | /** 87 | * Remove bit subscription 88 | * @param cio Channel Input Output 89 | * @return true if there was a subscription 90 | */ 91 | boolean unsuscribe(CIO cio); 92 | 93 | /** 94 | * Adds a subscription to the state change events of the indicated bit 95 | * @param position 96 | * @param bit 97 | */ 98 | void subscribe(int position, int bit); 99 | 100 | /** 101 | * Remove bit subscription 102 | * @param position 103 | * @param bit 104 | */ 105 | void unsuscribe(int position, int bit); 106 | 107 | /** 108 | * Remove all bit subscriptions 109 | */ 110 | void unsuscribeAll(); 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/Cp1lFinsImpl.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | import java.io.EOFException; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.Socket; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * 2021 Sergio S. - sergi.ss4@gmail.com 13 | * @author Sergio S. 14 | * 15 | */ 16 | public class Cp1lFinsImpl implements Cp1lFins { 17 | 18 | private static final int TIMEOUT = 10_000; 19 | 20 | public static final byte READ_ID = 2; 21 | public static final byte WRITE_ID = 4; 22 | 23 | private byte[] tmp = new byte[1024]; 24 | 25 | private String host; 26 | private int port; 27 | 28 | private Socket socket; 29 | private InputStream is; 30 | private OutputStream os; 31 | 32 | private Thread thread; 33 | private InputListener listener; 34 | 35 | private Map cioMap = new HashMap<>(); 36 | 37 | private long refreshTime; 38 | 39 | public Cp1lFinsImpl() { 40 | this("169.254.0.1"); 41 | } 42 | 43 | public Cp1lFinsImpl(String host) { 44 | this(host, 9600); 45 | } 46 | 47 | public Cp1lFinsImpl(String host, int port) { 48 | this.host = host; 49 | this.port = port; 50 | 51 | this.refreshTime = 25; // Default time 52 | } 53 | 54 | @Override 55 | public synchronized void connect() throws Exception { 56 | 57 | if(!isConnected()) { 58 | socket = new Socket(host, port); 59 | socket.setSoTimeout(TIMEOUT); 60 | is = socket.getInputStream(); 61 | os = socket.getOutputStream(); 62 | 63 | byte[] message = { 64 | 0x46, 0x49, 0x4E, 0x53, // Header 65 | 0x00, 0x00, 0x00, 0x0C, // CMD length 66 | 0x00, 0x00, 0x00, 0x00, // Frame command 67 | 0x00, 0x00, 0x00, 0x00, // Error 68 | 0x00, 0x00, 0x00, 0x00 69 | }; 70 | 71 | os.write(message, 0, 20); 72 | os.flush(); 73 | 74 | int n = readResponse(tmp); 75 | if(n != 24 || tmp[23] != 1) { // check 76 | disconnect(); 77 | throw new RuntimeException("connection message error"); 78 | } 79 | thread = new Thread() { 80 | public void run() { 81 | 82 | try { 83 | 84 | boolean value; 85 | // Initial state 86 | synchronized (cioMap) { 87 | for (CIO cio : cioMap.values()) { // iterate 88 | value = read(cio.getPosition(), cio.getBit()); // read state 89 | cio.state = value; // update state 90 | } 91 | } 92 | 93 | while (!isInterrupted()) { 94 | Thread.sleep(refreshTime); 95 | // Update state 96 | synchronized (cioMap) { 97 | for (CIO cio : cioMap.values()) { // iterate 98 | value = read(cio.getPosition(), cio.getBit()); // read state 99 | if (value != cio.state) { // check 100 | cio.state = value; // update state 101 | if (listener != null) listener.onEvent(cio.getPosition(), cio.getBit(), value); // notify 102 | } 103 | } 104 | } 105 | } 106 | 107 | } catch (InterruptedException e) { 108 | // Ignore 109 | } catch (Exception e) { 110 | if(listener != null && isConnected()) { 111 | listener.onError(e); 112 | } 113 | } finally { 114 | disconnect(); 115 | } 116 | } 117 | }; 118 | thread.start(); 119 | } 120 | 121 | } 122 | 123 | @Override 124 | public synchronized boolean isConnected() { 125 | return socket != null; 126 | } 127 | 128 | @Override 129 | public synchronized void disconnect() { 130 | if(isConnected()) { 131 | try { 132 | thread.interrupt(); 133 | } catch (Exception e) { 134 | } finally { 135 | thread = null; 136 | } 137 | try { 138 | socket.close(); 139 | } catch (Exception e) { 140 | } finally { 141 | socket = null; 142 | is = null; 143 | os = null; 144 | } 145 | } 146 | } 147 | 148 | @Override 149 | public synchronized ControllerData getControllerData() throws Exception { 150 | 151 | byte[] message = { 152 | 0x46, 0x49, 0x4E, 0x53, // Header 153 | 0x00, 0x00, 0x00, 0x15, // CMD length 154 | 0x00, 0x00, 0x00, 0x02, // Frame command 155 | 0x00, 0x00, 0x00, 0x00, // Error 156 | /* 0 */ (byte) 0x80, // ICF - Display frame information - 157 | /* 1 */ 0x00, // RSV - Reserved by system - 158 | /* 2 */ 0x02, // GCT - Permissible number of gateways - 159 | /* 3 */ 0x00, // DNA - Destination network address - 160 | /* 4 */ 0x01, // DA1 - Ethernet Unit FINS NODE NUMBER - 161 | /* 5 */ 0x00, // DA2 - Destination unit address - 162 | /* 6 */ 0x00, // SNA - Source network address - 163 | /* 7 */ 0x00, // SA1 - WS FINS NODE NUMBER - 164 | /* 8 */ 0x00, // SA2 - Source unit address - 165 | /* 9 */ READ_ID, // SID - Service ID - 166 | /* ------- FINS COMMAND -------------- */ 167 | /* 10 */ 0x05, // MRC - Main Request code - 168 | /* 11 */ 0x01, // SRC - Sub-request code - 169 | /* ------- SEND DATA ----------------- */ 170 | /* 12 */ 0x00, // VARIABLE TYPE: DM 171 | }; 172 | 173 | os.write(message, 0, 29); 174 | os.flush(); 175 | 176 | readResponse(tmp); 177 | if(tmp[25] != READ_ID || tmp[23] != 1) { // check 178 | throw new RuntimeException("read message error"); 179 | } 180 | 181 | String model = new String(tmp, 30, 20).trim(); 182 | String version = new String(tmp, 50, 10).trim(); 183 | // String v2 = new String(tmp, 60, 10).trim(); 184 | return new ControllerData(model, version); 185 | } 186 | 187 | @Override 188 | public boolean read(CIO cio) throws Exception { 189 | return read(cio.getPosition(), cio.getBit()); 190 | } 191 | 192 | @Override 193 | public synchronized boolean read(int pos, int bit) throws Exception { 194 | 195 | byte[] message = { 196 | 0x46, 0x49, 0x4E, 0x53, // Header 197 | 0x00, 0x00, 0x00, 0x1A, // CMD length 198 | 0x00, 0x00, 0x00, 0x02, // Frame command 199 | 0x00, 0x00, 0x00, 0x00, // Error 200 | /* 0 */ (byte) 0x80, // ICF - Display frame information - 201 | /* 1 */ 0x00, // RSV - Reserved by system - 202 | /* 2 */ 0x02, // GCT - Permissible number of gateways - 203 | /* 3 */ 0x00, // DNA - Destination network address - 204 | /* 4 */ 0x01, // DA1 - Ethernet Unit FINS NODE NUMBER - 205 | /* 5 */ 0x00, // DA2 - Destination unit address - 206 | /* 6 */ 0x00, // SNA - Source network address - 207 | /* 7 */ 0x00, // SA1 - WS FINS NODE NUMBER - 208 | /* 8 */ 0x00, // SA2 - Source unit address - 209 | /* 9 */ READ_ID, // SID - Service ID - 210 | /* ------- FINS COMMAND -------------- */ 211 | /* 10 */ 0x01, // MRC - Main Request code - 212 | /* 11 */ 0x01, // SRC - Sub-request code - (READ) 213 | /* ------- SEND DATA ----------------- */ 214 | /* 12 */ 0x30, // VARIABLE TYPE: DM 215 | /* 13 */ 0x00, 216 | /* 14 */ (byte) pos, // READ START ADDRESS 217 | /* 15 */ (byte) bit, 218 | /* 16 */ 0x00, // WORDS READ 219 | /* 17 */ 0x01 220 | }; 221 | 222 | os.write(message, 0, 34); 223 | os.flush(); 224 | 225 | int n = readResponse(tmp); 226 | if(n != 31 || tmp[25] != READ_ID || tmp[23] != 1) { // check 227 | throw new RuntimeException("read message error"); 228 | } 229 | return tmp[30] == 1; 230 | } 231 | 232 | @Override 233 | public void write(CIO cio, boolean value) throws Exception { 234 | write(cio.getPosition(), cio.getBit(), value); 235 | } 236 | 237 | @Override 238 | public synchronized void write(int pos, int bit, boolean value) throws Exception { 239 | 240 | byte[] message = { 241 | 0x46, 0x49, 0x4E, 0x53, // Header 242 | 0x00, 0x00, 0x00, 0x1B, // CMD length 243 | 0x00, 0x00, 0x00, 0x02, // Frame command 244 | 0x00, 0x00, 0x00, 0x00, // Error 245 | /* 0 */ (byte) 0x80, // ICF - Display frame information - 246 | /* 1 */ 0x00, // RSV - Reserved by system - 247 | /* 2 */ 0x02, // GCT - Permissible number of gateways - 248 | /* 3 */ 0x00, // DNA - Destination network address - 249 | /* 4 */ 0x01, // DA1 - Ethernet Unit FINS NODE NUMBER - 250 | /* 5 */ 0x00, // DA2 - Destination unit address - 251 | /* 6 */ 0x00, // SNA - Source network address - 252 | /* 7 */ 0x00, // SA1 - WS FINS NODE NUMBER - 253 | /* 8 */ 0x00, // SA2 - Source unit address - 254 | /* 9 */ WRITE_ID, // SID - Service ID - 255 | /* ------- FINS COMMAND -------------- */ 256 | /* 10 */ 0x01, // MRC - Main Request code - 257 | /* 11 */ 0x02, // SRC - Sub-request code - (WRITE) 258 | /* ------- SEND DATA ----------------- */ 259 | /* 12 */ 0x30, // VARIABLE TYPE: DM 260 | /* 13 */ 0x00, 261 | /* 14 */ (byte) pos, // WRITE START ADDRESS 262 | /* 15 */ (byte) bit, 263 | /* 16 */ 0x00, // WORDS WRITE 264 | /* 17 */ 0x01, 265 | /* 18 */ (byte) (value ? 0x01 : 0x00) // WRITE VALUE 266 | }; 267 | 268 | os.write(message, 0, 35); 269 | os.flush(); 270 | 271 | int n = readResponse(tmp); 272 | if(n != 30 || tmp[25] != WRITE_ID || tmp[23] != 1) { // check 273 | throw new RuntimeException("write message error"); 274 | } 275 | 276 | } 277 | 278 | @Override 279 | public void subscribe(CIO cio) { 280 | synchronized (cioMap) { 281 | if(isConnected()) { 282 | try { 283 | cio.state = read(cio); // initial state 284 | } catch (Exception e) {} 285 | } 286 | cioMap.put(cio.hashCode(), cio); 287 | } 288 | } 289 | 290 | @Override 291 | public boolean unsuscribe(CIO cio) { 292 | synchronized (cioMap) { 293 | return cioMap.remove(cio.hashCode()) != null; 294 | } 295 | } 296 | 297 | @Override 298 | public void subscribe(int position, int bit) { 299 | subscribe(new CIO(position, bit)); 300 | } 301 | 302 | @Override 303 | public void unsuscribe(int position, int bit) { 304 | unsuscribe(new CIO(position, bit)); 305 | } 306 | 307 | @Override 308 | public void unsuscribeAll() { 309 | synchronized (cioMap) { 310 | cioMap.clear(); 311 | } 312 | } 313 | 314 | @Override 315 | public void setInputListener(InputListener listener) { 316 | this.listener = listener; 317 | } 318 | 319 | @Override 320 | public InputListener getInputListener() { 321 | return listener; 322 | } 323 | 324 | private int readResponse(byte[] data) throws IOException { 325 | readFully(is, data, 0, 8); 326 | int n = (data[6] << 1) | (data[7] & 0xFF); 327 | readFully(is, data, 8, n); 328 | return n + 8; 329 | } 330 | 331 | private static void readFully(InputStream is, byte[] data, int off, int len) throws IOException { 332 | int r, n = 0; 333 | while(n < len) { 334 | r = is.read(data, off + n, len - n); 335 | if(r < 0) throw new EOFException(); 336 | n += r; 337 | } 338 | } 339 | 340 | public String getHost() { 341 | return host; 342 | } 343 | 344 | public void setHost(String host) { 345 | this.host = host; 346 | } 347 | 348 | public int getPort() { 349 | return port; 350 | } 351 | 352 | public void setPort(int port) { 353 | this.port = port; 354 | } 355 | 356 | public long getRefreshTime() { 357 | return refreshTime; 358 | } 359 | 360 | public void setRefreshTime(long refreshTime) { 361 | this.refreshTime = Math.max(1, refreshTime); 362 | } 363 | 364 | } 365 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/Hashcode.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | public class Hashcode { 4 | 5 | private int ch, pin; 6 | 7 | public int getCh() { 8 | return ch; 9 | } 10 | 11 | public void setCh(int ch) { 12 | this.ch = ch; 13 | } 14 | 15 | public int getPin() { 16 | return pin; 17 | } 18 | 19 | public void setPin(int pin) { 20 | this.pin = pin; 21 | } 22 | 23 | @Override 24 | public int hashCode() { 25 | final int prime = 31; 26 | int result = 1; 27 | result = prime * result + ch; 28 | result = prime * result + pin; 29 | return result; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object obj) { 34 | if (this == obj) 35 | return true; 36 | if (obj == null) 37 | return false; 38 | if (getClass() != obj.getClass()) 39 | return false; 40 | Hashcode other = (Hashcode) obj; 41 | if (ch != other.ch) 42 | return false; 43 | if (pin != other.pin) 44 | return false; 45 | return true; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/com/delmesoft/cp1l/fins/InputListener.java: -------------------------------------------------------------------------------- 1 | package com.delmesoft.cp1l.fins; 2 | 3 | /** 4 | * 2021 Sergio S. - sergi.ss4@gmail.com 5 | * @author Sergio 6 | * 7 | */ 8 | public interface InputListener { 9 | 10 | void onEvent(int ch, int pin, boolean state); 11 | 12 | void onError(Exception e); 13 | 14 | } 15 | --------------------------------------------------------------------------------