├── .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 [![Build Status](https://travis-ci.org/hiroeorz/omron-fins-go.svg?branch=master)](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 | --------------------------------------------------------------------------------