Go Down

Topic: Car Heads Up Display (Read 11981 times) previous topic - next topic

Power_Broker

#45
Mar 07, 2020, 09:21 pm Last Edit: Mar 07, 2020, 09:28 pm by Power_Broker
Images:






"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

jimLee

Pretty cool! Looks like its getting elaborate. You have plans for permeant packaging and mounting?

-jim lee
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

Power_Broker

Thanks!

Probably won't make it much more permanent.
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

av4625

Awesome thread! I can confirm that this library is very easy to use and required very little thinking for me to get RPM data! Thanks! and thanks for your help on my question thread!

I seem to be able to get RPM data at 7-8Hz, I saw you talking about 13Hz. Would that speed difference just be down to your ELM327 and mine? Mine is just a cheap blue stubby ebay one.

I recall seeing information somewhere that says you shouldn't poll OBD more than 10 times a second as it causes buffer overflow in the ECU and that is potentially very bad I guess lol. Theres a linux OBD library and they won't let you poll more than that. This limitation is on cars before a certain year. I must try and find this information again, I found it back at the very start of messing with my project.

Power_Broker

Awesome thread! I can confirm that this library is very easy to use and required very little thinking for me to get RPM data! Thanks! and thanks for your help on my question thread!
Thanks! Always glad to help!


I seem to be able to get RPM data at 7-8Hz, I saw you talking about 13Hz. Would that speed difference just be down to your ELM327 and mine? Mine is just a cheap blue stubby ebay one.
Pretty much all ELM327 modules under $40 are el-cheapo China devices. The one I got was probably not much more than $10.

Yeah, I get about 13Hz for repeatedly querying a single value (i.e. rpm). If I add more query values (i.e. poll both speed and rpm), the refresh rate for each value type goes down. Because of this, if you poll speed and rpm, you will get ~13/2Hz = ~7Hz refresh rate for each value type. Why your results are different, I'm not sure.


I recall seeing information somewhere that says you shouldn't poll OBD more than 10 times a second as it causes buffer overflow in the ECU and that is potentially very bad I guess lol. Theres a linux OBD library and they won't let you poll more than that. This limitation is on cars before a certain year. I must try and find this information again, I found it back at the very start of messing with my project.
My library doesn't allow you to re-poll the ELM327 until it's returned a response for the current poll - this allows you to poll the device as fast as possible. This is one of the reasons the library is blocking.


"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

Power_Broker

#50
Apr 21, 2020, 06:32 am Last Edit: Apr 21, 2020, 06:33 am by Power_Broker
I updated the software a couple of weeks ago that allow SD card datalogging on the Teensy along with a very, very basic command line style interface. Basically, you can connect to the Teensy via USB while the HUD is running, type "ls" and it will list all of the "drive files" (a new "drive file" is created upon startup to store that drive's data). You can then enter the complete filename of the "drive file" you would like to analyze and the Teensy will do a complete datadump of the file.

I then wrote a Python program that, with the help of command line arguments, can use this interface to analyze and plot a test dataset, a dataset from a file system, or a dataset downloaded from the HUD directly over USB.

Here is the Python:
Code: [Select]
import os
import serial
import serial.tools.list_ports
import argparse
import pandas as pd
import matplotlib.pyplot as plt


SCRIPT_PATH   = os.path.realpath(__file__)
SCRIPT_DIR    = os.path.dirname(SCRIPT_PATH)
SER_DATA_DIR  = os.path.join(SCRIPT_DIR, 'ser_data')
SER_DATA_PATH = os.path.join(SER_DATA_DIR, 'ser_data.txt')
TEST_FILE_NUM = 1
TEST_FILE     = os.path.join(SCRIPT_DIR, 'test_data', 'drive_{}.txt'.format(TEST_FILE_NUM))


plot_list = []


class InvalidSerialPort(Exception):
    pass


def setup_args():
    parser = argparse.ArgumentParser()

    parser.add_argument('--td',   help='Use test data file')
    parser.add_argument('--ser',  help='Use serial interface to download data from the HUD')
    parser.add_argument('--port', help='Serial port to communicate with HUD')
    parser.add_argument('--baud', help='Serial baud to communicate with HUD')
    parser.add_argument('--dn',   help='Drive number - used with serial download only')
    parser.add_argument('--file', help='URL to file with data to be processed')
   
    return parser.parse_args()


def serial_ports():
    return [p.device for p in serial.tools.list_ports.comports(include_links=True)]


def plot_data(data_file, plot_title='Drive Data'):
    df = pd.read_csv(data_file)
    df.plot(subplots=True, figsize=(8, 8), x='Epoch', title=plot_title)
    plt.legend(loc='best')
    plot_list.append(plt)

def display_plots():
    for plot in plot_list:
        plot.show()

def conn_to_ser():
    port_name = None
   
    for p in serial_ports():
        if p == args.port or os.path.split(p)[-1] == args.port:
            port_name = p
            break

    if port_name is None:
        raise InvalidSerialPort('Invalid serial port specified.\
            Valid options are {ports},  but {port} was provided'.format(
            **{'ports': serial_ports(), 'port': args.port}))
    else:
        connection = serial.Serial()
        connection.port = port_name
        connection.baudrate = int(args.baud)
        connection.open()
       
        print('Connected on {}'.format(port_name))
   
    return connection

def drives_from_ser(connection):
    connection.write('ls\n'.encode())
                   
    while b'?' in connection.readline():
        connection.write('ls\n'.encode())
       
    drive_files = []
   
    while connection.in_waiting:
        try:
            line = connection.readline().decode('utf-8')
            print(' '.join(line.split()))
           
            if ('.txt' in line) or ('.csv' in line):
                drive_files.append(line.split()[0])
        except UnicodeDecodeError:
            pass
   
    return drive_files

def grab_ser_data(connection, drive):
    connection.write(drive.encode())
   
    while b'?' in connection.readline():
        connection.write(drive.encode())
   
    data_lines = []
   
    while True:
        line = connection.readline()
       
        if (b'-' * 50) in line:
            break
       
        elif (b'.txt' not in line) and (b'.csv' not in line):
            data_lines.append(line)
   
    return data_lines

def log_ser_data(connection):
    with open(SER_DATA_PATH, 'w') as ser_data:
        for data_line in data_lines:
            write_line = ''
           
            for char in data_line:
                if chr(char).isascii():
                    write_line += chr(char)
           
            if write_line.count(',') == 5:
                ser_data.write(write_line.strip())
                ser_data.write('\n')


if __name__ == '__main__':
    args = setup_args()

    if args.ser:
        if args.ser.lower() == 'true':
            if args.port and args.baud:
                try:
                    connection = conn_to_ser()
                    drive_files = drives_from_ser(connection)
                   
                    print(drive_files)
                   
                    if not os.path.exists(SER_DATA_DIR):
                        os.makedirs(SER_DATA_DIR)
                   
                    if args.dn:
                        drive_number = int(args.dn)
                        drive = 'drive_{}.txt'.format(drive_number)
                       
                        if drive in drive_files:
                            print(drive)
                           
                            data_lines = grab_ser_data(connection, drive)
                            log_ser_data(connection)
                           
                            if os.path.exists(SER_DATA_PATH):
                                try:
                                    plot_data(SER_DATA_PATH, drive)
                                except pd.errors.ParserError:
                                    pass
                                except TypeError:
                                    pass
                                except pd.errors.EmptyDataError:
                                    print('ERROR - Empty text file')
                               
                                display_plots()
                            else:
                                print('ERROR - Could not find file {}'.format(SER_DATA_PATH))
                        else:
                            print('ERROR - Drive file {} not found on SD'.format(drive))
                       
                    else:
                        for drive in drive_files:
                            print(drive)
                           
                            data_lines = grab_ser_data(connection, drive)
                            log_ser_data(connection)
                           
                            if os.path.exists(SER_DATA_PATH):
                                try:
                                    plot_data(SER_DATA_PATH, drive)
                                except pd.errors.ParserError:
                                    pass
                                except TypeError:
                                    pass
                                except pd.errors.EmptyDataError:
                                    print('ERROR - Empty text file')
                            else:
                                print('ERROR - Could not find file {}'.format(SER_DATA_PATH))
                       
                        display_plots()
                except:
                    import traceback
                    traceback.print_exc()
                   
                    data_file = TEST_FILE
            else:
                data_file = TEST_FILE
    elif args.file:
        if os.path.exists(args.file):
            try:
                plot_data(args.file)
                display_plots()
            except pd.errors.ParserError:
                pass
            except TypeError:
                pass
            except pd.errors.EmptyDataError:
                print('ERROR - Empty text file')
        else:
            print('ERROR - Could not find file {}'.format(args.file))
    elif args.td:
        data_file = TEST_FILE
    else:
        data_file = TEST_FILE
   
    try:
        if os.path.exists(data_file):
            try:
                plot_data(data_file)
                display_plots()
            except pd.errors.ParserError:
                pass
            except TypeError:
                pass
            except pd.errors.EmptyDataError:
                print('ERROR - Empty text file')
        else:
            print('ERROR - Could not find file {}'.format(data_file))
    except NameError:
        pass
   
    try:
        connection.close()
    except:
        pass



And here is an example dataset plotted by Python:



For the complete code of all pieces of the project, see the repo.


"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

jimLee

Why does the RPM look so jaggy? I'd think it would be pretty smooth, just like speed.

-jim lee
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

Power_Broker

I honestly don't know. I'd be curious to see what the RPM curves are like for other cars.
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

av4625

Why does the RPM look so jaggy? I'd think it would be pretty smooth, just like speed.

-jim lee
I'd expect the RPM to look jaggy like that. For example, see where the car gets up to the higher speed and then stays at that high speed for a bit? The engine revs up to accelerate to the speed and then it drops into a higher gear when up to speed meaning the RPM comes down. I guess the slower bits are like junctions etc and here you would be reving up to accelerate and then braking and doing this over and over making it jaggy.

HM78

Hi, congratz for project.

I can't understand how you can connect to ELM327. It gives me 'connected' on serial monitor but it isn´t because i can't get values from it and BT led isn't on. I have the same elm327 BT that you showed (black with orange and blue tag).

On webpage from your last example you put in here, it didn´t show if its connected or not (i think).
Can you explain better how to check connection?

I have only the ESP32 as circuit hardware. Do i need something more?

When connect by Android i can connect to elm327 and get values in "torque" or "car scanner" but not with esp32.

I have read that using BT and Wifi at same time, ESP32 can be hot, confused or get burned.

Hoping for your answer,
Hugo Morais

Power_Broker

1.) Post your code and schematic
2.) Ensure that your car is on and running
3.) Ensure that all other devices in the area have bluetooth turned off
4.) Ensure your ELM327's name is "OBDII", if not, change the name in the sketch or simply use the MAC address
5.) Try using 38400 baud instead of 115200
6.) Attempt connection manually with this sketch. Try the following commands:
Quote
AT Z
AT E0
AT S0
AT SP0
010C
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

Go Up