diff --git a/.gitignore b/.gitignore
index 5984917..7092d5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,13 @@
# Binaries for programs and plugins
*.exe
+*.exe~
*.dll
*.so
*.dylib
+example/debug
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
-
-# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
-.glide/
-
-*.idea
\ No newline at end of file
diff --git a/README.md b/README.md
index 90e7475..60e7bcb 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
-# gofins
+# GoFINS
[![Build Status](https://travis-ci.org/l1va/gofins.svg?branch=master)](https://travis-ci.org/l1va/gofins)
-
This is fins command client written by Go.
The library support communication to omron PLC from Go application.
@@ -10,10 +9,10 @@ The library support communication to omron PLC from Go application.
Ideas were taken from https://github.com/hiroeorz/omron-fins-go and https://github.com/patrick--/node-omron-fins
Library was tested with Omron PLC NJ501-1300. Mean time of the cycle request-response is 4ms.
+Additional work in the siyka-au repository was tested against a CP1L-EM.
Feel free to ask questions, raise issues and make pull requests!
-
PS. Build is ok but Travis is randomly failing to run tests with go 1.10
(with earlier go versions tests are passed too): [travis](https://travis-ci.org/l1va/gofins)
```
diff --git a/example/main.go b/example/main.go
index b837386..1f5a101 100644
--- a/example/main.go
+++ b/example/main.go
@@ -2,47 +2,69 @@ package main
import (
"fmt"
- "log"
- "github.com/l1va/gofins/fins"
+ "net"
+
+ "github.com/siyka-au/gofins/fins"
)
func main() {
- plcAddr := "192.168.250.1:9600"
-
- c := fins.NewClient(plcAddr)
- defer c.CloseConnection()
- err := c.WriteDAsync(100, []uint16{5, 4, 3, 2, 1}, func(fins.Response) {
- log.Println("writing done!")
- })
- if err != nil {
- log.Println("writing request failed:", err)
+ localClientAddr := &net.UDPAddr{
+ IP: net.ParseIP("192.168.250.2"),
+ Port: 9600,
}
-
- err = c.ReadDAsync(100, 5, func(r fins.Response) {
- log.Println("readed values: ", r.Data)
- })
- if err != nil {
- log.Println("reading request failed:", err)
+ // localServerAddr := &net.UDPAddr{
+ // IP: net.ParseIP("192.168.250.3"),
+ // Port: 9600,
+ // }
+ plcAddr := &net.UDPAddr{
+ IP: net.ParseIP("192.168.250.10"),
+ Port: 9600,
}
- for i := 0; i < 10; i += 1 {
- t := uint16(i * 5)
- err := c.WriteD(200+t, []uint16{t, t + 1, t + 2, t + 3, t + 4})
- if err != nil {
- log.Fatal(err)
- }
+ c, e := fins.NewClient(localClientAddr, plcAddr, fins.NewAddress(0, 10, 0), fins.NewAddress(0, 2, 0))
+ defer c.Close()
+ if e != nil {
+ panic(e)
}
- err = c.WriteDNoResponse(200, []uint16{5, 4, 3, 2, 1})
- if err != nil {
- log.Println("writing request without response failed:", err)
- }
+ // s, e := fins.NewServer(localServerAddr, fins.NewAddress(0, 3, 0))
+ // if e != nil {
+ // panic(e)
+ // }
- vals, err := c.ReadD(200, 50)
- if err != nil {
- log.Fatal(err)
- }
+ defer c.Close()
+ // defer s.Close()
+
+ z, _ := c.ReadWords(fins.MemoryAreaDMWord, 10000, 500)
+ fmt.Println(z)
- fmt.Println(vals)
+ // s, _ := c.ReadString(fins.MemoryAreaDMWord, 10000, 10)
+ // fmt.Println(s)
+ // fmt.Println(len(s))
+
+ // b, _ := c.ReadBits(fins.MemoryAreaDMWord, 10473, 2, 1)
+ // fmt.Println(b)
+ // fmt.Println(len(b))
+
+ // c.WriteWords(fins.MemoryAreaDMWord, 24000, []uint16{z[0] + 1, z[1] - 1})
+ // c.WriteBits(fins.MemoryAreaDMBit, 24002, 0, []bool{false, false, false, true,
+ // true, false, false, true,
+ // false, false, false, false,
+ // true, true, true, true})
+ // c.SetBit(fins.MemoryAreaDMBit, 24003, 1)
+ // c.ResetBit(fins.MemoryAreaDMBit, 24003, 0)
+ // c.ToggleBit(fins.MemoryAreaDMBit, 24003, 2)
+
+ // cron := cron.New()
+ // s := rasc.NewShelter()
+ // cron.AddFunc("*/5 * * * * *", func() {
+ // t, _ := c.ReadClock()
+ // fmt.Printf("Setting PLC time to: %s\n", t.Format(time.RFC3339))
+ // c.WriteString(fins.MemoryAreaDMWord, 10000, 10, t.Format(time.RFC3339))
+ // })
+ // cron.Start()
+
+ for {
+ }
}
diff --git a/fins/address.go b/fins/address.go
new file mode 100644
index 0000000..5de2662
--- /dev/null
+++ b/fins/address.go
@@ -0,0 +1,28 @@
+package fins
+
+// Address A FINS device address
+type Address struct {
+ network byte
+ node byte
+ unit byte
+}
+
+func NewAddress(network byte, node byte, unit byte) *Address {
+ a := new(Address)
+ a.network = network
+ a.node = node
+ a.unit = unit
+ return a
+}
+
+func (a *Address) Network() byte {
+ return a.network
+}
+
+func (a *Address) Node() byte {
+ return a.node
+}
+
+func (a *Address) Unit() byte {
+ return a.unit
+}
diff --git a/fins/client.go b/fins/client.go
index 6804138..adc70f9 100644
--- a/fins/client.go
+++ b/fins/client.go
@@ -1,131 +1,292 @@
package fins
import (
+ "bufio"
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
"log"
"net"
- "fmt"
- "bufio"
"sync"
+ "time"
)
+// Client Omron FINS client
type Client struct {
- conn net.Conn
- resp []chan Response
+ conn *net.UDPConn
+ resp []chan Frame
sync.Mutex
- sid byte
-}
-
-type Response struct {
- sid byte
- Data []uint16
+ dst *Address
+ src *Address
+ sid byte
}
-func NewClient(plcAddr string) *Client {
+// NewClient creates a new Omron FINS client
+func NewClient(localAddr, plcAddr *net.UDPAddr, dst *Address, src *Address) (*Client, error) {
c := new(Client)
- conn, err := net.Dial("udp", plcAddr)
+ c.dst = dst
+ c.src = src
+
+ conn, err := net.DialUDP("udp", localAddr, plcAddr)
if err != nil {
- log.Fatal(err)
- panic(fmt.Sprintf("error resolving UDP port: %s\n", plcAddr))
+ return nil, err
}
c.conn = conn
- c.resp = make([]chan Response, 256) //storage for all responses, sid is byte - only 256 values
- go c.listenLoop()
-
- return c
+ c.resp = make([]chan Frame, 256) //storage for all responses, sid is byte - only 256 values
+ go c.listenLoop()
+ return c, nil
}
-func (c *Client) CloseConnection() {
+// Close Closes an Omron FINS connection
+func (c *Client) Close() {
c.conn.Close()
}
-func (c *Client) incrementSid() byte {
- c.Lock() //thread-safe sid incrementation
- c.sid += 1
- sid := c.sid
- c.Unlock()
- c.resp[sid] = make(chan Response) //clearing cell of storage for new response
- return sid
+// ReadWords Reads words from the PLC data area
+func (c *Client) ReadWords(memoryArea byte, address uint16, readCount uint16) ([]uint16, error) {
+ if checkIsWordMemoryArea(memoryArea) == false {
+ return nil, ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ command := readCommand(NewIOAddress(memoryArea, address), readCount)
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return nil, e
+ }
+
+ data := make([]uint16, readCount, readCount)
+ for i := 0; i < int(readCount); i++ {
+ data[i] = binary.BigEndian.Uint16(r.Data()[i*2 : i*2+2])
+ }
+
+ return data, nil
}
-func (c *Client) ReadD(startAddr uint16, readCount uint16) ([]uint16, error) {
- sid := c.incrementSid()
- cmd := readDCommand(defaultHeader(sid), startAddr, readCount)
- return c.read(sid, cmd)
+// ReadBytes Reads a string from the PLC data area
+func (c *Client) ReadBytes(memoryArea byte, address uint16, readCount uint16) ([]byte, error) {
+ if checkIsWordMemoryArea(memoryArea) == false {
+ return nil, ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ command := readCommand(NewIOAddress(memoryArea, address), readCount)
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return nil, e
+ }
+
+ return r.Data(), nil
}
-func (c *Client) ReadW(startAddr uint16, readCount uint16) ([]uint16, error) {
- sid := c.incrementSid()
- cmd := readWCommand(defaultHeader(sid), startAddr, readCount)
- return c.read(sid, cmd)
+// ReadString Reads a string from the PLC data area
+func (c *Client) ReadString(memoryArea byte, address uint16, readCount uint16) (*string, error) {
+ data, e := c.ReadBytes(memoryArea, address, readCount)
+ if e != nil {
+ return nil, e
+ }
+ n := bytes.Index(data, []byte{0})
+ s := string(data[:n])
+ return &s, nil
}
-func (c *Client) read(sid byte, cmd []byte) ([]uint16, error) {
- _, err := c.conn.Write(cmd)
- if err != nil {
- return nil, err
+// ReadBits Reads bits from the PLC data area
+func (c *Client) ReadBits(memoryArea byte, address uint16, bitOffset byte, readCount uint16) ([]bool, error) {
+ if checkIsBitMemoryArea(memoryArea) == false {
+ return nil, ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ command := readCommand(NewIOAddressWithBitOffset(memoryArea, address, bitOffset), readCount)
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return nil, e
+ }
+
+ data := make([]bool, readCount, readCount)
+ for i := 0; i < int(readCount); i++ {
+ data[i] = r.Data()[i]&0x01 > 0
}
- ans := <-c.resp[sid]
- return ans.Data, nil
+ return data, nil
}
-func (c *Client) WriteD(startAddr uint16, data []uint16) error {
- sid := c.incrementSid()
- cmd := writeDCommand(defaultHeader(sid), startAddr, data)
- return c.write(sid, cmd)
+// ReadClock Reads the PLC clock
+func (c *Client) ReadClock() (*time.Time, error) {
+ header := c.nextHeader()
+ command := NewCommand(CommandCodeClockRead, []byte{})
+ responseFrame, e := c.sendCommand(header, command)
+ if e != nil {
+ return nil, e
+ }
+ year, _ := decodeBCD(responseFrame.Data()[0:1])
+ if year < 50 {
+ year += 2000
+ } else {
+ year += 1900
+ }
+ month, _ := decodeBCD(responseFrame.Data()[1:2])
+ day, _ := decodeBCD(responseFrame.Data()[2:3])
+ hour, _ := decodeBCD(responseFrame.Data()[3:4])
+ minute, _ := decodeBCD(responseFrame.Data()[4:5])
+ second, _ := decodeBCD(responseFrame.Data()[5:6])
+
+ t := time.Date(
+ int(year), time.Month(month), int(day), int(hour), int(minute), int(second),
+ 0, // nanosecond
+ time.Local,
+ )
+
+ return &t, nil
}
-func (c *Client) WriteW(startAddr uint16, data []uint16) error {
- sid := c.incrementSid()
- cmd := writeWCommand(defaultHeader(sid), startAddr, data)
- return c.write(sid, cmd)
+// WriteWords Writes words to the PLC data area
+func (c *Client) WriteWords(memoryArea byte, address uint16, data []uint16) error {
+ if checkIsWordMemoryArea(memoryArea) == false {
+ return ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ l := uint16(len(data))
+ bytes := make([]byte, 2*l, 2*l)
+ for i := 0; i < int(l); i++ {
+ binary.BigEndian.PutUint16(bytes[i*2:i*2+2], data[i])
+ }
+ command := writeCommand(NewIOAddress(memoryArea, address), l, bytes)
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return e
+ }
+ if r.EndCode() != EndCodeNormalCompletion {
+ return fmt.Errorf("Error reported by destination, end code 0x%x", r.EndCode)
+ }
+
+ return nil
}
-func (c *Client) write(sid byte, cmd []byte) error {
- _, err := c.conn.Write(cmd)
- if err != nil {
- return err
+// WriteString Writes a string to the PLC data area
+func (c *Client) WriteString(memoryArea byte, address uint16, itemCount uint16, s string) error {
+ if checkIsWordMemoryArea(memoryArea) == false {
+ return ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ bytes := make([]byte, 2*itemCount, 2*itemCount)
+ copy(bytes, s)
+ command := writeCommand(NewIOAddress(memoryArea, address), itemCount, bytes)
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return e
+ }
+ if r.EndCode() != EndCodeNormalCompletion {
+ return fmt.Errorf("Error reported by destination, end code 0x%x", r.EndCode)
}
- <-c.resp[sid]
return nil
}
-func (c *Client) ReadDAsync(startAddr uint16, readCount uint16, callback func(resp Response)) error {
- sid := c.incrementSid()
- cmd := readDCommand(defaultHeader(sid), startAddr, readCount)
- return c.asyncCommand(sid, cmd, callback)
+// WriteBits Writes bits to the PLC data area
+func (c *Client) WriteBits(memoryArea byte, address uint16, bitOffset byte, data []bool) error {
+ if checkIsBitMemoryArea(memoryArea) == false {
+ return ErrIncompatibleMemoryArea
+ }
+ header := c.nextHeader()
+ l := uint16(len(data))
+ bytes := make([]byte, 0, l)
+ var d byte
+ for i := 0; i < int(l); i++ {
+ if data[i] {
+ d = 0x01
+ } else {
+ d = 0x00
+ }
+ bytes = append(bytes, d)
+ }
+ command := writeCommand(NewIOAddressWithBitOffset(memoryArea, address, bitOffset), l, bytes)
+
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return e
+ }
+ if r.EndCode() != EndCodeNormalCompletion {
+ return fmt.Errorf("Error reported by destination, end code 0x%x", r.EndCode)
+ }
+
+ return nil
}
-func (c *Client) WriteDAsync(startAddr uint16, data []uint16, callback func(resp Response)) error {
- sid := c.incrementSid()
- cmd := writeDCommand(defaultHeader(sid), startAddr, data)
- return c.asyncCommand(sid, cmd, callback)
+// SetBit Sets a bit in the PLC data area
+func (c *Client) SetBit(memoryArea byte, address uint16, bitOffset byte) error {
+ return c.bitTwiddle(memoryArea, address, bitOffset, 0x01)
}
-func (c *Client) asyncCommand(sid byte, cmd []byte, callback func(resp Response)) error {
- _, err := c.conn.Write(cmd)
- if err != nil {
- return err
+// ResetBit Resets a bit in the PLC data area
+func (c *Client) ResetBit(memoryArea byte, address uint16, bitOffset byte) error {
+ return c.bitTwiddle(memoryArea, address, bitOffset, 0x00)
+}
+
+// ToggleBit Toggles a bit in the PLC data area
+func (c *Client) ToggleBit(memoryArea byte, address uint16, bitOffset byte) error {
+ b, e := c.ReadBits(memoryArea, address, bitOffset, 1)
+ if e != nil {
+ return e
}
- asyncResponse(c.resp[sid], callback)
- return nil
+ var t byte
+ if b[0] {
+ t = 0x00
+ } else {
+ t = 0x01
+ }
+ return c.bitTwiddle(memoryArea, address, bitOffset, t)
}
-func asyncResponse(ch chan Response, callback func(r Response)) {
- if callback != nil {
- go func(ch chan Response, callback func(r Response)) {
- ans := <-ch
- callback(ans)
- }(ch, callback)
+func (c *Client) bitTwiddle(memoryArea byte, address uint16, bitOffset byte, value byte) error {
+ if checkIsBitMemoryArea(memoryArea) == false {
+ return ErrIncompatibleMemoryArea
}
+ header := c.nextHeader()
+ command := writeCommand(NewIOAddressWithBitOffset(memoryArea, address, bitOffset), 1, []byte{value})
+
+ r, e := c.sendCommand(header, command)
+ if e != nil {
+ return e
+ }
+ if r.EndCode() != EndCodeNormalCompletion {
+ return fmt.Errorf("Error reported by destination, end code 0x%x", r.EndCode)
+ }
+
+ return nil
}
-func (c *Client) WriteDNoResponse(startAddr uint16, data []uint16) error {
+// ErrIncompatibleMemoryArea Error when the memory area is incompatible with the data type to be read
+var ErrIncompatibleMemoryArea = errors.New("The memory area is incompatible with the data type to be read")
+
+func (c *Client) nextHeader() *Header {
sid := c.incrementSid()
- cmd := writeDCommand(newHeaderNoResponse(sid), startAddr, data)
- return c.asyncCommand(sid, cmd, nil)
+ header := defaultCommandHeader(c.dst, c.src, sid)
+ return header
+}
+
+func (c *Client) incrementSid() byte {
+ c.Lock() //thread-safe sid incrementation
+ c.sid++
+ sid := c.sid
+ c.Unlock()
+ c.resp[sid] = make(chan Frame) //clearing cell of storage for new response
+ return sid
+}
+
+func (c *Client) sendCommand(header *Header, payload Payload) (*Response, error) {
+ bytes := encodeFrame(NewFrame(header, payload))
+ _, err := (*c.conn).Write(bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ responseFrame := <-c.resp[header.ServiceID()]
+ p := responseFrame.Payload()
+ response := NewResponse(
+ p.CommandCode(),
+ binary.BigEndian.Uint16(p.Data()[0:2]),
+ p.Data()[2:])
+ return response, nil
}
func (c *Client) listenLoop() {
@@ -137,14 +298,64 @@ func (c *Client) listenLoop() {
}
if n > 0 {
- ans, err := parseResponse(buf[0:n])
+ ans := decodeFrame(buf[0:n])
if err != nil {
log.Println("failed to parse response: ", err, " \nresponse: ", buf[0:n])
} else {
- c.resp[ans.sid] <- *ans
+ c.resp[ans.Header().ServiceID()] <- *ans
}
} else {
- log.Println("cannot read response: ", buf)
+ log.Println("Cannot read response: ", buf)
}
}
}
+
+func checkIsWordMemoryArea(memoryArea byte) bool {
+ if memoryArea == MemoryAreaDMWord ||
+ memoryArea == MemoryAreaARWord ||
+ memoryArea == MemoryAreaHRWord {
+ return true
+ }
+ return false
+}
+
+func checkIsBitMemoryArea(memoryArea byte) bool {
+ if memoryArea == MemoryAreaDMBit ||
+ memoryArea == MemoryAreaARBit ||
+ memoryArea == MemoryAreaHRBit {
+ return true
+ }
+ return false
+}
+
+// @ToDo Asynchronous functions
+// ReadDataAsync reads from the PLC data area asynchronously
+// func (c *Client) ReadDataAsync(startAddr uint16, readCount uint16, callback func(resp response)) error {
+// sid := c.incrementSid()
+// cmd := readDCommand(defaultHeader(c.dst, c.src, sid), startAddr, readCount)
+// return c.asyncCommand(sid, cmd, callback)
+// }
+
+// WriteDataAsync writes to the PLC data area asynchronously
+// func (c *Client) WriteDataAsync(startAddr uint16, data []uint16, callback func(resp response)) error {
+// sid := c.incrementSid()
+// cmd := writeDCommand(defaultHeader(c.dst, c.src, sid), startAddr, data)
+// return c.asyncCommand(sid, cmd, callback)
+// }
+// func (c *Client) asyncCommand(sid byte, cmd []byte, callback func(resp response)) error {
+// _, err := c.conn.Write(cmd)
+// if err != nil {
+// return err
+// }
+// asyncResponse(c.resp[sid], callback)
+// return nil
+// }
+
+// func asyncResponse(ch chan response, callback func(r response)) {
+// if callback != nil {
+// go func(ch chan response, callback func(r response)) {
+// ans := <-ch
+// callback(ans)
+// }(ch, callback)
+// }
+// }
diff --git a/fins/client_test.go b/fins/client_test.go
index 982a175..cf530ef 100644
--- a/fins/client_test.go
+++ b/fins/client_test.go
@@ -1,12 +1,13 @@
package fins
import (
- "testing"
+ "fmt"
"log"
"net"
- "github.com/stretchr/testify/assert"
"strconv"
- "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestFinsClient(t *testing.T) {
@@ -51,6 +52,7 @@ func makeWriteAnswer(sid byte, respNeeded bool) response {
ans[9] = sid
return response{data: ans, needed: respNeeded}
}
+
func makeReadAnswer(sid byte, data []uint16) response {
ans := make([]byte, 14)
ans[9] = sid
diff --git a/fins/command.go b/fins/command.go
new file mode 100644
index 0000000..b636a5e
--- /dev/null
+++ b/fins/command.go
@@ -0,0 +1,13 @@
+package fins
+
+// Command A FINS command
+type Command struct {
+ payloadImpl
+}
+
+func NewCommand(commandCode uint16, data []byte) *Command {
+ c := new(Command)
+ c.commandCode = commandCode
+ c.data = data
+ return c
+}
diff --git a/fins/command_code.go b/fins/command_code.go
new file mode 100644
index 0000000..4b906f2
--- /dev/null
+++ b/fins/command_code.go
@@ -0,0 +1,138 @@
+package fins
+
+const (
+ // CommandCodeMemoryAreaRead Command code: IO memory area read
+ CommandCodeMemoryAreaRead uint16 = 0x0101
+
+ // CommandCodeMemoryAreaWrite Command code: IO memory area write
+ CommandCodeMemoryAreaWrite uint16 = 0x0102
+
+ // CommandCodeMemoryAreaFill Command code: IO memory area fill
+ CommandCodeMemoryAreaFill uint16 = 0x0103
+
+ // CommandCodeMultipleMemoryAreaRead Command code: IO memory area multiple read
+ CommandCodeMultipleMemoryAreaRead uint16 = 0x0104
+
+ // CommandCodeMemoryAreaTransfer Command code: IO memory area transfer
+ CommandCodeMemoryAreaTransfer uint16 = 0x0105
+
+ // CommandCodeParameterAreaRead Command code: Parameter area read
+ CommandCodeParameterAreaRead uint16 = 0x0201
+
+ // CommandCodeParameterAreaWrite Command code: Parameter area write
+ CommandCodeParameterAreaWrite uint16 = 0x0202
+
+ // CommandCodeParameterAreaClear Command code: Parameter area clear
+ CommandCodeParameterAreaClear uint16 = 0x0203
+
+ // CommandCodeProgramAreaRead Command code: Program area read
+ CommandCodeProgramAreaRead uint16 = 0x0301
+
+ // CommandCodeProgramAreaWrite Command code: Program area write
+ CommandCodeProgramAreaWrite uint16 = 0x0302
+
+ // CommandCodeProgramAreaClear Command code: Program area clear
+ CommandCodeProgramAreaClear uint16 = 0x0303
+
+ // CommandCodeRun Command code: Set operating mode to run
+ CommandCodeRun uint16 = 0x0401
+
+ // CommandCodeStop Command code: Set operating mode to stop
+ CommandCodeStop uint16 = 0x0402
+
+ // CommandCodeCPUUnitDataRead Command code: CPU unit data read
+ CommandCodeCPUUnitDataRead uint16 = 0x0501
+
+ // CommandCodeConnectionDataRead Command code: connection data read
+ CommandCodeConnectionDataRead uint16 = 0x0502
+
+ // CommandCodeCPUUnitStatusRead Command code: CPU unit status read
+ CommandCodeCPUUnitStatusRead uint16 = 0x0601
+
+ // CommandCodeCycleTimeRead Command code: cycle time read
+ CommandCodeCycleTimeRead uint16 = 0x0620
+
+ // CommandCodeClockRead Command code: clock read
+ CommandCodeClockRead uint16 = 0x701
+
+ // CommandCodeClockWrite Command code: clock write
+ CommandCodeClockWrite uint16 = 0x702
+
+ // CommandCodeMessageReadClear Command code: message read/clear
+ CommandCodeMessageReadClear uint16 = 0x0920
+
+ // CommandCodeAccessRightAcquire Command code: access right acquire
+ CommandCodeAccessRightAcquire uint16 = 0x0c01
+
+ // CommandCodeAccessRightForcedAcquire Command code: accress right forced acquire
+ CommandCodeAccessRightForcedAcquire uint16 = 0x0c02
+
+ // CommandCodeAccessRightRelease Command code: access right release
+ CommandCodeAccessRightRelease uint16 = 0x0c03
+
+ // CommandCodeErrorClear Command code: error clear
+ CommandCodeErrorClear uint16 = 0x2101
+
+ // CommandCodeErrorLogRead Command code: error log read
+ CommandCodeErrorLogRead uint16 = 0x2102
+
+ // CommandCodeErrorLogClear Command code: error log clear
+ CommandCodeErrorLogClear uint16 = 0x2103
+
+ // CommandCodeFINSWriteAccessLogRead Command code: FINS write access log read
+ CommandCodeFINSWriteAccessLogRead uint16 = 0x2140
+
+ // CommandCodeFINSWriteAccessLogWrite Command code: FINS write access log write
+ CommandCodeFINSWriteAccessLogWrite uint16 = 0x2141
+
+ // CommandCodeFileNameRead Command code: file name read
+ CommandCodeFileNameRead uint16 = 0x2101
+
+ // CommandCodeSingleFileRead Command code: file read
+ CommandCodeSingleFileRead uint16 = 0x2102
+
+ // CommandCodeSingleFileWrite Command code: file write
+ CommandCodeSingleFileWrite uint16 = 0x2103
+
+ // CommandCodeFileMemoryFormat Command code: file memory format
+ CommandCodeFileMemoryFormat uint16 = 0x2104
+
+ // CommandCodeFileDelete Command code: file delete
+ CommandCodeFileDelete uint16 = 0x2105
+
+ // CommandCodeFileCopy Command code: file copy
+ CommandCodeFileCopy uint16 = 0x2107
+
+ // CommandCodeFileNameChange Command code: file name change
+ CommandCodeFileNameChange uint16 = 0x2108
+
+ // CommandCodeMemoryAreaFileTransfer Command code: memory area file transfer
+ CommandCodeMemoryAreaFileTransfer uint16 = 0x210a
+
+ // CommandCodeParameterAreaFileTransfer Command code: parameter area file transfer
+ CommandCodeParameterAreaFileTransfer uint16 = 0x210b
+
+ // CommandCodeProgramAreaFileTransfer Command code: program area file transfer
+ CommandCodeProgramAreaFileTransfer uint16 = 0x210b
+
+ // CommandCodeDirectoryCreateDelete Command code: directory create/delete
+ CommandCodeDirectoryCreateDelete uint16 = 0x2115
+
+ // CommandCodeMemoryCassetteTransfer Command code: memory cassette transfer (CP1H and CP1L CPU units only)
+ CommandCodeMemoryCassetteTransfer uint16 = 0x2120
+
+ // CommandCodeForcedSetReset Command code: forced set/reset
+ CommandCodeForcedSetReset uint16 = 0x2301
+
+ // CommandCodeForcedSetResetCancel Command code: forced set/reset cancel
+ CommandCodeForcedSetResetCancel uint16 = 0x2302
+
+ // CommandCodeConvertToCompoWayFCommand Command code: convert to CompoWay/F command
+ CommandCodeConvertToCompoWayFCommand uint16 = 0x2803
+
+ // CommandCodeConvertToModbusRTUCommand Command code: convert to Modbus-RTU command
+ CommandCodeConvertToModbusRTUCommand uint16 = 0x2804
+
+ // CommandCodeConvertToModbusASCIICommand Command code: convert to Modbus-ASCII command
+ CommandCodeConvertToModbusASCIICommand uint16 = 0x2805
+)
diff --git a/fins/driver.go b/fins/driver.go
index 1d14210..d92f929 100644
--- a/fins/driver.go
+++ b/fins/driver.go
@@ -1,119 +1,183 @@
package fins
import (
- "fmt"
+ "encoding/binary"
"errors"
)
-/*
-module.exports.Commands = {
- CONTROLLER_STATUS_READ : [0x06,0x01],
- MEMORY_AREA_READ : [0x01,0x01],
- MEMORY_AREA_WRITE : [0x01,0x02],
- MEMORY_AREA_FILL : [0x01,0x03],
- RUN : [0x04,0x01],
- STOP : [0x04,0x02]
-};
-module.exports.MemoryAreas = {
- 'E' : 0xA0,//Extended Memories
- 'C' : 0xB0,//CIO
- 'W' : 0xB1,//Work Area
- 'H' : 0xB2,//Holding Bit
- 'A' : 0xB3,//Auxiliary Bit
- 'D' : 0x82//Data Memories
-};
-*/
-//TODO: implement all areas and commands
-var CMD_MEMORY_AREA_READ = []byte{0x01, 0x01}
-var CMD_MEMORY_AREA_WRITE = []byte{0x01, 0x02}
-
-var MEMORY_AREA_DATA = byte(0x82)
-var MEMORY_AREA_WORK = byte(0xB1)
-
-func readDCommand(header *Header, startAddress uint16, readCount uint16) []byte {
- return readCommand(MEMORY_AREA_DATA, header, startAddress, readCount)
+func readCommand(ioAddr *IOAddress, itemCount uint16) *Command {
+ commandData := make([]byte, 0, 6)
+ commandData = append(commandData, encodeIOAddress(ioAddr)...)
+ commandData = append(commandData, []byte{0, 0}...)
+ binary.BigEndian.PutUint16(commandData[4:6], itemCount)
+ return NewCommand(CommandCodeMemoryAreaRead, commandData)
}
-func readWCommand(header *Header, startAddress uint16, readCount uint16) []byte {
- return readCommand(MEMORY_AREA_WORK, header, startAddress, readCount)
+func writeCommand(ioAddr *IOAddress, itemCount uint16, bytes []byte) *Command {
+ commandData := make([]byte, 0, 6+len(bytes))
+ commandData = append(commandData, encodeIOAddress(ioAddr)...)
+ commandData = append(commandData, []byte{0, 0}...)
+ binary.BigEndian.PutUint16(commandData[4:6], itemCount)
+ commandData = append(commandData, bytes...)
+ return NewCommand(CommandCodeMemoryAreaWrite, commandData)
}
-func readCommand(memoryArea byte, header *Header, startAddress uint16, readCount uint16) []byte {
- var addressBit byte = 0
- addressLower := byte(startAddress)
- addressUpper := byte(startAddress >> 8)
- countLower := byte(readCount)
- countUpper := byte(readCount >> 8)
-
- paramsBytes := []byte{
- memoryArea,
- addressUpper, addressLower,
- addressBit,
- countUpper, countLower}
-
- bytes1 := append(header.Format(), CMD_MEMORY_AREA_READ...)
- bytes2 := append(bytes1, paramsBytes...)
- return bytes2
+func encodeIOAddress(ioAddr *IOAddress) []byte {
+ bytes := make([]byte, 4, 4)
+ bytes[0] = ioAddr.MemoryArea()
+ binary.BigEndian.PutUint16(bytes[1:3], ioAddr.Address())
+ bytes[3] = ioAddr.BitOffset()
+ return bytes
}
-func writeDCommand(header *Header, startAddress uint16, data []uint16) []byte {
- return writeCommand(MEMORY_AREA_DATA, header, startAddress, data)
+func decodeFrame(bytes []byte) *Frame {
+ header := decodeHeader(bytes[0:10])
+ var payload Payload
+ if header.FrameIsCommand() {
+ payload = decodeCommand(bytes[10:])
+ } else if header.FrameIsResponse() {
+ payload = decodeResponse(bytes[10:])
+ }
+ frame := NewFrame(header, payload)
+ return frame
}
-func writeWCommand(header *Header, startAddress uint16, data []uint16) []byte {
- return writeCommand(MEMORY_AREA_WORK, header, startAddress, data)
+func encodeFrame(f *Frame) []byte {
+ bytes := encodeHeader(f.Header())
+ var payloadData []byte
+ if f.Header().FrameIsCommand() {
+ payloadData = encodeCommand(f.Payload().(*Command))
+ } else if f.Header().FrameIsResponse() {
+ payloadData = encodeResponse(f.Payload().(*Response))
+ }
+ bytes = append(bytes, payloadData...)
+ return bytes
}
-func writeCommand(memoryArea byte, header *Header, startAddress uint16, data []uint16) []byte {
- var addressBit byte = 0
- addressLower := byte(startAddress)
- addressUpper := byte(startAddress >> 8)
- dataLen := len(data)
- lenLower := byte(dataLen)
- lenUpper := byte(dataLen >> 8)
-
- paramsBytes := []byte{
- memoryArea,
- addressUpper, addressLower,
- addressBit,
- lenUpper, lenLower}
-
- bytes1 := append(header.Format(), CMD_MEMORY_AREA_WRITE...)
- bytes2 := append(bytes1, paramsBytes...)
- bytes3 := append(bytes2, toBytes(data)...)
- return bytes3
+const (
+ icfBridgesBit byte = 7
+ icfMessageTypeBit byte = 6
+ icfResponseRequiredBit byte = 0
+)
+
+func decodeHeader(bytes []byte) *Header {
+ header := new(Header)
+ icf := bytes[0]
+ if icf&1<> 8)
- res[2*i+1] = byte(data[i])
+func encodeHeader(h *Header) []byte {
+ var icf byte
+ icf = 0x80
+ if h.responseRequired == false {
+ icf |= 1 << icfResponseRequiredBit
+ }
+ if h.messgeType == MessageTypeResponse {
+ icf |= 1 << icfMessageTypeBit
}
- return res
+ bytes := []byte{
+ icf, 0x00, h.gatewayCount,
+ h.dst.Network(), h.dst.Node(), h.dst.Unit(),
+ h.src.Network(), h.src.Node(), h.src.Unit(),
+ h.serviceID}
+ return bytes
}
-func parseResponse(bytes []byte) (*Response, error) {
- finishCode1 := bytes[12]
- finishCode2 := bytes[13]
+func decodeCommand(bytes []byte) *Command {
+ return NewCommand(
+ binary.BigEndian.Uint16(bytes[0:2]),
+ bytes[2:])
+}
+
+func encodeCommand(command *Command) []byte {
+ bytes := make([]byte, 2, 2+len(command.Data()))
+ binary.BigEndian.PutUint16(bytes[0:2], command.CommandCode())
+ bytes = append(bytes, command.Data()...)
+ return bytes
+}
- if finishCode1 != 0 || finishCode2 != 0 {
- msg := fmt.Sprintln("failure code: ", finishCode1, ": ", finishCode2)
- return nil, errors.New(msg)
+func decodeResponse(bytes []byte) *Response {
+ return NewResponse(
+ binary.BigEndian.Uint16(bytes[0:2]),
+ binary.BigEndian.Uint16(bytes[2:4]),
+ bytes[4:])
+}
+
+func encodeResponse(response *Response) []byte {
+ bytes := make([]byte, 4, 4+len(response.Data()))
+ binary.BigEndian.PutUint16(bytes[0:2], response.CommandCode())
+ binary.BigEndian.PutUint16(bytes[2:4], response.EndCode())
+ bytes = append(bytes, response.Data()...)
+ return bytes
+}
+
+var errBCDBadDigit = errors.New("Bad digit in BCD decoding")
+var errBCDOverflow = errors.New("Overflow occurred in BCD decoding")
+
+func encodeBCD(x uint64) []byte {
+ if x == 0 {
+ return []byte{0x0f}
}
+ var n int
+ for xx := x; xx > 0; n++ {
+ xx = xx / 10
+ }
+ bcd := make([]byte, (n+1)/2)
+ if n%2 == 1 {
+ hi, lo := byte(x%10), byte(0x0f)
+ bcd[(n-1)/2] = hi<<4 | lo
+ x = x / 10
+ n--
+ }
+ for i := n/2 - 1; i >= 0; i-- {
+ hi, lo := byte((x/10)%10), byte(x%10)
+ bcd[i] = hi<<4 | lo
+ x = x / 100
+ }
+ return bcd
+}
- return &Response{
- sid: bytes[9],
- Data: toUint16(bytes[14:]),
- }, nil
+func timesTenPlusCatchingOverflow(x uint64, digit uint64) (uint64, error) {
+ x5 := x<<2 + x
+ if int64(x5) < 0 || x5<<1 > ^digit {
+ return 0, errBCDOverflow
+ }
+ return x5<<1 + digit, nil
}
-func toUint16(data []byte) []uint16 {
- res := make([]uint16, len(data)/2)
- for i := 0; i < len(data); i += 2 {
- upper := uint16(data[i]) << 8
- lower := uint16(data[i+1])
- res[i/2] = (upper | lower)
+func decodeBCD(bcd []byte) (x uint64, err error) {
+ for i, b := range bcd {
+ hi, lo := uint64(b>>4), uint64(b&0x0f)
+ if hi > 9 {
+ return 0, errBCDBadDigit
+ }
+ x, err = timesTenPlusCatchingOverflow(x, hi)
+ if err != nil {
+ return 0, err
+ }
+ if lo == 0x0f && i == len(bcd)-1 {
+ return x, nil
+ }
+ if lo > 9 {
+ return 0, errBCDBadDigit
+ }
+ x, err = timesTenPlusCatchingOverflow(x, lo)
+ if err != nil {
+ return 0, err
+ }
}
- return res
+ return x, nil
}
diff --git a/fins/end_code.go b/fins/end_code.go
new file mode 100644
index 0000000..6ce9ea4
--- /dev/null
+++ b/fins/end_code.go
@@ -0,0 +1,259 @@
+package fins
+
+// Data taken from Omron document Cat. No. W342-E1-15, pages 155-161
+const (
+ // EndCodeNormalCompletion End code: normal completion
+ EndCodeNormalCompletion uint16 = 0x0000
+
+ // EndCodeServiceInterrupted End code: normal completion; service was interrupted
+ EndCodeServiceInterrupted uint16 = 0x0001
+
+ // EndCodeLocalNodeNotInNetwork End code: local node error; local node not in network
+ EndCodeLocalNodeNotInNetwork uint16 = 0x0101
+
+ // EndCodeTokenTimeout End code: local node error; token timeout
+ EndCodeTokenTimeout uint16 = 0x0102
+
+ // EndCodeRetriesFailed End code: local node error; retries failed
+ EndCodeRetriesFailed uint16 = 0x0103
+
+ // EndCodeTooManySendFrames End code: local node error; too many send frames
+ EndCodeTooManySendFrames uint16 = 0x0104
+
+ // EndCodeNodeAddressRangeError End code: local node error; node address range error
+ EndCodeNodeAddressRangeError uint16 = 0x0105
+
+ // EndCodeNodeAddressRangeDuplication End code: local node error; node address range duplication
+ EndCodeNodeAddressRangeDuplication uint16 = 0x0106
+
+ // EndCodeDestinationNodeNotInNetwork End code: destination node error; destination node not in network
+ EndCodeDestinationNodeNotInNetwork uint16 = 0x0201
+
+ // EndCodeUnitMissing End code: destination node error; unit missing
+ EndCodeUnitMissing uint16 = 0x0202
+
+ // EndCodeThirdNodeMissing End code: destination node error; third node missing
+ EndCodeThirdNodeMissing uint16 = 0x0203
+
+ // EndCodeDestinationNodeBusy End code: destination node error; destination node busy
+ EndCodeDestinationNodeBusy uint16 = 0x0204
+
+ // EndCodeResponseTimeout End code: destination node error; response timeout
+ EndCodeResponseTimeout uint16 = 0x0205
+
+ // EndCodeCommunicationsControllerError End code: controller error; communication controller error
+ EndCodeCommunicationsControllerError uint16 = 0x0301
+
+ // EndCodeCPUUnitError End code: controller error; CPU unit error
+ EndCodeCPUUnitError uint16 = 0x0302
+
+ // EndCodeControllerError End code: controller error; controller error
+ EndCodeControllerError uint16 = 0x0303
+
+ // EndCodeUnitNumberError End code: controller error; unit number error
+ EndCodeUnitNumberError uint16 = 0x0304
+
+ // EndCodeUndefinedCommand End code: service unsupported; undefined command
+ EndCodeUndefinedCommand uint16 = 0x0401
+
+ // EndCodeNotSupportedByModelVersion End code: service unsupported; not supported by model version
+ EndCodeNotSupportedByModelVersion uint16 = 0x0402
+
+ // EndCodeDestinationAddressSettingError End code: routing table error; destination address setting error
+ EndCodeDestinationAddressSettingError uint16 = 0x0501
+
+ // EndCodeNoRoutingTables End code: routing table error; no routing tables
+ EndCodeNoRoutingTables uint16 = 0x0502
+
+ // EndCodeRoutingTableError End code: routing table error; routing table error
+ EndCodeRoutingTableError uint16 = 0x0503
+
+ // EndCodeTooManyRelays End code: routing table error; too many relays
+ EndCodeTooManyRelays uint16 = 0x0504
+
+ // EndCodeCommandTooLong End code: command format error; command too long
+ EndCodeCommandTooLong uint16 = 0x1001
+
+ // EndCodeCommandTooShort End code: command format error; command too short
+ EndCodeCommandTooShort uint16 = 0x1002
+
+ // EndCodeElementsDataDontMatch End code: command format error; elements/data don't match
+ EndCodeElementsDataDontMatch uint16 = 0x1003
+
+ // EndCodeCommandFormatError End code: command format error; command format error
+ EndCodeCommandFormatError uint16 = 0x1004
+
+ // EndCodeHeaderError End code: command format error; header error
+ EndCodeHeaderError uint16 = 0x1005
+
+ // EndCodeAreaClassificationMissing End code: parameter error; classification missing
+ EndCodeAreaClassificationMissing uint16 = 0x1101
+
+ // EndCodeAccessSizeError End code: parameter error; access size error
+ EndCodeAccessSizeError uint16 = 0x1102
+
+ // EndCodeAddressRangeError End code: parameter error; address range error
+ EndCodeAddressRangeError uint16 = 0x1103
+
+ // EndCodeAddressRangeExceeded End code: parameter error; address range exceeded
+ EndCodeAddressRangeExceeded uint16 = 0x1104
+
+ // EndCodeProgramMissing End code: parameter error; program missing
+ EndCodeProgramMissing uint16 = 0x1106
+
+ // EndCodeRelationalError End code: parameter error; relational error
+ EndCodeRelationalError uint16 = 0x1109
+
+ // EndCodeDuplicateDataAccess End code: parameter error; duplicate data access
+ EndCodeDuplicateDataAccess uint16 = 0x110a
+
+ // EndCodeResponseTooBig End code: parameter error; response too big
+ EndCodeResponseTooBig uint16 = 0x110b
+
+ // EndCodeParameterError End code: parameter error
+ EndCodeParameterError uint16 = 0x110c
+
+ // EndCodeReadNotPossibleProtected End code: read not possible; protected
+ EndCodeReadNotPossibleProtected uint16 = 0x2002
+
+ // EndCodeReadNotPossibleTableMissing End code: read not possible; table missing
+ EndCodeReadNotPossibleTableMissing uint16 = 0x2003
+
+ // EndCodeReadNotPossibleDataMissing End code: read not possible; data missing
+ EndCodeReadNotPossibleDataMissing uint16 = 0x2004
+
+ // EndCodeReadNotPossibleProgramMissing End code: read not possible; program missing
+ EndCodeReadNotPossibleProgramMissing uint16 = 0x2005
+
+ // EndCodeReadNotPossibleFileMissing End code: read not possible; file missing
+ EndCodeReadNotPossibleFileMissing uint16 = 0x2006
+
+ // EndCodeReadNotPossibleDataMismatch End code: read not possible; data mismatch
+ EndCodeReadNotPossibleDataMismatch uint16 = 0x2007
+
+ // EndCodeWriteNotPossibleReadOnly End code: write not possible; read only
+ EndCodeWriteNotPossibleReadOnly uint16 = 0x2101
+
+ // EndCodeWriteNotPossibleProtected End code: write not possible; write protected
+ EndCodeWriteNotPossibleProtected uint16 = 0x2102
+
+ // EndCodeWriteNotPossibleCannotRegister End code: write not possible; cannot register
+ EndCodeWriteNotPossibleCannotRegister uint16 = 0x2103
+
+ // EndCodeWriteNotPossibleProgramMissing End code: write not possible; program missing
+ EndCodeWriteNotPossibleProgramMissing uint16 = 0x2105
+
+ // EndCodeWriteNotPossibleFileMissing End code: write not possible; file missing
+ EndCodeWriteNotPossibleFileMissing uint16 = 0x2106
+
+ // EndCodeWriteNotPossibleFileNameAlreadyExists End code: write not possible; file name already exists
+ EndCodeWriteNotPossibleFileNameAlreadyExists uint16 = 0x2107
+
+ // EndCodeWriteNotPossibleCannotChange End code: write not possible; cannot change
+ EndCodeWriteNotPossibleCannotChange uint16 = 0x2108
+
+ // EndCodeNotExecutableInCurrentModeNotPossibleDuringExecution End code: not executeable in current mode during execution
+ EndCodeNotExecutableInCurrentModeNotPossibleDuringExecution uint16 = 0x2201
+
+ // EndCodeNotExecutableInCurrentModeNotPossibleWhileRunning End code: not executeable in current mode while running
+ EndCodeNotExecutableInCurrentModeNotPossibleWhileRunning uint16 = 0x2202
+
+ // EndCodeNotExecutableInCurrentModeWrongPLCModeInProgram End code: not executeable in current mode; PLC is in PROGRAM mode
+ EndCodeNotExecutableInCurrentModeWrongPLCModeInProgram uint16 = 0x2203
+
+ // EndCodeNotExecutableInCurrentModeWrongPLCModeInDebug End code: not executeable in current mode; PLC is in DEBUG mode
+ EndCodeNotExecutableInCurrentModeWrongPLCModeInDebug uint16 = 0x2204
+
+ // EndCodeNotExecutableInCurrentModeWrongPLCModeInMonitor End code: not executeable in current mode; PLC is in MONITOR mode
+ EndCodeNotExecutableInCurrentModeWrongPLCModeInMonitor uint16 = 0x2205
+
+ // EndCodeNotExecutableInCurrentModeWrongPLCModeInRun End code: not executeable in current mode; PLC is in RUN mode
+ EndCodeNotExecutableInCurrentModeWrongPLCModeInRun uint16 = 0x2206
+
+ // EndCodeNotExecutableInCurrentModeSpecifiedNodeNotPollingNode End code: not executeable in current mode; specified node is not polling node
+ EndCodeNotExecutableInCurrentModeSpecifiedNodeNotPollingNode uint16 = 0x2207
+
+ // EndCodeNotExecutableInCurrentModeStepCannotBeExecuted End code: not executeable in current mode; step cannot be executed
+ EndCodeNotExecutableInCurrentModeStepCannotBeExecuted uint16 = 0x2208
+
+ // EndCodeNoSuchDeviceFileDeviceMissing End code: no such device; file device missing
+ EndCodeNoSuchDeviceFileDeviceMissing uint16 = 0x2301
+
+ // EndCodeNoSuchDeviceMemoryMissing End code: no such device; memory missing
+ EndCodeNoSuchDeviceMemoryMissing uint16 = 0x2302
+
+ // EndCodeNoSuchDeviceClockMissing End code: no such device; clock missing
+ EndCodeNoSuchDeviceClockMissing uint16 = 0x2303
+
+ // EndCodeCannotStartStopTableMissing End code: cannot start/stop; table missing
+ EndCodeCannotStartStopTableMissing uint16 = 0x2401
+
+ // EndCodeUnitErrorMemoryError End code: unit error; memory error
+ EndCodeUnitErrorMemoryError uint16 = 0x2502
+
+ // EndCodeUnitErrorIOError End code: unit error; IO error
+ EndCodeUnitErrorIOError uint16 = 0x2503
+
+ // EndCodeUnitErrorTooManyIOPoints End code: unit error; too many IO points
+ EndCodeUnitErrorTooManyIOPoints uint16 = 0x2504
+
+ // EndCodeUnitErrorCPUBusError End code: unit error; CPU bus error
+ EndCodeUnitErrorCPUBusError uint16 = 0x2505
+
+ // EndCodeUnitErrorIODuplication End code: unit error; IO duplication
+ EndCodeUnitErrorIODuplication uint16 = 0x2506
+
+ // EndCodeUnitErrorIOBusError End code: unit error; IO bus error
+ EndCodeUnitErrorIOBusError uint16 = 0x2507
+
+ // EndCodeUnitErrorSYSMACBUS2Error End code: unit error; SYSMAC BUS/2 error
+ EndCodeUnitErrorSYSMACBUS2Error uint16 = 0x2509
+
+ // EndCodeUnitErrorCPUBusUnitError End code: unit error; CPU bus unit error
+ EndCodeUnitErrorCPUBusUnitError uint16 = 0x250a
+
+ // EndCodeUnitErrorSYSMACBusNumberDuplication End code: unit error; SYSMAC bus number duplication
+ EndCodeUnitErrorSYSMACBusNumberDuplication uint16 = 0x250d
+
+ // EndCodeUnitErrorMemoryStatusError End code: unit error; memory status error
+ EndCodeUnitErrorMemoryStatusError uint16 = 0x250f
+
+ // EndCodeUnitErrorSYSMACBusTerminatorMissing End code: unit error; SYSMAC bus terminator missing
+ EndCodeUnitErrorSYSMACBusTerminatorMissing uint16 = 0x2510
+
+ // EndCodeCommandErrorNoProtection End code: command error; no protection
+ EndCodeCommandErrorNoProtection uint16 = 0x2601
+
+ // EndCodeCommandErrorIncorrectPassword End code: command error; incorrect password
+ EndCodeCommandErrorIncorrectPassword uint16 = 0x2602
+
+ // EndCodeCommandErrorProtected End code: command error; protected
+ EndCodeCommandErrorProtected uint16 = 0x2604
+
+ // EndCodeCommandErrorServiceAlreadyExecuting End code: command error; service already executing
+ EndCodeCommandErrorServiceAlreadyExecuting uint16 = 0x2605
+
+ // EndCodeCommandErrorServiceStopped End code: command error; service stopped
+ EndCodeCommandErrorServiceStopped uint16 = 0x2606
+
+ // EndCodeCommandErrorNoExecutionRight End code: command error; no execution right
+ EndCodeCommandErrorNoExecutionRight uint16 = 0x2607
+
+ // EndCodeCommandErrorSettingsNotComplete End code: command error; settings not complete
+ EndCodeCommandErrorSettingsNotComplete uint16 = 0x2608
+
+ // EndCodeCommandErrorNecessaryItemsNotSet End code: command error; necessary items not set
+ EndCodeCommandErrorNecessaryItemsNotSet uint16 = 0x2609
+
+ // EndCodeCommandErrorNumberAlreadyDefined End code: command error; number already defined
+ EndCodeCommandErrorNumberAlreadyDefined uint16 = 0x260a
+
+ // EndCodeCommandErrorErrorWillNotClear End code: command error; error will not clear
+ EndCodeCommandErrorErrorWillNotClear uint16 = 0x260b
+
+ // EndCodeAccessWriteErrorNoAccessRight End code: access write error; no access right
+ EndCodeAccessWriteErrorNoAccessRight uint16 = 0x3001
+
+ // EndCodeAbortServiceAborted End code: abort; service aborted
+ EndCodeAbortServiceAborted uint16 = 0x4001
+)
diff --git a/fins/frame.go b/fins/frame.go
new file mode 100644
index 0000000..902fd92
--- /dev/null
+++ b/fins/frame.go
@@ -0,0 +1,23 @@
+package fins
+
+// Frame A FINS frame
+type Frame struct {
+ header *Header
+ payload Payload
+}
+
+// NewFrame Creates a new FINS frame
+func NewFrame(header *Header, payload Payload) *Frame {
+ f := new(Frame)
+ f.header = header
+ f.payload = payload
+ return f
+}
+
+func (f *Frame) Header() *Header {
+ return f.header
+}
+
+func (f *Frame) Payload() Payload {
+ return f.payload
+}
diff --git a/fins/header.go b/fins/header.go
index 16b3759..eed21d8 100644
--- a/fins/header.go
+++ b/fins/header.go
@@ -1,86 +1,95 @@
package fins
-// For now we have only one PLC - it means we do not need in any networks settings -
-// almost everywhere is 0.
+// Header A FINS frame header
type Header struct {
- icf byte
- rsv byte
- gct byte
- dna byte
- da1 byte
- da2 byte
- sna byte
- sa1 byte
- sa2 byte
- sid byte
+ messgeType uint8
+ responseRequired bool
+ dst *Address
+ src *Address
+ serviceID byte
+ gatewayCount uint8
}
-func defaultHeader(sid byte) *Header {
- h := new(Header)
- h.icf = icf()
- h.rsv = rsv()
- h.gct = gct()
- h.dna = dstNetwork()
- h.da1 = dstNode()
- h.da2 = dstUnit()
- h.sna = srcNetwork()
- h.sa1 = srcNode()
- h.sa2 = srcUnit()
- h.sid = sid
- return h
-}
+const (
+ // MessageTypeCommand Command message type
+ MessageTypeCommand uint8 = iota
-func newHeaderNoResponse(sid byte) *Header {
- h := defaultHeader(sid)
- h.icf = icfNoResponse()
- return h
-}
-
-func (f *Header) Format() []byte {
+ // MessageTypeResponse Response message type
+ MessageTypeResponse uint8 = iota
+)
- return []byte{
- f.icf, f.rsv, f.gct,
- f.dna, f.da1, f.da2,
- f.sna, f.sa1, f.sa2,
- f.sid}
+// IsResponseRequired Returns true if this header indicates that a response should be required
+func (h *Header) IsResponseRequired() bool {
+ return h.responseRequired
}
-func icf() byte {
- return 0x80 //128
+// FrameIsCommand Returns true if the frame this header was contained within was a command
+func (h *Header) FrameIsCommand() bool {
+ return h.messgeType == MessageTypeCommand
}
-func icfNoResponse() byte {
- return 0x81 //129
+// FrameIsResponse Returns true if the frame this header was contained within was a response
+func (h *Header) FrameIsResponse() bool {
+ return h.messgeType == MessageTypeResponse
}
-func rsv() byte {
- return 0
-}
+// SetToRequireResponse Will set this header to indicate that a response is required
+// func (h *Header) SetToRequireResponse() {
+// h.responseRequired = true
+// }
-func gct() byte {
- return 0x02
+// SetToRequireNoResponse Will set this header to indicate that a response is not required
+// func (h *Header) SetToRequireNoResponse() {
+// h.responseRequired = false
+// }
+
+// SetToCommandMessageType Will set this header to indicate that the message is a command
+// func (h *Header) SetToCommandMessageType() {
+// h.messgeType = MessageTypeCommand
+// }
+
+// SetToResponseMessageType Will set this header to indicate that the message is a response
+// func (h *Header) SetToResponseMessageType() {
+// h.messgeType = MessageTypeResponse
+// }
+
+// GatewayCount Gets the gateway count
+func (h *Header) GatewayCount() byte {
+ return h.gatewayCount
}
-func dstNetwork() byte {
- return 0
+// SourceAddress Gets the source address
+func (h *Header) SourceAddress() Address {
+ return *h.src
}
-func dstNode() byte {
- return 0
+// DestinationAddress Gets the destination address
+func (h *Header) DestinationAddress() Address {
+ return *h.dst
}
-func dstUnit() byte {
- return 0
+// ServiceID Gets the service id
+func (h *Header) ServiceID() byte {
+ return h.serviceID
}
-func srcNetwork() byte {
- return 0
+func defaultHeader(messageType uint8, responseRequired bool, dst *Address, src *Address, serviceID byte) *Header {
+ h := new(Header)
+ h.messgeType = messageType
+ h.responseRequired = responseRequired
+ h.gatewayCount = 2
+ h.dst = dst
+ h.src = src
+ h.serviceID = serviceID
+ return h
}
-func srcNode() byte {
- return 0x22
+func defaultCommandHeader(dst *Address, src *Address, serviceID byte) *Header {
+ h := defaultHeader(MessageTypeCommand, true, src, dst, serviceID)
+ return h
}
-func srcUnit() byte {
- return 0
+func defaultResponseHeader(commandHeader *Header) *Header {
+ h := defaultHeader(MessageTypeResponse, false, commandHeader.src, commandHeader.dst, commandHeader.serviceID)
+ return h
}
diff --git a/fins/io_address.go b/fins/io_address.go
new file mode 100644
index 0000000..275ad1d
--- /dev/null
+++ b/fins/io_address.go
@@ -0,0 +1,32 @@
+package fins
+
+// IOAddress A FINS IO address representing some type of data or work area within the PLC
+type IOAddress struct {
+ memoryArea byte
+ address uint16
+ bitOffset byte
+}
+
+func NewIOAddress(memoryArea byte, address uint16) *IOAddress {
+ return NewIOAddressWithBitOffset(memoryArea, address, 0)
+}
+
+func NewIOAddressWithBitOffset(memoryArea byte, address uint16, bitOffset byte) *IOAddress {
+ ioAddr := new(IOAddress)
+ ioAddr.memoryArea = memoryArea
+ ioAddr.address = address
+ ioAddr.bitOffset = bitOffset
+ return ioAddr
+}
+
+func (ioAddr *IOAddress) MemoryArea() byte {
+ return ioAddr.memoryArea
+}
+
+func (ioAddr *IOAddress) Address() uint16 {
+ return ioAddr.address
+}
+
+func (ioAddr *IOAddress) BitOffset() byte {
+ return ioAddr.bitOffset
+}
diff --git a/fins/memory_area.go b/fins/memory_area.go
new file mode 100644
index 0000000..4e73c83
--- /dev/null
+++ b/fins/memory_area.go
@@ -0,0 +1,54 @@
+package fins
+
+const (
+ // MemoryAreaCIOBit Memory area: CIO area; bit
+ MemoryAreaCIOBit byte = 0x30
+
+ // MemoryAreaWRBit Memory area: work area; bit
+ MemoryAreaWRBit byte = 0x31
+
+ // MemoryAreaHRBit Memory area: holding area; bit
+ MemoryAreaHRBit byte = 0x32
+
+ // MemoryAreaARBit Memory area: axuillary area; bit
+ MemoryAreaARBit byte = 0x33
+
+ // MemoryAreaCIOWord Memory area: CIO area; word
+ MemoryAreaCIOWord byte = 0xb0
+
+ // MemoryAreaWRWord Memory area: work area; word
+ MemoryAreaWRWord byte = 0xb1
+
+ // MemoryAreaHRWord Memory area: holding area; word
+ MemoryAreaHRWord byte = 0xb2
+
+ // MemoryAreaARWord Memory area: auxillary area; word
+ MemoryAreaARWord byte = 0xb3
+
+ // MemoryAreaTimerCounterCompletionFlag Memory area: counter completin flag
+ MemoryAreaTimerCounterCompletionFlag byte = 0x09
+
+ // MemoryAreaTimerCounterPV Memory area: counter PV
+ MemoryAreaTimerCounterPV byte = 0x89
+
+ // MemoryAreaDMBit Memory area: data area; bit
+ MemoryAreaDMBit byte = 0x02
+
+ // MemoryAreaDMWord Memory area: data area; word
+ MemoryAreaDMWord byte = 0x82
+
+ // MemoryAreaTaskBit Memory area: task flags; bit
+ MemoryAreaTaskBit byte = 0x06
+
+ // MemoryAreaTaskStatus Memory area: task flags; status
+ MemoryAreaTaskStatus byte = 0x46
+
+ // MemoryAreaIndexRegisterPV Memory area: CIO bit
+ MemoryAreaIndexRegisterPV byte = 0xdc
+
+ // MemoryAreaDataRegisterPV Memory area: CIO bit
+ MemoryAreaDataRegisterPV byte = 0xbc
+
+ // MemoryAreaClockPulsesConditionFlagsBit Memory area: CIO bit
+ MemoryAreaClockPulsesConditionFlagsBit byte = 0x07
+)
diff --git a/fins/payload.go b/fins/payload.go
new file mode 100644
index 0000000..0f28bd5
--- /dev/null
+++ b/fins/payload.go
@@ -0,0 +1,20 @@
+package fins
+
+// Payload A FINS frame payload
+type Payload interface {
+ CommandCode() uint16
+ Data() []byte
+}
+
+type payloadImpl struct {
+ commandCode uint16
+ data []byte
+}
+
+func (p *payloadImpl) CommandCode() uint16 {
+ return p.commandCode
+}
+
+func (p *payloadImpl) Data() []byte {
+ return p.data
+}
diff --git a/fins/response.go b/fins/response.go
new file mode 100644
index 0000000..8ab61f0
--- /dev/null
+++ b/fins/response.go
@@ -0,0 +1,19 @@
+package fins
+
+// Response A FINS command response
+type Response struct {
+ payloadImpl
+ endCode uint16
+}
+
+func NewResponse(commandCode uint16, endCode uint16, data []byte) *Response {
+ r := new(Response)
+ r.commandCode = commandCode
+ r.endCode = endCode
+ r.data = data
+ return r
+}
+
+func (r *Response) EndCode() uint16 {
+ return r.endCode
+}
diff --git a/fins/server.go b/fins/server.go
new file mode 100644
index 0000000..86ebede
--- /dev/null
+++ b/fins/server.go
@@ -0,0 +1,80 @@
+package fins
+
+import (
+ "fmt"
+ "net"
+)
+
+// Server Omron FINS server (PLC emulator)
+type Server struct {
+ conn *net.UDPConn
+ addr *Address
+ handler CommandHandler
+}
+
+type CommandHandler func(*Command) *Response
+
+func NewServer(udpAddr *net.UDPAddr, addr *Address, handler CommandHandler) (*Server, error) {
+ s := new(Server)
+
+ conn, err := net.ListenUDP("udp", udpAddr)
+ if err != nil {
+ return nil, err
+ }
+ s.conn = conn
+ s.addr = addr
+ if handler == nil {
+ s.handler = func(command *Command) *Response {
+ fmt.Printf("Null command handler: 0x%04x\n", command.CommandCode())
+
+ response := NewResponse(command.CommandCode(), EndCodeNotSupportedByModelVersion, []byte{})
+ return response
+ }
+ } else {
+ s.handler = handler
+ }
+
+ go func() {
+ var buf [1024]byte
+ for {
+ //rlen
+ rlen, remote, err := conn.ReadFromUDP(buf[:])
+ cmdFrame := decodeFrame(buf[:rlen])
+ cmd := cmdFrame.Payload().(*Command)
+ rsp := s.handler(cmd)
+ if err != nil {
+ panic(err)
+ }
+
+ rspFrame := NewFrame(defaultResponseHeader(cmdFrame.Header()), rsp)
+ _, err = conn.WriteToUDP(encodeFrame(rspFrame), &net.UDPAddr{IP: remote.IP, Port: remote.Port})
+ if err != nil {
+ panic(err)
+ }
+ }
+ }()
+
+ return s, nil
+}
+
+// Close Closes the FINS server
+func (s *Server) Close() {
+ s.conn.Close()
+}
+
+// Handles incoming requests.
+func handleRequest(conn net.Conn) {
+ // Make a buffer to hold incoming data.
+ buf := make([]byte, 1024)
+ // Read the incoming connection into the buffer.
+ reqLen, err := conn.Read(buf)
+ if err != nil {
+ fmt.Println("Error reading:", err.Error())
+ }
+
+ fmt.Printf("Received %d bytes\n", reqLen)
+ // Send a response back to person contacting us.
+ conn.Write([]byte("Message received."))
+ // Close the connection when you're done with it.
+ conn.Close()
+}