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