1 /** 2 * Fins client 3 */ 4 module dfins.fins; 5 6 import dfins.channel; 7 8 /** 9 * Fins protocol exception 10 */ 11 class FinsException : Exception { 12 this(ubyte mainCode, ubyte subCode, string file = null, size_t line = 0) @trusted { 13 _mainCode = mainCode; 14 _subCode = subCode; 15 import std..string : format; 16 17 super("Fins error %s".format(mainErrToString(_mainCode))); 18 } 19 20 private ubyte _mainCode; 21 /** 22 * Main error code 23 */ 24 ubyte mainCode() { 25 return _mainCode; 26 } 27 28 private ubyte _subCode; 29 /** 30 * Sub error code 31 */ 32 ubyte subCode() { 33 return _subCode; 34 } 35 } 36 37 /** 38 * Memory area code. See pg.15 of $(I FINS Commands reference manual) 39 */ 40 enum MemoryArea : ubyte { 41 CIO_BIT = 0x30, 42 W_BIT = 0x31, 43 H_BIT = 0x32, 44 A_BIT = 0x33, 45 D_BIT = 0x02, 46 /** 47 * CIO Channel IO area, word 48 */ 49 IO = 0xB0, 50 /** 51 * WR Work area, word 52 */ 53 WR = 0xB1, 54 /* 55 * HR Holding area, word 56 */ 57 HR = 0xB2, 58 /** 59 * AR Auxiliary Relay area, word 60 */ 61 AR = 0xB3, 62 /** 63 * DM Data Memory area, word 64 */ 65 DM = 0x82, 66 /** 67 * CNT, Counter area, word 68 */ 69 CT = 0x89 70 } 71 72 /// 73 enum FINS_HEADER_LEN = 12; 74 75 /// 76 struct Header { 77 /** 78 * Information Control Field, set to 0x80 79 */ 80 ubyte icf = 0x80; 81 //ubyte rsv;//reserved, set to 0x00 82 //ubyte gct = 0x02;//gateway count, set to 0x02 83 /** 84 * Destination network address, 0x0 local , 0x01 if there are not network intermediaries 85 */ 86 ubyte dna; 87 /** 88 * Destination node number, if set to default this is the subnet byte of the ip of the plc (ex. 192.168.0.1 -> 0x01) 89 */ 90 ubyte da1; 91 /** 92 * Destination unit number, the unit number, see the hw config of plc, generally 0x00 93 */ 94 ubyte da2; 95 /** 96 * Source network, generally 0x01 97 */ 98 ubyte sna; 99 /** 100 * Source node number, like the destination node number, you could set a fixed number into plc config 101 */ 102 ubyte sa1 = 0x02; 103 /** 104 * Source unit number, like the destination unit number 105 */ 106 ubyte sa2; 107 /** 108 * Counter for the resend, generally 0x00 109 */ 110 ubyte sid; 111 /** 112 * Main command code (high byte) 113 */ 114 ubyte mainRqsCode; 115 /** 116 * Sub request code 117 */ 118 ubyte subRqsCode; 119 } 120 121 /** 122 * Convenience function for creating an `Header` with subnet data (`da1`) 123 */ 124 Header header(ubyte subnet) { 125 Header h; 126 h.da1 = subnet; 127 return h; 128 } 129 130 unittest { 131 Header hdr = header(0x11); 132 assert(hdr.icf == 0x80); 133 assert(hdr.dna == 0x0); 134 assert(hdr.da1 == 0x11); 135 } 136 137 /** 138 * Convert an `Header` to array of bytes. 139 */ 140 ubyte[] toBytes(Header data) { 141 enum RSV = 0x0; 142 enum GCT = 0x02; 143 ubyte[] b; 144 b ~= data.icf; 145 b ~= RSV; 146 b ~= GCT; 147 b ~= data.dna; 148 b ~= data.da1; 149 b ~= data.da2; 150 b ~= data.sna; 151 b ~= data.sa1; 152 b ~= data.sa2; 153 b ~= data.sid; 154 b ~= data.mainRqsCode; 155 b ~= data.subRqsCode; 156 return b; 157 } 158 159 unittest { 160 Header data; 161 data.dna = 0; 162 data.da1 = 0x16; 163 data.da2 = 0; 164 data.sna = 0; 165 data.sa1 = 0x02; 166 data.sa2 = 0; 167 data.mainRqsCode = 0x01; 168 data.subRqsCode = 0x01; 169 170 ubyte[] exp = [0x80, 0x00, 0x02, 0x00, 0x16, 0x0, 0x00, 0x02, 0x0, 0x0, 0x01, 0x01]; 171 auto b = data.toBytes; 172 assert(b.length == FINS_HEADER_LEN); 173 174 import std.conv; 175 176 for (int i = 0; i < FINS_HEADER_LEN; ++i) { 177 assert(b[i] == exp[i], i.to!string()); 178 } 179 } 180 181 /** 182 * Converts an array of bytes to `Header` 183 */ 184 Header toHeader(ubyte[] blob) 185 in { 186 assert(blob.length >= FINS_HEADER_LEN, "Blob too short (less than 12)"); 187 } 188 do { 189 Header h; 190 h.icf = blob[0]; 191 h.dna = blob[3]; 192 h.da1 = blob[4]; 193 h.da2 = blob[5]; 194 h.sna = blob[6]; 195 h.sa1 = blob[7]; 196 h.sa2 = blob[8]; 197 h.sid = blob[9]; 198 h.mainRqsCode = blob[10]; 199 h.subRqsCode = blob[11]; 200 return h; 201 } 202 203 unittest { 204 ubyte[] blob = [0xc0, 0x0, 0x02, 0x0, 0x02, 0x0, 0x0, 0x16, 0x0, 0x0, 0x01, 0x02]; 205 Header h = blob.toHeader; 206 assert(h.icf == 0xC0); 207 assert(h.dna == 0x0); 208 assert(h.da1 == 0x2); 209 assert(h.da2 == 0x0); 210 assert(h.sna == 0x0); 211 assert(h.sa1 == 0x16); 212 assert(h.sa2 == 0x0); 213 assert(h.sid == 0x0); 214 assert(h.mainRqsCode == 0x01); 215 assert(h.subRqsCode == 0x02); 216 } 217 218 /// 219 struct ResponseData { 220 Header header; 221 ubyte mainRspCode; 222 ubyte subRspCode; 223 ubyte[] text; 224 } 225 226 /** 227 * Converts an array of bytes to `ResponseData` structure 228 */ 229 ResponseData toResponse(ubyte[] data) { 230 ResponseData resp; 231 resp.header = data.toHeader; 232 resp.mainRspCode = data[12]; 233 resp.subRspCode = data[13]; 234 for (int i = 0; i < data.length - 14; i++) { 235 resp.text ~= data[14 + i]; 236 } 237 return resp; 238 } 239 240 /** 241 * Returs an array with start address and size. 242 */ 243 private ubyte[] getAddrBlock(ushort start, ushort size) { 244 ubyte[] cmdBlock; 245 246 //beginning address 247 cmdBlock ~= cast(ubyte)(start >> 8); 248 cmdBlock ~= cast(ubyte)start; 249 cmdBlock ~= 0x00; 250 cmdBlock ~= cast(ubyte)(size >> 8); 251 cmdBlock ~= cast(ubyte)size; 252 return cmdBlock; 253 } 254 255 /** 256 * Client for Fins protocol 257 */ 258 class FinsClient { 259 private IChannel channel; 260 private Header header; 261 this(IChannel channel, Header header) { 262 assert(channel !is null); 263 this.channel = channel; 264 this.header = header; 265 } 266 267 /** 268 * Write an Omron PLC area: the area must be defined as CJ like area 269 * 270 * Params: 271 * area = The area type 272 * start = The start offset for the write process. 273 * buffer = The byte array buffer which will be write in the PLC. 274 */ 275 void writeArea(MemoryArea area, ushort start, ubyte[] buffer) 276 in { 277 assert((buffer.length & 1) == 0, "Odd buffer length"); 278 } 279 do { 280 import std.conv : to; 281 import dfins.util : BYTES_PER_WORD; 282 283 ubyte[] text; 284 //memory area code 285 text ~= cast(ubyte)area; 286 //IMPORTANT: The size is expressed in WORD (2 byte) 287 ushort size = (buffer.length / BYTES_PER_WORD).to!ushort; 288 text ~= getAddrBlock(start, size); 289 text ~= buffer; 290 sendFinsCommand(0x01, 0x02, text); 291 } 292 293 /** 294 * Read an Omron PLC area: the area must be defined as CJ like area 295 * 296 * Params: 297 * area = The area type 298 * start = The start offset for the read process. 299 * size = The size of the area to read. IMPORTANT: The size is expressed in WORD (2 byte) 300 * 301 * 302 * Returns: 303 * The byte array buffer in which will be store the PLC readed area. 304 * 305 */ 306 ubyte[] readArea(MemoryArea area, ushort start, ushort size) { 307 import std.stdio; 308 309 ubyte[] cmdBlock; 310 311 //memory area code 312 cmdBlock ~= cast(ubyte)area; 313 cmdBlock ~= getAddrBlock(start, size); 314 315 return sendFinsCommand(0x01, 0x01, cmdBlock); 316 } 317 318 private ubyte[] sendFinsCommand(ubyte mainCode, ubyte subCode, ubyte[] comText) { 319 header.mainRqsCode = mainCode; 320 header.subRqsCode = subCode; 321 322 ubyte[] sendFrame = header.toBytes() ~ comText; 323 ubyte[] receiveFrame = channel.send(sendFrame); 324 325 ResponseData response = receiveFrame.toResponse(); 326 if (response.mainRspCode != 0) { 327 throw new FinsException(response.mainRspCode, response.subRspCode); 328 } 329 return response.text; 330 } 331 } 332 333 /** 334 * Converts main error code into string 335 */ 336 string mainErrToString(ubyte mainErr) { 337 switch (mainErr) { 338 case 0x01: 339 return "Local node error"; 340 case 0x02: 341 return "Destination node error"; 342 case 0x03: 343 return "Communications controller error"; 344 case 0x04: 345 return "Not executable"; 346 case 0x05: 347 return "Routing error"; 348 case 0x10: 349 return "Command format error"; 350 case 0x11: 351 return "Parameter error"; 352 case 0x20: 353 return "Read not possible"; 354 case 0x21: 355 return "Write not possible"; 356 case 0x22: 357 return "Not executable in current mode"; 358 case 0x23: 359 return "No Unit"; 360 case 0x24: 361 return "Start/stop not possible"; 362 case 0x25: 363 return "Unit error"; 364 case 0x26: 365 return "Command error"; 366 case 0x30: 367 return "Access right error"; 368 case 0x40: 369 return "Abort"; 370 default: 371 return "Unknown error"; 372 } 373 }