github.com/simpleiot/simpleiot@v0.18.3/data/node.go (about) 1 package data 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/simpleiot/simpleiot/internal/pb" 9 "google.golang.org/protobuf/proto" 10 ) 11 12 // SwUpdateState represents the state of an update 13 type SwUpdateState struct { 14 Running bool `json:"running"` 15 Error string `json:"error"` 16 PercentDone int `json:"percentDone"` 17 } 18 19 // Points converts SW update state to node points 20 func (sws *SwUpdateState) Points() Points { 21 running := 0.0 22 if sws.Running { 23 running = 1 24 } 25 26 return Points{ 27 Point{ 28 Type: PointTypeSwUpdateRunning, 29 Value: running, 30 }, 31 Point{ 32 Type: PointTypeSwUpdateError, 33 Text: sws.Error, 34 }, 35 Point{ 36 Type: PointTypeSwUpdatePercComplete, 37 Value: float64(sws.PercentDone), 38 }} 39 } 40 41 // TODO move Node to db/store package and make it internal to that package 42 43 // Node represents the state of a device. UUID is recommended 44 // for ID to prevent collisions is distributed instances. 45 type Node struct { 46 ID string `json:"id"` 47 Type string `json:"type"` 48 Points Points `json:"points"` 49 } 50 51 func (n Node) String() string { 52 ret := fmt.Sprintf("NODE: %v (%v)\n", n.ID, n.Type) 53 54 for _, p := range n.Points { 55 ret += fmt.Sprintf(" - Point: %v\n", p) 56 } 57 58 return ret 59 } 60 61 // Desc returns Description if set, otherwise ID 62 func (n *Node) Desc() string { 63 desc := n.Points.Desc() 64 65 if desc != "" { 66 return desc 67 } 68 69 return n.ID 70 } 71 72 // FIXME all of the below functions need to be modified to go through NATS 73 // perhaps they should be removed 74 75 // GetState checks state of node and 76 // returns true if state was updated. We originally considered 77 // offline to be when we did not receive data from a remote device 78 // for X minutes. However, with points that could represent a config 79 // change as well. Eventually we may want to improve this to look 80 // at point types (perhaps Sample). 81 func (n *Node) GetState() (string, bool) { 82 sysState := n.State() 83 switch sysState { 84 case PointValueSysStateUnknown, PointValueSysStateOnline: 85 if time.Since(n.Points.LatestTime()) > 15*time.Minute { 86 // mark device as offline 87 return PointValueSysStateOffline, true 88 } 89 } 90 91 return sysState, false 92 } 93 94 // State returns the current state of a device 95 func (n *Node) State() string { 96 s, _ := n.Points.Text(PointTypeSysState, "") 97 return s 98 } 99 100 // ToUser converts a node to user struct 101 func (n *Node) ToUser() User { 102 first, _ := n.Points.Text(PointTypeFirstName, "") 103 last, _ := n.Points.Text(PointTypeLastName, "") 104 phone, _ := n.Points.Text(PointTypePhone, "") 105 email, _ := n.Points.Text(PointTypeEmail, "") 106 pass, _ := n.Points.Text(PointTypePass, "") 107 108 return User{ 109 ID: n.ID, 110 FirstName: first, 111 LastName: last, 112 Phone: phone, 113 Email: email, 114 Pass: pass, 115 } 116 } 117 118 // ToNodeEdge converts to data structure used in API 119 // requests 120 func (n *Node) ToNodeEdge(edge Edge) NodeEdge { 121 return NodeEdge{ 122 ID: n.ID, 123 Type: n.Type, 124 Parent: edge.Up, 125 Points: n.Points, 126 EdgePoints: edge.Points, 127 Hash: edge.Hash, 128 } 129 } 130 131 // Nodes defines a list of nodes 132 type Nodes []NodeEdge 133 134 // ToPb converts a list of nodes to protobuf 135 func (nodes *Nodes) ToPb() ([]byte, error) { 136 pbNodes := make([]*pb.Node, len(*nodes)) 137 for i, n := range *nodes { 138 nPb, err := n.ToPbNode() 139 if err != nil { 140 return nil, err 141 } 142 143 pbNodes[i] = nPb 144 } 145 146 return proto.Marshal(&pb.Nodes{Nodes: pbNodes}) 147 } 148 149 // ToPbNodes converts a list of nodes to protobuf nodes 150 func (nodes *Nodes) ToPbNodes() ([]*pb.Node, error) { 151 pbNodes := make([]*pb.Node, len(*nodes)) 152 for i, n := range *nodes { 153 nPb, err := n.ToPbNode() 154 if err != nil { 155 return nil, err 156 } 157 158 pbNodes[i] = nPb 159 } 160 161 return pbNodes, nil 162 } 163 164 // define valid commands 165 const ( 166 CmdUpdateApp = "updateApp" 167 CmdPoll = "poll" 168 CmdFieldMode = "fieldMode" 169 ) 170 171 // NodeCmd represents a command to be sent to a device 172 type NodeCmd struct { 173 ID string `json:"id,omitempty"` 174 Cmd string `json:"cmd"` 175 Detail string `json:"detail,omitempty"` 176 } 177 178 // NodeVersion represents the device SW version 179 type NodeVersion struct { 180 OS string `json:"os"` 181 App string `json:"app"` 182 HW string `json:"hw"` 183 } 184 185 // FIXME -- seems like we could eventually get rid of node edge if we 186 // do recursion in the client instead of the server. Then the client 187 // could keep track of the parents and edges in tree data structures 188 // on the client. 189 190 // NodeEdge combines node and edge data, used for APIs 191 type NodeEdge struct { 192 ID string `json:"id"` 193 Type string `json:"type"` 194 Hash uint32 `json:"hash" yaml:"-"` 195 Parent string `json:"parent"` 196 Points Points `json:"points,omitempty"` 197 EdgePoints Points `json:"edgePoints,omitempty"` 198 } 199 200 func (n NodeEdge) String() string { 201 ret := fmt.Sprintf("NODE: %v (%v)\n", n.ID, n.Type) 202 ret += fmt.Sprintf(" - Parent: %v\n", n.Parent) 203 if n.Hash != 0 { 204 ret += fmt.Sprintf(" - Hash: 0x%x\n", n.Hash) 205 } 206 207 for _, p := range n.Points { 208 ret += fmt.Sprintf(" - Point: %v\n", p) 209 } 210 211 for _, p := range n.EdgePoints { 212 ret += fmt.Sprintf(" - Edge point: %v\n", p) 213 } 214 215 return ret 216 } 217 218 // IsTombstone returns Tombstone value and timestamp 219 func (n NodeEdge) IsTombstone() (bool, time.Time) { 220 p, _ := n.EdgePoints.Find(PointTypeTombstone, "") 221 return p.Bool(), p.Time 222 } 223 224 // Desc returns Description if set, otherwise ID 225 func (n NodeEdge) Desc() string { 226 desc := n.Points.Desc() 227 228 if desc != "" { 229 return desc 230 } 231 232 return n.ID 233 } 234 235 // CalcHash calculates the hash for a node 236 func (n NodeEdge) CalcHash(children []NodeEdge) uint32 { 237 var ret uint32 238 for _, p := range n.Points { 239 ret ^= p.CRC() 240 } 241 242 for _, p := range n.EdgePoints { 243 ret ^= p.CRC() 244 } 245 246 for _, c := range children { 247 ret ^= c.Hash 248 } 249 250 return ret 251 } 252 253 // FIXME -- should ToNode really be used as it is lossy? 254 255 // ToNode converts to structure stored in db 256 func (n *NodeEdge) ToNode() Node { 257 return Node{ 258 ID: n.ID, 259 Type: n.Type, 260 Points: n.Points, 261 } 262 } 263 264 // ToPb encodes a node to a protobuf 265 func (n *NodeEdge) ToPb() ([]byte, error) { 266 267 pbNode, err := n.ToPbNode() 268 if err != nil { 269 return nil, err 270 } 271 272 return proto.Marshal(pbNode) 273 } 274 275 // ToPbNode converts a node to pb.Node type 276 func (n *NodeEdge) ToPbNode() (*pb.Node, error) { 277 points := make([]*pb.Point, len(n.Points)) 278 edgePoints := make([]*pb.Point, len(n.EdgePoints)) 279 280 for i, p := range n.Points { 281 pPb, err := p.ToPb() 282 if err != nil { 283 return &pb.Node{}, err 284 } 285 286 points[i] = &pPb 287 } 288 289 for i, p := range n.EdgePoints { 290 pPb, err := p.ToPb() 291 if err != nil { 292 return &pb.Node{}, err 293 } 294 295 edgePoints[i] = &pPb 296 } 297 298 pbNode := &pb.Node{ 299 Id: n.ID, 300 Type: n.Type, 301 Hash: int32(n.Hash), 302 Points: points, 303 EdgePoints: edgePoints, 304 Parent: n.Parent, 305 } 306 307 return pbNode, nil 308 } 309 310 // AddPoint takes a point for a device and adds/updates its array of points 311 func (n *NodeEdge) AddPoint(pIn Point) { 312 n.Points.Add(pIn) 313 } 314 315 // PbDecodeNode converts a protobuf to node data structure 316 func PbDecodeNode(data []byte) (NodeEdge, error) { 317 pbNode := &pb.Node{} 318 319 err := proto.Unmarshal(data, pbNode) 320 if err != nil { 321 return NodeEdge{}, err 322 } 323 324 return PbToNode(pbNode) 325 } 326 327 // PbDecodeNodeRequest converts a protobuf to node data structure 328 func PbDecodeNodeRequest(buf []byte) (NodeEdge, error) { 329 pbNodeRequest := &pb.NodeRequest{} 330 331 err := proto.Unmarshal(buf, pbNodeRequest) 332 if err != nil { 333 return NodeEdge{}, err 334 } 335 336 if pbNodeRequest.Error != "" { 337 // error compares fail if they are not the exact same 338 // error, even if they have the same text, so compare 339 // actual error string here 340 if pbNodeRequest.Error == ErrDocumentNotFound.Error() { 341 return NodeEdge{}, ErrDocumentNotFound 342 } 343 344 return NodeEdge{}, errors.New(pbNodeRequest.Error) 345 } 346 347 return PbToNode(pbNodeRequest.Node) 348 } 349 350 // PbToNode converts pb node to node 351 func PbToNode(pbNode *pb.Node) (NodeEdge, error) { 352 353 points := make([]Point, len(pbNode.Points)) 354 edgePoints := make([]Point, len(pbNode.EdgePoints)) 355 356 for i, pPb := range pbNode.Points { 357 s, err := PbToPoint(pPb) 358 if err != nil { 359 return NodeEdge{}, err 360 } 361 points[i] = s 362 } 363 364 for i, pPb := range pbNode.EdgePoints { 365 s, err := PbToPoint(pPb) 366 if err != nil { 367 return NodeEdge{}, err 368 } 369 edgePoints[i] = s 370 } 371 372 ret := NodeEdge{ 373 ID: pbNode.Id, 374 Type: pbNode.Type, 375 Hash: uint32(pbNode.Hash), 376 Points: points, 377 EdgePoints: edgePoints, 378 Parent: pbNode.Parent, 379 } 380 381 return ret, nil 382 } 383 384 // PbDecodeNodes decode probuf encoded nodes 385 func PbDecodeNodes(data []byte) ([]NodeEdge, error) { 386 pbNodes := &pb.Nodes{} 387 err := proto.Unmarshal(data, pbNodes) 388 if err != nil { 389 return nil, err 390 } 391 392 ret := make([]NodeEdge, len(pbNodes.Nodes)) 393 394 for i, nPb := range pbNodes.Nodes { 395 ret[i], err = PbToNode(nPb) 396 397 if err != nil { 398 return ret, err 399 } 400 } 401 402 return ret, nil 403 } 404 405 // PbDecodeNodesRequest decode probuf encoded nodes 406 func PbDecodeNodesRequest(data []byte) ([]NodeEdge, error) { 407 pbNodesRequest := &pb.NodesRequest{} 408 err := proto.Unmarshal(data, pbNodesRequest) 409 if err != nil { 410 return nil, err 411 } 412 413 if pbNodesRequest.Error != "" { 414 // error compares fail if they are not the exact same 415 // error, even if they have the same text, so compare 416 // actual error string here 417 if pbNodesRequest.Error == ErrDocumentNotFound.Error() { 418 return []NodeEdge{}, ErrDocumentNotFound 419 } 420 421 return []NodeEdge{}, errors.New(pbNodesRequest.Error) 422 } 423 424 ret := make([]NodeEdge, len(pbNodesRequest.Nodes)) 425 426 for i, nPb := range pbNodesRequest.Nodes { 427 ret[i], err = PbToNode(nPb) 428 429 if err != nil { 430 return ret, err 431 } 432 } 433 434 return ret, nil 435 } 436 437 // RemoveDuplicateNodesIDParent removes duplicate nodes in list with the 438 // same ID and parent 439 func RemoveDuplicateNodesIDParent(nodes []NodeEdge) []NodeEdge { 440 keys := make(map[string]bool) 441 ret := []NodeEdge{} 442 443 for _, n := range nodes { 444 key := n.ID + n.Parent 445 if _, ok := keys[key]; !ok { 446 keys[key] = true 447 ret = append(ret, n) 448 } 449 } 450 451 return ret 452 } 453 454 // RemoveDuplicateNodesID removes duplicate nodes in list with the 455 // same ID (can have different parents) 456 func RemoveDuplicateNodesID(nodes []NodeEdge) []NodeEdge { 457 keys := make(map[string]bool) 458 ret := []NodeEdge{} 459 460 for _, n := range nodes { 461 key := n.ID 462 if _, ok := keys[key]; !ok { 463 keys[key] = true 464 ret = append(ret, n) 465 } 466 } 467 468 return ret 469 }