├── source ├── ets2_plugin │ ├── plugin.def │ ├── dash_plugin.txt │ ├── serial.hpp │ ├── options.hpp │ ├── options.cpp │ ├── serial.cpp │ └── plugin.cpp └── arduino_controller │ └── arduino_controller.ino ├── README.md └── LICENSE /source/ets2_plugin/plugin.def: -------------------------------------------------------------------------------- 1 | LIBRARY dash_plugin 2 | EXPORTS 3 | scs_telemetry_init=scs_telemetry_init 4 | scs_telemetry_shutdown=scs_telemetry_shutdown -------------------------------------------------------------------------------- /source/ets2_plugin/dash_plugin.txt: -------------------------------------------------------------------------------- 1 | comport str COM3 2 | 3 | factor_speed flt 4.0 4 | factor_engine_rpm flt 0.066 5 | factor_brake_air_pressure flt 1.0 6 | factor_brake_temperature flt 1.0 7 | factor_fuel_ratio flt 100.0 8 | factor_oil_pressure flt 1.0 9 | factor_oil_temperature flt 1.0 10 | factor_water_temperature flt 1.0 11 | factor_battery_voltage flt 5.0 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ets2_dashboard 2 | ============== 3 | 4 | **Euro Truck Simulator 2 Arduino Dashboard Code** 5 | 6 | This is the code for my dashboard project, there is a 7 | [blog post](http://skyhisi.blogspot.co.uk/2013/09/euro-truck-simulator-2-prototype-real.html) 8 | which contains photos and videos of it working and how it works. 9 | 10 | Installing 11 | ---------- 12 | 13 | 1. Connect up some servos and LEDs to your Arduino 14 | 2. Change the pin assignment in the `arduino_controller.ino` file and flash to the Arduino 15 | 3. Copy a pre-built DLL into your ETS2 plugins folder 16 | *or* 17 | 4. Build the DLL by running the `build.bat` file from a Visual Studio 2010 command prompt 18 | 5. Copy the `dash_plugin.txt` options file into the plugins folder 19 | 20 | Troubleshooting 21 | --------------- 22 | 23 | - Check you have copied the `dash_plugin.txt` file as well as the DLL into the plugins folder 24 | 25 | - Check the Arduino is on COM3 - the plugin expects to find the Arduino on COM3, you can change this by editing the `dash_plugin.txt` options file 26 | 27 | - Check for error messages in the game log (in `%USERPROFILE%\Documents\Euro Truck Simulator 2\game.log.txt`). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Silas Parker 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | The name of Silas Parker may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /source/ets2_plugin/serial.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #ifndef ETS2_DASHBOARD_PLUGIN_SERIAL_HPP 28 | #define ETS2_DASHBOARD_PLUGIN_SERIAL_HPP 29 | 30 | #include 31 | 32 | #define WINVER 0x0500 33 | #define _WIN32_WINNT 0x0500 34 | #include 35 | 36 | #include 37 | 38 | class Serial 39 | { 40 | public: 41 | Serial(); 42 | ~Serial(); 43 | 44 | bool is_valid() const; 45 | bool open(const std::string& name, std::string& errmsg); 46 | void close(); 47 | 48 | 49 | void write(const unsigned char* data, unsigned length); 50 | 51 | private: 52 | HANDLE com_port; 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /source/ets2_plugin/options.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef OPTIONS_HPP 29 | #define OPTIONS_HPP 30 | 31 | #include 32 | #include 33 | 34 | class Options 35 | { 36 | public: 37 | Options(); 38 | 39 | bool read_file(const std::string& path); 40 | 41 | float get_option_float(const std::string& key, float def) const; 42 | int get_option_int(const std::string& key, int def) const; 43 | const std::string& get_option_string(const std::string& key, const std::string& def) const; 44 | 45 | private: 46 | void parse_option_line(const std::string& line); 47 | 48 | typedef std::map float_map; 49 | typedef std::map int_map; 50 | typedef std::map string_map; 51 | 52 | float_map mOptionsFloat; 53 | int_map mOptionsInt; 54 | string_map mOptionsString; 55 | 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /source/ets2_plugin/options.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "options.hpp" 29 | 30 | #include 31 | #include 32 | 33 | Options::Options() : 34 | mOptionsFloat(), 35 | mOptionsInt(), 36 | mOptionsString() 37 | { 38 | } 39 | 40 | bool Options::read_file(const std::string& path) 41 | { 42 | std::ifstream file(path); 43 | if (!file.is_open()) 44 | return false; 45 | 46 | while (file.good()) 47 | { 48 | std::string line; 49 | std::getline(file, line); 50 | parse_option_line(line); 51 | } 52 | return true; 53 | } 54 | 55 | float Options::get_option_float(const std::string& key, float def) const 56 | { 57 | const float_map::const_iterator it = mOptionsFloat.find(key); 58 | return (it == mOptionsFloat.end()) ? def : it->second; 59 | } 60 | 61 | int Options::get_option_int(const std::string& key, int def) const 62 | { 63 | const int_map::const_iterator it = mOptionsInt.find(key); 64 | return (it == mOptionsInt.end()) ? def : it->second; 65 | } 66 | 67 | const std::string& Options::get_option_string(const std::string& key, const std::string& def) const 68 | { 69 | const string_map::const_iterator it = mOptionsString.find(key); 70 | return (it == mOptionsString.end()) ? def : it->second; 71 | } 72 | 73 | 74 | void Options::parse_option_line(const std::string& line) 75 | { 76 | std::string key; 77 | std::string type; 78 | std::stringstream ss(line); 79 | 80 | ss >> key >> type; 81 | if (type == "flt") 82 | { 83 | float val; 84 | ss >> val; 85 | mOptionsFloat[key] = val; 86 | } 87 | else if (type == "int") 88 | { 89 | int val; 90 | ss >> val; 91 | mOptionsInt[key] = val; 92 | } 93 | else if (type == "str") 94 | { 95 | std::string val; 96 | ss >> val; 97 | mOptionsString[key] = val; 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /source/ets2_plugin/serial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "serial.hpp" 29 | 30 | Serial::Serial() : com_port(INVALID_HANDLE_VALUE) 31 | {} 32 | 33 | Serial::~Serial() 34 | { 35 | close(); 36 | } 37 | 38 | bool Serial::is_valid() const 39 | { 40 | return com_port != INVALID_HANDLE_VALUE; 41 | } 42 | 43 | bool Serial::open(const std::string& name, std::string& errmsg) 44 | { 45 | bool retval = false; 46 | DCB config; 47 | 48 | errmsg.clear(); 49 | 50 | // If port already open, ignore call 51 | if (com_port != INVALID_HANDLE_VALUE) 52 | { 53 | errmsg = "COM port already open"; 54 | goto exit; 55 | } 56 | 57 | // Open COM port 58 | com_port = CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, 59 | OPEN_EXISTING, 0, NULL); 60 | if (com_port == INVALID_HANDLE_VALUE) 61 | { 62 | errmsg = "Can not open COM port"; 63 | goto exit; 64 | } 65 | 66 | // Get current config 67 | config.DCBlength = sizeof(config); 68 | if (!GetCommState(com_port, &config)) 69 | { 70 | errmsg = "Can not get COM port config"; 71 | goto exit; 72 | } 73 | 74 | // Set defaults 75 | config.BaudRate = CBR_115200; 76 | config.StopBits = ONESTOPBIT; 77 | config.Parity = NOPARITY; 78 | config.ByteSize = 8; 79 | 80 | // Set config 81 | if (!SetCommState(com_port, &config)) 82 | { 83 | errmsg = "Can not set COM port config"; 84 | goto exit; 85 | } 86 | 87 | retval = true; 88 | 89 | exit: 90 | if (retval == false) 91 | { 92 | close(); 93 | } 94 | return retval; 95 | } 96 | 97 | void Serial::close() 98 | { 99 | if (com_port != INVALID_HANDLE_VALUE) 100 | { 101 | CloseHandle(com_port); 102 | com_port = INVALID_HANDLE_VALUE; 103 | } 104 | } 105 | 106 | void Serial::write(const unsigned char* data, unsigned length) 107 | { 108 | if (data == 0 || length == 0) 109 | return; 110 | 111 | if (com_port != INVALID_HANDLE_VALUE) 112 | { 113 | unsigned long wrote; 114 | WriteFile(com_port, data, length, &wrote, NULL); 115 | FlushFileBuffers(com_port); 116 | } 117 | } 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /source/arduino_controller/arduino_controller.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include 29 | #include 30 | 31 | const int SPEEDO_PIN = A1; 32 | const int RPM_PIN = A0; 33 | const int LEFT_INDICATOR = A2; 34 | const int RIGHT_INDICATOR = A3; 35 | const int PARKING_BREAK = A4; 36 | const int FUEL_WARNING = A5; 37 | 38 | // Servo variables 39 | Servo speedo; 40 | Servo rpm; 41 | 42 | 43 | 44 | #define PACKET_SYNC 0xFF 45 | #define PACKET_VER 2 46 | 47 | #define SERVO_DIR_NORMAL false 48 | #define SERVO_DIR_INVERT true 49 | 50 | int serial_byte; 51 | 52 | LiquidCrystal lcd(12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2); 53 | 54 | void setup() 55 | { 56 | Serial.begin(115200); 57 | 58 | lcd.begin(16, 2); 59 | lcd.print("Self Test"); 60 | 61 | // Initialise servos 62 | speedo.attach(SPEEDO_PIN); 63 | speedo.write(180); 64 | 65 | rpm.attach(RPM_PIN); 66 | rpm.write(180); 67 | 68 | // Initialise LEDs 69 | pinMode(LEFT_INDICATOR, OUTPUT); 70 | pinMode(RIGHT_INDICATOR, OUTPUT); 71 | pinMode(PARKING_BREAK, OUTPUT); 72 | pinMode(FUEL_WARNING, OUTPUT); 73 | 74 | digitalWrite(LEFT_INDICATOR, 0); 75 | digitalWrite(RIGHT_INDICATOR, 0); 76 | digitalWrite(PARKING_BREAK, 0); 77 | digitalWrite(FUEL_WARNING, 0); 78 | 79 | 80 | delay(500); 81 | 82 | speedo.write(0); 83 | rpm.write(0); 84 | digitalWrite(LEFT_INDICATOR, 1); 85 | digitalWrite(RIGHT_INDICATOR, 1); 86 | digitalWrite(PARKING_BREAK, 1); 87 | digitalWrite(FUEL_WARNING, 1); 88 | 89 | 90 | delay(500); 91 | 92 | speedo.write(180); 93 | rpm.write(180); 94 | digitalWrite(LEFT_INDICATOR, 0); 95 | digitalWrite(RIGHT_INDICATOR, 0); 96 | digitalWrite(PARKING_BREAK, 0); 97 | digitalWrite(FUEL_WARNING, 0); 98 | 99 | 100 | lcd.clear(); 101 | lcd.print("Wait"); 102 | 103 | // Wait a second to ensure serial data isn't from re-programming 104 | delay(1000); 105 | lcd.clear(); 106 | lcd.print("Ready"); 107 | } 108 | 109 | 110 | 111 | void read_serial_byte_set_servo(Servo& servo, bool invert) 112 | { 113 | serial_byte = Serial.read(); 114 | serial_byte = (serial_byte < 0) ? 0 : ((serial_byte > 180) ? 180 : serial_byte); 115 | if (invert) 116 | servo.write(180 - serial_byte); 117 | else 118 | servo.write(serial_byte); 119 | } 120 | 121 | void skip_serial_byte() 122 | { 123 | (void)Serial.read(); 124 | } 125 | 126 | void digitalWriteFromBit(int port, int value, int shift) 127 | { 128 | digitalWrite(port, (value >> shift) & 0x01); 129 | } 130 | 131 | void loop() 132 | { 133 | if (Serial.available() < 16) 134 | return; 135 | 136 | serial_byte = Serial.read(); 137 | if (serial_byte != PACKET_SYNC) 138 | return; 139 | 140 | serial_byte = Serial.read(); 141 | if (serial_byte != PACKET_VER) 142 | { 143 | lcd.clear(); 144 | lcd.print("PROTOCOL VERSION ERROR"); 145 | return; 146 | } 147 | 148 | read_serial_byte_set_servo(speedo, SERVO_DIR_INVERT); // Speed 149 | read_serial_byte_set_servo(rpm, SERVO_DIR_INVERT); // RPM 150 | 151 | skip_serial_byte(); // Brake air pressure 152 | skip_serial_byte(); // Brake temperature 153 | skip_serial_byte(); // Fuel ratio 154 | skip_serial_byte(); // Oil pressure 155 | skip_serial_byte(); // Oil temperature 156 | skip_serial_byte(); // Water temperature 157 | skip_serial_byte(); // Battery voltage 158 | 159 | 160 | // Truck lights byte 161 | serial_byte = Serial.read(); 162 | digitalWriteFromBit(LEFT_INDICATOR, serial_byte, 5); 163 | digitalWriteFromBit(RIGHT_INDICATOR, serial_byte, 4); 164 | 165 | // Warning lights bytes 166 | 167 | serial_byte = Serial.read(); 168 | digitalWriteFromBit(PARKING_BREAK, serial_byte, 7); 169 | digitalWriteFromBit(FUEL_WARNING, serial_byte, 3); 170 | 171 | // Enabled flags 172 | serial_byte = Serial.read(); 173 | 174 | // Text length 175 | int text_len = Serial.read(); 176 | 177 | // Followed by text 178 | if (0 < text_len && text_len < 127) 179 | { 180 | lcd.clear(); 181 | for (int i = 0; i < text_len; ++i) 182 | { 183 | while (Serial.available() == 0) // Wait for data if slow 184 | { 185 | delay(2); 186 | } 187 | serial_byte = Serial.read(); 188 | if (serial_byte < 0 && serial_byte > 127) 189 | return; 190 | 191 | if (serial_byte == '\n') 192 | lcd.setCursor(0, 1); 193 | else 194 | lcd.print(char(serial_byte)); 195 | // delay(2); 196 | } 197 | } 198 | 199 | } 200 | 201 | -------------------------------------------------------------------------------- /source/ets2_plugin/plugin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Silas Parker 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | The name of Silas Parker may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | 29 | #define WINVER 0x0500 30 | #define _WIN32_WINNT 0x0500 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | 41 | #include "serial.hpp" 42 | #include "options.hpp" 43 | 44 | static const float METERS_PER_SEC_TO_MILES_PER_HOUR = 2.2369f; 45 | static const float METERS_PER_SEC_TO_KM_PER_HOUR = 3.6f; 46 | 47 | // Game log file 48 | scs_log_t game_log = NULL; 49 | 50 | // COM port file 51 | Serial serial_port; 52 | 53 | unsigned long last_update = 0; 54 | 55 | // Packet 56 | #define PACKET_MAX_SIZE 720 // Can send a maximum of 720 bytes per frame at 20 FPS over a 115200 bps line 57 | #define PACKET_SYNC 0xFF 58 | #define PACKET_VER 2 59 | 60 | unsigned char packet[PACKET_MAX_SIZE]; 61 | 62 | 63 | #define PACKBOOL(A,B,C,D,E,F,G,H) \ 64 | (((unsigned char)(A) << 7) | ((unsigned char)(B) << 6) | \ 65 | ((unsigned char)(C) << 5) | ((unsigned char)(D) << 4) | \ 66 | ((unsigned char)(E) << 3) | ((unsigned char)(F) << 2) | \ 67 | ((unsigned char)(G) << 1) | ((unsigned char)(H))) 68 | 69 | 70 | // Telemetry data 71 | struct telemetry_state_t 72 | { 73 | float speed; // Meters per Second 74 | float engine_rpm; // RPM 75 | 76 | int engine_gear; // >0 Forward, 0 Neutral, <0 Reverse 77 | 78 | bool parking_brake; 79 | bool motor_brake; 80 | 81 | float brake_air_pressure; // PSI 82 | bool brake_air_pressure_warning; 83 | bool brake_air_pressure_emergency; 84 | float brake_temperature; 85 | 86 | float fuel; // Liters 87 | bool fuel_warning; 88 | float fuel_average_consumption; // Liters / KM 89 | float fuel_capacity; // Liters 90 | 91 | float oil_pressure; // PSI 92 | bool oil_pressure_warning; 93 | float oil_temperature; // Celsius 94 | 95 | float water_temperature; // Celsius 96 | bool water_temperature_warning; 97 | 98 | float battery_voltage; // Volts 99 | bool battery_voltage_warning; 100 | 101 | bool electric_enabled; 102 | bool engine_enabled; 103 | 104 | bool light_lblinker; 105 | bool light_rblinker; 106 | bool light_parking; 107 | bool light_low_beam; // Dipped headlights 108 | bool light_high_beam; // Full headlights 109 | bool light_brake; 110 | bool light_reverse; 111 | 112 | float odometer; // Kilometres 113 | 114 | } telemetry; 115 | 116 | 117 | Options option_file; 118 | 119 | 120 | unsigned char float_to_byte(float value) 121 | { 122 | return 123 | (value > 254.0f) ? 254 : // Sync byte is 255 so avoid that 124 | ((value < 0.0f) ? 0 : 125 | (unsigned char)(value)); 126 | } 127 | 128 | void send_empty_packet() 129 | { 130 | memset(packet, 0, PACKET_MAX_SIZE); 131 | packet[0] = PACKET_SYNC; 132 | packet[1] = PACKET_VER; 133 | 134 | serial_port.write(packet, 16); 135 | } 136 | 137 | SCSAPI_VOID telemetry_frame_end(const scs_event_t /*event*/, const void *const /*event_info*/, const scs_context_t /*context*/) 138 | { 139 | if (!serial_port.is_valid()) 140 | return; 141 | 142 | const unsigned long now = GetTickCount(); 143 | const unsigned long diff = now - last_update; 144 | if (diff < 50) 145 | return; 146 | 147 | last_update = now; 148 | 149 | const float speed_mph = telemetry.speed * METERS_PER_SEC_TO_MILES_PER_HOUR; 150 | const float speed_kph = telemetry.speed * METERS_PER_SEC_TO_KM_PER_HOUR; 151 | 152 | const float fuel_ratio = telemetry.fuel / telemetry.fuel_capacity; 153 | 154 | 155 | 156 | unsigned idx = 0; 157 | 158 | memset(packet, 0, PACKET_MAX_SIZE); 159 | 160 | #define PUT_BYTE(X) packet[idx++] = (unsigned char)(X) 161 | #define PUT_BYTE_FLOAT(X) packet[idx++] = (unsigned char)(float_to_byte(X)) 162 | #define GETFLTOPT(X) (option_file.get_option_float(X, 1.0f)) 163 | 164 | // Packet header 165 | PUT_BYTE(PACKET_SYNC); 166 | PUT_BYTE(PACKET_VER); 167 | 168 | // Convert data for output by servos 169 | // Adjust the constant values to map to servo range 170 | 171 | PUT_BYTE_FLOAT(fabs(telemetry.speed) * GETFLTOPT("factor_speed")); // Absolute value for when reversing 172 | PUT_BYTE_FLOAT(telemetry.engine_rpm * GETFLTOPT("factor_engine_rpm")); 173 | PUT_BYTE_FLOAT(telemetry.brake_air_pressure * GETFLTOPT("factor_brake_air_pressure")); 174 | PUT_BYTE_FLOAT(telemetry.brake_temperature * GETFLTOPT("factor_brake_temperature")); 175 | PUT_BYTE_FLOAT(fuel_ratio * GETFLTOPT("factor_fuel_ratio")); // Fuel level 176 | PUT_BYTE_FLOAT(telemetry.oil_pressure * GETFLTOPT("factor_oil_pressure")); 177 | PUT_BYTE_FLOAT(telemetry.oil_temperature * GETFLTOPT("factor_oil_temperature")); 178 | PUT_BYTE_FLOAT(telemetry.water_temperature * GETFLTOPT("factor_water_temperature")); 179 | PUT_BYTE_FLOAT(telemetry.battery_voltage * GETFLTOPT("factor_battery_voltage")); 180 | 181 | 182 | // Pack data for LEDs into bytes 183 | 184 | // Truck lights 185 | PUT_BYTE(PACKBOOL( 186 | 0, telemetry.light_parking, 187 | telemetry.light_lblinker, telemetry.light_rblinker, 188 | telemetry.light_low_beam, telemetry.light_high_beam, 189 | telemetry.light_brake, telemetry.light_reverse)); 190 | 191 | // Warning lights 192 | PUT_BYTE(PACKBOOL( 193 | telemetry.parking_brake, telemetry.motor_brake, 194 | telemetry.brake_air_pressure_warning, telemetry.brake_air_pressure_emergency, 195 | telemetry.fuel_warning, telemetry.battery_voltage_warning, 196 | telemetry.oil_pressure_warning, telemetry.water_temperature_warning)); 197 | 198 | // Enabled flags 199 | PUT_BYTE(PACKBOOL( 200 | 0,0,0,0,0, 0, 201 | telemetry.electric_enabled, telemetry.engine_enabled)); 202 | 203 | 204 | // Build string for LCD display 205 | 206 | std::stringstream ss; 207 | 208 | ss.width(3); 209 | ss << abs(int(speed_mph)); 210 | ss << " MPH"; 211 | 212 | ss << " G "; 213 | 214 | if (telemetry.engine_gear > 0) 215 | { 216 | ss << "D"; 217 | ss.width(2); 218 | ss << telemetry.engine_gear; 219 | } 220 | else if (telemetry.engine_gear == 0) 221 | { 222 | ss << "N "; 223 | } 224 | else 225 | { 226 | ss << "R"; 227 | ss.width(2); 228 | ss << abs(telemetry.engine_gear); 229 | } 230 | 231 | ss << "\n"; 232 | 233 | ss.width(3); 234 | ss << abs(int(speed_kph)); 235 | ss << " KPH"; 236 | 237 | ss << " F "; 238 | 239 | ss.width(3); 240 | ss << int(fuel_ratio * 100.0f) << "%"; 241 | 242 | 243 | ss.width(3); 244 | ss << abs(int(speed_kph)); 245 | 246 | std::string display_str(ss.str()); 247 | 248 | // Write length of string 249 | PUT_BYTE(display_str.size()); 250 | // Followed by string 251 | display_str.copy((char*)&packet[idx], PACKET_MAX_SIZE - idx); 252 | idx += display_str.size(); 253 | 254 | 255 | serial_port.write(packet, idx); 256 | } 257 | 258 | 259 | 260 | 261 | 262 | 263 | SCSAPI_VOID telemetry_store_float(const scs_string_t /*name*/, const scs_u32_t /*index*/, const scs_value_t *const value, const scs_context_t context) 264 | { 265 | assert(value); 266 | assert(value->type == SCS_VALUE_TYPE_float); 267 | assert(context); 268 | *static_cast(context) = value->value_float.value; 269 | } 270 | 271 | SCSAPI_VOID telemetry_store_bool(const scs_string_t /*name*/, const scs_u32_t /*index*/, const scs_value_t *const value, const scs_context_t context) 272 | { 273 | assert(value); 274 | assert(value->type == SCS_VALUE_TYPE_bool); 275 | assert(context); 276 | *static_cast(context) = (value->value_bool.value != 0); 277 | } 278 | 279 | SCSAPI_VOID telemetry_store_s32(const scs_string_t /*name*/, const scs_u32_t /*index*/, const scs_value_t *const value, const scs_context_t context) 280 | { 281 | assert(value); 282 | assert(value->type == SCS_VALUE_TYPE_s32); 283 | assert(context); 284 | *static_cast(context) = value->value_s32.value; 285 | } 286 | 287 | SCSAPI_VOID telemetry_configuration(const scs_event_t /*event*/, const void *const event_info, const scs_context_t /*context*/) 288 | { 289 | const struct scs_telemetry_configuration_t *const info = static_cast(event_info); 290 | 291 | for (const scs_named_value_t *current = info->attributes; current->name; ++current) 292 | { 293 | 294 | #define GET_CONFIG(PROPERTY, TYPE) \ 295 | if (strcmp(SCS_TELEMETRY_CONFIG_ATTRIBUTE_ ## PROPERTY, current->name) == 0) \ 296 | { telemetry.PROPERTY = current->value.value_ ## TYPE.value; } 297 | 298 | GET_CONFIG(fuel_capacity, float); 299 | 300 | } 301 | } 302 | 303 | void get_cwd(std::string& str) 304 | { 305 | char buffer[256]; 306 | GetCurrentDirectory(256, buffer); 307 | str.assign(buffer); 308 | } 309 | 310 | SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params) 311 | { 312 | if (version != SCS_TELEMETRY_VERSION_1_00) { 313 | return SCS_RESULT_unsupported; 314 | } 315 | 316 | const scs_telemetry_init_params_v100_t *const version_params = static_cast(params); 317 | game_log = version_params->common.log; 318 | 319 | game_log(SCS_LOG_TYPE_message, "Plugin initialising"); 320 | 321 | std::string cwd; 322 | get_cwd(cwd); 323 | 324 | game_log(SCS_LOG_TYPE_message, (std::string("Plugin CWD: ") + cwd).c_str()); 325 | 326 | std::string option_filepath(cwd + "\\plugins\\dash_plugin.txt"); 327 | if (!option_file.read_file(option_filepath)) 328 | { 329 | game_log(SCS_LOG_TYPE_error, (std::string("Error reading settings file: ") + option_filepath).c_str()); 330 | return SCS_RESULT_generic_error; 331 | } 332 | 333 | const std::string com_port = option_file.get_option_string("comport", "COM3"); 334 | game_log(SCS_LOG_TYPE_message, (std::string("Using serial port: ") + com_port).c_str()); 335 | 336 | // Open COM port 337 | std::string errmsg; 338 | if (!serial_port.open(com_port, errmsg)) 339 | { 340 | game_log(SCS_LOG_TYPE_error, errmsg.c_str()); 341 | return SCS_RESULT_generic_error; 342 | } 343 | 344 | send_empty_packet(); 345 | 346 | // Register for in game events 347 | bool registered = 348 | (version_params->register_for_event( 349 | SCS_TELEMETRY_EVENT_frame_end, telemetry_frame_end, NULL) == SCS_RESULT_ok) && 350 | (version_params->register_for_event( 351 | SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, NULL) == SCS_RESULT_ok); 352 | 353 | // Register for truck channels 354 | 355 | #define REG_CHAN(CHANNEL, TYPE) \ 356 | registered &= (version_params->register_for_channel( \ 357 | SCS_TELEMETRY_TRUCK_CHANNEL_ ## CHANNEL, SCS_U32_NIL, SCS_VALUE_TYPE_ ## TYPE, \ 358 | SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_ ## TYPE, &telemetry.CHANNEL) == SCS_RESULT_ok) 359 | 360 | REG_CHAN(speed, float); 361 | REG_CHAN(engine_rpm, float); 362 | REG_CHAN(engine_gear, s32); 363 | REG_CHAN(parking_brake, bool); 364 | REG_CHAN(motor_brake, bool); 365 | REG_CHAN(brake_air_pressure, float); 366 | REG_CHAN(brake_air_pressure_warning, bool); 367 | REG_CHAN(brake_air_pressure_emergency, bool); 368 | REG_CHAN(brake_temperature, float); 369 | REG_CHAN(fuel, float); 370 | REG_CHAN(fuel_warning, bool); 371 | REG_CHAN(fuel_average_consumption, float); 372 | REG_CHAN(oil_pressure, float); 373 | REG_CHAN(oil_pressure_warning, bool); 374 | REG_CHAN(oil_temperature, float); 375 | REG_CHAN(water_temperature, float); 376 | REG_CHAN(water_temperature_warning, bool); 377 | REG_CHAN(battery_voltage, float); 378 | REG_CHAN(battery_voltage_warning, bool); 379 | REG_CHAN(electric_enabled, bool); 380 | REG_CHAN(engine_enabled, bool); 381 | REG_CHAN(light_lblinker, bool); 382 | REG_CHAN(light_rblinker, bool); 383 | REG_CHAN(light_parking, bool); 384 | REG_CHAN(light_low_beam, bool); 385 | REG_CHAN(light_high_beam, bool); 386 | REG_CHAN(light_brake, bool); 387 | REG_CHAN(light_reverse, bool); 388 | REG_CHAN(odometer, float); 389 | 390 | 391 | if (!registered) 392 | { 393 | game_log(SCS_LOG_TYPE_error, "Unable to register callbacks"); 394 | return SCS_RESULT_generic_error; 395 | } 396 | 397 | memset(&telemetry, 0, sizeof(telemetry)); 398 | 399 | return SCS_RESULT_ok; 400 | } 401 | 402 | SCSAPI_VOID scs_telemetry_shutdown(void) 403 | { 404 | send_empty_packet(); 405 | serial_port.close(); 406 | 407 | game_log(SCS_LOG_TYPE_message, "Plugin shutdown"); 408 | game_log = NULL; 409 | } 410 | 411 | BOOL APIENTRY DllMain(HMODULE /*module*/, DWORD reason_for_call, LPVOID /*reseved*/) 412 | { 413 | if (reason_for_call == DLL_PROCESS_DETACH) 414 | { 415 | serial_port.close(); 416 | } 417 | return TRUE; 418 | } 419 | 420 | --------------------------------------------------------------------------------