2024年2月27日 星期二
用 dart 語法, 針對 Uint8List 資料流(stream): 內容是 vt100 字串, 僅把 ESC[ ... m 移除並轉換成 utf8 字串
String utf8Log = "";
void stream2utf8(Uint8List data) {
final findLF = data.lastIndexWhere((e) => e == 0xa);// find position of the last \n
final endLF = findLF + 1; // to include LF
final len = remain.length + (findLF < 0 ? data.length : endLF);
final resemble = Uint8List(len);
if (remain.isNotEmpty) resemble.setAll(0, remain);// copy remain to front of resemble
resemble.setRange(remain.length, len, data); // append sublist of data to resemble
if (findLF < 0) { // keep in memory
remain = resemble;
} else { // line feed found, time to flush out
remain = Uint8List.sublistView(data, endLF);
Uint8List afterESC = Uint8List.sublistView(resemble);
while (afterESC.isNotEmpty) { // until empty
final findESC = afterESC.indexOf(0x1b); // find ESC position
utf8Log += utf8.decode(Uint8List.sublistView(afterESC, 0,
findESC < 0 ? afterESC.length : findESC
));
if (findESC < 0) break;
var lenESC = 1; // sequence ^[ ... m seek
if (afterESC[findESC + lenESC] == 0x5b) { // found sequence start '['
do {
lenESC ++; // one byte advance
if (afterESC[findESC + lenESC] == 0x6d) { // found end of character 'm'
lenESC ++;// one byte advance
break;
}
} while (findESC + lenESC < afterESC.length);
if (findESC + lenESC >= afterESC.length) { // todo: other ^[ sequence not yet implement!
afterESC[findESC] = 0x5E; // change ESC from 0x1b to '^' to prevent infinit loop
continue;// backoff to show this sequence
}
} else { // todo: other ^ sequence not yet implement!
afterESC[findESC] = 0x5E; // change ESC from 0x1b to '^' to prevent infinit loop
continue;// backoff to show this sequence
}
afterESC = Uint8List.sublistView(afterESC, findESC + lenESC);// ok, go ahead
}
setState((){}); // update screen if necessary
}
2024年2月19日 星期一
簡單的 flutter app 用來測試 usb serial for android
在 Android 系統上要使用 usb serial port 可以透過 usb-serial-for-android , 它是用 JAVA 語言寫的, 上官網打包先將它下載回來: https://github.com/mik3y/usb-serial-for-android, 將它解壓縮
unzip usb-serial-for-android-master.zip
1. 打開終端機用 flutter 命令產生一個新專案(例如 usbadc):
flutter create usbadc
2. 將上述 usb-serial-for-android 解開的檔案, 只要把目錄 usbSerialForAndroid/src/main/java/com 整個驅動程式的目錄複製到新專案: usbadc/android/app/src/main/java 內, 之後透過 kotin 程式碼呼叫它
3. 編輯 usbadc/android/app/src/main/AndroidManifest.xml 把以下綠色內容貼到標籤 <activity> </activity> 標籤內
<activity>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
4. 接著要產生一個 device_filter 檔案, 把它放到目錄 usbadc/android/app/src/main/res/xml 內:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 0x0403 / 0x60??: FTDI -->
<usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
<usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
<usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
<usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
<usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
<usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
<usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
<usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
<usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
<usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- CDC driver -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
<usb-device vendor-id="11914" product-id="10" /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
<usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources>
5. 編寫 android 端的主程式 usbadc/android/app/src/main/kotlin/com/example/usbadc/MainActivity.kt 如下:
package com.example.usbadc
import java.nio.ByteBuffer
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import androidx.annotation.NonNull
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.app.PendingIntent
import android.content.Intent
import android.hardware.usb.UsbManager
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.driver.UsbSerialPort
class MainActivity: FlutterActivity() {
private lateinit var usbManager: UsbManager
override fun onCreate(savedInstanceState: Bundle?) {// ← ↑ → ↓
super.onCreate(savedInstanceState)
usbManager = getSystemService(USB_SERVICE) as UsbManager
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.apply {
val eventHandler = object: EventChannel.StreamHandler {
override fun onCancel(args: Any?) { }
override fun onListen(args: Any?, sink: EventChannel.EventSink?) {
sink ?. apply {
val flutterSink = this
setupTTY(115200) ?. apply {
val usbSerialPort: UsbSerialPort = this
val packetSize: Int = usbSerialPort.readEndpoint.maxPacketSize
if (packetSize <= 0) return
flutterSink.success("Usb serial port is ready (packet Size = $packetSize).")
val usbBuffer: ByteBuffer = ByteBuffer.allocate(packetSize)
val usbPacket: ByteArray = usbBuffer.array()
var readLoop : Boolean = false
val mainHandler = object: Handler(Looper.getMainLooper()) { // to run ttyRead() in the message loop
override fun handleMessage(msg: Message) {// binding sink: EventChannel.EventSink
when(msg.what) {
0 ->{
flutterSink.endOfStream() // event channel close
looper.quit() // mainHandler quit
}
1 ->{ // read loop
val size = usbSerialPort.read(usbPacket, 1000) // read from usbSerialPort
if (size == packetSize) {
flutterSink.success(usbPacket) // send to flutter
} else if (size > 0) {// retrive part of data in usbPacket
val u8list = ByteArray(size)
System.arraycopy(usbPacket, 0, u8list, 0, size)
flutterSink.success(u8list) // send to flutter
}
sendEmptyMessage(1) // loopback to continue
}
2 ->{ // optional message handle for usbSerialPort write
if (msg.obj is String) {
val bytes: ByteArray = (msg.obj as String).toByteArray()
usbSerialPort.write(bytes, 1000)
}
if (!readLoop) {
readLoop = true// only send once
sendEmptyMessage(1) // trigger to begin read loop
}
}
else -> { }
}
}
}
val msg = mainHandler.obtainMessage().apply {
what = 2
obj = "hi\n"
}
mainHandler.sendMessage(msg)
}
}
}
}
EventChannel(dartExecutor.binaryMessenger, "com.example.ttyUSB").setStreamHandler(eventHandler)
}
}
private fun setupTTY(baudRate: Int): UsbSerialPort? {
val prob = UsbSerialProber.getDefaultProber()
for (device in usbManager.deviceList.values) { // scan all available devices
prob.probeDevice(device) ?. apply { // check only when driver is up
val driver = this
usbManager.openDevice(driver.device) ?. apply {// try to open usb connection
val usbConnection = this
for (port in driver.ports.indices) { // try all available ports
try {
driver.ports[port] . apply { // this is UsbSerialPort
open(usbConnection)
setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE) // set com: baudRate, n, 8, 1
}
return driver.ports[port] // finally UsbSerialPort is ready.
} catch (e: Exception) { }
}
}
}
}
return null
}
}
6. 編寫 flutter 端主程式 usbadc/lib/main.dart 如下
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) => MaterialApp(
title: "demo",// id, not title name!
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home : const MyHomePage(),
);
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String history = "Hello\n";// init history to show
int total = 0;
int count2Clear = 0;
@override void initState() {
super.initState();
const event = EventChannel('com.example.ttyUSB');
event.receiveBroadcastStream().listen((data) async {
if (count2Clear ++ == 20) {
count2Clear = 0;
history = "";
}
var txt = (data is Uint8List) ? String.fromCharCodes(data) : "$data";// convert data to String
txt = txt.replaceAll("\r","") ;// remove CR
txt = txt.replaceAll("\n",";") ;// change LF to ';'
history += "${++ total}: $txt\n";// history add line number and txt
setState(() {}) ;// update screen
});
}
@override Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("ttyUSB demo"),
actions: [PopupMenuButton<int>(
itemBuilder: (context) => List.generate(1, (idx) => PopupMenuItem(value: idx, child: const Text("關閉"))),
onSelected: (idx) => SystemNavigator.pop() // app finish
)]
),
body: SingleChildScrollView(scrollDirection: Axis.vertical, child: Text(history))
);
}
}
進入專案目錄, 編譯上傳並執行
cd usbadc && flutter run
2024年2月8日 星期四
linux 上解決核心模組 ch34x.ko 掛載 /dev/ttyUSB0 時被強制斷線的問題
參考討論文章: https://unix.stackexchange.com/questions/670636/unable-to-use-usb-dongle-based-on-usb-serial-converter-chip
只要有 root 權限執行以下腳本就能解決, 一勞永逸辦法是將它放在 /etc/rc.local 裡面, 一開機就執行
# ...
for f in /usr/lib/udev/rules.d/*brltty*.rules; do
sudo ln -s /dev/null "/etc/udev/rules.d/$(basename "$f")"
done
sudo udevadm control --reload-rules
Linux mint 玩 waydroid 一些心得
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 上 Firebase 網站 註冊啟用 Firebase : https://firebase.google.com/ 2. 進入 Firebase Console 註冊開啟新專案 : https://console.firebase.google.com/...
-
Flutter 讓人很容易短時間就能開發出 app, 關鍵在於他開發了很多種小部件稱為 Widget, 將Widget 組合起來就是一個 app. 所謂 部 件(Widget)就是一個可以呈現在螢幕上的視覺系 物 件,幾乎可以說 Flutter 每個物件都是 部 件. 開發者透...