-
Notifications
You must be signed in to change notification settings - Fork 14
/
udt_write.go
179 lines (158 loc) · 4.77 KB
/
udt_write.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package gologix
import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
)
// convert tagged struct to a map in the format of {"fieldTag": fieldvalue}
func multi_to_dict(data any) (map[string]interface{}, error) {
//TODO: handle nested structs and arrays
// convert the struct to a dict of FieldName: FieldValue
d := make(map[string]interface{})
vs := reflect.ValueOf(data)
fs := reflect.TypeOf(data)
for i := 0; i < vs.NumField(); i++ {
v := vs.Field(i)
f := fs.Field(i)
fulltag := f.Tag.Get("gologix")
switch v.Kind() {
case reflect.Struct:
d2, err := udt_to_dict(fulltag, v.Interface())
if err != nil {
return nil, fmt.Errorf("problem parsing %s. %w", fulltag, err)
}
for k := range d2 {
d[k] = d2[k]
}
case reflect.Array:
//TODO
default:
d[fulltag] = v.Interface()
}
}
return d, nil
}
// convert a struct to a map in the format of {"tag.fieldName": fieldvalue}
func udt_to_dict(tag string, data any) (map[string]interface{}, error) {
//TODO: handle nested structs and arrays
// convert the struct to a dict of FieldName: FieldValue
d := make(map[string]interface{})
vs := reflect.ValueOf(data)
fs := reflect.TypeOf(data)
for i := 0; i < vs.NumField(); i++ {
v := vs.Field(i)
f := fs.Field(i)
fulltag := fmt.Sprintf("%s.%s", tag, f.Name)
switch v.Kind() {
}
switch v.Kind() {
case reflect.Struct:
d2, err := udt_to_dict(fulltag, v.Interface())
if err != nil {
return nil, fmt.Errorf("problem parsing %s. %w", fulltag, err)
}
for k := range d2 {
d[k] = d2[k]
}
case reflect.Array:
//TODO
default:
d[fulltag] = v.Interface()
}
}
return d, nil
}
// Write multiple tags at once where the tagnames are the keys of a map and the values are the corresponding
// values.
//
// To write multiple tags with a struct, see WriteMulti()
func (client *Client) WriteMap(tag_str map[string]interface{}) error {
// build the tag list from the structure
tags := make([]string, 0)
types := make([]CIPType, 0)
for k := range tag_str {
ct, _ := GoVarToCIPType(tag_str[k])
types = append(types, ct)
tags = append(tags, k)
}
// first generate IOIs for each tag
qty := len(tags)
iois := make([]*tagIOI, qty)
for i, tag := range tags {
var err error
iois[i], err = client.newIOI(tag, types[i])
if err != nil {
return err
}
}
reqitems := make([]CIPItem, 2)
reqitems[0] = newItem(cipItem_ConnectionAddress, &client.OTNetworkConnectionID)
ioi_header := msgCIPConnectedMultiServiceReq{
Sequence: uint16(sequencer()),
Service: CIPService_MultipleService,
PathSize: 2,
Path: [4]byte{0x20, 0x02, 0x24, 0x01},
ServiceCount: uint16(qty),
}
b := bytes.Buffer{}
// we now have to build up the jump table for each IOI.
// and pack all the IOIs together into b
jump_table := make([]uint16, qty)
jump_start := 2 + qty*2 // 2 bytes + 2 bytes per jump entry
for i := 0; i < qty; i++ {
jump_table[i] = uint16(jump_start + b.Len())
ioi := iois[i]
h := msgCIPMultiIOIHeader{
Service: CIPService_Write,
Size: byte(len(ioi.Buffer) / 2),
}
f := msgCIPWriteIOIFooter{
DataType: uint16(types[i]),
Elements: 1,
}
//f := msgCIPIOIFooter{
//Elements: 1,
//}
err := binary.Write(&b, binary.LittleEndian, h)
if err != nil {
return fmt.Errorf("problem writing udt item header to buffer. %w", err)
}
b.Write(ioi.Buffer)
ftr_buf, err := Serialize(f)
if err != nil {
return fmt.Errorf("problem serializing footer for item %d: %w", i, err)
}
err = binary.Write(&b, binary.LittleEndian, ftr_buf.Bytes())
if err != nil {
return fmt.Errorf("problem writing udt item footer to buffer. %w", err)
}
item_buf, err := Serialize(tag_str[tags[i]])
if err != nil {
return fmt.Errorf("problem serializing %v: %w", tags[i], err)
}
err = binary.Write(&b, binary.LittleEndian, item_buf.Bytes())
if err != nil {
return fmt.Errorf("problem writing udt tag name to buffer. %w", err)
}
}
// right now I'm putting the IOI data into the cip Item, but I suspect it might actually be that the readsequencer is
// the item's data and the service code actually starts the next portion of the message. But the item's header length reflects
// the total data so maybe not.
reqitems[1] = CIPItem{Header: cipItemHeader{ID: cipItem_ConnectedData}}
reqitems[1].Serialize(ioi_header)
reqitems[1].Serialize(jump_table)
reqitems[1].Serialize(b.Bytes())
itemdata, err := serializeItems(reqitems)
if err != nil {
return err
}
hdr, data, err := client.send_recv_data(cipCommandSendUnitData, itemdata)
if err != nil {
return err
}
_ = hdr
_ = data
//TODO: do something with the result here!
return nil
}