-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathserver_router.go
129 lines (112 loc) · 4.2 KB
/
server_router.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package gologix
import (
"errors"
"fmt"
"reflect"
"strings"
"sync"
)
// The Server uses PathRouter to resolve paths to tag providers
// Its use is similar to a mux in an HTTP server where you add endpoints with
// the .Handle() method. Instead of an http path you use a CIP route byte slice and
// instead of a handler function you use an object that provides the TagProvider interface.
type PathRouter struct {
Path map[string]CIPEndpoint
}
func NewRouter() *PathRouter {
p := new(PathRouter)
p.Path = make(map[string]CIPEndpoint)
return p
}
func (router *PathRouter) Handle(path []byte, p CIPEndpoint) {
if router.Path == nil {
router.Path = make(map[string]CIPEndpoint)
}
router.Path[string(path)] = p
}
// find the tag provider for a given cip path
func (router *PathRouter) Resolve(path []byte) (CIPEndpoint, error) {
tp, ok := router.Path[string(path)]
if !ok {
return nil, fmt.Errorf("path %v not recognized", path)
}
return tp, nil
}
// This interface specifies all the needed methods to handle incoming CIP messages.
// currently supports Class1 IO messages and Class3 tag read/write messages.
// if a type only handles some subset, it should return an error for those methods
type CIPEndpoint interface {
// These functions are called when a cip service attempts to use the write or read services.
TagRead(tag string, qty int16) (any, error)
TagWrite(tag string, value any) error
// IORead is called every time the RPI triggers for an Input (from the PLC's perspective) IO message.
// It should return the serialized bytes to send to the controller.
IORead() ([]byte, error)
// IOWrite is called every time a class 1 IO message comes in. The CIP items that came in with the message
// are passed as arguments. You should check that you have the correct number of items (should be 2?) and
// that they are the correct type.
//
// items[1] has the actual write payload and it should be a connectedData item.
// it contains the following in the data section which you can deserialize with items[1].deserialize(xxx):
// SequenceCounter uint32
// Header uint16
// Payload [items[1].Header.Length - 6]byte
IOWrite(items []CIPItem) error
}
// This is a generic tag provider that can handle bi-directional class 3 tag reads and writes.
// If a tag is written that does not exist, that will create it.
// if a tag is read that does not exist, that will result in an error
// it does not handle IO messages.
//
// The built-in MapTagProvider type also only provides rudimentary tag access.
// It doesn't support addressing arrays directly
//
// It interprets "testtag[3]" as a tag with a literal "[3]" as a string at the end of the map key.
// Same thing for nested UDT tags - it interprets the dots as literals in the map keys.
//
// So if you need those for testing you'll have to kind of fake out the tag mapping on the server end by
// creating an individual entry in the map for each nested tag with the key being the full access path.
type MapTagProvider struct {
Mutex sync.Mutex
Data map[string]any
}
func (p *MapTagProvider) IORead() ([]byte, error) {
return nil, errors.New("not implemented")
}
func (p *MapTagProvider) IOWrite(items []CIPItem) error {
return errors.New("not implemented")
}
// this is a thread-safe way to get the value for a tag.
func (p *MapTagProvider) TagRead(tag string, qty int16) (any, error) {
tag = strings.ToLower(tag)
p.Mutex.Lock()
defer p.Mutex.Unlock()
if p.Data == nil {
p.Data = make(map[string]any)
}
val, ok := p.Data[tag]
if !ok {
return nil, fmt.Errorf("tag %v not in map", tag)
}
t := reflect.ValueOf(val)
if t.Kind() == reflect.Slice {
if int(qty) <= t.Len() {
values := reflect.Indirect(t)
v := values.Slice(0, int(qty))
return v.Interface(), nil
}
return nil, fmt.Errorf("too many elements requested %v > %v", qty, t.Len())
}
return val, nil
}
// this is a thread-safe way to write a value to a tag.
func (p *MapTagProvider) TagWrite(tag string, value any) error {
tag = strings.ToLower(tag)
p.Mutex.Lock()
defer p.Mutex.Unlock()
if p.Data == nil {
p.Data = make(map[string]any)
}
p.Data[tag] = value
return nil
}