Skip to content

Commit baa60a7

Browse files
authored
Merge pull request #18 from REVrobotics/improve-heartbeats
2 parents 678b741 + 409d244 commit baa60a7

File tree

1 file changed

+76
-32
lines changed

1 file changed

+76
-32
lines changed

src/canWrapper.cc

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <thread>
1313
#include <chrono>
1414
#include <map>
15+
#include <array>
1516
#include <vector>
1617
#include <set>
1718
#include <exception>
@@ -20,7 +21,11 @@
2021
#include "canWrapper.h"
2122
#include "DfuSeFile.h"
2223

23-
#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()"
25+
26+
#define REV_COMMON_HEARTBEAT_ID 0x00502C0
27+
#define SPARK_HEARTBEAT_ID 0x2052C80
28+
#define HEARTBEAT_PERIOD_MS 20
2429

2530
rev::usb::CandleWinUSBDriver* driver = new rev::usb::CandleWinUSBDriver();
2631

@@ -29,11 +34,16 @@ bool halInitialized = false;
2934
uint32_t m_notifier;
3035

3136
std::mutex canDevicesMtx;
37+
// These values should only be accessed while holding canDevicesMtx
3238
std::map<std::string, std::shared_ptr<rev::usb::CANDevice>> canDeviceMap;
3339

3440
std::mutex watchdogMtx;
41+
// These values should only be accessed while holding watchdogMtx
3542
std::vector<std::string> heartbeatsRunning;
36-
auto latestHeartbeatAck = std::chrono::system_clock::now();
43+
bool heartbeatTimeoutExpired = false; // Should only be changed in heartbeatsWatchdog()
44+
std::map<std::string, std::array<uint8_t, 1>> revCommonHeartbeatMap;
45+
std::map<std::string, std::array<uint8_t, 8>> sparkHeartbeatMap;
46+
auto latestHeartbeatAck = std::chrono::steady_clock::now();
3747

3848
// Only call when holding canDevicesMtx
3949
void removeExtraDevicesFromDeviceMap(std::vector<std::string> descriptors) {
@@ -617,6 +627,7 @@ void waitForNotifierAlarm(const Napi::CallbackInfo& info) {
617627
int32_t status;
618628

619629
HAL_UpdateNotifierAlarm(m_notifier, HAL_GetFPGATime(&status) + time, &status);
630+
// TODO(Noah): Don't discard the returned value (this function is marked as [nodiscard])
620631
HAL_WaitForNotifierAlarm(m_notifier, &status);
621632
cb.Call(info.Env().Global(), {info.Env().Null(), Napi::Number::New(info.Env(), status)});
622633
}
@@ -644,7 +655,7 @@ void writeDfuToBin(const Napi::CallbackInfo& info) {
644655

645656
void heartbeatsWatchdog() {
646657
while (true) {
647-
std::this_thread::sleep_for (std::chrono::seconds(1));
658+
std::this_thread::sleep_for (std::chrono::milliseconds(250));
648659

649660
{
650661
// Erase removed CAN buses from heartbeatsRunning
@@ -661,22 +672,51 @@ void heartbeatsWatchdog() {
661672

662673
if (heartbeatsRunning.size() < 1) { break; }
663674

664-
auto now = std::chrono::system_clock::now();
675+
auto now = std::chrono::steady_clock::now();
665676
std::chrono::duration<double> elapsed_seconds = now-latestHeartbeatAck;
666-
if (elapsed_seconds.count() > 1) {
667-
uint8_t sparkMaxHeartbeat[] = {0, 0, 0, 0, 0, 0, 0, 0};
668-
uint8_t revCommonHeartbeat[] = {0};
677+
if (elapsed_seconds.count() >= 1 && !heartbeatTimeoutExpired) {
678+
// The heartbeat timeout just expired
679+
heartbeatTimeoutExpired = true;
680+
uint8_t disabledSparkHeartbeat[] = {0, 0, 0, 0, 0, 0, 0, 0};
681+
uint8_t disabledRevCommonHeartbeat[] = {0};
669682
for(int i = 0; i < heartbeatsRunning.size(); i++) {
670-
_sendCANMessage(heartbeatsRunning[i], 0x2052C80, sparkMaxHeartbeat, 8, -1);
671-
_sendCANMessage(heartbeatsRunning[i], 0x00502C0, revCommonHeartbeat, 1, -1);
683+
if (sparkHeartbeatMap.contains(heartbeatsRunning[i])) {
684+
// Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
685+
_sendCANMessage(descriptor, SPARK_HEARTBEAT_ID, disabledSparkHeartbeat, 8, -1);
686+
687+
_sendCANMessage(heartbeatsRunning[i], SPARK_HEARTBEAT_ID, disabledSparkHeartbeat, 8, HEARTBEAT_PERIOD_MS);
688+
}
689+
if (revCommonHeartbeatMap.contains(heartbeatsRunning[i])) {
690+
// Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
691+
_sendCANMessage(descriptor, REV_COMMON_HEARTBEAT_ID, disabledRevCommonHeartbeat, 1, -1);
692+
693+
_sendCANMessage(heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, disabledRevCommonHeartbeat, 1, HEARTBEAT_PERIOD_MS);
694+
}
695+
}
696+
} else if (elapsed_seconds.count() < 1 && heartbeatTimeoutExpired) {
697+
// The heartbeat timeout is newly un-expired
698+
heartbeatTimeoutExpired = false;
699+
for(int i = 0; i < heartbeatsRunning.size(); i++) {
700+
if (auto heartbeatEntry = sparkHeartbeatMap.find(heartbeatsRunning[i]); heartbeatEntry != sparkHeartbeatMap.end()) {
701+
// Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
702+
_sendCANMessage(descriptor, SPARK_HEARTBEAT_ID, heartbeatEntry->second.data(), 8, -1);
703+
704+
_sendCANMessage(heartbeatsRunning[i], SPARK_HEARTBEAT_ID, heartbeatEntry->second.data(), 8, HEARTBEAT_PERIOD_MS);
705+
}
706+
if (auto heartbeatEntry = revCommonHeartbeatMap.find(heartbeatsRunning[i]); heartbeatEntry != revCommonHeartbeatMap.end()) {
707+
// Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
708+
_sendCANMessage(descriptor, REV_COMMON_HEARTBEAT_ID, heartbeatEntry->second.data(), 1, -1);
709+
710+
_sendCANMessage(heartbeatsRunning[i], REV_COMMON_HEARTBEAT_ID, heartbeatEntry->second.data(), 1, HEARTBEAT_PERIOD_MS);
711+
}
672712
}
673713
}
674714
}
675715
}
676716

677717
void ackHeartbeats(const Napi::CallbackInfo& info) {
678718
std::scoped_lock lock{watchdogMtx};
679-
latestHeartbeatAck = std::chrono::system_clock::now();
719+
latestHeartbeatAck = std::chrono::steady_clock::now();
680720
}
681721

682722
// Params:
@@ -691,14 +731,19 @@ void startRevCommonHeartbeat(const Napi::CallbackInfo& info) {
691731
if (deviceIterator == canDeviceMap.end()) return;
692732
}
693733

694-
uint8_t payload[] = {1};
695-
_sendCANMessage(descriptor, 0x00502C0, payload, 1, 20);
734+
std::array<uint8_t, 1> payload = {1};
696735

697736
std::scoped_lock lock{watchdogMtx};
698737

738+
if (!heartbeatTimeoutExpired) {
739+
_sendCANMessage(descriptor, REV_COMMON_HEARTBEAT_ID, payload.data(), 1, HEARTBEAT_PERIOD_MS);
740+
}
741+
742+
revCommonHeartbeatMap[descriptor] = payload;
743+
699744
if (heartbeatsRunning.size() == 0) {
700745
heartbeatsRunning.push_back(descriptor);
701-
latestHeartbeatAck = std::chrono::system_clock::now();
746+
latestHeartbeatAck = std::chrono::steady_clock::now();
702747
std::thread hb(heartbeatsWatchdog);
703748
hb.detach();
704749
} else {
@@ -717,41 +762,40 @@ void setSparkMaxHeartbeatData(const Napi::CallbackInfo& info) {
717762
std::string descriptor = info[0].As<Napi::String>().Utf8Value();
718763
Napi::Array dataParam = info[1].As<Napi::Array>();
719764

720-
uint8_t heartbeat[] = {0, 0, 0, 0, 0, 0, 0, 0};
765+
std::array<uint8_t, 8> heartbeat = {0, 0, 0, 0, 0, 0, 0, 0};
721766

722767
{
723768
std::scoped_lock lock{canDevicesMtx};
724769
auto deviceIterator = canDeviceMap.find(descriptor);
725770
if (deviceIterator == canDeviceMap.end()) return;
726771
}
727772

728-
_sendCANMessage(descriptor, 0x2052C80, heartbeat, 8, -1);
729-
std::this_thread::sleep_for(std::chrono::milliseconds(50));
730-
731773
int sum = 0;
732774
for (uint32_t i = 0; i < dataParam.Length(); i++) {
733775
heartbeat[i] = dataParam.Get(i).As<Napi::Number>().Uint32Value();
734776
sum+= heartbeat[i];
735777
}
736778

737-
if (sum == 0) {
738-
_sendCANMessage(descriptor, 0x2052C80, heartbeat, 8, -1);
779+
std::scoped_lock lock{watchdogMtx};
780+
781+
if (!heartbeatTimeoutExpired) {
782+
// Clear the scheduled heartbeat that has outdated data so that the updated one gets sent out immediately
783+
_sendCANMessage(descriptor, SPARK_HEARTBEAT_ID, heartbeat.data(), 8, -1);
784+
785+
_sendCANMessage(descriptor, SPARK_HEARTBEAT_ID, heartbeat.data(), 8, HEARTBEAT_PERIOD_MS);
739786
}
740-
else {
741-
_sendCANMessage(descriptor, 0x2052C80, heartbeat, 8, 10);
742787

743-
std::scoped_lock lock{watchdogMtx};
788+
sparkHeartbeatMap[descriptor] = heartbeat;
744789

745-
if (heartbeatsRunning.size() == 0) {
746-
heartbeatsRunning.push_back(descriptor);
747-
latestHeartbeatAck = std::chrono::system_clock::now();
748-
std::thread hb(heartbeatsWatchdog);
749-
hb.detach();
750-
} else {
751-
for(int i = 0; i < heartbeatsRunning.size(); i++) {
752-
if (heartbeatsRunning[i].compare(descriptor) == 0) return;
753-
}
754-
heartbeatsRunning.push_back(descriptor);
790+
if (heartbeatsRunning.size() == 0) {
791+
heartbeatsRunning.push_back(descriptor);
792+
latestHeartbeatAck = std::chrono::steady_clock::now();
793+
std::thread hb(heartbeatsWatchdog);
794+
hb.detach();
795+
} else {
796+
for(int i = 0; i < heartbeatsRunning.size(); i++) {
797+
if (heartbeatsRunning[i].compare(descriptor) == 0) return;
755798
}
799+
heartbeatsRunning.push_back(descriptor);
756800
}
757801
}

0 commit comments

Comments
 (0)