├── .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 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | //when we ask cxxopts to use Unicode, help strings are processed using ICU, 46 | //which results in the correct lengths being computed for strings when they 47 | //are formatted for the help output 48 | //it is necessary to make sure that can be found by the 49 | //compiler, and that icu-uc is linked in to the binary. 50 | 51 | #ifdef CXXOPTS_USE_UNICODE 52 | #include 53 | 54 | namespace cxxopts 55 | { 56 | typedef icu::UnicodeString String; 57 | 58 | inline 59 | String 60 | toLocalString(std::string s) 61 | { 62 | return icu::UnicodeString::fromUTF8(std::move(s)); 63 | } 64 | 65 | class UnicodeStringIterator : public 66 | std::iterator 67 | { 68 | public: 69 | 70 | UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) 71 | : s(string) 72 | , i(pos) 73 | { 74 | } 75 | 76 | value_type 77 | operator*() const 78 | { 79 | return s->char32At(i); 80 | } 81 | 82 | bool 83 | operator==(const UnicodeStringIterator& rhs) const 84 | { 85 | return s == rhs.s && i == rhs.i; 86 | } 87 | 88 | bool 89 | operator!=(const UnicodeStringIterator& rhs) const 90 | { 91 | return !(*this == rhs); 92 | } 93 | 94 | UnicodeStringIterator& 95 | operator++() 96 | { 97 | ++i; 98 | return *this; 99 | } 100 | 101 | UnicodeStringIterator 102 | operator+(int32_t v) 103 | { 104 | return UnicodeStringIterator(s, i + v); 105 | } 106 | 107 | private: 108 | const icu::UnicodeString* s; 109 | int32_t i; 110 | }; 111 | 112 | inline 113 | String& 114 | stringAppend(String&s, String a) 115 | { 116 | return s.append(std::move(a)); 117 | } 118 | 119 | inline 120 | String& 121 | stringAppend(String& s, int n, UChar32 c) 122 | { 123 | for (int i = 0; i != n; ++i) 124 | { 125 | s.append(c); 126 | } 127 | 128 | return s; 129 | } 130 | 131 | template 132 | String& 133 | stringAppend(String& s, Iterator begin, Iterator end) 134 | { 135 | while (begin != end) 136 | { 137 | s.append(*begin); 138 | ++begin; 139 | } 140 | 141 | return s; 142 | } 143 | 144 | inline 145 | size_t 146 | stringLength(const String& s) 147 | { 148 | return s.length(); 149 | } 150 | 151 | inline 152 | std::string 153 | toUTF8String(const String& s) 154 | { 155 | std::string result; 156 | s.toUTF8String(result); 157 | 158 | return result; 159 | } 160 | 161 | inline 162 | bool 163 | empty(const String& s) 164 | { 165 | return s.isEmpty(); 166 | } 167 | } 168 | 169 | namespace std 170 | { 171 | cxxopts::UnicodeStringIterator 172 | begin(const icu::UnicodeString& s) 173 | { 174 | return cxxopts::UnicodeStringIterator(&s, 0); 175 | } 176 | 177 | cxxopts::UnicodeStringIterator 178 | end(const icu::UnicodeString& s) 179 | { 180 | return cxxopts::UnicodeStringIterator(&s, s.length()); 181 | } 182 | } 183 | 184 | //ifdef CXXOPTS_USE_UNICODE 185 | #else 186 | 187 | namespace cxxopts 188 | { 189 | typedef std::string String; 190 | 191 | template 192 | T 193 | toLocalString(T&& t) 194 | { 195 | return t; 196 | } 197 | 198 | inline 199 | size_t 200 | stringLength(const String& s) 201 | { 202 | return s.length(); 203 | } 204 | 205 | inline 206 | String& 207 | stringAppend(String&s, String a) 208 | { 209 | return s.append(std::move(a)); 210 | } 211 | 212 | inline 213 | String& 214 | stringAppend(String& s, size_t n, char c) 215 | { 216 | return s.append(n, c); 217 | } 218 | 219 | template 220 | String& 221 | stringAppend(String& s, Iterator begin, Iterator end) 222 | { 223 | return s.append(begin, end); 224 | } 225 | 226 | template 227 | std::string 228 | toUTF8String(T&& t) 229 | { 230 | return std::forward(t); 231 | } 232 | 233 | inline 234 | bool 235 | empty(const std::string& s) 236 | { 237 | return s.empty(); 238 | } 239 | } 240 | 241 | //ifdef CXXOPTS_USE_UNICODE 242 | #endif 243 | 244 | namespace cxxopts 245 | { 246 | namespace 247 | { 248 | #ifdef _WIN32 249 | const std::string LQUOTE("\'"); 250 | const std::string RQUOTE("\'"); 251 | #else 252 | const std::string LQUOTE("‘"); 253 | const std::string RQUOTE("’"); 254 | #endif 255 | } 256 | 257 | class Value : public std::enable_shared_from_this 258 | { 259 | public: 260 | 261 | virtual void 262 | parse(const std::string& text) const = 0; 263 | 264 | virtual void 265 | parse() const = 0; 266 | 267 | virtual bool 268 | has_arg() const = 0; 269 | 270 | virtual bool 271 | has_default() const = 0; 272 | 273 | virtual bool 274 | is_container() const = 0; 275 | 276 | virtual bool 277 | has_implicit() const = 0; 278 | 279 | virtual std::string 280 | get_default_value() const = 0; 281 | 282 | virtual std::string 283 | get_implicit_value() const = 0; 284 | 285 | virtual std::shared_ptr 286 | default_value(const std::string& value) = 0; 287 | 288 | virtual std::shared_ptr 289 | implicit_value(const std::string& value) = 0; 290 | }; 291 | 292 | class OptionException : public std::exception 293 | { 294 | public: 295 | OptionException(const std::string& message) 296 | : m_message(message) 297 | { 298 | } 299 | 300 | virtual const char* 301 | what() const noexcept 302 | { 303 | return m_message.c_str(); 304 | } 305 | 306 | private: 307 | std::string m_message; 308 | }; 309 | 310 | class OptionSpecException : public OptionException 311 | { 312 | public: 313 | 314 | OptionSpecException(const std::string& message) 315 | : OptionException(message) 316 | { 317 | } 318 | }; 319 | 320 | class OptionParseException : public OptionException 321 | { 322 | public: 323 | OptionParseException(const std::string& message) 324 | : OptionException(message) 325 | { 326 | } 327 | }; 328 | 329 | class option_exists_error : public OptionSpecException 330 | { 331 | public: 332 | option_exists_error(const std::string& option) 333 | : OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + u8" already exists") 334 | { 335 | } 336 | }; 337 | 338 | class invalid_option_format_error : public OptionSpecException 339 | { 340 | public: 341 | invalid_option_format_error(const std::string& format) 342 | : OptionSpecException(u8"Invalid option format " + LQUOTE + format + RQUOTE) 343 | { 344 | } 345 | }; 346 | 347 | class option_not_exists_exception : public OptionParseException 348 | { 349 | public: 350 | option_not_exists_exception(const std::string& option) 351 | : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" does not exist") 352 | { 353 | } 354 | }; 355 | 356 | class missing_argument_exception : public OptionParseException 357 | { 358 | public: 359 | missing_argument_exception(const std::string& option) 360 | : OptionParseException( 361 | u8"Option " + LQUOTE + option + RQUOTE + u8" is missing an argument" 362 | ) 363 | { 364 | } 365 | }; 366 | 367 | class option_requires_argument_exception : public OptionParseException 368 | { 369 | public: 370 | option_requires_argument_exception(const std::string& option) 371 | : OptionParseException( 372 | u8"Option " + LQUOTE + option + RQUOTE + u8" requires an argument" 373 | ) 374 | { 375 | } 376 | }; 377 | 378 | class option_not_has_argument_exception : public OptionParseException 379 | { 380 | public: 381 | option_not_has_argument_exception 382 | ( 383 | const std::string& option, 384 | const std::string& arg 385 | ) 386 | : OptionParseException( 387 | u8"Option " + LQUOTE + option + RQUOTE + 388 | u8" does not take an argument, but argument" + 389 | LQUOTE + arg + RQUOTE + " given" 390 | ) 391 | { 392 | } 393 | }; 394 | 395 | class option_not_present_exception : public OptionParseException 396 | { 397 | public: 398 | option_not_present_exception(const std::string& option) 399 | : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" not present") 400 | { 401 | } 402 | }; 403 | 404 | class argument_incorrect_type : public OptionParseException 405 | { 406 | public: 407 | argument_incorrect_type 408 | ( 409 | const std::string& arg 410 | ) 411 | : OptionParseException( 412 | u8"Argument " + LQUOTE + arg + RQUOTE + u8" failed to parse" 413 | ) 414 | { 415 | } 416 | }; 417 | 418 | class option_required_exception : public OptionParseException 419 | { 420 | public: 421 | option_required_exception(const std::string& option) 422 | : OptionParseException( 423 | u8"Option " + LQUOTE + option + RQUOTE + u8" is required but not present" 424 | ) 425 | { 426 | } 427 | }; 428 | 429 | namespace values 430 | { 431 | namespace 432 | { 433 | std::basic_regex integer_pattern 434 | ("(-)?(0x)?([1-9a-zA-Z][0-9a-zA-Z]*)|(0)"); 435 | } 436 | 437 | namespace detail 438 | { 439 | template 440 | struct SignedCheck; 441 | 442 | template 443 | struct SignedCheck 444 | { 445 | template 446 | void 447 | operator()(bool negative, U u, const std::string& text) 448 | { 449 | if (negative) 450 | { 451 | if (u > static_cast(-std::numeric_limits::min())) 452 | { 453 | throw argument_incorrect_type(text); 454 | } 455 | } 456 | else 457 | { 458 | if (u > static_cast(std::numeric_limits::max())) 459 | { 460 | throw argument_incorrect_type(text); 461 | } 462 | } 463 | } 464 | }; 465 | 466 | template 467 | struct SignedCheck 468 | { 469 | template 470 | void 471 | operator()(bool, U, const std::string&) {} 472 | }; 473 | 474 | template 475 | void 476 | check_signed_range(bool negative, U value, const std::string& text) 477 | { 478 | SignedCheck::is_signed>()(negative, value, text); 479 | } 480 | } 481 | 482 | template 483 | R 484 | checked_negate(T&& t, const std::string&, std::true_type) 485 | { 486 | // if we got to here, then `t` is a positive number that fits into 487 | // `R`. So to avoid MSVC C4146, we first cast it to `R`. 488 | // See https://github.com/jarro2783/cxxopts/issues/62 for more details. 489 | return -static_cast(t); 490 | } 491 | 492 | template 493 | T 494 | checked_negate(T&&, const std::string& text, std::false_type) 495 | { 496 | throw argument_incorrect_type(text); 497 | } 498 | 499 | template 500 | void 501 | integer_parser(const std::string& text, T& value) 502 | { 503 | std::smatch match; 504 | std::regex_match(text, match, integer_pattern); 505 | 506 | if (match.length() == 0) 507 | { 508 | throw argument_incorrect_type(text); 509 | } 510 | 511 | if (match.length(4) > 0) 512 | { 513 | value = 0; 514 | return; 515 | } 516 | 517 | using US = typename std::make_unsigned::type; 518 | 519 | constexpr auto umax = std::numeric_limits::max(); 520 | constexpr bool is_signed = std::numeric_limits::is_signed; 521 | const bool negative = match.length(1) > 0; 522 | const auto base = match.length(2) > 0 ? 16 : 10; 523 | 524 | auto value_match = match[3]; 525 | 526 | US result = 0; 527 | 528 | for (auto iter = value_match.first; iter != value_match.second; ++iter) 529 | { 530 | int digit = 0; 531 | 532 | if (*iter >= '0' && *iter <= '9') 533 | { 534 | digit = *iter - '0'; 535 | } 536 | else if (*iter >= 'a' && *iter <= 'f') 537 | { 538 | digit = *iter - 'a' + 10; 539 | } 540 | else if (*iter >= 'A' && *iter <= 'F') 541 | { 542 | digit = *iter - 'A' + 10; 543 | } 544 | 545 | if (umax - digit < result * base) 546 | { 547 | throw argument_incorrect_type(text); 548 | } 549 | 550 | result = result * base + digit; 551 | } 552 | 553 | detail::check_signed_range(negative, result, text); 554 | 555 | if (negative) 556 | { 557 | value = checked_negate(result, 558 | text, 559 | std::integral_constant()); 560 | //if (!is_signed) 561 | //{ 562 | // throw argument_incorrect_type(text); 563 | //} 564 | //value = -result; 565 | } 566 | else 567 | { 568 | value = result; 569 | } 570 | } 571 | 572 | template 573 | void stringstream_parser(const std::string& text, T& value) 574 | { 575 | std::stringstream in(text); 576 | in >> value; 577 | if (!in) { 578 | throw argument_incorrect_type(text); 579 | } 580 | } 581 | 582 | inline 583 | void 584 | parse_value(const std::string& text, uint8_t& value) 585 | { 586 | integer_parser(text, value); 587 | } 588 | 589 | inline 590 | void 591 | parse_value(const std::string& text, int8_t& value) 592 | { 593 | integer_parser(text, value); 594 | } 595 | 596 | inline 597 | void 598 | parse_value(const std::string& text, uint16_t& value) 599 | { 600 | integer_parser(text, value); 601 | } 602 | 603 | inline 604 | void 605 | parse_value(const std::string& text, int16_t& value) 606 | { 607 | integer_parser(text, value); 608 | } 609 | 610 | inline 611 | void 612 | parse_value(const std::string& text, uint32_t& value) 613 | { 614 | integer_parser(text, value); 615 | } 616 | 617 | inline 618 | void 619 | parse_value(const std::string& text, int32_t& value) 620 | { 621 | integer_parser(text, value); 622 | } 623 | 624 | inline 625 | void 626 | parse_value(const std::string& text, uint64_t& value) 627 | { 628 | integer_parser(text, value); 629 | } 630 | 631 | inline 632 | void 633 | parse_value(const std::string& text, int64_t& value) 634 | { 635 | integer_parser(text, value); 636 | } 637 | 638 | inline 639 | void 640 | parse_value(const std::string& /*text*/, bool& value) 641 | { 642 | //TODO recognise on, off, yes, no, enable, disable 643 | //so that we can write --long=yes explicitly 644 | value = true; 645 | } 646 | 647 | inline 648 | void 649 | parse_value(const std::string& text, std::string& value) 650 | { 651 | value = text; 652 | } 653 | 654 | // The fallback parser. It uses the stringstream parser to parse all types 655 | // that have not been overloaded explicitly. It has to be placed in the 656 | // source code before all other more specialized templates. 657 | template 658 | void 659 | parse_value(const std::string& text, T& value) { 660 | stringstream_parser(text, value); 661 | } 662 | 663 | template 664 | void 665 | parse_value(const std::string& text, std::vector& value) 666 | { 667 | T v; 668 | parse_value(text, v); 669 | value.push_back(v); 670 | } 671 | 672 | template 673 | struct value_has_arg 674 | { 675 | static constexpr bool value = true; 676 | }; 677 | 678 | template <> 679 | struct value_has_arg 680 | { 681 | static constexpr bool value = false; 682 | }; 683 | 684 | template 685 | struct type_is_container 686 | { 687 | static constexpr bool value = false; 688 | }; 689 | 690 | template 691 | struct type_is_container> 692 | { 693 | static constexpr bool value = true; 694 | }; 695 | 696 | template 697 | class standard_value : public Value 698 | { 699 | public: 700 | standard_value() 701 | : m_result(std::make_shared()) 702 | , m_store(m_result.get()) 703 | { 704 | } 705 | 706 | standard_value(T* t) 707 | : m_store(t) 708 | { 709 | } 710 | 711 | void 712 | parse(const std::string& text) const 713 | { 714 | parse_value(text, *m_store); 715 | } 716 | 717 | bool 718 | is_container() const 719 | { 720 | return type_is_container::value; 721 | } 722 | 723 | void 724 | parse() const 725 | { 726 | parse_value(m_default_value, *m_store); 727 | } 728 | 729 | bool 730 | has_arg() const 731 | { 732 | return value_has_arg::value; 733 | } 734 | 735 | bool 736 | has_default() const 737 | { 738 | return m_default; 739 | } 740 | 741 | bool 742 | has_implicit() const 743 | { 744 | return m_implicit; 745 | } 746 | 747 | virtual std::shared_ptr 748 | default_value(const std::string& value){ 749 | m_default = true; 750 | m_default_value = value; 751 | return shared_from_this(); 752 | } 753 | 754 | virtual std::shared_ptr 755 | implicit_value(const std::string& value){ 756 | m_implicit = true; 757 | m_implicit_value = value; 758 | return shared_from_this(); 759 | } 760 | 761 | std::string 762 | get_default_value() const 763 | { 764 | return m_default_value; 765 | } 766 | 767 | std::string 768 | get_implicit_value() const 769 | { 770 | return m_implicit_value; 771 | } 772 | 773 | const T& 774 | get() const 775 | { 776 | if (m_store == nullptr) 777 | { 778 | return *m_result; 779 | } 780 | else 781 | { 782 | return *m_store; 783 | } 784 | } 785 | 786 | protected: 787 | std::shared_ptr m_result; 788 | T* m_store; 789 | bool m_default = false; 790 | std::string m_default_value; 791 | bool m_implicit = false; 792 | std::string m_implicit_value; 793 | }; 794 | } 795 | 796 | template 797 | std::shared_ptr 798 | value() 799 | { 800 | return std::make_shared>(); 801 | } 802 | 803 | template 804 | std::shared_ptr 805 | value(T& t) 806 | { 807 | return std::make_shared>(&t); 808 | } 809 | 810 | class OptionAdder; 811 | 812 | class OptionDetails 813 | { 814 | public: 815 | OptionDetails 816 | ( 817 | const String& desc, 818 | std::shared_ptr val 819 | ) 820 | : m_desc(desc) 821 | , m_value(val) 822 | , m_count(0) 823 | { 824 | } 825 | 826 | const String& 827 | description() const 828 | { 829 | return m_desc; 830 | } 831 | 832 | bool 833 | has_arg() const 834 | { 835 | return m_value->has_arg(); 836 | } 837 | 838 | void 839 | parse(const std::string& text) 840 | { 841 | m_value->parse(text); 842 | ++m_count; 843 | } 844 | 845 | void 846 | parse_default() 847 | { 848 | m_value->parse(); 849 | } 850 | 851 | int 852 | count() const 853 | { 854 | return m_count; 855 | } 856 | 857 | const Value& value() const { 858 | return *m_value; 859 | } 860 | 861 | template 862 | const T& 863 | as() const 864 | { 865 | #ifdef CXXOPTS_NO_RTTI 866 | return static_cast&>(*m_value).get(); 867 | #else 868 | return dynamic_cast&>(*m_value).get(); 869 | #endif 870 | } 871 | 872 | private: 873 | String m_desc; 874 | std::shared_ptr m_value; 875 | int m_count; 876 | }; 877 | 878 | struct HelpOptionDetails 879 | { 880 | std::string s; 881 | std::string l; 882 | String desc; 883 | bool has_arg; 884 | bool has_default; 885 | std::string default_value; 886 | bool has_implicit; 887 | std::string implicit_value; 888 | std::string arg_help; 889 | bool is_container; 890 | }; 891 | 892 | struct HelpGroupDetails 893 | { 894 | std::string name; 895 | std::string description; 896 | std::vector options; 897 | }; 898 | 899 | class Options 900 | { 901 | public: 902 | 903 | Options(std::string program, std::string help_string = "") 904 | : m_program(std::move(program)) 905 | , m_help_string(toLocalString(std::move(help_string))) 906 | , m_positional_help("positional parameters") 907 | , m_next_positional(m_positional.end()) 908 | { 909 | } 910 | 911 | inline 912 | Options& 913 | positional_help(std::string help_text) 914 | { 915 | m_positional_help = std::move(help_text); 916 | return *this; 917 | } 918 | 919 | inline 920 | void 921 | parse(int& argc, char**& argv); 922 | 923 | inline 924 | OptionAdder 925 | add_options(std::string group = ""); 926 | 927 | inline 928 | void 929 | add_option 930 | ( 931 | const std::string& group, 932 | const std::string& s, 933 | const std::string& l, 934 | std::string desc, 935 | std::shared_ptr value, 936 | std::string arg_help 937 | ); 938 | 939 | int 940 | count(const std::string& o) const 941 | { 942 | auto iter = m_options.find(o); 943 | if (iter == m_options.end()) 944 | { 945 | return 0; 946 | } 947 | 948 | return iter->second->count(); 949 | } 950 | 951 | const OptionDetails& 952 | operator[](const std::string& option) const 953 | { 954 | auto iter = m_options.find(option); 955 | 956 | if (iter == m_options.end()) 957 | { 958 | throw option_not_present_exception(option); 959 | } 960 | 961 | return *iter->second; 962 | } 963 | 964 | //parse positional arguments into the given option 965 | inline 966 | void 967 | parse_positional(std::string option); 968 | 969 | inline 970 | void 971 | parse_positional(std::vector options); 972 | 973 | inline 974 | std::string 975 | help(const std::vector& groups = {""}) const; 976 | 977 | inline 978 | const std::vector 979 | groups() const; 980 | 981 | inline 982 | const HelpGroupDetails& 983 | group_help(const std::string& group) const; 984 | 985 | private: 986 | 987 | inline 988 | void 989 | add_one_option 990 | ( 991 | const std::string& option, 992 | std::shared_ptr details 993 | ); 994 | 995 | inline 996 | bool 997 | consume_positional(std::string a); 998 | 999 | inline 1000 | void 1001 | add_to_option(const std::string& option, const std::string& arg); 1002 | 1003 | inline 1004 | void 1005 | parse_option 1006 | ( 1007 | std::shared_ptr value, 1008 | const std::string& name, 1009 | const std::string& arg = "" 1010 | ); 1011 | 1012 | inline 1013 | void 1014 | checked_parse_arg 1015 | ( 1016 | int argc, 1017 | char* argv[], 1018 | int& current, 1019 | std::shared_ptr value, 1020 | const std::string& name 1021 | ); 1022 | 1023 | inline 1024 | String 1025 | help_one_group(const std::string& group) const; 1026 | 1027 | inline 1028 | void 1029 | generate_group_help 1030 | ( 1031 | String& result, 1032 | const std::vector& groups 1033 | ) const; 1034 | 1035 | inline 1036 | void 1037 | generate_all_groups_help(String& result) const; 1038 | 1039 | std::string m_program; 1040 | String m_help_string; 1041 | std::string m_positional_help; 1042 | 1043 | std::map> m_options; 1044 | std::vector m_positional; 1045 | std::vector::iterator m_next_positional; 1046 | std::unordered_set m_positional_set; 1047 | 1048 | //mapping from groups to help options 1049 | std::map m_help; 1050 | }; 1051 | 1052 | class OptionAdder 1053 | { 1054 | public: 1055 | 1056 | OptionAdder(Options& options, std::string group) 1057 | : m_options(options), m_group(std::move(group)) 1058 | { 1059 | } 1060 | 1061 | inline 1062 | OptionAdder& 1063 | operator() 1064 | ( 1065 | const std::string& opts, 1066 | const std::string& desc, 1067 | std::shared_ptr value 1068 | = ::cxxopts::value(), 1069 | std::string arg_help = "" 1070 | ); 1071 | 1072 | private: 1073 | Options& m_options; 1074 | std::string m_group; 1075 | }; 1076 | 1077 | // A helper function for setting required arguments 1078 | inline 1079 | void 1080 | check_required 1081 | ( 1082 | const Options& options, 1083 | const std::vector& required 1084 | ) 1085 | { 1086 | for (auto& r : required) 1087 | { 1088 | if (options.count(r) == 0) 1089 | { 1090 | throw option_required_exception(r); 1091 | } 1092 | } 1093 | } 1094 | 1095 | namespace 1096 | { 1097 | constexpr int OPTION_LONGEST = 30; 1098 | constexpr int OPTION_DESC_GAP = 2; 1099 | 1100 | std::basic_regex option_matcher 1101 | ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); 1102 | 1103 | std::basic_regex option_specifier 1104 | ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); 1105 | 1106 | String 1107 | format_option 1108 | ( 1109 | const HelpOptionDetails& o 1110 | ) 1111 | { 1112 | auto& s = o.s; 1113 | auto& l = o.l; 1114 | 1115 | String result = " "; 1116 | 1117 | if (s.size() > 0) 1118 | { 1119 | result += "-" + toLocalString(s) + ","; 1120 | } 1121 | else 1122 | { 1123 | result += " "; 1124 | } 1125 | 1126 | if (l.size() > 0) 1127 | { 1128 | result += " --" + toLocalString(l); 1129 | } 1130 | 1131 | if (o.has_arg) 1132 | { 1133 | auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; 1134 | 1135 | if (o.has_implicit) 1136 | { 1137 | result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; 1138 | } 1139 | else 1140 | { 1141 | result += " " + arg; 1142 | } 1143 | } 1144 | 1145 | return result; 1146 | } 1147 | 1148 | String 1149 | format_description 1150 | ( 1151 | const HelpOptionDetails& o, 1152 | size_t start, 1153 | size_t width 1154 | ) 1155 | { 1156 | auto desc = o.desc; 1157 | 1158 | if (o.has_default) 1159 | { 1160 | desc += toLocalString(" (default: " + o.default_value + ")"); 1161 | } 1162 | 1163 | String result; 1164 | 1165 | auto current = std::begin(desc); 1166 | auto startLine = current; 1167 | auto lastSpace = current; 1168 | 1169 | auto size = size_t{}; 1170 | 1171 | while (current != std::end(desc)) 1172 | { 1173 | if (*current == ' ') 1174 | { 1175 | lastSpace = current; 1176 | } 1177 | 1178 | if (size > width) 1179 | { 1180 | if (lastSpace == startLine) 1181 | { 1182 | stringAppend(result, startLine, current + 1); 1183 | stringAppend(result, "\n"); 1184 | stringAppend(result, start, ' '); 1185 | startLine = current + 1; 1186 | lastSpace = startLine; 1187 | } 1188 | else 1189 | { 1190 | stringAppend(result, startLine, lastSpace); 1191 | stringAppend(result, "\n"); 1192 | stringAppend(result, start, ' '); 1193 | startLine = lastSpace + 1; 1194 | } 1195 | size = 0; 1196 | } 1197 | else 1198 | { 1199 | ++size; 1200 | } 1201 | 1202 | ++current; 1203 | } 1204 | 1205 | //append whatever is left 1206 | stringAppend(result, startLine, current); 1207 | 1208 | return result; 1209 | } 1210 | } 1211 | 1212 | OptionAdder 1213 | Options::add_options(std::string group) 1214 | { 1215 | return OptionAdder(*this, std::move(group)); 1216 | } 1217 | 1218 | OptionAdder& 1219 | OptionAdder::operator() 1220 | ( 1221 | const std::string& opts, 1222 | const std::string& desc, 1223 | std::shared_ptr value, 1224 | std::string arg_help 1225 | ) 1226 | { 1227 | std::match_results result; 1228 | std::regex_match(opts.c_str(), result, option_specifier); 1229 | 1230 | if (result.empty()) 1231 | { 1232 | throw invalid_option_format_error(opts); 1233 | } 1234 | 1235 | const auto& short_match = result[2]; 1236 | const auto& long_match = result[3]; 1237 | 1238 | if (!short_match.length() && !long_match.length()) 1239 | { 1240 | throw invalid_option_format_error(opts); 1241 | } else if (long_match.length() == 1 && short_match.length()) 1242 | { 1243 | throw invalid_option_format_error(opts); 1244 | } 1245 | 1246 | auto option_names = [] 1247 | ( 1248 | const std::sub_match& short_, 1249 | const std::sub_match& long_ 1250 | ) 1251 | { 1252 | if (long_.length() == 1) 1253 | { 1254 | return std::make_tuple(long_.str(), short_.str()); 1255 | } 1256 | else 1257 | { 1258 | return std::make_tuple(short_.str(), long_.str()); 1259 | } 1260 | }(short_match, long_match); 1261 | 1262 | m_options.add_option 1263 | ( 1264 | m_group, 1265 | std::get<0>(option_names), 1266 | std::get<1>(option_names), 1267 | desc, 1268 | value, 1269 | std::move(arg_help) 1270 | ); 1271 | 1272 | return *this; 1273 | } 1274 | 1275 | void 1276 | Options::parse_option 1277 | ( 1278 | std::shared_ptr value, 1279 | const std::string& /*name*/, 1280 | const std::string& arg 1281 | ) 1282 | { 1283 | value->parse(arg); 1284 | } 1285 | 1286 | void 1287 | Options::checked_parse_arg 1288 | ( 1289 | int argc, 1290 | char* argv[], 1291 | int& current, 1292 | std::shared_ptr value, 1293 | const std::string& name 1294 | ) 1295 | { 1296 | if (current + 1 >= argc) 1297 | { 1298 | if (value->value().has_implicit()) 1299 | { 1300 | parse_option(value, name, value->value().get_implicit_value()); 1301 | } 1302 | else 1303 | { 1304 | throw missing_argument_exception(name); 1305 | } 1306 | } 1307 | else 1308 | { 1309 | if (argv[current + 1][0] == '-' && value->value().has_implicit()) 1310 | { 1311 | parse_option(value, name, value->value().get_implicit_value()); 1312 | } 1313 | else 1314 | { 1315 | parse_option(value, name, argv[current + 1]); 1316 | ++current; 1317 | } 1318 | } 1319 | } 1320 | 1321 | void 1322 | Options::add_to_option(const std::string& option, const std::string& arg) 1323 | { 1324 | auto iter = m_options.find(option); 1325 | 1326 | if (iter == m_options.end()) 1327 | { 1328 | throw option_not_exists_exception(option); 1329 | } 1330 | 1331 | parse_option(iter->second, option, arg); 1332 | } 1333 | 1334 | bool 1335 | Options::consume_positional(std::string a) 1336 | { 1337 | while (m_next_positional != m_positional.end()) 1338 | { 1339 | auto iter = m_options.find(*m_next_positional); 1340 | if (iter != m_options.end()) 1341 | { 1342 | if (!iter->second->value().is_container()) 1343 | { 1344 | if (iter->second->count() == 0) 1345 | { 1346 | add_to_option(*m_next_positional, a); 1347 | ++m_next_positional; 1348 | return true; 1349 | } 1350 | else 1351 | { 1352 | ++m_next_positional; 1353 | continue; 1354 | } 1355 | } 1356 | else 1357 | { 1358 | add_to_option(*m_next_positional, a); 1359 | return true; 1360 | } 1361 | } 1362 | ++m_next_positional; 1363 | } 1364 | 1365 | return false; 1366 | } 1367 | 1368 | void 1369 | Options::parse_positional(std::string option) 1370 | { 1371 | parse_positional(std::vector{option}); 1372 | } 1373 | 1374 | void 1375 | Options::parse_positional(std::vector options) 1376 | { 1377 | m_positional = std::move(options); 1378 | m_next_positional = m_positional.begin(); 1379 | 1380 | m_positional_set.insert(m_positional.begin(), m_positional.end()); 1381 | } 1382 | 1383 | void 1384 | Options::parse(int& argc, char**& argv) 1385 | { 1386 | int current = 1; 1387 | 1388 | int nextKeep = 1; 1389 | 1390 | bool consume_remaining = false; 1391 | 1392 | while (current != argc) 1393 | { 1394 | if (strcmp(argv[current], "--") == 0) 1395 | { 1396 | consume_remaining = true; 1397 | ++current; 1398 | break; 1399 | } 1400 | 1401 | std::match_results result; 1402 | std::regex_match(argv[current], result, option_matcher); 1403 | 1404 | if (result.empty()) 1405 | { 1406 | //not a flag 1407 | 1408 | //if true is returned here then it was consumed, otherwise it is 1409 | //ignored 1410 | if (consume_positional(argv[current])) 1411 | { 1412 | } 1413 | else 1414 | { 1415 | argv[nextKeep] = argv[current]; 1416 | ++nextKeep; 1417 | } 1418 | //if we return from here then it was parsed successfully, so continue 1419 | } 1420 | else 1421 | { 1422 | //short or long option? 1423 | if (result[4].length() != 0) 1424 | { 1425 | const std::string& s = result[4]; 1426 | 1427 | for (std::size_t i = 0; i != s.size(); ++i) 1428 | { 1429 | std::string name(1, s[i]); 1430 | auto iter = m_options.find(name); 1431 | 1432 | if (iter == m_options.end()) 1433 | { 1434 | throw option_not_exists_exception(name); 1435 | } 1436 | 1437 | auto value = iter->second; 1438 | 1439 | //if no argument then just add it 1440 | if (!value->has_arg()) 1441 | { 1442 | parse_option(value, name); 1443 | } 1444 | else 1445 | { 1446 | //it must be the last argument 1447 | if (i + 1 == s.size()) 1448 | { 1449 | checked_parse_arg(argc, argv, current, value, name); 1450 | } 1451 | else if (value->value().has_implicit()) 1452 | { 1453 | parse_option(value, name, value->value().get_implicit_value()); 1454 | } 1455 | else 1456 | { 1457 | //error 1458 | throw option_requires_argument_exception(name); 1459 | } 1460 | } 1461 | } 1462 | } 1463 | else if (result[1].length() != 0) 1464 | { 1465 | const std::string& name = result[1]; 1466 | 1467 | auto iter = m_options.find(name); 1468 | 1469 | if (iter == m_options.end()) 1470 | { 1471 | throw option_not_exists_exception(name); 1472 | } 1473 | 1474 | auto opt = iter->second; 1475 | 1476 | //equals provided for long option? 1477 | if (result[3].length() != 0) 1478 | { 1479 | //parse the option given 1480 | 1481 | //but if it doesn't take an argument, this is an error 1482 | if (!opt->has_arg()) 1483 | { 1484 | throw option_not_has_argument_exception(name, result[3]); 1485 | } 1486 | 1487 | parse_option(opt, name, result[3]); 1488 | } 1489 | else 1490 | { 1491 | if (opt->has_arg()) 1492 | { 1493 | //parse the next argument 1494 | checked_parse_arg(argc, argv, current, opt, name); 1495 | } 1496 | else 1497 | { 1498 | //parse with empty argument 1499 | parse_option(opt, name); 1500 | } 1501 | } 1502 | } 1503 | 1504 | } 1505 | 1506 | ++current; 1507 | } 1508 | 1509 | for (auto& opt : m_options) 1510 | { 1511 | auto& detail = opt.second; 1512 | auto& value = detail->value(); 1513 | 1514 | if(!detail->count() && value.has_default()){ 1515 | detail->parse_default(); 1516 | } 1517 | } 1518 | 1519 | if (consume_remaining) 1520 | { 1521 | while (current < argc) 1522 | { 1523 | if (!consume_positional(argv[current])) { 1524 | break; 1525 | } 1526 | ++current; 1527 | } 1528 | 1529 | //adjust argv for any that couldn't be swallowed 1530 | while (current != argc) { 1531 | argv[nextKeep] = argv[current]; 1532 | ++nextKeep; 1533 | ++current; 1534 | } 1535 | } 1536 | 1537 | argc = nextKeep; 1538 | 1539 | } 1540 | 1541 | void 1542 | Options::add_option 1543 | ( 1544 | const std::string& group, 1545 | const std::string& s, 1546 | const std::string& l, 1547 | std::string desc, 1548 | std::shared_ptr value, 1549 | std::string arg_help 1550 | ) 1551 | { 1552 | auto stringDesc = toLocalString(std::move(desc)); 1553 | auto option = std::make_shared(stringDesc, value); 1554 | 1555 | if (s.size() > 0) 1556 | { 1557 | add_one_option(s, option); 1558 | } 1559 | 1560 | if (l.size() > 0) 1561 | { 1562 | add_one_option(l, option); 1563 | } 1564 | 1565 | //add the help details 1566 | auto& options = m_help[group]; 1567 | 1568 | options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, 1569 | value->has_arg(), 1570 | value->has_default(), value->get_default_value(), 1571 | value->has_implicit(), value->get_implicit_value(), 1572 | std::move(arg_help), 1573 | value->is_container()}); 1574 | } 1575 | 1576 | void 1577 | Options::add_one_option 1578 | ( 1579 | const std::string& option, 1580 | std::shared_ptr details 1581 | ) 1582 | { 1583 | auto in = m_options.emplace(option, details); 1584 | 1585 | if (!in.second) 1586 | { 1587 | throw option_exists_error(option); 1588 | } 1589 | } 1590 | 1591 | String 1592 | Options::help_one_group(const std::string& g) const 1593 | { 1594 | typedef std::vector> OptionHelp; 1595 | 1596 | auto group = m_help.find(g); 1597 | if (group == m_help.end()) 1598 | { 1599 | return ""; 1600 | } 1601 | 1602 | OptionHelp format; 1603 | 1604 | size_t longest = 0; 1605 | 1606 | String result; 1607 | 1608 | if (!g.empty()) 1609 | { 1610 | result += toLocalString(" " + g + " options:\n"); 1611 | } 1612 | 1613 | for (const auto& o : group->second.options) 1614 | { 1615 | if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end()) 1616 | { 1617 | continue; 1618 | } 1619 | 1620 | auto s = format_option(o); 1621 | longest = std::max(longest, stringLength(s)); 1622 | format.push_back(std::make_pair(s, String())); 1623 | } 1624 | 1625 | longest = std::min(longest, static_cast(OPTION_LONGEST)); 1626 | 1627 | //widest allowed description 1628 | auto allowed = size_t{76} - longest - OPTION_DESC_GAP; 1629 | 1630 | auto fiter = format.begin(); 1631 | for (const auto& o : group->second.options) 1632 | { 1633 | if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end()) 1634 | { 1635 | continue; 1636 | } 1637 | 1638 | auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); 1639 | 1640 | result += fiter->first; 1641 | if (stringLength(fiter->first) > longest) 1642 | { 1643 | result += '\n'; 1644 | result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); 1645 | } 1646 | else 1647 | { 1648 | result += toLocalString(std::string(longest + OPTION_DESC_GAP - 1649 | stringLength(fiter->first), 1650 | ' ')); 1651 | } 1652 | result += d; 1653 | result += '\n'; 1654 | 1655 | ++fiter; 1656 | } 1657 | 1658 | return result; 1659 | } 1660 | 1661 | void 1662 | Options::generate_group_help 1663 | ( 1664 | String& result, 1665 | const std::vector& print_groups 1666 | ) const 1667 | { 1668 | for (size_t i = 0; i != print_groups.size(); ++i) 1669 | { 1670 | const String& group_help_text = help_one_group(print_groups[i]); 1671 | if (empty(group_help_text)) 1672 | { 1673 | continue; 1674 | } 1675 | result += group_help_text; 1676 | if (i < print_groups.size() - 1) 1677 | { 1678 | result += '\n'; 1679 | } 1680 | } 1681 | } 1682 | 1683 | void 1684 | Options::generate_all_groups_help(String& result) const 1685 | { 1686 | std::vector all_groups; 1687 | all_groups.reserve(m_help.size()); 1688 | 1689 | for (auto& group : m_help) 1690 | { 1691 | all_groups.push_back(group.first); 1692 | } 1693 | 1694 | generate_group_help(result, all_groups); 1695 | } 1696 | 1697 | std::string 1698 | Options::help(const std::vector& help_groups) const 1699 | { 1700 | String result = m_help_string + "\nUsage:\n " + 1701 | toLocalString(m_program) + " [OPTION...]"; 1702 | 1703 | if (m_positional.size() > 0) { 1704 | result += " " + toLocalString(m_positional_help); 1705 | } 1706 | 1707 | result += "\n\n"; 1708 | 1709 | if (help_groups.size() == 0) 1710 | { 1711 | generate_all_groups_help(result); 1712 | } 1713 | else 1714 | { 1715 | generate_group_help(result, help_groups); 1716 | } 1717 | 1718 | return toUTF8String(result); 1719 | } 1720 | 1721 | const std::vector 1722 | Options::groups() const 1723 | { 1724 | std::vector g; 1725 | 1726 | std::transform( 1727 | m_help.begin(), 1728 | m_help.end(), 1729 | std::back_inserter(g), 1730 | [] (const std::map::value_type& pair) 1731 | { 1732 | return pair.first; 1733 | } 1734 | ); 1735 | 1736 | return g; 1737 | } 1738 | 1739 | const HelpGroupDetails& 1740 | Options::group_help(const std::string& group) const 1741 | { 1742 | return m_help.at(group); 1743 | } 1744 | 1745 | } 1746 | 1747 | #if defined(__GNU__) 1748 | #pragma GCC diagnostic pop 1749 | #endif 1750 | 1751 | #endif //CXX_OPTS_HPP -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | CXXFLAGS=-std=c++14 -O3 -Wall -lpthread 3 | 4 | 5 | all: cantx canprint cangw cansim 6 | 7 | 8 | cantx: cansocket.o cantx.o 9 | $(CXX) $(CXXFLAGS) cansocket.o cantx.o -o cantx 10 | @echo "Build finished" 11 | 12 | canprint: cansocket.o canprint.o 13 | $(CXX) $(CXXFLAGS) cansocket.o canprint.o -o canprint 14 | @echo "Build finished" 15 | 16 | cangw: cansocket.o udpsocket.o cangw.o 17 | $(CXX) $(CXXFLAGS) cansocket.o udpsocket.o cangw.o -o cangw 18 | @echo "Build finished" 19 | 20 | cansim: timer.o udpsocket.o cansim.o 21 | $(CXX) $(CXXFLAGS) timer.o udpsocket.o cansim.o -o cansim 22 | @echo "Build finished" 23 | 24 | 25 | timer.o: timer.cpp timer.h 26 | $(CXX) -c $(CXXFLAGS) timer.cpp 27 | 28 | cansocket.o: cansocket.cpp cansocket.h 29 | $(CXX) -c $(CXXFLAGS) cansocket.cpp 30 | 31 | udpsocket.o: udpsocket.cpp udpsocket.h 32 | $(CXX) -c $(CXXFLAGS) udpsocket.cpp 33 | 34 | cantx.o: cantx.cpp cansocket.h 35 | $(CXX) -c $(CXXFLAGS) cantx.cpp 36 | 37 | canprint.o: canprint.cpp cansocket.h 38 | $(CXX) -c $(CXXFLAGS) canprint.cpp 39 | 40 | cangw.o: cangw.cpp cansocket.h udpsocket.h priority.h 41 | $(CXX) -c $(CXXFLAGS) cangw.cpp 42 | 43 | cansim.o: cansim.cpp udpsocket.h priority.h 44 | $(CXX) -c $(CXXFLAGS) cansim.cpp 45 | 46 | 47 | clean: 48 | -rm *o cantx canprint cangw cansim 49 | -------------------------------------------------------------------------------- /priority.h: -------------------------------------------------------------------------------- 1 | /* Functions for handling POSIX thread scheduling policy/priority 2 | */ 3 | 4 | 5 | #ifndef PRIORITY_H 6 | #define PRIORITY_H 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | namespace priority 15 | { 16 | 17 | 18 | inline bool set_realtime(std::thread::native_handle_type handle) 19 | { 20 | sched_param sch; 21 | auto priority = sched_get_priority_max(SCHED_FIFO); 22 | sch.sched_priority = priority > 0 ? priority : 1; // FIFO must be at least 1 23 | return pthread_setschedparam(handle, SCHED_FIFO, &sch) == 0; 24 | } 25 | 26 | 27 | inline bool is_realtime(std::thread::native_handle_type handle) 28 | { 29 | sched_param sch; 30 | int policy; 31 | if (pthread_getschedparam(handle, &policy, &sch) == 0) 32 | return policy == SCHED_FIFO || policy == SCHED_RR; 33 | return false; 34 | } 35 | 36 | 37 | inline int current_priority(std::thread::native_handle_type handle) 38 | { 39 | sched_param sch; 40 | int policy; 41 | if (pthread_getschedparam(handle, &policy, &sch) == 0) 42 | return sch.sched_priority; 43 | return -1; 44 | } 45 | 46 | 47 | } // namespace priority 48 | 49 | 50 | #endif // PRIORITY_H 51 | -------------------------------------------------------------------------------- /timer.cpp: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | 11 | void util::Timer::init_system_timer() 12 | { 13 | auto fd = ::open("/dev/mem", O_RDONLY); 14 | if (fd == -1) 15 | throw Timer_error{"System timer init error: Can't read /dev/mem, forgot sudo?"}; 16 | 17 | auto* m = ::mmap(nullptr, 4096, PROT_READ, MAP_SHARED, fd, ST_BASE_RPI_2_AND_3); 18 | if (m == MAP_FAILED) 19 | throw Timer_error{"System timer init error: Can't map memory"}; 20 | 21 | st_time = reinterpret_cast(reinterpret_cast(m) + TIMER_OFFSET); 22 | } 23 | 24 | 25 | std::uint64_t util::Timer::epoch_time() const 26 | { 27 | return std::chrono::high_resolution_clock::now().time_since_epoch().count(); 28 | } 29 | -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | /* A small timer class for the raspberry pi system timer 2 | */ 3 | 4 | 5 | #ifndef UTIL_TIMER_H 6 | #define UTIL_TIMER_H 7 | 8 | 9 | #include 10 | #include 11 | 12 | 13 | #define ST_BASE_RPI_1_AND_ZERO 0x20003000 14 | #define ST_BASE_RPI_2_AND_3 0x3F003000 15 | #define TIMER_OFFSET 4 16 | 17 | 18 | namespace util 19 | { 20 | 21 | 22 | class Timer_error : public std::runtime_error 23 | { 24 | public: 25 | Timer_error(const std::string& s) : std::runtime_error{s} {} 26 | Timer_error(const char* s) : std::runtime_error{s} {} 27 | }; 28 | 29 | 30 | class Timer 31 | { 32 | public: 33 | Timer() : st_time{&st_dummy} {} 34 | void init_system_timer(); // 64-bit microsecond system timer (requires sudo) 35 | std::uint64_t epoch_time() const; 36 | std::uint64_t system_time() const { return *st_time; } 37 | 38 | private: 39 | // 64-bit system timer 40 | std::uint64_t st_dummy{0}; 41 | std::uint64_t* st_time{nullptr}; 42 | }; 43 | 44 | 45 | } // namespace util 46 | 47 | 48 | #endif // UTIL_TIMER_H 49 | -------------------------------------------------------------------------------- /udpsocket.cpp: -------------------------------------------------------------------------------- 1 | #include "udpsocket.h" 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | namespace 14 | { 15 | 16 | 17 | class Scope_guard 18 | { 19 | public: 20 | Scope_guard(int fd) : fd_{fd} {} 21 | ~Scope_guard() { if (fd_ != -1) ::close(fd_); fd_ = -1; } 22 | Scope_guard(const Scope_guard&) = delete; 23 | Scope_guard& operator=(const Scope_guard&) = delete; 24 | void release() { fd_ = -1; } 25 | 26 | private: 27 | int fd_; 28 | }; 29 | 30 | 31 | } // namespace 32 | 33 | 34 | void udp::Socket::open(const std::string& ip, std::uint16_t port) 35 | { 36 | if (fd_ != -1) 37 | throw Socket_error{"Already open"}; 38 | 39 | fd_ = ::socket(AF_INET, SOCK_DGRAM, 0); 40 | 41 | if (fd_ == -1) 42 | throw Socket_error{"Could not open"}; 43 | 44 | Scope_guard guard{fd_}; 45 | 46 | addr_.sin_family = AF_INET; 47 | 48 | if (inet_aton(ip.c_str(), &addr_.sin_addr) == 0) 49 | throw Socket_error{"Error resolving IP address"}; 50 | 51 | addr_.sin_port = htons(port); 52 | 53 | guard.release(); 54 | } 55 | 56 | 57 | void udp::Socket::close() 58 | { 59 | if (fd_ != -1) { 60 | ::close(fd_); 61 | } 62 | 63 | reset(); 64 | } 65 | 66 | 67 | void udp::Socket::bind() 68 | { 69 | if (::bind(fd_, reinterpret_cast(&addr_), sizeof(addr_)) < 0) 70 | throw Socket_error{"Error while binding socket"}; 71 | } 72 | 73 | 74 | void udp::Socket::bind(const std::string& ip, std::uint16_t port) 75 | { 76 | sockaddr_in addr; 77 | addr.sin_family = AF_INET; 78 | 79 | if (inet_aton(ip.c_str(), &addr.sin_addr) == 0) 80 | throw Socket_error{"Error resolving IP address"}; 81 | 82 | addr.sin_port = htons(port); 83 | 84 | if (::bind(fd_, reinterpret_cast(&addr), sizeof(addr)) < 0) 85 | throw Socket_error{"Error while binding socket"}; 86 | } 87 | 88 | 89 | void udp::Socket::set_receive_timeout(time_t timeout) 90 | { 91 | if (timeout <= 0) 92 | throw Socket_error{"Timeout must be larger then 0"}; 93 | 94 | timeval tv; 95 | tv.tv_sec = timeout; 96 | tv.tv_usec = 0; 97 | if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) 98 | throw Socket_error{"Error setting receive timeout"}; 99 | } 100 | 101 | 102 | int udp::Socket::transmit(const std::vector& data) 103 | { 104 | return sendto(fd_, data.data(), data.size(), 0, reinterpret_cast(&addr_), 105 | sizeof(addr_)); 106 | } 107 | 108 | 109 | int udp::Socket::transmit(const can_frame* frame) 110 | { 111 | return sendto(fd_, frame, sizeof(can_frame), 0, reinterpret_cast(&addr_), 112 | sizeof(addr_)); 113 | } 114 | 115 | 116 | int udp::Socket::receive(can_frame* frame) 117 | { 118 | return recv(fd_, frame, sizeof(can_frame), 0); 119 | } 120 | 121 | 122 | void udp::Socket::reset() 123 | { 124 | fd_ = -1; 125 | addr_ = sockaddr_in{}; 126 | } 127 | -------------------------------------------------------------------------------- /udpsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef UDP_SOCKET_H 2 | #define UDP_SOCKET_H 3 | 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | struct can_frame; 15 | 16 | 17 | namespace udp 18 | { 19 | 20 | 21 | class Socket_error : public std::runtime_error 22 | { 23 | public: 24 | Socket_error(const std::string& s) : std::runtime_error{s} {} 25 | Socket_error(const char* s) : std::runtime_error{s} {} 26 | }; 27 | 28 | 29 | class Socket 30 | { 31 | public: 32 | Socket() : fd_{-1} {} 33 | ~Socket() { close(); } 34 | 35 | Socket(const Socket&) = delete; 36 | Socket& operator=(const Socket&) = delete; 37 | Socket(Socket&&) = delete; 38 | Socket& operator=(Socket&&) = delete; 39 | 40 | void open(const std::string& ip, std::uint16_t port); 41 | void close(); 42 | 43 | void bind(); 44 | void bind(const std::string& ip, std::uint16_t port); 45 | void set_receive_timeout(time_t timeout); 46 | 47 | int transmit(const std::vector& data); 48 | int transmit(const can_frame* frame); 49 | int receive(can_frame* frame); 50 | 51 | private: 52 | void reset(); 53 | 54 | int fd_; 55 | sockaddr_in addr_; 56 | }; 57 | 58 | 59 | } // namespace udp 60 | 61 | 62 | #endif // UDP_SOCKET_H 63 | --------------------------------------------------------------------------------