OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_can_util.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2024 by David Register *
3 * Copyright (C) 2024 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
25#include <algorithm>
26#include <vector>
27
28#include "model/comm_can_util.h"
29
30static const int kNotFound = -1;
31
33static const int kGcThreshold = 100;
34
36static const int kGcIntervalSecs = 10;
37
39static const int kEntryMaxAgeSecs = 100;
40
41typedef struct can_frame CanFrame;
42
43bool IsFastMessagePGN(unsigned pgn) {
44 static const std::vector<unsigned> haystack = {
45 // All known multiframe fast messages, adopted from canboat
46 // https://github.com/canboat/canboat
47 65240u, 126208u, 126464u, 126720u, 126983u, 126984u, 126985u, 126986u,
48 126987u, 126988u, 126996u, 126998u, 127233u, 127237u, 127489u, 127490u,
49 127491u, 127494u, 127495u, 127496u, 127497u, 127498u, 127503u, 127504u,
50 127506u, 127507u, 127510u, 127513u, 128275u, 128520u, 128538u, 129029u,
51 129038u, 129039u, 129040u, 129041u, 129044u, 129045u, 129284u, 129285u,
52 129301u, 129302u, 129538u, 129540u, 129541u, 129542u, 129545u, 129547u,
53 129549u, 129550u, 129551u, 129556u, 129792u, 129793u, 129794u, 129795u,
54 129796u, 129797u, 129798u, 129799u, 129800u, 129801u, 129802u, 129803u,
55 129804u, 129805u, 129806u, 129807u, 129808u, 129809u, 129810u, 130052u,
56 130053u, 130054u, 130060u, 130061u, 130064u, 130065u, 130066u, 130067u,
57 130068u, 130069u, 130070u, 130071u, 130072u, 130073u, 130074u, 130320u,
58 130321u, 130322u, 130323u, 130324u, 130330u, 130561u, 130562u, 130563u,
59 130564u, 130565u, 130566u, 130567u, 130569u, 130570u, 130571u, 130572u,
60 130573u, 130574u, 130577u, 130578u, 130580u, 130581u, 130583u, 130584u,
61 130586u, 130816u, 130817u, 130818u, 130819u, 130820u, 130821u, 130822u,
62 130824u, 130827u, 130828u, 130831u, 130832u, 130834u, 130835u, 130836u,
63 130837u, 130838u, 130839u, 130840u, 130842u, 130843u, 130845u, 130846u,
64 130847u, 130850u, 130851u, 130856u, 130880u, 130881u, 130944u};
65
66 unsigned needle = static_cast<unsigned>(pgn);
67 auto found = std::find_if(haystack.begin(), haystack.end(),
68 [needle](unsigned i) { return i == needle; });
69 return found != haystack.end();
70}
71
72unsigned long BuildCanID(int priority, int source, int destination, int pgn) {
73 // build CanID
74 unsigned long cid = 0;
75 unsigned char pf = (unsigned char)(pgn >> 8);
76 if (pf < 240) {
77 cid = ((unsigned long)(priority & 0x7)) << 26 | pgn << 8 |
78 ((unsigned long)destination) << 8 | (unsigned long)source;
79 } else {
80 cid = ((unsigned long)(priority & 0x7)) << 26 | pgn << 8 |
81 (unsigned long)source;
82 }
83 return cid;
84}
85
86// CanHeader implementation
87
88CanHeader::CanHeader(const CanFrame frame) {
89 unsigned char buf[4];
90 buf[0] = frame.can_id & 0xFF;
91 buf[1] = (frame.can_id >> 8) & 0xFF;
92 buf[2] = (frame.can_id >> 16) & 0xFF;
93 buf[3] = (frame.can_id >> 24) & 0xFF;
94
95 source = buf[0];
96 destination = buf[2] < 240 ? buf[1] : 255;
97 pgn = (buf[3] & 0x01) << 16 | (buf[2] << 8) | (buf[2] < 240 ? 0 : buf[1]);
98 priority = (buf[3] & 0x1c) >> 2;
99}
100
102 return IsFastMessagePGN(static_cast<unsigned>(pgn));
103#if 0
104 static const std::vector<unsigned> haystack = {
105 // All known multiframe fast messages
106 65240u, 126208u, 126464u, 126996u, 126998u, 127233u, 127237u, 127489u,
107 127496u, 127506u, 128275u, 129029u, 129038u, 129039u, 129040u, 129041u,
108 129284u, 129285u, 129540u, 129793u, 129794u, 129795u, 129797u, 129798u,
109 129801u, 129802u, 129808u, 129809u, 129810u, 130065u, 130074u, 130323u,
110 130577u, 130820u, 130822u, 130824u};
111
112 unsigned needle = static_cast<unsigned>(pgn);
113 auto found = std::find_if(haystack.begin(), haystack.end(),
114 [needle](unsigned i) { return i == needle; });
115 return found != haystack.end();
116#endif
117}
118
119// FastMessage implementation
120
121bool FastMessageMap::IsEntryExpired(unsigned int i) {
122 return (wxDateTime::Now() - entries[i].time_arrived >
123 wxTimeSpan(0, 0, kEntryMaxAgeSecs));
124}
125
126void FastMessageMap::CheckGc() {
127 bool last_run_over_age =
128 (wxDateTime::Now() - last_gc_run) > wxTimeSpan(0, 0, kGcIntervalSecs);
129 if (last_run_over_age || entries.size() > kGcThreshold) {
130 GarbageCollector();
131 last_gc_run = wxDateTime::Now();
132 }
133}
134
136 const unsigned char sid) {
137 for (unsigned i = 0; i < entries.size(); i++) {
138 if (((sid & 0xE0) == (entries[i].sid & 0xE0)) &&
139 (entries[i].header.pgn == header.pgn) &&
140 (entries[i].header.source == header.source) &&
141 (entries[i].header.destination == header.destination)) {
142 return i;
143 }
144 }
145 return kNotFound;
146}
147
149 entries.push_back(Entry());
150 return entries.size() - 1;
151}
152
153int FastMessageMap::GarbageCollector() {
154 std::vector<unsigned> stale_entries;
155 bool bremoved;
156 int nremoved = 0;
157 do {
158 bremoved = false;
159 for (unsigned i = 0; i < entries.size(); i++) {
160 if (IsEntryExpired(i)) {
161 Remove(i);
162 nremoved++;
163 bremoved = true;
164 break;
165 }
166 }
167 } while (bremoved);
168
169 return nremoved;
170}
171
173 const unsigned char* data, int index) {
174 // first message of fast packet
175 // data[0] Sequence Identifier (sid)
176 // data[1] Length of data bytes
177 // data[2..7] 6 data bytes
178
179 CheckGc();
180 // Ensure that this is indeed the first frame of a fast message
181 if ((data[0] & 0x1F) == 0) {
182 int total_data_len; // will also include padding as we memcpy all of the
183 // frame, because I'm lazy
184 total_data_len = static_cast<unsigned int>(data[1]);
185 total_data_len += 7 - ((total_data_len - 6) % 7);
186
187 entries[index].sid = static_cast<unsigned int>(data[0]);
188 entries[index].expected_length = static_cast<unsigned int>(data[1]);
189 entries[index].header = header;
190 entries[index].time_arrived = wxDateTime::Now();
191
192 entries[index].data.resize(total_data_len);
193 memcpy(&entries[index].data[0], &data[2], 6);
194 // First frame of a multi-frame Fast Message contains six data bytes.
195 // Position the cursor ready for next message
196 entries[index].cursor = 6;
197
198 // Fusion, using fast messages to sends frames less than eight bytes
199 return entries[index].expected_length <= 6;
200 }
201 return false;
202 // No further processing is performed if this is not a start frame.
203 // A start frame may have been dropped and we received a subsequent frame
204}
205
207 const unsigned char* data, int position) {
208 // Check that this is the next message in the sequence
209 if ((entries[position].sid + 1) == data[0]) {
210 memcpy(&entries[position].data[entries[position].cursor], &data[1], 7);
211 entries[position].sid = data[0];
212 // Subsequent messages contains seven data bytes (last message may be padded
213 // with 0xFF)
214 entries[position].cursor += 7;
215 // Is this the last message ?
216 return entries[position].cursor >= entries[position].expected_length;
217 } else if ((data[0] & 0x1F) == 0) {
218 // We've found a matching entry, however this is a start frame, therefore
219 // we've missed an end frame, and now we have a start frame with the same id
220 // (top 3 bits). The id has obviously rolled over. Should really double
221 // check that (data[0] & 0xE0) Clear the entry as we don't want to leak
222 // memory, prior to inserting a start frame
223 entries.erase(entries.begin() + position);
224 position = AddNewEntry();
225 // And now insert it
226 InsertEntry(header, data, position);
227 // FIXME (dave) Should update the dropped frame stats
228 return false;
229 } else {
230 // This is not the next frame in the sequence and not a start frame
231 // We've dropped an intermedite frame, so free the slot and do no further
232 // processing
233 entries.erase(entries.begin() + position);
234 // Dropped Frame Statistics
235 if (dropped_frames == 0) {
236 dropped_frame_time = wxDateTime::Now();
237 dropped_frames += 1;
238 } else {
239 dropped_frames += 1;
240 }
241 // FIXME (dave)
242 // if ((dropped_frames > CONST_DROPPEDFRAME_THRESHOLD) &&
243 // (wxDateTime::Now() < (dropped_frame_time +
244 // wxTimeSpan::Seconds(CONST_DROPPEDFRAME_PERIOD) ) ) ) {
245 // wxLogError("TwoCan Device, Dropped Frames rate exceeded");
246 // wxLogError(wxString::Format(_T("Frame: Source: %d Destination: %d
247 // Priority: %d PGN: %d"),header.source, header.destination,
248 // header.priority, header.pgn)); dropped_frames = 0;
249 // }
250 return false;
251 }
252}
253
255 if ((unsigned int)(pos + 1) <= entries.size())
256 entries.erase(entries.begin() + pos);
257}
CAN v2.0 29 bit header as used by NMEA 2000.
CanHeader()
CAN v2.0 29 bit header as used by NMEA 2000.
bool IsFastMessage() const
Return true if header reflects a multipart fast message.
int AddNewEntry(void)
Allocate a new, fresh entry and return index to it.
void Remove(int pos)
Remove entry at pos.
bool AppendEntry(const CanHeader hdr, const unsigned char *data, int index)
Append fragment to existing multipart message.
int FindMatchingEntry(const CanHeader header, const unsigned char sid)
Setter.
bool InsertEntry(const CanHeader header, const unsigned char *data, int index)
Insert a new entry, first part of a multipart message.
Low-level socketcan utility functions.