由於最近應產品經理的需求,需要做一個Android版的上位機APP,為此專門到某寶上購買了一個Type-C轉串口的小設備,然後就開始折騰了。花了幾天的時間就把上位機APP做出來了,後來在空閒時間又做了一個串口調試的小工具,效果如下圖
創建項目
ionic start blank
創建一個空白項目
安裝串口插件
要做一個串口通訊的工具,那就得和硬件打交道,正好根據ionic官方文檔,我找到了一個串口通訊的插件,名為cordovarduino,經過嘗試之後,發現此插件由於久年失修,雖然可以使用,但是在收發數據的時候總是無法完整接收到數據。根據對其代碼查看,發現其中lib目錄下有一個usbseriallibrary.jar文件,這個應該就是USB串口的驅動文件了吧。
久年失修的插件,估計就是這個jar包有問題,應該更新一下這個jar包就可以了,因此,通過usb-serial-for-android這個項目的介紹我重新打包了一個jar包,完成後嘗試了一下,確實很完美,並且收發數據也沒有任何問題了。因此,自己根據cordovarduino項目重新copy了一個項目cordova-plugin-usbserialport,因此你只需要安裝我提供的插件即可
安裝串口插件
ionic cordova plugin add cordova-plugin-usbserialport
安裝本地數據存儲插件
ionic cordova plugin add cordova-plugin-nativestorage
npm install @ionic-native/native-storage
安裝狀態欄插件
ionic cordova plugin add cordova-plugin-statusbar
npm install @ionic-native/status-bar
安裝設備信息插件
ionic cordova plugin add cordova-plugin-device
npm install @ionic-native/device
安裝獲取版本號插件
ionic cordova plugin add cordova-plugin-app-version
npm install @ionic-native/app-version
安裝APP最小化插件
ionic cordova plugin add cordova-plugin-appminimize
npm install @ionic-native/app-minimize
安裝後台運行插件
ionic cordova plugin add cordova-plugin-background-mode
npm install @ionic-native/background-mode
串口操作主要代碼
declare let usbSerialPort: any; // 引入串口插件
// 打開串口
async openSerialPort() {
const config = await this.nativeStorage.getItem('config');
// First request permission
usbSerialPort.requestPermission(() => {
console.log('get permission success.');
usbSerialPort.getDevice(data => {
this.title = data.name;
});
// open serial
usbSerialPort.open(config, () => {
console.log('Serial connection opened');
// get open status
this.isOpen();
// read listener
usbSerialPort.readListener(data => {
clearTimeout(this.timer);
const view = new Uint8Array(data);
console.log(this.utils.bytes2HexString(view));
this.receiveDataArray.push(view);
this.timer = setTimeout(() => {
const now = new Date();
const dateMs = now.getMilliseconds();
this.zone.run(() => {
const date = `<span style="color: #2fdf75">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} > </span>`;
const resultUint8Array = this.utils.concatUint(Uint8Array, ...this.receiveDataArray);
if (!this.utils.bytes2HexString(resultUint8Array)) {
return;
}
this.receiveData += `
<div style="
-webkit-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;">
${date}${this.utils.strDivision(this.utils.bytes2HexString(resultUint8Array), 2)}
</div>
`;
this.receiveData += `<div style="margin-top:8px"></div>`;
this.receiveLength = this.utils.bytes2HexString(resultUint8Array).length / 2;
this.scrollToBottom();
});
}, 500);
}, err => {
console.log(`Read listener error: ${err}`);
});
});
}, err => {
console.log(`Get permission error: ${err}`);
if (this.openStatus) {
this.zone.run(() => {
this.openStatus = false;
this.title = this.translate.instant('SERIAL_DEVICE_TITLE');
});
}
this.presentToast(this.translate.instant('NO_DEVICE_CONNECTED'));
});
}
// 串口寫入
writerSerial() {
if (!this.openStatus) {
if (this.pack) {
this.presentAlert();
}
return;
}
this.receiveDataArray = [];
const now = new Date();
const dateMs = now.getMilliseconds();
if (this.isWriterHex) {
usbSerialPort.writeHex(this.pack, (res: any) => {
console.log('writer res: ', res);
const date = `<span style="color:#3880ff">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < </span>`;
this.receiveData += `<div>${date}${this.utils.strDivision(this.pack, 2)}</div>`;
this.sendLength = this.pack.length / 2;
}, err => {
console.log('writer hex err: ', err);
this.presentToast();
this.closeSerial();
});
} else {
usbSerialPort.write(this.pack, (res: any) => {
console.log('writer res: ', res);
const date = `<span style="color:#3880ff">${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < </span>`;
this.receiveData += `<div>
${date}${this.utils.strDivision(this.utils.bufToHex(this.utils.stringToBytes(this.pack)), 2)}
</div>`;
this.sendLength = this.utils.getStringByteLength(this.pack);
}, err => {
console.log('writer string err: ', err);
this.presentToast();
this.closeSerial();
});
}
}
// 串口開啓狀態
isOpen() {
usbSerialPort.isOpen(status => {
console.log(`Serial open status: ${status}`);
this.zone.run(() => {
this.openStatus = status;
});
});
}
// 關閉串口
closeSerial(isOpenSerial?: boolean) {
usbSerialPort.close(() => {
this.isOpen();
this.receiveDataArray = [];
if (isOpenSerial) {
this.openSerialPort();
}
});
}
其他
為了能夠對串口波特率進行設置,我還做了一個設置頁面,主要用於設置波特率、數據位、停止位、以及收發數據記錄的背景顏色切換、語言切換等功能。
重要代碼如下:
version: any = '';
config: any = {};
// eslint-disable-next-line @typescript-eslint/ban-types
configTemp: object = {};
// 顏色列表
colorList: any[] = [
'color-white',
'color-red',
'color-blue',
'color-cyan',
'color-yellow',
'color-green',
'color-black',
'color-cornsilk',
'color-darkviolet',
'color-gainsboro',
'color-maroon',
'color-pink',
];
lang: any;
constructor(
private appVersion: AppVersion,
private nativeStorage: NativeStorage,
private modalController: ModalController,
private translate: TranslateService,
private zone: NgZone
) { }
ionViewWillEnter() {
this.initBackgroundColor();
this.getVersion();
this.getSerialPortConfig();
this.getLanguage();
}
async initBackgroundColor() {
const backgroundClass = await this.nativeStorage.getItem('backgroundClass');
console.log('settings backagroun class', backgroundClass);
const activeClass = 'color-active';
this.colorList.forEach((item, index) => {
if (item === backgroundClass) {
console.log('have same');
this.zone.run(() => {
this.colorList[index] = `${item} ${activeClass}`;
});
}
});
console.log('color list', this.colorList);
}
/**
* get App version
*
* @memberof SettingsPage
*/
async getVersion() {
this.version = await this.appVersion.getVersionNumber();
}
/**
* Get serial port config
*
* @memberof SettingsPage
*/
async getSerialPortConfig() {
this.config = await this.nativeStorage.getItem('config');
this.configTemp = Object.assign({}, this.config);
console.log('config', this.config);
}
async setSerialPortConfig() {
await this.nativeStorage.setItem('config', this.config);
const configIsCHange = JSON.stringify(this.configTemp) !== JSON.stringify(this.config);
this.modalController.dismiss({ configIsChange: configIsCHange });
}
async setBackgroundColor(className: string) {
await this.nativeStorage.setItem('backgroundClass', className);
this.modalController.dismiss();
}
async getLanguage() {
this.lang = await this.nativeStorage.getItem('locale');
}
async setLanguage() {
await this.nativeStorage.setItem('locale', this.lang);
this.translate.setDefaultLang(this.lang);
this.translate.use(this.lang);
}
總結
Cordova確實太老了,感覺都快已經被Apache拋棄了,Cordova的更新速度也很慢,就連目前的ionic都開發了自己的混合框架capacitor,而且也兼容Cordova插件,只不過面對react-native以及flutter來説,ionic目前處於一個比較尷尬的場面,因為react-native與flutter從性能上都可以碾壓ionic,不過ionic的優點就是打包後apk佔用空間是極小的。
不論如何,ionic相對於react-native和flutter來説,可以讓前端開發人員快速上手,並且快速開發與發佈應用,其中坑較少,學習成本低,再加上如今的ionic已經完全從一個依賴於Cordova的移動端框架轉變為了UI框架,你可以使用angular、vue、react甚至是原生JavaScript進行快速開發。
不過capacitor的插件目前少之又少,而Cordova的插件雖然多,但是太舊很多插件更新速度太慢,大家就抱着學習的態度去使用就可以了,當然如果選擇ionic來作為生產力框架的話也沒多大問題。
項目地址
APP項目 https://github.com/king2088/i...
cordova串口插件項目 https://github.com/king2088/c...