OBD serial app

Vader

Well-Known Member
Licensed User
Longtime User
Ok, so this is what I need to do...

On options screen, choose a number of commands to send. Each Command represents some data to send/receive.

Once up to 20 commands have been selected, then compose and send the Command string.

Now, we have to receive and display the data. So, receive one set of data, draw and display the data (some will be graphed), then receive the next set of data, graph and display, etc.

At some point we will decide to send a stop to cancel the current data stream. Then we may want to compose a new set of commands, and then start over.

This is basically what I need to do, and is the only reason I bought B4a. If anyone can help me out, then I would be very grateful.

The above application is a car diagnostic / data logging application. And before anyone suggests I use OBD2, the car is not OBD2 compliant.

Thanks in advance.
 

Vader

Well-Known Member
Licensed User
Longtime User
Do you need to wait for the response before sending the next command?

For every piece of data sent, there is a reply.

The reply tells you if the command was received correctly.

So, the answer to your question is yes - you put together a command, send it, then receive the appropriate number of bytes from the buffer, and check them to ensure they are as expected.
If they are what is required, then you can either send more commands (following the above principle of checking the reply), or read more data if you were expecting a data stream.
If the response is not as you expect, you can send a stop command (or two) and then try again.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
There are several ways to build such a system.
You need to implement a "state machine".

One possible solution is to create a custom type that represents a command. This type holds all the information you need for each command.
You should then add all the commands to a list and send the first item.

Then in NewData event you should read the response. The current command is at the head of the list. So you have access to both the command and the response.

Lets start simple. All the data for this command was received. So you remove the command from the head of the list and send the next request.

If you need to wait for more data then you should not remove the command from the list. Instead you can add the partial data to the command and wait for the next call of NewData to continue.
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Sounds good, except for the State Machine part, but if I have no other choice, then so be it.

Because I already have 75% of the solution working in Windows, it's fairly easy for me to see how and what I need to do.

Here are the types you mention that I need:

B4X:
Public Enum ECUSingleByteCommands As Byte
        Proceed = &HF0
        Stop_Stream = &H30
        Read_DTC_Set2 = &HD1
        Reset_DTC_Set1 = &HC1
        Reset_DTC_Set3 = &H51
        Read_ECU_Part_Number = &HD0
        Read_ROM_Byte = &HC9
        Read_Register_Parameter = &H5A
        Active_Test = &HA
    End Enum

    Public Enum ECUActiveTest As Byte
        Adjust_Coolant_Temp = &H80
        Adjust_Fuel_Injection_Time = &H81
        Adjust_Ignition_Timing = &H82
        Adjust_IAAC_ValveOpening = &H84
        Power_Balance = &H88
        Fuel_Pump_Relay = &H89
        Clear_Self_Learn = &H8B
    End Enum

    Public Enum ECUActiveTestData As Byte
        ''' <summary>
        ''' Fuel Injection
        ''' </summary>
        ''' <remarks>
        ''' 0x63 = -1%
        ''' 0x65 = +1%
        ''' </remarks>
        Fuel_Injection_Normal = &H64

        ''' <summary>
        ''' Ignition Timing
        ''' </summary>
        ''' <remarks>
        ''' 0x01 = +1 deg
        ''' 0xFF = -1 deg
        ''' </remarks>
        Ignition_Timing_Normal = &H0

        ''' <summary>
        ''' IAAC Valve
        ''' </summary>
        ''' <remarks>
        ''' 0x01 = +0.5%
        ''' 0xFF = -0.5%
        ''' </remarks>
        IAAC_Valve_Open_Normal = &H0

        Power_Balance_Firing_Normal = &H0
        Power_Balance_Cylinder_1_Off = &H1
        Power_Balance_Cylinder_2_Off = &H2
        Power_Balance_Cylinder_3_Off = &H3
        Power_Balance_Cylinder_4_Off = &H4
        Power_Balance_Cylinder_5_Off = &H5
        Power_Balance_Cylinder_6_Off = &H6
        Power_Balance_Cylinder_7_Off = &H7
        Power_Balance_Cylinder_8_Off = &H8

        Fuel_Pump_Relay_On = &H0
        Fuel_Pump_Relay_Off = &H1

        Clear_Self_Learn_Value = &H0
    End Enum

    Public Enum ECURegister As Byte
        CAS_POSITION_MSB = &H0                 ' Value * 12.5 (RPM)
        CAS_POSITION_LSB = &H1
        CAS_REFERENCE_MSB = &H2                ' Value * 8 (RPM)
        CAS_REFERENCE_LSB = &H3
        MAF_VOLTAGE_MSB = &H4                  ' Value * 5 (mV)
        MAF_VOLTAGE_LSB = &H5
        RH_MAF_VOLTAGE_MSB = &H6               ' Value * 5 (mV)
        RH_MAF_VOLTAGE_LSB = &H7
        COOLANT_TEMP = &H8                     ' Value - 50 (deg C)
        LH_O2_SENSOR_VOLTAGE = &H9             ' Value * 10 (mV)
        RH_O2_SENSOR_VOLTAGE = &HA             ' Value * 10 (mV)
        VEHICLE_SPEED = &HB                    ' Value * 2 (km/h)
        BATTERY_VOLTAGE = &HC                  ' Value * 80 (mV)
        THROTTLE_POSITION_VOLTAGE = &HD        ' Value * 20 (mV)
        FUEL_TEMP = &HF                        ' Value - 50 (deg C)
        INTAKE_AIR_TEMP = &H11                 ' Value - 50 (deg C)
        EXHAUST_GAS_TEMP = &H12                ' Value * 20 (mV)  ????
        LH_INJECTION_TIME_MSB = &H14           ' Value / 100 (ms)
        LH_INJECTION_TIME_LSB = &H15
        IGNITION_TIMING = &H16                 ' 110 - Value (deg BTDC)
        IDLE_AIR_VALVE_PERCENT = &H17          ' Value / 2 (%)
        LH_AIR_FUEL_ALPHA = &H1A               ' Value (%)
        RH_AIR_FUEL_ALPHA = &H1B               ' Value (%)
        LH_AIR_FUEL_ALPHA_SELF_LEARN = &H1C    ' Value (%)
        RH_AIR_FUEL_ALPHA_SELF_LEARN = &H1D    ' Value (%)
        RH_INJECTION_TIME_MSB = &H22           ' Value / 100 (ms)
        RH_INJECTION_TIME_LSB = &H23           ' Value / 100 (ms)
    End Enum

    Public Enum ECUMonitorParameter As Byte
        NULL = 0
        CAS_POS
        CAS_REF
        MAF_VOLT
        RH_MAF_VOLT
        COOLANT_TEMP
        LH_O2_VOLT
        RH_O2_VOLT
        SPEED
        BAT_VOLT
        TPS_VOLT
        FUEL_TEMP
        INTAKE_AIR_TEMP
        EXHAUST_GAS_TEMP
        LH_INJECTION_TIME
        IGN_TIMING
        IDLE_AIR_VALVE
        LH_AF_ALPHA
        RH_AF_ALPHA
        LH_AF_ALPHA_SL
        RH_AF_ALPHA_SL
        RH_INJECTION_TIME
        WASTE_GATE_SOLENOID
        TURBO_BOOST_SENSOR_VOLTAGE
        ENGINE_MOUNT_ON_OFF
        POSITION_COUNTER
        PURG_VOL_CONT_VALVE
        TANK_FUEL_TEMP
        FPCM_DR_VOLTAGE
        FUEL_GAUGE_VOLTAGE
        FR_O2_HEATER_B1
        FR_O2_HEATER_B2
        IGN_SW
        CAL_LD_VALUE
        B_FUEL_SCHEDULE
        RR_O2_SENSOR_VOLTAGE_B1
        RR_O2_SENSOR_VOLTAGE_B2
        ABS_THROTTLE_POSITION
        MAF_GM_PER_SEC
    End Enum

    Public Enum ConnectionState As Int32
        Disconnected = 0
        Connecting = 1
        Connected = 2
    End Enum

    Public Structure ECUData
        Public Command As ECUCommand
        Public FrameStartLeader As Byte ' Should be &HFF
        Public DataByteCount As Byte
        Public Data As Byte()
    End Structure

    Public Structure ECUCommand
        Dim Data As Byte
        Dim Response As Byte
    End Structure
 
Upvote 0

KitCarlson

Active Member
Licensed User
Longtime User
Erel provides excellent advice. I see the state machine as case statements in the Newdata receive. The switch global variable is derived from the request command. All happens in a serial fashion, with time limit error handling.

The system handles all the various receive and data handing based on command requirements. I am likely stating the obvious.

I develop engine controllers and use b4a as the gui via BT with great success.
 
Upvote 0

irda

Member
Licensed User
Longtime User
There are several ways to build such a system.
You need to implement a "state machine".

One possible solution is to create a custom type that represents a command. This type holds all the information you need for each command.
You should then add all the commands to a list and send the first item.

Then in NewData event you should read the response. The current command is at the head of the list. So you have access to both the command and the response.

Lets start simple. All the data for this command was received. So you remove the command from the head of the list and send the next request.

If you need to wait for more data then you should not remove the command from the list. Instead you can add the partial data to the command and wait for the next call of NewData to continue.

It is a good solution, but only if the other side always responds to the command. If there is not reply the NewData function is not executed and therefore we'll be waiting to send the next command, which just might respond.
 
Upvote 0

KitCarlson

Active Member
Licensed User
Longtime User
The timer error, waits for sufficient time plus some. If complete reply is not received, it clears buffer, posts error, sends next command in que... Vader has to work with OBD fixed on one side. There are better solutions if one enhances the system dialog.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
The timer error, waits for sufficient time plus some. If complete reply is not received, it clears buffer, posts error, sends next command in que... Vader has to work with OBD fixed on one side. There are better solutions if one enhances the system dialog.
I agree. You should use a "watchdog" timer for that.
 
Upvote 0
Top