Xamarin.Forms-手机串口调试程序开发文档
Xamarin.Forms 手机串口调试程序开发文档
1.开发背景:
因工作性质特殊,需要通过手持设备与电力设备进行报文通讯,达到设备状态、地址码等数据的下发及查询功能。但因为后期手持设备厂家停产,维护不及时,造成设备稀缺,无法满足正常工作需要,特制作此手机APP,通过串口驱动连接串口转红外设备,使之发送与接收来自终端的报文信息,达到数据交互的目的,以下是软件主界面:
2.开发框架:
开发工具:VisualStudio2022,框架采用Maui框架进行开发。
3.开发所用设备及接口:
开发过程中,引用Android.UsbSerial 程序集;设备采用Type_C 转USB转换器与USB红外设备进行连接,根据设备类型在程序集中匹配合适驱动,通过报文的编码逻辑组织报文,以达到数据发送和接收的目的。
4.界面功能设计:
界面共设计两个,第一个是设备搜寻页面及安全提示内容,插入串口设备后,如果驱动合适,则会显示设备的详细信息,包括设备型号、驱动信息、生产厂家等。如果驱动不合适,则不会显示设备的详细信息等内容。
第二个页面则是数据功能页面,包括端口号、波特率、起止位、校验位的设置,以及相关的操作选项设置等。通过设置相关的参数及功能,将报文进行重组,发送报文到终端设备,终端设备根据报文的内容进行动作。
5.主要技术问题:
本软件开发过程中遇到的主要问题,还是对串口驱动的应用知识方面了解太少;第一次做Maui方面的开发,不太熟练;手机APP相关软件的开发涉及的项目太少,经验不足。
6.部分代码:
using Android.Icu.Text;
using Android.Telephony.Euicc;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Hoho.Android.UsbSerial.Driver;
using MauiUsbSerialForAndroid.Converter;
using MauiUsbSerialForAndroid.Helper;
using MauiUsbSerialForAndroid.Model;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;
namespace MauiUsbSerialForAndroid.ViewModel
{
[ObservableObject]
public partial class SerialDataViewModel : IQueryAttributable
{
[ObservableProperty]
bool isOpen = false;
public UsbDeviceInfo DeviceInfo { get; set; }
public string[] AllEncoding { get; } = new string[] { "HEX", "ASCII", "UTF-8", "GBK", "GB2312", "Unicode" };
public int[] AllBaudRate { get; } = new[] { 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 43000, 57600, 76800, 115200, 128000, 230400, 256000, 460800, 921600, 1382400 };
public int[] AllDataBits { get; } = new[] { 5, 6, 7, 8 };
public string[] AllParity { get; } = Enum.GetNames(typeof(Parity));
public string[] AllStopBits { get; } = Enum.GetNames(typeof(StopBits));
[ObservableProperty]
string encodingSend = "HEX";
[ObservableProperty]
string encodingReceive = "HEX";
[ObservableProperty]
int intervalReceive = 50;
[ObservableProperty]
int intervalSend = 1000;
[ObservableProperty]
bool cycleToSend = false;
[ObservableProperty]
bool showTimeStamp = true;
[ObservableProperty]
bool autoScroll = true;
[ObservableProperty]
string sendData = "";
[ObservableProperty]
SerialOption serialOption = new SerialOption();
[ObservableProperty]
string softName = "";
// 终端地址;
[ObservableProperty]
string terminalAddr = "20785593";
//终端操作;
[ObservableProperty]
string terminalOperation = "";
//测量点操作;
[ObservableProperty]
string pointOperation = "";
//测量点编号;
[ObservableProperty]
string pointIndex = "";
//终端操作;
[ObservableProperty]
bool terminalChecked = true;
//测量点操作;
[ObservableProperty]
bool pointChecked;
//信息操作;
[ObservableProperty]
bool msgChecked;
public ObservableCollection<SerialLog> Datas { get; } = new();
System.Timers.Timer timerSend;
[ObservableProperty]
string receivedText;
public SerialDataViewModel()
{
SerialPortHelper.WhenDataReceived().Subscribe(data =>
{
ReceivedText = SerialPortHelper.GetData(data, EncodingReceive);
//Shell.Current.DisplayAlert("TooFast", ReceivedText, "ok");
if (receivedText.Length > 0)
{
AddLog(new SerialLog($"{TerminalAddr}: {TerminalOperation} 成功", false));
}
else
{
AddLog(new SerialLog($"{TerminalAddr}: {TerminalOperation} 失败", false));
}
//AddLog(new SerialLog($"终端地址:{convertedText}", false));
//AddLog(new SerialLog($"终端地址:{convertedText}", false));
});
timerSend = new System.Timers.Timer(intervalSend);
timerSend.Elapsed += TimerSend_Elapsed;
timerSend.Enabled = false;
SerialPortHelper.WhenUsbDeviceAttached((usbDevice) =>
{
if (usbDevice.DeviceId == DeviceInfo.Device.DeviceId)
{
AddLog(new SerialLog("Usb device attached", false));
Open();
}
});
SerialPortHelper.WhenUsbDeviceDetached((usbDevice) =>
{
if (usbDevice.DeviceId == DeviceInfo.Device.DeviceId)
{
AddLog(new SerialLog("Usb device detached", false));
Close();
}
});
}
private void TimerSend_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Send();
}
partial void OnIntervalReceiveChanged(int value)
{
SerialPortHelper.IntervalChange(value);
}
async partial void OnIntervalSendChanged(int value)
{
if (value < 5)
{
await Shell.Current.DisplayAlert("TooFast", "Set at least 5 milliseconds", "ok");
IntervalSend = 5;
}
timerSend.Interval = IntervalSend;
}
partial void OnCycleToSendChanged(bool value)
{
timerSend.Enabled = value;
}
Regex regHexRemove = new Regex("[^a-fA-F0-9 ]");
partial void OnSendDataChanged(string value)
{
if (EncodingSend == "HEX")
{
string temp = regHexRemove.Replace(value, "");
if (SendData != temp)
{
Shell.Current.DisplayAlert("Only hex", "Only HEX characters can be entered", "Ok");
SendData = temp;
}
}
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.ContainsKey("Serial"))
{
DeviceInfo = (UsbDeviceInfo)query["Serial"];
Open();
}
}
[RelayCommand]
public void Toggle()
{
if (IsOpen)
{
Close();
}
else
{
Open();
}
}
[RelayCommand]
public async void Open()
{
if (!IsOpen)
{
string r = await SerialPortHelper.RequestPermissionAsync(DeviceInfo);
if (SerialPortHelper.CheckError(r, showDialog: false))
{
r = SerialPortHelper.Open(DeviceInfo, SerialOption);
if (SerialPortHelper.CheckError(r, showDialog: false))
{
IsOpen = true;
}
else
{
AddLog(new SerialLog(r, false));
}
}
else
{
AddLog(new SerialLog(r, false));
}
}
}
[RelayCommand]
public void Close()
{
try
{
SerialPortHelper.Close();
CycleToSend = false;
IsOpen = false;
}
catch (Exception)
{
}
}
[RelayCommand]
public void Clear()
{
Datas.Clear();
}
/// <summary>
/// 发送信息,先判断是何种操作;
/// </summary>
[RelayCommand]
public void Send()
{
if (SoftName.Length == 0)
{
Shell.Current.DisplayAlert("Choose right softname", "请选择合适的操作软件!", "Ok");
}
else
{
if ("二层集抄".Equals(SoftName))
{
EcjcConverter ecjc = new EcjcConverter();
if (terminalChecked is true) //如果是终端操作;
{
if (TerminalOperation.Equals("读终端地址"))
{
SendData = ecjc.ReadAddressMethod(TerminalAddr);
}
else if ("写终端地址".Equals(TerminalOperation))
{
SendData=ecjc.WriteAddressMethod(TerminalAddr);
}
else if ("硬件初始化".Equals(TerminalOperation))
{
SendData = ecjc.HardwareFormatingMethod(TerminalAddr);
}
else if ("数据区初始化".Equals(TerminalOperation))
{
SendData = ecjc.DataAreaFormatingMethod(TerminalAddr);
}
}
}
// 不是二层集抄
else if ("GW376".Equals(SoftName))
{
Gw376Converter gw376 = new Gw376Converter();
if (terminalChecked is true) //如果是终端操作;
{
if (TerminalOperation.Equals("读终端地址"))
{
SendData=gw376.ReadAddressMethod(TerminalAddr);
}
else if ("写终端地址".Equals(TerminalOperation))
{
SendData=gw376.WriteAddressMethod(this.terminalAddr);
}
else if ("硬件初始化".Equals(TerminalOperation))
{
SendData = gw376.HardwareFormatingMethod(TerminalAddr);
}
else if ("数据区初始化".Equals(TerminalOperation))
{
SendData=gw376.DataAreaFormatingMethod(TerminalAddr);
}
}
}
byte[] send = SerialPortHelper.GetBytes(SendData, EncodingSend);
if (send.Length == 0)
{
return;
}
string s = SerialPortHelper.Write(send);
if (SerialPortHelper.CheckError(s))
{
if (EncodingSend == "HEX")
{
//AddLog(new SerialLog(SendData.ToUpper(), true));
AddLog(new SerialLog($"{terminalOperation}:{TerminalAddr}", true));
}
else
{
AddLog(new SerialLog(SendData, true));
}
}
else
{
AddLog(new SerialLog(s, true));
}
}
}
[RelayCommand]
public async void Back()
{
Close();
await Shell.Current.GoToAsync("..");
}
void AddLog(SerialLog serialLog)
{
Datas.Add(serialLog);
//fix VirtualView cannot be null here
Task.Delay(50);
}
[RelayCommand]
void SerialOptionChange()
{
SerialPortHelper.SetOption(serialOption);
}
}
}
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MauiUsbSerialForAndroid.Helper;
using System.Collections.ObjectModel;
using Hoho.Android.UsbSerial.Driver;
using Android.Hardware.Usb;
using CommunityToolkit.Mvvm.Input;
using MauiUsbSerialForAndroid.Model;
using MauiUsbSerialForAndroid.View;
using Android.OS;
using Android.Content;
using Android.Util;
namespace MauiUsbSerialForAndroid.ViewModel
{
[ObservableObject]
public partial class SerialPortViewModel : IQueryAttributable
{
bool openIng = false;
public ObservableCollection<UsbDeviceInfo> UsbDevices { get; } = new();
public SerialPortViewModel()
{
SerialPortHelper.WhenUsbDeviceAttached((usbDevice) =>
{
GetUsbDevices();
});
SerialPortHelper.WhenUsbDeviceDetached((usbDevice) =>
{
GetUsbDevices();
});
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
GetUsbDevices();
}
[RelayCommand]
public async Task GetUsbDevices()
{
UsbDevices.Clear();
var list = SerialPortHelper.GetUsbDevices();
foreach (var item in list)
{
UsbDevices.Add(item);
//fix VirtualView cannot be null here
await Task.Delay(50);
}
}
[RelayCommand]
async Task Open(UsbDeviceInfo usbDeviceInfo)
{
if (openIng) { return; }
openIng = true;
await Shell.Current.GoToAsync(nameof(SerialDataPage), new Dictionary<string, object> {
{ "Serial",usbDeviceInfo}
});
openIng = false;
}
}
}
以上代码,仅作参考,QQ:1009714648