OpenCPN Partial API docs
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 
31 static const int kNotFound = -1;
32 
34 static const int kGcThreshold = 100;
35 
37 static const int kGcIntervalSecs = 10;
38 
40 static const int kEntryMaxAgeSecs = 100;
41 
42 typedef struct can_frame CanFrame;
43 
44 bool 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 
59 unsigned 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 | ((unsigned long)destination)<<8 | (unsigned long)source;
65  }
66  else {
67  cid = ((unsigned long)(priority & 0x7))<<26 | pgn<<8 | (unsigned long)source;
68  }
69  return cid;
70 }
71 
72 
73 // CanHeader implementation
74 
75 CanHeader::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 
88 
90  return IsFastMessagePGN(static_cast<unsigned>(pgn));
91 #if 0
92  static const std::vector<unsigned> haystack = {
93  // All known multiframe fast messages
94  65240u, 126208u, 126464u, 126996u, 126998u, 127233u, 127237u, 127489u,
95  127496u, 127506u, 128275u, 129029u, 129038u, 129039u, 129040u, 129041u,
96  129284u, 129285u, 129540u, 129793u, 129794u, 129795u, 129797u, 129798u,
97  129801u, 129802u, 129808u, 129809u, 129810u, 130065u, 130074u, 130323u,
98  130577u, 130820u, 130822u, 130824u};
99 
100  unsigned needle = static_cast<unsigned>(pgn);
101  auto found = std::find_if(haystack.begin(), haystack.end(),
102  [needle](unsigned i) { return i == needle; });
103  return found != haystack.end();
104 #endif
105 }
106 
107 // FastMessage implementation
108 
109 bool FastMessageMap::IsEntryExpired(unsigned int i) {
110  return (wxDateTime::Now() - entries[i].time_arrived
111  > wxTimeSpan(0, 0, kEntryMaxAgeSecs));
112 }
113 
114 void FastMessageMap::CheckGc() {
115  bool last_run_over_age = (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 
140 int 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 
241 void FastMessageMap::Remove(int pos) {
242  if ((unsigned int)(pos + 1) >= entries.size())
243  entries.erase(entries.begin() + pos);
244 }
245 
246 
CAN v2.0 29 bit header as used by NMEA 2000.
Definition: comm_can_util.h:64
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.