OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_can_util.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Low-level utility functions for socketcan support.
5 * Author: David Register, Alec Leamas
6 *
7 ***************************************************************************
8 * Copyright (C) 2024 by David Register, Alec Leamas *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26#include <algorithm>
27#include <vector>
28
29#include "model/comm_can_util.h"
30
31static const int kNotFound = -1;
32
34static const int kGcThreshold = 100;
35
37static const int kGcIntervalSecs = 10;
38
40static const int kEntryMaxAgeSecs = 100;
41
42typedef struct can_frame CanFrame;
43
44bool IsFastMessagePGN(unsigned pgn) {
45 static const std::vector<unsigned> haystack = {
46 // All known multiframe fast messages
47 65240u, 126208u, 126464u, 126996u, 126998u, 127233u, 127237u, 127489u,
48 127496u, 127506u, 128275u, 129029u, 129038u, 129039u, 129040u, 129041u,
49 129284u, 129285u, 129540u, 129793u, 129794u, 129795u, 129797u, 129798u,
50 129801u, 129802u, 129808u, 129809u, 129810u, 130065u, 130074u, 130323u,
51 130577u, 130820u, 130822u, 130824u};
52
53 unsigned needle = static_cast<unsigned>(pgn);
54 auto found = std::find_if(haystack.begin(), haystack.end(),
55 [needle](unsigned i) { return i == needle; });
56 return found != haystack.end();
57}
58
59unsigned long BuildCanID(int priority, int source, int destination, int pgn) {
60 // build CanID
61 unsigned long cid = 0;
62 unsigned char pf = (unsigned char)(pgn >> 8);
63 if (pf < 240) {
64 cid = ((unsigned long)(priority & 0x7)) << 26 | pgn << 8 |
65 ((unsigned long)destination) << 8 | (unsigned long)source;
66 } else {
67 cid = ((unsigned long)(priority & 0x7)) << 26 | pgn << 8 |
68 (unsigned long)source;
69 }
70 return cid;
71}
72
73// CanHeader implementation
74
75CanHeader::CanHeader(const CanFrame frame) {
76 unsigned char buf[4];
77 buf[0] = frame.can_id & 0xFF;
78 buf[1] = (frame.can_id >> 8) & 0xFF;
79 buf[2] = (frame.can_id >> 16) & 0xFF;
80 buf[3] = (frame.can_id >> 24) & 0xFF;
81
82 source = buf[0];
83 destination = buf[2] < 240 ? buf[1] : 255;
84 pgn = (buf[3] & 0x01) << 16 | (buf[2] << 8) | (buf[2] < 240 ? 0 : buf[1]);
85 priority = (buf[3] & 0x1c) >> 2;
86}
87
89 return IsFastMessagePGN(static_cast<unsigned>(pgn));
90#if 0
91 static const std::vector<unsigned> haystack = {
92 // All known multiframe fast messages
93 65240u, 126208u, 126464u, 126996u, 126998u, 127233u, 127237u, 127489u,
94 127496u, 127506u, 128275u, 129029u, 129038u, 129039u, 129040u, 129041u,
95 129284u, 129285u, 129540u, 129793u, 129794u, 129795u, 129797u, 129798u,
96 129801u, 129802u, 129808u, 129809u, 129810u, 130065u, 130074u, 130323u,
97 130577u, 130820u, 130822u, 130824u};
98
99 unsigned needle = static_cast<unsigned>(pgn);
100 auto found = std::find_if(haystack.begin(), haystack.end(),
101 [needle](unsigned i) { return i == needle; });
102 return found != haystack.end();
103#endif
104}
105
106// FastMessage implementation
107
108bool FastMessageMap::IsEntryExpired(unsigned int i) {
109 return (wxDateTime::Now() - entries[i].time_arrived >
110 wxTimeSpan(0, 0, kEntryMaxAgeSecs));
111}
112
113void FastMessageMap::CheckGc() {
114 bool last_run_over_age =
115 (wxDateTime::Now() - last_gc_run) > wxTimeSpan(0, 0, kGcIntervalSecs);
116 if (last_run_over_age || entries.size() > kGcThreshold) {
117 GarbageCollector();
118 last_gc_run = wxDateTime::Now();
119 }
120}
121
123 const unsigned char sid) {
124 for (unsigned i = 0; i < entries.size(); i++) {
125 if (((sid & 0xE0) == (entries[i].sid & 0xE0)) &&
126 (entries[i].header.pgn == header.pgn) &&
127 (entries[i].header.source == header.source) &&
128 (entries[i].header.destination == header.destination)) {
129 return i;
130 }
131 }
132 return kNotFound;
133}
134
136 entries.push_back(Entry());
137 return entries.size() - 1;
138}
139
140int FastMessageMap::GarbageCollector(void) {
141 std::vector<unsigned> stale_entries;
142 bool bremoved;
143 int nremoved = 0;
144 do {
145 bremoved = false;
146 for (unsigned i = 0; i < entries.size(); i++) {
147 if (IsEntryExpired(i)) {
148 Remove(i);
149 nremoved++;
150 bremoved = true;
151 break;
152 }
153 }
154 } while (bremoved);
155
156 return nremoved;
157}
158
160 const unsigned char* data, int index) {
161 // first message of fast packet
162 // data[0] Sequence Identifier (sid)
163 // data[1] Length of data bytes
164 // data[2..7] 6 data bytes
165
166 CheckGc();
167 // Ensure that this is indeed the first frame of a fast message
168 if ((data[0] & 0x1F) == 0) {
169 int total_data_len; // will also include padding as we memcpy all of the
170 // frame, because I'm lazy
171 total_data_len = static_cast<unsigned int>(data[1]);
172 total_data_len += 7 - ((total_data_len - 6) % 7);
173
174 entries[index].sid = static_cast<unsigned int>(data[0]);
175 entries[index].expected_length = static_cast<unsigned int>(data[1]);
176 entries[index].header = header;
177 entries[index].time_arrived = wxDateTime::Now();
178
179 entries[index].data.resize(total_data_len);
180 memcpy(&entries[index].data[0], &data[2], 6);
181 // First frame of a multi-frame Fast Message contains six data bytes.
182 // Position the cursor ready for next message
183 entries[index].cursor = 6;
184
185 // Fusion, using fast messages to sends frames less than eight bytes
186 return entries[index].expected_length <= 6;
187 }
188 return false;
189 // No further processing is performed if this is not a start frame.
190 // A start frame may have been dropped and we received a subsequent frame
191}
192
194 const unsigned char* data, int position) {
195 // Check that this is the next message in the sequence
196 if ((entries[position].sid + 1) == data[0]) {
197 memcpy(&entries[position].data[entries[position].cursor], &data[1], 7);
198 entries[position].sid = data[0];
199 // Subsequent messages contains seven data bytes (last message may be padded
200 // with 0xFF)
201 entries[position].cursor += 7;
202 // Is this the last message ?
203 return entries[position].cursor >= entries[position].expected_length;
204 } else if ((data[0] & 0x1F) == 0) {
205 // We've found a matching entry, however this is a start frame, therefore
206 // we've missed an end frame, and now we have a start frame with the same id
207 // (top 3 bits). The id has obviously rolled over. Should really double
208 // check that (data[0] & 0xE0) Clear the entry as we don't want to leak
209 // memory, prior to inserting a start frame
210 entries.erase(entries.begin() + position);
211 position = AddNewEntry();
212 // And now insert it
213 InsertEntry(header, data, position);
214 // FIXME (dave) Should update the dropped frame stats
215 return false;
216 } else {
217 // This is not the next frame in the sequence and not a start frame
218 // We've dropped an intermedite frame, so free the slot and do no further
219 // processing
220 entries.erase(entries.begin() + position);
221 // Dropped Frame Statistics
222 if (dropped_frames == 0) {
223 dropped_frame_time = wxDateTime::Now();
224 dropped_frames += 1;
225 } else {
226 dropped_frames += 1;
227 }
228 // FIXME (dave)
229 // if ((dropped_frames > CONST_DROPPEDFRAME_THRESHOLD) &&
230 // (wxDateTime::Now() < (dropped_frame_time +
231 // wxTimeSpan::Seconds(CONST_DROPPEDFRAME_PERIOD) ) ) ) {
232 // wxLogError(_T("TwoCan Device, Dropped Frames rate exceeded"));
233 // wxLogError(wxString::Format(_T("Frame: Source: %d Destination: %d
234 // Priority: %d PGN: %d"),header.source, header.destination,
235 // header.priority, header.pgn)); dropped_frames = 0;
236 // }
237 return false;
238 }
239}
240
242 if ((unsigned int)(pos + 1) >= entries.size())
243 entries.erase(entries.begin() + pos);
244}
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.