33#include  < rev/CANMessage.h> 
44#include  < rev/CANStatus.h> 
55#include  < rev/CANBridgeUtils.h> 
6- #ifdef  _WIN32
76#include  < rev/Drivers/CandleWinUSB/CandleWinUSBDriver.h> 
87#include  < rev/Drivers/CandleWinUSB/CandleWinUSBDevice.h> 
9- #else 
10- 
11- #include  " rev/Drivers/SerialPort/SerialDriver.h" 
12- #endif 
13- 
14- 
158#include  < utils/ThreadUtils.h> 
169#include  < hal/HAL.h> 
1710#include  < hal/CAN.h> 
1811#include  < napi.h> 
1912#include  < thread> 
2013#include  < chrono> 
2114#include  < map> 
15+ #include  < array> 
2216#include  < vector> 
2317#include  < set> 
2418#include  < exception> 
2721#include  " canWrapper.h" 
2822#include  " DfuSeFile.h" 
2923
30- #define  DEVICE_NOT_FOUND_ERROR  " Device not found.   Make sure to run getDevices()" 
24+ #define  DEVICE_NOT_FOUND_ERROR  " Device not found. Make sure to run getDevices()" 
3125
32- rev::usb::CANDriver* driver = 
33- #ifdef  _WIN32
34-     new  rev::usb::CandleWinUSBDriver();
35- #else 
36-     new  rev::usb::SerialDriver();
37- #endif 
26+ #define  REV_COMMON_HEARTBEAT_ID  0x00502C0 
27+ #define  SPARK_HEARTBEAT_ID  0x2052C80 
28+ #define  HEARTBEAT_PERIOD_MS  20 
29+ 
30+ #define  SPARK_HEARTBEAT_LENGTH  8 
31+ #define  REV_COMMON_HEARTBEAT_LENGTH  1 
32+ uint8_t  disabledSparkHeartbeat[] = {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
33+ uint8_t  disabledRevCommonHeartbeat[] = {0 };
34+ 
35+ rev::usb::CandleWinUSBDriver* driver = new  rev::usb::CandleWinUSBDriver();
3836
3937std::set<std::string> devicesRegisteredToHal; //  TODO(Noah): Protect with mutex
4038bool  halInitialized = false ;
4139uint32_t  m_notifier;
4240
4341std::mutex canDevicesMtx;
42+ //  These values should only be accessed while holding canDevicesMtx
4443std::map<std::string, std::shared_ptr<rev::usb::CANDevice>> canDeviceMap;
4544
4645std::mutex watchdogMtx;
46+ //  These values should only be accessed while holding watchdogMtx
4747std::vector<std::string> heartbeatsRunning;
48- auto  latestHeartbeatAck = std::chrono::system_clock::now();
48+ bool  heartbeatTimeoutExpired = false ; //  Should only be changed in heartbeatsWatchdog()
49+ std::map<std::string, std::array<uint8_t , REV_COMMON_HEARTBEAT_LENGTH>> revCommonHeartbeatMap;
50+ std::map<std::string, std::array<uint8_t , SPARK_HEARTBEAT_LENGTH>> sparkHeartbeatMap;
51+ auto  latestHeartbeatAck = std::chrono::steady_clock::now();
4952
5053//  Only call when holding canDevicesMtx
5154void  removeExtraDevicesFromDeviceMap (std::vector<std::string> descriptors) {
@@ -239,7 +242,7 @@ Napi::Object receiveMessage(const Napi::CallbackInfo& info) {
239242    size_t  messageSize = message->GetSize ();
240243    const  uint8_t * messageData = message->GetData ();
241244    Napi::Array napiMessage = Napi::Array::New (env, messageSize);
242-     for  (size_t  i = 0 ; i < messageSize; i++) {
245+     for  (int  i = 0 ; i < messageSize; i++) {
243246        napiMessage[i] =  messageData[i];
244247    }
245248    Napi::Object messageInfo = Napi::Object::New (env);
@@ -332,7 +335,7 @@ Napi::Number openStreamSession(const Napi::CallbackInfo& info) {
332335    try  {
333336        rev::usb::CANStatus status = device->OpenStreamSession (&sessionHandle, filter, maxSize);
334337        if  (status != rev::usb::CANStatus::kOk ) {
335-             Napi::Error::New (env, " Opening stream session failed with error code " std::to_string (( int )status) ).ThrowAsJavaScriptException ();
338+             Napi::Error::New (env, " Opening stream session failed with error code " ( int )status).ThrowAsJavaScriptException ();
336339        } else  {
337340            return  Napi::Number::New (env, sessionHandle);
338341        }
@@ -629,6 +632,7 @@ void waitForNotifierAlarm(const Napi::CallbackInfo& info) {
629632    int32_t  status;
630633
631634    HAL_UpdateNotifierAlarm (m_notifier, HAL_GetFPGATime (&status) + time, &status);
635+     //  TODO(Noah): Don't discard the returned value (this function is marked as [nodiscard])
632636    HAL_WaitForNotifierAlarm (m_notifier, &status);
633637    cb.Call (info.Env ().Global (), {info.Env ().Null (), Napi::Number::New (info.Env (), status)});
634638}
@@ -654,41 +658,70 @@ void writeDfuToBin(const Napi::CallbackInfo& info) {
654658    cb.Call (info.Env ().Global (), {info.Env ().Null (), Napi::Number::New (info.Env (), status)});
655659}
656660
661+ void  cleanupHeartbeatsRunning () {
662+     //  Erase removed CAN buses from heartbeatsRunning
663+     std::scoped_lock lock{watchdogMtx, canDevicesMtx};
664+     for (int  i = 0 ; i < heartbeatsRunning.size (); i++) {
665+         auto  deviceIterator = canDeviceMap.find (heartbeatsRunning[i]);
666+         if  (deviceIterator == canDeviceMap.end ()) {
667+             heartbeatsRunning.erase (heartbeatsRunning.begin () + i);
668+         }
669+     }
670+ }
671+ 
657672void  heartbeatsWatchdog () {
658673    while  (true ) {
659-         std::this_thread::sleep_for  (std::chrono::seconds (1 ));
660- 
661-         {
662-             //  Erase removed CAN buses from heartbeatsRunning
663-             std::scoped_lock lock{watchdogMtx, canDevicesMtx};
664-             for (size_t  i = 0 ; i < heartbeatsRunning.size (); i++) {
665-                 auto  deviceIterator = canDeviceMap.find (heartbeatsRunning[i]);
666-                 if  (deviceIterator == canDeviceMap.end ()) {
667-                     heartbeatsRunning.erase (heartbeatsRunning.begin () + i);
668-                 }
669-             }
670-         }
674+         std::this_thread::sleep_for  (std::chrono::milliseconds (250 ));
675+ 
676+         cleanupHeartbeatsRunning ();
671677
672678        std::scoped_lock lock{watchdogMtx};
673679
674680        if  (heartbeatsRunning.size () < 1 ) { break ; }
675681
676-         auto  now = std::chrono::system_clock ::now ();
682+         auto  now = std::chrono::steady_clock ::now ();
677683        std::chrono::duration<double > elapsed_seconds = now-latestHeartbeatAck;
678-         if  (elapsed_seconds.count () > 1 ) {
679-             uint8_t  sparkMaxHeartbeat[] = {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
680-             uint8_t  revCommonHeartbeat[] = {0 };
681-             for (size_t  i = 0 ; i < heartbeatsRunning.size (); i++) {
682-                 _sendCANMessage (heartbeatsRunning[i], 0x2052C80 , sparkMaxHeartbeat, 8 , -1 );
683-                 _sendCANMessage (heartbeatsRunning[i], 0x00502C0 , revCommonHeartbeat, 1 , -1 );
684+         if  (elapsed_seconds.count () >= 1  && !heartbeatTimeoutExpired) {
685+             //  The heartbeat timeout just expired
686+             heartbeatTimeoutExpired = true ;
687+             for (int  i = 0 ; i < heartbeatsRunning.size (); i++) {
688+                 if  (sparkHeartbeatMap.contains (heartbeatsRunning[i])) {
689+                     //  Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
690+                     _sendCANMessage (heartbeatsRunning[i], SPARK_HEARTBEAT_ID, disabledSparkHeartbeat, SPARK_HEARTBEAT_LENGTH, -1 );
691+ 
692+                     _sendCANMessage (heartbeatsRunning[i], SPARK_HEARTBEAT_ID, disabledSparkHeartbeat, SPARK_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
693+                 }
694+                 if  (revCommonHeartbeatMap.contains (heartbeatsRunning[i])) {
695+                     //  Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
696+                     _sendCANMessage (heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, disabledRevCommonHeartbeat, REV_COMMON_HEARTBEAT_LENGTH, -1 );
697+ 
698+                     _sendCANMessage (heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, disabledRevCommonHeartbeat, REV_COMMON_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
699+                 }
700+             }
701+         } else  if  (elapsed_seconds.count () < 1  && heartbeatTimeoutExpired) {
702+             //  The heartbeat timeout is newly un-expired
703+             heartbeatTimeoutExpired = false ;
704+             for (int  i = 0 ; i < heartbeatsRunning.size (); i++) {
705+                 if  (auto  heartbeatEntry = sparkHeartbeatMap.find (heartbeatsRunning[i]); heartbeatEntry != sparkHeartbeatMap.end ()) {
706+                     //  Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
707+                     _sendCANMessage (heartbeatsRunning[i], SPARK_HEARTBEAT_ID, heartbeatEntry->second .data (), SPARK_HEARTBEAT_LENGTH, -1 );
708+ 
709+                     _sendCANMessage (heartbeatsRunning[i], SPARK_HEARTBEAT_ID, heartbeatEntry->second .data (), SPARK_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
710+                 }
711+                 if  (auto  heartbeatEntry = revCommonHeartbeatMap.find (heartbeatsRunning[i]); heartbeatEntry != revCommonHeartbeatMap.end ()) {
712+                     //  Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
713+                     _sendCANMessage (heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, heartbeatEntry->second .data (), REV_COMMON_HEARTBEAT_LENGTH, -1 );
714+ 
715+                     _sendCANMessage (heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, heartbeatEntry->second .data (), REV_COMMON_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
716+                 }
684717            }
685718        }
686719    }
687720}
688721
689722void  ackHeartbeats (const  Napi::CallbackInfo& info) {
690723    std::scoped_lock lock{watchdogMtx};
691-     latestHeartbeatAck = std::chrono::system_clock ::now ();
724+     latestHeartbeatAck = std::chrono::steady_clock ::now ();
692725}
693726
694727//  Params:
@@ -703,18 +736,23 @@ void startRevCommonHeartbeat(const Napi::CallbackInfo& info) {
703736        if  (deviceIterator == canDeviceMap.end ()) return ;
704737    }
705738
706-     uint8_t  payload[] = {1 };
707-     _sendCANMessage (descriptor, 0x00502C0 , payload, 1 , 20 );
739+     std::array<uint8_t , REV_COMMON_HEARTBEAT_LENGTH> payload = {1 };
708740
709741    std::scoped_lock lock{watchdogMtx};
710742
743+     if  (!heartbeatTimeoutExpired) {
744+         _sendCANMessage (descriptor, REV_COMMON_HEARTBEAT_ID, payload.data (), REV_COMMON_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
745+     }
746+ 
747+     revCommonHeartbeatMap[descriptor] = payload;
748+ 
711749    if  (heartbeatsRunning.size () == 0 ) {
712750        heartbeatsRunning.push_back (descriptor);
713-         latestHeartbeatAck = std::chrono::system_clock ::now ();
751+         latestHeartbeatAck = std::chrono::steady_clock ::now ();
714752        std::thread hb (heartbeatsWatchdog);
715753        hb.detach ();
716754    } else  {
717-         for (size_t  i = 0 ; i < heartbeatsRunning.size (); i++) {
755+         for (int  i = 0 ; i < heartbeatsRunning.size (); i++) {
718756            if  (heartbeatsRunning[i].compare (descriptor) == 0 ) return ;
719757        }
720758        heartbeatsRunning.push_back (descriptor);
@@ -729,50 +767,53 @@ void setSparkMaxHeartbeatData(const Napi::CallbackInfo& info) {
729767    std::string descriptor = info[0 ].As <Napi::String>().Utf8Value ();
730768    Napi::Array dataParam = info[1 ].As <Napi::Array>();
731769
732-     uint8_t   heartbeat[]  = {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
770+     std::array< uint8_t , SPARK_HEARTBEAT_LENGTH>  heartbeat = {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
733771
734772    {
735773        std::scoped_lock lock{canDevicesMtx};
736774        auto  deviceIterator = canDeviceMap.find (descriptor);
737775        if  (deviceIterator == canDeviceMap.end ()) return ;
738776    }
739777
740-     _sendCANMessage (descriptor, 0x2052C80 , heartbeat, 8 , -1 );
741-     std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
742- 
743778    int  sum = 0 ;
744779    for  (uint32_t  i = 0 ; i < dataParam.Length (); i++) {
745780        heartbeat[i] = dataParam.Get (i).As <Napi::Number>().Uint32Value ();
746781        sum+= heartbeat[i];
747782    }
748783
749-     if  (sum == 0 ) {
750-         _sendCANMessage (descriptor, 0x2052C80 , heartbeat, 8 , -1 );
784+     std::scoped_lock lock{watchdogMtx};
785+ 
786+     if  (!heartbeatTimeoutExpired) {
787+         //  Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
788+         _sendCANMessage (descriptor, SPARK_HEARTBEAT_ID, heartbeat.data (), SPARK_HEARTBEAT_LENGTH, -1 );
789+ 
790+         _sendCANMessage (descriptor, SPARK_HEARTBEAT_ID, heartbeat.data (), SPARK_HEARTBEAT_LENGTH, HEARTBEAT_PERIOD_MS);
751791    }
752-     else  {
753-         _sendCANMessage (descriptor, 0x2052C80 , heartbeat, 8 , 10 );
754792
755-         std::scoped_lock lock{watchdogMtx} ;
793+     sparkHeartbeatMap[descriptor] = heartbeat ;
756794
757-         if  (heartbeatsRunning.size () == 0 ) {
758-             heartbeatsRunning.push_back (descriptor);
759-             latestHeartbeatAck = std::chrono::system_clock::now ();
760-             std::thread hb (heartbeatsWatchdog);
761-             hb.detach ();
762-         } else  {
763-             for (size_t  i = 0 ; i < heartbeatsRunning.size (); i++) {
764-                 if  (heartbeatsRunning[i].compare (descriptor) == 0 ) return ;
765-             }
766-             heartbeatsRunning.push_back (descriptor);
795+     if  (heartbeatsRunning.size () == 0 ) {
796+         heartbeatsRunning.push_back (descriptor);
797+         latestHeartbeatAck = std::chrono::steady_clock::now ();
798+         std::thread hb (heartbeatsWatchdog);
799+         hb.detach ();
800+     } else  {
801+         for (int  i = 0 ; i < heartbeatsRunning.size (); i++) {
802+             if  (heartbeatsRunning[i].compare (descriptor) == 0 ) return ;
767803        }
804+         heartbeatsRunning.push_back (descriptor);
768805    }
769806}
770807
771- /* *
772-  * This function was removed from commit b0ca096624286b1e975eaaa816e38599933b7e84, which broke MSVC builds. 
773-  * It has been re-added here to fix the build. 
774-  */  
775808void  stopHeartbeats (const  Napi::CallbackInfo& info) {
776-     // ! TODO: Reimplement this function with current codebase
777-     Napi::Env env = info.Env ();
809+     std::string descriptor = info[0 ].As <Napi::String>().Utf8Value ();
810+     bool  sendDisabledHeartbeatsFirst = info[1 ].As <Napi::Boolean>().Value ();
811+ 
812+     //  0 sends and then cancels, -1 cancels without sending
813+     const  int  repeatPeriod = sendDisabledHeartbeatsFirst ? 0  : -1 ;
814+ 
815+     std::scoped_lock lock{watchdogMtx};
816+     //  Send disabled SPARK and REV common heartbeats and un-schedule them for the future
817+     _sendCANMessage (descriptor, SPARK_HEARTBEAT_ID, disabledSparkHeartbeat, SPARK_HEARTBEAT_LENGTH, repeatPeriod);
818+     _sendCANMessage (descriptor, REV_COMMON_HEARTBEAT_ID, disabledRevCommonHeartbeat, REV_COMMON_HEARTBEAT_LENGTH, repeatPeriod);
778819}
0 commit comments