I'm developing a digital dashboard for a Xamarin Android app using C#. As part of this project, I'm receiving RPM responses in the form of hexadecimal strings. Now, I need to extract the last 4 digits from these responses, convert them to decimal, and then divide the result by 4 programmatically Like if response is in this format 41 0C 11 F1 I would like to extract this 11 F1 but the values could be different. here is my mainactitivy.cs
using Android.App;
using Android.OS;
using Android.Widget;
using Android.Views;
using Java.Util;
using System;
using System.Threading;
using System.Timers;
using Xamarin.Essentials;
using System.Threading.Tasks;
using Android.Bluetooth;
using System.Reflection;
using In.UnicodeLabs.KdGaugeViewLib;
namespace App6
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : Activity
{
private TextView rpmLabel;
private TextView speedLabel;
private ObdManager obdManager;
private BluetoothSocket btnSocket;
private TextView obdInfoLabel;
private Button requestButton;
private TextView rawRpmLabel;
private TextView rawSpeedLabel;
private TextView anotherRpmDoubleLabel;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
rpmLabel = FindViewById<TextView>(Resource.Id.rpmLabel);
speedLabel = FindViewById<TextView>(Resource.Id.speedLabel);
rawRpmLabel = FindViewById<TextView>(Resource.Id.rawRpmLabel);
rawSpeedLabel = FindViewById<TextView>(Resource.Id.rawSpeedLabel);
anotherRpmDoubleLabel = FindViewById<TextView>(Resource.Id.anotherRpmDoubleLabel);
InitializeBluetooth();
obdManager = new ObdManager(btnSocket);
// Set up a timer to periodically request and update RPM and speed (every 0.1 seconds)
System.Timers.Timer updateTimer = new System.Timers.Timer(2000);
updateTimer.Elapsed += OnUpdateTimerElapsed;
updateTimer.Start();
}
private void OnUpdateTimerElapsed(object sender, ElapsedEventArgs e)
{
// Request RPM
obdManager.SendCommand("010C", OnRPMReceived);
obdManager.SendCommand("010C", OnRAWRpmReceived);
// Request speed
obdManager.SendCommand("010D", OnSpeedReceived);
}
private void OnRPMReceived(string rpmdata)
{
try
{
// Parse the RPM value
string rpmValue = ObdProtocol.ParseRPM(rpmdata);
// Update the raw RPM label with the parsed raw RPM data
RunOnUiThread(() => rawRpmLabel.Text = $"Raw RPM Data: {rpmValue}");
// Optionally, you can also update the RPM label with the same value
RunOnUiThread(() => rpmLabel.Text = $"RPM: {rpmValue}");
//obdManager.SendCommand("010D", OnRealRAWRpmReceived);
// RunOnUiThread(() => anotherRpmDoubleLabel.Text = $"RPM: {rpmdata}");
// Update the new label with the parsed RPM data as a double
}
catch (Exception ex)
{
Console.WriteLine($"Error updating RPM label: {ex.Message}");
}
}
/*public static double ParseRealRPM(string realrpmdata)
{
// Find the RPM value in the response
int startIndex = realrpmdata.LastIndexOf("010C") + 4;
// Check if the start index is valid
if (startIndex < 0 || startIndex + 4 > realrpmdata.Length)
{
// Handle invalid data or return a default value
return 0.0; // You can adjust the default value based on your needs
}
// Get the last 4 digits of the response
string rpmHex = realrpmdata.Substring(startIndex, 4);
try
{
// Convert the hexadecimal value to decimal
int rpmDecimal = Convert.ToInt32(rpmHex, 16);
// Divide the decimal value by 4
double realrpmValue = rpmDecimal / 4.0;
return realrpmValue;
}
catch (FormatException ex)
{
// Handle the FormatException (non-parsable characters)
// Log the error or return a default value
Console.WriteLine($"Error parsing RPM value: {ex.Message}");
return 0.0; // You can adjust the default value based on your needs
}
}*/
/* public void OnRealRAWRpmReceived(string realrpmdata)
{
RunOnUiThread(() => anotherRpmDoubleLabel.Text = $"RealRPM: {ParseRealRPM(realrpmdata)}");
}*/
private void UpdateRPMUI(double actualRPM)
{
RunOnUiThread(() => rpmLabel.Text = $"RPM: {actualRPM:F2} RPM");
}
private void OnSpeedReceived(string speeddata)
{
try
{
// Update the speed label with raw speed data
RunOnUiThread(() => rawSpeedLabel.Text = $"Raw Speed Data: {speeddata}");
// Parse and display the converted speed value
int speedValue = ObdProtocol.ParseSpeed(speeddata);
UpdateSpeedUI(speedValue);
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing Speed data: {ex.Message}");
}
}
private void UpdateSpeedUI(int speedValue)
{
RunOnUiThread(() => speedLabel.Text = $"Speed: {speedValue} km/h");
}
/* public void OnRAWRpmReceived(string rpmData)
{
// Check if the response contains "010C" (the command)
int index = rpmData.IndexOf("010C");
if (index != -1)
{
// If "010C" is found, extract the RPM data after it
string rpmDataWithoutCommand = rpmData.Substring(index + 8); // Assuming "010C" is 4 characters long
// Extract the last 4 digits of the response
string lastFourDigitsHex = rpmDataWithoutCommand.Substring(0, 8);
// Convert the last 4 hexadecimal digits to decimal
int decimalValue = Convert.ToInt32(lastFourDigitsHex, 16);
// Divide the decimal value by 4
double result = decimalValue / 4.0;
// Now you can update the UI with the result
RunOnUiThread(() => rawRpmLabel.Text = $"Raw RPM Data: {result}");
}
else
{
// If "010C" is not found, handle the response as needed
Console.WriteLine("Response does not contain RPM data.");
}
}*/
public void OnRAWRpmReceived(string rpmData)
{
// Check if the response contains "010C" (the command)
int index = rpmData.IndexOf("010C");
if (index != -1)
{
// If "010C" is found, extract the RPM data after it
string rpmDataWithoutCommand = rpmData.Substring(index + 4); // Assuming "010C" is 4 characters long
// Now you can update the UI with the extracted RPM data
RunOnUiThread(() => rawRpmLabel.Text = $"Raw RPM Data: {rpmDataWithoutCommand}");
}
else
{
// If "010C" is not found, handle the response as needed
Console.WriteLine("Response does not contain RPM data.");
}
}
/* public void OnRAWRpmReceived(string rpmData)
{
// Check if the response contains "410C" (the command)
int index = rpmData.IndexOf("410C");
if (index != -1)
{
// If "410C" is found, extract the RPM data after it
string rpmDataWithoutCommand = rpmData.Substring(index + 4); // Assuming "410C" is 4 characters long
// Extract the last 4 digits of the response
string lastFourDigitsHex = rpmDataWithoutCommand.Substring(0, 4);
// Convert the last 4 hexadecimal digits to decimal
int decimalValue = Convert.ToInt32(lastFourDigitsHex, 16);
// Divide the decimal value by 4
double result = decimalValue / 4.0;
// Now you can update the UI with the result
RunOnUiThread(() => rawRpmLabel.Text = $"Raw RPM Data: {result}");
}
else
{
// If "410C" is not found, handle the response as needed
Console.WriteLine("Response does not contain RPM data.");
}
}*/
/*public void OnRAWRpmReceived(string rpmdata)
{
try
{
// Parse the RPM value as a string
string rpmHex = ObdProtocol.ParseRPM(rpmdata);
// Convert the string to decimal and divide by 4.0
double rpmValue = Convert.ToInt32(rpmHex, 16) / 4.0;
// Update the raw RPM label with the parsed raw RPM data
RunOnUiThread(() => rawRpmLabel.Text = $"Raw RPM{rpmValue} data");
}
catch (Exception ex)
{
// Handle any exceptions that might occur during parsing or UI update
Console.WriteLine($"Error processing RPM data: {ex.Message}");
}
}*/
/*VAJNO RABOTESHT KOD ZA SKOROST V 16-TICEN KOD private void OnRawSpeedReceived(string rawspeeddata)
{
RunOnUiThread(() => rawSpeedLabel.Text = $"Raw Speed Data: {rawspeeddata}");
}*/
public void OnRawSpeedReceived(string rawSpeedData)
{
// Check if the response contains "010D" (the command for speed)
int index = rawSpeedData.IndexOf("010D");
if (index != -1)
{
// If "010D" is found, extract the speed data after it
string speedDataWithoutCommand = rawSpeedData.Substring(index + 4); // Assuming "010D" is 4 characters long
// Now you can update the UI with the extracted speed data
RunOnUiThread(() => rawSpeedLabel.Text = $"Raw Speed Data: {speedDataWithoutCommand}");
}
else
{
// If "010D" is not found, handle the response as needed
Console.WriteLine("Response does not contain speed data.");
}
}
private void InitializeBluetooth()
{
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter;
if (bluetoothAdapter == null)
{
return;
}
if (!bluetoothAdapter.IsEnabled)
{
Android.Content.Intent enableBtIntent = new Android.Content.Intent(BluetoothAdapter.ActionRequestEnable);
StartActivityForResult(enableBtIntent, requestCode: 1);
return;
}
BluetoothDevice obdDevice = FindOBDIIBluetoothDevice("00:1D:A5:68:98:8A");
if (obdDevice != null)
{
try
{
UUID sppUuid = UUID.FromString("00001101-0000-1000-8000-00805F9B34FB");
btnSocket = obdDevice.CreateRfcommSocketToServiceRecord(sppUuid);
btnSocket.Connect();
// Connection successful, show a message to the user
MainThread.BeginInvokeOnMainThread(() =>
{
Toast.MakeText(this, $"Bluetooth connection to {obdDevice.Name} established.", ToastLength.Long).Show();
});
// Set the protocol to "AUTO" after connection
// Request protocol information after the connection is successful
}
catch (Exception ex)
{
// Connection failed, show a message to the user
MainThread.BeginInvokeOnMainThread(() =>
{
Toast.MakeText(this, $"Bluetooth connection error: {ex.Message}", ToastLength.Long).Show();
});
}
}
else
{
// Device not found, show a message to the user
MainThread.BeginInvokeOnMainThread(() =>
{
Toast.MakeText(this, "Bluetooth device not found.", ToastLength.Long).Show();
});
}
}
private BluetoothDevice FindOBDIIBluetoothDevice(string deviceAddress)
{
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter;
if (bluetoothAdapter == null || !bluetoothAdapter.IsEnabled)
{
return null;
}
foreach (BluetoothDevice device in bluetoothAdapter.BondedDevices)
{
if (device.Address == deviceAddress)
{
return device;
}
}
// If not found among bonded devices, try discovering new devices
if (bluetoothAdapter.IsDiscovering)
{
bluetoothAdapter.CancelDiscovery();
}
bluetoothAdapter.StartDiscovery();
// Wait for discovery to complete (you might want to implement a BroadcastReceiver for better handling)
System.Threading.Thread.Sleep(5000); // Adjust this timeout as needed
foreach (BluetoothDevice device in bluetoothAdapter.BondedDevices)
{
if (device.Address == deviceAddress)
{
return device;
}
}
return null;
}
}
}
and my obdprotocol class
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace App6
{
public class ObdProtocol
{
/* public static double ParseRPM(string rpmdata)
{
// Find the RPM value in the response
int startIndex = rpmdata.IndexOf("010C") + 6;
string rpmHex = rpmdata.Substring(startIndex, 4);
// Convert the hex RPM value to decimal
int rpmDecimal = Convert.ToInt32(rpmHex, 16);
// Calculate the actual RPM value as per the specified formula
double actualRPM = rpmDecimal * 0.25; // Multiply by 0.25 to get RPM in its physical units
return actualRPM;
}*/
/* WOOORKS public static string ParseRPM(string rpmdata)
{
// Find the RPM value in the response
//changed from 010C to 410C int startIndex = rpmdata.IndexOf("410C") + 6;
string rpmHex = rpmdata.Substring(startIndex, 4);
return rpmHex;
}*/
public static string ParseRPM(string rpmdata)
{
// Find the RPM value in the response
//hanged from 010C to 410C
int startIndex = rpmdata.IndexOf("410C") + 6;
string rpmHex = rpmdata.Substring(startIndex, 4);
return rpmHex;
}
/* public static double ParseRealRPM(string realrpmdata)
{
// Find the RPM value in the response
int startIndex = realrpmdata.LastIndexOf("010C") + 4;
// Get the last 4 digits of the response
string rpmHex = realrpmdata.Substring(startIndex, 4);
// Convert the hexadecimal value to decimal
int rpmDecimal = Convert.ToInt32(rpmHex, 16);
// Divide the decimal value by 4
double realrpmValue = rpmDecimal / 4.0;
return realrpmValue;
}*/
public static int ParseSpeed(string speeddata)
{
// Example: assuming data is in the format "2131 7F", where 7F is speed
int startIndex = speeddata.IndexOf("410D") + 8;
string speedHex = speeddata.Substring(startIndex, 2);
return Convert.ToInt32(speedHex, 16);
}
}
}
The following is a typical string response from a CAN vehicle for Pid 0x0C rpm
"7E804410C0AF4".
This response shows that the command 410C has been sent to the Ecu of the vehicle and the Ecu has responded with the above string. The last two bytes contain the info required for the calculation of rpm. They are 0A F4.
Using the SAE convention we call them dataA and dataB, declared as int. The simplest way to handle them is the following. The 2-byte response is dataA = 000A and dataB = 00F4. Therefore
rpm = dataA << 8 | dataB.Now you have a numeric representation of the two bytes.
The scaling bit for Pid 0x0C is ¼ rpm per bit. So now divide by 4. Therefore, the rpm range is 0 – 16383.75rpm.
The easiest way to process Pid string responses is to have an event handler for each Pid. E.g.
Then when you are parsing the OBD string responses
Response from the same vehicle for Pid 0x0D speed 7E803410D19. dataA is 19. Single byte response. Scaling bit 1 km/h. Therefore 19km/h. Maximum value 255 km/h.
Processing OBD data is always hex string to numeric, so you need to use the most efficient techniques to handle the conversion. C# sharp has all the bit manipulation tools you need without resorting to things like Convert.ToInt32() etc.
Don't forget that my example responses are for CAN vehicles, totally different responses (data is the same but different headers) for the earlier protocols.