meoser
Version:
meos protocol engine
506 lines (474 loc) • 16.4 kB
JavaScript
/**
* @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;