├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example └── main.go └── fins ├── address.go ├── client.go ├── client_test.go ├── command_code.go ├── driver.go ├── end_code.go ├── error.go ├── header.go ├── memory_area.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | - 1.9 6 | - "1.10" 7 | - tip 8 | 9 | install: 10 | - go get github.com/pkg/errors 11 | - go get github.com/stretchr/testify/assert 12 | - go get github.com/stretchr/testify/require -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 l1va 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 | # GoFINS 2 | 3 | [![Build Status](https://travis-ci.org/l1va/gofins.svg?branch=master)](https://travis-ci.org/l1va/gofins) 4 | 5 | This is fins command client written by Go. 6 | 7 | The library support communication to omron PLC from Go application. 8 | 9 | Ideas were taken from https://github.com/hiroeorz/omron-fins-go and https://github.com/patrick--/node-omron-fins 10 | 11 | Library was tested with Omron PLC NJ501-1300. Mean time of the cycle request-response is 4ms. 12 | Additional work in the siyka-au repository was tested against a CP1L-EM. 13 | 14 | There is simple Omron FINS Server (PLC emulator) in the fins/server.go 15 | 16 | Feel free to ask questions, raise issues and make pull requests! 17 | 18 | ### Thanks 19 | [malleblas](https://github.com/malleblas) for his PR: https://github.com/l1va/gofins/pull/1 20 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "math" 7 | 8 | "github.com/l1va/gofins/fins" 9 | ) 10 | 11 | func main() { 12 | 13 | //clientAddr := fins.NewAddress("192.168.250.10", 9600, 0, 34, 0) 14 | //plcAddr := fins.NewAddress("192.168.250.1", 9601, 0, 0, 0) 15 | clientAddr := fins.NewAddress("127.0.0.1", 9600, 0, 34, 0) 16 | plcAddr := fins.NewAddress("127.0.0.1", 9601, 0, 0, 0) 17 | 18 | s, e := fins.NewPLCSimulator(plcAddr) 19 | if e != nil { 20 | panic(e) 21 | } 22 | defer s.Close() 23 | 24 | c, err := fins.NewClient(clientAddr, plcAddr) 25 | if err != nil { 26 | panic(err) 27 | } 28 | defer c.Close() 29 | 30 | z, err := c.ReadWords(fins.MemoryAreaDMWord, 1000, 50) 31 | if err != nil { 32 | panic(err) 33 | } 34 | fmt.Println(z) 35 | // output: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 36 | c.WriteWords(fins.MemoryAreaDMWord, 2000, []uint16{z[0] + 1, z[1] - 1}) 37 | 38 | z, err = c.ReadWords(fins.MemoryAreaDMWord, 2000, 50) 39 | if err != nil { 40 | panic(err) 41 | } 42 | fmt.Println(z) 43 | // output: [1 65535 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 44 | buf := make([]byte, 8, 8) 45 | binary.LittleEndian.PutUint64(buf[:], math.Float64bits(15.6)) 46 | err = c.WriteBytes(fins.MemoryAreaDMWord, 10, buf) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | b, err := c.ReadBytes(fins.MemoryAreaDMWord, 10, 4) 52 | if err != nil { 53 | panic(err) 54 | } 55 | floatRes := math.Float64frombits(binary.LittleEndian.Uint64(b)) 56 | fmt.Println("Float result:", floatRes) 57 | // output: Float result: 15.6 58 | 59 | err = c.WriteString(fins.MemoryAreaDMWord, 10000, "teststring") 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | str, _ := c.ReadString(fins.MemoryAreaDMWord, 10000, 5) 65 | fmt.Println(str, len(str)) 66 | // output: teststring 10 67 | 68 | //bit, _ := c.ReadBits(fins.MemoryAreaDMWord, 10473, 2, 1) 69 | //fmt.Println(bit) 70 | //fmt.Println(len(bit)) 71 | 72 | // c.WriteWords(fins.MemoryAreaDMWord, 24000, []uint16{z[0] + 1, z[1] - 1}) 73 | // c.WriteBits(fins.MemoryAreaDMBit, 24002, 0, []bool{false, false, false, true, 74 | // true, false, false, true, 75 | // false, false, false, false, 76 | // true, true, true, true}) 77 | // c.SetBit(fins.MemoryAreaDMBit, 24003, 1) 78 | // c.ResetBit(fins.MemoryAreaDMBit, 24003, 0) 79 | // c.ToggleBit(fins.MemoryAreaDMBit, 24003, 2) 80 | 81 | // cron := cron.New() 82 | // s := rasc.NewShelter() 83 | // cron.AddFunc("*/5 * * * * *", func() { 84 | // t, _ := c.ReadClock() 85 | // fmt.Printf("Setting PLC time to: %s\n", t.Format(time.RFC3339)) 86 | // c.WriteString(fins.MemoryAreaDMWord, 10000, 10, t.Format(time.RFC3339)) 87 | // }) 88 | // cron.Start() 89 | } 90 | -------------------------------------------------------------------------------- /fins/address.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import "net" 4 | 5 | // finsAddress A FINS device address 6 | type finsAddress struct { 7 | network byte 8 | node byte 9 | unit byte 10 | } 11 | 12 | // Address A full device address 13 | type Address struct { 14 | finsAddress finsAddress 15 | udpAddress *net.UDPAddr 16 | } 17 | 18 | func NewAddress(ip string, port int, network, node, unit byte) Address { 19 | return Address{ 20 | udpAddress: &net.UDPAddr{ 21 | IP: net.ParseIP(ip), 22 | Port: port, 23 | }, 24 | finsAddress: finsAddress{ 25 | network: network, 26 | node: node, 27 | unit: unit, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fins/client.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "log" 9 | "net" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const DEFAULT_RESPONSE_TIMEOUT = 20 // ms 15 | 16 | // Client Omron FINS client 17 | type Client struct { 18 | conn *net.UDPConn 19 | resp []chan response 20 | sync.Mutex 21 | dst finsAddress 22 | src finsAddress 23 | sid byte 24 | closed bool 25 | responseTimeoutMs time.Duration 26 | byteOrder binary.ByteOrder 27 | } 28 | 29 | // NewClient creates a new Omron FINS client 30 | func NewClient(localAddr, plcAddr Address) (*Client, error) { 31 | c := new(Client) 32 | c.dst = plcAddr.finsAddress 33 | c.src = localAddr.finsAddress 34 | c.responseTimeoutMs = DEFAULT_RESPONSE_TIMEOUT 35 | c.byteOrder = binary.BigEndian 36 | 37 | conn, err := net.DialUDP("udp", localAddr.udpAddress, plcAddr.udpAddress) 38 | if err != nil { 39 | return nil, err 40 | } 41 | c.conn = conn 42 | 43 | c.resp = make([]chan response, 256) //storage for all responses, sid is byte - only 256 values 44 | go c.listenLoop() 45 | return c, nil 46 | } 47 | // Set byte order 48 | // Default value: binary.BigEndian 49 | func (c *Client) SetByteOrder(o binary.ByteOrder) { 50 | c.byteOrder = o 51 | } 52 | 53 | // Set response timeout duration (ms). 54 | // Default value: 20ms. 55 | // A timeout of zero can be used to block indefinitely. 56 | func (c *Client) SetTimeoutMs(t uint) { 57 | c.responseTimeoutMs = time.Duration(t) 58 | } 59 | 60 | // Close Closes an Omron FINS connection 61 | func (c *Client) Close() { 62 | c.closed = true 63 | c.conn.Close() 64 | } 65 | 66 | // ReadWords Reads words from the PLC data area 67 | func (c *Client) ReadWords(memoryArea byte, address uint16, readCount uint16) ([]uint16, error) { 68 | if checkIsWordMemoryArea(memoryArea) == false { 69 | return nil, IncompatibleMemoryAreaError{memoryArea} 70 | } 71 | command := readCommand(memAddr(memoryArea, address), readCount) 72 | r, e := c.sendCommand(command) 73 | e = checkResponse(r, e) 74 | if e != nil { 75 | return nil, e 76 | } 77 | 78 | data := make([]uint16, readCount, readCount) 79 | for i := 0; i < int(readCount); i++ { 80 | data[i] = c.byteOrder.Uint16(r.data[i*2 : i*2+2]) 81 | } 82 | 83 | return data, nil 84 | } 85 | 86 | // ReadBytes Reads bytes from the PLC data area 87 | func (c *Client) ReadBytes(memoryArea byte, address uint16, readCount uint16) ([]byte, error) { 88 | if checkIsWordMemoryArea(memoryArea) == false { 89 | return nil, IncompatibleMemoryAreaError{memoryArea} 90 | } 91 | command := readCommand(memAddr(memoryArea, address), readCount) 92 | r, e := c.sendCommand(command) 93 | e = checkResponse(r, e) 94 | if e != nil { 95 | return nil, e 96 | } 97 | 98 | return r.data, nil 99 | } 100 | 101 | // ReadString Reads a string from the PLC data area 102 | func (c *Client) ReadString(memoryArea byte, address uint16, readCount uint16) (string, error) { 103 | data, e := c.ReadBytes(memoryArea, address, readCount) 104 | if e != nil { 105 | return "", e 106 | } 107 | n := bytes.IndexByte(data, 0) 108 | if n == -1 { 109 | n = len(data) 110 | } 111 | return string(data[:n]), nil 112 | } 113 | 114 | // ReadBits Reads bits from the PLC data area 115 | func (c *Client) ReadBits(memoryArea byte, address uint16, bitOffset byte, readCount uint16) ([]bool, error) { 116 | if checkIsBitMemoryArea(memoryArea) == false { 117 | return nil, IncompatibleMemoryAreaError{memoryArea} 118 | } 119 | command := readCommand(memAddrWithBitOffset(memoryArea, address, bitOffset), readCount) 120 | r, e := c.sendCommand(command) 121 | e = checkResponse(r, e) 122 | if e != nil { 123 | return nil, e 124 | } 125 | 126 | data := make([]bool, readCount, readCount) 127 | for i := 0; i < int(readCount); i++ { 128 | data[i] = r.data[i]&0x01 > 0 129 | } 130 | 131 | return data, nil 132 | } 133 | 134 | // ReadClock Reads the PLC clock 135 | func (c *Client) ReadClock() (*time.Time, error) { 136 | r, e := c.sendCommand(clockReadCommand()) 137 | e = checkResponse(r, e) 138 | if e != nil { 139 | return nil, e 140 | } 141 | year, _ := decodeBCD(r.data[0:1]) 142 | if year < 50 { 143 | year += 2000 144 | } else { 145 | year += 1900 146 | } 147 | month, _ := decodeBCD(r.data[1:2]) 148 | day, _ := decodeBCD(r.data[2:3]) 149 | hour, _ := decodeBCD(r.data[3:4]) 150 | minute, _ := decodeBCD(r.data[4:5]) 151 | second, _ := decodeBCD(r.data[5:6]) 152 | 153 | t := time.Date( 154 | int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 155 | 0, // nanosecond 156 | time.Local, 157 | ) 158 | return &t, nil 159 | } 160 | 161 | // WriteWords Writes words to the PLC data area 162 | func (c *Client) WriteWords(memoryArea byte, address uint16, data []uint16) error { 163 | if checkIsWordMemoryArea(memoryArea) == false { 164 | return IncompatibleMemoryAreaError{memoryArea} 165 | } 166 | l := uint16(len(data)) 167 | bts := make([]byte, 2*l, 2*l) 168 | for i := 0; i < int(l); i++ { 169 | c.byteOrder.PutUint16(bts[i*2:i*2+2], data[i]) 170 | } 171 | command := writeCommand(memAddr(memoryArea, address), l, bts) 172 | 173 | return checkResponse(c.sendCommand(command)) 174 | } 175 | 176 | // WriteString Writes a string to the PLC data area 177 | func (c *Client) WriteString(memoryArea byte, address uint16, s string) error { 178 | if checkIsWordMemoryArea(memoryArea) == false { 179 | return IncompatibleMemoryAreaError{memoryArea} 180 | } 181 | bts := make([]byte, 2*len(s), 2*len(s)) 182 | copy(bts, s) 183 | 184 | command := writeCommand(memAddr(memoryArea, address), uint16((len(s)+1)/2), bts) //TODO: test on real PLC 185 | 186 | return checkResponse(c.sendCommand(command)) 187 | } 188 | 189 | // WriteBytes Writes bytes array to the PLC data area 190 | func (c *Client) WriteBytes(memoryArea byte, address uint16, b []byte) error { 191 | if checkIsWordMemoryArea(memoryArea) == false { 192 | return IncompatibleMemoryAreaError{memoryArea} 193 | } 194 | command := writeCommand(memAddr(memoryArea, address), uint16(len(b)), b) 195 | return checkResponse(c.sendCommand(command)) 196 | } 197 | 198 | // WriteBits Writes bits to the PLC data area 199 | func (c *Client) WriteBits(memoryArea byte, address uint16, bitOffset byte, data []bool) error { 200 | if checkIsBitMemoryArea(memoryArea) == false { 201 | return IncompatibleMemoryAreaError{memoryArea} 202 | } 203 | l := uint16(len(data)) 204 | bts := make([]byte, 0, l) 205 | var d byte 206 | for i := 0; i < int(l); i++ { 207 | if data[i] { 208 | d = 0x01 209 | } else { 210 | d = 0x00 211 | } 212 | bts = append(bts, d) 213 | } 214 | command := writeCommand(memAddrWithBitOffset(memoryArea, address, bitOffset), l, bts) 215 | 216 | return checkResponse(c.sendCommand(command)) 217 | } 218 | 219 | // SetBit Sets a bit in the PLC data area 220 | func (c *Client) SetBit(memoryArea byte, address uint16, bitOffset byte) error { 221 | return c.bitTwiddle(memoryArea, address, bitOffset, 0x01) 222 | } 223 | 224 | // ResetBit Resets a bit in the PLC data area 225 | func (c *Client) ResetBit(memoryArea byte, address uint16, bitOffset byte) error { 226 | return c.bitTwiddle(memoryArea, address, bitOffset, 0x00) 227 | } 228 | 229 | // ToggleBit Toggles a bit in the PLC data area 230 | func (c *Client) ToggleBit(memoryArea byte, address uint16, bitOffset byte) error { 231 | b, e := c.ReadBits(memoryArea, address, bitOffset, 1) 232 | if e != nil { 233 | return e 234 | } 235 | var t byte 236 | if b[0] { 237 | t = 0x00 238 | } else { 239 | t = 0x01 240 | } 241 | return c.bitTwiddle(memoryArea, address, bitOffset, t) 242 | } 243 | 244 | func (c *Client) bitTwiddle(memoryArea byte, address uint16, bitOffset byte, value byte) error { 245 | if checkIsBitMemoryArea(memoryArea) == false { 246 | return IncompatibleMemoryAreaError{memoryArea} 247 | } 248 | mem := memoryAddress{memoryArea, address, bitOffset} 249 | command := writeCommand(mem, 1, []byte{value}) 250 | 251 | return checkResponse(c.sendCommand(command)) 252 | } 253 | 254 | func checkResponse(r *response, e error) error { 255 | if e != nil { 256 | return e 257 | } 258 | if r.endCode != EndCodeNormalCompletion { 259 | return fmt.Errorf("error reported by destination, end code 0x%x", r.endCode) 260 | } 261 | return nil 262 | } 263 | 264 | func (c *Client) nextHeader() *Header { 265 | sid := c.incrementSid() 266 | header := defaultCommandHeader(c.src, c.dst, sid) 267 | return &header 268 | } 269 | 270 | func (c *Client) incrementSid() byte { 271 | c.Lock() //thread-safe sid incrementation 272 | c.sid++ 273 | sid := c.sid 274 | c.Unlock() 275 | c.resp[sid] = make(chan response) //clearing cell of storage for new response 276 | return sid 277 | } 278 | 279 | func (c *Client) sendCommand(command []byte) (*response, error) { 280 | header := c.nextHeader() 281 | bts := encodeHeader(*header) 282 | bts = append(bts, command...) 283 | _, err := (*c.conn).Write(bts) 284 | if err != nil { 285 | return nil, err 286 | } 287 | 288 | // if response timeout is zero, block indefinitely 289 | if c.responseTimeoutMs > 0 { 290 | select { 291 | case resp := <-c.resp[header.serviceID]: 292 | return &resp, nil 293 | case <-time.After(c.responseTimeoutMs * time.Millisecond): 294 | return nil, ResponseTimeoutError{c.responseTimeoutMs} 295 | } 296 | } else { 297 | resp := <-c.resp[header.serviceID] 298 | return &resp, nil 299 | } 300 | } 301 | 302 | func (c *Client) listenLoop() { 303 | for { 304 | buf := make([]byte, 2048) 305 | n, err := bufio.NewReader(c.conn).Read(buf) 306 | if err != nil { 307 | // do not complain when connection is closed by user 308 | if !c.closed { 309 | log.Fatal(err) 310 | } 311 | break 312 | } 313 | 314 | if n > 0 { 315 | ans := decodeResponse(buf[:n]) 316 | c.resp[ans.header.serviceID] <- ans 317 | } else { 318 | log.Println("cannot read response: ", buf) 319 | } 320 | } 321 | } 322 | 323 | func checkIsWordMemoryArea(memoryArea byte) bool { 324 | if memoryArea == MemoryAreaDMWord || 325 | memoryArea == MemoryAreaARWord || 326 | memoryArea == MemoryAreaHRWord || 327 | memoryArea == MemoryAreaWRWord { 328 | return true 329 | } 330 | return false 331 | } 332 | 333 | func checkIsBitMemoryArea(memoryArea byte) bool { 334 | if memoryArea == MemoryAreaDMBit || 335 | memoryArea == MemoryAreaARBit || 336 | memoryArea == MemoryAreaHRBit || 337 | memoryArea == MemoryAreaWRBit { 338 | return true 339 | } 340 | return false 341 | } 342 | -------------------------------------------------------------------------------- /fins/client_test.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import ( 4 | "encoding/binary" 5 | "math" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFinsClient(t *testing.T) { 12 | clientAddr := NewAddress("", 9600, 0, 2, 0) 13 | plcAddr := NewAddress("", 9601, 0, 10, 0) 14 | 15 | toWrite := []uint16{5, 4, 3, 2, 1} 16 | 17 | s, e := NewPLCSimulator(plcAddr) 18 | if e != nil { 19 | panic(e) 20 | } 21 | defer s.Close() 22 | 23 | c, e := NewClient(clientAddr, plcAddr) 24 | if e != nil { 25 | panic(e) 26 | } 27 | defer c.Close() 28 | 29 | // ------------- Test Words 30 | err := c.WriteWords(MemoryAreaDMWord, 100, toWrite) 31 | assert.Nil(t, err) 32 | 33 | vals, err := c.ReadWords(MemoryAreaDMWord, 100, 5) 34 | assert.Nil(t, err) 35 | assert.Equal(t, toWrite, vals) 36 | 37 | // test setting response timeout 38 | c.SetTimeoutMs(50) 39 | _, err = c.ReadWords(MemoryAreaDMWord, 100, 5) 40 | assert.Nil(t, err) 41 | 42 | // ------------- Test Strings 43 | err = c.WriteString(MemoryAreaDMWord, 10, "ф1234") 44 | assert.Nil(t, err) 45 | 46 | v, err := c.ReadString(MemoryAreaDMWord, 12, 1) 47 | assert.Nil(t, err) 48 | assert.Equal(t, "12", v) 49 | 50 | v, err = c.ReadString(MemoryAreaDMWord, 10, 3) 51 | assert.Nil(t, err) 52 | assert.Equal(t, "ф1234", v) 53 | 54 | v, err = c.ReadString(MemoryAreaDMWord, 10, 5) 55 | assert.Nil(t, err) 56 | assert.Equal(t, "ф1234", v) 57 | 58 | // ------------- Test Bytes 59 | err = c.WriteBytes(MemoryAreaDMWord, 10, []byte{0x00, 0x00 ,0xC1 , 0xA0}) 60 | assert.Nil(t, err) 61 | 62 | b, err := c.ReadBytes(MemoryAreaDMWord, 10, 2) 63 | assert.Nil(t, err) 64 | assert.Equal(t, []byte{0x00, 0x00 ,0xC1 , 0xA0}, b) 65 | 66 | buf := make([]byte, 8, 8) 67 | binary.LittleEndian.PutUint64(buf[:], math.Float64bits(-20)) 68 | err = c.WriteBytes(MemoryAreaDMWord, 10, buf) 69 | assert.Nil(t, err) 70 | 71 | b, err = c.ReadBytes(MemoryAreaDMWord, 10, 4) 72 | assert.Nil(t, err) 73 | assert.Equal(t, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34, 0xc0}, b) 74 | 75 | 76 | // ------------- Test Bits 77 | err = c.WriteBits(MemoryAreaDMBit, 10, 2, []bool{true, false, true}) 78 | assert.Nil(t, err) 79 | 80 | bs, err := c.ReadBits(MemoryAreaDMBit, 10, 2, 3) 81 | assert.Nil(t, err) 82 | assert.Equal(t, []bool{true, false, true}, bs) 83 | 84 | bs, err = c.ReadBits(MemoryAreaDMBit, 10, 1, 5) 85 | assert.Nil(t, err) 86 | assert.Equal(t, []bool{false, true, false, true, false}, bs) 87 | 88 | } 89 | -------------------------------------------------------------------------------- /fins/command_code.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | const ( 4 | // CommandCodeMemoryAreaRead Command code: IO memory area read 5 | CommandCodeMemoryAreaRead uint16 = 0x0101 6 | 7 | // CommandCodeMemoryAreaWrite Command code: IO memory area write 8 | CommandCodeMemoryAreaWrite uint16 = 0x0102 9 | 10 | // CommandCodeMemoryAreaFill Command code: IO memory area fill 11 | CommandCodeMemoryAreaFill uint16 = 0x0103 12 | 13 | // CommandCodeMultipleMemoryAreaRead Command code: IO memory area multiple read 14 | CommandCodeMultipleMemoryAreaRead uint16 = 0x0104 15 | 16 | // CommandCodeMemoryAreaTransfer Command code: IO memory area transfer 17 | CommandCodeMemoryAreaTransfer uint16 = 0x0105 18 | 19 | // CommandCodeParameterAreaRead Command code: Parameter area read 20 | CommandCodeParameterAreaRead uint16 = 0x0201 21 | 22 | // CommandCodeParameterAreaWrite Command code: Parameter area write 23 | CommandCodeParameterAreaWrite uint16 = 0x0202 24 | 25 | // CommandCodeParameterAreaClear Command code: Parameter area clear 26 | CommandCodeParameterAreaClear uint16 = 0x0203 27 | 28 | // CommandCodeProgramAreaRead Command code: Program area read 29 | CommandCodeProgramAreaRead uint16 = 0x0301 30 | 31 | // CommandCodeProgramAreaWrite Command code: Program area write 32 | CommandCodeProgramAreaWrite uint16 = 0x0302 33 | 34 | // CommandCodeProgramAreaClear Command code: Program area clear 35 | CommandCodeProgramAreaClear uint16 = 0x0303 36 | 37 | // CommandCodeRun Command code: Set operating mode to run 38 | CommandCodeRun uint16 = 0x0401 39 | 40 | // CommandCodeStop Command code: Set operating mode to stop 41 | CommandCodeStop uint16 = 0x0402 42 | 43 | // CommandCodeCPUUnitDataRead Command code: CPU unit data read 44 | CommandCodeCPUUnitDataRead uint16 = 0x0501 45 | 46 | // CommandCodeConnectionDataRead Command code: connection data read 47 | CommandCodeConnectionDataRead uint16 = 0x0502 48 | 49 | // CommandCodeCPUUnitStatusRead Command code: CPU unit status read 50 | CommandCodeCPUUnitStatusRead uint16 = 0x0601 51 | 52 | // CommandCodeCycleTimeRead Command code: cycle time read 53 | CommandCodeCycleTimeRead uint16 = 0x0620 54 | 55 | // CommandCodeClockRead Command code: clock read 56 | CommandCodeClockRead uint16 = 0x701 57 | 58 | // CommandCodeClockWrite Command code: clock write 59 | CommandCodeClockWrite uint16 = 0x702 60 | 61 | // CommandCodeMessageReadClear Command code: message read/clear 62 | CommandCodeMessageReadClear uint16 = 0x0920 63 | 64 | // CommandCodeAccessRightAcquire Command code: access right acquire 65 | CommandCodeAccessRightAcquire uint16 = 0x0c01 66 | 67 | // CommandCodeAccessRightForcedAcquire Command code: accress right forced acquire 68 | CommandCodeAccessRightForcedAcquire uint16 = 0x0c02 69 | 70 | // CommandCodeAccessRightRelease Command code: access right release 71 | CommandCodeAccessRightRelease uint16 = 0x0c03 72 | 73 | // CommandCodeErrorClear Command code: error clear 74 | CommandCodeErrorClear uint16 = 0x2101 75 | 76 | // CommandCodeErrorLogRead Command code: error log read 77 | CommandCodeErrorLogRead uint16 = 0x2102 78 | 79 | // CommandCodeErrorLogClear Command code: error log clear 80 | CommandCodeErrorLogClear uint16 = 0x2103 81 | 82 | // CommandCodeFINSWriteAccessLogRead Command code: FINS write access log read 83 | CommandCodeFINSWriteAccessLogRead uint16 = 0x2140 84 | 85 | // CommandCodeFINSWriteAccessLogWrite Command code: FINS write access log write 86 | CommandCodeFINSWriteAccessLogWrite uint16 = 0x2141 87 | 88 | // CommandCodeFileNameRead Command code: file name read 89 | CommandCodeFileNameRead uint16 = 0x2101 90 | 91 | // CommandCodeSingleFileRead Command code: file read 92 | CommandCodeSingleFileRead uint16 = 0x2102 93 | 94 | // CommandCodeSingleFileWrite Command code: file write 95 | CommandCodeSingleFileWrite uint16 = 0x2103 96 | 97 | // CommandCodeFileMemoryFormat Command code: file memory format 98 | CommandCodeFileMemoryFormat uint16 = 0x2104 99 | 100 | // CommandCodeFileDelete Command code: file delete 101 | CommandCodeFileDelete uint16 = 0x2105 102 | 103 | // CommandCodeFileCopy Command code: file copy 104 | CommandCodeFileCopy uint16 = 0x2107 105 | 106 | // CommandCodeFileNameChange Command code: file name change 107 | CommandCodeFileNameChange uint16 = 0x2108 108 | 109 | // CommandCodeMemoryAreaFileTransfer Command code: memory area file transfer 110 | CommandCodeMemoryAreaFileTransfer uint16 = 0x210a 111 | 112 | // CommandCodeParameterAreaFileTransfer Command code: parameter area file transfer 113 | CommandCodeParameterAreaFileTransfer uint16 = 0x210b 114 | 115 | // CommandCodeProgramAreaFileTransfer Command code: program area file transfer 116 | CommandCodeProgramAreaFileTransfer uint16 = 0x210b 117 | 118 | // CommandCodeDirectoryCreateDelete Command code: directory create/delete 119 | CommandCodeDirectoryCreateDelete uint16 = 0x2115 120 | 121 | // CommandCodeMemoryCassetteTransfer Command code: memory cassette transfer (CP1H and CP1L CPU units only) 122 | CommandCodeMemoryCassetteTransfer uint16 = 0x2120 123 | 124 | // CommandCodeForcedSetReset Command code: forced set/reset 125 | CommandCodeForcedSetReset uint16 = 0x2301 126 | 127 | // CommandCodeForcedSetResetCancel Command code: forced set/reset cancel 128 | CommandCodeForcedSetResetCancel uint16 = 0x2302 129 | 130 | // CommandCodeConvertToCompoWayFCommand Command code: convert to CompoWay/F command 131 | CommandCodeConvertToCompoWayFCommand uint16 = 0x2803 132 | 133 | // CommandCodeConvertToModbusRTUCommand Command code: convert to Modbus-RTU command 134 | CommandCodeConvertToModbusRTUCommand uint16 = 0x2804 135 | 136 | // CommandCodeConvertToModbusASCIICommand Command code: convert to Modbus-ASCII command 137 | CommandCodeConvertToModbusASCIICommand uint16 = 0x2805 138 | ) 139 | -------------------------------------------------------------------------------- /fins/driver.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | // request A FINS command request 8 | type request struct { 9 | header Header 10 | commandCode uint16 11 | data []byte 12 | } 13 | 14 | // response A FINS command response 15 | type response struct { 16 | header Header 17 | commandCode uint16 18 | endCode uint16 19 | data []byte 20 | } 21 | 22 | // memoryAddress A plc memory address to do a work 23 | type memoryAddress struct { 24 | memoryArea byte 25 | address uint16 26 | bitOffset byte 27 | } 28 | 29 | func memAddr(memoryArea byte, address uint16) memoryAddress { 30 | return memAddrWithBitOffset(memoryArea, address, 0) 31 | } 32 | 33 | func memAddrWithBitOffset(memoryArea byte, address uint16, bitOffset byte) memoryAddress { 34 | return memoryAddress{memoryArea, address, bitOffset} 35 | } 36 | 37 | func readCommand(memoryAddr memoryAddress, itemCount uint16) []byte { 38 | commandData := make([]byte, 2, 8) 39 | binary.BigEndian.PutUint16(commandData[0:2], CommandCodeMemoryAreaRead) 40 | commandData = append(commandData, encodeMemoryAddress(memoryAddr)...) 41 | commandData = append(commandData, []byte{0, 0}...) 42 | binary.BigEndian.PutUint16(commandData[6:8], itemCount) 43 | return commandData 44 | } 45 | 46 | func writeCommand(memoryAddr memoryAddress, itemCount uint16, bytes []byte) []byte { 47 | commandData := make([]byte, 2, 8+len(bytes)) 48 | binary.BigEndian.PutUint16(commandData[0:2], CommandCodeMemoryAreaWrite) 49 | commandData = append(commandData, encodeMemoryAddress(memoryAddr)...) 50 | commandData = append(commandData, []byte{0, 0}...) 51 | binary.BigEndian.PutUint16(commandData[6:8], itemCount) 52 | commandData = append(commandData, bytes...) 53 | return commandData 54 | } 55 | 56 | func clockReadCommand() []byte { 57 | commandData := make([]byte, 2, 2) 58 | binary.BigEndian.PutUint16(commandData[0:2], CommandCodeClockRead) 59 | return commandData 60 | } 61 | 62 | func encodeMemoryAddress(memoryAddr memoryAddress) []byte { 63 | bytes := make([]byte, 4, 4) 64 | bytes[0] = memoryAddr.memoryArea 65 | binary.BigEndian.PutUint16(bytes[1:3], memoryAddr.address) 66 | bytes[3] = memoryAddr.bitOffset 67 | return bytes 68 | } 69 | 70 | func decodeMemoryAddress(data []byte) memoryAddress { 71 | return memoryAddress{data[0], binary.BigEndian.Uint16(data[1:3]), data[3]} 72 | } 73 | 74 | func decodeRequest(bytes []byte) request { 75 | return request{ 76 | decodeHeader(bytes[0:10]), 77 | binary.BigEndian.Uint16(bytes[10:12]), 78 | bytes[12:], 79 | } 80 | } 81 | 82 | func decodeResponse(bytes []byte) response { 83 | return response{ 84 | decodeHeader(bytes[0:10]), 85 | binary.BigEndian.Uint16(bytes[10:12]), 86 | binary.BigEndian.Uint16(bytes[12:14]), 87 | bytes[14:], 88 | } 89 | } 90 | func encodeResponse(resp response) []byte { 91 | bytes := make([]byte, 4, 4+len(resp.data)) 92 | binary.BigEndian.PutUint16(bytes[0:2], resp.commandCode) 93 | binary.BigEndian.PutUint16(bytes[2:4], resp.endCode) 94 | bytes = append(bytes, resp.data...) 95 | bh := encodeHeader(resp.header) 96 | bh = append(bh, bytes...) 97 | return bh 98 | } 99 | 100 | const ( 101 | icfBridgesBit byte = 7 102 | icfMessageTypeBit byte = 6 103 | icfResponseRequiredBit byte = 0 104 | ) 105 | 106 | func decodeHeader(bytes []byte) Header { 107 | header := Header{} 108 | icf := bytes[0] 109 | if icf&1< 0; n++ { 148 | xx = xx / 10 149 | } 150 | bcd := make([]byte, (n+1)/2) 151 | if n%2 == 1 { 152 | hi, lo := byte(x%10), byte(0x0f) 153 | bcd[(n-1)/2] = hi<<4 | lo 154 | x = x / 10 155 | n-- 156 | } 157 | for i := n/2 - 1; i >= 0; i-- { 158 | hi, lo := byte((x/10)%10), byte(x%10) 159 | bcd[i] = hi<<4 | lo 160 | x = x / 100 161 | } 162 | return bcd 163 | } 164 | 165 | func timesTenPlusCatchingOverflow(x uint64, digit uint64) (uint64, error) { 166 | x5 := x<<2 + x 167 | if int64(x5) < 0 || x5<<1 > ^digit { 168 | return 0, BCDOverflowError{} 169 | } 170 | return x5<<1 + digit, nil 171 | } 172 | 173 | func decodeBCD(bcd []byte) (x uint64, err error) { 174 | for i, b := range bcd { 175 | hi, lo := uint64(b>>4), uint64(b&0x0f) 176 | if hi > 9 { 177 | return 0, BCDBadDigitError{"hi", hi} 178 | } 179 | x, err = timesTenPlusCatchingOverflow(x, hi) 180 | if err != nil { 181 | return 0, err 182 | } 183 | if lo == 0x0f && i == len(bcd)-1 { 184 | return x, nil 185 | } 186 | if lo > 9 { 187 | return 0, BCDBadDigitError{"lo", lo} 188 | } 189 | x, err = timesTenPlusCatchingOverflow(x, lo) 190 | if err != nil { 191 | return 0, err 192 | } 193 | } 194 | return x, nil 195 | } 196 | -------------------------------------------------------------------------------- /fins/end_code.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | // Data taken from Omron document Cat. No. W342-E1-15, pages 155-161 4 | const ( 5 | // EndCodeNormalCompletion End code: normal completion 6 | EndCodeNormalCompletion uint16 = 0x0000 7 | 8 | // EndCodeServiceInterrupted End code: normal completion; service was interrupted 9 | EndCodeServiceInterrupted uint16 = 0x0001 10 | 11 | // EndCodeLocalNodeNotInNetwork End code: local node error; local node not in network 12 | EndCodeLocalNodeNotInNetwork uint16 = 0x0101 13 | 14 | // EndCodeTokenTimeout End code: local node error; token timeout 15 | EndCodeTokenTimeout uint16 = 0x0102 16 | 17 | // EndCodeRetriesFailed End code: local node error; retries failed 18 | EndCodeRetriesFailed uint16 = 0x0103 19 | 20 | // EndCodeTooManySendFrames End code: local node error; too many send frames 21 | EndCodeTooManySendFrames uint16 = 0x0104 22 | 23 | // EndCodeNodeAddressRangeError End code: local node error; node address range error 24 | EndCodeNodeAddressRangeError uint16 = 0x0105 25 | 26 | // EndCodeNodeAddressRangeDuplication End code: local node error; node address range duplication 27 | EndCodeNodeAddressRangeDuplication uint16 = 0x0106 28 | 29 | // EndCodeDestinationNodeNotInNetwork End code: destination node error; destination node not in network 30 | EndCodeDestinationNodeNotInNetwork uint16 = 0x0201 31 | 32 | // EndCodeUnitMissing End code: destination node error; unit missing 33 | EndCodeUnitMissing uint16 = 0x0202 34 | 35 | // EndCodeThirdNodeMissing End code: destination node error; third node missing 36 | EndCodeThirdNodeMissing uint16 = 0x0203 37 | 38 | // EndCodeDestinationNodeBusy End code: destination node error; destination node busy 39 | EndCodeDestinationNodeBusy uint16 = 0x0204 40 | 41 | // EndCodeResponseTimeout End code: destination node error; response timeout 42 | EndCodeResponseTimeout uint16 = 0x0205 43 | 44 | // EndCodeCommunicationsControllerError End code: controller error; communication controller error 45 | EndCodeCommunicationsControllerError uint16 = 0x0301 46 | 47 | // EndCodeCPUUnitError End code: controller error; CPU unit error 48 | EndCodeCPUUnitError uint16 = 0x0302 49 | 50 | // EndCodeControllerError End code: controller error; controller error 51 | EndCodeControllerError uint16 = 0x0303 52 | 53 | // EndCodeUnitNumberError End code: controller error; unit number error 54 | EndCodeUnitNumberError uint16 = 0x0304 55 | 56 | // EndCodeUndefinedCommand End code: service unsupported; undefined command 57 | EndCodeUndefinedCommand uint16 = 0x0401 58 | 59 | // EndCodeNotSupportedByModelVersion End code: service unsupported; not supported by model version 60 | EndCodeNotSupportedByModelVersion uint16 = 0x0402 61 | 62 | // EndCodeDestinationAddressSettingError End code: routing table error; destination address setting error 63 | EndCodeDestinationAddressSettingError uint16 = 0x0501 64 | 65 | // EndCodeNoRoutingTables End code: routing table error; no routing tables 66 | EndCodeNoRoutingTables uint16 = 0x0502 67 | 68 | // EndCodeRoutingTableError End code: routing table error; routing table error 69 | EndCodeRoutingTableError uint16 = 0x0503 70 | 71 | // EndCodeTooManyRelays End code: routing table error; too many relays 72 | EndCodeTooManyRelays uint16 = 0x0504 73 | 74 | // EndCodeCommandTooLong End code: command format error; command too long 75 | EndCodeCommandTooLong uint16 = 0x1001 76 | 77 | // EndCodeCommandTooShort End code: command format error; command too short 78 | EndCodeCommandTooShort uint16 = 0x1002 79 | 80 | // EndCodeElementsDataDontMatch End code: command format error; elements/data don't match 81 | EndCodeElementsDataDontMatch uint16 = 0x1003 82 | 83 | // EndCodeCommandFormatError End code: command format error; command format error 84 | EndCodeCommandFormatError uint16 = 0x1004 85 | 86 | // EndCodeHeaderError End code: command format error; header error 87 | EndCodeHeaderError uint16 = 0x1005 88 | 89 | // EndCodeAreaClassificationMissing End code: parameter error; classification missing 90 | EndCodeAreaClassificationMissing uint16 = 0x1101 91 | 92 | // EndCodeAccessSizeError End code: parameter error; access size error 93 | EndCodeAccessSizeError uint16 = 0x1102 94 | 95 | // EndCodeAddressRangeError End code: parameter error; address range error 96 | EndCodeAddressRangeError uint16 = 0x1103 97 | 98 | // EndCodeAddressRangeExceeded End code: parameter error; address range exceeded 99 | EndCodeAddressRangeExceeded uint16 = 0x1104 100 | 101 | // EndCodeProgramMissing End code: parameter error; program missing 102 | EndCodeProgramMissing uint16 = 0x1106 103 | 104 | // EndCodeRelationalError End code: parameter error; relational error 105 | EndCodeRelationalError uint16 = 0x1109 106 | 107 | // EndCodeDuplicateDataAccess End code: parameter error; duplicate data access 108 | EndCodeDuplicateDataAccess uint16 = 0x110a 109 | 110 | // EndCodeResponseTooBig End code: parameter error; response too big 111 | EndCodeResponseTooBig uint16 = 0x110b 112 | 113 | // EndCodeParameterError End code: parameter error 114 | EndCodeParameterError uint16 = 0x110c 115 | 116 | // EndCodeReadNotPossibleProtected End code: read not possible; protected 117 | EndCodeReadNotPossibleProtected uint16 = 0x2002 118 | 119 | // EndCodeReadNotPossibleTableMissing End code: read not possible; table missing 120 | EndCodeReadNotPossibleTableMissing uint16 = 0x2003 121 | 122 | // EndCodeReadNotPossibleDataMissing End code: read not possible; data missing 123 | EndCodeReadNotPossibleDataMissing uint16 = 0x2004 124 | 125 | // EndCodeReadNotPossibleProgramMissing End code: read not possible; program missing 126 | EndCodeReadNotPossibleProgramMissing uint16 = 0x2005 127 | 128 | // EndCodeReadNotPossibleFileMissing End code: read not possible; file missing 129 | EndCodeReadNotPossibleFileMissing uint16 = 0x2006 130 | 131 | // EndCodeReadNotPossibleDataMismatch End code: read not possible; data mismatch 132 | EndCodeReadNotPossibleDataMismatch uint16 = 0x2007 133 | 134 | // EndCodeWriteNotPossibleReadOnly End code: write not possible; read only 135 | EndCodeWriteNotPossibleReadOnly uint16 = 0x2101 136 | 137 | // EndCodeWriteNotPossibleProtected End code: write not possible; write protected 138 | EndCodeWriteNotPossibleProtected uint16 = 0x2102 139 | 140 | // EndCodeWriteNotPossibleCannotRegister End code: write not possible; cannot register 141 | EndCodeWriteNotPossibleCannotRegister uint16 = 0x2103 142 | 143 | // EndCodeWriteNotPossibleProgramMissing End code: write not possible; program missing 144 | EndCodeWriteNotPossibleProgramMissing uint16 = 0x2105 145 | 146 | // EndCodeWriteNotPossibleFileMissing End code: write not possible; file missing 147 | EndCodeWriteNotPossibleFileMissing uint16 = 0x2106 148 | 149 | // EndCodeWriteNotPossibleFileNameAlreadyExists End code: write not possible; file name already exists 150 | EndCodeWriteNotPossibleFileNameAlreadyExists uint16 = 0x2107 151 | 152 | // EndCodeWriteNotPossibleCannotChange End code: write not possible; cannot change 153 | EndCodeWriteNotPossibleCannotChange uint16 = 0x2108 154 | 155 | // EndCodeNotExecutableInCurrentModeNotPossibleDuringExecution End code: not executeable in current mode during execution 156 | EndCodeNotExecutableInCurrentModeNotPossibleDuringExecution uint16 = 0x2201 157 | 158 | // EndCodeNotExecutableInCurrentModeNotPossibleWhileRunning End code: not executeable in current mode while running 159 | EndCodeNotExecutableInCurrentModeNotPossibleWhileRunning uint16 = 0x2202 160 | 161 | // EndCodeNotExecutableInCurrentModeWrongPLCModeInProgram End code: not executeable in current mode; PLC is in PROGRAM mode 162 | EndCodeNotExecutableInCurrentModeWrongPLCModeInProgram uint16 = 0x2203 163 | 164 | // EndCodeNotExecutableInCurrentModeWrongPLCModeInDebug End code: not executeable in current mode; PLC is in DEBUG mode 165 | EndCodeNotExecutableInCurrentModeWrongPLCModeInDebug uint16 = 0x2204 166 | 167 | // EndCodeNotExecutableInCurrentModeWrongPLCModeInMonitor End code: not executeable in current mode; PLC is in MONITOR mode 168 | EndCodeNotExecutableInCurrentModeWrongPLCModeInMonitor uint16 = 0x2205 169 | 170 | // EndCodeNotExecutableInCurrentModeWrongPLCModeInRun End code: not executeable in current mode; PLC is in RUN mode 171 | EndCodeNotExecutableInCurrentModeWrongPLCModeInRun uint16 = 0x2206 172 | 173 | // EndCodeNotExecutableInCurrentModeSpecifiedNodeNotPollingNode End code: not executeable in current mode; specified node is not polling node 174 | EndCodeNotExecutableInCurrentModeSpecifiedNodeNotPollingNode uint16 = 0x2207 175 | 176 | // EndCodeNotExecutableInCurrentModeStepCannotBeExecuted End code: not executeable in current mode; step cannot be executed 177 | EndCodeNotExecutableInCurrentModeStepCannotBeExecuted uint16 = 0x2208 178 | 179 | // EndCodeNoSuchDeviceFileDeviceMissing End code: no such device; file device missing 180 | EndCodeNoSuchDeviceFileDeviceMissing uint16 = 0x2301 181 | 182 | // EndCodeNoSuchDeviceMemoryMissing End code: no such device; memory missing 183 | EndCodeNoSuchDeviceMemoryMissing uint16 = 0x2302 184 | 185 | // EndCodeNoSuchDeviceClockMissing End code: no such device; clock missing 186 | EndCodeNoSuchDeviceClockMissing uint16 = 0x2303 187 | 188 | // EndCodeCannotStartStopTableMissing End code: cannot start/stop; table missing 189 | EndCodeCannotStartStopTableMissing uint16 = 0x2401 190 | 191 | // EndCodeUnitErrorMemoryError End code: unit error; memory error 192 | EndCodeUnitErrorMemoryError uint16 = 0x2502 193 | 194 | // EndCodeUnitErrorIOError End code: unit error; IO error 195 | EndCodeUnitErrorIOError uint16 = 0x2503 196 | 197 | // EndCodeUnitErrorTooManyIOPoints End code: unit error; too many IO points 198 | EndCodeUnitErrorTooManyIOPoints uint16 = 0x2504 199 | 200 | // EndCodeUnitErrorCPUBusError End code: unit error; CPU bus error 201 | EndCodeUnitErrorCPUBusError uint16 = 0x2505 202 | 203 | // EndCodeUnitErrorIODuplication End code: unit error; IO duplication 204 | EndCodeUnitErrorIODuplication uint16 = 0x2506 205 | 206 | // EndCodeUnitErrorIOBusError End code: unit error; IO bus error 207 | EndCodeUnitErrorIOBusError uint16 = 0x2507 208 | 209 | // EndCodeUnitErrorSYSMACBUS2Error End code: unit error; SYSMAC BUS/2 error 210 | EndCodeUnitErrorSYSMACBUS2Error uint16 = 0x2509 211 | 212 | // EndCodeUnitErrorCPUBusUnitError End code: unit error; CPU bus unit error 213 | EndCodeUnitErrorCPUBusUnitError uint16 = 0x250a 214 | 215 | // EndCodeUnitErrorSYSMACBusNumberDuplication End code: unit error; SYSMAC bus number duplication 216 | EndCodeUnitErrorSYSMACBusNumberDuplication uint16 = 0x250d 217 | 218 | // EndCodeUnitErrorMemoryStatusError End code: unit error; memory status error 219 | EndCodeUnitErrorMemoryStatusError uint16 = 0x250f 220 | 221 | // EndCodeUnitErrorSYSMACBusTerminatorMissing End code: unit error; SYSMAC bus terminator missing 222 | EndCodeUnitErrorSYSMACBusTerminatorMissing uint16 = 0x2510 223 | 224 | // EndCodeCommandErrorNoProtection End code: command error; no protection 225 | EndCodeCommandErrorNoProtection uint16 = 0x2601 226 | 227 | // EndCodeCommandErrorIncorrectPassword End code: command error; incorrect password 228 | EndCodeCommandErrorIncorrectPassword uint16 = 0x2602 229 | 230 | // EndCodeCommandErrorProtected End code: command error; protected 231 | EndCodeCommandErrorProtected uint16 = 0x2604 232 | 233 | // EndCodeCommandErrorServiceAlreadyExecuting End code: command error; service already executing 234 | EndCodeCommandErrorServiceAlreadyExecuting uint16 = 0x2605 235 | 236 | // EndCodeCommandErrorServiceStopped End code: command error; service stopped 237 | EndCodeCommandErrorServiceStopped uint16 = 0x2606 238 | 239 | // EndCodeCommandErrorNoExecutionRight End code: command error; no execution right 240 | EndCodeCommandErrorNoExecutionRight uint16 = 0x2607 241 | 242 | // EndCodeCommandErrorSettingsNotComplete End code: command error; settings not complete 243 | EndCodeCommandErrorSettingsNotComplete uint16 = 0x2608 244 | 245 | // EndCodeCommandErrorNecessaryItemsNotSet End code: command error; necessary items not set 246 | EndCodeCommandErrorNecessaryItemsNotSet uint16 = 0x2609 247 | 248 | // EndCodeCommandErrorNumberAlreadyDefined End code: command error; number already defined 249 | EndCodeCommandErrorNumberAlreadyDefined uint16 = 0x260a 250 | 251 | // EndCodeCommandErrorErrorWillNotClear End code: command error; error will not clear 252 | EndCodeCommandErrorErrorWillNotClear uint16 = 0x260b 253 | 254 | // EndCodeAccessWriteErrorNoAccessRight End code: access write error; no access right 255 | EndCodeAccessWriteErrorNoAccessRight uint16 = 0x3001 256 | 257 | // EndCodeAbortServiceAborted End code: abort; service aborted 258 | EndCodeAbortServiceAborted uint16 = 0x4001 259 | ) 260 | -------------------------------------------------------------------------------- /fins/error.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Client errors 9 | 10 | type ResponseTimeoutError struct { 11 | duration time.Duration 12 | } 13 | 14 | func (e ResponseTimeoutError) Error() string { 15 | return fmt.Sprintf("Response timeout of %d has been reached", e.duration) 16 | } 17 | 18 | type IncompatibleMemoryAreaError struct { 19 | area byte 20 | } 21 | 22 | func (e IncompatibleMemoryAreaError) Error() string { 23 | return fmt.Sprintf("The memory area is incompatible with the data type to be read: 0x%X", e.area) 24 | } 25 | 26 | // Driver errors 27 | 28 | type BCDBadDigitError struct { 29 | v string 30 | val uint64 31 | } 32 | 33 | func (e BCDBadDigitError) Error() string { 34 | return fmt.Sprintf("Bad digit in BCD decoding: %s = %d", e.v, e.val) 35 | } 36 | 37 | type BCDOverflowError struct {} 38 | 39 | func (e BCDOverflowError) Error() string { 40 | return "Overflow occurred in BCD decoding" 41 | } 42 | -------------------------------------------------------------------------------- /fins/header.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | // Header A FINS frame header 4 | type Header struct { 5 | messageType uint8 6 | responseRequired bool 7 | src finsAddress 8 | dst finsAddress 9 | serviceID byte 10 | gatewayCount uint8 11 | } 12 | 13 | const ( 14 | // MessageTypeCommand Command message type 15 | MessageTypeCommand uint8 = iota 16 | 17 | // MessageTypeResponse Response message type 18 | MessageTypeResponse uint8 = iota 19 | ) 20 | 21 | func defaultHeader(messageType uint8, responseRequired bool, src finsAddress, dst finsAddress, serviceID byte) Header { 22 | h := Header{} 23 | h.messageType = messageType 24 | h.responseRequired = responseRequired 25 | h.gatewayCount = 2 26 | h.src = src 27 | h.dst = dst 28 | h.serviceID = serviceID 29 | return h 30 | } 31 | 32 | func defaultCommandHeader(src finsAddress, dst finsAddress, serviceID byte) Header { 33 | h := defaultHeader(MessageTypeCommand, true, src, dst, serviceID) 34 | return h 35 | } 36 | 37 | func defaultResponseHeader(commandHeader Header) Header { 38 | h := defaultHeader(MessageTypeResponse, false, commandHeader.dst, commandHeader.src, commandHeader.serviceID) 39 | return h 40 | } 41 | -------------------------------------------------------------------------------- /fins/memory_area.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | const ( 4 | // MemoryAreaCIOBit Memory area: CIO area; bit 5 | MemoryAreaCIOBit byte = 0x30 6 | 7 | // MemoryAreaWRBit Memory area: work area; bit 8 | MemoryAreaWRBit byte = 0x31 9 | 10 | // MemoryAreaHRBit Memory area: holding area; bit 11 | MemoryAreaHRBit byte = 0x32 12 | 13 | // MemoryAreaARBit Memory area: axuillary area; bit 14 | MemoryAreaARBit byte = 0x33 15 | 16 | // MemoryAreaCIOWord Memory area: CIO area; word 17 | MemoryAreaCIOWord byte = 0xb0 18 | 19 | // MemoryAreaWRWord Memory area: work area; word 20 | MemoryAreaWRWord byte = 0xb1 21 | 22 | // MemoryAreaHRWord Memory area: holding area; word 23 | MemoryAreaHRWord byte = 0xb2 24 | 25 | // MemoryAreaARWord Memory area: auxillary area; word 26 | MemoryAreaARWord byte = 0xb3 27 | 28 | // MemoryAreaTimerCounterCompletionFlag Memory area: counter completion flag 29 | MemoryAreaTimerCounterCompletionFlag byte = 0x09 30 | 31 | // MemoryAreaTimerCounterPV Memory area: counter PV 32 | MemoryAreaTimerCounterPV byte = 0x89 33 | 34 | // MemoryAreaDMBit Memory area: data area; bit 35 | MemoryAreaDMBit byte = 0x02 36 | 37 | // MemoryAreaDMWord Memory area: data area; word 38 | MemoryAreaDMWord byte = 0x82 39 | 40 | // MemoryAreaTaskBit Memory area: task flags; bit 41 | MemoryAreaTaskBit byte = 0x06 42 | 43 | // MemoryAreaTaskStatus Memory area: task flags; status 44 | MemoryAreaTaskStatus byte = 0x46 45 | 46 | // MemoryAreaIndexRegisterPV Memory area: CIO bit 47 | MemoryAreaIndexRegisterPV byte = 0xdc 48 | 49 | // MemoryAreaDataRegisterPV Memory area: CIO bit 50 | MemoryAreaDataRegisterPV byte = 0xbc 51 | 52 | // MemoryAreaClockPulsesConditionFlagsBit Memory area: CIO bit 53 | MemoryAreaClockPulsesConditionFlagsBit byte = 0x07 54 | ) 55 | -------------------------------------------------------------------------------- /fins/server.go: -------------------------------------------------------------------------------- 1 | package fins 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | "net" 7 | ) 8 | 9 | // Server Omron FINS server (PLC emulator) 10 | type Server struct { 11 | addr Address 12 | conn *net.UDPConn 13 | dmarea []byte 14 | bitdmarea []byte 15 | closed bool 16 | } 17 | 18 | const DM_AREA_SIZE = 32768 19 | 20 | func NewPLCSimulator(plcAddr Address) (*Server, error) { 21 | s := new(Server) 22 | s.addr = plcAddr 23 | s.dmarea = make([]byte, DM_AREA_SIZE) 24 | s.bitdmarea = make([]byte, DM_AREA_SIZE) 25 | 26 | conn, err := net.ListenUDP("udp", plcAddr.udpAddress) 27 | if err != nil { 28 | return nil, err 29 | } 30 | s.conn = conn 31 | 32 | go func() { 33 | var buf [1024]byte 34 | for { 35 | rlen, remote, err := conn.ReadFromUDP(buf[:]) 36 | if rlen > 0 { 37 | req := decodeRequest(buf[:rlen]) 38 | resp := s.handler(req) 39 | 40 | _, err = conn.WriteToUDP(encodeResponse(resp), &net.UDPAddr{IP: remote.IP, Port: remote.Port}) 41 | } 42 | if err != nil { 43 | // do not complain when connection is closed by user 44 | if !s.closed { 45 | log.Fatal("Encountered error in server loop: ", err) 46 | } 47 | break 48 | } 49 | } 50 | }() 51 | 52 | return s, nil 53 | } 54 | 55 | // Works with only DM area, 2 byte integers 56 | func (s *Server) handler(r request) response { 57 | var endCode uint16 58 | data := []byte{} 59 | switch r.commandCode { 60 | case CommandCodeMemoryAreaRead, CommandCodeMemoryAreaWrite: 61 | memAddr := decodeMemoryAddress(r.data[:4]) 62 | ic := binary.BigEndian.Uint16(r.data[4:6]) // Item count 63 | 64 | switch memAddr.memoryArea { 65 | case MemoryAreaDMWord: 66 | 67 | if memAddr.address+ic*2 > DM_AREA_SIZE { // Check address boundary 68 | endCode = EndCodeAddressRangeExceeded 69 | break 70 | } 71 | 72 | if r.commandCode == CommandCodeMemoryAreaRead { //Read command 73 | data = s.dmarea[memAddr.address : memAddr.address+ic*2] 74 | } else { // Write command 75 | copy(s.dmarea[memAddr.address:memAddr.address+ic*2], r.data[6:6+ic*2]) 76 | } 77 | endCode = EndCodeNormalCompletion 78 | 79 | case MemoryAreaDMBit: 80 | if memAddr.address+ic > DM_AREA_SIZE { // Check address boundary 81 | endCode = EndCodeAddressRangeExceeded 82 | break 83 | } 84 | start := memAddr.address + uint16(memAddr.bitOffset) 85 | if r.commandCode == CommandCodeMemoryAreaRead { //Read command 86 | data = s.bitdmarea[start : start+ic] 87 | } else { // Write command 88 | copy(s.bitdmarea[start:start+ic], r.data[6:6+ic]) 89 | } 90 | endCode = EndCodeNormalCompletion 91 | 92 | default: 93 | log.Printf("Memory area is not supported: 0x%04x\n", memAddr.memoryArea) 94 | endCode = EndCodeNotSupportedByModelVersion 95 | } 96 | 97 | default: 98 | log.Printf("Command code is not supported: 0x%04x\n", r.commandCode) 99 | endCode = EndCodeNotSupportedByModelVersion 100 | } 101 | return response{defaultResponseHeader(r.header), r.commandCode, endCode, data} 102 | } 103 | 104 | // Close Closes the FINS server 105 | func (s *Server) Close() { 106 | s.closed = true 107 | s.conn.Close() 108 | } 109 | --------------------------------------------------------------------------------