If an admin thinks this is better suited in the Software Development, please move it there. I didn't see an dedicated OBD section over there and I wanted to make sure the people using OBDPro see this.
I've written a c# app that reads data from my cars OBD port using the OBDPro scantool. My program loops through my "Command" objects and sends commands to a serial port connection object. I'm only sending 4 commands on each loop; Speed, RPM, CoolantTemp and Throttle position. After all four have been sent the application sleeps for 1 seconds and resumes the loop. Meanwhile I have an event listener on the Serial Port object that fires anytime data appears on the serial ports buffer. When it does I read all the characters until I find a carriage return and convert that hex string to a readable integer value and update my windows form. However whenever I send 4 or more commands on each iteration I often get "NO DATA" or ? from the cars OBD port for at least one of the commands. Before talk to the OBD port I first send ate0 and atl0 to suppress echoes and linefeeds. The baud rate is also @ 128000. ScanTool.net doesn't seem to behave this way. Can anyone give some pointers on how to make the responses more reliable? Here are a few snippets of my code:
My control loop:
My event listener:Code:private void BeginControlLoopThread() { while (true) { foreach (OBDCommand obdCommand in mOBDCommandRepository.OBDCommands) { mSerialPort.WriteLine(obdCommand.Mode + " " + obdCommand.PID); mMainWindow.WriteToInputLogDisplay(obdCommand.Mode + " " + obdCommand.PID); } Thread.Sleep(1000); } }
Code:private void SerialPort_DataReceived(object sender, EventArgs e) { string response = ""; try { response = mSerialPort.ReadTo("\r>"); } catch (Exception exception) { // do nothing, exit // exception.Message.ToString(); return; } response = response.Trim(); mMainWindow.WriteToOutputLogDisplay(response); if (response.StartsWith("4")) { string[] translatedResponse = mOBDCommandRepository.TranslateResponse(response); SendToUI(translatedResponse); } }
If an admin thinks this is better suited in the Software Development, please move it there. I didn't see an dedicated OBD section over there and I wanted to make sure the people using OBDPro see this.
OrangeSword,
In your control loop thread you should wait for the ">" prompt before sending another command. The OBDPro can buffer serial data while executing a previous OBD Command but ocasionally it will not be able to keep up with buffering serial data and processing the OBD command at the same time.
One way to do this is create a boolean promptSeen. Set this to true when you read a ">" on the serial port handler, and only send a new OBD command when promptSeen == TRUE
hth
Paul
www.obdpros.com
I am not getting a "Buffer Full" message back so I don't think the buffer is full.
But anyway following your advice, I've changed the code to this:
Potentially I will have a large number of responses to loop through and it might take awhile to finish the loop if I have to wait for every response. Is there a way to get multiple responses back at one time? Like ALL of the data in Mode 01? Am I doing it the most efficient way, is what I am trying to get at.Code:private void BeginControlLoopThread() { bool promptFound; ; bool responseEndFound; string response; while (true) { foreach (OBDCommand obdCommand in mOBDCommandRepository.OBDCommands) { response = ""; promptFound = false; while (!promptFound) { try { mSerialPort.ReadTo(">"); mSerialPort.WriteLine(obdCommand.Mode + " " + obdCommand.PID); mMainWindow.WriteToInputLogDisplay(obdCommand.Mode + " " + obdCommand.PID); promptFound = true; } catch (Exception e) { // Prompt was not found, do nothing } } responseEndFound = false; while (!responseEndFound) { try { response = mSerialPort.ReadTo("\r"); response = response.Trim(); mMainWindow.WriteToOutputLogDisplay(response); if (response.StartsWith("4")) { string[] translatedResponse = mOBDCommandRepository.TranslateResponse(response); SendToUI(translatedResponse); } responseEndFound = true; } catch (Exception e) { // Command not finished was not found, do nothing } } } Thread.Sleep(1000); }
Sorry I should have made it a bit more clear....
It's not the OBD buffer that is full, but when getting data from the vehicle bus there are times when the interface needs to shut off processing the serial port interrupts, this means some characters would be dropped...
The ">" char signals the PC that the OBDPro is done executing the command and is now ready to accept additional commands. To make sure you can issue the command immediately when the interface is ready one way would be to send the data right out of the serial data recieved handler. Let me find the code that I use to test the interface and I can send you something as an example...
Paul
www.obdpros.com
Thanks. I appreciate it. One other question, is the "NO DATA" and "?" results terminated by a carriage return?
Keep in mind that if you are reading up to the prompt, you will not be able to use this when looking at streaming data (such as the ATMA mode) since it does not produce a prompt after each line. Using the ReadLine method and keeping line breaks on (ATL1) will solve this. You can always trim the prompt off the front of the response line.
Also, for those looking to use VB.NET you will need to use the Write method and append a carriage return to the command. The WriteLine method does not work with this in VB.NET.
I'll be posting a demo app on obdpros.com forums in a day or so. Just something basic to help beginners get started. My nick on obdpros.com is Deviation....
Yes. If you leave line breaks on.
hi have you finished this yet
No not yet. I'm not really building it for distribution, but here is the code I have so far if you are interested in that:
Code:using System; using System.Collections.Generic; using System.Text; using System.Collections; using System.IO.Ports; using System.Configuration; namespace OrangeSword.FrontierDash.ObdReader.BusinessLogic { /// <summary> /// Represents a OBD device such as a OBDPro. /// </summary> public class ObdDevice { private string portName; private int baudRate; private SerialPort serialPort; /// <summary> /// Initializes a new instance of the <see cref="ObdDevice"/> class. /// </summary> public ObdDevice() { portName = ConfigurationManager.AppSettings["portName"]; baudRate = Convert.ToInt32(ConfigurationManager.AppSettings["baudRate"]); Connect(); } /// <summary> /// Connects this instance. /// </summary> private void Connect() { serialPort = new SerialPort(portName, baudRate); serialPort.Open(); serialPort.ReadTimeout = 1000; serialPort.NewLine = "\r"; } /// <summary> /// Disconnects this instance. /// </summary> private void Disconnect() { serialPort.Close(); } /// <summary> /// Initializes this instance. /// </summary> public void Initialize() { Reset(); SendAtCommand("ate0"); SendAtCommand("atl0"); } /// <summary> /// Sends the obd command. /// </summary> /// <param name="obdCommand">The obd command.</param> /// <returns>The response to the OBD command from the OBD device.</returns> public string SendObdCommand(ObdCommand obdCommand) { string response = ""; bool promptFound = false; bool responseEndFound = false; while (!promptFound) { try { serialPort.ReadTo(">"); serialPort.WriteLine(obdCommand.Mode + " " + obdCommand.PID); promptFound = true; } catch (Exception e) { // Prompt was not found Console.WriteLine("Prompt not found: " + e.Message); } } while (!responseEndFound) { try { response = serialPort.ReadTo("\r"); response = response.Trim(); if (response.StartsWith("4")) { responseEndFound = true; } } catch (Exception e) { // Carriage Return not found Console.WriteLine("Carriage Return not found: " + e.Message); if (response == "NO DATA") { responseEndFound = true; } } } return response; } /// <summary> /// Sends the AT command. /// </summary> /// <param name="atCommand">The AT command.</param> /// <returns></returns> public string SendAtCommand(string atCommand) { string response = ""; bool promptFound = false; bool responseEndFound = false; while (!promptFound) { try { serialPort.ReadTo(">"); serialPort.WriteLine(atCommand); promptFound = true; } catch (Exception e) { // Prompt was not found Console.WriteLine("Prompt not Found: " + e.Message); } } while (!responseEndFound) { try { response = serialPort.ReadTo("\r"); response = response.Trim(); responseEndFound = true; } catch (Exception e) { // Carriage Return not found Console.WriteLine("Carriage Return not found: " + e.Message); } } return response; } /// <summary> /// Resets this instance. /// </summary> public void Reset() { serialPort.WriteLine("atz"); } } }
Hey guys, new here but been lurking a few weeks. I am about to start a project as well in C#.net, and Orange I think that code is a great start, I had no idea that there's a ports namespace (just looked it up and it looks like it's new to framework 2.0+). upon just a quick msdn read, it looks like the SerialPort class has event handler delegates, so that would seem to be your best bet, rather than waiting for a certain response, you could just use the SerialDataReceivedEventHanlder delegate to invoke a callback function. I'm going to start messing around with this tonight so if I make any progress I'll be sure and post it up here.
Bookmarks