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
Hide 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连接
### 架构概览
-
📁
**文件管理**
- 文件列表、下载、删除等操作
-
📱
**蓝牙管理**
- 蓝牙设备扫描、连接、状态查询
-
⏰
**时间同步**
- 设备时间获取和设置
-
🔧
**设备设置**
- 自动录音、自动播放、通知等设置
-
📊
**实时数据**
- 支持实时数据传输和控制
-
📝
**日志系统**
- 完整的调试和错误日志
## 📦 安装
```
┌─────────────────────────────────────────────────────────────────┐
### 从npm安装
│ ConnectionManager (mgr) │
├─────────────────────────────────────────────────────────────────┤
```
bash
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
npm
install
jensen-webusb
│ │ Device 1 │ │ Device 2 │ │ Device N │ │
│ │ (Jensen) │ │ (Jensen) │ │ (Jensen) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │TaskManager 1│ │TaskManager 2│ │TaskManager N│ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ StateStore │ │ StateStore │ │ StateStore │ │
│ │ TimerTasks │ │ TimerTasks │ │ TimerTasks │ │
│ │ UserTask │ │ UserTask │ │ UserTask │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
```
###
从源码安装
###
核心类说明
```
bash
| 类名 | 说明 |
git clone http://gitlab.sugrsugr.com/skye/jensen.git
|------|------|
cd
jensen
|
`ConnectionManager`
| 设备连接管理器,负责多设备的连接、断开和实例管理 |
npm
install
|
`TaskManager`
| 任务调度器,管理初始化任务、定时任务、后台任务和用户任务 |
```
|
`DeviceStateStore`
| 设备状态存储,提供状态读写和订阅功能 |
|
`Jensen`
| 设备通信实例,提供所有与设备交互的 API |
##
🚀 快速开始
##
# 任务类型
### 基本使用
| 任务类型 | 说明 | 典型用途 |
|----------|------|----------|
|
`InitializeTask`
| 初始化任务,设备连接后执行一次 | 获取设备信息、同步时间 |
|
`TimerTask`
| 定时任务,按指定间隔循环执行 | 轮询电池状态、文件列表 |
|
`BackgroundTask`
| 后台任务,带调度逻辑的周期任务 | 文件同步、数据上传 |
|
`UserTask`
| 用户任务,
**排它性任务**
,用于用户交互操作 | 文件下载、设备配置 |
```
typescript
> ⚠️ **重要**:`UserTask` 是排它性的,当一个 UserTask 在执行时,所有定时任务和后台任务会暂停,以确保用户操作不被干扰。
import
Jensen
from
'jensen-webusb'
;
// 创建Jensen实例
## 快速开始
const
jensen
=
new
Jensen
();
// 连接设备
### 安装依赖
await
jensen
.
connect
();
// 获取设备信息
```
bash
const
deviceInfo
=
await
jensen
.
getDeviceInfo
();
npm
install
console
.
log
(
'设备序列号:'
,
deviceInfo
.
sn
);
console
.
log
(
'版本号:'
,
deviceInfo
.
versionCode
);
```
```
### 在React项目中使用
### 基本使用
```
typescript
import
{
useEffect
,
useState
}
from
'react'
;
import
Jensen
from
'jensen-webusb'
;
function
DeviceManager
()
{
```
tsx
const
[
jensen
]
=
useState
(()
=>
new
Jensen
());
import
{
mgr
}
from
'./utils/mgr'
;
const
[
isConnected
,
setIsConnected
]
=
useState
(
false
);
const
[
deviceInfo
,
setDeviceInfo
]
=
useState
(
null
);
useEffect
(()
=>
{
// 1. 监听自动连接事件
jensen
.
onconnect
=
()
=>
{
mgr
.
onautoconnect
((
dinfo
)
=>
{
setIsConnected
(
true
);
console
.
log
(
'设备已连接:'
,
dinfo
.
sn
);
jensen
.
getDeviceInfo
().
then
(
setDeviceInfo
);
});
};
jensen
.
ondisconnect
=
()
=>
{
setIsConnected
(
false
);
};
},
[
jensen
]);
const
handleConnect
=
async
()
=>
{
// 2. 尝试连接已授权的设备
try
{
await
mgr
.
tryconnect
();
await
jensen
.
connect
();
}
catch
(
error
)
{
console
.
error
(
'连接失败:'
,
error
);
}
};
return
(
<
div
>
<
button
onClick
=
{
handleConnect
}
>
连接设备
<
/button
>
{
isConnected
&&
<
p
>
设备已连接
:
{
deviceInfo
?.
sn
}
<
/p>
}
<
/div
>
);
}
```
```
## 📚 API 文档
## 设备连接管理
### 核心类
###
# Jensen
###
自动连接
主要的设备通信类,提供所有设备操作接口。
当页面加载时,调用
`tryconnect()`
会自动连接所有已授权的 HiDock 设备:
```
typescript
```
tsx
class
Jensen
{
import
{
mgr
}
from
'./utils/mgr'
;
constructor
(
log
?:
Logger
);
}
```
### 连接管理
// 注册连接事件监听器
mgr
.
onautoconnect
((
dinfo
)
=>
{
console
.
log
(
'设备序列号:'
,
dinfo
.
sn
);
console
.
log
(
'固件版本:'
,
dinfo
.
versionCode
);
});
#### connect()
// 尝试自动连接
连接到WebUSB设备
await
mgr
.
tryconnect
();
```
typescript
await
jensen
.
connect
():
Promise
<
void
>
```
```
#### disconnect()
### 手动连接
断开设备连接
```
typescript
如果需要让用户选择设备进行连接:
await
jensen
.
disconnect
():
Promise
<
void
>
```
#### isConnected()
```
tsx
检查设备连接状态
const
dinfo
=
await
mgr
.
connect
();
if
(
dinfo
)
{
console
.
log
(
'手动连接成功:'
,
dinfo
.
sn
);
}
```
```
typescript
### 监听连接状态变化
jensen
.
isConnected
():
boolean
```
tsx
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
switch
(
state
)
{
case
'init'
:
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
;
}
});
```
```
#### tryconnect(disableOnConnect?: boolean)
### 断开连接
尝试连接已授权的设备
```
t
ypescript
```
t
sx
await
jensen
.
tryconnect
(
disableOnConnect
?:
boolean
):
Promise
<
boolean
>
await
mgr
.
close
(
sn
);
```
```
### 设备信息
## 任务系统
#### getDeviceInfo(time?: number)
获取设备信息
```
typescript
### UserTask(用户任务)
await
jensen
.
getDeviceInfo
(
time
?:
number
):
Promise
<
DeviceInfo
>
interface
DeviceInfo
{
UserTask 是用于用户交互操作的
**排它性任务**
。当你需要执行用户操作(如下载文件、修改设置)时,必须先注册 UserTask。
sn
:
string
;
// 设备序列号
versionNumber
:
number
;
// 版本号
versionCode
:
string
;
// 版本代码
}
```
#### getModel()
#### 为什么需要 UserTask?
获取设备型号
```
typescript
1.
**防止冲突**
:确保用户操作不会被定时任务打断
jensen
.
getModel
():
string
2.
**资源独占**
:在 UserTask 执行期间,后台任务会暂停
```
3.
**超时保护**
:空闲超过 30 秒的 UserTask 会自动释放
###
文件管理
###
# 注册和释放 UserTask
#### listFiles(time?: number)
```
tsx
获取文件列表
// 注册 UserTask(带超时等待)
const
success
=
await
mgr
.
registerTask
(
sn
,
'my-task-id'
);
if
(
!
success
)
{
console
.
error
(
'注册任务失败,可能有其他任务正在执行'
);
return
;
}
```
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)
断开蓝牙设备
```
typescript
-
`battery-status`
:每秒同步电池状态
await
jensen
.
disconnectBTDevice
(
seconds
?:
number
):
Promise
<
ReturnStruct
[
'common'
]
>
-
`get-device-time`
:每秒获取设备时间
```
-
`recording-status`
:每秒检查录音状态
-
`file-list-sync`
:每秒检查文件列表变化
#### 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'
;
import
Jensen
from
'../jensen'
;
export
function
DeviceManager
()
{
// 状态定义
const
[
sn
,
setSn
]
=
useState
<
string
|
null
>
(
null
);
const
[
time
,
setTime
]
=
useState
<
string
|
null
>
(
null
);
const
[
battery
,
setBattery
]
=
useState
<
string
|
null
>
(
null
);
const
[
userTask
,
setUserTask
]
=
useState
<
string
|
null
>
(
null
);
const
[
files
,
setFiles
]
=
useState
<
Jensen
.
FileInfo
[]
>
([]);
const
[
recording
,
setRecording
]
=
useState
<
Jensen
.
FileInfo
|
null
>
(
null
);
// 设备连接和状态订阅
useEffect
(()
=>
{
mgr
.
onautoconnect
((
dinfo
)
=>
{
setSn
(
dinfo
.
sn
);
// 订阅时间状态变化
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'time'
,
(
key
,
newValue
)
=>
{
setTime
(
JSON
.
stringify
(
newValue
));
});
// 订阅电池状态变化
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'battery-status'
,
(
key
,
newValue
)
=>
{
setBattery
(
JSON
.
stringify
(
newValue
));
});
// 订阅文件列表变化
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'files'
,
(
key
,
newValue
)
=>
{
setFiles
(
newValue
as
Jensen
.
FileInfo
[]);
});
// 订阅录音状态变化
mgr
.
subscribeDeviceState
(
dinfo
.
sn
,
'recording'
,
(
key
,
newValue
)
=>
{
setRecording
(
newValue
as
Jensen
.
FileInfo
|
null
);
});
});
},
[]);
async
function
downloadLatestFile
()
{
const
jensen
=
new
Jensen
();
try
{
// 连接设备
// 连接设备
await
jensen
.
connect
();
const
doConnect
=
async
()
=>
{
console
.
log
(
'设备连接成功'
);
await
mgr
.
tryconnect
();
};
// 获取文件列表
const
files
=
await
jensen
.
listFiles
();
if
(
files
.
length
===
0
)
{
console
.
log
(
'没有找到文件'
);
return
;
}
// 下载最新文件
const
latestFile
=
files
[
files
.
length
-
1
];
console
.
log
(
`开始下载文件:
${
latestFile
.
name
}
`
);
jensen
.
getFile
(
latestFile
.
name
,
latestFile
.
length
,
(
data
)
=>
{
if
(
data
instanceof
Uint8Array
)
{
// 保存文件
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
);
}
}
```
### 案例2: 蓝牙设备管理
```
typescript
import
Jensen
from
'jensen-webusb'
;
async
function
manageBluetoothDevices
()
{
const
jensen
=
new
Jensen
();
try
{
await
jensen
.
connect
();
// 扫描蓝牙设备
console
.
log
(
'开始扫描蓝牙设备...'
);
const
devices
=
await
jensen
.
scanDevices
();
console
.
log
(
`找到
${
devices
.
length
}
个设备:`
);
devices
.
forEach
(
device
=>
{
console
.
log
(
`-
${
device
.
name
}
(
${
device
.
mac
}
)`
);
});
// 连接第一个设备
if
(
devices
.
length
>
0
)
{
const
result
=
await
jensen
.
connectBTDevice
(
devices
[
0
].
mac
);
console
.
log
(
'连接结果:'
,
result
.
result
);
// 获取连接状态
const
status
=
await
jensen
.
getBluetoothStatus
();
console
.
log
(
'蓝牙状态:'
,
status
);
}
}
catch
(
error
)
{
console
.
error
(
'蓝牙操作失败:'
,
error
);
}
}
```
### 案例3: 设备设置管理
```
typescript
// 注册 UserTask
import
Jensen
from
'jensen-webusb'
;
const
doRequestUserTask
=
async
()
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
async
function
configureDevice
()
{
const
jensen
=
new
Jensen
();
const
tid
=
't-'
+
new
Date
().
getTime
();
const
success
=
await
mgr
.
registerTask
(
sn
,
tid
);
try
{
await
jensen
.
connect
();
if
(
!
success
)
{
return
alert
(
'注册任务失败'
);
// 获取当前设置
}
const
settings
=
await
jensen
.
getSettings
();
console
.
log
(
'当前设置:'
,
settings
);
setUserTask
(
tid
);
console
.
log
(
'UserTask 已注册:'
,
tid
);
// 修改设置
};
await
jensen
.
setAutoRecord
(
true
);
await
jensen
.
setAutoPlay
(
false
);
await
jensen
.
setNotification
(
true
);
console
.
log
(
'设置已更新'
);
// 验证设置
const
newSettings
=
await
jensen
.
getSettings
();
console
.
log
(
'更新后的设置:'
,
newSettings
);
}
catch
(
error
)
{
console
.
error
(
'设置操作失败:'
,
error
);
}
}
```
### 案例4: 实时数据监控
// 释放 UserTask
const
doReleaseUserTask
=
async
()
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
await
mgr
.
unregisterTask
(
sn
,
userTask
);
setUserTask
(
null
);
console
.
log
(
'UserTask 已释放'
);
};
```
typescript
// 获取设备时间(需要 UserTask)
import
Jensen
from
'jensen-webusb'
;
const
doGetTime
=
async
()
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
async
function
monitorRealtimeData
()
{
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
const
jensen
=
new
Jensen
();
try
{
await
jensen
.
connect
();
// 开始实时数据传输
await
jensen
.
startRealtime
();
console
.
log
(
'实时数据传输已开始'
);
// 定期获取数据
const
interval
=
setInterval
(
async
()
=>
{
try
{
const
result
=
await
jensen
.
getRealtime
(
100
);
console
.
log
(
`接收到
${
result
.
data
.
length
}
字节数据`
);
// 处理数据...
try
{
processData
(
result
.
data
);
// 方式一:使用 getInstance
const
jensen
=
mgr
.
getInstance
(
sn
,
userTask
);
if
(
jensen
==
null
)
return
alert
(
'设备未连接'
);
const
time
=
await
jensen
.
getTime
(
1
);
alert
(
'设备时间: '
+
time
.
time
);
// 方式二:使用 executeWithUserTask(推荐)
// const time = await mgr.executeWithUserTask(sn, userTask, async (jensen) => {
// return await jensen.getTime(1);
// });
// alert('设备时间: ' + time.time);
}
catch
(
err
:
any
)
{
alert
(
'错误: '
+
err
.
message
);
}
};
// 下载文件(需要 UserTask)
const
doDownloadFile
=
async
(
fileName
:
string
)
=>
{
if
(
sn
==
null
)
return
alert
(
'请先连接设备'
);
if
(
userTask
==
null
)
return
alert
(
'请先注册 UserTask'
);
}
catch
(
error
)
{
try
{
console
.
error
(
'获取实时数据失败:'
,
error
);
const
jensen
=
mgr
.
getInstance
(
sn
,
userTask
);
clearInterval
(
interval
);
if
(
jensen
==
null
)
return
alert
(
'设备未连接'
);
}
},
1000
);
await
jensen
.
getFile
(
fileName
,
1024
,
(
data
)
=>
{
if
(
data
instanceof
Uint8Array
)
{
// 10秒后停止
console
.
log
(
'文件数据:'
,
data
);
setTimeout
(
async
()
=>
{
// 处理下载的数据...
clearInterval
(
interval
);
}
else
{
await
jensen
.
stopRealtime
();
console
.
error
(
'下载失败'
);
console
.
log
(
'实时数据传输已停止'
);
}
},
10000
);
});
}
catch
(
err
:
any
)
{
}
catch
(
error
)
{
alert
(
'错误: '
+
err
.
message
);
console
.
error
(
'实时监控失败:'
,
error
);
}
}
};
}
function
processData
(
data
:
Uint8Array
)
{
return
(
// 处理实时数据的逻辑
<>
console
.
log
(
'处理数据:'
,
data
);
<
h1
>
设备管理器
</
h1
>
{
/* 操作按钮 */
}
<
div
className=
"btn-container"
>
<
button
onClick=
{
doConnect
}
>
连接设备
</
button
>
<
button
onClick=
{
doRequestUserTask
}
>
注册任务
</
button
>
<
button
onClick=
{
doReleaseUserTask
}
>
释放任务
</
button
>
<
button
onClick=
{
doGetTime
}
>
获取时间
</
button
>
</
div
>
{
/* 设备信息 */
}
<
div
>
<
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)`
| 关闭指定设备的连接 |
function
JensenComponent
()
{
|
`onautoconnect(handler)`
| 注册自动连接成功的回调 |
const
[
jensen
]
=
useState
(()
=>
new
Jensen
());
|
`onconnectionstatechanged(handler)`
| 注册连接状态变化的回调 |
const
[
isConnected
,
setIsConnected
]
=
useState
(
false
);
useEffect
(()
=>
{
jensen
.
onconnect
=
()
=>
setIsConnected
(
true
);
jensen
.
ondisconnect
=
()
=>
setIsConnected
(
false
);
},
[
jensen
]);
// 组件逻辑...
}
```
###
在Vue项目中使用
###
# 任务管理
```
bash
| 方法 | 说明 |
npm
install
jensen-webusb
|------|------|
```
|
`registerTask(sn, tag)`
| 注册 UserTask(超时 3 秒) |
|
`unregisterTask(sn, tag)`
| 释放 UserTask |
|
`keepUserTaskAlive(sn, tag)`
| 保持 UserTask 活跃 |
|
`verifyUserTask(sn, tag)`
| 验证 UserTask 是否有效 |
|
`getCurrentUserTaskTag(sn)`
| 获取当前 UserTask 的标签 |
|
`executeWithUserTask(sn, tag, callback)`
| 验证后执行回调 |
```
typescript
#### 设备实例
// 在Vue组件中使用
import
{
ref
,
onMounted
}
from
'vue'
;
import
Jensen
from
'jensen-webusb'
;
export
default
{
setup
()
{
const
jensen
=
ref
(
new
Jensen
());
const
isConnected
=
ref
(
false
);
onMounted
(()
=>
{
jensen
.
value
.
onconnect
=
()
=>
isConnected
.
value
=
true
;
jensen
.
value
.
ondisconnect
=
()
=>
isConnected
.
value
=
false
;
});
return
{
jensen
,
isConnected
};
}
};
```
### 在Node.js项目中使用
| 方法 | 说明 |
|------|------|
|
`getInstance(sn, userTaskTag)`
| 获取受保护的 Jensen 实例 |
```
bash
#### 状态管理
npm
install
jensen-webusb
```
```
typescript
| 方法 | 说明 |
// 注意:WebUSB API仅在浏览器环境中可用
|------|------|
import
Jensen
from
'jensen-webusb'
;
|
`getDeviceState(sn, key)`
| 获取设备状态值 |
|
`setDeviceState(sn, key, value)`
| 设置设备状态值 |
|
`subscribeDeviceState(sn, key, listener)`
| 订阅状态变更 |
|
`isDeviceStateStale(sn, key, maxAge)`
| 检查状态是否过期 |
|
`dumpDeviceState(sn)`
| 导出状态快照(调试用) |
// 在浏览器环境中使用
### TaskManager
if
(
typeof
window
!==
'undefined'
)
{
const
jensen
=
new
Jensen
();
// 使用逻辑...
}
```
### 在TypeScript项目中使用
| 方法 | 说明 |
|------|------|
|
`registerInitializeTask(task)`
| 注册初始化任务 |
|
`registerTimerTask(task)`
| 注册定时任务 |
|
`registerBackgroundTask(task)`
| 注册后台任务 |
|
`applyUserTask(tag, timeout)`
| 申请 UserTask |
|
`releaseUserTask(tag)`
| 释放 UserTask |
|
`verifyUserTask(tag)`
| 验证 UserTask |
```
typescript
### DeviceStateStore
// 类型定义已包含在包中
import
Jensen
,
{
DeviceInfo
,
FileInfo
,
BluetoothDevice
}
from
'jensen-webusb'
;
const
jensen
=
new
Jensen
();
| 方法 | 说明 |
|------|------|
|
`get(key)`
| 获取状态值 |
|
`set(key, value)`
| 设置状态值(自动触发监听器) |
|
`has(key)`
| 检查状态是否存在 |
|
`getUpdateTime(key)`
| 获取最后更新时间 |
|
`isStale(key, maxAge)`
| 检查状态是否过期 |
|
`subscribe(key, listener)`
| 订阅状态变更(返回取消函数) |
|
`clear()`
| 清空所有状态 |
|
`clearListeners()`
| 清空所有监听器 |
async
function
typedExample
()
{
## 常见问题
const
deviceInfo
:
DeviceInfo
=
await
jensen
.
getDeviceInfo
();
const
files
:
FileInfo
[]
=
await
jensen
.
listFiles
();
const
bluetoothDevices
:
BluetoothDevice
[]
=
await
jensen
.
scanDevices
();
}
```
##
📋 版本依赖
##
# 1. 为什么调用 `getInstance()` 报错?
### 核心依赖
确保传入了
`userTaskTag`
参数:
```
tsx
// ❌ 错误
const
jensen
=
mgr
.
getInstance
(
sn
);
```
json
// ✅ 正确
{
const
jensen
=
mgr
.
getInstance
(
sn
,
userTaskTag
);
"dependencies"
:
{
"react"
:
"^19.0.0"
,
"react-dom"
:
"^19.0.0"
}
}
```
```
### 开发依赖
### 2. UserTask 被自动释放了?
```
json
{
"devDependencies"
:
{
"@types/node"
:
"^22.10.2"
,
"@types/react-dom"
:
"^19.0.2"
,
"@vitejs/plugin-react-swc"
:
"^3.7.2"
,
"prettier"
:
"^3.3.3"
,
"typescript"
:
"^5.7.2"
,
"vite"
:
"^6.0.3"
}
}
```
### 浏览器兼容性
UserTask 空闲超过 30 秒会被自动释放。对于长时间操作,定期调用:
```
tsx
mgr
.
keepUserTaskAlive
(
sn
,
userTaskTag
);
```
-
Chrome 67+
### 3. 如何知道设备断开了?
-
Edge 79+
-
Opera 54+
-
支持WebUSB API的现代浏览器
### Node.js版本要求
使用
`onconnectionstatechanged`
监听:
```
tsx
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
if
(
state
===
'disconnected'
)
{
console
.
log
(
'设备断开:'
,
dinfo
);
}
});
```
-
Node.js 16.0.0+
### 4. 状态订阅如何取消?
## 🛠️ 开发指南
`subscribeDeviceState`
返回取消函数:
```
tsx
const
unsubscribe
=
mgr
.
subscribeDeviceState
(
sn
,
'battery-status'
,
listener
);
### 本地开发
// 取消订阅
unsubscribe
?.();
```
```
bash
## 开发调试
# 克隆项目
git clone http://gitlab.sugrsugr.com/skye/jensen.git
cd
jensen
# 安装依赖
### 启动开发服务器
npm
install
# 启动开发服务器
```
bash
npm run dev
npm run dev
# 构建项目
npm run build
# 代码格式化
npm run prettier
```
```
###
项目结构
###
构建项目
```
bash
npm run build
```
```
jensen/
├── 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