Skip to content

Commit 9a8b548

Browse files
authored
Merge pull request #1 from ThadHouse/versionreporting
Add version reporting to can daemon
2 parents cfc2216 + 9ce453b commit 9a8b548

File tree

6 files changed

+343
-228
lines changed

6 files changed

+343
-228
lines changed

CMakeLists.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ add_subdirectory(common)
9393
add_subdirectory(radio)
9494

9595
if (MRC_BUILD)
96-
add_subdirectory(powerdistribution)
96+
add_subdirectory(kitcan)
9797
endif()
9898

9999
if (MRC_BUILD)
100100
install(
101-
TARGETS RadioDaemon PowerDistributionDaemon
101+
TARGETS RadioDaemon KitCanDaemon
102102
RUNTIME DESTINATION bin
103103
LIBRARY DESTINATION lib
104104
ARCHIVE DESTINATION lib

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Currently, allwpilib is a submodule for all builds. However eventually we plan o
66

77
## Services
88

9-
### Power Distribution
9+
### KitCan
1010

11-
This service reads the Power Distribution can data, and sends that data to the DS service. That service then sends the data to the DS allowing integrated logging.
11+
This service reads the Power Distribution and Pnequmatics can data, and sends that data to the DS service. That service then sends the data to the DS allowing integrated logging and version reporting.
1212

1313
### Radio
1414

kitcan/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
project(KitCanDaemon)
2+
3+
add_executable(KitCanDaemon main.cpp)
4+
target_compile_features(KitCanDaemon PRIVATE cxx_std_20)
5+
wpilib_target_warnings(KitCanDaemon)
6+
target_link_libraries(KitCanDaemon PRIVATE Common ntcore wpinet wpiutil)
7+
8+
if (MRC_BUILD)
9+
target_compile_definitions(KitCanDaemon PRIVATE MRC_DAEMON_BUILD)
10+
endif()

kitcan/main.cpp

+329
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
#if defined(__linux__) && defined(MRC_DAEMON_BUILD)
2+
#include <signal.h>
3+
#endif
4+
#include <stdio.h>
5+
6+
#include "version.h"
7+
8+
#include <linux/can.h>
9+
#include <linux/can/raw.h>
10+
#include <net/if.h>
11+
#include <sys/ioctl.h>
12+
13+
#include <wpinet/EventLoopRunner.h>
14+
#include <wpinet/uv/Poll.h>
15+
16+
#include "networktables/NetworkTableInstance.h"
17+
#include "networktables/RawTopic.h"
18+
#include "networktables/IntegerTopic.h"
19+
#include "networktables/StringArrayTopic.h"
20+
#include "networktables/BooleanTopic.h"
21+
22+
#define NUM_CAN_BUSES 5
23+
24+
static constexpr uint32_t deviceTypeMask = 0x3F000000;
25+
static constexpr uint32_t powerDistributionFilter = 0x08000000;
26+
static constexpr uint32_t pneumaticsFilter = 0x09000000;
27+
static constexpr uint32_t manufacturerMask = 0x00FF0000;
28+
static constexpr uint32_t ctreFilter = 0x00040000;
29+
static constexpr uint32_t revFilter = 0x00050000;
30+
31+
constexpr uint32_t revPhVersionPacketMask = 0x09052600;
32+
constexpr uint32_t revPhAnyPacketMask = 0x09050000;
33+
constexpr uint32_t revPdhVersionPacketMask = 0x08052600;
34+
35+
struct CanState {
36+
int socketHandle{-1};
37+
nt::IntegerPublisher deviceIdPublisher;
38+
std::array<nt::RawPublisher, 4> framePublishers;
39+
nt::StringArrayPublisher versionPublisher;
40+
std::unordered_map<uint32_t, std::string> sentVersions;
41+
unsigned busId{0};
42+
43+
~CanState() {
44+
if (socketHandle != -1) {
45+
close(socketHandle);
46+
}
47+
}
48+
49+
void maybeSendVersionRequest(uint32_t canId);
50+
51+
void handleRevVersionFrame(const canfd_frame& frame);
52+
53+
void handleCanFrame(const canfd_frame& frame);
54+
void handlePowerFrame(const canfd_frame& frame);
55+
void handlePneumaticsFrame(const canfd_frame& frame);
56+
bool startUvLoop(unsigned bus, const nt::NetworkTableInstance& ntInst,
57+
wpi::uv::Loop& loop);
58+
};
59+
60+
void CanState::maybeSendVersionRequest(uint32_t canId) {
61+
auto& sent = sentVersions[canId];
62+
if (!sent.empty()) {
63+
return;
64+
}
65+
66+
canfd_frame frame;
67+
memset(&frame, 0, sizeof(frame));
68+
frame.can_id = canId | CAN_EFF_FLAG | CAN_RTR_FLAG;
69+
frame.len = 8;
70+
71+
printf("Requesting %x version frame\n", canId);
72+
73+
send(socketHandle, &frame, CAN_MTU, 0);
74+
}
75+
76+
void CanState::handleRevVersionFrame(const canfd_frame& frame) {
77+
if (frame.len < 8) {
78+
return;
79+
}
80+
81+
uint8_t year = frame.data[2];
82+
uint8_t minor = frame.data[1];
83+
uint8_t fix = frame.data[0];
84+
85+
uint32_t frameId = frame.can_id & CAN_EFF_MASK;
86+
87+
char buf[32];
88+
snprintf(buf, sizeof(buf), "%u.%u.%u", year, minor, fix);
89+
90+
std::array<std::string, 3> sendData;
91+
sendData[0] = std::to_string(frameId);
92+
sendData[1] = ((frame.can_id & deviceTypeMask) == pneumaticsFilter)
93+
? "Rev PH"
94+
: "Rev PDH";
95+
sendData[2] = buf;
96+
97+
fmt::print("Setting {:x} {} {}\n", frameId, sendData[1], sendData[2]);
98+
99+
versionPublisher.Set(sendData);
100+
101+
sentVersions[frameId] = buf;
102+
}
103+
104+
void CanState::handlePneumaticsFrame(const canfd_frame& frame) {
105+
// The only thing we're doing with pneumatics is version receiving.
106+
107+
if ((frame.can_id & revPhVersionPacketMask) == revPhVersionPacketMask) {
108+
handleRevVersionFrame(frame);
109+
} else if ((frame.can_id & revPhAnyPacketMask) == revPhAnyPacketMask) {
110+
maybeSendVersionRequest(revPhVersionPacketMask | (frame.can_id & 0x3F));
111+
}
112+
}
113+
114+
void CanState::handleCanFrame(const canfd_frame& frame) {
115+
// Can't support FD frames
116+
if (frame.flags & CANFD_FDF) {
117+
return;
118+
}
119+
120+
// Looking for Device Type 8 or 9.
121+
// That will tell us what we're handling
122+
uint32_t maskedDeviceType = frame.can_id & deviceTypeMask;
123+
124+
if (maskedDeviceType == powerDistributionFilter) {
125+
handlePowerFrame(frame);
126+
} else if (maskedDeviceType == pneumaticsFilter) {
127+
handlePneumaticsFrame(frame);
128+
}
129+
}
130+
131+
void CanState::handlePowerFrame(const canfd_frame& frame) {
132+
uint16_t apiId = (frame.can_id >> 6) & 0x3FF;
133+
134+
int frameNum = 0;
135+
uint32_t deviceId = frame.can_id & 0x1FFF003F;
136+
137+
if (frame.can_id & 0x10000) {
138+
// Rev Frame
139+
if (apiId == 0x98) {
140+
// Version frame
141+
handleRevVersionFrame(frame);
142+
return;
143+
} else if (apiId < 0x60 || apiId > 0x63) {
144+
// Not valid
145+
return;
146+
}
147+
148+
maybeSendVersionRequest(revPdhVersionPacketMask | (deviceId & 0x3F));
149+
150+
frameNum = apiId - 0x60;
151+
} else {
152+
// CTRE frame
153+
154+
if (apiId == 0x5D) {
155+
// Special case
156+
frameNum = 3;
157+
} else {
158+
if (apiId < 0x50 || apiId > 0x52) {
159+
// Not valid
160+
return;
161+
}
162+
163+
frameNum = apiId - 0x50;
164+
}
165+
}
166+
167+
deviceIdPublisher.Set(deviceId);
168+
169+
std::span<const uint8_t> frameSpan = {
170+
reinterpret_cast<const uint8_t*>(frame.data), frame.len};
171+
172+
if (frameNum < 0 || frameNum >= static_cast<int>(framePublishers.size())) {
173+
printf("BUGBUG logic error invalid frame number\n");
174+
return;
175+
}
176+
177+
framePublishers[frameNum].Set(frameSpan);
178+
}
179+
180+
bool CanState::startUvLoop(unsigned bus, const nt::NetworkTableInstance& ntInst,
181+
wpi::uv::Loop& loop) {
182+
if (busId >= NUM_CAN_BUSES) {
183+
return false;
184+
}
185+
186+
busId = bus;
187+
188+
nt::PubSubOptions options;
189+
options.sendAll = true;
190+
options.keepDuplicates = true;
191+
options.periodic = 0.005;
192+
193+
auto busIdStr = std::to_string(busId);
194+
195+
for (size_t i = 0; i < framePublishers.size(); i++) {
196+
auto iStr = std::to_string(i);
197+
framePublishers[i] =
198+
ntInst.GetRawTopic("/pd/" + busIdStr + "/frame" + iStr)
199+
.Publish("pd", options);
200+
}
201+
202+
deviceIdPublisher =
203+
ntInst.GetIntegerTopic("/pd/" + busIdStr + "/deviceid").Publish();
204+
205+
versionPublisher =
206+
ntInst.GetStringArrayTopic("/Netcomm/Reporting/UserVersionStr")
207+
.Publish(options);
208+
209+
socketHandle =
210+
socket(PF_CAN, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, CAN_RAW);
211+
212+
if (socketHandle == -1) {
213+
return false;
214+
}
215+
216+
// Filter to PD or pneumatics device type.
217+
// Both mfg types have the "4" bit set. They just
218+
// differ on the 1 bit. So a single filter can be used,
219+
// ignoring that bit.
220+
// Same thing for 8 vs 9 for the device type
221+
struct can_filter filter {
222+
.can_id = 0x08040000 | CAN_EFF_FLAG,
223+
.can_mask = 0x1EFE0000 | CAN_EFF_FLAG,
224+
};
225+
226+
if (setsockopt(socketHandle, SOL_CAN_RAW, CAN_RAW_FILTER, &filter,
227+
sizeof(filter)) == -1) {
228+
return false;
229+
}
230+
231+
ifreq ifr;
232+
std::snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "can%u", busId);
233+
234+
if (ioctl(socketHandle, SIOCGIFINDEX, &ifr) == -1) {
235+
return false;
236+
}
237+
238+
sockaddr_can addr;
239+
std::memset(&addr, 0, sizeof(addr));
240+
addr.can_family = AF_CAN;
241+
addr.can_ifindex = ifr.ifr_ifindex;
242+
243+
if (bind(socketHandle, reinterpret_cast<const sockaddr*>(&addr),
244+
sizeof(addr)) == -1) {
245+
return false;
246+
}
247+
248+
auto poll = wpi::uv::Poll::Create(loop, socketHandle);
249+
if (!poll) {
250+
return false;
251+
}
252+
253+
poll->pollEvent.connect([this, fd = socketHandle](int mask) {
254+
if (mask & UV_READABLE) {
255+
canfd_frame frame;
256+
int rVal = read(fd, &frame, sizeof(frame));
257+
258+
if (rVal != CAN_MTU && rVal != CANFD_MTU) {
259+
// TODO Error handling, do we need to reopen the socket?
260+
return;
261+
}
262+
263+
if (frame.can_id & CAN_ERR_FLAG) {
264+
// Do nothing if this is an error frame
265+
return;
266+
}
267+
268+
if (rVal == CANFD_MTU) {
269+
frame.flags = CANFD_FDF;
270+
}
271+
272+
handleCanFrame(frame);
273+
}
274+
});
275+
276+
poll->Start(UV_READABLE);
277+
278+
return true;
279+
}
280+
281+
int main() {
282+
printf("Starting KitCanDaemon\n");
283+
printf("\tBuild Hash: %s\n", MRC_GetGitHash());
284+
printf("\tBuild Timestamp: %s\n", MRC_GetBuildTimestamp());
285+
286+
#if defined(__linux__) && defined(MRC_DAEMON_BUILD)
287+
sigset_t signal_set;
288+
sigemptyset(&signal_set);
289+
sigaddset(&signal_set, SIGTERM);
290+
sigaddset(&signal_set, SIGINT);
291+
sigprocmask(SIG_BLOCK, &signal_set, nullptr);
292+
#endif
293+
294+
std::array<CanState, NUM_CAN_BUSES> states;
295+
296+
auto ntInst = nt::NetworkTableInstance::Create();
297+
ntInst.SetServer({"localhost"}, 6810);
298+
ntInst.StartClient4("PowerDistributionDaemon");
299+
300+
wpi::EventLoopRunner loopRunner;
301+
302+
bool success = false;
303+
loopRunner.ExecSync([&success, &states, &ntInst](wpi::uv::Loop& loop) {
304+
for (size_t i = 0; i < states.size(); i++) {
305+
success = states[i].startUvLoop(i, ntInst, loop);
306+
if (!success) {
307+
return;
308+
}
309+
}
310+
});
311+
312+
if (!success) {
313+
loopRunner.Stop();
314+
return -1;
315+
}
316+
317+
{
318+
#if defined(__linux__) && defined(MRC_DAEMON_BUILD)
319+
int sig = 0;
320+
sigwait(&signal_set, &sig);
321+
#else
322+
(void)getchar();
323+
#endif
324+
}
325+
ntInst.StopClient();
326+
nt::NetworkTableInstance::Destroy(ntInst);
327+
328+
return 0;
329+
}

powerdistribution/CMakeLists.txt

-10
This file was deleted.

0 commit comments

Comments
 (0)