Commit 5d3ca68c authored by martin hou's avatar martin hou

feat: 调整测试页面

parent 26be443f
...@@ -2,18 +2,55 @@ import { useEffect, useState } from 'react'; ...@@ -2,18 +2,55 @@ import { useEffect, useState } from 'react';
import Jensen, { BluetoothDevice } from '..'; import Jensen, { BluetoothDevice } from '..';
import './index.css'; import './index.css';
import { Logger } from './Logger' import { Logger } from './Logger'
import { mgr } from './utils/mgr';
const firmwareVersions = [
{ model: 'hidock-p1:mini', version: '2.1.6', url: 'https://jensen.test.hidock.com/firmwares/p1-mini-2.1.6.bin', remark: 'for samsung' },
{ model: 'hidock-p1:mini', version: '2.1.0', url: 'https://jensen.test.hidock.com/firmwares/mini-2.1.0.bin', remark: '' },
{ model: 'hidock-h1', version: '5.2.9', url: 'https://jensen.test.hidock.com/firmwares/hidock-h1-5.2.9.bin', remark: '' },
{ model: 'hidock-h1e', version: '6.2.9', url: 'https://jensen.test.hidock.com/firmwares/hidock-h1e-6.2.9.bin', remark: '' },
{ model: 'hidock-p1', version: '1.3.6', url: 'https://jensen.test.hidock.com/firmwares/p1-1.3.6.bin', remark: '' },
{ model: 'hidock-p1', version: '1.2.28', url: 'https://jensen.test.hidock.com/firmwares/hidock-p1-1.2.28.bin', remark: 'for testing' },
{ model: 'hidock-p1', version: '1.2.25', url: 'https://jensen.test.hidock.com/firmwares/hidock-p1-1.2.25.bin', remark: '' }
];
const jensen = new Jensen();
export function Home() { export function Home() {
const [files, setFiles] = useState<Jensen.FileInfo[]>([]); const [files, setFiles] = useState<Jensen.FileInfo[]>([]);
const [devices, setDevices] = useState<Jensen.BluetoothDevice[]>([]); const [devices, setDevices] = useState<Jensen.BluetoothDevice[]>([]);
const [greeting, setGreeting] = useState<string|null>(null); const [greeting, setGreeting] = useState<string|null>(null);
const [firmwares, setFirmwares] = useState<typeof firmwareVersions|null>(null);
const [logs, setLogs] = useState<string[]>([]);
const [sn, setSn] = useState<string|null>(null);
mgr.onconnectionstatechanged((state, dinfo) => {
console.log('onconnectionstatechanged', state, dinfo);
// alert(JSON.stringify(dinfo));
});
mgr.onautoconnect((dinfo) => {
// console.log('onautoconnect', dinfo);
setSn(dinfo.sn);
// alert(JSON.stringify(dinfo));
});
const getJensen = () => {
if (sn == null) return alert('Please connect to a device first'), null;
return mgr.getInstance(sn);
}
useEffect(() => { useEffect(() => {
// console.log(mgr);
mgr.tryconnect();
}, []);
useEffect(() => {
/*
jensen.connect(); jensen.connect();
jensen.onconnect = () => { jensen.onconnect = () => {
console.log('connect successfully'); console.log('connect successfully');
jensen.addWakeupListener(function() {
console.log('Wake up from sleep?');
});
jensen.getDeviceInfo().then((info) => { jensen.getDeviceInfo().then((info) => {
console.log('getDeviceInfo', info); console.log('getDeviceInfo', info);
alert(info.sn + ' connected'); alert(info.sn + ' connected');
...@@ -21,58 +58,101 @@ export function Home() { ...@@ -21,58 +58,101 @@ export function Home() {
console.log('getDeviceInfo error', e); console.log('getDeviceInfo error', e);
}); });
}; };
*/
// jensen.onerror = (e) => { // jensen.onerror = (e) => {
// console.log('onerror', e); // console.log('onerror', e);
// alert('此设备已经在其它已打开的HiNotes网页上建立连接'); // alert('此设备已经在其它已打开的HiNotes网页上建立连接');
// }; // };
}, []);
const getFilePart = () => { // 创建日志更新定时器
const file = files[4]; const logUpdateInterval = setInterval(() => {
const secondsLength = Math.ceil(file.length / file.duration) * 1000; // 从Logger.messages中获取最后500条记录
console.time('!!!time'); let last = Logger.messages.slice(-500).reverse();
jensen.getFilePart(file.name, secondsLength * 600, (res) => { // 将last转换为字符串数组
if (res instanceof Uint8Array) { let logStr: string[] = last.map((item) => {
console.timeEnd('!!!time'); return `[${item.level === 'error' ? 'x' : '*'}] [${new Date(item.time).toLocaleString()}] (${item.module} - ${item.procedure}) ${item.message}`;
}
}); });
setLogs(logStr);
}, 1000);
// 清理函数
return () => {
clearInterval(logUpdateInterval);
}; };
}, []);
const info = async () => {
// alert(sn);
let jensen = getJensen();
if (jensen == null) return;
// console.log('jensen -> ', jensen);
let info = await jensen?.getDeviceInfo();
// alert(JSON.stringify(info));
Logger.info('jensen', 'info', 'Device Info: ' + JSON.stringify(info));
}
let bluetoothDevices: BluetoothDevice[] = []; let bluetoothDevices: BluetoothDevice[] = [];
const bluetoothScan = async () => { const bluetoothScan = async () => {
let jensen = getJensen();
if (jensen == null) return;
setDevices([]); setDevices([]);
setGreeting('scan started at: ' + new Date().toLocaleString()); setGreeting('scan started at: ' + new Date().toLocaleString());
let devices = await jensen.scanDevices(); let count = prompt('Please Input the Scan Count:', '1');
if (count === undefined || count === null) return;
let countNum = parseInt(count);
if (isNaN(countNum) || countNum <= 0) return alert('Please Input the Correct Scan Count');
let rst = await jensen.startBluetoothScan(countNum, 5);
if (rst == null || rst.result != 'success') return alert('start bluetooth scan failed');
// console.log(devices); // console.log(devices);
// setDevices(devices);
setGreeting('scanning...');
}
const stopBluetoothScan = async () => {
let jensen = getJensen();
if (jensen == null) return;
let rst = await jensen.stopBluetoothScan(5);
if (rst == null || rst.result != 'success') return alert('stop bluetooth scan failed.');
setGreeting('scan stopped');
}
const getScanResults = async () => {
let jensen = getJensen();
if (jensen == null) return;
let devices = await jensen.getScanResults(5);
if (devices == null) return alert('get scan results failed.');
setDevices(devices); setDevices(devices);
setGreeting(null)
} }
const connecty = async () => {
const usb = (navigator as any).usb; const writeBluetoothDeviceList = async () => {
let conn = await usb.requestDevice({ let jensen = getJensen();
filters: [{ vendorId: 0x10d6 }, { vendorId: 0x3887 }] if (jensen == null) return;
}); let macs = prompt('Please Input the MACs (Separated by ","):', '00:00:00:00:00:00');
if (conn == null) return null; if (macs === undefined || macs === null) return;
// if (conn.opened) throw new Error('device_already_opened'); let macsList = macs.split(',').map((item) => item.trim());
await conn.open(); let rst = await jensen.writeBluetoothDeviceList(macsList, 5);
let jensen = new Jensen(Logger, conn); if (rst == null || rst.result != 'success') return alert('write bluetooth device list failed.');
jensen.onerror = (e) => { setGreeting('write bluetooth device list success');
console.log('onerror', e); }
alert('此设备已经在其它已打开的HiNotes网页上建立连接');
};
await jensen.initialize();
let dinfo = await jensen.getDeviceInfo();
if (dinfo)
console.log('dinfo', dinfo);
return null;
}
const connect = async () => { const connect = async () => {
await jensen.connect(); // await jensen.connect();
alert(jensen.getModel() + ' connected') // alert(jensen.getModel() + ' connected')
let info = await mgr.connect();
if (info && info.sn)
{
setSn(info.sn);
Logger.info('jensen', 'connect', info.sn + ' connected');
}
else
{
alert('connect failed');
}
} }
const disconnectBTDevice = async () => { const disconnectBTDevice = async () => {
let jensen = getJensen();
if (jensen == null) return;
let r = await jensen.disconnectBTDevice(); let r = await jensen.disconnectBTDevice();
console.log(r); console.log(r);
} }
...@@ -100,52 +180,62 @@ export function Home() { ...@@ -100,52 +180,62 @@ export function Home() {
} }
const doConnectBluetooth = async (mac:string) => { const doConnectBluetooth = async (mac:string) => {
// alert(mac); let jensen = getJensen();
if (jensen == null) return;
let rst = await jensen.connectBTDevice(mac, 10); let rst = await jensen.connectBTDevice(mac, 10);
alert('connect: ' + rst.result); alert('connect: ' + rst.result);
} }
const getTime = async () => { const getTime = async () => {
let jensen = getJensen();
if (jensen == null) return;
let time = await jensen.getTime(); let time = await jensen.getTime();
alert(JSON.stringify(time)); // alert(JSON.stringify(time));
Logger.info('jensen', 'getTime', 'Time: ' + JSON.stringify(time));
} }
const listFiles = async () => { const listFiles = async () => {
let jensen = getJensen();
if (jensen == null) return;
let fc = await jensen.getFileCount(); let fc = await jensen.getFileCount();
// alert(fc?.count); // alert(fc?.count);
let files = await jensen.listFiles(); let files = await jensen.listFiles();
files.sort((a, b) => {
return b.time.getTime() - a.time.getTime();
});
console.log(files); console.log(files);
setFiles(files) setFiles(files)
} }
const getBluetoothStatus = async () => { const getBluetoothStatus = async () => {
let jensen = getJensen();
if (jensen == null) return;
let info = await jensen.getBluetoothStatus(); let info = await jensen.getBluetoothStatus();
alert(JSON.stringify(info)); // alert(JSON.stringify(info));
Logger.info('jensen', 'getBluetoothStatus', 'Bluetooth: ' + JSON.stringify(info));
} }
/*
const readFilePartial = async() => { const readFilePartial = async() => {
if (files.length == 0) return alert('你没有录音文件,或是还没有查询过文件列表'); if (files.length == 0) return alert('You don\'t have any recording files, or you haven\'t queried the file list');
let s0 = prompt('请输入需要读取的文件序号,从0开始:', '0'); let s0 = prompt('Please Input the File Index (Start from 0):', '0');
let s1 = prompt('请输入开始位置,从0开始:', '0'); let s1 = prompt('Please Input the Start Position (Start from 0):', '0');
let s2 = prompt('请输入读取字节数量', '32'); let s2 = prompt('Please Input the Read Byte Count', '32');
if (s0 && s1 && s2) console.log(); if (s0 && s1 && s2) console.log();
else return; else return;
let index = parseInt(s0); let index = parseInt(s0);
let offset = parseInt(s1); let offset = parseInt(s1);
let length = parseInt(s2); let length = parseInt(s2);
if (isNaN(index) && isNaN(offset) || isNaN(length)) return; if (isNaN(index) && isNaN(offset) || isNaN(length)) return;
if (index >= files.length) return alert('请输入正确的文件序号'); if (index >= files.length) return alert('Please Input the Correct File Index');
let file = files[index]; let file = files[index];
if (offset < 0 || offset > file.length) return alert('请输入正确的读取开始位置'); if (offset < 0 || offset > file.length) return alert('Please Input the Correct Read Start Position');
if (length <= 0) return alert('请输入正确的文件读取长度'); if (length <= 0) return alert('Please Input the Correct Read Byte Count');
let data = await jensen.readFile(file.name, offset, length); let data = await jensen.readFile(file.name, offset, length);
console.log(data); console.log(data);
} }
*/
const updateDeviceTone = async () => { const updateDeviceTone = async () => {
let jensen = getJensen();
if (jensen == null) return;
let resp = await jensen.requestToneUpdate('826d9bac0b535e7babe02b389327a9f2', 1050688); let resp = await jensen.requestToneUpdate('826d9bac0b535e7babe02b389327a9f2', 1050688);
if (resp.code != 0x00) return alert(resp.result); if (resp.code != 0x00) return alert(resp.result);
...@@ -165,13 +255,21 @@ export function Home() { ...@@ -165,13 +255,21 @@ export function Home() {
} }
const updateUAC = async () => { const updateUAC = async () => {
// 92e66fd8cfd36f09c83fc61491899307 1024 let jensen = getJensen();
let resp = await jensen.requestUACUpdate('92e66fd8cfd36f09c83fc61491899307', 1024); if (jensen == null) return;
const uac_firmwares = {
'hidock-h1' : { url : '/firmwares/jensen-1-uac.bin', md5 : '92e66fd8cfd36f09c83fc61491899307' },
'hidock-h1e' : { url : '/firmwares/jensen-3-uac.bin', md5 : 'c355c5bf8cc8a8da8bea6b6315ad7649' }
};
let uac = uac_firmwares[jensen.getModel() as keyof typeof uac_firmwares];
if (!uac) return alert('No UAC firmware for your device');
let resp = await jensen.requestUACUpdate(uac.md5, 1024);
if (resp.code != 0x00) return alert(resp.result); if (resp.code != 0x00) return alert(resp.result);
// 下载文件并完成更新处理 // 下载文件并完成更新处理
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open('GET', '/UAC.bin'); xhr.open('GET', uac.url);
xhr.setRequestHeader('Content-Type', 'application/octet-stream'); xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.responseType = 'arraybuffer'; xhr.responseType = 'arraybuffer';
xhr.onload = function(e) xhr.onload = function(e)
...@@ -185,19 +283,23 @@ export function Home() { ...@@ -185,19 +283,23 @@ export function Home() {
} }
const batteryStatus = async () => { const batteryStatus = async () => {
let jensen = getJensen();
if (jensen == null) return;
let status = await jensen.getBatteryStatus(5); let status = await jensen.getBatteryStatus(5);
alert(JSON.stringify(status)); // alert(JSON.stringify(status));
Logger.info('jensen', 'batteryStatus', 'Battery: ' + JSON.stringify(status));
} }
let filename = ''; let filename = '';
let filelength = 0; let filelength = 0;
const transferFile = async () => { const transferFile = async () => {
if (files == null || files.length == 0) return alert('请先点击List Files'); if (files == null || files.length == 0) return alert('Please Click "List Files" first');
let idx = prompt('请输入需要获取的文件序号(从0开始):', '0'); let idx = prompt('Please Input the File Index (Start from 0):', '0');
if (idx === undefined || idx === null) return; if (idx === undefined || idx === null) return;
let num = parseInt(idx); let num = parseInt(idx);
if (isNaN(num) || num < 0 || num >= files.length) return alert('请输入正确的序号'); if (isNaN(num) || num < 0 || num >= files.length) return alert('Please Input the Correct Index');
let file = files[num]; let file = files[num];
// 2025Jun24-165914-Wip00.hda, 232704 @ 19392
filename = file.name; filename = file.name;
filelength = file.length; filelength = file.length;
...@@ -205,78 +307,183 @@ export function Home() { ...@@ -205,78 +307,183 @@ export function Home() {
} }
const get_file = () => { const get_file = () => {
let jensen = getJensen();
if (jensen == null) return;
let recvBytes = 0;
let stime = new Date().getTime();
jensen.getFile(filename, filelength, function(data) { jensen.getFile(filename, filelength, function(data) {
console.log('data', data.length); // console.log('data', data.length);
recvBytes += data.length;
if (recvBytes >= filelength) {
let cost = new Date().getTime() - stime;
alert('File Transfer Complete, Cost: ' + cost + ' ms');
}
}, function(recvBytes) { }, function(recvBytes) {
let percent = Math.floor(recvBytes / filelength * 100); let percent = Math.floor(recvBytes / filelength * 100);
console.log('progress', percent); // console.log('progress', percent);
}); });
} }
const writeSN = async () => { /*
let nsn = prompt('请输入新的SN号', ''); const test = async () => {
if (nsn && nsn.match(/^HD(H1|1E|P1|PM)\w{9}$/gi)) await jensen.reconnect();
{ let rst = await jensen.getDeviceInfo();
let rst = await jensen.writeSerialNumber(nsn); if (rst) alert(rst.sn + ' reconnected...');
alert(rst.result); else alert('Something went wrong...');
}
*/
const setWebUSBTimeout = async () => {
let jensen = getJensen();
if (jensen == null) return;
let ts = prompt('Please Input the Timeout (ms):', '10000');
if (ts === undefined || ts === null) return;
let timeout = parseInt(ts);
if (isNaN(timeout) || timeout <= 0) return alert('Please Input the Correct Timeout');
await jensen.setWebUSBTimeout(timeout, 30);
Logger.info('jensen', 'setWebUSBTimeout', 'Set Timeout Success');
}
const getWebUSBTimeout = async () => {
let jensen = getJensen();
if (jensen == null) return;
let rst = await jensen.getWebUSBTimeout(5);
// alert(JSON.stringify(rst));
Logger.info('jensen', 'getWebUSBTimeout', 'WebUSB Timeout: ' + JSON.stringify(rst));
} }
const listFirmwares = async () => {
let jensen = getJensen();
if (jensen == null) return;
let model = jensen.getModel();
let versions = firmwareVersions.filter((item) => item.model == model);
setFirmwares(versions);
} }
const connectx = async () => { const updateFirmware = async (version: string, url: string) => {
const usb = (navigator as any).usb; let jensen = getJensen();
usb.onconnect = (evt: any) => { if (jensen == null) return;
console.log(evt); // let rst = await jensen.requestFirmwareUpgrade(version, url);
// alert(JSON.stringify(rst));
if (!confirm('Ready to update firmware, DO NOT close/refresh this page, and wait for the update to complete.\n\n Confirm to continue')) return;
// 版本号类似于1.2.3,需要转换为0x010203
// 将类似于"1.2.3"的字符串转换为0x010203的数值
let versionNumber = 0;
if (version && typeof version === 'string') {
let parts = version.split('.');
if (parts.length === 3) {
versionNumber = (parseInt(parts[0]) << 16) | (parseInt(parts[1]) << 8) | (parseInt(parts[2]));
} else {
alert('invalid version number');
return;
}
} else {
alert('invalid version number');
return;
} }
usb.getDevices().then(async (devices: any[]) => {
for (let i = 0; i < devices.length; i++) Logger.info('jensen', 'ota', 'firmware download start');
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status != 200) return alert('Http Error: ' + this.status);
Logger.info('jensen', 'ota', 'firmware download complete');
Logger.info('jensen', 'ota', 'firmware upgrade start');
jensen.requestFirmwareUpgrade(versionNumber, this.response.byteLength, 30).then((info) => {
Logger.info('jensen', 'ota', 'firmware upgrade: ' + JSON.stringify(info));
if (info.result == 'accepted') {
// alert('firmware upgrade accepted');
Logger.info('jensen', 'ota', 'firmware transfer start');
jensen.uploadFirmware(Array.from(new Uint8Array(this.response)), 30).then((info) => {
if (info == null)
{ {
let dev = devices[i]; Logger.info('jensen', 'ota', 'firmware transfer timedout');
await dev.open(); }
let jensen = new Jensen(Logger, dev); else
await jensen.initialize(); {
let rst = await jensen.getDeviceInfo(); Logger.info('jensen', 'ota', 'firmware transfer complete: ' + JSON.stringify(info));
console.log(rst); Logger.info('jensen', 'ota', 'please wait for the firmware upgrade to complete');
}
});
}
else
{
alert('cannot upgrade firmware: ' + info.result);
} }
}); });
} }
xhr.send();
}
const test = async () => { const turnPopupOn = async () => {
await jensen.reconnect(); let jensen = getJensen();
let rst = await jensen.getDeviceInfo(); if (jensen == null) return;
if (rst) alert(rst.sn + ' reconnected...'); // await jensen.();
else alert('what the fuck????'); let rst = await jensen.setNotification(true, 5);
Logger.info('jensen', 'popup', 'Turn Popup On: ' + JSON.stringify(rst));
}
const turnPopupOff = async () => {
let jensen = getJensen();
if (jensen == null) return;
// await jensen.turnPopupOff();
let rst = await jensen.setNotification(false, 5);
Logger.info('jensen', 'popup', 'Turn Popup On: ' + JSON.stringify(rst));
} }
const getPairedDevices = async () => { const getSettings = async () => {
let devices = await jensen.getPairedDevices(); let jensen = getJensen();
alert(JSON.stringify(devices)); if (jensen == null) return;
let rst = await jensen.getSettings(5);
Logger.info('jensen', 'settings', 'Get Settings: ' + JSON.stringify(rst));
} }
return ( return (
<> <>
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px', padding: '16px', alignItems: 'center', flexWrap: 'wrap' }}> <div className="btn-container" style={{ display: 'flex', flexDirection: 'row', gap: '16px', padding: '16px', alignItems: 'center', flexWrap: 'wrap' }}>
<button onClick={connecty}>连接新设备</button> <button onClick={connect}>Connect</button>
<button onClick={connect}>连接</button> <button onClick={info}>Info</button>
<button onClick={getFilePart}>获取文件</button> <button onClick={getTime}>Get Time</button>
<button onClick={writeSN}>SN写号</button> <button onClick={listFiles}>List Files</button>
<button onClick={getTime}>获取时间</button> <button onClick={transferFile}>Transfer</button>
<button onClick={listFiles}>文件列表</button> <button onClick={batteryStatus}>Battery</button>
<button onClick={transferFile}>传输文件</button> <button onClick={getBluetoothStatus}>Bluetooth Status</button>
<button onClick={batteryStatus}>电池电量</button> <button onClick={bluetoothScan}>Start Scan</button>
<button onClick={getBluetoothStatus}>蓝牙连接状态</button> <button onClick={stopBluetoothScan}>Stop Scan</button>
<button onClick={bluetoothScan}>蓝牙扫描</button> <button onClick={getScanResults}>Scan Results</button>
<button onClick={disconnectBTDevice}>蓝牙断开</button> <button onClick={writeBluetoothDeviceList}>Write MACs</button>
<button onClick={getPairedDevices}>Paired</button> <button onClick={disconnectBTDevice}>Disconnect</button>
<button onClick={readFilePartial}>Read File Partial</button> <button onClick={updateDeviceTone}>Update Tone</button>
<button onClick={updateDeviceTone}>更新提示音</button> <button onClick={updateUAC}>Update UAC</button>
<button onClick={updateUAC}>更新UAC</button> <button onClick={setWebUSBTimeout}>Set Timeout</button>
<button onClick={test}>测试重连</button> <button onClick={getWebUSBTimeout}>Get Timeout</button>
<button onClick={listFirmwares}>Firmwares</button>
<button onClick={turnPopupOn}>Turn Popup On</button>
<button onClick={turnPopupOff}>Turn Popup Off</button>
<button onClick={getSettings}>Get Settings</button>
</div>
<div className="result-container">
<div className="list-container">
{
firmwares && firmwares.length ? (
<div style={{ padding: '0px 0px 0px 30px', marginBottom: '20px' }}>
<h3>Firmwares: </h3>
<ol style={{ padding: '0px 0px 0px 20px', 'listStyle': 'none', lineHeight: '20px' }}>
{
firmwares.map((item, index) => {
return <li key={item.version}>{item.version} {item.remark ? '- (' + item.remark + ')' : ''} - <a href="#" onClick={() => { updateFirmware(item.version, item.url) }}>Update</a></li>
})
}
</ol>
</div> </div>
) : (<></>)
}
<div id="files" style={{ padding: '0px 0px 0px 30px', marginBottom: '20px' }}> <div id="files" style={{ padding: '0px 0px 0px 30px', marginBottom: '20px' }}>
<h3>Files: </h3> <h3>Files: </h3>
<ol style={{ padding: '0px 0px 0px 30px', 'listStyle': 'none' }}> <ol style={{ padding: '0px 0px 0px 30px', 'listStyle': 'none' }}>
{ files.map((item, index) => { { files.map((item, index) => {
return <li key={item.name}>{index} - {item?.name}, {item?.length} @ {Math.floor(item.duration / 1000 / 60)} mins</li> return <li key={item.name}>{index} - {item?.name}, {item?.length} @ {item.duration}</li>
})} })}
</ol> </ol>
</div> </div>
...@@ -297,6 +504,11 @@ export function Home() { ...@@ -297,6 +504,11 @@ export function Home() {
) )
} }
</div> </div>
</div>
<div className="log-container">
<textarea id="log" style={{ width: '100%', height: '100%' }} value={logs.join('\n')} readOnly />
</div>
</div>
</> </>
); );
} }
import Jensen, { DeviceInfo, ScheduleInfo } from "../../";
import { Logger } from "../Logger";
// 指令参数
export type CommandOptions = {
exclusive?: boolean; // 是否排它性的,在排它性的事务执行期间,其它所有的新事务都不再等待而是直接返回错误
expires?: number; // 超时时长
retry?: number; // 重试次数
wait?: number; // 等待前一个事务完结的最大时长
reconnect?: boolean; // 是否重连来确保事务的成功
params?: any[]; // 原方法的参数
}
// 指令代理函数
export type CommandProxy = (jensen: Jensen | null) => Promise<any> | undefined;
// 指令请求
export type CommandRequest = {
id: string;
command: CommandProxy;
options: CommandOptions;
createTime: number;
}
export type CommandPromise = {
id: string;
resolve: (value:any) => void;
reject: (error:any) => void;
timeout: number;
}
// 定时任务
export type ScheduledTask = {
interval: number;
command: CommandProxy;
onComplete?: (data: any) => void;
}
// 定时任务信息,增加了执行结果的记录
type ScheduledTaskInfo = {
interval: number;
lastExecuteTime: number;
executedCount: number;
command: CommandProxy;
onComplete?: (data: any) => void;
}
type AutoConnectEventHandler = (dinfo: DeviceInfo) => void;
type ConnectionEventHandler = (state: ConnectionStatus, dinfo: DeviceInfo | string | null) => void;
// 连接状态枚举
export type ConnectionStatus = 'connect-timeout' | 'init' | 'connected' | 'reconnected' | 'disconnected';
// exec('set-time', { exclusive: false, expires: 5, })
// setTime(false, true, new Date(), 5)
//
// 连接管理
export class ConnectionManager
{
private logger: typeof Logger;
private connections: Map<string, Jensen>;
private onautoconnectEventListener: AutoConnectEventHandler | null = null;
private onconnectionstatechangedListener: ConnectionEventHandler | null = null;
constructor (_logger: typeof Logger) {
this.logger = _logger;
this.connections = new Map<string, Jensen>();
this.registerUSBEventListener();
}
async registerUSBEventListener () {
const usb = (navigator as any).usb;
const self = this;
usb.onconnect = (evt: any) => {
try
{
let dev = evt.device;
if (dev.vendorId != 0x10d6 && dev.vendorId != 0x3887) return;
self._connect(dev);
}
catch(ex)
{
this.logger.error('jensen', 'onconnect', String(ex));
}
}
usb.ondisconnect = (evt: any) => {
try
{
let dev = evt.device;
if (dev.vendorId != 0x10d6 && dev.vendorId != 0x3887) return;
self._disconnect(dev);
}
catch(ex)
{
this.logger.error('jensen', 'ondisconnect', String(ex));
}
}
}
// 尝试连接,遍历所有的WebUSB设备,如果是HiDock设备,则直接建立连接,并且还要
async tryconnect () {
const usb = (navigator as any).usb;
const self = this;
usb.getDevices().then(async (devices: any[]) => {
for (let i = 0; i < devices.length; i++)
{
let dev = devices[i];
if (dev.opend) continue;
if (dev.vendorId != 0x10d6 && dev.vendorId != 0x3887) continue;
// console.log('auto connect', dev);
self.logger.info('jensen', 'tryconnect', 'VID: ' + dev.vendorId + ', PID: ' + dev.productId);
// 使用dev创建新的Jensen实例
try
{
self._connect(dev);
}
catch(ex)
{
self.logger.error('jensen', 'tryconnect', String(ex));
}
}
});
}
// 连接事件
private async _onstatechanged (state: ConnectionStatus, jensen: Jensen) {
}
// 当设备断开连接时触发,如果设备是之前已经连接过的,那还需要触发ondisconnect事件
private async _disconnect(dev: any) {
let self = this;
this.connections.forEach((j, k) => {
let ud = j.getUSBDevice();
if (ud == dev)
{
// console.log(k + ' disconnected...');
self.logger.info('jensen', 'ondisconnect', k + ' disconnected');
self.onconnectionstatechangedListener?.('disconnected', k);
}
});
}
// 当设备连接时触发,如果设备是之前已经连接过的,那还需要触发onconnect事件
private async _connect (dev: any) {
await dev.open();
let inst = new Jensen(Logger, dev);
this.logger.info('jensen', 'auto-connect', 'initialize');
await inst.initialize();
this.onconnectionstatechangedListener?.('init', inst.getModel());
let dinfo: DeviceInfo | null = null;
for (let i = 0; i < 30; i++)
{
try
{
dinfo = await inst.getDeviceInfo(5);
if (dinfo == null)
{
inst.reconnect();
await sleep(100);
continue;
}
break;
}
catch(e)
{
// ...
}
}
if(dinfo == null) {
console.log('dinfo is null', dev);
}
if(dinfo == null && dev.opened) {
this.onconnectionstatechangedListener?.('connect-timeout', inst.getModel());
try {
dev.close();
dev.forget();
} catch (error) {
// do nothing
}
return;
}
this.logger.info('jensen', 'auto-connect', JSON.stringify(dinfo));
if (dinfo)
{
// 如果是之前已连接的设备,那就需要触发onconnect事件
if (this.connections.has(dinfo.sn))
{
this.connections.set(
dinfo.sn,
// new CommandManager(dinfo, inst)
inst
);
this.logger.info('jensen', 'onconnect', dinfo.sn + ' reconnected');
// let jensen = this.connections.get(dinfo.sn);
// jensen?.setUSBDevice(dev);
this.onconnectionstatechangedListener?.('reconnected', dinfo);
return;
}
this.connections.set(
dinfo.sn,
// new CommandManager(dinfo, inst)
inst
);
try
{
this.onautoconnectEventListener?.(dinfo);
this.onconnectionstatechangedListener?.('connected', dinfo);
}
catch(err)
{
this.logger.error('jensen', 'autoconnect', String(err));
}
}
}
onautoconnect (eventListener: AutoConnectEventHandler) {
this.onautoconnectEventListener = eventListener;
}
// 这个方法用于管理 连接/断开连接 的回调
onconnectionstatechanged (eventHandler: ConnectionEventHandler)
{
this.onconnectionstatechangedListener = eventHandler;
}
// 获取一个Jensen实例
getInstance(tag: string) {
return this.connections.get(tag);
}
// 设置一个Jensen实例
setInstance(tag: string, jensen: Jensen, oldTag: string | null = null) {
if (oldTag) this.connections.delete(oldTag);
this.connections.set(tag, jensen);
}
// 主动连接新设备,如果没有连接成功或是未连接,则直接返回null,如果用户选择的是已连接的设备,则直接抛出异常
// 但是异常的表现形式需要另外商量沟通
async connect () {
const usb = (navigator as any).usb;
let self = this;
let conn = await usb.requestDevice({
filters: [{ vendorId: 0x10d6 }, { vendorId: 0x3887 }]
});
if (conn == null) return null;
if (conn.opened) return Logger.error('jensen', 'connect', 'device already opened');
await conn.open();
let jensen = new Jensen(self.logger, conn);
jensen.onerror = (err) => {
self.logger.error('jensen', 'connect error', String(err));
// myMessage.error(i18n.t('connection.error'), 10000);
}
await jensen.initialize();
let dinfo = await jensen.getDeviceInfo();
// @ts-ignore
jensen.onerror = null;
if (dinfo)
{
self.connections.set(
dinfo.sn,
// new CommandManager(dinfo, inst)
jensen
);
return dinfo
}
return null;
}
// 关闭连接
async close (sn: string) {
let jensen = this.getInstance(sn);
if (jensen) jensen.disconnect();
this.connections.delete(sn);
}
}
// 指令管理/代理
class CommandManager
{
// 设备信息
private serialNumber: string | null = null;
// 命令队列
private commands:CommandRequest[] = [];
// 指令Promise响应
private directives: Map<string, CommandPromise> = new Map<string, CommandPromise>();
// 定时任务
private tasks: ScheduledTaskInfo[] = [];
// 是否暂停定时任务
private suspendTimer: boolean = false;
// Jensen实例
private jensen: Jensen | null = null;
// 当前事务
private currentTask: String | null = null;
constructor(dinfo: DeviceInfo, jensen: Jensen)
{
// TODO: 要不要完全保存deviceInfo?
this.serialNumber = dinfo.sn;
this.jensen = jensen;
// 开启定时器
let self = this;
window.setInterval(function() {
self._executeTimer();
}, 1000);
}
dump () {
// TODO: 完成内部成员的状态输出,用于跟踪连接和事务状态
console.log('###############################################################');
console.log('SerialNumber', this.serialNumber);
console.log('Tasks', this.tasks);
console.log('Commands', this.commands);
console.log('SuspendTimer', this.suspendTimer);
console.log('Jensen Instance', this.jensen);
}
setup () {
// 在连接初次建立时需要做的初始化工作,是不是放在这里还没有想好
}
// onconnect事件如何触发?
// 是不是应该全局唯一触发?好像至少多少时间上就应该重新触发一次
// 或者是每次触发,然后可以被别的地方接管走
// 另外,还有一个问题,如果断开了连接再次连接上来,会话都不同了,还是要按SN做标识,否则多连接管理会出问题
// 增加一个新指令消息
// 怎么样触发新指令呢?
async getDeviceInfo(secs?: number) {
// 当前是否可以执行?
// 我是不是要分一下组?
// 我是不是要加一个模式转换的方法?
return this.jensen?.getDeviceInfo(secs);
// return this.request((jensen) => jensen?.getDeviceInfo(), { reconnect: false });
}
getSerialNumber () {
return this.serialNumber;
}
// 更改模式,要不要超时时长?在超过多长时间后要销毁或者说是恢复过去?
// 1. 当前是什么模式
// 2. 请求指令的是来自于哪个模式?
// 3. 整一个事务控制吧
// 事务开始或结束,不传参数就是事务结束了
// 对于每一个事务而言,应该要尽量确保它执行的成功的,包括重连尝试
async begin(secs: number = 5) {
// 如果应用端尝试变更事务切换不过去,那就应该一直等
// 这里是不是要弄一个可以打断的?如果事务与当前事务一致,又应该怎么办?还是要阻止吧?
// 结束事务
if (this.currentTask)
{
throw new Error("pending task: " + this.currentTask);
}
let tid = this.uuid();
this.currentTask = tid;
return tid;
}
end (id: string) {
if (this.currentTask != id) throw new Error('invalid task: ' + this.currentTask + ' --> ' + id);
this.currentTask = null;
}
// 开始/停止定时任务
// 但是如果停止的话,也只是停止下一轮的执行,要不要等待全部停止完成再说?
startTimer () {
this.suspendTimer = false;
}
stopTimer() {
this.suspendTimer = true;
}
private async _executeTimer () {
if (this.suspendTimer) return;
// 遍历所有的定时器任务
for (let i = 0; i < this.tasks.length; i++)
{
let item = this.tasks[i];
let now = new Date().getTime() / 1000;
if (now - item.lastExecuteTime < item.interval) continue;
try
{
let rst = await item.command.apply(null, [this.jensen]);
item.onComplete?.apply(null, [rst]);
/*
item.command.apply(null, [this.jensen])?.then((rst) => {
item.onComplete?.apply(null, [ rst ]);
});
*/
}
catch(ex)
{
console.error(ex);
}
finally
{
item.lastExecuteTime = now;
}
}
}
// 注册定时任务,返回值为它在列表中的索引位置,可能后面有要解除定时执行的需要,先预留一下
// 这里有个问题,我要如何完成回调呢?
async registerTimer(task: ScheduledTask) {
let len = this.tasks.length;
this.tasks.push({
interval: task.interval,
command: task.command,
lastExecuteTime: 0,
executedCount: 0
});
return len;
}
// 指令分两种,有复杂结构的返回值的或是普通boolean类型的两种
async request (func: CommandProxy, opts: CommandOptions) {
// TODO: 需要在这里注册一个Promise
let self = this;
let rid = this.uuid();
let future = new Promise((resolve, reject) => {
self.directives.set(rid, {
id: rid,
resolve: resolve,
reject: reject,
timeout: opts.expires || 5
});
});
this.commands.push({ command: func, id: rid, options: opts, createTime: new Date().getTime() });
return future;
}
// 需要怎么样持续的执行呢?
async execute(func: CommandProxy, opts: CommandOptions) {
let tid = await this.begin(opts.wait);
try
{
let args: any[] | undefined = opts.params;
let execCount = 0;
let maxTries = (opts.retry || 0) + 1;
do {
execCount += 1;
try
{
let rst: any = await func(this.jensen);
if (rst) return rst;
if (opts.reconnect)
{
// TODO: 连接重新建立
this.logger.info('jensen', 'execute', 'try reconnect');
}
}
catch(ex)
{
console.error(ex);
}
}
while (execCount < maxTries);
}
finally
{
this.end(tid);
}
}
private async tryReconnect()
{
try
{
// TODO: 需要准备一个新的reconnect方法,使用同一个WebUSB实例来close/open来实现重连,否则必然会乱掉
this.jensen?.reconnect();
}
catch(ex)
{
console.error(ex);
}
}
async getTime (timeout?: number) {
return this.jensen?.getTime(timeout);
}
async setTime (date: Date, timeout?: number) {
return this.jensen?.setTime(date, timeout);
}
async getRecordingFile () {
return this.jensen?.getRecordingFile();
}
async getFileCount (timeout?: number) {
return this.jensen?.getFileCount(timeout);
}
async listFiles (timeout?: number) {
return this.jensen?.listFiles(timeout);
}
async readFile (fname: string, offset: number, length: number) {
return this.jensen?.readFile(fname, offset, length);
}
async getFile (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) {
return this.jensen?.getFile(fileName, length, on, onprogress);
}
async getFilePart (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) {
return this.jensen?.getFilePart(fileName, length, on, onprogress);
}
async getFileBlock (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void) {
return this.jensen?.getFileBlock(fileName, length, on);
}
async requestFirmwareUpgrade (vn: number, length: number, timeout?: number) {
return this.jensen?.requestFirmwareUpgrade(vn, length, timeout);
}
async uploadFirmware (data: number[], timeout?: number, onProgress?: (cur: number, total: number) => void) {
return this.jensen?.uploadFirmware(data, timeout, onProgress);
}
async beginBNC (timeout?: number) {
return this.jensen?.beginBNC(timeout);
}
async endBNC (timeout?: number) {
return this.jensen?.endBNC(timeout);
}
async deleteFile (fileName: string) {
return this.jensen?.deleteFile(fileName);
}
async getSettings (timeout?: number) {
return this.jensen?.getSettings(timeout);
}
async setAutoRecord (enable: boolean, timeout?: number) {
return this.jensen?.setAutoRecord(enable, timeout);
}
async setAutoPlay (enable: boolean, timeout?: number) {
return this.jensen?.setAutoPlay(enable, timeout);
}
async setNotification (enable: boolean, timeout?: number) {
return this.jensen?.setNotification(enable, timeout);
}
async setBluetoothPromptPlay (enable: boolean, timeout?: number) {
return this.jensen?.setBluetoothPromptPlay(enable, timeout);
}
async getCardInfo (timeout?: number) {
return this.jensen?.getCardInfo(timeout);
}
async formatCard (timeout?: number) {
return this.jensen?.formatCard(timeout);
}
async factoryReset (timeout?: number) {
return this.jensen?.factoryReset(timeout);
}
async restoreFactorySettings (timeout?: number) {
return this.jensen?.restoreFactorySettings(timeout);
}
async recordTestStart (type: number, timeout?: number) {
return this.jensen?.recordTestStart(type, timeout);
}
async recordTestEnd (type: number, timeout?: number) {
return this.jensen?.recordTestEnd(type, timeout);
}
async test (timeout?: number) {
return this.jensen?.test(timeout);
}
async writeSerialNumber (sn: string) {
return this.jensen?.writeSerialNumber(sn);
}
async getRealtimeSettings () {
return this.jensen?.getRealtimeSettings();
}
async startRealtime () {
return this.jensen?.startRealtime();
}
async pauseRealtime () {
return this.jensen?.pauseRealtime();
}
async stopRealtime () {
return this.jensen?.stopRealtime();
}
async getRealtime (frames: number) {
return this.jensen?.getRealtime(frames);
}
async scanDevices (timeout?: number) {
return this.jensen?.scanDevices(timeout);
}
async connectBTDevice (mac: string, timeout?: number) {
return this.jensen?.connectBTDevice(mac, timeout);
}
async disconnectBTDevice (timeout?: number) {
return this.jensen?.disconnectBTDevice(timeout);
}
async getBluetoothStatus (timeout?: number) {
return this.jensen?.getBluetoothStatus(timeout);
}
async requestToneUpdate (signature: string, size: number, timeout?: number) {
return this.jensen?.requestToneUpdate(signature, size, timeout);
}
async updateTone (data: Uint8Array, timeout?: number) {
return this.jensen?.updateTone(data, timeout);
}
async requestUACUpdate (signature: string, size: number, timeout?: number) {
return this.jensen?.requestUACUpdate(signature, size, timeout);
}
async updateUAC (data: Uint8Array, timeout?: number) {
return this.jensen?.updateUAC(data, timeout);
}
async sendScheduleInfo (info: ScheduleInfo[]) {
return this.jensen?.sendScheduleInfo(info);
}
// 生成唯一id,暂时用随机数生成,后面最好用顺序递增的整数比较好
private uuid () {
return String(Math.floor(1000_0000_0000_0000 + Math.random() * 1000_0000_0000_0000));
}
}
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export const mgr = new ConnectionManager(Logger);
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment