Skip to content

Commit 56ac61f

Browse files
committed
Add version reporting to can daemon
1 parent cfc2216 commit 56ac61f

File tree

6 files changed

+357
-228
lines changed

6 files changed

+357
-228
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 10 additions & 0 deletions
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

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

powerdistribution/CMakeLists.txt

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)