├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── example
├── Makefile
└── main.go
└── fins
├── fins.go
├── fins_driver.go
└── fins_header.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 | example/example
4 | omron-fins-go
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.2
5 | - tip
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 SHIN
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | GO=go
4 | PACKAGE=github.com/hiroeorz/omron-fins-go/fins
5 | GOOS=linux
6 | GOARCH=arm
7 | GOARM=5
8 |
9 | all: get install
10 |
11 | get:
12 | ${GO} get ${PACKAGE}
13 |
14 | install:
15 | ${GO} install ${PACKAGE}
16 |
17 | arm:
18 | GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} ${GO} install ${PACKAGE}
19 |
20 | clean:
21 | rm -f ${GOPATH}/pkg/*/${PACKAGE}.a
22 |
23 | fmt:
24 | go fmt ./...
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # omron-fins-go [](https://travis-ci.org/hiroeorz/omron-fins-go)
2 |
3 | ## About
4 |
5 | This is fins command client written by Go.
6 |
7 | This library support communication to omron PLC from Go application.
8 |
9 | * omron:
10 | * omron PLC:
11 |
12 | ##Install
13 |
14 | ```
15 | $ go get github.com/hiroeorz/omron-fins-go/fins
16 | ```
17 |
18 | ## Usage
19 |
20 | Read DM values sample.
21 |
22 | ```go
23 | package main
24 |
25 | import (
26 | "fmt"
27 | "github.com/hiroeorz/omron-fins-go/fins"
28 | "log"
29 | )
30 |
31 | func main() {
32 | srcAddr := "192.168.0.1:9600" // Host address
33 | dstAddr := "192.168.0.6:9600" // PLC address
34 |
35 | // Start listener at first.
36 | listenChan := fins.Listen(srcAddr)
37 |
38 | // Send ReadDM request (startAddress:100 getCount:10).
39 | vals, err := fins.ReadDM(listenChan, srcAddr, dstAddr, 100, 10)
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | fmt.Println(vals)
45 | // => [12799 24752 12799 24768 12799 24784 12799 24800 12799 24816]
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/example/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | GO=go
4 | GOOS=linux
5 | GOARCH=arm
6 | GOARM=5
7 |
8 | all: build
9 |
10 | build:
11 | ${GO} build
12 |
13 | arm:
14 | GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} ${GO} build
15 |
16 | clean:
17 | rm -f example
18 |
19 | fmt:
20 | go fmt ./...
21 |
--------------------------------------------------------------------------------
/example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/hiroeorz/omron-fins-go/fins"
6 | "log"
7 | )
8 |
9 | func main() {
10 | srcAddr := "192.168.10.30:9600"
11 | dstAddr := "192.168.10.6:9600"
12 | listenChan := fins.Listen(srcAddr)
13 |
14 | vals, err := fins.ReadDM(listenChan, srcAddr, dstAddr, 100, 10)
15 | if err != nil {
16 | log.Fatal(err)
17 | }
18 |
19 | fmt.Println(vals)
20 | }
21 |
--------------------------------------------------------------------------------
/fins/fins.go:
--------------------------------------------------------------------------------
1 | package fins
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "net"
8 | )
9 |
10 | type ReqCommand struct {
11 | ch chan []byte
12 | command []byte
13 | dstAddr string
14 | }
15 |
16 | // ReadIO get uint16 values from PLC DM Areas.
17 | func ReadDM(listenChan chan *ReqCommand,
18 | srcAddr string, dstAddr string, startAddr uint16, readCount uint16) ([]uint16, error) {
19 |
20 | header := newHeader(srcAddr, dstAddr)
21 | command := readDMCom(header, 0x82, startAddr, readCount)
22 |
23 | bytes, err := syncSend(listenChan, srcAddr, dstAddr, command)
24 | if err != nil {
25 | log.Fatal(err)
26 | return []uint16{}, errors.New("fins: send command error")
27 | }
28 |
29 | return parseReadDM(bytes)
30 | }
31 |
32 | // Listen start listenLoop and return channel that used in listenLoop.
33 | func Listen(srcAddr string) chan *ReqCommand {
34 | udpAddress, err := net.ResolveUDPAddr("udp4", srcAddr)
35 | if err != nil {
36 | log.Fatal(err)
37 | panic(fmt.Sprintf("error resolving UDP port: %s\n", srcAddr))
38 | }
39 |
40 | conn, err := net.ListenUDP("udp", udpAddress)
41 | if err != nil {
42 | log.Fatal(err)
43 | panic(fmt.Sprintf("error listening UDP port: %s\n", srcAddr))
44 | }
45 |
46 | listenChan := make(chan *ReqCommand)
47 | go listenLoop(listenChan, conn)
48 | return listenChan
49 | }
50 |
51 | // listenLoop wait request through a channel and send request and receive response.
52 | func listenLoop(listenChan chan *ReqCommand, conn *net.UDPConn) {
53 | defer conn.Close()
54 | for {
55 | reqCom := <-listenChan
56 |
57 | n, err := send(reqCom.command, reqCom.dstAddr)
58 | if err != nil || n == 0 {
59 | log.Println("cannot write reqest: ", err, " bytes:", n)
60 | continue
61 | }
62 |
63 | var buf []byte = make([]byte, 1500)
64 | n, address, err := conn.ReadFromUDP(buf)
65 |
66 | if err != nil {
67 | log.Fatal(err)
68 | }
69 |
70 | if address != nil && n > 0 {
71 | reqCom.ch <- buf[0:n]
72 | } else {
73 | log.Println("cannot read reqest: ", err, " bytes:", n)
74 | reqCom.ch <- nil
75 | }
76 | }
77 | }
78 |
79 | // syncSend send binary command to PLC and wait response.
80 | func syncSend(listenChan chan *ReqCommand, srcAddr string, dstAddr string, command []byte) ([]byte, error) {
81 | reqCom := &ReqCommand{make(chan []byte), command, dstAddr}
82 | listenChan <- reqCom
83 | bytes := <-reqCom.ch
84 | return bytes, nil
85 | }
86 |
87 | // send is send command data to PLC.
88 | func send(command []byte, dstAddr string) (int, error) {
89 | conn, err := net.Dial("udp", dstAddr)
90 | if err != nil {
91 | return 0, err
92 | }
93 |
94 | defer conn.Close()
95 | return conn.Write(command)
96 | }
97 |
--------------------------------------------------------------------------------
/fins/fins_driver.go:
--------------------------------------------------------------------------------
1 | package fins
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | func readDMCom(header *FinsHeader, ioFacility uint8, startAddress uint16,
9 | readCount uint16) []byte {
10 |
11 | if ioFacility != 0x82 && ioFacility != 0x02 {
12 | panic(fmt.Sprintf("invalid ioFacility: %d\n", ioFacility))
13 | }
14 |
15 | var addressBit byte = 0
16 |
17 | headerBytes := header.Format()
18 | addressLower := byte(startAddress)
19 | addressUpper := byte(startAddress >> 8)
20 | countLower := byte(readCount)
21 | countUpper := byte(readCount >> 8)
22 |
23 | code := []byte{1, 1}
24 | paramsBytes := []byte{
25 | ioFacility,
26 | addressUpper, addressLower,
27 | addressBit,
28 | countUpper, countLower}
29 |
30 | bytes1 := append(headerBytes, code...)
31 | bytes2 := append(bytes1, paramsBytes...)
32 | return bytes2
33 | }
34 |
35 | func parseReadDM(bytes []byte) ([]uint16, error) {
36 | err := validate(bytes)
37 | if err != nil {
38 | return []uint16{}, err
39 | }
40 |
41 | body := bytes[14:]
42 | var result []uint16
43 |
44 | for i := 0; i < len(body); i += 2 {
45 | upper := (uint16(body[i]) << 8)
46 | lower := uint16(body[i + 1])
47 | result = append(result, (upper | lower))
48 | }
49 |
50 | return result, nil
51 | }
52 |
53 | func validate(bytes []byte) error {
54 | finishCode1 := bytes[12]
55 | finishCode2 := bytes[13]
56 |
57 | if finishCode1 != 0 || finishCode1 != 0 {
58 | msg := fmt.Sprintln("failure code:", finishCode1, ":", finishCode2)
59 | return errors.New(msg)
60 | }
61 |
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/fins/fins_header.go:
--------------------------------------------------------------------------------
1 | package fins
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | type FinsHeader struct {
11 | icf byte
12 | rsv byte
13 | gct byte
14 | dna byte
15 | da1 byte
16 | da2 byte
17 | sna byte
18 | sa1 byte
19 | sa2 byte
20 | sid byte
21 | }
22 |
23 | func newHeader(srcAddr string, dstAddr string) *FinsHeader {
24 | h := new(FinsHeader)
25 | h.icf = icf()
26 | h.rsv = rsv()
27 | h.gct = gct()
28 | h.SetDstNetwork(0)
29 | h.SetDstNode(getAddrNode(dstAddr))
30 | h.SetDstUnit(0)
31 | h.SetSrcNetwork(0)
32 | h.SetSrcNode(getAddrNode(srcAddr))
33 | h.SetSrcUnit(0)
34 | h.SetIdentifier(0)
35 | return h
36 | }
37 |
38 | func getAddrNode(addr string) byte {
39 | addrPort := strings.Split(addr, ":")
40 | if len(addrPort) != 2 {
41 | panic(fmt.Sprintln("invalid PLC address:", addr))
42 | }
43 |
44 | parts := strings.Split(addrPort[0], ".")
45 | if len(parts) != 4 {
46 | panic(fmt.Sprintln("invalid PLC address:", addr))
47 | }
48 |
49 | node, err := strconv.ParseUint(parts[3], 10, 8)
50 | if err != nil {
51 | log.Fatal(err)
52 | panic(fmt.Sprintln("invalid PLC address:", addr))
53 | }
54 |
55 | return byte(node)
56 | }
57 |
58 | func (f *FinsHeader) Format() []byte {
59 |
60 | return []byte{
61 | f.icf, f.rsv, f.gct,
62 | f.dna, f.da1, f.da2,
63 | f.sna, f.sa1, f.sa2,
64 | f.sid}
65 | }
66 |
67 | func icf() byte {
68 | return 1 << 7
69 | }
70 |
71 | func rsv() byte {
72 | return 0x00
73 | }
74 |
75 | func gct() byte {
76 | return 0x02
77 | }
78 |
79 | func (f *FinsHeader) SetDstNetwork(network byte) {
80 | if network == 0 || (1 <= network && network <= 0x7F) {
81 | f.dna = network
82 | return
83 | }
84 | panic(fmt.Sprintf("invalid network at dna: %d", network))
85 | }
86 |
87 | func (f *FinsHeader) SetDstNode(node byte) {
88 | if node == 0 || node == 0xFF || (0 <= node && node <= 0x20) {
89 | f.da1 = node
90 | return
91 | }
92 | panic(fmt.Sprintf("invalid network at da1: %d", node))
93 | }
94 |
95 | func (f *FinsHeader) SetDstUnit(unitNo byte) {
96 | if unitNo == 0 || unitNo == 0xFE || unitNo == 0xE1 ||
97 | (1 <= unitNo && unitNo <= 0x7F) {
98 | f.da2 = unitNo
99 | return
100 | }
101 | panic(fmt.Sprintf("invalid unitNo at da2: %d", unitNo))
102 | }
103 |
104 | func (f *FinsHeader) SetSrcNetwork(srcNetwork byte) {
105 | if srcNetwork == 0 || (1 <= srcNetwork && srcNetwork <= 0x7F) {
106 | f.sna = srcNetwork
107 | return
108 | }
109 | panic(fmt.Sprintf("invalid srcNetwork at sna: %d", srcNetwork))
110 | }
111 |
112 | func (f *FinsHeader) SetSrcNode(node byte) {
113 | if node == 0 || (1 <= node && node <= 0x20) {
114 | f.sa1 = node
115 | return
116 | }
117 | panic(fmt.Sprintf("invalid node at sa1: %d", node))
118 | }
119 |
120 | func (f *FinsHeader) SetSrcUnit(unitNo byte) {
121 | if unitNo == 0 || (10 <= unitNo && unitNo <= 0x1F) {
122 | f.sa2 = unitNo
123 | return
124 | }
125 | panic(fmt.Sprintf("invalid unitNo at sa2: %d", unitNo))
126 | }
127 |
128 | func (f *FinsHeader) SetIdentifier(identifier byte) {
129 | if 0 <= identifier && identifier <= 0xFF {
130 | f.sid = identifier
131 | return
132 | }
133 | panic(fmt.Sprintf("invalid identifier at sid: %d", identifier))
134 | }
135 |
--------------------------------------------------------------------------------