├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── cangw.cpp
├── canprint.cpp
├── cansim.cpp
├── cansocket.cpp
├── cansocket.h
├── cantx.cpp
├── cxxopts.hpp
├── makefile
├── priority.h
├── timer.cpp
├── timer.h
├── udpsocket.cpp
└── udpsocket.h
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Default behavior
2 | * text eol=lf
3 |
4 | # Explicitly declare text files
5 | *.c text
6 | *.cc text
7 | *.cpp text
8 | *.h text
9 | *.hh text
10 | *.hpp text
11 | *.md text
12 |
13 | # Denote all files that are truly binary and should not be modified
14 | *.png binary
15 | *.jpg binary
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Peter Ebermann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cantools
2 | A collection of CLI tools for receiving and transmitting CAN frames using Linux SocketCAN
3 |
4 | Description
5 | ---
6 | These tools can be used - and were developed - using a Raspberry Pi 3 with a PiCAN2 HAT.
7 | * __cantx__: one-time or cyclic transmission of a single frame
8 | * __canprint__: printing frames into the console
9 | * __cangw__: routing frames between CAN and UDP
10 |
11 | Build
12 | ---
13 | Use `make` to build all tools or `make cangw` etc.
14 |
15 | Usage
16 | ---
17 | | Tool | Options | Short | Required | Default | Description |
18 | | ---- | ------- | :---: | :------: | ------- | ----------- |
19 | | cantx | device
id
payload
cycle
realtime | `-d`
`-i`
`-p`
`-c`
`-r` |
✓
| can0
00
-1 (send once)
false | CAN device
Frame ID
Hex data string
Repetition time in ms
Enable realtime scheduling policy |
20 | | canprint | device | `-d` | | can0 | CAN device |
21 | | cangw | listen
send
realtime
timestamp
device
ip
port | `-l`
`-s`
`-r`
`-t`
`-d`
`-i`
`-p` | `-l` ∨ `-s`
`-l` ∨ `-s`
✓
✓ |
false
false
can0
| Route frames from CAN to UDP
Route frames from UDP to CAN
Enable realtime scheduling policy
Prefix payload with 8-byte timestamp (ms)
CAN device
IP of remote device
UDP port |
22 |
23 |
24 |
25 | Examples:
26 | ```bash
27 | # Send frame each 100 ms
28 | $ ./cantx --device=can0 --id=42 --data=0807060504030201 --cycle=100
29 |
30 | # Route frames from CAN to UDP
31 | $ ./cangw --listen --ip=192.168.1.5 --port=30001
32 |
33 | # Same but using short options
34 | $ ./cangw -li 192.168.1.5 -p 30001
35 |
36 | # Route frames between interfaces and add timestamps to UDP payload
37 | $ ./cangw -lsti 192.168.1.5 -p 30001
38 | ```
39 |
40 | Acknowledgements
41 | ---
42 | [cxxopts](https://github.com/jarro2783/cxxopts) for parsing command line options
43 |
--------------------------------------------------------------------------------
/cangw.cpp:
--------------------------------------------------------------------------------
1 | /* A small command line program for routing frames between a CAN socket and an UDP socket
2 | */
3 |
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "cxxopts.hpp"
14 |
15 | #include "cansocket.h"
16 | #include "udpsocket.h"
17 | #include "priority.h"
18 |
19 |
20 | namespace cangw
21 | {
22 |
23 |
24 | struct Options
25 | {
26 | bool listen; // Read frames from CAN bus
27 | bool send; // Send frames to CAN bus
28 | bool realtime; // Set listen and/or send thread to realtime scheduling policy
29 | bool timestamp; // Pass original CAN receive timestamp to remote device
30 | std::string remote_ip;
31 | std::uint16_t data_port;
32 | std::string can_device;
33 | };
34 |
35 |
36 | } // namespace cangw
37 |
38 |
39 | void route_to_udp(can::Socket& can_socket, udp::Socket& udp_socket, std::atomic& stop,
40 | bool timestamp)
41 | {
42 | if (timestamp) {
43 | // Pass-through of original receive timestamp for more accurate timing information of frames
44 | std::vector buffer(sizeof(std::uint64_t) + sizeof(can_frame));
45 | auto* time = reinterpret_cast(buffer.data());
46 | auto* frame = reinterpret_cast(buffer.data() + sizeof(std::uint64_t));
47 | while (!stop.load()) {
48 | // Ancillary data (timestamp) is not part of socket payload
49 | if (can_socket.receive(frame, time) == sizeof(can_frame)) {
50 | udp_socket.transmit(buffer);
51 | }
52 | }
53 | }
54 | else {
55 | can_frame frame;
56 | while (!stop.load()) {
57 | if (can_socket.receive(&frame) == sizeof(can_frame)) {
58 | udp_socket.transmit(&frame);
59 | }
60 | }
61 | }
62 | }
63 |
64 |
65 | void route_to_can(can::Socket& can_socket, udp::Socket& udp_socket, std::atomic& stop)
66 | {
67 | can_frame frame;
68 | while (!stop.load()) {
69 | if (udp_socket.receive(&frame) == sizeof(can_frame)) {
70 | can_socket.transmit(&frame);
71 | }
72 | }
73 | }
74 |
75 |
76 | cangw::Options parse_args(int argc, char** argv)
77 | {
78 | cangw::Options options;
79 | options.listen = false;
80 | options.send = false;
81 | options.realtime = false;
82 | options.timestamp = false;
83 |
84 | try {
85 | cxxopts::Options cli_options{"cangw", "CAN to UDP gateway"};
86 | cli_options.add_options()
87 | ("l,listen", "Route frames from CAN to UDP", cxxopts::value(options.listen))
88 | ("s,send", "Route frames from UDP to CAN", cxxopts::value(options.send))
89 | ("r,realtime", "Enable realtime scheduling policy", cxxopts::value(options.realtime))
90 | ("t,timestamp", "Prefix UDP payload with timestamp", cxxopts::value(options.timestamp))
91 | ("i,ip", "Remote device IP", cxxopts::value(options.remote_ip))
92 | ("p,port", "UDP data port", cxxopts::value(options.data_port))
93 | ("d,device", "CAN device name", cxxopts::value(options.can_device)
94 | ->default_value("can0"))
95 | ;
96 | cli_options.parse(argc, argv);
97 |
98 | if (cli_options.count("listen") + cli_options.count("send") == 0) {
99 | throw std::runtime_error{"Mode must be specified, use the -l or --listen "
100 | "and/or -s or --send option"};
101 | }
102 | if (cli_options.count("ip") == 0) {
103 | throw std::runtime_error{"Remote IP must be specified, use the -i or --ip option"};
104 | }
105 | if (cli_options.count("port") == 0) {
106 | throw std::runtime_error{"UDP port must be specified, use the -p or --port option"};
107 | }
108 |
109 | return options;
110 | }
111 | catch (const cxxopts::OptionException& e) {
112 | throw std::runtime_error{e.what()};
113 | }
114 | }
115 |
116 |
117 | int main(int argc, char** argv)
118 | {
119 | cangw::Options options;
120 | can::Socket can_socket;
121 | udp::Socket udp_socket;
122 |
123 | try {
124 | options = parse_args(argc, argv);
125 | can_socket.open(options.can_device);
126 | if (options.listen) {
127 | can_socket.bind();
128 | can_socket.set_receive_timeout(3);
129 | }
130 | if (options.timestamp)
131 | can_socket.set_socket_timestamp(true);
132 | udp_socket.open(options.remote_ip, options.data_port); // Transmit frames to remote device
133 | if (options.send) {
134 | udp_socket.bind("0.0.0.0", options.data_port); // Receive frames from remote device
135 | udp_socket.set_receive_timeout(3);
136 | }
137 | }
138 | catch (const std::runtime_error& e) {
139 | std::cerr << e.what() << std::endl;
140 | return 1;
141 | }
142 |
143 | std::cout << "Routing frames between " << options.can_device << " and " << options.remote_ip
144 | << ":" << options.data_port << "\nPress enter to stop..." << std::endl;
145 |
146 | std::atomic stop{false};
147 | std::thread listener{};
148 | std::thread sender{};
149 |
150 | if (options.listen) {
151 | listener = std::thread{&route_to_udp, std::ref(can_socket), std::ref(udp_socket),
152 | std::ref(stop), options.timestamp};
153 | }
154 |
155 | if (options.send) {
156 | sender = std::thread{&route_to_can, std::ref(can_socket), std::ref(udp_socket),
157 | std::ref(stop)};
158 | }
159 |
160 | if (options.realtime) {
161 | // Only attempt to set active threads to realtime scheduling policy
162 | bool success = false;
163 |
164 | if (listener.joinable() && sender.joinable()) {
165 | success = priority::set_realtime(listener.native_handle()) &&
166 | priority::set_realtime(sender.native_handle());
167 | }
168 | else if (listener.joinable()) {
169 | success = priority::set_realtime(listener.native_handle());
170 | }
171 | else if (sender.joinable()) {
172 | success = priority::set_realtime(sender.native_handle());
173 | }
174 |
175 | if (success)
176 | std::cout << "Gateway thread(s) set to realtime scheduling policy" << std::endl;
177 | else
178 | std::cout << "Warning: Could not set scheduling policy, forgot sudo?" << std::endl;
179 | }
180 | std::cin.ignore(); // Wait in main thread
181 |
182 | std::cout << "Stopping gateway..." << std::endl;
183 | stop.store(true);
184 | if (listener.joinable())
185 | listener.join();
186 | if (sender.joinable())
187 | sender.join();
188 |
189 | std::cout << "Program finished" << std::endl;
190 | return 0;
191 | }
192 |
--------------------------------------------------------------------------------
/canprint.cpp:
--------------------------------------------------------------------------------
1 | /* A small command line program for printing frames into the console
2 | */
3 |
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "cxxopts.hpp"
14 | #include "cansocket.h"
15 |
16 |
17 | void print_frame(const can_frame& frame, std::uint64_t time)
18 | {
19 | std::cout << time << ',' << std::setfill(' ') << std::hex << std::setw(8) << frame.can_id
20 | << std::dec << ',' << static_cast(frame.can_dlc) << ',' << std::hex << std::setfill('0');
21 | for (int i=frame.can_dlc-1; i>0; --i)
22 | std::cout << std::setw(2) << static_cast(frame.data[i]) << ' ';
23 | if (frame.can_dlc > 0)
24 | std::cout << std::setw(2) << static_cast(frame.data[0]) << '\n';
25 | std::cout.copyfmt(std::ios{nullptr}); // Reset format state
26 | }
27 |
28 |
29 | void print_frames(std::atomic& stop, std::string device)
30 | {
31 | can::Socket can_socket;
32 | try {
33 | can_socket.open(device);
34 | can_socket.bind();
35 | can_socket.set_receive_timeout(3);
36 | can_socket.set_socket_timestamp(true);
37 | }
38 | catch (const can::Socket_error& e) {
39 | std::cerr << e.what() << std::endl;
40 | return;
41 | }
42 |
43 | std::uint64_t time;
44 | can_frame frame;
45 |
46 | while (!stop.load()) {
47 | auto n = can_socket.receive(&frame, &time);
48 | if (n == CAN_MTU) {
49 | print_frame(frame, time);
50 | }
51 | else if (n > 0) {
52 | std::cout << "Received incomplete frame (" << n << " bytes)" << std::endl;
53 | }
54 | else if (n == -1) {
55 | if (errno == EAGAIN || errno == EWOULDBLOCK)
56 | std::cout << "Receive timeout" << std::endl;
57 | else
58 | std::cout << "Unknown error" << std::endl;
59 | }
60 | }
61 | }
62 |
63 |
64 | std::string parse_args(int argc, char** argv)
65 | {
66 | std::string can_device;
67 |
68 | try {
69 | cxxopts::Options options{"canprint", "Prints CAN frames to console"};
70 | options.add_options()
71 | ("d,device", "CAN device name", cxxopts::value(can_device)->default_value("can0"))
72 | ;
73 | options.parse(argc, argv);
74 | return can_device;
75 | }
76 | catch (const cxxopts::OptionException& e) {
77 | throw std::runtime_error{e.what()};
78 | }
79 | }
80 |
81 |
82 | int main(int argc, char** argv)
83 | {
84 | std::string can_device;
85 |
86 | try {
87 | can_device = parse_args(argc, argv);
88 | }
89 | catch (const std::runtime_error& e) {
90 | std::cerr << "Error parsing command line options:\n" << e.what() << std::endl;
91 | return 1;
92 | }
93 |
94 | std::cout << "Printing frames from " << can_device << "\nPress enter to stop..." << std::endl;
95 |
96 | std::atomic stop{false};
97 | std::thread printer{&print_frames, std::ref(stop), can_device};
98 | std::cin.ignore(); // Wait in main thread
99 |
100 | std::cout << "Stopping printer..." << std::endl;
101 | stop.store(true);
102 | printer.join();
103 |
104 | std::cout << "Program finished" << std::endl;
105 | return 0;
106 | }
107 |
--------------------------------------------------------------------------------
/cansim.cpp:
--------------------------------------------------------------------------------
1 | /* CAN bus to UDP simulation for development purposes
2 | */
3 |
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | #include "cxxopts.hpp"
18 |
19 | #include "timer.h"
20 | #include "udpsocket.h"
21 | #include "priority.h"
22 |
23 |
24 | // Evil functions to defeat the optimizer
25 | __attribute__((unused)) static void escape(void* p) { asm volatile("" : : "g"(p) : "memory"); }
26 | __attribute__((unused)) static void clobber() { asm volatile("" : : : "memory"); }
27 |
28 |
29 | namespace
30 | {
31 |
32 |
33 | #pragma pack(push, 1)
34 |
35 | struct Frame_veh_state
36 | {
37 | std::uint8_t crc;
38 | std::uint8_t aliv : 4;
39 | std::uint8_t velocity_0 : 4;
40 | std::uint8_t velocity_1;
41 | std::uint8_t velocity_2 : 2;
42 | std::uint8_t wiper_position_0 : 6;
43 | std::uint8_t wiper_position_1;
44 | std::uint8_t wiper_position_2 : 2;
45 | };
46 |
47 | struct Frame_flux
48 | {
49 | std::uint32_t power_level;
50 | std::uint32_t dispersal_rate;
51 | };
52 |
53 | struct Frame_date_time
54 | {
55 | std::uint8_t time_type : 2;
56 | std::uint8_t year_0 : 6;
57 | std::uint8_t year_1;
58 | std::uint8_t month : 4;
59 | std::uint8_t day_0 : 4;
60 | std::uint8_t day_1 : 1;
61 | std::uint8_t am_pm : 1;
62 | std::uint8_t hour : 4;
63 | std::uint8_t minute_0 : 2;
64 | std::uint8_t minute_1 : 4;
65 | };
66 |
67 |
68 | struct Frame_fuel
69 | {
70 | std::uint8_t fuel_type : 1;
71 | std::uint8_t reserved : 7;
72 | union {
73 | struct {
74 | std::uint8_t e_range;
75 | std::uint8_t e_cons_0;
76 | std::uint8_t e_cons_1 : 2;
77 | std::uint8_t bat_volt_0 : 6;
78 | std::uint8_t bat_volt_1 : 6;
79 | std::uint8_t charging : 1;
80 | std::uint8_t super_charging : 1;
81 | } m0;
82 | struct {
83 | std::uint8_t gas_range_0;
84 | std::uint8_t gas_range_1 : 2;
85 | std::uint8_t gas_cons_0 : 6;
86 | std::uint8_t gas_cons_1 : 4;
87 | } m1;
88 | } mux;
89 | };
90 |
91 | #pragma pack(pop)
92 |
93 |
94 | } // namespace
95 |
96 |
97 | void simulate(std::atomic& stop, std::string&& ip, std::uint16_t port, bool timestamp)
98 | {
99 | #pragma GCC diagnostic push
100 | #pragma GCC diagnostic ignored "-Woverflow"
101 |
102 | // Example frames
103 | can_frame veh_state{0};
104 | veh_state.can_id = 201;
105 | veh_state.can_dlc = 8;
106 | auto* veh_state_data = reinterpret_cast(veh_state.data);
107 | veh_state_data->crc = 0xFF;
108 | veh_state_data->aliv = 0;
109 | veh_state_data->velocity_0 = 8800;
110 | veh_state_data->velocity_1 = 8800 >> 4;
111 | veh_state_data->velocity_2 = 8800 >> 12;
112 | veh_state_data->wiper_position_0 = 2122;
113 | veh_state_data->wiper_position_1 = 2122 >> 4;
114 | veh_state_data->wiper_position_2 = 2122 >> 12;
115 |
116 | can_frame flux{0};
117 | flux.can_id = 233;
118 | flux.can_dlc = 6;
119 | auto* flux_data = reinterpret_cast(flux.data);
120 | flux_data->power_level = 1210000000;
121 | flux_data->dispersal_rate = 7743;
122 |
123 | can_frame date_time{0};
124 | date_time.can_id = 245;
125 | date_time.can_dlc = 5;
126 | auto* date_time_data = reinterpret_cast(date_time.data);
127 | date_time_data->year_0 = 1955;
128 | date_time_data->year_1 = 1955 >> 6;
129 | date_time_data->month = 11;
130 | date_time_data->day_0 = 5;
131 | date_time_data->day_1 = 5 >> 4;
132 | date_time_data->hour = 6;
133 | date_time_data->minute_0 = 31;
134 | date_time_data->minute_1 = 31 >> 2;
135 |
136 | can_frame fuel{0};
137 | fuel.can_id = 502;
138 | fuel.can_dlc = 5;
139 | Frame_fuel electric;
140 | electric.mux.m0.e_range = 149;
141 | electric.mux.m0.e_cons_0 = 354;
142 | electric.mux.m0.e_cons_1 = 354 >> 8;
143 | electric.mux.m0.bat_volt_0 = 3751;
144 | electric.mux.m0.bat_volt_1 = 3751 >> 6;
145 | electric.mux.m0.charging = 0;
146 | electric.mux.m0.super_charging = 0;
147 | Frame_fuel gasoline;
148 | gasoline.mux.m1.gas_range_0 = 381;
149 | gasoline.mux.m1.gas_range_1 = 381 >> 8;
150 | gasoline.mux.m1.gas_cons_0 = 178;
151 | gasoline.mux.m1.gas_cons_1 = 178 >> 6;
152 | auto* fuel_data = reinterpret_cast(fuel.data);
153 | fuel_data->reserved = 0;
154 | fuel_data->fuel_type = 0;
155 | fuel_data->mux = electric.mux;
156 |
157 | #pragma GCC diagnostic pop
158 |
159 | can_frame radar{0};
160 | radar.can_id = 402;
161 | radar.can_dlc = 4;
162 | // Motorola byte order
163 | // Pos 7, len 14 = 12317
164 | // Pos 9, len 14 = 4404
165 | // Pos 27, len 4 = 11
166 | radar.data[0] = 0b1100'0000;
167 | radar.data[1] = 0b0111'0101;
168 | radar.data[2] = 0b0001'0011;
169 | radar.data[3] = 0b0100'1011;
170 |
171 | // Network inferface and timer
172 | std::vector buffer(sizeof(std::uint64_t) + sizeof(can_frame));
173 | auto* time_buffer = reinterpret_cast(buffer.data());
174 | auto* frame_buffer = reinterpret_cast(buffer.data() + sizeof(std::uint64_t));
175 | udp::Socket udp_socket;
176 | util::Timer timer;
177 | try {
178 | udp_socket.open(ip, port);
179 | timer.init_system_timer();
180 | }
181 | catch (const std::runtime_error& e) {
182 | std::cerr << e.what() << std::endl;
183 | return;
184 | }
185 |
186 | // Transmit scheduling
187 | auto transmit = [&](std::uint64_t time_ms) {
188 | if (time_ms % 250 == 0) { // 250 ms cycle time
189 | veh_state_data->aliv++;
190 | // Add crc calculation
191 | udp_socket.transmit(&veh_state);
192 | }
193 | if (time_ms % 100 == 0) {
194 | udp_socket.transmit(&flux);
195 | }
196 | if (time_ms % 225 == 0) {
197 | udp_socket.transmit(&radar);
198 | }
199 | if (time_ms % 1333 == 0) {
200 | udp_socket.transmit(&date_time);
201 | }
202 | if (time_ms % 500 == 0) {
203 | fuel_data->fuel_type = ~fuel_data->fuel_type;
204 | if (fuel_data->fuel_type == 0)
205 | fuel_data->mux = electric.mux;
206 | else
207 | fuel_data->mux = gasoline.mux;
208 | udp_socket.transmit(&fuel);
209 | }
210 | };
211 |
212 | auto transmit_with_timestamp = [&](std::uint64_t time_ms) {
213 | if (time_ms % 250 == 0) { // 250 ms cycle time
214 | veh_state_data->aliv++;
215 | // Add crc calculation
216 | *time_buffer = time_ms;
217 | std::memcpy(frame_buffer, &veh_state, sizeof(can_frame));
218 | udp_socket.transmit(buffer);
219 | }
220 | if (time_ms % 100 == 0) {
221 | *time_buffer = time_ms;
222 | std::memcpy(frame_buffer, &flux, sizeof(can_frame));
223 | udp_socket.transmit(buffer);
224 | }
225 | if (time_ms % 225 == 0) {
226 | *time_buffer = time_ms;
227 | std::memcpy(frame_buffer, &radar, sizeof(can_frame));
228 | udp_socket.transmit(buffer);
229 | }
230 | if (time_ms % 1333 == 0) {
231 | *time_buffer = time_ms;
232 | std::memcpy(frame_buffer, &date_time, sizeof(can_frame));
233 | udp_socket.transmit(buffer);
234 | }
235 | if (time_ms % 500 == 0) {
236 | fuel_data->fuel_type = ~fuel_data->fuel_type;
237 | if (fuel_data->fuel_type == 0) {
238 | // Update range
239 | if (electric.mux.m0.super_charging == 1) electric.mux.m0.e_range += 4;
240 | else if (electric.mux.m0.charging == 1) electric.mux.m0.e_range++;
241 | else electric.mux.m0.e_range--;
242 | // Set charging state
243 | if (electric.mux.m0.e_range == 0) electric.mux.m0.charging = 1;
244 | if (electric.mux.m0.charging == 1 && electric.mux.m0.e_range == 100) electric.mux.m0.super_charging = 1;
245 | else if (electric.mux.m0.e_range > 200) electric.mux.m0.charging = electric.mux.m0.super_charging = 0;
246 | fuel_data->mux = electric.mux;
247 | }
248 | else {
249 | fuel_data->mux = gasoline.mux;
250 | }
251 |
252 | *time_buffer = time_ms;
253 | std::memcpy(frame_buffer, &fuel, sizeof(can_frame));
254 | udp_socket.transmit(buffer);
255 | }
256 | };
257 |
258 | // Looping and timing
259 | auto current_time_us = timer.system_time();
260 | escape(¤t_time_us); // Prevent infinite loop due to optimizer fixing this value
261 | auto next_cycle = current_time_us + 1000ull;
262 |
263 | while (!stop.load())
264 | {
265 | do {
266 | current_time_us = timer.system_time();
267 | } while (current_time_us < next_cycle); // Polling wait until exactly 1 ms passed
268 | next_cycle = current_time_us + 1000ull;
269 | if (timestamp)
270 | transmit_with_timestamp(current_time_us / 1000ull);
271 | else
272 | transmit(current_time_us / 1000ull);
273 | usleep(750);
274 | }
275 | }
276 |
277 |
278 | std::tuple parse_args(int argc, char** argv)
279 | {
280 | bool realtime = false;
281 | bool timestamp = false;
282 | std::string remote_ip;
283 | std::uint16_t data_port;
284 |
285 | try {
286 | cxxopts::Options options{"cansim", "CAN bus to UDP simulation"};
287 | options.add_options()
288 | ("r,realtime", "Enable realtime scheduling policy", cxxopts::value(realtime))
289 | ("t,timestamp", "Prefix UDP packets with timestamp", cxxopts::value(timestamp))
290 | ("i,ip", "Remote device IP", cxxopts::value(remote_ip))
291 | ("p,port", "UDP data port", cxxopts::value(data_port))
292 | ;
293 | options.parse(argc, argv);
294 |
295 | if (options.count("ip") == 0) {
296 | throw std::runtime_error{"Remote IP must be specified, use the -i or --ip option"};
297 | }
298 | if (options.count("port") == 0) {
299 | throw std::runtime_error{"UDP port must be specified, use the -p or --port option"};
300 | }
301 |
302 | return std::make_tuple(std::move(remote_ip), data_port, realtime, timestamp);
303 | }
304 | catch (const cxxopts::OptionException& e) {
305 | throw std::runtime_error{e.what()};
306 | }
307 | }
308 |
309 |
310 | int main(int argc, char** argv)
311 | {
312 | bool realtime;
313 | bool timestamp;
314 | std::string remote_ip;
315 | std::uint16_t data_port;
316 |
317 | try {
318 | std::tie(remote_ip, data_port, realtime, timestamp) = parse_args(argc, argv);
319 | }
320 | catch (const std::runtime_error& e) {
321 | std::cerr << e.what() << std::endl;
322 | return 1;
323 | }
324 |
325 | std::cout << "Sending frames to " << remote_ip << ":" << data_port
326 | << "\nPress enter to stop..." << std::endl;
327 |
328 | std::atomic stop{false};
329 | std::thread simulation{&simulate, std::ref(stop), std::move(remote_ip), data_port, timestamp};
330 |
331 | if (realtime) {
332 | if (priority::set_realtime(simulation.native_handle()))
333 | std::cout << "Simulation thread set to realtime scheduling policy\n";
334 | else
335 | std::cout << "Warning: Could not set scheduling policy, forgot sudo?\n";
336 | }
337 |
338 | std::cin.ignore(); // Wait in main thread
339 |
340 | std::cout << "Stopping simulation..." << std::endl;
341 | stop.store(true);
342 | simulation.join();
343 |
344 | std::cout << "Program finished" << std::endl;
345 | return 0;
346 | }
347 |
--------------------------------------------------------------------------------
/cansocket.cpp:
--------------------------------------------------------------------------------
1 | #include "cansocket.h"
2 |
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | #include
14 |
15 |
16 | namespace
17 | {
18 |
19 |
20 | class Scope_guard
21 | {
22 | public:
23 | Scope_guard(int fd) : fd_{fd} {}
24 | ~Scope_guard() { if (fd_ != -1) ::close(fd_); fd_ = -1; }
25 | Scope_guard(const Scope_guard&) = delete;
26 | Scope_guard& operator=(const Scope_guard&) = delete;
27 | void release() { fd_ = -1; }
28 |
29 | private:
30 | int fd_;
31 | };
32 |
33 |
34 | } // namespace
35 |
36 |
37 | void can::Socket::open(const std::string& device)
38 | {
39 | if (fd_ != -1)
40 | throw Socket_error{"Already open"};
41 |
42 | fd_ = ::socket(PF_CAN, SOCK_RAW, CAN_RAW);
43 |
44 | if (fd_ == -1)
45 | throw Socket_error{"Could not open"};
46 |
47 | Scope_guard guard{fd_};
48 |
49 | if (device.size() + 1 >= IFNAMSIZ)
50 | throw Socket_error{"Device name too long"};
51 |
52 | addr_.can_family = AF_CAN;
53 | ifreq ifr;
54 | std::memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
55 | std::strcpy(ifr.ifr_name, device.c_str());
56 |
57 | if (ioctl(fd_, SIOCGIFINDEX, &ifr) < 0)
58 | throw Socket_error{"Error retrieving interface index"};
59 | addr_.can_ifindex = ifr.ifr_ifindex;
60 |
61 | guard.release();
62 | }
63 |
64 |
65 | void can::Socket::close()
66 | {
67 | if (fd_ != -1) {
68 | ::close(fd_);
69 | }
70 |
71 | reset();
72 | }
73 |
74 |
75 | void can::Socket::bind()
76 | {
77 | if (::bind(fd_, reinterpret_cast(&addr_), sizeof(addr_)) < 0)
78 | throw Socket_error{"Error while binding socket"};
79 |
80 | msg_.msg_name = &addr_;
81 | msg_.msg_iov = &iov_;
82 | msg_.msg_iovlen = 1;
83 | msg_.msg_control = cmsg_buffer.data();
84 | }
85 |
86 |
87 | void can::Socket::set_receive_timeout(time_t timeout)
88 | {
89 | if (timeout <= 0)
90 | throw Socket_error{"Timeout must be larger then 0"};
91 |
92 | timeval tv;
93 | tv.tv_sec = timeout;
94 | tv.tv_usec = 0;
95 | if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0)
96 | throw Socket_error{"Error setting receive timeout"};
97 | }
98 |
99 |
100 | void can::Socket::set_socket_timestamp(bool enable)
101 | {
102 | const int param = enable ? 1 : 0;
103 | if (setsockopt(fd_, SOL_SOCKET, SO_TIMESTAMP, ¶m, sizeof(param)) != 0)
104 | throw Socket_error{"Error setting socket timestamp"};
105 | }
106 |
107 |
108 | int can::Socket::transmit(const can_frame* frame)
109 | {
110 | return write(fd_, frame, sizeof(can_frame));
111 | }
112 |
113 |
114 | int can::Socket::receive(can_frame* frame)
115 | {
116 | iov_.iov_base = frame;
117 | iov_.iov_len = sizeof(can_frame);
118 | msg_.msg_namelen = sizeof(addr_);
119 | msg_.msg_controllen = 0;
120 | msg_.msg_flags = 0;
121 |
122 | return recvmsg(fd_, &msg_, 0);
123 | }
124 |
125 |
126 | int can::Socket::receive(can_frame* frame, std::uint64_t* time)
127 | {
128 | iov_.iov_base = frame;
129 | iov_.iov_len = sizeof(can_frame);
130 | msg_.msg_namelen = sizeof(addr_);
131 | msg_.msg_controllen = cmsg_buffer.size();
132 | msg_.msg_flags = 0;
133 |
134 | auto len = recvmsg(fd_, &msg_, 0);
135 |
136 | // Get receive time from ancillary data
137 | for (auto* cmsg = CMSG_FIRSTHDR(&msg_);
138 | cmsg && cmsg->cmsg_level == SOL_SOCKET;
139 | cmsg = CMSG_NXTHDR(&msg_, cmsg)) {
140 | if (cmsg->cmsg_type == SO_TIMESTAMP) {
141 | timeval tv;
142 | memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv));
143 | *time = (tv.tv_sec * 1'000'000ull + tv.tv_usec) / 1000ull; // Time in ms
144 | }
145 | }
146 |
147 | return len;
148 | }
149 |
150 |
151 | void can::Socket::reset()
152 | {
153 | fd_ = -1;
154 | addr_ = sockaddr_can{};
155 | iov_ = iovec{};
156 | msg_ = msghdr{};
157 | }
158 |
--------------------------------------------------------------------------------
/cansocket.h:
--------------------------------------------------------------------------------
1 | #ifndef CAN_SOCKET_H
2 | #define CAN_SOCKET_H
3 |
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 |
13 |
14 | namespace can
15 | {
16 |
17 |
18 | class Socket_error : public std::runtime_error
19 | {
20 | public:
21 | Socket_error(const std::string& s) : std::runtime_error{s} {}
22 | Socket_error(const char* s) : std::runtime_error{s} {}
23 | };
24 |
25 |
26 | class Socket
27 | {
28 | public:
29 | Socket() : fd_{-1} {}
30 | ~Socket() { close(); }
31 |
32 | Socket(const Socket&) = delete;
33 | Socket& operator=(const Socket&) = delete;
34 | Socket(Socket&&) = delete;
35 | Socket& operator=(Socket&&) = delete;
36 |
37 | void open(const std::string& device);
38 | void close();
39 |
40 | void bind();
41 | void set_receive_timeout(time_t timeout);
42 | void set_socket_timestamp(bool enable);
43 |
44 | int transmit(const can_frame* frame);
45 | int receive(can_frame* frame);
46 | int receive(can_frame* frame, std::uint64_t* time);
47 |
48 | private:
49 | void reset();
50 |
51 | int fd_;
52 | sockaddr_can addr_;
53 | iovec iov_;
54 | msghdr msg_;
55 | std::array cmsg_buffer; // Receive time
56 | };
57 |
58 |
59 | } // namespace can
60 |
61 |
62 | #endif // CAN_SOCKET_H
63 |
--------------------------------------------------------------------------------
/cantx.cpp:
--------------------------------------------------------------------------------
1 | /* A small command line program for a one-time or cyclic transmission of a single frame
2 | */
3 |
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "cxxopts.hpp"
16 |
17 | #include "cansocket.h"
18 | #include "priority.h"
19 |
20 |
21 | can_frame build_frame(const std::string& id, const std::string& data)
22 | {
23 | // Invalid inputs will result in id and data set to 0
24 | can_frame frame;
25 | frame.can_id = std::strtoul(id.c_str(), nullptr, 16) & 0x7FF; // Ignoring extended format
26 | frame.can_dlc = (data.size() + 1) / 2;
27 | if (frame.can_dlc > CAN_MAX_DLC)
28 | frame.can_dlc = CAN_MAX_DLC;
29 |
30 | auto d = std::strtoull(data.c_str(), nullptr, 16);
31 | for (int i=0; i> i * 8;
33 |
34 | return frame;
35 | }
36 |
37 |
38 | void print_frame(const can_frame& frame)
39 | {
40 | std::cout << "id: " << std::hex << frame.can_id << std::dec << ", dlc: "
41 | << static_cast(frame.can_dlc) << ", data: " << std::hex << std::setfill('0');
42 | for (int i=frame.can_dlc-1; i>0; --i) {
43 | std::cout << std::setw(2) << static_cast(frame.data[i]) << ' ';
44 | }
45 | if (frame.can_dlc > 0)
46 | std::cout << std::setw(2) << static_cast(frame.data[0]) << '\n';
47 | std::cout.copyfmt(std::ios{nullptr}); // Reset format state
48 | }
49 |
50 |
51 | void transmit_frame(std::atomic& transmit_cyclical, const std::string device, can_frame frame,
52 | int cycle_time)
53 | {
54 | can::Socket can_socket;
55 | try {
56 | can_socket.open(device);
57 | }
58 | catch (const can::Socket_error& e) {
59 | std::cerr << e.what() << std::endl;
60 | return;
61 | }
62 |
63 | do
64 | {
65 | if (can_socket.transmit(&frame) != sizeof(frame)) {
66 | std::cerr << "Socket write error" << std::endl;
67 | return;
68 | }
69 | if (cycle_time > 0) {
70 | std::this_thread::sleep_for(std::chrono::milliseconds(cycle_time));
71 | }
72 | } while (transmit_cyclical.load());
73 | }
74 |
75 |
76 | std::tuple parse_args(int argc, char** argv)
77 | {
78 | std::string device;
79 | std::string id;
80 | std::string payload;
81 | int cycle_time;
82 | bool realtime = false;
83 |
84 | try {
85 | cxxopts::Options options{"cantx", "CAN message transmitter"};
86 | options.add_options()
87 | ("i,id", "Hex frame ID", cxxopts::value(id))
88 | ("p,payload", "Hex data string", cxxopts::value(payload)->default_value("00"))
89 | ("c,cycle", "Cycle time in ms", cxxopts::value(cycle_time)->default_value("-1"))
90 | ("d,device", "CAN device name", cxxopts::value(device)->default_value("can0"))
91 | ("r,realtime", "Enable realtime scheduling policy", cxxopts::value(realtime))
92 | ;
93 | options.parse(argc, argv);
94 |
95 | if (options.count("id") == 0) {
96 | throw std::runtime_error{"Message ID must be specified, use -i or --id option"};
97 | }
98 | if (payload.size() % 2 || payload.size() > 16) {
99 | throw std::runtime_error{"Payload size error, size must be even and <= 16"};
100 | }
101 |
102 | return std::make_tuple(std::move(device), build_frame(id, payload), cycle_time, realtime);
103 | }
104 | catch (const cxxopts::OptionException& e) {
105 | throw std::runtime_error{e.what()};
106 | }
107 | }
108 |
109 |
110 | int main(int argc, char** argv)
111 | {
112 | std::string device;
113 | can_frame frame;
114 | int cycle_time;
115 | bool realtime;
116 |
117 | try {
118 | std::tie(device, frame, cycle_time, realtime) = parse_args(argc, argv);
119 | }
120 | catch (const std::runtime_error& e) {
121 | std::cerr << "Error parsing command line options:\n" << e.what() << std::endl;
122 | return 1;
123 | }
124 |
125 | std::atomic transmit_cyclical{cycle_time > 0};
126 |
127 | std::cout << "Transmitting frame on " << device;
128 | if (transmit_cyclical.load())
129 | std::cout << " each " << cycle_time << " ms...";
130 | std::cout << '\n';
131 | print_frame(frame);
132 |
133 | std::thread transmitter{&transmit_frame, std::ref(transmit_cyclical), device, frame, cycle_time};
134 |
135 | if (transmit_cyclical.load()) {
136 | if (realtime) {
137 | if (priority::set_realtime(transmitter.native_handle()))
138 | std::cout << "Transmitter thread set to realtime scheduling policy\n";
139 | else
140 | std::cout << "Warning: Could not set scheduling policy, forgot sudo?\n";
141 | }
142 | std::cout << "Press enter to stop..." << std::endl;
143 | std::cin.ignore(); // Wait in main thread
144 | std::cout << "Stopping transmitter..." << std::endl;
145 | transmit_cyclical.store(false);
146 | }
147 |
148 | transmitter.join();
149 |
150 | std::cout << "Program finished" << std::endl;
151 | return 0;
152 | }
153 |
--------------------------------------------------------------------------------
/cxxopts.hpp:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 | */
24 |
25 | #ifndef CXX_OPTS_HPP
26 | #define CXX_OPTS_HPP
27 |
28 | #if defined(__GNUC__)
29 | #pragma GCC diagnostic push
30 | #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
31 | #endif
32 |
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include