我用ionic撸了一个USB转串口的调试工具

由于最近应产品经理的需求,需要做一个Android版的上位机APP,为此专门到某宝上购买了一个Type-C转串口的小设备,然后就开始折腾了。花了几天的时间就把上位机APP做出来了,后来在空闲时间又做了一个串口调试的小工具,效果如下图
我用ionic撸了一个USB转串口的调试工具
文章图片

创建项目

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 = `${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} > `; const resultUint8Array = this.utils.concatUint(Uint8Array, ...this.receiveDataArray); if (!this.utils.bytes2HexString(resultUint8Array)) { return; } this.receiveData += `${date}${this.utils.strDivision(this.utils.bytes2HexString(resultUint8Array), 2)}`; this.receiveData += ``; 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 = `${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < `; this.receiveData += `${date}${this.utils.strDivision(this.pack, 2)}`; 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 = `${this.utils.formatDate(now, 'hh:mm:ss')}.${dateMs} < `; this.receiveData += ` ${date}${this.utils.strDivision(this.utils.bufToHex(this.utils.stringToBytes(this.pack)), 2)} `; 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来作为生产力框架的话也没多大问题。
项目地址 【我用ionic撸了一个USB转串口的调试工具】APP项目 https://github.com/king2088/i...
cordova串口插件项目 https://github.com/king2088/c...

    推荐阅读