github.com/stampzilla/stampzilla-go@v2.0.0-rc9+incompatible/nodes/stampzilla-knx/tunnel.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "math" 6 "sync" 7 "time" 8 9 "github.com/sirupsen/logrus" 10 "github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models/devices" 11 "github.com/stampzilla/stampzilla-go/pkg/node" 12 "github.com/vapourismo/knx-go/knx" 13 "github.com/vapourismo/knx-go/knx/cemi" 14 "github.com/vapourismo/knx-go/knx/dpt" 15 ) 16 17 type tunnel struct { 18 Node *node.Node 19 Address string 20 Connected bool 21 Client *knx.GroupTunnel 22 23 Groups map[string][]groupLink 24 25 OnConnect func() 26 OnDisconnect func() 27 28 reconnect bool 29 wg sync.WaitGroup 30 sync.RWMutex 31 } 32 33 type groupLink struct { 34 Name string 35 Type string 36 Device *devices.Device 37 } 38 39 func newTunnel(node *node.Node) *tunnel { 40 return &tunnel{ 41 Node: node, 42 Groups: make(map[string][]groupLink), 43 reconnect: true, 44 } 45 } 46 47 func (tunnel *tunnel) SetAddress(address string) { 48 if address == tunnel.Address { 49 return 50 } 51 52 for { 53 err := tunnel.Connect(address) 54 55 if err == nil { 56 return 57 } 58 59 logrus.WithFields(logrus.Fields{ 60 "address": address, 61 "error": err, 62 }).Error("Failed to open KNX tunnel") 63 64 if !tunnel.reconnect { 65 return 66 } 67 logrus.Warn("Going to try again in 10s") 68 <-time.After(time.Second * 10) 69 } 70 } 71 72 func (tunnel *tunnel) Connect(address string) error { 73 // Connect to the gateway 74 client, err := knx.NewGroupTunnel(address, knx.DefaultTunnelConfig) 75 if err != nil { 76 return err 77 } 78 79 // Disconnect the previous tunnel 80 if tunnel.Client != nil { 81 tunnel.reconnect = false 82 tunnel.Client.Close() 83 tunnel.wg.Wait() 84 tunnel.reconnect = true 85 } 86 87 // Start using the new one 88 go func() { 89 tunnel.wg.Add(1) 90 tunnel.Address = address 91 tunnel.Client = &client 92 tunnel.onConnect() 93 94 defer func() { 95 tunnel.onDisconnect() 96 tunnel.Client = nil 97 client.Close() 98 tunnel.wg.Done() 99 logrus.Warn("Disconnect from KNX gateway done") 100 101 if tunnel.reconnect { 102 logrus.Warn("Going to try again in 10s") 103 <-time.After(time.Second * 10) 104 go tunnel.Connect(address) 105 } 106 }() 107 108 for msg := range client.Inbound() { 109 err := tunnel.decodeKNX(msg) 110 if err != nil { 111 logrus.WithFields(logrus.Fields{ 112 "dest": msg.Destination.String(), 113 "error": err, 114 "message": fmt.Sprintf("%+v", msg), 115 }).Warn("Failed to handle message") 116 } 117 } 118 }() 119 120 return nil 121 } 122 123 func (tunnel *tunnel) onConnect() { 124 logrus.Info("Connected to KNX gateway") 125 tunnel.Connected = true 126 // Trigger a read on each group address that we monitor 127 tunnel.RLock() 128 for ga, _ := range tunnel.Groups { 129 tunnel.triggerRead(ga) 130 } 131 tunnel.RUnlock() 132 tunnel.OnConnect() 133 } 134 func (tunnel *tunnel) onDisconnect() { 135 logrus.Warn("Disconnected from KNX gateway") 136 tunnel.Connected = false 137 tunnel.OnDisconnect() 138 } 139 140 func (tunnel *tunnel) triggerRead(ga string) { 141 if !tunnel.Connected { // Dont try to send if we are not connected 142 return 143 } 144 145 addr, err := cemi.NewGroupAddrString(ga) 146 if err != nil { 147 logrus.WithFields(logrus.Fields{ 148 "group_address": ga, 149 "error": err, 150 }).Error("Failed to read group address") 151 } 152 153 err = tunnel.Client.Send(knx.GroupEvent{ 154 Command: knx.GroupRead, 155 Destination: addr, 156 }) 157 if err != nil { 158 logrus.WithFields(logrus.Fields{ 159 "group_address": ga, 160 "error": err, 161 }).Error("Failed to send read request") 162 } 163 logrus.WithFields(logrus.Fields{ 164 "group_address": ga, 165 }).Info("Sent read request") 166 167 <-time.After(time.Millisecond * 200) 168 } 169 170 func (tunnel *tunnel) decodeKNX(msg knx.GroupEvent) error { 171 tunnel.RLock() 172 defer tunnel.RUnlock() 173 174 tunnel.RLock() 175 links, ok := tunnel.Groups[msg.Destination.String()] 176 if !ok { 177 return fmt.Errorf("No link was found for: %s", msg.Destination.String()) 178 } 179 tunnel.RUnlock() 180 181 for _, gl := range links { 182 logrus.WithFields(logrus.Fields{ 183 "dest": msg.Destination.String(), 184 "name": gl.Name, 185 "deviceId": gl.Device.ID, 186 }).Trace("Found link") 187 188 var value interface{} 189 var err error 190 switch gl.Type { 191 case "bool": 192 value = new(dpt.DPT_1001) 193 case "temperature": 194 value = new(dpt.DPT_9001) //2 bytes floating point 195 case "lux": 196 value = new(dpt.DPT_9004) //2 bytes floating point 197 case "humidity": 198 value = new(dpt.DPT_9001) //2 bytes floating point 199 } 200 201 if dptv, ok := value.(dpt.DatapointValue); ok { 202 err = dptv.Unpack(msg.Data) 203 if err != nil { 204 return fmt.Errorf("Failed to unpack: %s", err.Error()) 205 } 206 207 gl.Device.State[gl.Name] = dptv 208 gl.Device.Online = true 209 210 //If temperature and relative humidity is known, calculate absolute humidity 211 dh, okh := gl.Device.State["humidity"] 212 dt, okt := gl.Device.State["temperature"] 213 if okh && okt { 214 srh, _ := dh.(dpt.DPT_9001) 215 st, _ := dt.(dpt.DPT_9001) 216 217 //pws := 6.116441 * 10 * ((7.591386 - t) / (t + 240.7263)) 218 //pw := pws * h / 100 219 //a := 2.116679 * pw / (273.15 + t) 220 //logrus.Warnf("absolute humidity %.2f", a) 221 ////gl.Device.State["absolute_humidity"] = a 222 223 t := float64(st) 224 rh := float64(srh) 225 mw := 18.01534 // molar mass of water g/mol 226 r := 8.31447215 // Universal gas constant J/mol/K 227 ah := (6.112 * math.Exp((17.67*t)/(t+243.5)) * rh * mw) / (273.15 + t*r) // in grams/m^3 228 logrus.Warnf("Got temp %s (%#v) and humid %s (%#v) %#v -> %f", st, dt, srh, dh, gl.Device.State, ah) 229 } 230 231 tunnel.Node.AddOrUpdate(gl.Device) 232 } else { 233 return fmt.Errorf("Unsupported type: %s", gl.Type) 234 } 235 } 236 return nil 237 } 238 239 func (tunnel *tunnel) ClearAllLinks() { 240 tunnel.Lock() 241 tunnel.Groups = make(map[string][]groupLink) 242 tunnel.Unlock() 243 } 244 245 func (tunnel *tunnel) AddLink(ga string, n string, t string, d *devices.Device) { 246 logrus.WithFields(logrus.Fields{ 247 "dest": ga, 248 "name": n, 249 "to": d.ID, 250 }).Tracef("Add link") 251 252 tunnel.Lock() 253 if _, ok := tunnel.Groups[ga]; !ok { 254 tunnel.Groups[ga] = []groupLink{} 255 } 256 257 tunnel.Groups[ga] = append(tunnel.Groups[ga], groupLink{ 258 Name: n, 259 Type: t, 260 Device: d, 261 }) 262 tunnel.Unlock() 263 264 d.State[n] = nil 265 266 // Trigger a read of the group 267 if tunnel.Connected { 268 tunnel.triggerRead(ga) 269 } 270 } 271 272 func (tunnel *tunnel) Close() { 273 if tunnel.Client != nil { 274 tunnel.Client.Close() 275 tunnel.wg.Wait() 276 } 277 }