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
簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶
#include "stdio.h" // 五行: // 木2 // 水1 火3 // 金0 土4 // // 先天八卦 vs 五行 // ...
-
市面上便宜的時鐘, 內部機芯大多使用晶體振盪器產生固定頻率的脈沖去驅動 Lavet-type 步進馬達, 它只要一個 1.5V 電池就足以推動, 而馬達扭力主要是靠線圈內的電流產生磁力所致, 因此電流才是動力來源. 電壓太高會造成線圈電流飽和導致發熱,可能影響震盪頻率甚至燒毀...
-
參考資料: https://developer.android.com/training/connect-devices-wirelessly/nsd linux mint 上針對區域網路的名稱服務協定可以使用 avahi, 透過 avahi-browse -a -r 就能...