Commit 33a7f2df authored by Skye Yu's avatar Skye Yu

init jensen package

parents
Pipeline #1879 canceled with stages
.idea
.vscode
node_modules
\ No newline at end of file
{
"printWidth": 140,
"singleQuote": true,
"tabWidth": 4
}
import { Jensen } from './src/jensen';
export default Jensen;
{
"name": "jensen",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jensen",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"prettier": "^3.3.3",
"typescript": "^5.5.4"
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
{
"name": "jensen",
"version": "1.0.0",
"description": "webUSB Tool",
"scripts": {
"prettier": "prettier --write ."
},
"keywords": [
"WebUSB"
],
"author": "Skye",
"license": "ISC",
"devDependencies": {
"prettier": "^3.3.3",
"typescript": "^5.5.4"
}
}
declare class MessageDecoder {
constructor();
decode: (msg: any) => any;
}
export type DeviceInfo = {
sn: string;
versionNumber: number;
versionCode: string;
};
type ReturnStruct = {
common: { result: 'failed' | 'success' };
};
declare class Jensen {
onconnect: Function;
onmessage: (data: any) => void | null;
constructor();
connect(): Promise<any, any>;
init(): Promise<boolean>;
getDeviceInfo: (time?: number) => Promise<DeviceInfo>;
listFiles: (time?: number) => Promise<FileInfo[]>;
tryconnect: () => boolean;
getFile: (flieName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress: (size: number) => void) => any;
requestFirmwareUpgrade: (
vn: number,
length: number,
time?: number,
) => Promise<{
result: 'accepted' | 'fail';
}>;
beginBNC: (time?: number) => Promise<ReturnStruct['common']>;
endBNC: (time?: number) => Promise<ReturnStruct['common']>;
setTime: (date: Date, timeout?: number) => Promise<ReturnStruct['common']>;
deleteFile: (fileName: string) => Promise<{ result: string }>;
uploadFirmware: (
data: number[],
seconds?: number,
onProgress?: (cur: number, total: number) => void,
) => Promise<ReturnStruct['common']>;
getTime: (time?: number) => Promise<{
time: string;
}>;
getSettings: (time?: number) => Promise<{
autoRecord: boolean;
autoPlay: boolean;
notification?: boolean;
} | null>;
setAutoRecord: (enable: boolean, time?: number) => Promise<ReturnStruct['common']>;
setAutoPlay: (enable: boolean, time?: number) => Promise<ReturnStruct['common']>;
isConnected: () => boolean;
setNotification: (state: boolean) => void;
ondisconnect?: Function;
isStopConnctionCheck: boolean;
getRecordingFile: () => Promise<{
recording: null | string;
createTime: string;
createDate: string;
}>;
getCardInfo: (seconds?: number) => Promise<{ used: number; capacity: number; status: string }>;
formatCard: (seconds?: number) => Promise<ReturnStruct['common']>;
factoryReset: (seconds?: number) => Promise<ReturnStruct['common']>;
getModel: () => Promise<string>;
getFileCount: (seconds?: number) => Promise<{ count: number } | null>;
}
declare module 'device' {
export { MessageDecoder, Jensen };
}
import { Logger } from './utils/utils';
const QUERY_DEVICE_INFO = 0x01;
const QUERY_DEVICE_TIME = 0x02;
const SET_DEVICE_TIME = 0x03;
const QUERY_FILE_LIST = 0x04;
const TRANSFER_FILE = 0x05;
const QUERY_FILE_COUNT = 0x06;
const DELETE_FILE = 0x07;
const REQUEST_FIRMWARE_UPGRADE = 0x08;
const FIRMWARE_UPLOAD = 0x09;
const DEVICE_MSG_TEST = 0x0a;
const BNC_DEMO_TEST = 0x0a;
const GET_SETTINGS = 0x0b;
const SET_SETTINGS = 0x0c;
const READ_CARD_INFO = 0x10;
const FORMAT_CARD = 0x11;
const GET_RECORDING_FILE = 0x12;
const FACTORY_RESET = 0xf00b;
const COMMAND_NAMES = [
'invalid-0',
'get-device-info',
'get-device-time',
'set-device-time',
'get-file-list',
'transfer-file',
'get-file-count',
'delete-file',
'request-firmware-upgrade',
'firmware-upload',
'demo-test',
'get-settings',
'set-settings',
'd',
'e',
'f',
'get-card-info',
'optimize',
'get-recording',
];
function Jensen() {
let device = null;
let actions = {};
let buffer = [];
let blocks = [];
let sequence = 0;
let current = null;
let commands = [];
let handlers = [];
let statusTimeout = null;
let recv = false;
let ready = false;
let totalBytes = 0;
let self = this;
this.data = {};
// 消息
this.decodeTimeout = 0;
this.timewait = 1;
this.ondisconnect = null;
this.isStopConnctionCheck = false;
this.onconnect = null;
this.onreceive = null;
const RECV_BUFF_SIZE = 51200;
const _check_conn_status = () => {
if (device?.opened == false) {
try {
clearTimeout(statusTimeout);
if (this.ondisconnect && !this.isStopConnctionCheck) this.ondisconnect();
} catch (e) {
console.log(e);
}
}
statusTimeout = setTimeout(() => {
_check_conn_status();
}, 100);
};
const crash = async function (procedure, error) {
Logger.error('jensen', procedure, String(error));
self.versionCode = null;
self.versionNumber = null;
};
const setup = async function () {
self.versionCode = null;
self.versionNumber = null;
try {
await device.selectConfiguration(1);
await device.claimInterface(0);
await device.selectAlternateInterface(0, 0);
self.model = device.productId == 45069 ? 'hidock-h1e' : 'hidock-h1';
} catch (e) {
Logger.error('jensen', 'setup', String(e));
throw e;
}
_check_conn_status();
current = null;
recv = false;
ready = true;
Logger.debug('jensen', 'setup', 'setup webusb connection');
try {
self.onconnect?.();
} catch (err) {
Logger.error('jensen', 'setup', err);
}
};
this.connect = async function () {
Logger.debug('jensen', 'connect', 'connect');
let r = await self.tryconnect();
if (r) return;
let conn = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x10d6 }],
});
await conn.open();
self.model = conn.productId == 45069 ? 'hidock-h1e' : 'hidock-h1';
device = conn;
await setup();
};
this.getModel = function () {
return this.model;
};
this.init = async function _init() {
if (!navigator.usb) {
Logger.error('jensen', 'init', 'webusb not supported');
return;
}
navigator.usb.onconnect = function (e) {
self.tryconnect();
};
await self.connect();
};
this.tryconnect = async function () {
await this.disconnect();
let devices = await navigator.usb.getDevices();
for (let i = 0; i < devices.length; i++) {
let item = devices[i];
if (item.productName.indexOf('HiDock') > -1) {
Logger.debug('jensen', 'tryconnect', 'detected: ' + item.productName);
await item.open();
device = item;
await setup();
return true;
}
}
Logger.debug('jensen', 'tryconnect', 'no HiDock found');
return false;
};
this.isConnected = function () {
return device != null;
};
this.disconnect = async function () {
Logger.info('jensen', 'disconnect', 'disconnect');
try {
await device?.close();
} catch (e) {}
};
this.send = function (cmd, seconds, onprogress) {
cmd.sequence(sequence++);
cmd.onprogress = onprogress;
if (seconds) cmd.expireAfter(seconds);
commands.push(cmd);
sendNext();
return register(cmd, seconds);
};
const sendNext = async function () {
if (current) {
// Logger.info('jensen', 'sendNext', 'return cuz current is: ' + current);
return;
}
let cmd = null;
let now = new Date().getTime();
while (true) {
if (commands.length == 0) return;
cmd = commands.shift();
if (cmd.expireTime > 0 && cmd.expireTime < now) {
Logger.info('jensen', 'sendNext', 'expired: cmd-' + cmd.command + '-' + cmd.index + ', ' + COMMAND_NAMES[cmd.command]);
continue;
}
break;
}
let data = cmd.make();
current = 'cmd-' + cmd.command + '-' + cmd.index;
Logger.debug('jensen', 'sendNext', 'command: ' + COMMAND_NAMES[cmd.command] + ', data bytes: ' + data.byteLength);
self.timewait = cmd.command == TRANSFER_FILE ? 1000 : 10;
await device.transferOut(1, data).catch((e) => crash('sendNext', e));
if (cmd.onprogress) cmd.onprogress(1, 1);
/*
if (data.byteLength < 1000) await device.transferOut(1, data).catch((e) => crash('sendNext', e));
else
{
let blks = Math.ceil(data.byteLength / 1000);
Logger.info('jensen', 'sendNext', blks + ' blocks / ' + data.byteLength + ' bytes');
for (let i = 0, k = blks; i < k; i++)
{
var start = i * 1000;
var end = (i + 1) * 1000;
await device.transferOut(1, data.subarray(start, end)).then(() =>
{
Logger.debug('jensen', 'sendNext', i + ' / ' + blks);
if (cmd.onprogress)
{
cmd.onprogress(Math.min(end, data.byteLength), data.byteLength);
}
})
.catch((e) => crash('sendNext', e));
}
}
*/
totalBytes = 0;
if (recv == false) tryReceive();
else recv = true;
};
const register = function (cmd, seconds) {
let tag = 'cmd-' + cmd.command + '-' + cmd.index;
let t = seconds
? setTimeout(() => {
timeout(tag);
}, seconds * 1000)
: null;
return new Promise((resolve, reject) => {
actions[tag] = {
tag: tag,
resolve: resolve,
reject: reject,
timeout: t,
};
});
};
const trigger = function (resp, msgid) {
if (current == null) return;
Logger.debug('jensen', 'trigger', 'trigger - ' + current.substring(0, current.lastIndexOf('-')) + ' <---> cmd-' + msgid);
if (current.substring(0, current.lastIndexOf('-')) != 'cmd-' + msgid) {
current = null;
return;
}
if (current in actions == false) {
Logger.debug('jensen', 'trigger', 'no action registered');
return;
}
let p = actions[current];
if (p.timeout) clearTimeout(p.timeout);
p.resolve(resp);
delete actions[current];
current = null;
};
const timeout = function (tag) {
Logger.debug('jensen', 'timeout', 'timeout ' + tag);
let p = actions[tag];
p.resolve(null);
delete actions[tag];
};
this.dump = function () {
console.log('actions', actions);
console.log('pending commands', commands);
console.log('current', current);
console.log('device', device);
};
const tryReceive = function () {
if (device)
device.transferIn(2, RECV_BUFF_SIZE).then((r) => {
receive(r);
});
};
const read_int = function (a, b, c, d) {
if (arguments.length == 2) {
return ((a & 0xff) << 8) | (b & 0xff);
} else if (arguments.length == 4) {
return ((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff);
}
};
/*
const receive = function (result) {
for (let i = 0; i < result.data.byteLength; i++) {
buffer.push(result.data.getInt8(i));
}
while (true) {
let msg = decode();
if (msg == null) break;
var cname = msg.id == FACTORY_RESET ? 'factory-reset' : COMMAND_NAMES[msg.id];
Logger.debug('jensen', 'receive', 'recv: ' + cname + ', seq: ' + msg.sequence + ', data bytes: ' + msg.body?.length + ', data: ' + msg.body.slice(0, 16).join(','));
try {
let handler = Jensen.handlers[msg.id];
let r = handler(msg, self);
if (r) trigger(r, msg.id);
}
catch (e) {
trigger(e);
Logger.error('jensen', 'receive', 'recv: ' + COMMAND_NAMES[msg.id] + ', seq: ' + msg.sequence + ', error: ' + String(e));
}
sendNext();
}
device.transferIn(2, 1024).then(function (r) {
receive(r);
});
}
*/
const receive = function (result) {
totalBytes += result.data.byteLength;
// 做一个回调,怎么样找到它呢?
blocks.push(result.data);
tryReceive();
if (self.decodeTimeout) clearTimeout(self.decodeTimeout);
self.decodeTimeout = setTimeout(function () {
tryDecode();
}, self.timewait);
if (self.onreceive)
try {
self.onreceive(totalBytes);
} catch (e) {}
};
const tryDecode = function () {
// 一个容器,比单独任意一个小包要大一点儿,好像还差一点儿
let stime = new Date();
let buff = new ArrayBuffer(RECV_BUFF_SIZE * 2);
let bview = new Uint8Array(buff);
// buff中实际使用的字节数,有效字节数
let buffLength = 0;
let crash = false;
for (let i = 0, l = blocks.length; i < l; i++) {
let block = blocks.shift();
// 把block加到buff的最末尾
for (let k = 0; k < block.byteLength; k++) {
bview[k + buffLength] = block.getInt8(k);
}
buffLength += block.byteLength;
let startIndex = 0;
while (true) {
let rst = null;
try {
rst = decodeMessage(bview, startIndex, buffLength);
} catch (e) {
crash = true;
break;
}
if (rst == null) {
break;
}
startIndex += rst.length;
let msg = rst.message;
// WARN: 接下来怎么整
let cname = msg.id == FACTORY_RESET ? 'factory-reset' : COMMAND_NAMES[msg.id];
let heading = [];
for (let x = 0; x < msg.body?.byteLength && x < 32; x++) {
heading.push('0' + (msg.body[x] & 0xff).toString(16).replace(/^0(\w{2})$/gi, '$1'));
}
if (msg.id != TRANSFER_FILE)
Logger.debug(
'jensen',
'receive',
'recv: ' +
cname +
', seq: ' +
msg.sequence +
', data bytes: ' +
msg.body?.byteLength +
', data: ' +
heading.join(' '),
);
try {
let handler = Jensen.handlers[msg.id];
let r = handler(msg, self);
if (r) trigger(r, msg.id);
} catch (e) {
trigger(e);
Logger.error(
'jensen',
'receive',
'recv: ' + COMMAND_NAMES[msg.id] + ', seq: ' + msg.sequence + ', error: ' + String(e),
);
}
sendNext();
}
// 是否已经崩溃了?
if (crash) {
// cmd-5-42
// 5是msgid
let msgid = parseInt(current.replace(/^cmd-(\d+)-(\d+)$/gi, '$1'));
try {
let handler = Jensen.handlers[msgid];
handler(null, self);
} catch (e) {
trigger(e);
Logger.error('jensen', 'decode', 'decode error: ' + String(e));
}
trigger(null, msgid);
blocks.length = 0;
break;
}
// WARN: 需要把剩余的字节数挪到最前面去
// startIndex已经抵达最后的位置,startIndex -> buffLength就是需要移到最前面去的字节内容了
for (let k = 0, bs = buffLength - startIndex; k < bs; k++) {
bview[k] = bview[k + startIndex];
}
buffLength = buffLength - startIndex;
}
console.error('Decode: ' + (new Date().getTime() - stime.getTime()) + 'ms');
};
const decodeMessage = function (dataView, startIndex, buffLength) {
let dataLen = buffLength - startIndex;
if (dataLen < 12) return null;
if (dataView[startIndex + 0] != 0x12 || dataView[startIndex + 1] != 0x34) throw new Error('invalid header');
// 2 字节的指令id
let idx = 2;
// let cmdid = this.nextShort(idx);
let cmdid = read_int(dataView[startIndex + idx], dataView[startIndex + idx + 1]);
idx += 2;
let sequeue = read_int(
dataView[startIndex + idx + 0],
dataView[startIndex + idx + 1],
dataView[startIndex + idx + 2],
dataView[startIndex + idx + 3],
);
idx += 4;
let len = read_int(
dataView[startIndex + idx + 0],
dataView[startIndex + idx + 1],
dataView[startIndex + idx + 2],
dataView[startIndex + idx + 3],
);
let padding = (len >> 24) & 0xff;
len = len & 0xffffff;
idx += 4;
// 需要去除的字节数
var cutLen = 0;
// 数据还没有完全准备好
if (dataLen < 12 + len + padding) return null;
// 去掉header部分
// 下面这一行做什么用的?
// for (let i = 0; i < 12; i++) this.buffer[i + cutLen];
cutLen += 12;
// 取走body部分
// 数据体部分
// let body = new Uint8Array(new ArrayBuffer(len));
// for (let i = 0; i < len; i++) body.push(this.buffer[i + cutLen]);
let body = dataView.slice(startIndex + cutLen, startIndex + cutLen + len);
cutLen += len;
// 干掉补上来的数据
// 下面这一行做什么用的?
// for (let i = 0; i < padding; i++) this.buffer[i + cutLen];
cutLen += padding;
// self.buffer = self.buffer.slice(cutLen);
return { message: new Message(cmdid, sequeue, body), length: cutLen };
};
this.to_bcd = function (str) {
let x = new Array();
for (let i = 0; i < str.length; i += 2) {
let h = (str.charCodeAt(i) - 48) & 0xff;
let l = (str.charCodeAt(i + 1) - 48) & 0xff;
x.push((h << 4) | l);
}
return x;
};
this.from_bcd = function () {
let str = '';
for (let i = 0; i < arguments.length; i++) {
let v = arguments[i] & 0xff;
str += (v >> 4) & 0x0f;
str += v & 0x0f;
}
return str;
};
}
function Command(id) {
this.command = id;
this.msgBody = new Array();
this.index = 0;
this.expireTime = 0;
this.timeout = 0;
this.body = function (data) {
this.msgBody = data;
return this;
};
this.expireAfter = function (seconds) {
this.expireTime = new Date().getTime() + seconds * 1000;
};
this.sequence = function (seq) {
this.index = seq;
return this;
};
this.make = function () {
let msg = new Uint8Array(2 + 2 + 4 + 4 + this.msgBody.length);
let idx = 0;
// msg header
msg[idx++] = 0x12;
msg[idx++] = 0x34;
// msg command id
msg[idx++] = (this.command >> 8) & 0xff;
msg[idx++] = (this.command >> 0) & 0xff;
// msg sequence id
msg[idx++] = (this.index >> 24) & 0xff;
msg[idx++] = (this.index >> 16) & 0xff;
msg[idx++] = (this.index >> 8) & 0xff;
msg[idx++] = (this.index >> 0) & 0xff;
// msg body length
let len = this.msgBody.length;
msg[idx++] = (len >> 24) & 0xff;
msg[idx++] = (len >> 16) & 0xff;
msg[idx++] = (len >> 8) & 0xff;
msg[idx++] = (len >> 0) & 0xff;
// msg body
for (let i = 0; i < this.msgBody.length; i++) msg[idx++] = this.msgBody[i] & 0xff;
return msg;
};
}
function Message(id, sequence, body) {
this.id = id;
this.sequence = sequence;
this.body = body;
}
/**
* @param {*} cmdid command id
* @param {*} handler callback function for response processing, return `non-undefined` means command ended
*/
Jensen.registerHandler = function (cmdid, handler) {
if (typeof Jensen.handlers == 'undefined') Jensen.handlers = {};
Jensen.handlers[cmdid] = handler;
};
Jensen.prototype.getDeviceInfo = async function (seconds) {
return this.send(new Command(QUERY_DEVICE_INFO), seconds);
};
Jensen.prototype.getTime = async function (seconds) {
return this.send(new Command(QUERY_DEVICE_TIME), seconds);
};
Jensen.prototype.getFileCount = async function (seconds) {
return this.send(new Command(QUERY_FILE_COUNT), seconds);
};
Jensen.prototype.factoryReset = async function (seconds) {
if (this.versionNumber < 327705) return null;
return this.send(new Command(FACTORY_RESET), seconds);
};
Jensen.prototype.listFiles = async function () {
let tag = 'filelist';
if (this[tag] != null) return null;
let fc = null;
if (typeof this.versionNumber == 'undefined' || this.versionNumber <= 327722) {
fc = await this.getFileCount(5);
if (fc == null) return null;
}
if (fc && fc.count == 0) return null;
let self = this;
// let tag = 'data_' + new Date().getTime();
this[tag] = [];
Jensen.registerHandler(QUERY_FILE_LIST, (msg, jensen) => {
if (msg.body.length == 0) {
jensen[tag] = null;
return [];
}
jensen[tag].push(msg.body);
let data = [];
let files = [];
let fcount = -1;
let start = 0;
for (let i = 0; i < jensen[tag].length; i++) {
for (let k = 0; k < jensen[tag][i].length; k++) data.push(jensen[tag][i][k]);
}
if ((data[0] & 0xff) == 0xff && (data[1] & 0xff) == 0xff) {
fcount = ((data[2] & 0xff) << 24) | ((data[3] & 0xff) << 16) | ((data[4] & 0xff) << 8) | (data[5] & 0xff);
start += 6;
}
let fnpad = function (v) {
return v > 9 ? v : '0' + v;
};
for (let i = start; i < data.length; ) {
let len = 0;
let fname = [];
if (i + 4 >= data.length) break;
let ver = data[i++] & 0xff;
let nameLen = ((data[i++] & 0xff) << 16) | ((data[i++] & 0xff) << 8) | (data[i++] & 0xff);
for (let k = 0; k < nameLen && i < data.length; k++) {
let c = data[i++] & 0xff;
if (c > 0) fname.push(String.fromCharCode(c));
}
// 4 + nameLen + 4 + 6 + 16
if (i + 4 + 6 + 16 > data.length) {
break;
}
let flen = ((data[i++] & 0xff) << 24) | ((data[i++] & 0xff) << 16) | ((data[i++] & 0xff) << 8) | (data[i++] & 0xff);
i += 6;
let sign = [];
for (let k = 0; k < 16; k++, i++) {
let h = (data[i] & 0xff).toString(16);
sign.push(h.length == 1 ? '0' + h : h);
}
let ftime = fname.join('');
let duration = 0;
if (ftime.match(/^\d{14}REC\d+\.wav$/gi)) {
ftime = ftime.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})REC.*$/gi, '$1-$2-$3 $4:$5:$6');
ftime = new Date(ftime);
duration = flen / 32;
} else if (ftime.match(/^(\d{2})?(\d{2})(\w{3})(\d{2})-(\d{2})(\d{2})(\d{2})-.*\.hda$/gi)) {
// 2024Mar19-110932-Rec00.hda
ftime = ftime.replace(/^(\d{2})?(\d{2})(\w{3})(\d{2})-(\d{2})(\d{2})(\d{2})-.*\.hda$/gi, '20$2 $3 $4 $5:$6:$7');
ftime = new Date(ftime);
duration = (flen / 32) * 4;
} else {
ftime = null;
}
if (ver == 1) {
duration = duration * 2;
}
let createDate = '';
let createTime = '';
if (ftime) {
createDate = ftime.getFullYear() + '/' + fnpad(ftime.getMonth() + 1) + '/' + fnpad(ftime.getDate());
createTime = fnpad(ftime.getHours()) + ':' + fnpad(ftime.getMinutes()) + ':' + fnpad(ftime.getSeconds());
}
files.push({
name: fname.join(''),
createDate: createDate,
createTime: createTime,
time: ftime,
duration: duration,
length: flen,
signature: sign.join(''),
});
}
// if (fcount == -1 && (fc))
// 如果没有判断数量的依据
if (fcount == -1) {
// return [];
}
if ((fc && files.length >= fc.count) || (fcount > -1 && files.length >= fcount)) {
// delete jensen[tag];
jensen[tag] = null;
return files.filter((f) => {
return Boolean(f.time);
});
}
});
return this.send(new Command(QUERY_FILE_LIST));
};
Jensen.prototype.deleteFile = async function (filename, seconds) {
let fname = [];
for (let i = 0; i < filename.length; i++) fname.push(filename.charCodeAt(i));
return this.send(new Command(DELETE_FILE).body(fname), seconds);
};
Jensen.prototype.setTime = async function (time, seconds) {
let str =
time.getFullYear() +
'-0' +
(time.getMonth() + 1) +
'-0' +
time.getDate() +
'-0' +
time.getHours() +
'-0' +
time.getMinutes() +
'-0' +
time.getSeconds();
str = str.replace(/(\d{4})\-0*(\d{2})\-0*(\d{2})\-0*(\d{2})\-0*(\d{2})\-0*(\d{2})/gi, '$1$2$3$4$5$6');
return this.send(new Command(SET_DEVICE_TIME).body(this.to_bcd(str)), seconds);
};
Jensen.prototype.getFile = async function (filename, length, ondata, onprogress) {
if (typeof length != 'number') throw new Error('parameter `length` required');
if (length <= 0) throw new Error('parameter `length` must greater than zero');
let fname = [];
for (let i = 0; i < filename.length; i++) fname.push(filename.charCodeAt(i));
let flen = 0;
let handler = (msg) => {
if (msg != null) {
flen += msg.body.length || msg.body.byteLength;
ondata(msg.body);
// return OK indicates all file blocks received
if (flen >= length) return 'OK';
} else {
ondata('fail');
}
};
this.onreceive = onprogress;
Jensen.registerHandler(TRANSFER_FILE, handler);
this.send(new Command(TRANSFER_FILE).body(fname));
};
Jensen.prototype.requestFirmwareUpgrade = async function (versionNumber, fileSize, seconds) {
let data = [];
data[0] = (versionNumber >> 24) & 0xff;
data[1] = (versionNumber >> 16) & 0xff;
data[2] = (versionNumber >> 8) & 0xff;
data[3] = (versionNumber >> 0) & 0xff;
data[4] = (fileSize >> 24) & 0xff;
data[5] = (fileSize >> 16) & 0xff;
data[6] = (fileSize >> 8) & 0xff;
data[7] = (fileSize >> 0) & 0xff;
return this.send(new Command(REQUEST_FIRMWARE_UPGRADE).body(data), seconds);
};
Jensen.prototype.uploadFirmware = async function (data, seconds, onprogress) {
return this.send(new Command(FIRMWARE_UPLOAD).body(data), seconds, onprogress);
};
Jensen.prototype.beginBNC = async function (seconds) {
return this.send(new Command(BNC_DEMO_TEST).body([0x01]), seconds);
};
Jensen.prototype.endBNC = async function (seconds) {
return this.send(new Command(BNC_DEMO_TEST).body([0x00]), seconds);
};
Jensen.prototype.getSettings = async function (seconds) {
if (this.versionNumber < 327714) {
return { autoRecord: false, autoPlay: false };
}
return this.send(new Command(GET_SETTINGS), seconds);
};
Jensen.prototype.setAutoRecord = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, enable ? 1 : 2, 0, 0, 0, 0]), seconds);
};
Jensen.prototype.setAutoPlay = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, 0, 0, 0, 0, enable ? 1 : 2]), seconds);
};
Jensen.prototype.setNotification = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, enable ? 1 : 2]), seconds);
};
Jensen.prototype.getCardInfo = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(READ_CARD_INFO), seconds);
};
Jensen.prototype.formatCard = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(FORMAT_CARD).body([0x01, 0x02, 0x03, 0x04]), seconds);
};
Jensen.prototype.getRecordingFile = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(GET_RECORDING_FILE), seconds);
};
const commonMessageParser = (msg) => {
return { result: msg.body[0] == 0x00 ? 'success' : 'failed' };
};
Jensen.registerHandler(SET_DEVICE_TIME, commonMessageParser);
Jensen.registerHandler(BNC_DEMO_TEST, commonMessageParser);
Jensen.registerHandler(DELETE_FILE, (msg) => {
let rst = 'failed';
if (msg.body[0] == 0x00) rst = 'success';
else if (msg.body[0] == 0x01) rst = 'not-exists';
else if (msg.body[0] == 0x02) rst = 'failed';
return { result: rst };
});
Jensen.registerHandler(QUERY_DEVICE_INFO, (msg, jensen) => {
let vc = [],
vn = 0;
let sn = [];
for (let i = 0; i < 4; i++) {
let b = msg.body[i] & 0xff;
if (i > 0) {
vc.push(String(b));
}
vn = vn | (b << ((4 - i - 1) * 8));
}
for (let i = 0; i < 16; i++) {
let chr = msg.body[i + 4];
if (chr > 0) sn.push(String.fromCharCode(chr));
}
jensen.versionCode = vc.join('.');
jensen.versionNumber = vn;
sn = sn.join('');
jensen.serialNumber = sn;
return {
versionCode: vc.join('.'),
versionNumber: vn,
sn: sn,
};
});
Jensen.registerHandler(QUERY_DEVICE_TIME, (msg, jensen) => {
let time = jensen.from_bcd(
msg.body[0] & 0xff,
msg.body[1] & 0xff,
msg.body[2] & 0xff,
msg.body[3] & 0xff,
msg.body[4] & 0xff,
msg.body[5] & 0xff,
msg.body[6] & 0xff,
);
return {
time: time == '00000000000000' ? 'unknown' : time.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/gi, '$1-$2-$3 $4:$5:$6'),
};
});
Jensen.registerHandler(QUERY_FILE_COUNT, (msg) => {
if (msg.body.length == 0) return { count: 0 };
let c = 0;
for (let i = 0; i < 4; i++) {
let b = msg.body[i] & 0xff;
c = c | (b << ((4 - i - 1) * 8));
}
return { count: c };
});
Jensen.registerHandler(GET_SETTINGS, (msg) => {
let r1 = msg.body[3];
let r2 = msg.body[7];
let rst = { autoRecord: r1 == 1, autoPlay: r2 == 1 };
if (msg.body.length >= 12) {
let r3 = msg.body[11] == 1;
rst['notification'] = r3;
}
return rst;
});
Jensen.registerHandler(SET_SETTINGS, commonMessageParser);
Jensen.registerHandler(FACTORY_RESET, commonMessageParser);
Jensen.registerHandler(REQUEST_FIRMWARE_UPGRADE, (msg) => {
let rst = '';
let c = msg.body[0];
if (c == 0x00) rst = 'accepted';
if (c == 0x01) rst = 'wrong-version';
if (c == 0x02) rst = 'busy';
if (c == 0x03) return 'unknown';
return { result: rst };
});
Jensen.registerHandler(FIRMWARE_UPLOAD, (msg) => {
let c = msg.body[0];
return { result: c == 0x00 ? 'success' : 'failed' };
});
Jensen.registerHandler(READ_CARD_INFO, (msg) => {
let i = 0;
let used = ((msg.body[i++] & 0xff) << 24) | ((msg.body[i++] & 0xff) << 16) | ((msg.body[i++] & 0xff) << 8) | (msg.body[i++] & 0xff);
let capacity = ((msg.body[i++] & 0xff) << 24) | ((msg.body[i++] & 0xff) << 16) | ((msg.body[i++] & 0xff) << 8) | (msg.body[i++] & 0xff);
let status = ((msg.body[i++] & 0xff) << 24) | ((msg.body[i++] & 0xff) << 16) | ((msg.body[i++] & 0xff) << 8) | (msg.body[i++] & 0xff);
return { used: used, capacity: capacity, status: status.toString(16) };
});
Jensen.registerHandler(FORMAT_CARD, commonMessageParser);
Jensen.registerHandler(GET_RECORDING_FILE, (msg) => {
if (msg.body == null || msg.body.length == 0) return { recording: null };
else {
var fname = [];
for (var i = 0; i < msg.body.length; i++) {
fname.push(String.fromCharCode(msg.body[i]));
}
let fnpad = function (v) {
return v > 9 ? v : '0' + v;
};
let ftime = fname.join('');
if (ftime.match(/^\d{14}REC\d+\.wav$/gi)) {
ftime = ftime.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})REC.*$/gi, '$1-$2-$3 $4:$5:$6');
ftime = new Date(ftime);
} else if (ftime.match(/^(\d{2})?(\d{2})(\w{3})(\d{2})-(\d{2})(\d{2})(\d{2})-.*\.hda$/gi)) {
// 2024Mar19-110932-Rec00.hda
ftime = ftime.replace(/^(\d{2})?(\d{2})(\w{3})(\d{2})-(\d{2})(\d{2})(\d{2})-.*\.hda$/gi, '20$2 $3 $4 $5:$6:$7');
ftime = new Date(ftime);
} else {
ftime = null;
}
let createDate = '';
let createTime = '';
if (ftime) {
createDate = ftime.getFullYear() + '/' + fnpad(ftime.getMonth() + 1) + '/' + fnpad(ftime.getDate());
createTime = fnpad(ftime.getHours()) + ':' + fnpad(ftime.getMinutes()) + ':' + fnpad(ftime.getSeconds());
}
return {
recording: fname.join(''),
name: fname.join(''),
createDate: createDate,
createTime: createTime,
time: ftime,
duration: 0,
length: 0,
signature: '0'.repeat(32),
};
}
});
export { Jensen };
const Level = {
debug: 'debug',
info: 'info',
error: 'error',
};
export const Logger = {
messages: [],
consoleOutput: true,
info(module, procedure, message) {
this._append(Level.info, module, procedure, message);
},
debug(module, procedure, message) {
this._append(Level.debug, module, procedure, message);
},
error(module, procedure, message) {
this._append(Level.error, module, procedure, message);
},
_append(level, module, procedure, message) {
let log = {
level,
module,
procedure,
message: String(message),
time: new Date().getTime(),
};
this.messages.push(log);
if (this.consoleOutput) {
this._print(log);
}
if (this.messages.length > 15000) this.messages.shift();
},
_print(log) {
let time = new Date(log.time);
console.info(
'[' +
(log.level === 'error' ? 'x' : '*') +
'][' +
time.toLocaleString() +
'](' +
log.module +
' - ' +
log.procedure +
') ' +
log.message,
);
},
filter(module, procedure) {
return this.messages.filter((i) => i.module === module && i.procedure === procedure);
},
enableConsoleOutput() {
this.consoleOutput = true;
},
disableConsoleOutput() {
this.consoleOutput = false;
},
peek(rows) {
return this.messages.slice(-rows);
},
search(module, procedure, keyword) {
return this.messages.filter((i) => {
let r = i.module === module;
if (!r) return false;
if (procedure) {
if (i.procedure !== procedure) return false;
}
if (keyword) {
if (i.message.indexOf(keyword) === -1) return false;
}
return true;
});
},
};
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src"],
"exclude": ["node_modules"]
}
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