elm327の研究 その22

# 概要
elm327の研究やってみた。
練習問題やってみた。

# 練習問題
c#で、ELM327のエミュレータを書け。

# 参考にしたページ

https://github.com/jglim/ExpressOBD


# 写真

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/fd90616d-48c2-4e09-af64-a9488b391e9f.png)


# サンプルコード

```

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Resources;

namespace ExpressOBD
{
	public class ELM327 {
		public bool IsOpen = true;
		public int BytesToRead = 2;
		public string res = "";
		public void Open() {
		}
		public void Close() {
		}
		public string Read() {
			if (BytesToRead > 1)
			{
				BytesToRead = 1;
				return res;
			}
			else
			{
				BytesToRead = 0;
				return ">";
			}
		}
		public void Write(string a) {
			//Console.WriteLine(a);
			string[] b = a.Split(new string[] {
				"\r"
			}, StringSplitOptions.None);
			res = "";
			int len = b[0].Length;
			if (len < 2)
			{
				return;
			}
			if (b[0].Substring(0, 2) == "AT")
			{
				switch (b[0])
				{
				case "ATWS":
				case "ATE0":
				case "ATL0":
				case "ATH1":
				case "ATSP0":
				break;
				case "AT@1":
					res += "OBDII to RS232 Interpreter";
				break;
				case "ATRV":
					res += "12.8V";
				break;
				case "ATZ":
				case "ATI":
					res += "ELM327 v1.4";
				break;
				case "ATDP":
					res += "AUTO, ISO 9141-2";
				break;
				default:
					res += " NO COMMAND\n";
				break;
				}
			}
			else
			{
				switch (b[0])
				{
				case "01 00":
					res += "41 00 FF FF FC FF";
				break;
				case "01 01":
					res += "41 01 84 07 61 00";
				break;
				case "01 20":
					res += "41 20 FF FF FC FF";
				break;
				case "01 40":
					res += "41 40 FF FF FC FF";
				break;
				case "01 60":
					res += "41 60 FF FF FC FF";
				break;
				case "01 80":
					res += "41 80 FF FF FC FF";
				break;
				case "01 A0":
					res += "41 A0 FF FF FC FF";
				break;
				case "01 C0":
					res += "41 C0 FF FF FC FF";
				break;
				case "04 ":
					res += "44";
				break;
				default:
					res += b[0] + " NO DATA\n";
				break;
				}
			}
			BytesToRead = 2;
			//Console.WriteLine(res);
			return;
		}
	}
	public partial class MainForm: Form {
		private ELM327 sp;
		private GroupBox gbConnection;
		private Button btnConnect;
		private Label descRate;
		private Label descPort;
		private ComboBox cbBaud;
		private ComboBox cbPort;
		private GroupBox gbOBDActions;
		private Button btnResetErrors;
		private Label descReset;
		private GroupBox gbStatus;
		private Label label1;
		private ListView lvLog;
		private ColumnHeader chLog;
		private ImageList ilListviewImages;
		private int numberOfPromptsSinceInit = 0;
		private bool ecuIsAvailable = false;
		private bool serialPortsAreAvailable = true;
		private bool connectingInProgress = false;
		StringBuilder serialBuffer = new StringBuilder();
		private IContainer components = null;
		public MainForm() {
			this.components = new Container();
			ListViewItem listViewItem2 = new ListViewItem("ExpressOBD Initializing..", 2);
			ResXResourceSet resources = new ResXResourceSet("MainForm.resx");
			this.gbConnection = new GroupBox();
			this.label1 = new Label();
			this.btnConnect = new Button();
			this.descRate = new Label();
			this.descPort = new Label();
			this.cbBaud = new ComboBox();
			this.cbPort = new ComboBox();
			this.gbOBDActions = new GroupBox();
			this.btnResetErrors = new Button();
			this.descReset = new Label();
			this.gbStatus = new GroupBox();
			this.lvLog = new ListView();
			this.chLog = ((ColumnHeader)(new ColumnHeader()));
			this.ilListviewImages = new ImageList(this.components);
			this.gbConnection.SuspendLayout();
			this.gbOBDActions.SuspendLayout();
			this.gbStatus.SuspendLayout();
			this.SuspendLayout();
			this.gbConnection.Controls.Add(this.label1);
			this.gbConnection.Controls.Add(this.btnConnect);
			this.gbConnection.Controls.Add(this.descRate);
			this.gbConnection.Controls.Add(this.descPort);
			this.gbConnection.Controls.Add(this.cbBaud);
			this.gbConnection.Controls.Add(this.cbPort);
			this.gbConnection.Font = new Font("Segoe UI Light", 15.75F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.gbConnection.Location = new Point(12, 12);
			this.gbConnection.Name = "gbConnection";
			this.gbConnection.Padding = new Padding(15);
			this.gbConnection.Size = new Size(300, 241);
			this.gbConnection.TabIndex = 0;
			this.gbConnection.TabStop = false;
			this.gbConnection.Text = "Connection";
			this.label1.AutoSize = true;
			this.label1.Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.label1.Location = new Point(15, 122);
			this.label1.Name = "label1";
			this.label1.Size = new Size(246, 51);
			this.label1.TabIndex = 5;
			this.label1.Text = "To identify the correct port,\r\nDisconnect device && note available ports\r\nReconne" + "ct device && select the new entry";
			this.btnConnect.Enabled = true;
			this.btnConnect.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.btnConnect.Location = new Point(18, 186);
			this.btnConnect.Name = "btnConnect";
			this.btnConnect.Size = new Size(264, 37);
			this.btnConnect.TabIndex = 4;
			this.btnConnect.Text = "Connect";
			this.btnConnect.UseVisualStyleBackColor = true;
			this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
			this.descRate.AutoSize = true;
			this.descRate.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.descRate.Location = new Point(18, 83);
			this.descRate.Name = "descRate";
			this.descRate.Size = new Size(80, 21);
			this.descRate.TabIndex = 3;
			this.descRate.Text = "Baud Rate";
			this.descPort.AutoSize = true;
			this.descPort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.descPort.Location = new Point(18, 48);
			this.descPort.Name = "descPort";
			this.descPort.Size = new Size(85, 21);
			this.descPort.TabIndex = 2;
			this.descPort.Text = "Port Name";
			this.cbBaud.DropDownStyle = ComboBoxStyle.DropDownList;
			this.cbBaud.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.cbBaud.FormattingEnabled = true;
			this.cbBaud.Items.AddRange(new object[] {
				"9600",
				"19200",
				"38400",
				"57600",
				"115200"
			});
			this.cbBaud.Location = new Point(129, 80);
			this.cbBaud.Name = "cbBaud";
			this.cbBaud.Size = new Size(153, 29);
			this.cbBaud.TabIndex = 1;
			this.cbPort.DropDownStyle = ComboBoxStyle.DropDownList;
			this.cbPort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.cbPort.FormattingEnabled = true;
			this.cbPort.Location = new Point(129, 45);
			this.cbPort.Name = "cbPort";
			this.cbPort.Size = new Size(153, 29);
			this.cbPort.TabIndex = 0;
			this.gbOBDActions.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left)));
			this.gbOBDActions.Controls.Add(this.btnResetErrors);
			this.gbOBDActions.Controls.Add(this.descReset);
			this.gbOBDActions.Font = new Font("Segoe UI Light", 15.75F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.gbOBDActions.Location = new Point(12, 264);
			this.gbOBDActions.Name = "gbOBDActions";
			this.gbOBDActions.Padding = new Padding(15);
			this.gbOBDActions.Size = new Size(300, 250);
			this.gbOBDActions.TabIndex = 5;
			this.gbOBDActions.TabStop = false;
			this.gbOBDActions.Text = "OBD Actions";
			this.btnResetErrors.Enabled = false;
			this.btnResetErrors.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.btnResetErrors.Location = new Point(18, 195);
			this.btnResetErrors.Name = "btnResetErrors";
			this.btnResetErrors.Size = new Size(264, 37);
			this.btnResetErrors.TabIndex = 4;
			this.btnResetErrors.Text = "Reset Errors";
			this.btnResetErrors.UseVisualStyleBackColor = true;
			this.btnResetErrors.Click += new System.EventHandler(this.btnResetErrors_Click);
			this.descReset.AutoSize = true;
			this.descReset.Font = new Font("Segoe UI", 9.75F);
			this.descReset.Location = new Point(18, 43);
			this.descReset.Name = "descReset";
			this.descReset.Size = new Size(259, 136);
			this.descReset.TabIndex = 2;
			this.descReset.Text = "Reset Errors with 04 Command\r\n\r\nAvailable after connecting\r\n\r\nClear MIL (\"Check E" + "ngine Light\")\r\nErase Diagnostic Trouble Codes\r\nErase Freeze Frame Data\r\nErase Ox" + "ygen Test Data, Mode 06, 07 Data";
			this.gbStatus.Anchor = ((AnchorStyles)((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right)));
			this.gbStatus.Controls.Add(this.lvLog);
			this.gbStatus.Font = new Font("Segoe UI Light", 15.75F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.gbStatus.Location = new Point(318, 12);
			this.gbStatus.Name = "gbStatus";
			this.gbStatus.Padding = new Padding(15);
			this.gbStatus.Size = new Size(604, 502);
			this.gbStatus.TabIndex = 6;
			this.gbStatus.TabStop = false;
			this.gbStatus.Text = "Status";
			this.lvLog.Anchor = ((AnchorStyles) ((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right)));
			this.lvLog.Columns.AddRange(new ColumnHeader[] {
				this.chLog
			});
			this.lvLog.Font = new Font("Segoe UI Semibold", 9.75F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(0)));
			this.lvLog.HeaderStyle = ColumnHeaderStyle.Nonclickable;
			this.lvLog.Items.AddRange(new ListViewItem[] {
				listViewItem2
			});
			this.lvLog.Location = new Point(18, 46);
			this.lvLog.Name = "lvLog";
			this.lvLog.Size = new Size(570, 438);
			this.lvLog.SmallImageList = this.ilListviewImages;
			this.lvLog.TabIndex = 1;
			this.lvLog.UseCompatibleStateImageBehavior = false;
			this.lvLog.View = View.Details;
			this.chLog.Text = "Log Details";
			this.chLog.Width = 570;
			this.ilListviewImages.ImageStream = ((ImageListStreamer) (resources.GetObject("ilListviewImages.ImageStream")));
			this.ilListviewImages.TransparentColor = Color.Transparent;
			this.ilListviewImages.Images.SetKeyName(0, "accept.png");
			this.ilListviewImages.Images.SetKeyName(1, "error.png");
			this.ilListviewImages.Images.SetKeyName(2, "information.png");
			this.ilListviewImages.Images.SetKeyName(3, "delete.png");
			this.ilListviewImages.Images.SetKeyName(4, "application_xp_terminal.png");
			this.AutoScaleDimensions = new SizeF(6F, 13F);
			this.AutoScaleMode = AutoScaleMode.Font;
			this.ClientSize = new Size(934, 526);
			this.Controls.Add(this.gbStatus);
			this.Controls.Add(this.gbOBDActions);
			this.Controls.Add(this.gbConnection);
			this.Font = new Font("Segoe UI", 8.25F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
			this.Name = "MainForm";
			this.StartPosition = FormStartPosition.CenterScreen;
			this.Text = "ExpressOBD";
			this.FormClosing += new FormClosingEventHandler(this.MainForm_FormClosing);
			this.Load += new System.EventHandler(this.MainForm_Load);
			this.gbConnection.ResumeLayout(false);
			this.gbConnection.PerformLayout();
			this.gbOBDActions.ResumeLayout(false);
			this.gbOBDActions.PerformLayout();
			this.gbStatus.ResumeLayout(false);
			this.ResumeLayout(false);
		}
		private void MainForm_Load(object sender, EventArgs e) {
			lvLog.Items.Clear();
			cbBaud.SelectedIndex = 2;
			LogCustom("ExpressOBD 1.0 [github.com/jglim]. Icons: [famfamfam.com/lab/icons/silk]", 4);
		}
		protected override void Dispose(bool disposing) {
			if (disposing && (components != null))
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}
		private void btnConnect_Click(object sender, EventArgs e) {
			connectingInProgress = true;
			sp = new ELM327();
			sp.Open();
			numberOfPromptsSinceInit = 0;
			serialBuffer.Clear();
			sp.Write("ATWS\r");
			tmrSerialEvent();
			SetupFormForPostConnect();
		}
		private void SetupFormForPreConnect() {
			cbBaud.Enabled = true;
			cbPort.Enabled = true;
			btnConnect.Enabled = serialPortsAreAvailable;
			btnResetErrors.Enabled = false;
		}
		private void SetupFormForPostConnect() {
			cbBaud.Enabled = false;
			cbPort.Enabled = false;
			btnConnect.Enabled = false;
			btnResetErrors.Enabled = ecuIsAvailable;
		}
		private void tmrSerialEvent() {
			while (sp.IsOpen && sp.BytesToRead > 0)
			{
				string inString = sp.Read();
				if (inString == ">")
				{
					numberOfPromptsSinceInit++;
					Console.WriteLine("PC: {0}, Value: {1}", numberOfPromptsSinceInit, serialBuffer.ToString().Replace("\r", ""));
					if (numberOfPromptsSinceInit == 1)
					{
						sp.Write("ATZ\r");
						tmrSerialEvent();
					}
					else if (numberOfPromptsSinceInit == 2)
					{
						if (serialBuffer.ToString().Contains("ELM327"))
						{
							sp.Write("ATE0\r");
						}
					}
					else if (numberOfPromptsSinceInit == 3)
					{
						sp.Write("ATL0\r");
					}
					else if (numberOfPromptsSinceInit == 4)
					{
						sp.Write("ATH1\r");
					}
					else if (numberOfPromptsSinceInit == 5)
					{
						sp.Write("ATSP0\r");
					}
					else if (numberOfPromptsSinceInit == 6)
					{
						sp.Write("ATI\r");
					}
					else if (numberOfPromptsSinceInit == 7)
					{
						if (serialBuffer.ToString().Contains("ELM327"))
						{
							Log("Connected to " + serialBuffer.ToString());
							sp.Write("AT@1\r");
						}
						else
						{
							LogError("No compatible ELM327 device available on " + cbPort.Text);
							DisconnectAndTidyUp();
						}
					}
					else if (numberOfPromptsSinceInit == 8)
					{
						Log("Device Description: " + serialBuffer.ToString());
						sp.Write("ATRV\r");
					}
					else if (numberOfPromptsSinceInit == 9)
					{
						Log("Vehicle Voltage: " + serialBuffer.ToString());
						sp.Write("01 00\r");
					}
					else if (numberOfPromptsSinceInit == 10)
					{
						if (serialBuffer.ToString().Contains("UNABLE TO CONNECT"))
						{
							LogError("ECU not detected! Check OBD2 connection, or if vehicle is switched ON");
							DisconnectAndTidyUp();
						}
						else
						{
							Log("ECU Ready for OBD actions");
							ecuIsAvailable = true;
							sp.Write("ATDP\r");
						}
					}
					else if (numberOfPromptsSinceInit == 11)
					{
						Log("Communicating with ECU via " + serialBuffer.ToString());
						sp.Write("01 01\r");
					}
					else if (numberOfPromptsSinceInit == 12)
					{
						string milData = serialBuffer.ToString();
						milData = milData.Replace("\r", " ").Trim();
						string[] milDataSegments = milData.Split(' ');
						if (milDataSegments.Length != 6)
						{
							LogWarn("MIL Data could not be parsed");
						}
						else
						{
							byte milByte = byte.Parse(milDataSegments[2], System.Globalization.NumberStyles.HexNumber);
							bool milIsActive = ((milByte & 0x80) > 0);
							int milCount = (milByte & 0x7F);
							if (milIsActive)
							{
								LogWarn("MIL (Check Engine Light) is ON. Number of Errors: " + milCount.ToString());
							}
							else
							{
								LogSuccess("MIL (Check Engine Light) is OFF");
							}
						}
						sp.Write("01 01\r");
					}
					else if (numberOfPromptsSinceInit > 99)
					{
						LogSuccess("Erase acknowledged by ECU");
						LogWarn("Restart vehicle to reflect changes");
						DisconnectAndTidyUp();
					}
					serialBuffer.Clear();
				}
				else
				{
					serialBuffer.Append(inString);
				}
			}
		}
		private void btnResetErrors_Click(object sender, EventArgs e) {
			if (MessageBox.Show("This action will: \r\n\r\n\r\n" + "Reset Trouble Code Count and MIL (Check Engine Light)\r\n\r\n" +
				"Erase Diagnostic Trouble Codes, Freeze Frame Data(and associated DTCs), Oxygen Test Data, Mode 06, 07 Data\r\n\r\n" +
				"Mode 0A(Permanent Trouble Codes) are unaffected and can only be reset by ECU\r\n\r\n\r\n" +
				"Would you like to continue?", "Confirm Reset", MessageBoxButtons.OKCancel, MessageBoxIcon.Information) == DialogResult.OK)
			{
				numberOfPromptsSinceInit = 99;
				sp.Write("04 \r");
				tmrSerialEvent();
			}
		}
		private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
			if (sp != null)
			{
				if (sp.IsOpen)
				{
					sp.Close();
				}
			}
		}
		private void DisconnectAndTidyUp() {
			sp.Close();
			serialBuffer.Clear();
			connectingInProgress = false;
			ecuIsAvailable = false;
			Log("Connection closed automatically");
			SetupFormForPreConnect();
		}
		private void Log(string Message) {
			lvLog.Items.Add(new ListViewItem(Message, 2));
		}
		private void LogWarn(string Message) {
			lvLog.Items.Add(new ListViewItem(Message, 1));
		}
		private void LogSuccess(string Message) {
			lvLog.Items.Add(new ListViewItem(Message, 0));
		}
		private void LogCustom(string Message, int ImageIndex) {
			lvLog.Items.Add(new ListViewItem(Message, ImageIndex));
		}
		private void LogError(string Message) {
			lvLog.Items.Add(new ListViewItem(Message, 3));
		}
		[STAThread]
		static void Main() {
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			Application.Run(new MainForm());
		}
	}
}





```

以上。