文件
jw-beauty/miniprogram/services/ble/commands.js
T
Guoguo bb4b80f867 refactor: restructure entire project for human maintainability
Server:
- Add Express framework, replace custom router/request parser
- Create DAO layer (12 files) centralizing all 73 SQL queries
- Rewrite 7 route files as thin Express controllers calling DAOs
- Add SCF-to-Express adapter (lib/serverless.js)
- Add auth middleware (middleware/auth.js)
- Remove dead code from lib/auth.js

Admin console:
- Extract DataTable component (table + pagination)
- Extract ConfirmModal component (modal + form styles)
- Create listMixin for paginated list pages
- Move form styles to common.css for slot compatibility
- Refactor device + subscription pages as examples

Miniprogram:
- Split 734-line BLE monolith into 4 focused modules
  (protocol, connection, commands, barrel index)
- Create API module (utils/api.js) with named functions
- Create page utilities (utils/page.js)
- Refactor index + profile pages to use API module
2026-04-29 05:58:20 -07:00

188 行
5.1 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 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) {
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) {
var mask = regionMask || protocol.REGION.FULL_FACE
return writeCommandWithRetry(protocol.CMD.START, [mask])
}
function stopTreatment() {
return writeCommandWithRetry(protocol.CMD.STOP, [])
}
function queryStatus() {
return writeCommandWithRetry(protocol.CMD.QUERY_STATUS, [])
}
function bindDevice(userId, bindToken) {
var userBytes = protocol.hexToBytes(userId)
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) {
var userBytes = protocol.hexToBytes(userId)
var payload = [0x02].concat(userBytes)
return writeCommandWithRetry(protocol.CMD.UNBIND, payload)
}
module.exports = {
getPendingAcks: getPendingAcks,
clearPendingAcks: clearPendingAcks,
writeCommand: writeCommand,
writeCommandWithRetry: writeCommandWithRetry,
readDeviceInfo: readDeviceInfo,
setParams: setParams,
startTreatment: startTreatment,
stopTreatment: stopTreatment,
queryStatus: queryStatus,
bindDevice: bindDevice,
unbindDevice: unbindDevice
}