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