247 行
7.3 KiB
JavaScript
247 行
7.3 KiB
JavaScript
// BLE commands: writing commands and high-level device operations
|
|
|
|
var protocol = require('./protocol')
|
|
var connection = require('./connection')
|
|
|
|
var _cmdSeq = 0
|
|
var _pendingAcks = {}
|
|
|
|
function nextSeq() {
|
|
_cmdSeq = (_cmdSeq + 1) % 256
|
|
return _cmdSeq
|
|
}
|
|
|
|
function getPendingAcks() {
|
|
return _pendingAcks
|
|
}
|
|
|
|
function clearPendingAcks() {
|
|
_pendingAcks = {}
|
|
}
|
|
|
|
// --- low-level write ---
|
|
|
|
function writeCommand(type, payload) {
|
|
return new Promise(function (resolve, reject) {
|
|
if (!connection.isConnected()) {
|
|
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
|
return
|
|
}
|
|
var chars = connection.getChars()
|
|
if (!chars.command) {
|
|
reject({ error_code: 0xFF, error_msg: 'command characteristic not found' })
|
|
return
|
|
}
|
|
|
|
var seq = nextSeq()
|
|
var payloadWithSeq = (payload || []).concat([seq])
|
|
var buffer = protocol.buildFrame(type, payloadWithSeq)
|
|
|
|
_pendingAcks[seq] = { resolve: resolve, reject: reject }
|
|
|
|
setTimeout(function () {
|
|
if (_pendingAcks[seq]) {
|
|
_pendingAcks[seq].reject({ error_code: 0xFF, error_msg: 'ACK timeout' })
|
|
delete _pendingAcks[seq]
|
|
}
|
|
}, 5000)
|
|
|
|
var deviceId = connection.getDeviceId()
|
|
wx.writeBLECharacteristicValue({
|
|
deviceId: deviceId,
|
|
serviceId: chars.command.serviceId,
|
|
characteristicId: chars.command.uuid,
|
|
value: buffer,
|
|
success: function () {},
|
|
fail: function () {
|
|
delete _pendingAcks[seq]
|
|
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function writeRawCommand(bytes) {
|
|
return new Promise(function (resolve, reject) {
|
|
if (!connection.isConnected()) {
|
|
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
|
return
|
|
}
|
|
var chars = connection.getChars()
|
|
if (!chars.command) {
|
|
reject({ error_code: 0xFF, error_msg: 'command characteristic not found' })
|
|
return
|
|
}
|
|
console.log('[BLE] FFE1 发送', bytes.length + '字节', '| hex:', protocol.bytesToHex(bytes))
|
|
wx.writeBLECharacteristicValue({
|
|
deviceId: connection.getDeviceId(),
|
|
serviceId: chars.command.serviceId,
|
|
characteristicId: chars.command.uuid,
|
|
value: protocol.bytesToBuffer(bytes),
|
|
success: function () {
|
|
resolve({ message: 'success', bytes: bytes })
|
|
},
|
|
fail: function (err) {
|
|
console.error('[BLE] FFE1 发送失败', err)
|
|
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function writeCommandWithRetry(type, payload, maxRetries) {
|
|
if (maxRetries === undefined) maxRetries = 3
|
|
var attempt = 0
|
|
function tryOnce() {
|
|
attempt++
|
|
return writeCommand(type, payload).catch(function (err) {
|
|
if (err && err.error_code === 0x0C) {
|
|
return Promise.reject(err)
|
|
}
|
|
if (attempt >= maxRetries) {
|
|
return Promise.reject(err)
|
|
}
|
|
return new Promise(function (resolve) {
|
|
setTimeout(resolve, 500)
|
|
}).then(function () {
|
|
return tryOnce()
|
|
})
|
|
})
|
|
}
|
|
return tryOnce()
|
|
}
|
|
|
|
// --- high-level commands ---
|
|
|
|
function readDeviceInfo() {
|
|
return new Promise(function (resolve, reject) {
|
|
var chars = connection.getChars()
|
|
var deviceId = connection.getDeviceId()
|
|
if (!connection.isConnected() || !chars.deviceInfo) {
|
|
reject({ msg: '设备未连接或特征值未就绪' })
|
|
return
|
|
}
|
|
wx.readBLECharacteristicValue({
|
|
deviceId: deviceId,
|
|
serviceId: chars.deviceInfo.serviceId,
|
|
characteristicId: chars.deviceInfo.uuid,
|
|
success: function () {},
|
|
fail: function () { reject({ msg: '读取设备信息失败' }) }
|
|
})
|
|
|
|
var handler = function (res) {
|
|
if (res.characteristicId.toUpperCase().indexOf(protocol.CHAR.DEVICE_INFO) !== -1) {
|
|
wx.offBLECharacteristicValueChange(handler)
|
|
var bytes = protocol.bufferToBytes(res.value)
|
|
if (bytes.length >= 14) {
|
|
resolve({
|
|
hw_version: (bytes[0] << 8) | bytes[1],
|
|
fw_version: (bytes[2] << 8) | bytes[3],
|
|
device_type: (bytes[4] << 8) | bytes[5],
|
|
device_id: protocol.bytesToHex(bytes.slice(6, 14))
|
|
})
|
|
} else {
|
|
reject({ msg: '设备信息格式错误' })
|
|
}
|
|
}
|
|
}
|
|
wx.onBLECharacteristicValueChange(handler)
|
|
})
|
|
}
|
|
|
|
function setParams(options) {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
return writeRawCommand(protocol.buildVendorCommand33(options || {}))
|
|
}
|
|
|
|
var regionMask = options.region_mask || protocol.REGION.FULL_FACE
|
|
var wavelength = options.wavelength || protocol.WAVELENGTH.R
|
|
var brightness = options.brightness || 200
|
|
var durationMs = options.duration_ms || 600000
|
|
var mode = options.mode !== undefined ? options.mode : protocol.TREAT_MODE.NORMAL
|
|
|
|
var payload = [
|
|
regionMask,
|
|
wavelength,
|
|
brightness,
|
|
protocol.uint32ToBytes(durationMs),
|
|
mode
|
|
].reduce(function (a, b) {
|
|
return a.concat(Array.isArray(b) ? b : [b])
|
|
}, [])
|
|
|
|
return writeCommandWithRetry(protocol.CMD.SET_PARAMS, payload)
|
|
}
|
|
|
|
function startTreatment(regionMask) {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
return Promise.resolve({ message: 'vendor_33 uses setParams write as start', region_mask: regionMask })
|
|
}
|
|
var mask = regionMask || protocol.REGION.FULL_FACE
|
|
return writeCommandWithRetry(protocol.CMD.START, [mask])
|
|
}
|
|
|
|
function stopTreatment() {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
return writeRawCommand(protocol.buildVendorCommand({
|
|
region_mask: 0,
|
|
wavelength: protocol.WAVELENGTH.R,
|
|
brightness: 0,
|
|
current_gain: 0,
|
|
hold_time: 0,
|
|
control: protocol.CONTROL.IDLE
|
|
}))
|
|
}
|
|
return writeCommandWithRetry(protocol.CMD.STOP, [])
|
|
}
|
|
|
|
function queryStatus() {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
return Promise.resolve({ message: 'query status not defined by vendor_33 protocol' })
|
|
}
|
|
return writeCommandWithRetry(protocol.CMD.QUERY_STATUS, [])
|
|
}
|
|
|
|
function bindDevice(userId, bindToken) {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
setTimeout(function () {
|
|
connection.emit('bind_result', { success: true, skipped_ble_bind: true })
|
|
}, 0)
|
|
return Promise.resolve({ message: 'BLE bind not defined by vendor_33 protocol', user_id: userId, bind_token: bindToken })
|
|
}
|
|
|
|
var userBytes = protocol.uint32ToBytes(parseInt(userId, 10))
|
|
var tokenBytes = protocol.hexToBytes(bindToken)
|
|
var ts = Math.floor(Date.now() / 1000)
|
|
var tsBytes = protocol.uint32ToBytes(ts)
|
|
|
|
var payload = [0x01].concat(userBytes).concat(tokenBytes).concat(tsBytes)
|
|
return writeCommandWithRetry(protocol.CMD.BIND, payload)
|
|
}
|
|
|
|
function unbindDevice(userId) {
|
|
if (protocol.PROTOCOL_MODE === 'vendor_33') {
|
|
return Promise.resolve({ message: 'BLE unbind not defined by vendor_33 protocol', user_id: userId })
|
|
}
|
|
var userBytes = protocol.uint32ToBytes(parseInt(userId, 10))
|
|
var payload = [0x02].concat(userBytes)
|
|
return writeCommandWithRetry(protocol.CMD.UNBIND, payload)
|
|
}
|
|
|
|
module.exports = {
|
|
getPendingAcks: getPendingAcks,
|
|
clearPendingAcks: clearPendingAcks,
|
|
|
|
writeCommand: writeCommand,
|
|
writeRawCommand: writeRawCommand,
|
|
writeCommandWithRetry: writeCommandWithRetry,
|
|
|
|
readDeviceInfo: readDeviceInfo,
|
|
setParams: setParams,
|
|
startTreatment: startTreatment,
|
|
stopTreatment: stopTreatment,
|
|
queryStatus: queryStatus,
|
|
bindDevice: bindDevice,
|
|
unbindDevice: unbindDevice
|
|
}
|