github.com/simpleiot/simpleiot@v0.18.3/client/serial.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "io" 8 "log" 9 "path/filepath" 10 "strconv" 11 "time" 12 13 "github.com/fsnotify/fsnotify" 14 "github.com/kjx98/crc16" 15 "github.com/nats-io/nats.go" 16 "github.com/simpleiot/simpleiot/data" 17 "github.com/simpleiot/simpleiot/test" 18 "go.bug.st/serial" 19 ) 20 21 // SerialDev represents a serial (MCU) config 22 type SerialDev struct { 23 ID string `node:"id"` 24 Parent string `node:"parent"` 25 Description string `point:"description"` 26 Port string `point:"port"` 27 Baud string `point:"baud"` 28 MaxMessageLength int `point:"maxMessageLength"` 29 HRDestNode string `point:"hrDest"` 30 SyncParent bool `point:"syncParent"` 31 Debug int `point:"debug"` 32 Disabled bool `point:"disabled"` 33 Log string `point:"log"` 34 Rx int `point:"rx"` 35 RxReset bool `point:"rxReset"` 36 Tx int `point:"tx"` 37 TxReset bool `point:"txReset"` 38 HrRx int64 `point:"hrRx"` 39 HrRxReset bool `point:"hrRxReset"` 40 Uptime int `point:"uptime"` 41 ErrorCount int `point:"errorCount"` 42 ErrorCountReset bool `point:"errorCountReset"` 43 ErrorCountHR int `point:"errorCountHR"` 44 ErrorCountResetHR bool `point:"errorCountResetHR"` 45 Rate bool `point:"rate"` 46 RateHR bool `point:"rate"` 47 Connected bool `point:"connected"` 48 Download string `point:"download"` 49 Progress int `point:"progress"` 50 Files []File `child:"file"` 51 } 52 53 type sendData struct { 54 seq byte 55 ack bool 56 subject string 57 points data.Points 58 } 59 60 // SerialDevClient is a SIOT client used to manage serial devices 61 type SerialDevClient struct { 62 nc *nats.Conn 63 config SerialDev 64 stop chan struct{} 65 newPoints chan NewPoints 66 newEdgePoints chan NewPoints 67 wrSeq byte // convention is we increment this right before sending a packet 68 lastSendStats time.Time 69 natsSub string 70 natsSubSerialPoints string 71 natsSubHRUp string 72 parentSubscription *nats.Subscription 73 ratePointCount int 74 ratePointCountHR int 75 rateLastSend time.Time 76 portCobsWrapper *CobsWrapper 77 sendPointsCh chan sendData 78 } 79 80 // NewSerialDevClient ... 81 func NewSerialDevClient(nc *nats.Conn, config SerialDev) Client { 82 ret := &SerialDevClient{ 83 nc: nc, 84 config: config, 85 stop: make(chan struct{}), 86 newPoints: make(chan NewPoints), 87 newEdgePoints: make(chan NewPoints), 88 natsSub: SubjectNodePoints(config.ID), 89 sendPointsCh: make(chan sendData), 90 } 91 92 ret.populateNatsSubjects() 93 94 return ret 95 } 96 97 func (sd *SerialDevClient) populateNatsSubjects() { 98 phrup := fmt.Sprintf("phrup.%v.%v", sd.config.Parent, sd.config.ID) 99 if sd.config.HRDestNode != "" { 100 phrup = fmt.Sprintf("phrup.%v.%v", sd.config.HRDestNode, sd.config.ID) 101 } 102 sd.natsSubHRUp = phrup 103 104 if sd.parentSubscription != nil { 105 err := sd.parentSubscription.Unsubscribe() 106 if err != nil { 107 log.Println("Serial: error unsubscribing from parent sub:", err) 108 } 109 sd.parentSubscription = nil 110 } 111 112 if sd.config.SyncParent { 113 sd.natsSubSerialPoints = SubjectNodePoints(sd.config.Parent) 114 var err error 115 // Copy some config to avoid race conditions 116 serialID := sd.config.ID 117 debug := sd.config.Debug 118 sd.parentSubscription, err = sd.nc.Subscribe(sd.natsSubSerialPoints, func(msg *nats.Msg) { 119 points, err := data.PbDecodePoints(msg.Data) 120 if err != nil { 121 log.Println("Error decoding points in serial parent:", err) 122 return 123 } 124 125 // only send points whose orgin is not the serial node ID as those are just 126 // getting echo'd back 127 var pointsToSend data.Points 128 129 for _, p := range points { 130 if p.Origin != serialID { 131 pointsToSend = append(pointsToSend, p) 132 } 133 } 134 135 if len(pointsToSend) > 0 { 136 if sd.portCobsWrapper == nil { 137 if debug >= 4 { 138 log.Printf("Serial port closed; points not sent: %v", pointsToSend) 139 } 140 return 141 } 142 sd.sendPointsCh <- sendData{points: pointsToSend} 143 } 144 }) 145 if err != nil { 146 log.Println("Error subscribing to serial parent:", err) 147 } 148 } else { 149 sd.natsSubSerialPoints = SubjectNodePoints(sd.config.ID) 150 } 151 } 152 153 // if seq == 0, then sd.wrSeq is used 154 func (sd *SerialDevClient) sendPointsToDevice(seq byte, ack bool, sub string, pts data.Points) error { 155 if seq == 0 { 156 seq = sd.wrSeq 157 } 158 159 if sub == "" { 160 sub = "proto" 161 } 162 163 d, err := SerialEncode(seq, sub, pts) 164 if err != nil { 165 return fmt.Errorf("error encoding points to send to MCU: %w", err) 166 } 167 168 if sd.config.Debug >= 4 { 169 if len(pts) > 0 { 170 log.Printf("SER TX (%v) seq:%v sub:%v :\n%v", sd.config.Description, seq, sub, pts) 171 } else { 172 log.Printf("SER TX (%v) seq:%v sub:%v\n", sd.config.Description, seq, sub) 173 } 174 } 175 176 if sd.portCobsWrapper != nil { 177 _, err = sd.portCobsWrapper.Write(d) 178 if err != nil { 179 return fmt.Errorf("error writing data to port: %w", err) 180 } 181 182 sd.config.Tx++ 183 err = SendPoints(sd.nc, sd.natsSub, 184 data.Points{{Type: data.PointTypeTx, Value: float64(sd.config.Tx)}}, 185 false) 186 187 if err != nil { 188 return fmt.Errorf("Error sending Serial tx stats: %w", err) 189 } 190 } 191 192 if !ack { 193 _ = ack 194 // TODO: we need to check for response and implement retries 195 // yet. 196 } 197 198 return nil 199 } 200 201 type downloadState struct { 202 name string 203 fileBuf *bytes.Buffer 204 packet *bytes.Buffer 205 seq byte 206 currentBlock int 207 } 208 209 // Run the main logic for this client and blocks until stopped 210 func (sd *SerialDevClient) Run() error { 211 log.Println("Starting serial client:", sd.config.Description) 212 213 // -1 indicates nothing is downloading right now 214 dlTimeout := time.NewTicker(10 * time.Second) 215 dlTimeout.Stop() 216 217 if sd.config.Connected { 218 sd.config.Connected = false 219 err := SendNodePoint(sd.nc, sd.config.ID, data.Point{Type: data.PointTypeConnected, Value: 0}, false) 220 if err != nil { 221 log.Println("Error sending connected point") 222 } 223 } 224 225 watcher, err := fsnotify.NewWatcher() 226 if err != nil { 227 return fmt.Errorf("Error creating a new fsnotify watcher: %w", err) 228 229 } 230 defer watcher.Close() 231 232 if sd.config.Port != "" { 233 err := watcher.Add(filepath.Dir(sd.config.Port)) 234 if err != nil { 235 log.Println("Error adding watcher for:", sd.config.Port) 236 } 237 } 238 239 checkPortDur := time.Second * 10 240 timerCheckPort := time.NewTimer(checkPortDur) 241 242 serialReadData := make(chan []byte) 243 listenerClosed := make(chan struct{}) 244 listenerSerialErr := make(chan struct{}) 245 246 dlState := downloadState{} 247 248 dlFileStop := func() { 249 dlState.name = "" 250 _ = SendNodePoint(sd.nc, sd.config.ID, data.Point{Type: data.PointTypeDownload}, true) 251 } 252 253 dlFileSend := func() { 254 if dlState.name == "" { 255 log.Println("File download error, no file queued") 256 return 257 } 258 259 // packet format 260 // sequence (1 byte) 261 // subject (16 bytes, always file) 262 // filename (16 bytes) 263 // block index (4 bytes) 264 // data 265 // crc (2 bytes) 266 267 // we save off packet in struct so we can resend on retries 268 dlState.packet = &bytes.Buffer{} 269 270 sd.wrSeq++ 271 dlState.seq = sd.wrSeq 272 err := dlState.packet.WriteByte(sd.wrSeq) 273 if err != nil { 274 log.Println("Error writing seq to file packet: ", err) 275 dlFileStop() 276 return 277 } 278 279 // subject is always file 280 f := make([]byte, 16) 281 copy(f, []byte("file")) 282 _, err = dlState.packet.Write(f) 283 if err != nil { 284 log.Println("Error writing file to file packet: ", err) 285 dlFileStop() 286 return 287 } 288 289 n := make([]byte, 16) 290 copy(n, []byte(dlState.name)) 291 _, err = dlState.packet.Write(n) 292 if err != nil { 293 log.Println("Error writing filename to file packet: ", err) 294 dlFileStop() 295 return 296 } 297 298 if dlState.fileBuf.Len() <= 0 { 299 dlState.currentBlock = -1 300 } 301 302 // copy block number 303 b := make([]byte, 4) 304 binary.LittleEndian.PutUint32(b, uint32(dlState.currentBlock)) 305 _, err = dlState.packet.Write(b) 306 if err != nil { 307 log.Println("Error writing block numbr to packet: ", err) 308 dlFileStop() 309 return 310 } 311 312 // copy payload 313 _, err = io.CopyN(dlState.packet, dlState.fileBuf, 400) 314 if err != nil { 315 if err != io.EOF { 316 log.Println("Error copying file packet content: ", err) 317 dlFileStop() 318 return 319 } 320 } 321 322 crc := crc16.ChecksumCCITT(dlState.packet.Bytes()) 323 _ = binary.Write(dlState.packet, binary.LittleEndian, crc) 324 325 if sd.config.Debug >= 4 { 326 log.Printf("SER TX file seq:%v name:%v block:%v len:%v", sd.wrSeq, dlState.name, dlState.currentBlock, dlState.packet.Len()) 327 } 328 329 if sd.portCobsWrapper != nil { 330 // _, err = io.Copy(sd.portCobsWrapper, dlState.packet) 331 _, err = sd.portCobsWrapper.Write(dlState.packet.Bytes()) 332 333 if err != nil { 334 log.Println("Error writing file to cobs wrapper: ", err) 335 return 336 } 337 } 338 } 339 340 // dlRetry := func() { 341 342 // } 343 344 dlFileStart := func(name string) { 345 if dlState.name != "" { 346 log.Println("Error, file download already in progress") 347 return 348 } 349 350 if len(name) > 16 { 351 log.Println("file name is too long: ", name) 352 return 353 } 354 355 // see if we can find the file 356 for _, f := range sd.config.Files { 357 if f.Name == name { 358 d, err := f.GetContents() 359 if err != nil { 360 log.Println("Error decoding file: ", err) 361 continue 362 } 363 dlState.fileBuf = bytes.NewBuffer(d) 364 dlState.name = name 365 dlState.currentBlock = 0 366 367 dlFileSend() 368 return 369 } 370 } 371 372 log.Println("Error could not find file: ", name) 373 } 374 375 closePort := func() { 376 if sd.portCobsWrapper != nil { 377 log.Println("Closing serial port:", sd.config.Description) 378 sd.portCobsWrapper.Close() 379 } 380 sd.portCobsWrapper = nil 381 382 sd.config.Connected = false 383 err := SendNodePoint(sd.nc, sd.config.ID, data.Point{Type: data.PointTypeConnected, Value: 0}, false) 384 if err != nil { 385 log.Println("Error sending connected point") 386 } 387 } 388 389 if sd.config.Download != "" { 390 _ = SendNodePoint(sd.nc, sd.config.ID, data.Point{Type: data.PointTypeDownload}, true) 391 } 392 393 listener := func(port io.ReadWriteCloser, maxMessageLen int) { 394 errCount := 0 395 for { 396 buf := make([]byte, maxMessageLen) 397 c, err := port.Read(buf) 398 if err != nil { 399 if err != io.EOF && err.Error() != "Port has been closed" { 400 log.Printf("Error reading port %v: %v\n", sd.config.Description, err) 401 402 listenerSerialErr <- struct{}{} 403 404 // we don't want to reset the port on every COBS 405 // decode error, so accumulate a few before we do this 406 if err == ErrCobsDecodeError || 407 err == ErrCobsTooMuchData { 408 errCount++ 409 if errCount < 10000000 { 410 continue 411 } 412 } 413 414 listenerClosed <- struct{}{} 415 return 416 } 417 } 418 if c <= 0 { 419 continue 420 } 421 422 errCount = 0 423 424 buf = buf[0:c] 425 serialReadData <- buf 426 } 427 } 428 429 openPort := func() { 430 if sd.config.MaxMessageLength <= 0 { 431 sd.config.MaxMessageLength = 1024 432 err := SendPoints(sd.nc, sd.natsSub, 433 data.Points{{Type: data.PointTypeMaxMessageLength, Value: 1024}}, true) 434 if err != nil { 435 log.Println("Error sending max message len message:", err) 436 } 437 } 438 439 // make sure port is closed before we try to (re)open it 440 closePort() 441 442 if sd.config.Disabled { 443 closePort() 444 timerCheckPort.Stop() 445 return 446 } 447 448 var io io.ReadWriteCloser 449 450 if sd.config.Port == "serialfifo" { 451 // we are in test mode and using unix fifos instead of 452 // real serial ports. The fifo must already by started 453 // by the test harness 454 var err error 455 io, err = test.NewFifoB(sd.config.Port) 456 if err != nil { 457 log.Println("SerialDevClient: error opening fifo:", err) 458 return 459 } 460 } else { 461 if sd.config.Port == "" || sd.config.Baud == "" { 462 log.Printf("Serial port %v not configured\n", sd.config.Description) 463 timerCheckPort.Reset(checkPortDur) 464 return 465 } 466 467 baud, err := strconv.Atoi(sd.config.Baud) 468 469 if err != nil { 470 log.Printf("Serial port %v invalid baud\n", sd.config.Description) 471 timerCheckPort.Reset(checkPortDur) 472 return 473 } 474 475 mode := &serial.Mode{ 476 BaudRate: baud, 477 } 478 479 serialPort, err := serial.Open(sd.config.Port, mode) 480 if err != nil { 481 log.Printf("Error opening serial port %v: %v", sd.config.Description, 482 err) 483 timerCheckPort.Reset(checkPortDur) 484 return 485 } 486 487 time.Sleep(time.Millisecond) 488 err = serialPort.SetDTR(false) 489 if err != nil { 490 log.Printf("Error clearing serial port DTR: %v\n", err) 491 } 492 493 time.Sleep(time.Millisecond * 100) 494 err = serialPort.SetDTR(true) 495 if err != nil { 496 log.Printf("Error setting serial port DTR: %v\n", err) 497 } 498 499 io = serialPort 500 } 501 502 sd.portCobsWrapper = NewCobsWrapper(io, sd.config.MaxMessageLength) 503 sd.portCobsWrapper.SetDebug(sd.config.Debug) 504 timerCheckPort.Stop() 505 506 log.Println("Serial port opened:", sd.config.Description) 507 508 go listener(sd.portCobsWrapper, sd.config.MaxMessageLength) 509 510 p := data.Points{{ 511 Time: time.Now(), 512 Type: data.PointTypeTimeSync, 513 }} 514 515 sd.config.Connected = true 516 err := SendNodePoint(sd.nc, sd.config.ID, data.Point{Type: data.PointTypeConnected, Value: 1}, false) 517 if err != nil { 518 log.Println("Error sending connected point") 519 } 520 521 sd.wrSeq++ 522 err = sd.sendPointsToDevice(sd.wrSeq, false, "", p) 523 if err != nil { 524 log.Println("Error sending time sync point to device: %w", err) 525 } 526 } 527 528 openPort() 529 530 exitSerialClient: 531 532 for { 533 select { 534 case <-sd.stop: 535 break exitSerialClient 536 case <-timerCheckPort.C: 537 openPort() 538 case <-listenerClosed: 539 closePort() 540 timerCheckPort.Reset(checkPortDur) 541 case <-listenerSerialErr: 542 sd.config.ErrorCount++ 543 points := []data.Point{{Type: data.PointTypeErrorCount, Value: float64(sd.config.ErrorCount)}} 544 err := SendPoints(sd.nc, sd.natsSub, points, false) 545 if err != nil { 546 log.Println("Error sending error points:", err) 547 } 548 case e, ok := <-watcher.Events: 549 if ok { 550 if e.Name == sd.config.Port { 551 if e.Op == fsnotify.Remove { 552 closePort() 553 } else if e.Op == fsnotify.Create { 554 openPort() 555 } 556 } 557 } 558 case rd := <-serialReadData: 559 if sd.config.Debug >= 8 { 560 log.Println("SER RX RAW:", test.HexDump(rd)) 561 } 562 563 // decode serial packet 564 seq, subject, payload, err := SerialDecode(rd) 565 if err != nil { 566 log.Printf("Serial framing error (sub:%v): %v", subject, err) 567 568 var t string 569 var cnt int 570 571 if subject == "phr" { 572 t = data.PointTypeErrorCountHR 573 sd.config.ErrorCountHR++ 574 cnt = sd.config.ErrorCountHR 575 } else { 576 t = data.PointTypeErrorCount 577 sd.config.ErrorCount++ 578 cnt = sd.config.ErrorCount 579 } 580 581 err := SendPoints(sd.nc, sd.natsSub, []data.Point{{Type: t, Value: float64(cnt)}}, false) 582 if err != nil { 583 log.Println("Error sending error points:", err) 584 } 585 586 break 587 } 588 589 if subject == "ack" { 590 if sd.config.Debug >= 4 { 591 log.Printf("SER RX (%v) seq:%v sub:%v", sd.config.Description, seq, subject) 592 } 593 // TODO we need to handle acks, retries, etc 594 if dlState.name != "" { 595 if seq == dlState.seq { 596 if dlState.currentBlock == -1 { 597 log.Println("File download finished: ", dlState.name) 598 dlFileStop() 599 } else { 600 dlState.currentBlock++ 601 dlFileSend() 602 } 603 } 604 } 605 break 606 } 607 608 if subject == "phr" { 609 // we have high rate points 610 sd.config.HrRx++ 611 err := sd.nc.Publish(sd.natsSubHRUp, payload) 612 if err != nil { 613 log.Println("Error publishing HR data:", err) 614 } 615 sd.ratePointCountHR++ 616 // we're done 617 break 618 } 619 620 if subject == "log" { 621 points := data.Points{{Type: data.PointTypeLog, Text: string(payload)}} 622 623 if sd.config.Debug >= 1 { 624 log.Printf("Serial client %v: log: %v\n", 625 sd.config.Description, string(payload)) 626 } 627 err := SendPoints(sd.nc, sd.natsSubSerialPoints, points, false) 628 if err != nil { 629 log.Println("Error sending log point from MCU:", err) 630 } 631 } 632 633 // we must have a protobuf payload 634 points, errDecode := data.PbDecodeSerialPoints(payload) 635 var adminPoints data.Points 636 637 sd.config.Rx++ 638 639 // make sure time is set on all points 640 for i, p := range points { 641 if p.Time.Year() <= 1980 { 642 points[i].Time = time.Now() 643 } 644 } 645 646 sd.ratePointCount += len(points) 647 648 if errDecode == nil && len(points) > 0 { 649 if sd.config.Debug >= 4 { 650 log.Printf("SER RX (%v) seq:%v sub:%v\n%v", sd.config.Description, seq, subject, points) 651 } 652 653 // send response 654 err := sd.sendPointsToDevice(seq, false, "ack", nil) 655 if err != nil { 656 log.Println("Error sending ack to device:", err) 657 } 658 659 if !sd.config.SyncParent { 660 err = data.MergePoints(sd.config.ID, points, &sd.config) 661 if err != nil { 662 log.Println("error merging new points:", err) 663 } 664 } 665 } else { 666 log.Println("Error decoding serial packet from device:", 667 sd.config.Description, errDecode) 668 sd.config.ErrorCount++ 669 adminPoints = append(adminPoints, 670 data.Point{Type: data.PointTypeErrorCount, Value: float64(sd.config.ErrorCount)}) 671 } 672 673 if time.Since(sd.lastSendStats) > time.Second*5 { 674 adminPoints = append(adminPoints, 675 data.Points{ 676 {Time: time.Now(), Type: data.PointTypeRx, Value: float64(sd.config.Rx)}, 677 {Time: time.Now(), Type: data.PointTypeHrRx, Value: float64(sd.config.HrRx)}, 678 }...) 679 sd.lastSendStats = time.Now() 680 } 681 682 if time.Since(sd.rateLastSend) > time.Second { 683 now := time.Now() 684 elapsedSec := now.Sub(sd.rateLastSend).Seconds() 685 rate := float64(sd.ratePointCount) / elapsedSec 686 rateHR := float64(sd.ratePointCountHR) / elapsedSec 687 adminPoints = append(adminPoints, 688 data.Point{Time: now, Type: data.PointTypeRate, 689 Value: rate}, 690 data.Point{Time: now, Type: data.PointTypeRateHR, 691 Value: rateHR}, 692 ) 693 sd.rateLastSend = now 694 sd.ratePointCount = 0 695 sd.ratePointCountHR = 0 696 } 697 698 if sd.config.SyncParent { 699 // add serial ID to origin for all points we send to the parent 700 for i := range points { 701 points[i].Origin = sd.config.ID 702 } 703 } 704 705 if len(points) > 0 { 706 err := SendPoints(sd.nc, sd.natsSubSerialPoints, points, false) 707 if err != nil { 708 log.Println("Error sending points received from MCU:", err) 709 } 710 } 711 712 if len(adminPoints) > 0 { 713 err := SendPoints(sd.nc, sd.natsSub, adminPoints, false) 714 if err != nil { 715 log.Println("Error sending admin points:", err) 716 } 717 } 718 719 case pts := <-sd.newPoints: 720 op := false 721 updateNatsSubjects := false 722 for _, p := range pts.Points { 723 // check if any of the config changes should cause us to re-open the port 724 if p.Type == data.PointTypePort || 725 p.Type == data.PointTypeBaud || 726 p.Type == data.PointTypeDisabled || 727 p.Type == data.PointTypeMaxMessageLength { 728 op = true 729 } 730 731 if p.Type == data.PointTypePort { 732 err := watcher.Add(filepath.Dir(p.Text)) 733 if err != nil { 734 log.Println("Error adding watcher on serial port name change:", p.Text) 735 } 736 } 737 738 if p.Type == data.PointTypeDisabled { 739 if p.Value == 0 { 740 closePort() 741 } else { 742 op = true 743 } 744 } 745 746 if p.Type == data.PointTypeHRDest { 747 updateNatsSubjects = true 748 } 749 750 if p.Type == data.PointTypeSyncParent { 751 updateNatsSubjects = true 752 } 753 754 if p.Type == data.PointTypeDebug { 755 sd.portCobsWrapper.SetDebug(int(p.Value)) 756 } 757 758 if p.Type == data.PointTypeDownload { 759 if p.Text == "" { 760 log.Println("Stopping download: ", dlState.name) 761 dlFileStop() 762 } else { 763 dlFileStart(p.Text) 764 log.Println("Starting download: ", p.Text) 765 } 766 } 767 } 768 769 err := data.MergePoints(pts.ID, pts.Points, &sd.config) 770 if err != nil { 771 log.Println("error merging new points:", err) 772 } 773 774 if updateNatsSubjects { 775 sd.populateNatsSubjects() 776 } 777 778 if op { 779 openPort() 780 } 781 782 if sd.portCobsWrapper == nil { 783 break 784 } 785 786 if sd.config.ErrorCountReset { 787 points := data.Points{ 788 {Type: data.PointTypeErrorCount, Value: 0}, 789 {Type: data.PointTypeErrorCountReset, Value: 0}, 790 } 791 err = SendPoints(sd.nc, sd.natsSub, points, false) 792 if err != nil { 793 log.Println("Error resetting MCU error count:", err) 794 } 795 796 sd.config.ErrorCountReset = false 797 sd.config.ErrorCount = 0 798 } 799 800 if sd.config.ErrorCountResetHR { 801 points := data.Points{ 802 {Type: data.PointTypeErrorCountHR, Value: 0}, 803 {Type: data.PointTypeErrorCountResetHR, Value: 0}, 804 } 805 err = SendPoints(sd.nc, sd.natsSub, points, false) 806 if err != nil { 807 log.Println("Error resetting MCU error count:", err) 808 } 809 810 sd.config.ErrorCountResetHR = false 811 sd.config.ErrorCountHR = 0 812 } 813 814 if sd.config.RxReset { 815 points := data.Points{ 816 {Type: data.PointTypeRx, Value: 0}, 817 {Type: data.PointTypeRxReset, Value: 0}, 818 } 819 err = SendPoints(sd.nc, sd.natsSub, points, false) 820 if err != nil { 821 log.Println("Error resetting MCU error count:", err) 822 } 823 824 sd.config.RxReset = false 825 sd.config.Rx = 0 826 } 827 828 if sd.config.HrRxReset { 829 points := data.Points{ 830 {Type: data.PointTypeHrRx, Value: 0}, 831 {Type: data.PointTypeHrRxReset, Value: 0}, 832 } 833 err = SendPoints(sd.nc, sd.natsSub, points, false) 834 if err != nil { 835 log.Println("Error resetting MCU error count:", err) 836 } 837 838 sd.config.HrRxReset = false 839 sd.config.HrRx = 0 840 } 841 842 if sd.config.TxReset { 843 points := data.Points{ 844 {Type: data.PointTypeTx, Value: 0}, 845 {Type: data.PointTypeTxReset, Value: 0}, 846 } 847 err = SendPoints(sd.nc, sd.natsSub, points, false) 848 if err != nil { 849 log.Println("Error resetting MCU error count:", err) 850 } 851 852 sd.config.TxReset = false 853 sd.config.Tx = 0 854 } 855 856 // check if we have any points that need sent to MCU 857 if !sd.config.SyncParent { 858 toSend := data.Points{} 859 for _, p := range pts.Points { 860 switch p.Type { 861 case data.PointTypePort, 862 data.PointTypeBaud, 863 data.PointTypeDescription, 864 data.PointTypeErrorCount, 865 data.PointTypeErrorCountReset, 866 data.PointTypeRxReset, 867 data.PointTypeTxReset: 868 continue 869 } 870 871 // strip off Origin as MCU does not need that 872 p.Origin = "" 873 toSend = append(toSend, p) 874 } 875 876 if len(toSend) > 0 { 877 sd.wrSeq++ 878 err := sd.sendPointsToDevice(sd.wrSeq, false, "", toSend) 879 if err != nil { 880 log.Println("Error sending points to serial device:", err) 881 } 882 } 883 } 884 885 case pts := <-sd.newEdgePoints: 886 err := data.MergeEdgePoints(pts.ID, pts.Parent, pts.Points, &sd.config) 887 if err != nil { 888 log.Println("error merging new points:", err) 889 } 890 891 case sData := <-sd.sendPointsCh: 892 err := sd.sendPointsToDevice(sData.seq, sData.ack, sData.subject, sData.points) 893 if err != nil { 894 log.Println("Error sending data to device: ", err) 895 } 896 897 // TODO need to send edge points to MCU, not implemented yet 898 } 899 } 900 901 log.Println("Stopping serial client:", sd.config.Description) 902 closePort() 903 if sd.parentSubscription != nil { 904 err := sd.parentSubscription.Unsubscribe() 905 if err != nil { 906 log.Println("Error unsubscribing:", err) 907 } 908 } 909 910 return nil 911 912 } 913 914 // Stop sends a signal to the Run function to exit 915 func (sd *SerialDevClient) Stop(_ error) { 916 close(sd.stop) 917 } 918 919 // Points is called by the Manager when new points for this 920 // node are received. 921 func (sd *SerialDevClient) Points(nodeID string, points []data.Point) { 922 sd.newPoints <- NewPoints{nodeID, "", points} 923 } 924 925 // EdgePoints is called by the Manager when new edge points for this 926 // node are received. 927 func (sd *SerialDevClient) EdgePoints(nodeID, parentID string, points []data.Point) { 928 sd.newEdgePoints <- NewPoints{nodeID, parentID, points} 929 }