UNPKG

meoser

Version:

meos protocol engine

506 lines (474 loc) 16.4 kB
/** * @fileOverview Meos Class * @author Jeremy * main 文件,对外部模块提供接口,接口有: * - 设置上传内容 * - 读取上传内容 * - 文件上传 * - 文件新增 * - 文件删除 * - 文件查询 * - 上传进度 * - 发送器 * - 监听器 * - 获取传感器的值 */ import Transport from './src/transport'; import Command from './src/command'; import Encodebytes from './src/encodebytes'; import Dispatch from './src/dispatch'; import Uploader from './src/uploader'; import neuronAdapter from './src/neuron-adapter'; import Logger from './src/logger'; import { createEmitter, genEvent } from './src/emitter'; import { MAC_TYPE, READ_ACTION, REQUEST_TOPIC, EVENT, TIPS, WORKING_MODE, CORRECTION_MODE, DO_CORRECT } from './src/setting'; import CachePool from './src/cachepool'; import { floatArrayFixed, checkArrayDiff } from './src/util'; import utf8 from 'utf8'; const neuronsEngine = neuronAdapter.getEngine(); /** * Meos 主控 */ class Meoser { /** * create a instance */ constructor() { this.emiter = createEmitter(); // 重大bug: Emiter 和 Meoser 实例一一对应 this.cachePool = CachePool; this.uploader = new Uploader(this.emiter); Dispatch.init(this.emiter); Command.init(this.emiter); } /** * 环境变量设置为开发环境时,用于打印 mEos 上报日志 * @param {Boolean} env [description] */ setEnvDev(env = false) { Logger.setState(!!env); } /** * set mytransport which is pointed to RobotComm * @param {Function} sender send method * @param {Function} mytransport.onReceived onReceived method */ setSender(sender) { Transport.sender = sender; } /** * 数据分发,先经过解析器,解析出一条完整协议 * 再根据解析结果进行分发 * @param {Buffer} buff 由上一级路由发送过来 */ doReceived(buff) { let buffData = Array.isArray(buff) ? buff : buff.data; // 接口数据兼容 // 增加协议过滤器,将codey协议分发给codey协议解析器 // 将神经元协议发给神经元引擎 if (Transport.checkOnline()) { // 在线模式 Transport.receiveOnline(buffData); } // 总数据分发 Dispatch.doDispatch(buffData); } /** * 添加广播事件句柄,当 mEos 对外广播时,事件句柄将会被执行 * @return {[type]} [description] */ addBroadcastHandler(handler, isOnce) { return genEvent(EVENT.BROADCAST, this.emiter)(handler, isOnce); } /** * 向 meos 发送广播 * @param {String} msg 广播信息 */ sendBroadcast(msg) { if (typeof msg === 'string') { msg = utf8.encode(msg); let protocol = Encodebytes.broadcastBytes(msg); Transport.send(protocol); } } /** * 向 meos 请求读值 * @param {Number} sensorID sensor id from sensor map * @return {Promise} [description] */ async requestRead(sensorID) { let actionID = READ_ACTION.READ; let protocol = Encodebytes.sensorBytes(actionID, sensorID); return await Command.request(REQUEST_TOPIC.READ_SENSOR, protocol, { desc: 'sensor read' }); } /** * 向 meos 请求停止读值 * @param {Number} sensorID sensor id from sensor map * @return {Promise} [description] */ async requestUnRead(sensorID) { this.cachePool.setSensorValue(sensorID, undefined); let actionID = READ_ACTION.UNREAD; let protocol = Encodebytes.sensorBytes(actionID, sensorID); return await Command.request(REQUEST_TOPIC.READ_SENSOR, protocol, { desc: 'stop sensor read' }); } /** * 向 mEos 写入通信变量 * @param {String} name 写入的变量名 * @param {Number|String|Boolean} value 写入的变量值 */ writeCommVar(name, value) { if (!name) { return; } this.cachePool.setCommVarValue(name, value); let protocol = Encodebytes.commVarBytes(name, value); Transport.send(protocol); } /** * 读取 mEos 的 mac 地址 * @param {Number} type 1: wifi, 2: ble * @return {Promise} [description] */ async readMacAddr(type) { if (typeof type !== 'number') { type = MAC_TYPE.BLE; //ble } let topic = REQUEST_TOPIC.WIFI_MAC; if (type === MAC_TYPE.BLE) { topic = REQUEST_TOPIC.BLE_MAC; } let protocol = Encodebytes.macAddrBytes(type); let result = await Command.request(topic, protocol, { desc: 'query mac address' }); return result; } /** * 读取固件版本 * @return {Promise} [description] */ async readVersion() { let protocol = Encodebytes.mEosVersionBytes(); return await Command.request(REQUEST_TOPIC.VERSION, protocol, { desc: 'query meos version' }); } /** * DTR 握手请求 * 下位机响应 or 不响应 * @return {Promise} [description] */ async handshakeDTR() { let protocol = Encodebytes.DTRBytes(); return await Command.request(REQUEST_TOPIC.HANDSHAKE_DTR, protocol, { desc: 'handshake', time: 5000 }); } /** * 设备 ready 后会触发该事件 * 包括设备中途死掉重启 ready */ addReadyListener(handler, isOnce) { return genEvent(EVENT.READY, this.emiter)(handler, isOnce); } /** * unready 情形是:连接断开,正常连接但下位机故障处理超时 * 其他情况都是ready的 * @return {Promise} [description] */ // TODO: 要和连接检测区分开 async getReadyStatus() { let protocol = Encodebytes.readyStatusBytes(); let res = await Command.request(REQUEST_TOPIC.READY_STATUS, protocol, { desc: 'ready status' }); // 在已连接的情况下,若 codey 在启动中,则不会响应 ready,该请求会超时 // 若断开了连接,该请求同样会超时 if (res.err) { // res.err res.status = TIPS.unready; } return res; } // 是否处于连接状态 async getConnectionStatus() { await true; } /** * 获取当前的通信信道 * @return {Promise} [description] */ async getCommChannel() { let protocol = Encodebytes.commChannelBytes(); return await Command.request(REQUEST_TOPIC.COMM_CHANNEL, protocol, { desc: 'communication channel' }); } /** * 设置工作模式:在线模式,离线模式 * 模式切换一定会导致下位机有一个软重启过程,下位机会等到软重启结束后才会返回 set working mode 的结果 * 在线切在线,离线切离线,下位机不会有重启过程,直接返回结果。 * @param {Number|String} mode 0: 表示offline, 1: 表示online, 同时也接受 'offline', 'online' 作为参数 * @return {Promise} [description] */ async setWorkingMode(mode) { let mode_ = WORKING_MODE[mode]; if (typeof mode_ === 'string') { mode_ = mode; } else if (typeof mode_ === 'undefined') { console.warn('param is invalid'); return; } let protocol = Encodebytes.workingModeBytes(mode_); let result = await Command.request(REQUEST_TOPIC.WORKING_MODE, protocol, { desc: `set ${WORKING_MODE[mode_]} mode`, time: 4000 }); // 设置当前模式 Transport.currentMode = result.mode; if (result.mode === WORKING_MODE[1]) { // 在线 // 初始化在线模式 Transport.initOnlineMode(neuronAdapter.getEngine()); } return result; } /** * 获取工作模式 * @mark 基础 API 不做缓存,缓存交给调用者处理 * @return {Promise} [description] */ async getWorkingMode() { let protocol = Encodebytes.workingModeBytes(); return await Command.request(REQUEST_TOPIC.WORKING_MODE, protocol, { desc: 'get working mode' }); } // 进入矫正、开始矫正(成功、失败)、退出矫正 /** * 进入校正模式 * @return {Promise} [description] */ async enterCorrectionMode() { let protocol = Encodebytes.correctionModeBytes(CORRECTION_MODE.MODE.ENTER); return await Command.request(REQUEST_TOPIC.ENTER_CORRECTION_MODE, protocol, { desc: 'enter correction mode' }); } /** * 退出校正模式 * @return {Promise} [description] */ async quitCorrectionMode(n=3) { let desc = 'quit correction mode'; let protocol = Encodebytes.correctionModeBytes(CORRECTION_MODE.MODE.QUIT); for(let i = 0; i < n; i++) { let result = await Command.request(REQUEST_TOPIC.QUIT_CORRECTION_MODE, protocol, { desc }); if(!result.err) { return result; } // 退出错误,则继续退出,确保退出成功 continue; } return Promise.resolve({ err: `${desc} fail` }) } /** * * @param {Number} sensorId * @param {Number} opts.time the unit of the time is ms * @param {String} opts.name sensor name */ async doCorrect(sensorId, opts={}) { let protocol = Encodebytes.doCorrectBytes(sensorId); return await Command.request(REQUEST_TOPIC.DO_CORRECT, protocol, { desc: `correct ${opts.name}`, time: opts.time }); } /** * 颜色矫正,过程为:进入校正模式(成功),颜色校正(成功),退出校正模式(成功) */ async correctColor() { let enterResult = await this.enterCorrectionMode(); if (enterResult.err) { return enterResult; // 进入失败 } const sensorId = DO_CORRECT.SENSOR.COLOR; let correctResult = await this.doCorrect(sensorId, { name: DO_CORRECT.SENSOR[sensorId], time: 4000 }) // TODO: 如何确保退出是成功的? await this.quitCorrectionMode(3); return correctResult; // 校正成功、失败 } /** * 陀螺仪矫正,过程为:进入校正模式(成功),颜色校正(成功),退出校正模式(成功) */ async correctGyro() { let enterResult = await this.enterCorrectionMode(); if (enterResult.err) { return enterResult; // 进入失败 } const sensorId = DO_CORRECT.SENSOR.GYRO; let correctResult = await this.doCorrect(sensorId, { name: DO_CORRECT.SENSOR[sensorId] }) // TODO: 如何确保退出是成功的? await this.quitCorrectionMode(3); return correctResult; // 校正成功、失败 } /** * interface for debug * @param {Array} protocol test protocol */ sendTestProtocol(protocol) { Transport.send(protocol); } /** * 在线模式接口:神经元 set 命令 * @param {String} deviceName 'CODEY' 'ROCKY' * @param {String} commandStr such as 'SET_COLOUR' * @param {...Array} args command args * @return {Undefined} undefined */ sendBlockCommand(deviceName, commandStr, ...args) { // 可缓存已执行的命令 if (Transport.checkOnline()) { neuronsEngine.sendBlockCommand(...arguments); } } /** * 在线模式接口:神经元 get 命令 * @param {String} deviceName 'CODEY' 'ROCKY' * @param {String} commandStr such as 'SET_COLOUR' * @param {...Array} args command args * @return {Number|Undefined} */ getBlockSubStatus(deviceName, commandStr, ...args) { if (Transport.checkOnline()) { let val = neuronsEngine.getBlockSubStatus(...arguments); return val && val[0]; } } /** * 停止机器所有动作 * @return {[type]} [description] */ // 临时方法 stopRobot() { if (Transport.checkOnline()) { // stop sound.wav and stop note neuronsEngine.sendBlockCommand('CODEY', 'STOP_SOUND'); // stop move neuronsEngine.sendBlockCommand('ROCKY', 'SET_STOP_MOVE'); // turn off led neuronsEngine.sendBlockCommand('CODEY', 'SET_COLOUR', [0, 0, 0]); }; } startListenRobot(sensorCommandStr, fn) { if (!sensorCommandStr) { return; } let callback = function(name, values) { // TODO: 因为浮点型变化太过频繁,对变化值进行一次滤波 let value = floatArrayFixed(values, 2); // => array if (value.length && typeof fn === 'function') { fn({ sensor: name, value }); } } // 绑定回调函数并发送上报指令 neuronAdapter.bindBlockChangeHandler(sensorCommandStr, callback); neuronAdapter.sendReportCommand(sensorCommandStr, true); } endListenRobot(sensorCommandStr) { if (!sensorCommandStr) { return; } neuronAdapter.sendReportCommand(sensorCommandStr, false); // 停止上报 neuronAdapter.unBindBlockChangeHandler(sensorCommandStr); } /** * 监听机器人 * @param {String} sensorCommandStr, eg: 'CODEY_Command_GET_BUTTON_PRESSED' * @param {Function} fn 回调函数 * @param {Number} mode 0x01, 0x01, 0x02 */ addRobotListener(sensorCommandStr, fn) { console.error('error: ', 'the "addRobotListener" has been deprecated and please install version 0.3.7 or above.') } /** * 在线读取 codey 传感器的值 * @param {[type]} sensorCommandStr [description] * @return {[type]} [description] */ readRobotSensor(sensorCommandStr, params=[]) { if (!sensorCommandStr) { return {}; } return new Promise((resolve, reject) => { let timer = setTimeout(() => { neuronAdapter.unBindBlockChangeHandler(sensorCommandStr); reject({ 'err': 'system error, please check the connection or whether it works offline' }); }, 2000); let {deviceName, sensorName, command} = neuronAdapter.parseSensorName_(sensorCommandStr); //如果已处于上报模式(这是由帽子块触发的上报模式),则直接从中取值 let isReported = neuronAdapter.isReported(sensorCommandStr); if(isReported) { clearTimeout(timer); let name = (sensorName||'').toLowerCase(); let values = neuronsEngine.getBlockSubStatus(deviceName, name, 1); let value = floatArrayFixed(values||[], 2); // => array resolve({ sensor: name, value }); }else { // 非上报模式,则是一次指令读一次值 neuronAdapter.bindBlockChangeHandler(sensorCommandStr, (name, values) => { clearTimeout(timer); // 将本次监听解绑 neuronAdapter.unBindBlockChangeHandler(sensorCommandStr); let value = floatArrayFixed(values, 2); // => array resolve({ sensor: name, value }); }); this.sendBlockCommand(deviceName, command+'_'+sensorName, params); } }); } get ROBOT_SENSOR() { return neuronAdapter.SENSOR_COMMANDS; } } //umd 模块 webpack4已经修正这个问题,因此使用 es6的方式 umd 打包即可 // module.exports = Meoser; export default Meoser;