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  }