/
pathfinder.go
198 lines (168 loc) · 5.52 KB
/
pathfinder.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package pathfinder
import (
"errors"
"strings"
"github.com/byuoitav/av-control-api/api/base"
"github.com/byuoitav/common/log"
"github.com/fatih/color"
)
type SignalPathfinder struct {
Devices map[string]base.Device
Expected int
Actual int
Pending map[string][]base.Port // a list of the 'pending' ports. Really what this is is the fact that we can't add switches piecemeal into the graph
}
type Node struct {
ID string
Device base.Device
}
func InitializeSignalPathfinder(devices []base.Device, expected int) SignalPathfinder {
log.L.Info("[Pathfinder] initializing pathfinder")
sf := SignalPathfinder{
Expected: expected,
Pending: make(map[string][]base.Port),
Devices: make(map[string]base.Device),
Actual: 0,
}
for _, dev := range devices {
if _, ok := sf.Devices[dev.ID]; !ok {
sf.Devices[dev.ID] = dev
}
}
return sf
}
//we need to store the state so that we can later use it to trace the value
func (sp *SignalPathfinder) AddEdge(Device base.Device, port string) bool {
log.L.Infof(color.HiCyanString("[Pathfinder] Adding edge :%v %v", Device.ID, port))
//we need to get the port from the list of devices
//go through the ports
realPort := base.Port{}
if base.HasRole(Device, "VideoSwitcher") {
split := strings.Split(port, ":")
//do the OUT port
outPort := "OUT" + split[1]
inPort := "IN" + split[0]
realPort.ID = port
//we have to build the port based on the in and out ports.
for _, p := range Device.Ports {
if p.ID == outPort {
realPort.DestinationDevice = p.DestinationDevice
}
if p.ID == inPort {
realPort.SourceDevice = p.SourceDevice
}
}
} else if base.HasRole(Device, "av-ip-receiver") {
//For AV/IP Receivers we assume that the port coming in is the address of the transmitter it's connected to.
realPort.ID = "rx " + port
realPort.DestinationDevice = Device.ID
//we need to go through the devices and find the receiver with the address denoted
for _, v := range sp.Devices {
if strings.EqualFold(v.Address, port) {
//check to see if the device in question is a non-controllable one
if base.HasRole(v, "signal-passthrough") {
//validate that the length of ports is 1
if len(v.Ports) == 1 {
realPort.SourceDevice = v.Ports[0].SourceDevice
} else {
realPort.SourceDevice = v.ID
}
}
}
}
} else {
//we can just use the port itself
for _, p := range Device.Ports {
if p.ID == port {
realPort = p
}
}
}
if _, ok := sp.Pending[Device.ID]; !ok {
sp.Pending[Device.ID] = []base.Port{realPort}
} else {
duplicate := false
for _, edge := range sp.Pending[Device.ID] {
if edge.ID == realPort.ID && edge.SourceDevice == realPort.SourceDevice && edge.DestinationDevice == realPort.DestinationDevice {
//it's a duplicate port
duplicate = true
break
}
}
if !duplicate {
sp.Pending[Device.ID] = append(sp.Pending[Device.ID], realPort)
}
}
sp.Actual++
if sp.Actual >= sp.Expected {
return true
}
return false
}
//returns a map of output -> input of all available paths.
//we assume that there is an entry for each output device - and will trace back as far as we can through that route
//we assume that all the 'edges' have been added
func (sp *SignalPathfinder) GetInputs() (map[string]base.Device, error) {
log.L.Info(color.HiCyanString("[Pathfinder] Getting all inputs"))
toReturn := make(map[string]base.Device)
log.L.Infof(color.HiCyanString("[Pathfinder] Devices: %v", len(sp.Devices)))
//we need to go through and find all of our output devices - then
for k, v := range sp.Devices {
_, ok := sp.Pending[k]
if !v.Type.Output || !ok {
continue
}
log.L.Infof(color.HiCyanString("[Pathfinder] Tracing input for %v", k))
//we now trace his path back as far as we can
curDevice := k
prevDevice := ""
for !sp.Devices[curDevice].Type.Input && curDevice != prevDevice {
next, err := sp.getNextDeviceInPath(curDevice, prevDevice)
if err != nil {
return toReturn, err
}
prevDevice = curDevice
curDevice = next
log.L.Infof(color.HiCyanString("[Pathfinder] Path includes %v -> %v", prevDevice, curDevice))
}
log.L.Infof(color.HiCyanString("[Pathfinder] Path ended. Final is %v -> %v ", k, curDevice))
toReturn[k] = sp.Devices[curDevice]
}
return toReturn, nil
}
func (sp *SignalPathfinder) getNextDeviceInPath(curDevice string, lastDevice string) (string, error) {
log.L.Debugf("Getting next device from %v", curDevice)
//check to see if the current device has a port in the array
if _, ok := sp.Pending[curDevice]; !ok {
//it doesn't have an entry. Return
return curDevice, nil
}
array := sp.Pending[curDevice]
if len(array) == 0 {
return curDevice, nil
}
if len(array) == 1 {
//we can just return the device
return array[0].SourceDevice, nil
}
dev := sp.Devices[curDevice]
//we have multiple entries for the device, check if it's a vs, if not it's an error
if !base.HasRole(dev, "VideoSwitcher") {
log.L.Error(color.HiRedString("Non video switcher has multiple entries in the table, invalid state."))
return "", errors.New("Non video switcher has multiple entries in the table, invalid state.")
}
if len(lastDevice) == 0 {
msg := "Invalid state, videoswitcher evaluated as first in chain"
log.L.Error(color.HiRedString(msg))
return "", errors.New(msg)
}
//it's a video switcher - so we need to figure out which of the pending ports we're talking about
for _, v := range array {
if v.DestinationDevice == lastDevice {
//we return the SourceDevice
return v.SourceDevice, nil
}
}
//no path forward
return curDevice, nil
}