Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
J
jensen
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Skye Yu
jensen
Commits
1e9d5afd
Commit
1e9d5afd
authored
Jan 08, 2026
by
martin hou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 增加后台任务实现与模拟
parent
f5d1ac58
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
841 additions
and
689 deletions
+841
-689
README.md
README.md
+458
-540
mgr.tsx
src/mgr.tsx
+8
-8
mgr.ts
src/utils/mgr.ts
+6
-131
task-impl.ts
src/utils/task-impl.ts
+234
-0
tasks.ts
src/utils/tasks.ts
+135
-10
No files found.
README.md
View file @
1e9d5afd
# Jensen
- WebUSB Helper
# Jensen
设备管理开发指南
一个基于WebUSB API的硬件设备通信库,支持设备连接、文件管理、蓝牙扫描等功能
。
本文档介绍如何使用 Jensen 库进行 HiDock 设备的连接管理、任务调度和状态监控
。
## 📋 目录
## 📋 目录
-
[
功能特性
](
#功能特性
)
-
[
核心概念
](
#核心概念
)
-
[
安装
](
#安装
)
-
[
快速开始
](
#快速开始
)
-
[
快速开始
](
#快速开始
)
-
[
API 文档
](
#api-文档
)
-
[
设备连接管理
](
#设备连接管理
)
-
[
使用案例
](
#使用案例
)
-
[
任务系统
](
#任务系统
)
-
[
第三方项目导入
](
#第三方项目导入
)
-
[
设备状态管理
](
#设备状态管理
)
-
[
版本依赖
](
#版本依赖
)
-
[
完整示例
](
#完整示例
)
-
[
开发指南
](
#开发指南
)
-
[
API 参考
](
#api-参考
)
##
✨ 功能特性
##
核心概念
-
🔌
**WebUSB设备连接**
- 支持HiDock系列设备的USB连接
### 架构概览
-
📁
**文件管理**
- 文件列表、下载、删除等操作
-
📱
**蓝牙管理**
- 蓝牙设备扫描、连接、状态查询
-
⏰
**时间同步**
- 设备时间获取和设置
-
🔧
**设备设置**
- 自动录音、自动播放、通知等设置
-
📊
**实时数据**
- 支持实时数据传输和控制
-
📝
**日志系统**
- 完整的调试和错误日志
## 📦 安装
```
┌─────────────────────────────────────────────────────────────────┐
│ ConnectionManager (mgr) │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Device 1 │ │ Device 2 │ │ Device N │ │
│ │ (Jensen) │ │ (Jensen) │ │ (Jensen) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │TaskManager 1│ │TaskManager 2│ │TaskManager N│ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ StateStore │ │ StateStore │ │ StateStore │ │
│ │ TimerTasks │ │ TimerTasks │ │ TimerTasks │ │
│ │ UserTask │ │ UserTask │ │ UserTask │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
###
从npm安装
###
核心类说明
```
bash
| 类名 | 说明 |
npm
install
jensen-webusb
|------|------|
```
|
`ConnectionManager`
| 设备连接管理器,负责多设备的连接、断开和实例管理 |
|
`TaskManager`
| 任务调度器,管理初始化任务、定时任务、后台任务和用户任务 |
|
`DeviceStateStore`
| 设备状态存储,提供状态读写和订阅功能 |
|
`Jensen`
| 设备通信实例,提供所有与设备交互的 API |
### 任务类型
| 任务类型 | 说明 | 典型用途 |
|----------|------|----------|
|
`InitializeTask`
| 初始化任务,设备连接后执行一次 | 获取设备信息、同步时间 |
|
`TimerTask`
| 定时任务,按指定间隔循环执行 | 轮询电池状态、文件列表 |
|
`BackgroundTask`
| 后台任务,带调度逻辑的周期任务 | 文件同步、数据上传 |
|
`UserTask`
| 用户任务,
**排它性任务**
,用于用户交互操作 | 文件下载、设备配置 |
### 从源码安装
> ⚠️ **重要**:`UserTask` 是排它性的,当一个 UserTask 在执行时,所有定时任务和后台任务会暂停,以确保用户操作不被干扰。
## 快速开始
### 安装依赖
```
bash
```
bash
git clone http://gitlab.sugrsugr.com/skye/jensen.git
cd
jensen
npm
install
npm
install
```
```
## 🚀 快速开始
### 基本使用
### 基本使用
```
typescript
```
tsx
import
Jensen
from
'jensen-webusb'
;
import
{
mgr
}
from
'./utils/mgr'
;
// 创建Jensen实例
const
jensen
=
new
Jensen
();
// 连接设备
// 1. 监听自动连接事件
await
jensen
.
connect
();
mgr
.
onautoconnect
((
dinfo
)
=>
{
console
.
log
(
'设备已连接:'
,
dinfo
.
sn
);
});
// 获取设备信息
// 2. 尝试连接已授权的设备
const
deviceInfo
=
await
jensen
.
getDeviceInfo
();
await
mgr
.
tryconnect
();
console
.
log
(
'设备序列号:'
,
deviceInfo
.
sn
);
console
.
log
(
'版本号:'
,
deviceInfo
.
versionCode
);
```
```
### 在React项目中使用
## 设备连接管理
```
typescript
import
{
useEffect
,
useState
}
from
'react'
;
import
Jensen
from
'jensen-webusb'
;
function
DeviceManager
()
{
### 自动连接
const
[
jensen
]
=
useState
(()
=>
new
Jensen
());
const
[
isConnected
,
setIsConnected
]
=
useState
(
false
);
const
[
deviceInfo
,
setDeviceInfo
]
=
useState
(
null
);
useEffect
(()
=>
{
当页面加载时,调用
`tryconnect()`
会自动连接所有已授权的 HiDock 设备:
jensen
.
onconnect
=
()
=>
{
setIsConnected
(
true
);
jensen
.
getDeviceInfo
().
then
(
setDeviceInfo
);
};
jensen
.
ondisconnect
=
()
=>
{
```
tsx
setIsConnected
(
false
);
import
{
mgr
}
from
'./utils/mgr'
;
};
},
[
jensen
]);
const
handleConnect
=
async
()
=>
{
// 注册连接事件监听器
try
{
mgr
.
onautoconnect
((
dinfo
)
=>
{
await
jensen
.
connect
();
console
.
log
(
'设备序列号:'
,
dinfo
.
sn
);
}
catch
(
error
)
{
console
.
log
(
'固件版本:'
,
dinfo
.
versionCode
);
console
.
error
(
'连接失败:'
,
error
);
});
}
};
return
(
// 尝试自动连接
<
div
>
await
mgr
.
tryconnect
();
<
button
onClick
=
{
handleConnect
}
>
连接设备
<
/button
>
{
isConnected
&&
<
p
>
设备已连接
:
{
deviceInfo
?.
sn
}
<
/p>
}
<
/div
>
);
}
```
```
## 📚 API 文档
### 手动连接
### 核心类
#### Jensen
主要的设备通信类,提供所有设备操作接口。
如果需要让用户选择设备进行连接:
```
typescript
```
tsx
class
Jensen
{
const
dinfo
=
await
mgr
.
connect
();
constructor
(
log
?:
Logger
);
if
(
dinfo
)
{
console
.
log
(
'手动连接成功:'
,
dinfo
.
sn
);
}
}
```
```
### 连接管理
### 监听连接状态变化
#### connect()
```
tsx
连接到WebUSB设备
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
switch
(
state
)
{
```
typescript
case
'init'
:
await
jensen
.
connect
():
Promise
<
void
>
console
.
log
(
'正在初始化设备...'
);
break
;
case
'connected'
:
console
.
log
(
'设备已连接:'
,
dinfo
);
break
;
case
'reconnected'
:
console
.
log
(
'设备已重新连接:'
,
dinfo
);
break
;
case
'disconnected'
:
console
.
log
(
'设备已断开:'
,
dinfo
);
break
;
case
'connect-timeout'
:
console
.
log
(
'连接超时'
);
break
;
}
});
```
```
#### disconnect()
### 断开连接
断开设备连接
```
t
ypescript
```
t
sx
await
jensen
.
disconnect
():
Promise
<
void
>
await
mgr
.
close
(
sn
);
```
```
#### isConnected()
## 任务系统
检查设备连接状态
```
typescript
### UserTask(用户任务)
jensen
.
isConnected
():
boolean
```
#### tryconnect(disableOnConnect?: boolean)
UserTask 是用于用户交互操作的
**排它性任务**
。当你需要执行用户操作(如下载文件、修改设置)时,必须先注册 UserTask。
尝试连接已授权的设备
```
typescript
#### 为什么需要 UserTask?
await
jensen
.
tryconnect
(
disableOnConnect
?:
boolean
):
Promise
<
boolean
>
```
### 设备信息
#### getDeviceInfo(time?: number)
1.
**防止冲突**
:确保用户操作不会被定时任务打断
获取设备信息
2.
**资源独占**
:在 UserTask 执行期间,后台任务会暂停
3.
**超时保护**
:空闲超过 30 秒的 UserTask 会自动释放
```
typescript
#### 注册和释放 UserTask
await
jensen
.
getDeviceInfo
(
time
?:
number
):
Promise
<
DeviceInfo
>
interface
DeviceInfo
{
```
tsx
sn
:
string
;
// 设备序列号
// 注册 UserTask(带超时等待)
versionNumber
:
number
;
// 版本号
const
success
=
await
mgr
.
registerTask
(
sn
,
'my-task-id'
);
versionCode
:
string
;
// 版本代码
if
(
!
success
)
{
console
.
error
(
'注册任务失败,可能有其他任务正在执行'
);
return
;
}
}
```
#### getModel()
获取设备型号
```
typescript
jensen
.
getModel
():
string
```
### 文件管理
#### listFiles(time?: number)
获取文件列表
```
typescript
try
{
await
jensen
.
listFiles
(
time
?:
number
):
Promise
<
FileInfo
[]
>
// 执行用户操作...
}
finally
{
interface
FileInfo
{
// 释放 UserTask
name
:
string
;
// 文件名
await
mgr
.
unregisterTask
(
sn
,
'my-task-id'
);
createDate
:
string
;
// 创建日期
createTime
:
string
;
// 创建时间
time
:
Date
;
// 时间对象
duration
:
number
;
// 时长
length
:
number
;
// 文件大小
signature
:
string
;
// 文件签名
}
}
```
```
#### getFile(fileName, length, on?, onprogress?)
#### 使用 getInstance 获取设备实例
下载完整文件
```
typescript
获取 Jensen 实例时
**必须传入 UserTask 标签**
,这样所有操作都会自动验证任务有效性:
jensen
.
getFile
(
fileName
:
string
,
length
:
number
,
on
?:
(
msg
:
Uint8Array
|
'fail'
)
=>
void
,
onprogress
?:
(
size
:
number
)
=>
void
):
void
```
#### getFilePart(fileName, length, on?, onprogress?)
```
tsx
下载文件片段
// 正确用法:传入 userTaskTag
const
jensen
=
mgr
.
getInstance
(
sn
,
userTaskTag
);
if
(
jensen
==
null
)
return
alert
(
'设备未连接'
);
```
typescript
// 调用方法时会自动验证 UserTask
jensen
.
getFilePart
(
const
time
=
await
jensen
.
getTime
(
1
);
fileName
:
string
,
length
:
number
,
on
?:
(
msg
:
Uint8Array
|
'fail'
)
=>
void
,
onprogress
?:
(
size
:
number
)
=>
void
):
void
```
```
#### deleteFile(fileName)
#### 使用 executeWithUserTask(推荐)
删除文件
```
typescript
更安全的方式是使用
`executeWithUserTask`
,它会自动验证任务并执行回调:
await
jensen
.
deleteFile
(
fileName
:
string
):
Promise
<
{
result
:
'failed'
|
'success'
|
'not-exists'
}
>
```
### 蓝牙管理
```
tsx
const
time
=
await
mgr
.
executeWithUserTask
(
sn
,
userTaskTag
,
async
(
jensen
)
=>
{
return
await
jensen
.
getTime
(
1
);
});
```
#### scanDevices(seconds?: number)
#### 保持任务活跃
扫描蓝牙设备
```
typescript
如果 UserTask 长时间不活动(超过 30 秒),会被自动释放。对于长时间操作,需要定期调用保活方法:
await
jensen
.
scanDevices
(
seconds
?:
number
):
Promise
<
BluetoothDevice
[]
>
interface
BluetoothDevice
{
```
tsx
name
:
string
;
// 设备名称
// 保持任务活跃
mac
:
string
;
// MAC地址
mgr
.
keepUserTaskAlive
(
sn
,
userTaskTag
);
}
```
```
#### connectBTDevice(mac, seconds?: number)
### 定时任务(TimerTask)
连接蓝牙设备
```
typescript
定时任务会在设备连接后按指定间隔循环执行。内置的定时任务包括:
await
jensen
.
connectBTDevice
(
mac
:
string
,
seconds
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
```
#### disconnectBTDevice(seconds?: number)
-
`battery-status`
:每秒同步电池状态
断开蓝牙设备
-
`get-device-time`
:每秒获取设备时间
-
`recording-status`
:每秒检查录音状态
-
`file-list-sync`
:每秒检查文件列表变化
```
typescript
#### 注册自定义定时任务
await
jensen
.
disconnectBTDevice
(
seconds
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
```
#### getBluetoothStatus(seconds?: number)
获取蓝牙状态
```
typescript
```
tsx
await
jensen
.
getBluetoothStatus
(
seconds
?:
number
):
Promise
<
BluetoothStatus
>
mgr
.
registerTimerTask
(
sn
,
{
tag
:
'my-timer-task'
,
interface
BluetoothStatus
{
interval
:
5000
,
// 5秒执行一次
status
:
string
;
// 连接状态
task
:
async
(
jensen
,
store
)
=>
{
mac
?:
string
;
// MAC地址
const
settings
=
await
jensen
?.
getSettings
(
1
);
name
?:
string
;
// 设备名称
if
(
settings
)
{
a2dp
?:
boolean
;
// A2DP支持
store
.
set
(
'settings'
,
settings
);
hfp
?:
boolean
;
// HFP支持
}
avrcp
?:
boolean
;
// AVRCP支持
},
battery
?:
number
;
// 电池电量
onComplete
:
(
success
,
data
)
=>
{
}
console
.
log
(
'任务完成:'
,
success
);
}
});
```
```
###
时间管理
###
初始化任务(InitializeTask)
#### getTime(time?: number)
初始化任务在设备首次连接时执行一次。内置的初始化任务包括:
获取设备时间
```
typescript
-
`time-sync`
:同步设备时间
await
jensen
.
getTime
(
time
?:
number
):
Promise
<
{
time
:
string
}
>
-
`get-settings`
:获取设备设置
```
-
`get-card-info`
:获取存储卡信息
-
`battery-status`
:获取电池状态
#### setTime(date, timeout?: number)
#### 注册自定义初始化任务
设置设备时间
```
typescript
```
tsx
await
jensen
.
setTime
(
date
:
Date
,
timeout
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
mgr
.
registerInitializeTask
(
sn
,
{
tag
:
'my-init-task'
,
task
:
async
(
jensen
,
store
)
=>
{
const
info
=
await
jensen
?.
getDeviceInfo
(
1
);
store
.
set
(
'deviceInfo'
,
info
);
}
});
```
```
##
# 设备设置
##
设备状态管理
#### getSettings(time?: number)
### DeviceStateStore
获取设备设置
```
typescript
每个设备都有一个
`DeviceStateStore`
实例,用于存储设备状态。状态变更会自动通知所有订阅者。
await
jensen
.
getSettings
(
time
?:
number
):
Promise
<
{
autoRecord
:
boolean
;
autoPlay
:
boolean
;
notification
?:
boolean
;
}
|
null
>
```
#### setAutoRecord(enable, time?: number)
### 可用的状态键
设置自动录音
```
typescript
```
typescript
await
jensen
.
setAutoRecord
(
enable
:
boolean
,
time
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
interface
DeviceStateSchema
{
deviceInfo
:
DeviceInfo
|
null
;
time
:
{
deviceTime
:
string
;
syncTime
:
Date
}
|
null
;
settings
:
{
autoRecord
:
boolean
;
autoPlay
:
boolean
;
notification
?:
boolean
}
|
null
;
cardInfo
:
{
used
:
number
;
capacity
:
number
;
status
:
string
}
|
null
;
batteryStatus
:
BatteryStatus
|
null
;
bluetoothStatus
:
BluetoothStatus
|
null
;
files
:
FileInfo
[]
|
null
;
recording
:
FileInfo
|
null
;
// ... 可动态扩展
}
```
```
#### setAutoPlay(enable, time?: number)
### 订阅状态变更
设置自动播放
```
typescript
await
jensen
.
setAutoPlay
(
enable
:
boolean
,
time
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
```
### 实时数据
```
tsx
// 订阅电池状态变化
mgr
.
subscribeDeviceState
(
sn
,
'battery-status'
,
(
key
,
newValue
,
oldValue
)
=>
{
console
.
log
(
'电池状态更新:'
,
newValue
);
});
#### startRealtime()
// 订阅文件列表变化
开始实时数据传输
mgr
.
subscribeDeviceState
(
sn
,
'files'
,
(
key
,
newValue
,
oldValue
)
=>
{
console
.
log
(
'文件列表更新:'
,
newValue
);
});
```
typescript
// 订阅录音状态变化
await
jensen
.
startRealtime
():
Promise
<
ReturnStruct
[
'common'
]
>
mgr
.
subscribeDeviceState
(
sn
,
'recording'
,
(
key
,
newValue
,
oldValue
)
=>
{
if
(
newValue
&&
!
oldValue
)
{
console
.
log
(
'开始录音:'
,
newValue
.
name
);
}
else
if
(
!
newValue
&&
oldValue
)
{
console
.
log
(
'录音结束'
);
}
});
```
```
#### pauseRealtime()
### 直接读取状态
暂停实时数据传输
```
t
ypescript
```
t
sx
await
jensen
.
pauseRealtime
():
Promise
<
ReturnStruct
[
'common'
]
>
// 获取当前电池状态
```
const
battery
=
mgr
.
getDeviceState
(
sn
,
'battery-status'
);
#### stopRealtime()
// 获取文件列表
停止实时数据传输
const
files
=
mgr
.
getDeviceState
(
sn
,
'files'
);
```
typescript
// 检查状态是否过期(超过5秒)
await
jensen
.
stopRealtime
():
Promise
<
ReturnStruct
[
'common'
]
>
const
isStale
=
mgr
.
isDeviceStateStale
(
sn
,
'battery-status'
,
5000
);
```
```
#### getRealtime(frames)
### 手动设置状态
获取实时数据
```
typescript
```
tsx
await
jensen
.
getRealtime
(
frames
:
number
):
Promise
<
{
data
:
Uint8Array
;
rest
:
number
}
>
mgr
.
setDeviceState
(
sn
,
'settings'
,
{
autoRecord
:
true
,
autoPlay
:
false
});
```
```
##
💡 使用案
例
##
完整示
例
### 案例1: 设备连接和文件下载
以下是一个完整的 React 组件示例,展示了所有核心功能的使用:
```
typescript
```
tsx
import
Jensen
from
'jensen-webusb'
;
import
{
mgr
}
from
'./utils/mgr'
;
import
{
useEffect
,
useState
}
from
'react'
;
async
function
downloadLatestFile
()
{
import
Jensen
from
'../jensen'
;
const
jensen
=
new
Jensen
();
export
function
DeviceManager
()
{
try
{
// 状态定义
// 连接设备
const
[
sn
,
setSn
]
=
useState
<
string
|
null
>
(
null
);
await
jensen
.
connect
();
const
[
time
,
setTime
]
=
useState
<
string
|
null
>
(
null
);
console
.
log
(
'设备连接成功'
);
const
[
battery
,
setBattery
]
=
useState
<
string
|
null
>
(
null
);
const
[
userTask
,
setUserTask
]
=
useState
<
string
|
null
>
(
null
);
// 获取文件列表
const
[
files
,
setFiles
]
=
useState
<
Jensen
.
FileInfo
[]
>
([]);
const
files
=
await
jensen
.
listFiles
();
const
[
recording
,
setRecording
]
=
useState
<
Jensen
.
FileInfo
|
null
>
(
null
);
if
(
files
.
length
===
0
)
{
console
.
log
(
'没有找到文件'
);
// 设备连接和状态订阅
return
;
useEffect
(()
=>
{
}
mgr
.
onautoconnect
((
dinfo
)
=>
{
setSn
(
dinfo
.
sn
);
// 下载最新文件
const
latestFile
=
files
[
files
.
length
-
1
];
console
.
log
(
`开始下载文件:
${
latestFile
.
name
}
`
);
jensen
.
getFile
(
latestFile
.
name
,
latestFile
.
length
,
(
data
)
=>
{
// 订阅时间状态变化
if
(
data
instanceof
Uint8Array
)
{
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'time'
,
(
key
,
newValue
)
=>
{
// 保存文件
setTime
(
JSON
.
stringify
(
newValue
));
const
blob
=
new
Blob
([
data
]);
const
url
=
URL
.
createObjectURL
(
blob
);
const
a
=
document
.
createElement
(
'a'
);
a
.
href
=
url
;
a
.
download
=
latestFile
.
name
;
a
.
click
();
URL
.
revokeObjectURL
(
url
);
console
.
log
(
'文件下载完成'
);
}
else
{
console
.
error
(
'文件下载失败'
);
}
},
(
progress
)
=>
{
console
.
log
(
`下载进度:
${
progress
}
/
${
latestFile
.
length
}
`
);
});
});
}
catch
(
error
)
{
// 订阅电池状态变化
console
.
error
(
'操作失败:'
,
error
);
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'battery-status'
,
(
key
,
newValue
)
=>
{
}
setBattery
(
JSON
.
stringify
(
newValue
));
}
});
```
### 案例2: 蓝牙设备管理
```
typescript
import
Jensen
from
'jensen-webusb'
;
async
function
manageBluetoothDevices
()
{
// 订阅文件列表变化
const
jensen
=
new
Jensen
();
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'files'
,
(
key
,
newValue
)
=>
{
setFiles
(
newValue
as
Jensen
.
FileInfo
[]);
});
try
{
// 订阅录音状态变化
await
jensen
.
connect
();
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'recording'
,
(
key
,
newValue
)
=>
{
setRecording
(
newValue
as
Jensen
.
FileInfo
|
null
);
});
});
},
[]);
//
扫描蓝牙
设备
//
连接
设备
cons
ole
.
log
(
'开始扫描蓝牙设备...'
);
cons
t
doConnect
=
async
()
=>
{
const
devices
=
await
jensen
.
scanDevices
();
await
mgr
.
tryconnect
();
console
.
log
(
`找到
${
devices
.
length
}
个设备:`
)
;
}
;
devices
.
forEach
(
device
=>
{
// 注册 UserTask
console
.
log
(
`-
${
device
.
name
}
(
${
device
.
mac
}
)`
);
const
doRequestUserTask
=
async
()
=>
{
}
);
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
// 连接第一个设备
const
tid
=
't-'
+
new
Date
().
getTime
();
if
(
devices
.
length
>
0
)
{
const
success
=
await
mgr
.
registerTask
(
sn
,
tid
);
const
result
=
await
jensen
.
connectBTDevice
(
devices
[
0
].
mac
);
console
.
log
(
'连接结果:'
,
result
.
result
);
// 获取连接状态
if
(
!
success
)
{
const
status
=
await
jensen
.
getBluetoothStatus
();
return
alert
(
'注册任务失败'
);
console
.
log
(
'蓝牙状态:'
,
status
);
}
}
}
catch
(
error
)
{
setUserTask
(
tid
);
console
.
error
(
'蓝牙操作失败:'
,
error
);
console
.
log
(
'UserTask 已注册:'
,
tid
);
}
};
}
```
### 案例3: 设备设置管理
// 释放 UserTask
const
doReleaseUserTask
=
async
()
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
```
typescript
await
mgr
.
unregisterTask
(
sn
,
userTask
);
import
Jensen
from
'jensen-webusb'
;
setUserTask
(
null
);
console
.
log
(
'UserTask 已释放'
);
};
async
function
configureDevice
()
{
// 获取设备时间(需要 UserTask)
const
jensen
=
new
Jensen
();
const
doGetTime
=
async
()
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
try
{
try
{
await
jensen
.
connect
();
// 方式一:使用 getInstance
const
jensen
=
mgr
.
getInstance
(
sn
,
userTask
);
// 获取当前设置
if
(
jensen
==
null
)
return
alert
(
'设备未连接'
);
const
settings
=
await
jensen
.
getSettings
();
const
time
=
await
jensen
.
getTime
(
1
);
console
.
log
(
'当前设置:'
,
settings
);
alert
(
'设备时间: '
+
time
.
time
);
// 修改设置
// 方式二:使用 executeWithUserTask(推荐)
await
jensen
.
setAutoRecord
(
true
);
// const time = await mgr.executeWithUserTask(sn, userTask, async (jensen) => {
await
jensen
.
setAutoPlay
(
false
);
// return await jensen.getTime(1);
await
jensen
.
setNotification
(
true
);
// });
// alert('设备时间: ' + time.time);
console
.
log
(
'设置已更新'
);
}
catch
(
err
:
any
)
{
alert
(
'错误: '
+
err
.
message
);
// 验证设置
const
newSettings
=
await
jensen
.
getSettings
();
console
.
log
(
'更新后的设置:'
,
newSettings
);
}
catch
(
error
)
{
console
.
error
(
'设置操作失败:'
,
error
);
}
}
}
};
```
### 案例4: 实时数据监控
```
typescript
import
Jensen
from
'jensen-webusb'
;
async
function
monitorRealtimeData
()
{
const
jensen
=
new
Jensen
();
try
{
await
jensen
.
connect
();
// 开始实时数据传输
// 下载文件(需要 UserTask)
await
jensen
.
startRealtime
();
const
doDownloadFile
=
async
(
fileName
:
string
)
=>
{
console
.
log
(
'实时数据传输已开始'
);
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
// 定期获取数据
const
interval
=
setInterval
(
async
()
=>
{
try
{
try
{
const
result
=
await
jensen
.
getRealtime
(
100
);
const
jensen
=
mgr
.
getInstance
(
sn
,
userTask
);
console
.
log
(
`接收到
${
result
.
data
.
length
}
字节数据`
);
if
(
jensen
==
null
)
return
alert
(
'设备未连接'
);
// 处理数据...
await
jensen
.
getFile
(
fileName
,
1024
,
(
data
)
=>
{
processData
(
result
.
data
);
if
(
data
instanceof
Uint8Array
)
{
console
.
log
(
'文件数据:'
,
data
);
}
catch
(
error
)
{
// 处理下载的数据...
console
.
error
(
'获取实时数据失败:'
,
error
);
}
else
{
clearInterval
(
interval
);
console
.
error
(
'下载失败'
);
}
}
},
1000
);
});
}
catch
(
err
:
any
)
{
// 10秒后停止
alert
(
'错误: '
+
err
.
message
);
setTimeout
(
async
()
=>
{
clearInterval
(
interval
);
await
jensen
.
stopRealtime
();
console
.
log
(
'实时数据传输已停止'
);
},
10000
);
}
catch
(
error
)
{
console
.
error
(
'实时监控失败:'
,
error
);
}
}
}
};
return
(
<>
<
h1
>
设备管理器
</
h1
>
{
/* 操作按钮 */
}
<
div
className=
"btn-container"
>
<
button
onClick=
{
doConnect
}
>
连接设备
</
button
>
<
button
onClick=
{
doRequestUserTask
}
>
注册任务
</
button
>
<
button
onClick=
{
doReleaseUserTask
}
>
释放任务
</
button
>
<
button
onClick=
{
doGetTime
}
>
获取时间
</
button
>
</
div
>
function
processData
(
data
:
Uint8Array
)
{
{
/* 设备信息 */
}
// 处理实时数据的逻辑
<
div
>
console
.
log
(
'处理数据:'
,
data
);
<
h3
>
设备信息
</
h3
>
<
ul
>
<
li
>
SN:
{
sn
}
</
li
>
<
li
>
时间:
{
time
}
</
li
>
<
li
>
电池:
{
battery
}
</
li
>
<
li
>
录音中:
{
recording
?.
name
||
'无'
}
</
li
>
</
ul
>
<
h3
>
文件列表
</
h3
>
<
ul
>
{
files
.
map
((
file
,
index
)
=>
(
<
li
key=
{
index
}
>
{
file
.
name
}
-
{
file
.
length
}
bytes -
{
file
.
duration
}
ms
<
a
href=
"#"
onClick=
{
()
=>
doDownloadFile
(
file
.
name
)
}
>
下载
</
a
>
</
li
>
))
}
</
ul
>
</
div
>
</>
);
}
}
```
```
##
🔧 第三方项目导入
##
API 参考
###
在React项目中使用
###
ConnectionManager (mgr)
```
bash
#### 连接管理
npm
install
jensen-webusb
```
```
typescript
| 方法 | 说明 |
// 在组件中使用
|------|------|
import
{
useEffect
,
useState
}
from
'react'
;
|
`tryconnect()`
| 尝试自动连接已授权的设备 |
import
Jensen
from
'jensen-webusb'
;
|
`connect()`
| 打开设备选择对话框手动连接 |
|
`close(sn)`
| 关闭指定设备的连接 |
|
`onautoconnect(handler)`
| 注册自动连接成功的回调 |
|
`onconnectionstatechanged(handler)`
| 注册连接状态变化的回调 |
function
JensenComponent
()
{
#### 任务管理
const
[
jensen
]
=
useState
(()
=>
new
Jensen
());
const
[
isConnected
,
setIsConnected
]
=
useState
(
false
);
useEffect
(()
=>
{
| 方法 | 说明 |
jensen
.
onconnect
=
()
=>
setIsConnected
(
true
);
|------|------|
jensen
.
ondisconnect
=
()
=>
setIsConnected
(
false
);
|
`registerTask(sn, tag)`
| 注册 UserTask(超时 3 秒) |
},
[
jensen
]);
|
`unregisterTask(sn, tag)`
| 释放 UserTask |
|
`keepUserTaskAlive(sn, tag)`
| 保持 UserTask 活跃 |
|
`verifyUserTask(sn, tag)`
| 验证 UserTask 是否有效 |
|
`getCurrentUserTaskTag(sn)`
| 获取当前 UserTask 的标签 |
|
`executeWithUserTask(sn, tag, callback)`
| 验证后执行回调 |
// 组件逻辑...
#### 设备实例
}
```
### 在Vue项目中使用
| 方法 | 说明 |
|------|------|
|
`getInstance(sn, userTaskTag)`
| 获取受保护的 Jensen 实例 |
```
bash
#### 状态管理
npm
install
jensen-webusb
```
```
typescript
| 方法 | 说明 |
// 在Vue组件中使用
|------|------|
import
{
ref
,
onMounted
}
from
'vue'
;
|
`getDeviceState(sn, key)`
| 获取设备状态值 |
import
Jensen
from
'jensen-webusb'
;
|
`setDeviceState(sn, key, value)`
| 设置设备状态值 |
|
`subscribeDeviceState(sn, key, listener)`
| 订阅状态变更 |
export
default
{
|
`isDeviceStateStale(sn, key, maxAge)`
| 检查状态是否过期 |
setup
()
{
|
`dumpDeviceState(sn)`
| 导出状态快照(调试用) |
const
jensen
=
ref
(
new
Jensen
());
const
isConnected
=
ref
(
false
);
onMounted
(()
=>
{
jensen
.
value
.
onconnect
=
()
=>
isConnected
.
value
=
true
;
jensen
.
value
.
ondisconnect
=
()
=>
isConnected
.
value
=
false
;
});
return
{
### TaskManager
jensen
,
isConnected
};
}
};
```
### 在Node.js项目中使用
| 方法 | 说明 |
|------|------|
|
`registerInitializeTask(task)`
| 注册初始化任务 |
|
`registerTimerTask(task)`
| 注册定时任务 |
|
`registerBackgroundTask(task)`
| 注册后台任务 |
|
`applyUserTask(tag, timeout)`
| 申请 UserTask |
|
`releaseUserTask(tag)`
| 释放 UserTask |
|
`verifyUserTask(tag)`
| 验证 UserTask |
```
bash
### DeviceStateStore
npm
install
jensen-webusb
```
```
typescript
// 注意:WebUSB API仅在浏览器环境中可用
import
Jensen
from
'jensen-webusb'
;
// 在浏览器环境中使用
| 方法 | 说明 |
if
(
typeof
window
!==
'undefined'
)
{
|------|------|
const
jensen
=
new
Jensen
();
|
`get(key)`
| 获取状态值 |
// 使用逻辑...
|
`set(key, value)`
| 设置状态值(自动触发监听器) |
}
|
`has(key)`
| 检查状态是否存在 |
```
|
`getUpdateTime(key)`
| 获取最后更新时间 |
|
`isStale(key, maxAge)`
| 检查状态是否过期 |
|
`subscribe(key, listener)`
| 订阅状态变更(返回取消函数) |
|
`clear()`
| 清空所有状态 |
|
`clearListeners()`
| 清空所有监听器 |
##
# 在TypeScript项目中使用
##
常见问题
```
typescript
### 1. 为什么调用 `getInstance()` 报错?
// 类型定义已包含在包中
import
Jensen
,
{
DeviceInfo
,
FileInfo
,
BluetoothDevice
}
from
'jensen-webusb'
;
const
jensen
=
new
Jensen
();
确保传入了
`userTaskTag`
参数:
```
tsx
// ❌ 错误
const
jensen
=
mgr
.
getInstance
(
sn
);
async
function
typedExample
()
{
// ✅ 正确
const
deviceInfo
:
DeviceInfo
=
await
jensen
.
getDeviceInfo
();
const
jensen
=
mgr
.
getInstance
(
sn
,
userTaskTag
);
const
files
:
FileInfo
[]
=
await
jensen
.
listFiles
();
const
bluetoothDevices
:
BluetoothDevice
[]
=
await
jensen
.
scanDevices
();
}
```
```
## 📋 版本依赖
### 2. UserTask 被自动释放了?
### 核心依赖
```
json
UserTask 空闲超过 30 秒会被自动释放。对于长时间操作,定期调用:
{
```
tsx
"dependencies"
:
{
mgr
.
keepUserTaskAlive
(
sn
,
userTaskTag
);
"react"
:
"^19.0.0"
,
"react-dom"
:
"^19.0.0"
}
}
```
```
### 开发依赖
### 3. 如何知道设备断开了?
```
json
使用
`onconnectionstatechanged`
监听:
{
```
tsx
"devDependencies"
:
{
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
"@types/node"
:
"^22.10.2"
,
if
(
state
===
'disconnected'
)
{
"@types/react-dom"
:
"^19.0.2"
,
console
.
log
(
'设备断开:'
,
dinfo
);
"@vitejs/plugin-react-swc"
:
"^3.7.2"
,
"prettier"
:
"^3.3.3"
,
"typescript"
:
"^5.7.2"
,
"vite"
:
"^6.0.3"
}
}
}
}
);
```
```
### 浏览器兼容性
### 4. 状态订阅如何取消?
-
Chrome 67+
-
Edge 79+
-
Opera 54+
-
支持WebUSB API的现代浏览器
### Node.js版本要求
`subscribeDeviceState`
返回取消函数:
```
tsx
const
unsubscribe
=
mgr
.
subscribeDeviceState
(
sn
,
'battery-status'
,
listener
);
-
Node.js 16.0.0+
// 取消订阅
unsubscribe
?.();
```
##
🛠️ 开发指南
##
开发调试
###
本地开发
###
启动开发服务器
```
bash
```
bash
# 克隆项目
git clone http://gitlab.sugrsugr.com/skye/jensen.git
cd
jensen
# 安装依赖
npm
install
# 启动开发服务器
npm run dev
npm run dev
# 构建项目
npm run build
# 代码格式化
npm run prettier
```
```
###
项目结构
###
构建项目
```
```
bash
jensen/
npm run build
├── src/
│ ├── App.tsx # 主应用入口
│ ├── index.tsx # 主组件
│ ├── index.css # 样式文件
│ └── utils/
│ ├── jensen.js # 核心Jensen类
│ └── utils.js # 工具函数
├── jensen.d.ts # TypeScript类型定义
├── package.json # 项目配置
├── tsconfig.json # TypeScript配置
├── vite.config.mts # Vite配置
└── README.md # 项目文档
```
```
### 贡献指南
### 查看设备状态快照
1.
Fork 项目
2.
创建功能分支 (
`git checkout -b feature/AmazingFeature`
)
3.
提交更改 (
`git commit -m 'Add some AmazingFeature'`
)
4.
推送到分支 (
`git push origin feature/AmazingFeature`
)
5.
打开 Pull Request
### 许可证
本项目采用 ISC 许可证 - 查看
[
LICENSE
](
LICENSE
)
文件了解详情。
## 📞 支持
Martin Hou (martin@hidock.com)
```
tsx
console
.
log
(
mgr
.
dumpDeviceState
(
sn
));
```
---
**注意**
: 此库需要支持WebUSB API的现代浏览器,并且设备需要支持相应的USB协议。
**注意**
:本库基于 WebUSB API,仅支持 Chrome 67+、Edge 79+、Opera 54+ 等现代浏览器。
\ No newline at end of file
src/mgr.tsx
View file @
1e9d5afd
...
@@ -77,15 +77,15 @@ export function DeviceManager() {
...
@@ -77,15 +77,15 @@ export function DeviceManager() {
}
}
}
}
const
doDownloadFile
=
async
(
file
Name
:
string
)
=>
{
const
doDownloadFile
=
async
(
file
:
Jensen
.
FileInfo
)
=>
{
if
(
sn
==
null
)
return
alert
(
'Please connect first'
);
if
(
sn
==
null
)
return
alert
(
'Please connect first'
);
if
(
userTask
==
null
)
return
alert
(
'Please request a user task first'
);
// 需要有一个发起后台任务的方法
try
{
try
{
const
jensen
=
mgr
.
getInstance
(
sn
,
userTask
);
// ...
if
(
jensen
==
null
)
return
alert
(
'Please connect first'
);
mgr
.
submitFileTask
(
sn
,
{
await
jensen
.
getFile
(
fileName
,
1024
,
(
data
)
=>
{
fileName
:
file
.
name
,
console
.
log
(
data
);
fileLength
:
file
.
length
,
fileSignature
:
file
.
signature
,
task
:
'download'
});
});
}
catch
(
err
:
any
)
{
}
catch
(
err
:
any
)
{
alert
(
'Error: '
+
err
.
message
);
alert
(
'Error: '
+
err
.
message
);
...
@@ -113,7 +113,7 @@ export function DeviceManager() {
...
@@ -113,7 +113,7 @@ export function DeviceManager() {
<
ol
>
<
ol
>
{
{
files
.
map
((
file
,
index
)
=>
{
files
.
map
((
file
,
index
)
=>
{
return
<
li
key=
{
index
}
>
{
file
.
name
}
-
{
file
.
length
}
-
{
file
.
duration
}
<
a
href=
"
#"
onClick=
{
()
=>
{
doDownloadFile
(
file
.
nam
e
)
}
}
>
Download
</
a
></
li
>
return
<
li
key=
{
index
}
>
{
file
.
name
}
-
{
file
.
length
}
-
{
file
.
duration
}
<
a
href=
"
javascript:void(0)"
onClick=
{
()
=>
{
doDownloadFile
(
fil
e
)
}
}
>
Download
</
a
></
li
>
})
})
}
}
</
ol
>
</
ol
>
...
...
src/utils/mgr.ts
View file @
1e9d5afd
import
Jensen
,
{
DeviceInfo
,
ScheduleInfo
}
from
"../../"
;
import
Jensen
,
{
DeviceInfo
,
ScheduleInfo
}
from
"../../"
;
import
{
Logger
}
from
"../Logger"
;
import
{
Logger
}
from
"../Logger"
;
import
{
BackgroundTask
,
DeviceStateSchema
,
DeviceStateStore
,
InitializeTask
,
StateChangeListener
,
TaskManager
,
TimerTask
}
from
"./tasks"
;
import
{
BackgroundTask
,
DeviceStateSchema
,
DeviceStateStore
,
InitializeTask
,
StateChangeListener
,
TaskManager
,
TimerTask
}
from
"./tasks"
;
import
{
buildTasks
,
FileTaskData
}
from
"./task-impl"
;
// 指令参数
// 指令参数
export
type
CommandOptions
=
{
export
type
CommandOptions
=
{
...
@@ -110,6 +111,10 @@ export class ConnectionManager
...
@@ -110,6 +111,10 @@ export class ConnectionManager
return
tm
.
verifyUserTask
(
tag
);
return
tm
.
verifyUserTask
(
tag
);
}
}
submitFileTask
(
sn
:
string
,
data
:
FileTaskData
)
{
return
this
.
taskManagers
.
get
(
sn
)?.
getStateStore
()?.
pushTaskData
(
'file-transfer'
,
data
);
}
// 获取当前用户任务的 tag
// 获取当前用户任务的 tag
getCurrentUserTaskTag
(
sn
:
string
):
string
|
null
{
getCurrentUserTaskTag
(
sn
:
string
):
string
|
null
{
return
this
.
taskManagers
.
get
(
sn
)?.
getCurrentUserTaskTag
()
||
null
;
return
this
.
taskManagers
.
get
(
sn
)?.
getCurrentUserTaskTag
()
||
null
;
...
@@ -273,7 +278,7 @@ export class ConnectionManager
...
@@ -273,7 +278,7 @@ export class ConnectionManager
let
tm
=
this
.
taskManagers
.
get
(
dinfo
.
sn
);
let
tm
=
this
.
taskManagers
.
get
(
dinfo
.
sn
);
if
(
tm
==
null
)
{
if
(
tm
==
null
)
{
tm
=
new
TaskManager
(
inst
,
this
.
logger
);
tm
=
new
TaskManager
(
inst
,
this
.
logger
);
this
.
taskBuilder
(
inst
,
tm
);
buildTasks
(
inst
,
tm
);
await
tm
.
scheduleInitializeTasks
();
await
tm
.
scheduleInitializeTasks
();
this
.
taskManagers
.
set
(
dinfo
.
sn
,
tm
);
this
.
taskManagers
.
set
(
dinfo
.
sn
,
tm
);
}
}
...
@@ -294,136 +299,6 @@ export class ConnectionManager
...
@@ -294,136 +299,6 @@ export class ConnectionManager
}
}
}
}
taskBuilder
(
jensen
:
Jensen
,
tm
:
TaskManager
)
{
// 初始化任务的注册 - 数据会被存储到 DeviceStateStore 中
tm
.
registerInitializeTask
({
tag
:
'time-sync'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
time
=
await
jensen
?.
getTime
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-time: '
+
JSON
.
stringify
(
time
));
// 存储设备时间到状态容器
if
(
time
)
{
store
.
set
(
'time'
,
{
deviceTime
:
time
.
time
,
syncTime
:
new
Date
()
});
}
// 同步设置时间
await
jensen
?.
setTime
(
new
Date
(),
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'set-time-to: '
+
new
Date
().
toLocaleString
());
}
});
tm
.
registerInitializeTask
({
tag
:
'get-settings'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
settings
=
await
jensen
?.
getSettings
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-settings: '
+
JSON
.
stringify
(
settings
));
// 存储设置到状态容器
if
(
settings
)
{
store
.
set
(
'settings'
,
settings
);
}
}
});
tm
.
registerInitializeTask
({
tag
:
'get-card-info'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
cardInfo
=
await
jensen
?.
getCardInfo
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-card-info: '
+
JSON
.
stringify
(
cardInfo
));
// 存储卡信息到状态容器
if
(
cardInfo
)
{
store
.
set
(
'cardInfo'
,
cardInfo
);
}
}
});
tm
.
registerInitializeTask
({
tag
:
'battery-status'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
batteryStatus
=
await
jensen
?.
getBatteryStatus
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-battery-status: '
+
JSON
.
stringify
(
batteryStatus
));
// 存储电池状态到状态容器
if
(
batteryStatus
)
{
store
.
set
(
'battery-status'
,
batteryStatus
);
}
}
});
// 定时同步设备电池状态
tm
.
registerTimerTask
({
tag
:
'battery-status'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
batteryStatus
=
await
jensen
?.
getBatteryStatus
(
1
);
Logger
.
info
(
'jensen'
,
'timer-task'
,
'battery: '
+
JSON
.
stringify
(
batteryStatus
));
// 更新电池状态(会自动触发订阅者)
if
(
batteryStatus
)
{
store
.
set
(
'battery-status'
,
batteryStatus
);
}
}
});
// 定时获取设备时间
tm
.
registerTimerTask
({
tag
:
'get-device-time'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
time
=
await
jensen
?.
getTime
(
1
);
Logger
.
info
(
'jensen'
,
'timer-task'
,
'time: '
+
JSON
.
stringify
(
time
));
// 更新设备时间
if
(
time
)
{
store
.
set
(
'time'
,
{
deviceTime
:
time
.
time
,
syncTime
:
new
Date
()
});
}
}
});
// 录音中的状态同步
tm
.
registerTimerTask
({
tag
:
'recording-status'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
recording
=
await
jensen
?.
getRecordingFile
();
Logger
.
info
(
'jensen'
,
'timer-task'
,
'recording-status: '
+
JSON
.
stringify
(
recording
));
if
(
recording
)
store
.
set
(
'recording'
,
recording
);
}
});
// 设备录音文件列表同步
tm
.
registerTimerTask
({
tag
:
'file-list-sync'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
needRefresh
=
false
;
// 1. 定时查询文件数量
let
fileCount
=
await
jensen
?.
getFileCount
(
1
);
if
(
fileCount
)
{
let
lastCount
=
store
.
get
(
'file-count'
);
if
(
lastCount
!=
fileCount
.
count
)
{
needRefresh
=
true
;
Logger
.
info
(
'jensen'
,
'timer-task'
,
'file-count changed: '
+
lastCount
+
' -> '
+
fileCount
.
count
);
}
store
.
set
(
'file-count'
,
fileCount
.
count
);
}
if
(
!
needRefresh
)
return
;
let
files
=
await
jensen
?.
listFiles
();
if
(
files
)
store
.
set
(
'files'
,
files
);
}
});
// 后台任务的注册(如有需要可以在这里添加)
}
onautoconnect
(
eventListener
:
AutoConnectEventHandler
)
{
onautoconnect
(
eventListener
:
AutoConnectEventHandler
)
{
this
.
onautoconnectEventListener
=
eventListener
;
this
.
onautoconnectEventListener
=
eventListener
;
}
}
...
...
src/utils/task-impl.ts
0 → 100644
View file @
1e9d5afd
import
Jensen
,
{
FileInfo
}
from
"../../jensen"
;
import
{
Logger
}
from
"../Logger"
;
import
{
TaskManager
,
DeviceStateStore
}
from
"./tasks"
;
export
type
FileTaskData
=
{
fileName
:
string
;
fileLength
:
number
;
fileSignature
:
string
;
task
:
'download'
|
'transcribe'
|
'summarize'
;
}
/**
* 任务构建器 - 注册设备的所有后台任务
* @param jensen Jensen 实例
* @param tm TaskManager 实例
*/
export
function
buildTasks
(
jensen
:
Jensen
,
tm
:
TaskManager
)
{
// ===================== 初始化任务 =====================
// 时间同步
tm
.
registerInitializeTask
({
tag
:
'time-sync'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
time
=
await
jensen
?.
getTime
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-time: '
+
JSON
.
stringify
(
time
));
// 存储设备时间到状态容器
if
(
time
)
{
store
.
set
(
'time'
,
{
deviceTime
:
time
.
time
,
syncTime
:
new
Date
()
});
}
// 同步设置时间
await
jensen
?.
setTime
(
new
Date
(),
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'set-time-to: '
+
new
Date
().
toLocaleString
());
}
});
// 获取设备设置
tm
.
registerInitializeTask
({
tag
:
'get-settings'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
settings
=
await
jensen
?.
getSettings
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-settings: '
+
JSON
.
stringify
(
settings
));
// 存储设置到状态容器
if
(
settings
)
{
store
.
set
(
'settings'
,
settings
);
}
}
});
// 获取存储卡信息
tm
.
registerInitializeTask
({
tag
:
'get-card-info'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
cardInfo
=
await
jensen
?.
getCardInfo
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-card-info: '
+
JSON
.
stringify
(
cardInfo
));
// 存储卡信息到状态容器
if
(
cardInfo
)
{
store
.
set
(
'cardInfo'
,
cardInfo
);
}
}
});
// 获取电池状态(仅 P1 型号)
tm
.
registerInitializeTask
({
tag
:
'battery-status'
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
batteryStatus
=
await
jensen
?.
getBatteryStatus
(
1
);
Logger
.
info
(
'jensen'
,
'initialize'
,
'get-battery-status: '
+
JSON
.
stringify
(
batteryStatus
));
// 存储电池状态到状态容器
if
(
batteryStatus
)
{
store
.
set
(
'battery-status'
,
batteryStatus
);
}
}
});
// ===================== 定时任务 =====================
// 定时同步设备电池状态
tm
.
registerTimerTask
({
tag
:
'battery-status'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
batteryStatus
=
await
jensen
?.
getBatteryStatus
(
1
);
Logger
.
info
(
'jensen'
,
'timer-task'
,
'battery: '
+
JSON
.
stringify
(
batteryStatus
));
// 更新电池状态(会自动触发订阅者)
if
(
batteryStatus
)
{
store
.
set
(
'battery-status'
,
batteryStatus
);
}
}
});
// 定时获取设备时间
tm
.
registerTimerTask
({
tag
:
'get-device-time'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
if
(
jensen
?.
getModel
()
!=
'hidock-p1'
)
return
;
let
time
=
await
jensen
?.
getTime
(
1
);
Logger
.
info
(
'jensen'
,
'timer-task'
,
'time: '
+
JSON
.
stringify
(
time
));
// 更新设备时间
if
(
time
)
{
store
.
set
(
'time'
,
{
deviceTime
:
time
.
time
,
syncTime
:
new
Date
()
});
}
}
});
// 录音中的状态同步
tm
.
registerTimerTask
({
tag
:
'recording-status'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
recording
=
await
jensen
?.
getRecordingFile
();
Logger
.
info
(
'jensen'
,
'timer-task'
,
'recording-status: '
+
JSON
.
stringify
(
recording
));
if
(
recording
)
store
.
set
(
'recording'
,
recording
);
}
});
// 设备录音文件列表同步
tm
.
registerTimerTask
({
tag
:
'file-list-sync'
,
interval
:
1000
,
task
:
async
(
jensen
:
Jensen
|
null
,
store
)
=>
{
let
needRefresh
=
false
;
// 1. 定时查询文件数量
let
fileCount
=
await
jensen
?.
getFileCount
(
1
);
if
(
fileCount
)
{
let
lastCount
=
store
.
get
(
'file-count'
);
if
(
lastCount
!=
fileCount
.
count
)
{
needRefresh
=
true
;
Logger
.
info
(
'jensen'
,
'timer-task'
,
'file-count changed: '
+
lastCount
+
' -> '
+
fileCount
.
count
);
}
store
.
set
(
'file-count'
,
fileCount
.
count
);
}
if
(
!
needRefresh
)
return
;
let
files
=
await
jensen
?.
listFiles
();
if
(
files
)
store
.
set
(
'files'
,
files
);
}
});
// ===================== 后台任务 =====================
// 如有需要可以在这里添加后台任务
tm
.
registerBackgroundTask
({
tag
:
'file-transfer'
,
// 注意:必须使用 function 而非箭头函数,才能正确绑定 this
schedule
:
async
function
(
jensen
,
store
)
{
let
data
=
store
.
peekTaskData
(
'file-transfer'
)
as
FileTaskData
|
undefined
;
if
(
!
data
)
return
;
console
.
log
(
'xxxx'
,
data
);
// 通过 this 调用自定义方法
await
this
.
transfer
(
jensen
,
data
,
store
);
store
.
popTaskData
(
'file-transfer'
);
// this.postProcess(data);
if
(
data
.
task
==
'download'
)
{
this
.
download
();
}
else
if
(
data
.
task
==
'transcribe'
)
{
this
.
transcribe
();
}
else
if
(
data
.
task
==
'summarize'
)
{
this
.
summarize
();
}
},
// 自定义方法:文件传输
transfer
:
async
function
(
jensen
:
Jensen
|
null
,
data
:
FileTaskData
,
store
:
DeviceStateStore
)
{
if
(
!
jensen
)
return
false
;
// 用 Promise 包装回调逻辑,等待所有数据块接收完成
const
fileData
=
await
new
Promise
<
Uint8Array
|
null
>
((
resolve
)
=>
{
const
chunks
:
Uint8Array
[]
=
[];
let
receivedLength
=
0
;
jensen
.
getFile
(
data
.
fileName
,
data
.
fileLength
,
(
chunk
)
=>
{
if
(
chunk
===
'fail'
)
{
console
.
error
(
'文件传输失败'
);
resolve
(
null
);
return
;
}
// 收集数据块
chunks
.
push
(
chunk
);
receivedLength
+=
chunk
.
length
;
console
.
log
(
`接收进度:
${
receivedLength
}
/
${
data
.
fileLength
}
`
);
// 检查是否接收完成
if
(
receivedLength
>=
data
.
fileLength
)
{
// 合并所有数据块为一个完整的 Uint8Array
const
fileData
=
new
Uint8Array
(
receivedLength
);
let
offset
=
0
;
for
(
const
chunk
of
chunks
)
{
fileData
.
set
(
chunk
,
offset
);
offset
+=
chunk
.
length
;
}
console
.
log
(
'文件传输完成,总大小:'
,
receivedLength
);
resolve
(
fileData
);
}
});
});
if
(
fileData
)
{
// 将完整的文件数据存储到 store 中供后续处理使用
// store.set('transferredFile', { fileName: data.fileName, data: fileData });
return
true
;
}
return
false
;
},
download
:
async
function
()
{
console
.
log
(
'xxxx'
,
'download and save to local file'
);
},
transcribe
:
async
function
()
{
console
.
log
(
'xxxx'
,
'transcribe the file'
);
},
summarize
:
async
function
()
{
console
.
log
(
'xxxx'
,
'summarize the file'
);
}
});
}
src/utils/tasks.ts
View file @
1e9d5afd
...
@@ -28,6 +28,8 @@ export class DeviceStateStore {
...
@@ -28,6 +28,8 @@ export class DeviceStateStore {
private
updateTimes
:
Map
<
string
,
number
>
=
new
Map
();
private
updateTimes
:
Map
<
string
,
number
>
=
new
Map
();
// 状态变更监听器
// 状态变更监听器
private
listeners
:
Map
<
string
,
StateChangeListener
<
any
>
[]
>
=
new
Map
();
private
listeners
:
Map
<
string
,
StateChangeListener
<
any
>
[]
>
=
new
Map
();
// 后台任务队列:key 为任务 tag,value 为待处理数据数组
private
taskQueues
:
Map
<
string
,
any
[]
>
=
new
Map
();
// 获取状态
// 获取状态
get
<
K
extends
keyof
DeviceStateSchema
>
(
key
:
K
):
DeviceStateSchema
[
K
]
|
undefined
{
get
<
K
extends
keyof
DeviceStateSchema
>
(
key
:
K
):
DeviceStateSchema
[
K
]
|
undefined
{
...
@@ -95,10 +97,129 @@ export class DeviceStateStore {
...
@@ -95,10 +97,129 @@ export class DeviceStateStore {
}
}
}
}
// ===================== 后台任务队列操作方法 =====================
/**
* 向指定任务队列推入数据
* @param tag 任务标签(与后台任务的 tag 一致)
* @param data 要推入的数据
*/
pushTaskData
(
tag
:
string
,
data
:
any
):
void
{
if
(
!
this
.
taskQueues
.
has
(
tag
))
{
this
.
taskQueues
.
set
(
tag
,
[]);
}
this
.
taskQueues
.
get
(
tag
)
!
.
push
(
data
);
}
/**
* 向指定任务队列批量推入数据
* @param tag 任务标签
* @param dataList 要推入的数据数组
*/
pushTaskDataBatch
(
tag
:
string
,
dataList
:
any
[]):
void
{
if
(
!
this
.
taskQueues
.
has
(
tag
))
{
this
.
taskQueues
.
set
(
tag
,
[]);
}
this
.
taskQueues
.
get
(
tag
)
!
.
push
(...
dataList
);
}
/**
* 从指定任务队列弹出一个数据(FIFO,先进先出)
* @param tag 任务标签
* @returns 弹出的数据,如果队列为空则返回 undefined
*/
popTaskData
(
tag
:
string
):
any
|
undefined
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
if
(
!
queue
||
queue
.
length
===
0
)
return
undefined
;
return
queue
.
shift
();
}
/**
* 从指定任务队列弹出所有数据
* @param tag 任务标签
* @returns 所有数据的数组,如果队列为空则返回空数组
*/
popAllTaskData
(
tag
:
string
):
any
[]
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
if
(
!
queue
||
queue
.
length
===
0
)
return
[];
const
result
=
[...
queue
];
queue
.
length
=
0
;
// 清空队列
return
result
;
}
/**
* 查看指定任务队列的第一个数据(不弹出)
* @param tag 任务标签
* @returns 队列中的第一个数据,如果队列为空则返回 undefined
*/
peekTaskData
(
tag
:
string
):
any
|
undefined
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
if
(
!
queue
||
queue
.
length
===
0
)
return
undefined
;
return
queue
[
0
];
}
/**
* 查看指定任务队列的所有数据(不弹出)
* @param tag 任务标签
* @returns 队列中所有数据的副本,如果队列为空则返回空数组
*/
peekAllTaskData
(
tag
:
string
):
any
[]
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
if
(
!
queue
)
return
[];
return
[...
queue
];
}
/**
* 获取指定任务队列中的数据数量
* @param tag 任务标签
* @returns 队列中的数据数量
*/
getTaskDataCount
(
tag
:
string
):
number
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
return
queue
?
queue
.
length
:
0
;
}
/**
* 检查指定任务队列是否有数据
* @param tag 任务标签
* @returns 如果队列有数据返回 true,否则返回 false
*/
hasTaskData
(
tag
:
string
):
boolean
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
return
queue
!==
undefined
&&
queue
.
length
>
0
;
}
/**
* 清空指定任务队列
* @param tag 任务标签
*/
clearTaskData
(
tag
:
string
):
void
{
const
queue
=
this
.
taskQueues
.
get
(
tag
);
if
(
queue
)
queue
.
length
=
0
;
}
/**
* 清空所有任务队列
*/
clearAllTaskData
():
void
{
this
.
taskQueues
.
clear
();
}
/**
* 获取所有任务队列的 tag 列表
* @returns 所有任务队列的 tag 数组
*/
getTaskQueueTags
():
string
[]
{
return
Array
.
from
(
this
.
taskQueues
.
keys
());
}
// ===================== 状态管理方法 =====================
// 清空所有状态
// 清空所有状态
clear
():
void
{
clear
():
void
{
this
.
state
=
{};
this
.
state
=
{};
this
.
updateTimes
.
clear
();
this
.
updateTimes
.
clear
();
this
.
taskQueues
.
clear
();
// 同时清空任务队列
// 不清除监听器,允许重连后继续监听
// 不清除监听器,允许重连后继续监听
}
}
...
@@ -108,12 +229,19 @@ export class DeviceStateStore {
...
@@ -108,12 +229,19 @@ export class DeviceStateStore {
}
}
// 导出状态快照(用于调试)
// 导出状态快照(用于调试)
dump
():
{
state
:
Partial
<
DeviceStateSchema
>
;
updateTimes
:
Record
<
string
,
number
>
}
{
dump
():
{
state
:
Partial
<
DeviceStateSchema
>
;
updateTimes
:
Record
<
string
,
number
>
;
taskQueues
:
Record
<
string
,
any
[]
>
;
}
{
const
times
:
Record
<
string
,
number
>
=
{};
const
times
:
Record
<
string
,
number
>
=
{};
this
.
updateTimes
.
forEach
((
v
,
k
)
=>
times
[
k
]
=
v
);
this
.
updateTimes
.
forEach
((
v
,
k
)
=>
times
[
k
]
=
v
);
const
queues
:
Record
<
string
,
any
[]
>
=
{};
this
.
taskQueues
.
forEach
((
v
,
k
)
=>
queues
[
k
]
=
[...
v
]);
return
{
return
{
state
:
{
...
this
.
state
},
state
:
{
...
this
.
state
},
updateTimes
:
times
updateTimes
:
times
,
taskQueues
:
queues
};
};
}
}
}
}
...
@@ -135,14 +263,11 @@ export type TimerTask = {
...
@@ -135,14 +263,11 @@ export type TimerTask = {
export
type
BackgroundTask
=
{
export
type
BackgroundTask
=
{
tag
:
string
;
tag
:
string
;
// 用于任务调度
// 用于任务调度,可通过 this 调用同级定义的其它方法(注意:必须使用 function 而非箭头函数)
schedule
:
(
jensen
:
Jensen
|
null
,
store
:
DeviceStateStore
)
=>
Promise
<
any
>
;
schedule
:
(
this
:
BackgroundTask
,
jensen
:
Jensen
|
null
,
store
:
DeviceStateStore
)
=>
Promise
<
any
>
;
// 用于设备交互使用
lastExecuteTime
?:
number
;
deviceTask
?:
(
jensen
:
Jensen
|
null
,
store
:
DeviceStateStore
)
=>
Promise
<
any
>
;
// 允许定义任意其它方法/属性
// 任务善后处理调用
[
key
:
string
]:
any
;
generalTask
?:
(
jensen
:
Jensen
|
null
,
store
:
DeviceStateStore
)
=>
Promise
<
any
>
;
lastExecuteTime
:
number
;
interval
:
number
;
// 轮询间隔
}
}
export
type
UserTask
=
{
export
type
UserTask
=
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment