├── doc ├── S60.jpg ├── INT1.jpg ├── INT2.jpg ├── RTI-box1.jpg ├── RTI-box2.jpg ├── RTI-box3.jpg ├── RTI-now.jpg ├── SWM Pins.png ├── camera1.jpg ├── camera2.jpg ├── extender.jpg ├── rti-disp.png ├── Radio Pins.png ├── SWM Location.png ├── RTI Cable Extender Scheme.pdf ├── RTI Cable Extender Scheme.png ├── RTI Control Module Scheme.jpg └── RTI Control Module Scheme.pdf ├── Raspberry Pi ├── run.sh ├── splash1.h264 ├── splash2.h264 ├── openauto_equalizer.ini ├── key.py └── CONFIG.TXT ├── volvo-rti-retrofit ├── avr_util.cpp ├── custom_defs.h ├── avr_util.h ├── lin_frame.h ├── lin_frame.cpp └── volvo-rti-retrofit.ino ├── LICENSE ├── README.md └── aux-input └── aux-input.ino /doc/S60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/S60.jpg -------------------------------------------------------------------------------- /doc/INT1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/INT1.jpg -------------------------------------------------------------------------------- /doc/INT2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/INT2.jpg -------------------------------------------------------------------------------- /doc/RTI-box1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI-box1.jpg -------------------------------------------------------------------------------- /doc/RTI-box2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI-box2.jpg -------------------------------------------------------------------------------- /doc/RTI-box3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI-box3.jpg -------------------------------------------------------------------------------- /doc/RTI-now.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI-now.jpg -------------------------------------------------------------------------------- /doc/SWM Pins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/SWM Pins.png -------------------------------------------------------------------------------- /doc/camera1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/camera1.jpg -------------------------------------------------------------------------------- /doc/camera2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/camera2.jpg -------------------------------------------------------------------------------- /doc/extender.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/extender.jpg -------------------------------------------------------------------------------- /doc/rti-disp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/rti-disp.png -------------------------------------------------------------------------------- /doc/Radio Pins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/Radio Pins.png -------------------------------------------------------------------------------- /Raspberry Pi/run.sh: -------------------------------------------------------------------------------- 1 | sleep 5 2 | cd / 3 | cd /home/pi/Documents/ 4 | sudo nohup python3 key.py 5 | cd / 6 | -------------------------------------------------------------------------------- /doc/SWM Location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/SWM Location.png -------------------------------------------------------------------------------- /Raspberry Pi/splash1.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/Raspberry Pi/splash1.h264 -------------------------------------------------------------------------------- /Raspberry Pi/splash2.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/Raspberry Pi/splash2.h264 -------------------------------------------------------------------------------- /doc/RTI Cable Extender Scheme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI Cable Extender Scheme.pdf -------------------------------------------------------------------------------- /doc/RTI Cable Extender Scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI Cable Extender Scheme.png -------------------------------------------------------------------------------- /doc/RTI Control Module Scheme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI Control Module Scheme.jpg -------------------------------------------------------------------------------- /doc/RTI Control Module Scheme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speedxperts/volvo-rti-retrofit/HEAD/doc/RTI Control Module Scheme.pdf -------------------------------------------------------------------------------- /volvo-rti-retrofit/avr_util.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include "avr_util.h" 14 | 15 | namespace avr_util_private { 16 | 17 | // Using lookup to avoid individual bit shifting. This is faster 18 | // than (1 << n) or a switch/case statement. 19 | const byte kBitMaskArray[] = { 20 | H(0), 21 | H(1), 22 | H(2), 23 | H(3), 24 | H(4), 25 | H(5), 26 | H(6), 27 | H(7), 28 | }; 29 | 30 | } // namespace avr_util_private 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 samelyuk 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 | -------------------------------------------------------------------------------- /volvo-rti-retrofit/custom_defs.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #ifndef CUSTOM_DEFS_H 14 | #define CUSTOM_DEFS_H 15 | 16 | #include "avr_util.h" 17 | 18 | // Custom application specific parameters. 19 | // 20 | // Like all the other custom_* files, this file should be adapted to the specific application. 21 | // The example provided is for a Sport Mode button press injector for 981/Cayman. 22 | namespace custom_defs { 23 | 24 | // True for LIN checksum V2 (enahanced). False for LIN checksum version 1. 25 | const boolean kUseLinChecksumVersion2 = false; 26 | 27 | } // namepsace custom_defs 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /volvo-rti-retrofit/avr_util.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #ifndef AVR_UTIL_H 14 | #define AVR_UTIL_H 15 | 16 | #include 17 | 18 | // Get rid of the _t type suffix. 19 | typedef uint8_t uint8; 20 | typedef uint16_t uint16; 21 | typedef uint32_t uint32; 22 | 23 | typedef int8_t int8; 24 | typedef int16_t int16; 25 | typedef int32_t int32; 26 | 27 | // Bit index to bit mask. 28 | // AVR registers bit indices are defined in iom328p.h. 29 | #define H(x) (1 << (x)) 30 | #define L(x) (0 << (x)) 31 | 32 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 33 | 34 | // Private data. Do not use from other modules. 35 | namespace avr_util_private { 36 | extern const byte kBitMaskArray[]; 37 | } 38 | 39 | // Similar to (1 << bit_index) but more efficient for non consts. For 40 | // const masks use H(n). Undefined result if it_index not in [0, 7]. 41 | inline byte bitMask(byte bit_index) { 42 | return *(avr_util_private::kBitMaskArray + bit_index); 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /Raspberry Pi/openauto_equalizer.ini: -------------------------------------------------------------------------------- 1 | [Equalizer] 2 | Preset=5 3 | Preamp=0 4 | MinValue=-30 5 | MaxValue=70 6 | [Custom] 7 | Frequency1=2 8 | Frequency2=1 9 | Frequency3=1 10 | Frequency4=1 11 | Frequency5=0 12 | Frequency6=2 13 | Frequency7=0 14 | Frequency8=0 15 | Frequency9=1 16 | Frequency10=2 17 | Frequency11=2 18 | Frequency12=2 19 | Frequency13=1 20 | Frequency14=0 21 | Frequency15=2 22 | [Pop] 23 | Frequency1=5 24 | Frequency2=5 25 | Frequency3=4 26 | Frequency4=0 27 | Frequency5=0 28 | Frequency6=1 29 | Frequency7=1 30 | Frequency8=1 31 | Frequency9=1 32 | Frequency10=2 33 | Frequency11=2 34 | Frequency12=4 35 | Frequency13=3 36 | Frequency14=3 37 | Frequency15=2 38 | [Classic] 39 | Frequency1=-10 40 | Frequency2=-10 41 | Frequency3=-10 42 | Frequency4=-10 43 | Frequency5=-10 44 | Frequency6=-10 45 | Frequency7=-10 46 | Frequency8=-15 47 | Frequency9=-15 48 | Frequency10=-15 49 | Frequency11=-20 50 | Frequency12=-20 51 | Frequency13=-20 52 | Frequency14=-20 53 | Frequency15=-20 54 | [Jazz] 55 | Frequency1=6 56 | Frequency2=4 57 | Frequency3=-2 58 | Frequency4=-4 59 | Frequency5=-5 60 | Frequency6=-10 61 | Frequency7=-12 62 | Frequency8=-12 63 | Frequency9=-10 64 | Frequency10=-5 65 | Frequency11=-3 66 | Frequency12=-1 67 | Frequency13=-1 68 | Frequency14=2 69 | Frequency15=6 70 | [Flat] 71 | Frequency1=-10 72 | Frequency2=-10 73 | Frequency3=-10 74 | Frequency4=-10 75 | Frequency5=-10 76 | Frequency6=-10 77 | Frequency7=-10 78 | Frequency8=-10 79 | Frequency9=-10 80 | Frequency10=-10 81 | Frequency11=-10 82 | Frequency12=-10 83 | Frequency13=-10 84 | Frequency14=-10 85 | Frequency15=-10 86 | [Rock] 87 | Frequency1=4 88 | Frequency2=2 89 | Frequency3=1 90 | Frequency4=0 91 | Frequency5=0 92 | Frequency6=-1 93 | Frequency7=-1 94 | Frequency8=-2 95 | Frequency9=-2 96 | Frequency10=0 97 | Frequency11=0 98 | Frequency12=2 99 | Frequency13=3 100 | Frequency14=3 101 | Frequency15=3 102 | -------------------------------------------------------------------------------- /Raspberry Pi/key.py: -------------------------------------------------------------------------------- 1 | from pynput.keyboard import Key, Controller 2 | import os 3 | import serial 4 | from subprocess import run 5 | 6 | keyboard = Controller() 7 | 8 | def main(): 9 | ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) 10 | ser.reset_input_buffer() 11 | while True: 12 | if ser.in_waiting > 0: 13 | line = ser.readline().decode('utf-8').rstrip() 14 | print(line) 15 | if line=='EVENT_SHUTDOWN': 16 | os.system('shutdown -h now') 17 | if line=='UP': 18 | keyboard.press(Key.up) 19 | keyboard.release(Key.up) 20 | if line=='DOWN': 21 | keyboard.press(Key.down) 22 | keyboard.release(Key.down) 23 | if line=='LEFT': 24 | keyboard.press('1') 25 | keyboard.release('1') 26 | if line=='RIGHT': 27 | keyboard.press('2') 28 | keyboard.release('2') 29 | if line=='ENTER': 30 | keyboard.press(Key.enter) 31 | keyboard.release(Key.enter) 32 | if line=='ESC': 33 | keyboard.press(Key.esc) 34 | keyboard.release(Key.esc) 35 | if line=='HOME': 36 | keyboard.press('h') 37 | keyboard.release('h') 38 | if line=='NEXT': 39 | keyboard.press('n') 40 | keyboard.release('n') 41 | if line=='PREV': 42 | keyboard.press('v') 43 | keyboard.release('v') 44 | if line=='YES': 45 | keyboard.press('m') 46 | keyboard.release('m') 47 | if line=='NO': 48 | keyboard.press('b') 49 | keyboard.release('b') 50 | if line=='CALL': 51 | run('vcgencmd display_power 1', shell=True) 52 | if line=='DENY': 53 | run('vcgencmd display_power 0', shell=True) 54 | ser.close() 55 | 56 | if __name__ == '__main__': 57 | main() 58 | -------------------------------------------------------------------------------- /Raspberry Pi/CONFIG.TXT: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rpf.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # uncomment if you get no picture on HDMI for a default "safe" mode 6 | #hdmi_safe=1 7 | 8 | # uncomment this if your display has a black border of unused pixels visible 9 | # and your display can output without overscan 10 | disable_overscan=0 11 | avoid_warnings=1 12 | enable_tvout=1 13 | 14 | # uncomment the following to adjust overscan. Use positive numbers if console 15 | # goes off screen, and negative if there is too much border 16 | overscan_left=0 17 | overscan_right=0 18 | overscan_top=-35 19 | overscan_bottom=-20 20 | 21 | # uncomment to force a console size. By default it will be display's size minus 22 | # overscan. 23 | #framebuffer_width=1280 24 | #framebuffer_height=720 25 | 26 | # uncomment if hdmi display is not detected and composite is being output 27 | #hdmi_force_hotplug=1 28 | 29 | # uncomment to force a specific HDMI mode (this will force VGA) 30 | #hdmi_group=2 31 | #hdmi_mode=14 32 | #hdmi_ctv=800 480 60 6 0 0 0 33 | #hdmi_drive=1 34 | 35 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 36 | # DMT (computer monitor) modes 37 | #hdmi_drive=2 38 | 39 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 40 | 41 | #config_hdmi_boost=4 42 | 43 | # uncomment for composite PAL 44 | sdtv_mode=0 45 | sdtv_aspect=3 46 | 47 | #uncomment to overclock the arm. 700 MHz is the default. 48 | #arm_freq=800 49 | 50 | # Uncomment some or all of these to enable the optional hardware interfaces 51 | #dtparam=i2c_arm=on 52 | #dtparam=i2s=on 53 | #dtparam=spi=on 54 | 55 | # Uncomment this to enable the lirc-rpi module 56 | #dtoverlay=lirc-rpi 57 | 58 | # Additional overlays and parameters are documented /boot/overlays/README 59 | 60 | # Enable audio (loads snd_bcm2835) 61 | dtparam=audio=off 62 | dtoverlay=disable-bt 63 | disable_splash=1 64 | dtoverlay=vc4-fkms-v3d 65 | 66 | enable_uart=1 67 | 68 | [pi3] 69 | gpu_mem=256 70 | max_framebuffers=1 71 | [pi3+] 72 | gpu_mem=256 73 | max_framebuffers=1 74 | 75 | [pi4] 76 | max_framebuffers=2 77 | #hdmi_enable_4kp60=1 78 | 79 | -------------------------------------------------------------------------------- /volvo-rti-retrofit/lin_frame.h: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #ifndef LIN_FRAME_H 14 | #define LIN_FRAME_H 15 | 16 | #include "avr_util.h" 17 | 18 | // A buffer for a single frame. 19 | class LinFrame { 20 | public: 21 | LinFrame() { 22 | reset(); 23 | } 24 | 25 | // Number of bytes in a shotest frame. This is a frame with ID byte and no 26 | // slave response (and thus no data or checksum). 27 | static const uint8 kMinBytes = 1; 28 | 29 | // Number of bytes in the longest frame. One ID byte, 8 data bytes, one checksum byte. 30 | static const uint8 kMaxBytes = 1 + 8 + 1; 31 | 32 | // Compute the to checkum bits [P1,P0] of the lin id in bits [5:0] and return 33 | // [P1,P0][5:0] which is the wire representation of this id. 34 | static uint8 setLinIdChecksumBits(uint8 id); 35 | 36 | boolean isValid() const; 37 | 38 | // Compute LIN frame checksum. Assuming buffer has at least one byte. A valid 39 | // frame should contain one byte for id, 1-8 bytes for data, one byte for checksum. 40 | uint8 computeChecksum() const; 41 | 42 | inline void reset() { 43 | num_bytes_ = 0; 44 | } 45 | 46 | inline uint8 num_bytes() const { 47 | return num_bytes_; 48 | } 49 | 50 | // Get a the frame byte of given inde. 51 | // Byte index 0 is the frame id byte, followed by 1-8 data bytes 52 | // followed by 1 checksum byte. 53 | // 54 | // Caller should verify that index < num_bytes. 55 | inline uint8 get_byte(uint8 index) const { 56 | return bytes_[index]; 57 | } 58 | 59 | // Caller should check that num_bytes < kMaxBytes; 60 | inline void append_byte(uint8 value) { 61 | bytes_[num_bytes_++] = value; 62 | } 63 | 64 | inline void pop_byte() { 65 | num_bytes_--; 66 | } 67 | 68 | // TODO: make this stuff private without sacrifying performance. 69 | 70 | private: 71 | // Number of bytes in bytes_ buffer. At most kMaxBytes. 72 | uint8 num_bytes_; 73 | 74 | // Recieved frame bytes. Includes id, data and checksum. Does not 75 | // include the 0x55 sync byte. 76 | uint8 bytes_[kMaxBytes]; 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /volvo-rti-retrofit/lin_frame.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #include "lin_frame.h" 14 | 15 | #include "custom_defs.h" 16 | 17 | // Compute the checksum of the frame. For now using LIN checksum V2 only. 18 | uint8 LinFrame::computeChecksum() const { 19 | // LIN V2 checksum includes the ID byte, V1 does not. 20 | const uint8 startByteIndex = custom_defs::kUseLinChecksumVersion2 ? 0 : 1; 21 | const uint8* p = &bytes_[startByteIndex]; 22 | 23 | // Exclude the checksum byte at the end of the frame. 24 | uint8 numBytesToChecksum = num_bytes_ - startByteIndex - 1; 25 | 26 | // Sum bytes. We should not have 16 bit overflow here since the frame has a limited size. 27 | uint16 sum = 0; 28 | while (numBytesToChecksum-- > 0) { 29 | sum += *(p++); 30 | } 31 | 32 | // Keep adding the high and low bytes until no carry. 33 | for (;;) { 34 | const uint8 highByte = (uint8)(sum >> 8); 35 | if (!highByte) { 36 | break; 37 | } 38 | // NOTE: this can add additional carry. 39 | sum = (sum & 0xff) + highByte; 40 | } 41 | 42 | return (uint8)(~sum); 43 | } 44 | 45 | 46 | uint8 LinFrame::setLinIdChecksumBits(uint8 id) { 47 | // The algorithm is optimized for CPU time (avoiding individual shifts per id 48 | // bit). Using registers for the two checksum bits. P1 is computed in bit 7 of 49 | // p1_at_b7 and p0 is comptuted in bit 6 of p0_at_b6. 50 | uint8 p1_at_b7 = ~0; 51 | uint8 p0_at_b6 = 0; 52 | 53 | // P1: id5, P0: id4 54 | uint8 shifter = id << 2; 55 | p1_at_b7 ^= shifter; 56 | p0_at_b6 ^= shifter; 57 | 58 | // P1: id4, P0: id3 59 | shifter += shifter; 60 | p1_at_b7 ^= shifter; 61 | 62 | // P1: id3, P0: id2 63 | shifter += shifter; 64 | p1_at_b7 ^= shifter; 65 | p0_at_b6 ^= shifter; 66 | 67 | // P1: id2, P0: id1 68 | shifter += shifter; 69 | p0_at_b6 ^= shifter; 70 | 71 | // P1: id1, P0: id0 72 | shifter += shifter; 73 | p1_at_b7 ^= shifter; 74 | p0_at_b6 ^= shifter; 75 | 76 | return (p1_at_b7 & 0b10000000) | (p0_at_b6 & 0b01000000) | (id & 0b00111111); 77 | } 78 | 79 | boolean LinFrame::isValid() const { 80 | const uint8 n = num_bytes_; 81 | 82 | // Check frame size. 83 | // One ID byte with optional 1-8 data bytes and 1 checksum byte. 84 | // TODO: should we enforce only 1, 2, 4, or 8 data bytes? (total size 85 | // 1, 3, 4, 6, or 10) 86 | // 87 | // TODO: should we pass through frames with ID only (n == 1, no response from slave). 88 | if (n != 1 && (n < 3 || n > 10)) { 89 | return false; 90 | } 91 | 92 | // Check ID byte checksum bits. 93 | const uint8 id_byte = bytes_[0]; 94 | if (id_byte != setLinIdChecksumBits(id_byte)) { 95 | return false; 96 | } 97 | 98 | // If not an ID only frame, check also the overall checksum. 99 | if (n > 1) { 100 | if (bytes_[n - 1] != computeChecksum()) { 101 | return false; 102 | } 103 | } 104 | // TODO: check protected id. 105 | return true; 106 | } 107 | -------------------------------------------------------------------------------- /volvo-rti-retrofit/volvo-rti-retrofit.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "lin_frame.h" 4 | 5 | #define DEBUG true 6 | 7 | //PINOUT 8 | #define RX_PIN 6 9 | #define TX_PIN 7 10 | #define CS_PIN 8 11 | 12 | #define PHOTO_PIN A0 // Photoresistor pin 13 | 14 | #define RTI_RX_PIN 9 15 | #define RTI_TX_PIN 10 16 | //PINOUT(END) 17 | 18 | #define SYN_FIELD 0x55 19 | #define SWM_ID 0xC1 20 | 21 | #define ON_CLICK_DURATION 400 22 | #define OFF_CLICK_DURATION 600 23 | #define CLICK_TIMEOUT 400 24 | 25 | #define RTI_INTERVAL 100 26 | 27 | short rtiStep; 28 | 29 | SoftwareSerial LINBusSerial(RX_PIN, TX_PIN); 30 | SoftwareSerial RTISerial(RTI_RX_PIN, RTI_TX_PIN); 31 | 32 | // Volvo S60 2008 SWM key codes 33 | // 34 | // Enter: C1 3F 20 A0 35 | // Back: C1 3F 10 B0 36 | // 37 | // Up: C1 3F 8 B8 38 | // Down: C1 3F 4 BC 39 | // Left: C1 3F 2 BE 40 | // Right: C1 3F 1 BF 41 | // 42 | // Next: C1 3D 0 C2 43 | // Prev: C1 3E 0 C1 44 | // 45 | // Yes: C1 2F 0 D0 46 | // No: C1 1F 0 E0 47 | 48 | 49 | //BUTTONS 50 | #define JOYSTICK_UP 0x8 51 | #define JOYSTICK_DOWN 0x4 52 | #define JOYSTICK_LEFT 0x2 53 | #define JOYSTICK_RIGHT 0x1 54 | #define BUTTON_BACK 0x10 55 | #define BUTTON_ENTER 0x20 56 | #define BUTTON_HOME 0x30 57 | 58 | #define BUTTON_NEXT 0x3D 59 | #define BUTTON_PREV 0x3E 60 | #define BUTTON_YES 0x2F 61 | #define BUTTON_NO 0x1F 62 | #define BUTTON_ON 0x2D //Yes+Next 63 | #define BUTTON_OFF 0x1D //No+Next 64 | //BUTTONS(END) 65 | 66 | unsigned long currentMillis, lastRtiWrite, buttonDownAt, lastButtonAt, lastJoysticButtonAt; 67 | 68 | bool on = true; 69 | 70 | const char brightness_levels[] = {0x20, 0x61, 0x62, 0x23, 0x64, 0x25, 0x26, 0x67, 0x68, 0x29, 0x2A, 0x2C, 0x6B, 0x6D, 0x6E, 0x2F}; 71 | 72 | char current_brightness_level = 6; 73 | 74 | byte currentButton, currentJoystickButton; 75 | byte b, n; 76 | 77 | LinFrame frame; 78 | 79 | void setup() { 80 | 81 | // setup_r(); 82 | pinMode(LED_BUILTIN, OUTPUT); 83 | 84 | Serial.begin(9600); 85 | Serial.println("Starting"); 86 | 87 | RTISerial.begin(2400); 88 | LINBusSerial.begin(9600); 89 | 90 | pinMode(CS_PIN, OUTPUT); 91 | digitalWrite(CS_PIN, HIGH); 92 | 93 | frame = LinFrame(); 94 | } 95 | 96 | void loop() { 97 | 98 | currentMillis = millis(); 99 | timeout_button(); 100 | rti(); 101 | // loop_r(); 102 | 103 | if (LINBusSerial.available()) { 104 | b = LINBusSerial.read(); 105 | n = frame.num_bytes(); 106 | 107 | if (b == SYN_FIELD && n > 2 && frame.get_byte(n - 1) == 0) { 108 | digitalWrite(LED_BUILTIN, HIGH); 109 | frame.pop_byte(); 110 | handle_frame(); 111 | frame.reset(); 112 | digitalWrite(LED_BUILTIN, LOW); 113 | } else if (n == LinFrame::kMaxBytes) { 114 | frame.reset(); 115 | } else { 116 | frame.append_byte(b); 117 | } 118 | } 119 | 120 | 121 | } 122 | 123 | void read_linbus() { 124 | b = LINBusSerial.read(); 125 | n = frame.num_bytes(); 126 | 127 | if (b == SYN_FIELD && n > 2 && frame.get_byte(n - 1) == 0) { 128 | digitalWrite(LED_BUILTIN, HIGH); 129 | frame.pop_byte(); 130 | handle_frame(); 131 | frame.reset(); 132 | digitalWrite(LED_BUILTIN, LOW); 133 | } else if (n == LinFrame::kMaxBytes) { 134 | frame.reset(); 135 | } else { 136 | frame.append_byte(b); 137 | } 138 | } 139 | 140 | void handle_frame() { 141 | if (frame.get_byte(0) != SWM_ID) 142 | return; 143 | 144 | // skip zero values 20 0 0 0 0 FF 145 | if (frame.get_byte(5) == 0xFF) 146 | return; 147 | 148 | if (!frame.isValid()) 149 | return; 150 | 151 | // dump_frame(); 152 | handle_buttons(); 153 | handle_joystick(); 154 | } 155 | 156 | void handle_joystick() { 157 | 158 | byte button = frame.get_byte(2); 159 | 160 | timeout_joystic_button(); 161 | 162 | if (button != currentJoystickButton) { 163 | currentJoystickButton = button; 164 | 165 | click_joystick(button); 166 | } 167 | return; 168 | } 169 | 170 | void click_joystick(byte button) { 171 | switch (button) { 172 | case JOYSTICK_UP: 173 | debug("UP"); 174 | break; 175 | case JOYSTICK_DOWN: 176 | debug("DOWN"); 177 | break; 178 | case JOYSTICK_LEFT: 179 | debug("LEFT"); 180 | break; 181 | case JOYSTICK_RIGHT: 182 | debug("RIGHT"); 183 | break; 184 | case BUTTON_ENTER: 185 | debug("ENTER"); 186 | break; 187 | case BUTTON_BACK: 188 | debug("ESC"); 189 | break; 190 | case BUTTON_HOME: 191 | debug("HOME"); 192 | if (on == true){ 193 | on = false; 194 | } else on = true; 195 | break; 196 | } 197 | 198 | lastJoysticButtonAt = currentMillis; 199 | } 200 | 201 | void timeout_joystic_button() { 202 | if (!currentJoystickButton) 203 | return; 204 | 205 | if (since(lastJoysticButtonAt) > CLICK_TIMEOUT) 206 | currentJoystickButton = 0; 207 | } 208 | 209 | 210 | void handle_buttons() { 211 | byte button = frame.get_byte(1); 212 | 213 | if (!button) 214 | return; 215 | 216 | if (button != currentButton) { 217 | click_button(button); 218 | 219 | currentButton = button; 220 | buttonDownAt = currentMillis; 221 | } 222 | lastButtonAt = currentMillis; 223 | } 224 | 225 | void click_button(byte button) { 226 | 227 | switch (button) { 228 | case BUTTON_PREV: 229 | debug("PREV"); 230 | break; 231 | case BUTTON_NEXT: 232 | debug("NEXT"); 233 | break; 234 | case BUTTON_YES: 235 | debug("YES"); 236 | break; 237 | case BUTTON_NO: 238 | debug("NO"); 239 | break; 240 | case BUTTON_ON: 241 | // debug("CALL"); 242 | current_brightness_level = 15; 243 | break; 244 | case BUTTON_OFF: 245 | // debug("DENY"); 246 | current_brightness_level = 1; 247 | break; 248 | } 249 | } 250 | 251 | 252 | void timeout_button() { 253 | if (!currentButton) 254 | return; 255 | 256 | if (since(lastButtonAt) > CLICK_TIMEOUT) 257 | release_button(currentButton, since(buttonDownAt)); 258 | } 259 | 260 | void release_button(byte button, unsigned long clickDuration) { 261 | switch (button) { 262 | case BUTTON_YES: 263 | if (!on && clickDuration > ON_CLICK_DURATION) { 264 | debug("TURN ON"); 265 | // on = true; 266 | // turn_on(); 267 | } 268 | break; 269 | 270 | case BUTTON_NO: 271 | if (clickDuration > OFF_CLICK_DURATION) { 272 | debug("TURN OFF"); 273 | // on = false; 274 | // turn_off(); 275 | } 276 | break; 277 | } 278 | 279 | currentButton = 0; 280 | } 281 | 282 | void rti() { 283 | if (since(lastRtiWrite) < RTI_INTERVAL) return; 284 | 285 | // Read the photoresistor value 286 | int lightValue = analogRead(PHOTO_PIN); 287 | 288 | // Map the light value to the brightness level 289 | if (lightValue < 8) { 290 | current_brightness_level = 1; 291 | } else if (lightValue > 150) { 292 | current_brightness_level = 15; 293 | } else { 294 | current_brightness_level = map(lightValue, 8, 150, 2, 14); 295 | } 296 | 297 | // if (DEBUG) { 298 | // debug("Current Brightness Level: " + String((int)current_brightness_level) + "; Current Light value: " + String(lightValue)); 299 | // } 300 | 301 | 302 | switch (rtiStep) { 303 | case 0: // mode 304 | rti_print(on ? 0x4c : 0x46); 305 | // {0x40, 0x45, 0x4C, 0x46}; // RTI_RGB, RTI_PAL, RTI_NTSC, RTI_OFF 306 | // debug(on ? "ON" : "OFF"); 307 | rtiStep++; 308 | break; 309 | 310 | case 1: // brightness 311 | rti_print(brightness_levels[current_brightness_level]); 312 | rtiStep++; 313 | break; 314 | 315 | case 2: // sync 316 | rti_print(0x83); 317 | rtiStep = 0; 318 | break; 319 | } 320 | 321 | lastRtiWrite = currentMillis; 322 | } 323 | 324 | void rti_print(char byte) { 325 | RTISerial.print(byte); 326 | } 327 | 328 | void dump_frame() { 329 | for (int i = 0; i < frame.num_bytes(); i++) { 330 | Serial.print(frame.get_byte(i), HEX); 331 | Serial.print(" "); 332 | } 333 | Serial.println(); 334 | } 335 | 336 | void debug(String message) { 337 | if (DEBUG) 338 | Serial.println(message); 339 | } 340 | 341 | long since(long timestamp) { 342 | return currentMillis - timestamp; 343 | } 344 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Volvo RTI Retrofit 2 | Volvo P2 RTI retrofit with Android Auto, Carplay, Handsfree etc. 3 | 4 | # Version r1.2 5 | 6 | Welcome to the OpenAutoPro project tailored for the Volvo P2! This project enhances your driving experience by integrating a Raspberry Pi and Arduino into your vehicle, replacing the Control Module from the RTI system. Utilizing the stock RTI display and controls ensures seamless integration. 7 | 8 | RTI Retrofit - Android Auto 9 | ![RTI Retrofit - Android Auto](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/INT2.jpg) 10 | 11 | # Special Thanks 12 | 13 | I would like to extend my sincere thanks to the following contributors who have greatly contributed to the development and success of this project: 14 | - **Karl Hagström**: His comprehensive guide on adding AUX functionality to the Volvo HU-XXXX radio has been invaluable. Check out the guide [here](https://gizmosnack.blogspot.com/2015/11/aux-in-volvo-hu-xxxx-radio.html). 15 | - **klalle**: For updated AUX script and contributions. Check out their GitHub gist [here](https://gist.github.com/klalle/1ae1bfec5e2506918a3f89492180565e). 16 | - **laurynas**: For developing the Volvo LINbus reader program, which played a crucial role in integrating SWM buttons into the OpenAutoPro system. Find the program [here](https://github.com/laurynas/volvo_linbus). 17 | - **Luuk**: Their resource on enabling Android Auto on Volvo RTI systems has been immensely helpful. More details can be found [here](https://luuk.cc/p/vD2f/Android_Auto_on_Volvo_RTI). 18 | - **BlueWaveStudio Team** For developing OpenAuto Pro. This Project is unfortunately closed! 19 | - **KreAch3R**: For making Android Auto Wireless available again! More details: [here](https://github.com/KreAch3R/aa-proxy-oap). 20 | 21 | Your contributions have significantly enriched this project, and I deeply appreciate your efforts. Thank you! 22 | 23 | ## What is now working: 24 | - OpenAuto Pro with Bluetooth and internal audio player, hands-free calling, 15 band equalizer, etc. 25 | - Android Auto Wireless 26 | - Original display via serial video 27 | - Opening and closing the display using buttons Enter+Back. 28 | - The buttons on the steering wheel, including RTI buttons. 29 | - Rear camera 30 | - Automatic brightness control (That needs to be updated) 31 | 32 | ## Future works: 33 | - Add OBD info 34 | - Add a small battery to maintain power during engine startup 35 | - A better case for all the components 36 | - More stable and open source system instead of OpenAuto Pro 37 | 38 | # Table of Contents 39 | 1. [Connection Overview](#connection-overview) 40 | - [Necessary Components](#necessary-components) 41 | - [Cable Management](#cable-management) 42 | - [Display Cable Connection](#display-cable-connection) 43 | - [SWM Button Configuration - LIN bus](#swm-button-configuration---lin-bus) 44 | - [AUX Integration](#aux-integration) 45 | - [RTI Control Module Setup](#rti-control-module-setup) 46 | - [RTI Control Module Scheme](#rti-control-module-scheme) 47 | - [RTI Extender Cable Scheme](#rti-extender-cable-scheme) 48 | - [Rear Camera](#rear-camera) 49 | - [Automatic Brightness Control](#automatic-brightness-control) 50 | 2. [Programming Details](#programming-details) 51 | - [Raspberry Pi: OpenAutoPro installation, setup, Arduino connectivity, and configuring the splash screen](#raspberry-pi-openautopro-installation-setup-arduino-connectivity-and-configuring-the-splash-screen) 52 | - [Enabling Android Auto](#enabling-android-auto) 53 | 54 | ## Connection Overview 55 | 56 | ### Necessary Components 57 | - Raspberry Pi 4, 2 GB RAM: SD Card 64GB 58 | - USB Sound Card [Vention](https://www.alza.sk/vention-usb-external-sound-card-0-15m-gray-metal-type-omtp-ctia-d6093937.htm) 59 | - Bluetooth [C-TECH BTD-02](https://www.alza.sk/c-tech-btd-02-d7866536.htm) 60 | - Arduino Nano (Original ATmega328) - Clone doesn’t read SWM buttons 61 | - Arduino Nano (China clone) - For AUX 62 | - MCP2004 Chip [TME](https://www.tme.eu/sk/details/mcp2004a-e_p/integrovane-obvody-interface-ostatne/microchip-technology/) 63 | - Step-Down Converter 12V-5V, 5A 64 | - Ground Loop Isolator - From [Aliexpress](https://shorturl.at/lSVW0) 65 | - Various cables, connectors, etc. 66 | - Rear Camera for P2 [Aliexpress](https://www.aliexpress.com/item/1005006792687999.html?spm=a2g0o.order_list.order_list_main.5.a2201802Ed3mHx) 67 | - Photoresistor 68 | - Resistor R1 10k 69 | 70 | RTI Retrofit - OpenAutoPro 71 | ![RTI Retrofit - OpenAutoPro](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/INT1.jpg) 72 | 73 | ### Cable Management 74 | Efficient cable management is essential for organizing the components of the OpenAutoPro project within the Volvo S60 D5. Let's discuss how we've optimized cable routing for a tidy and functional setup. 75 | Utilizing Original Cables 76 | - To maintain a clean and integrated installation, I've repurposed existing cables from the front of the car to the trunk. 77 | - Two cables run from the RTI display and radio to the RTI control module. While one cable remains connected between the radio and the control module, we'll utilize the other 10-pin cable from the RTI display to the control module as an extension cord. 78 | Power Source Selection 79 | - To ensure seamless power management, we've tapped into the backside of the 12V socket in the front. We will use 2 pins for Power and ground in RTI cable. 80 | - The 12V socket offers the advantage of cutting off power entirely when the ignition is switched off, reducing the risk of power drain. 81 | - In future iterations, I plan to incorporate a small battery to maintain power during engine startup, further enhancing system reliability. 82 | 83 | ![RTI Controll module](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI-box1.jpg) 84 | ![RTI Controll module Disassembley](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI-box2.jpg) 85 | ![RTI Controll module Disassembled](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI-box3.jpg) 86 | 87 | ### Display Cable Connection: 88 | Incorporating the RTI (Road and Traffic Information) display into the OpenAutoPro system requires a meticulous approach to cable connection. Let's break down the cable setup for seamless integration. 89 | Cable Components: 90 | - Video Cable: This cable carries the video signal from the source to the RTI display, facilitating visual output. 91 | - Ground Cable: Ensures proper grounding for stable operation and electrical safety. 92 | - Display Serial Cable: Facilitates communication between the display and the system, enabling control and data exchange. 93 | 94 | ![RTI Display Pinout](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/rti-disp.png) 95 | 96 | ### SWM Button Configuration - LIN bus 97 | The SWM buttons from the steering wheel, along with the RTI buttons, will be read through the LIN bus. We will utilize the original Arduino Nano with MCP2025 and the "laurynas" Volvo LINbus reader program (https://github.com/laurynas/volvo_linbus). Simply pressing the required button is sufficient to view the LIN bus key code from our buttons, without the need to turn on the ignition. The key code for the Volvo S60 2008 appears as follows: Enter button: “ C1 3F 20 A0 ” 98 | 99 | The cables from the SWM/SAS are located up to the steering wheel, under the cover. See blue arrow. 100 | ![SWM Location](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/SWM%20Location.png) 101 | ![SWM Pinout](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/SWM%20Pins.png) 102 | 103 | ### AUX Integration 104 | While the Volvo S60 from 2008 typically comes equipped with an AUX input, my model had its radio upgraded to the HU-850, an older version lacking this feature. Following Karl Hagström's guide (https://gizmosnack.blogspot.com/2015/11/aux-in-volvo-hu-xxxx-radio.html), I created a CD changer emulator using an Arduino Nano clone. 105 | 106 | ![Radio Pinout](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/Radio%20Pins.png) 107 | 108 | ### RTI Control Module Setup 109 | I have removed almost everything from the RTI Control Module box. I only left three connectors: an 8-pin DIN socket, a 10-pin connector from the display and 5 pin Blue Connector. I have placed all the computing components in this box: Raspberry Pi with all its components, a step-down converter, and two Arduino microcontrollers on a PCB. Here is Temporary-Permanent set-up: 110 | ![RTI New Setup (Will be updated)](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI-now.jpg) 111 | 112 | ### RTI Control Module Scheme 113 | ![RTI Control Module](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI%20Control%20Module%20Scheme.jpg) 114 | 115 | ### RTI Extender Cable Scheme 116 | ![RTI Cable Extender](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/RTI%20Cable%20Extender%20Scheme.png) 117 | ![RTI Extender Photo](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/extender.jpg) 118 | 119 | ### Rear Camera 120 | I found a rear camera specifically for the Volvo P2, and its installation is straightforward. First, connect the camera power to a step-down converter from the 12V side. Next, connect the camera's video output to 5V Relay. Which will switch from RPi to Camera, when the reverse light is on. See Control module scheme. 121 | ![RTI Rear Camera](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/camera1.jpg) 122 | ![RTI Rear Camera P2](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/camera2.jpg) 123 | 124 | ### Automatic Brightness Control 125 | Initially, the brightness was configurable but required manual adjustment. Due to the absence of buttons, I decided to automate it. I installed a photoresistor under the rear glass and connected it to the Arduino Nano via the blue connector (refer to the schematic). 126 | 127 | ## Programming Details 128 | 129 | ### Raspberry Pi: OpenAutoPro installation, setup, Arduino connectivity, and configuring the splash screen 130 | 131 | This guide outlines the steps to configure your Raspberry Pi, including exchanging the Raspberry Pi configuration file, setting up autostart for a button reader, setting the Volvo splash screen, and configuring the equalizer. 132 | 133 | #### 1. Exchanging the Raspberry Pi Config File 134 | 135 | To make necessary system adjustments, you need to edit the Raspberry Pi boot configuration file. 136 | 137 | 1. Open the Raspberry Pi configuration file using the following command: 138 | 139 | ```bash 140 | sudo nano /boot/config.txt 141 | ``` 142 | 143 | 2. Modify or add any required settings based on your project’s specifications. 144 | 145 | 3. Save and exit the editor by pressing `Ctrl + X`, then `Y`, and `Enter`. 146 | 147 | #### 2. Setting Up Autostart for Button Reader 148 | 149 | To ensure that the `run.sh` script (which reads button inputs) runs automatically on startup, follow these steps: 150 | 151 | 1. Move the `run.sh` script to the `/home/pi` directory: 152 | 153 | ```bash 154 | sudo mv /home/pi/run.sh /home/pi 155 | ``` 156 | 157 | 2. **Move the `key.py` file to the `/home/pi/Documents/` directory:** 158 | 159 | ```bash 160 | sudo mv /home/pi/key.py /home/pi/Documents/ 161 | ``` 162 | 163 | 3. Edit the autostart file to add the script: 164 | 165 | ```bash 166 | sudo nano /etc/xdg/lxsession/LXDE-pi/autostart 167 | ``` 168 | 169 | 4. Append the following line to the end of the file: 170 | 171 | ```bash 172 | @/home/pi/run.sh 173 | ``` 174 | 175 | 5. Save and exit the editor by pressing `Ctrl + X`, then `Y`, and `Enter`. 176 | 177 | 6. Make the `run.sh` script executable: 178 | 179 | ```bash 180 | sudo chmod +x /home/pi/run.sh 181 | ``` 182 | 183 | 7. Install the required Python package `pynput`: 184 | 185 | ```bash 186 | sudo pip3 install pynput 187 | ``` 188 | 189 | #### 3. Setting the Volvo Splash Screen 190 | 191 | To set up the Volvo splash screen on your Raspberry Pi: 192 | 193 | 1. Move the splash screen files to the OpenAuto Pro directory: 194 | 195 | ```bash 196 | sudo mv /home/pi/splash1.h264 /usr/share/openautopro 197 | sudo mv /home/pi/splash2.h264 /usr/share/openautopro 198 | ``` 199 | 200 | #### 4. Exchanging the Equalizer Configuration 201 | 202 | To set up the equalizer configuration: 203 | 204 | 1. Move the `openauto_equalizer.ini` equalizer settings to: '/home/pi/.openautopro/config/' 205 | 206 | ### Enabling Android Auto 207 | After the latest Android Auto update (v12.6), the built-in OpenAuto Pro version stopped working. Thanks to KreAch3R, it’s now possible to reactivate it. [Follow his guide here to make it work again.](https://github.com/KreAch3R/aa-proxy-oap) 208 | 209 | However, I’ve noticed some issues: the "Next/Previous Track" and "Play/Pause" buttons no longer function, and starting Android Auto from the OpenAuto Pro menu is no longer possible (it only works on system startup). These issues should be addressed in a future update. 210 | 211 | --- 212 | 213 | With these steps completed, your Raspberry Pi should be properly configured for your project, with the required scripts and configurations in place. 214 | 215 | ![Image Description](https://github.com/speedxperts/volvo-rti-retrofit/blob/main/doc/S60.jpg) 216 | -------------------------------------------------------------------------------- /aux-input/aux-input.ino: -------------------------------------------------------------------------------- 1 | /* Melbus CDCHGR Emulator 2 | * Program that emulates the MELBUS communication from a CD-changer (CD-CHGR) in a Volvo V70 (HU-xxxx) to enable AUX-input through the 8-pin DIN-contact. 3 | * This setup is using an Arduino Nano 5v clone 4 | * 5 | * The HU enables the CD-CHGR in its source-menue after a successful initialization procedure is accomplished. 6 | * The HU will remove the CD-CHGR everytime the car starts if it wont get an response from CD-CHGR (second init-procedure). 7 | * 8 | * Karl Hagström 2015-11-04 9 | * mod by S. Zeller 2016-03-14 10 | * 11 | * This project went realy smooth thanks to these sources: 12 | * http://volvo.wot.lv/wiki/doku.php?id=melbus 13 | * https://github.com/festlv/screen-control/blob/master/Screen_control/melbus.cpp 14 | * http://forums.swedespeed.com/showthread.php?50450-VW-Phatbox-to-Volvo-Transplant-(How-To)&highlight=phatbox 15 | * 16 | * pulse train width=120us (15us per clock cycle), high phase between two pulse trains is 540us-600us 17 | */ 18 | 19 | //START 20 | #define SERDBG 1 21 | const uint8_t MELBUS_CLOCKBIT_INT = 0; //interrupt numer (INT1) on DDR3 22 | const uint8_t MELBUS_CLOCKBIT = 3; //Pin D3 - CLK 23 | const uint8_t MELBUS_DATA = 4; //Pin D4 - Data 24 | const uint8_t MELBUS_BUSY = 5; //Pin D5 - Busy= 25 | 26 | volatile uint8_t melbus_ReceivedByte = 0; 27 | volatile uint8_t melbus_CharBytes = 0; 28 | volatile uint8_t melbus_OutByte = 0xFF; 29 | volatile uint8_t melbus_LastReadByte[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 30 | volatile uint8_t melbus_SendBuffer[9] = {0x00,0x02,0x00,0x01,0x80,0x01,0xC7,0x0A,0x02}; 31 | volatile uint8_t melbus_SendCnt=0; 32 | volatile uint8_t melbus_DiscBuffer[6] = {0x00,0xFC,0xFF,0x4A,0xFC,0xFF}; 33 | volatile uint8_t melbus_DiscCnt=0; 34 | volatile uint8_t melbus_Bitposition = 0x80; 35 | 36 | volatile bool InitialSequence_ext = false; 37 | volatile bool ByteIsRead = false; 38 | volatile bool sending_byte = false; 39 | volatile bool melbus_MasterRequested = false; 40 | volatile bool melbus_MasterRequestAccepted = false; 41 | 42 | volatile bool testbool = false; 43 | volatile bool AllowInterruptRead = true; 44 | volatile int incomingByte = 0; // for incoming serial data 45 | //END 46 | 47 | 48 | //Startup seequence 49 | 50 | 51 | //Notify HU that we want to trigger the first initiate procedure to add a new device (CD-CHGR) by pulling BUSY line low for 1s 52 | void melbus_Init_CDCHRG() { 53 | //Disabel interrupt on INT1 quicker then: detachInterrupt(MELBUS_CLOCKBIT_INT); 54 | EIMSK &= ~(1< time to read datapin 74 | void MELBUS_CLOCK_INTERRUPT() { 75 | 76 | //Read status of Datapin and set status of current bit in recv_byte 77 | 78 | if(melbus_OutByte & melbus_Bitposition){ 79 | DDRD &= (~(1<0;i--){ 102 | melbus_LastReadByte[i] = melbus_LastReadByte[i-1]; 103 | } 104 | 105 | if (melbus_OutByte != 0xFF) { 106 | melbus_LastReadByte[0] = melbus_OutByte; 107 | melbus_OutByte = 0xFF; 108 | } else { 109 | 110 | //Insert the newly read byte into first position of array 111 | melbus_LastReadByte[0] = melbus_ReceivedByte; 112 | } 113 | //set bool to true to evaluate the bytes in main loop 114 | ByteIsRead = true; 115 | 116 | 117 | //Reset bitcount to first bit in byte 118 | melbus_Bitposition=0x80; 119 | if(melbus_LastReadByte[2] == 0x07 && (melbus_LastReadByte[1] == 0x1A || melbus_LastReadByte[1] == 0x4A) && melbus_LastReadByte[0] == 0xEE) 120 | { 121 | InitialSequence_ext = true; 122 | } 123 | else if(melbus_LastReadByte[2] == 0x0 && (melbus_LastReadByte[1] == 0x1C || melbus_LastReadByte[1] == 0x4C) && melbus_LastReadByte[0] == 0xED) 124 | { 125 | InitialSequence_ext = true; 126 | } 127 | else if((melbus_LastReadByte[0] == 0xE8 || melbus_LastReadByte[0] == 0xE9) && InitialSequence_ext == true){ 128 | InitialSequence_ext = false; 129 | 130 | //Returning the expected byte to the HU, to confirm that the CD-CHGR is present (0xEE)! see "ID Response"-table here http://volvo.wot.lv/wiki/doku.php?id=melbus 131 | melbus_OutByte = 0xEE; 132 | } 133 | else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x1E || melbus_LastReadByte[1] == 0x4E) && melbus_LastReadByte[0] == 0xEF) 134 | { 135 | // CartInfo 136 | melbus_DiscCnt=6; 137 | } 138 | else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x22) 139 | { 140 | // Powerdown 141 | melbus_OutByte = 0x00; // respond to powerdown; 142 | melbus_SendBuffer[1]=0x02; // STOP 143 | melbus_SendBuffer[8]=0x02; // STOP 144 | } 145 | else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x52) 146 | { 147 | // RND 148 | } 149 | else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x29) 150 | { 151 | // FF 152 | } 153 | else if((melbus_LastReadByte[2] == 0xE8 || melbus_LastReadByte[2] == 0xE9) && (melbus_LastReadByte[1] == 0x19 || melbus_LastReadByte[1] == 0x49) && melbus_LastReadByte[0] == 0x2F) 154 | { 155 | // FR 156 | melbus_OutByte = 0x00; // respond to start; 157 | melbus_SendBuffer[1]=0x08; // START 158 | melbus_SendBuffer[8]=0x08; // START 159 | } 160 | else if((melbus_LastReadByte[3] == 0xE8 || melbus_LastReadByte[3] == 0xE9) && (melbus_LastReadByte[2] == 0x1A || melbus_LastReadByte[2] == 0x4A) && melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x01) 161 | { 162 | // D- 163 | melbus_SendBuffer[3]--; 164 | melbus_SendBuffer[5]=0x01; 165 | } 166 | else if((melbus_LastReadByte[3] == 0xE8 || melbus_LastReadByte[3] == 0xE9) && (melbus_LastReadByte[2] == 0x1A || melbus_LastReadByte[2] == 0x4A) && melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x41) 167 | { 168 | // D+ 169 | melbus_SendBuffer[3]++; 170 | melbus_SendBuffer[5]=0x01; 171 | } 172 | else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0x2D && melbus_LastReadByte[1] == 0x00 && melbus_LastReadByte[0] == 0x01) 173 | { 174 | // T- 175 | melbus_SendBuffer[5]--; 176 | } 177 | else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0x2D && melbus_LastReadByte[1] == 0x40 && melbus_LastReadByte[0] == 0x01) 178 | { 179 | // T+ 180 | melbus_SendBuffer[5]++; 181 | } 182 | else if((melbus_LastReadByte[4] == 0xE8 || melbus_LastReadByte[4] == 0xE9) && (melbus_LastReadByte[3] == 0x1B || melbus_LastReadByte[3] == 0x4B) && melbus_LastReadByte[2] == 0xE0 && melbus_LastReadByte[1] == 0x01 && melbus_LastReadByte[0] == 0x08 ){ 183 | // Playinfo 184 | melbus_SendCnt=9; 185 | } 186 | if (melbus_SendCnt) { 187 | melbus_OutByte=melbus_SendBuffer[9-melbus_SendCnt]; 188 | melbus_SendCnt--; 189 | } else if (melbus_DiscCnt) { 190 | melbus_OutByte=melbus_DiscBuffer[6-melbus_DiscCnt]; 191 | melbus_DiscCnt--; 192 | } 193 | } else { 194 | //set bitnumber to address of next bit in byte 195 | melbus_Bitposition>>=1; 196 | } 197 | EIFR |= (1 << INTF1); 198 | } 199 | 200 | void setup() { 201 | //Data is deafult input high 202 | pinMode(MELBUS_DATA, INPUT_PULLUP); 203 | 204 | //Activate interrupt on clock pin (INT1, D3) 205 | attachInterrupt(MELBUS_CLOCKBIT_INT, MELBUS_CLOCK_INTERRUPT, FALLING); 206 | //Set Clockpin-interrupt to input 207 | pinMode(MELBUS_CLOCKBIT, INPUT_PULLUP); 208 | #ifdef SERDBG 209 | //Initiate serial communication to debug via serial-usb (arduino) 210 | // Serial.begin(230400); 211 | // Serial.println("Initiating contact with Melbus:"); 212 | #endif 213 | //Call function that tells HU that we want to register a new device 214 | melbus_Init_CDCHRG(); 215 | } 216 | 217 | //Main loop 218 | void loop() { 219 | //Waiting for the clock interrupt to trigger 8 times to read one byte before evaluating the data 220 | #ifdef SERDBG 221 | if (ByteIsRead) { 222 | //Reset bool to enable reading of next byte 223 | ByteIsRead=false; 224 | 225 | 226 | 227 | 228 | if (incomingByte == ' ') { 229 | if(melbus_LastReadByte[11] == 0x0 && (melbus_LastReadByte[10] == 0x4A || melbus_LastReadByte[10] == 0x4C || melbus_LastReadByte[10] == 0x4E) && melbus_LastReadByte[9] == 0xEC && melbus_LastReadByte[8] == 0x57 && melbus_LastReadByte[7] == 0x57 && melbus_LastReadByte[6] == 0x49 && melbus_LastReadByte[5] == 0x52 && melbus_LastReadByte[4] == 0xAF && melbus_LastReadByte[3] == 0xE0 && melbus_LastReadByte[2] == 0x0) 230 | { 231 | melbus_CharBytes=8; //print RDS station name 232 | } 233 | 234 | if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4A) 235 | { 236 | // Serial.println("\n LCD is master: (no CD init)"); 237 | } 238 | else if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4C) 239 | { 240 | // Serial.println("\n LCD is master: (/?/?/?)"); 241 | } 242 | else if(melbus_LastReadByte[1] == 0x0 && melbus_LastReadByte[0] == 0x4E) 243 | { 244 | // Serial.println("\n LCD is master: (with CD init)"); 245 | } 246 | else if(melbus_LastReadByte[1] == 0x80 && melbus_LastReadByte[0] == 0x4E) 247 | { 248 | // Serial.println("\n ???"); 249 | } 250 | else if(melbus_LastReadByte[1] == 0xE8 && melbus_LastReadByte[0] == 0x4E) 251 | { 252 | // Serial.println("\n ???"); 253 | } 254 | else if(melbus_LastReadByte[1] == 0xF9 && melbus_LastReadByte[0] == 0x49) 255 | { 256 | // Serial.println("\n HU is master: "); 257 | } 258 | else if(melbus_LastReadByte[1] == 0x80 && melbus_LastReadByte[0] == 0x49) 259 | { 260 | // Serial.println("\n HU is master: "); 261 | } 262 | else if(melbus_LastReadByte[1] == 0xE8 && melbus_LastReadByte[0] == 0x49) 263 | { 264 | // Serial.println("\n HU is master: "); 265 | } 266 | else if(melbus_LastReadByte[1] == 0xE9 && melbus_LastReadByte[0] == 0x4B) 267 | { 268 | // Serial.println("\n HU is master: -> CDC"); 269 | } 270 | else if(melbus_LastReadByte[1] == 0x81 && melbus_LastReadByte[0] == 0x4B) 271 | { 272 | // Serial.println("\n HU is master: -> CDP"); 273 | } 274 | else if(melbus_LastReadByte[1] == 0xF9 && melbus_LastReadByte[0] == 0x4E) 275 | { 276 | // Serial.println("\n HU is master: "); 277 | } 278 | else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4E) 279 | { 280 | // Serial.println("\n HU is master: "); 281 | } 282 | else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4C) 283 | { 284 | // Serial.println("\n HU is master: "); 285 | } 286 | else if(melbus_LastReadByte[1] == 0x50 && melbus_LastReadByte[0] == 0x4A) 287 | { 288 | // Serial.println("\n HU is master: "); 289 | } 290 | else if(melbus_LastReadByte[1] == 0xF8 && melbus_LastReadByte[0] == 0x4C) 291 | { 292 | // Serial.println("\n HU is master: "); 293 | } 294 | 295 | if (melbus_CharBytes) { 296 | // Serial.write(melbus_LastReadByte[1]); 297 | melbus_CharBytes--; 298 | }else 299 | { 300 | // Serial.print(melbus_LastReadByte[1],HEX); 301 | // Serial.write(' '); 302 | } 303 | } 304 | 305 | 306 | } 307 | #endif 308 | 309 | 310 | 311 | //If BUSYPIN is HIGH => HU is in between transmissions 312 | if (digitalRead(MELBUS_BUSY)==HIGH) 313 | { 314 | //Make sure we are in sync when reading the bits by resetting the clock reader 315 | 316 | #ifdef SERDBG 317 | if (melbus_Bitposition != 0x80) { 318 | // Serial.println(melbus_Bitposition,HEX); 319 | // Serial.println("\n not in sync! "); 320 | } 321 | #endif 322 | if (incomingByte != 'k') { 323 | melbus_Bitposition = 0x80; 324 | melbus_OutByte = 0xFF; 325 | melbus_SendCnt=0; 326 | melbus_DiscCnt=0; 327 | DDRD &= ~(1< 0) { 334 | // // read the incoming byte: 335 | // incomingByte = Serial.read(); 336 | // 337 | // 338 | // } 339 | // if (incomingByte == 'i') { 340 | // melbus_Init_CDCHRG(); 341 | // Serial.println("\n forced init: "); 342 | // incomingByte=0; 343 | // 344 | // } 345 | #endif 346 | if ((melbus_Bitposition == 0x80) && (PIND & (1<