diff --git a/app/drivers/robotry/Parodule_Firmware.exe b/app/drivers/robotry/Parodule_Firmware.exe new file mode 100644 index 0000000000..0ddbfd37b6 Binary files /dev/null and b/app/drivers/robotry/Parodule_Firmware.exe differ diff --git a/app/modules/robotry_parodule.js b/app/modules/robotry_parodule.js new file mode 100644 index 0000000000..147fab3094 --- /dev/null +++ b/app/modules/robotry_parodule.js @@ -0,0 +1,304 @@ +const BaseModule = require('./robotry'); +class Parodule extends BaseModule { + // 클래스 내부에서 사용될 필드들을 이곳에서 선언합니다. + constructor() { + super(); + this.sp = null; + this.controlTypes = { + DIGITAL: 0, + ANALOG: 1, + STRING: 2, + }; + this.UNKNOWN = 207; + this.NONE = 208; + this.PIXEL = 209; + this.MOTOR = 210; + this.BUZZER = 211; + this.paroduleData = { + SENSOR: { + '0': 0, + '1': 0, + '2': 0, + '3': 0, + }, + MODULE: { + '0': 0, + '1': 0, + '2': 0, + '3': 0, + }, + MODULE1: '픽셀', + MODULE2: '픽셀', + MODULE3: '픽셀', + MODULE4: '픽셀', + }; + this.isConnect = false; + this.cmdTime = 0; + this.portTimeList = [0, 0, 0, 0, 0]; + this.terminal = [0xee, 0xee, 0xee, 0xee, '\n']; // 터미널 버퍼 저장 공간 + this.moduleOff = [0xff, 0x55, 0xc8, 0xc8, 0xc8, 0xc8, '\n']; // 모듈 종료 인터럽트 + // this.bleDisconCode = new Buffer('123\r\n'); + this.paroduleEntry = new Buffer('entry\r\n'); // 엔트리 모듈 내부 셰이킹 + this.paroduleInit = [0xff, 0x55, 0xff, 0xff, 0xff, 0xff, 0x0a]; // 엔트리용 모듈 인식 코드 + this.paroduleClose = new Buffer('spclose\r\n'); // 시리어 포트 종료 신호 + this.isSend = true; + this.pre_time = 0; + } + /* + 최초에 커넥션이 이루어진 후의 초기 설정. + handler 는 워크스페이스와 통신하 데이터를 json 화 하는 오브젝트입니다. (datahandler/json 참고) + config 은 module.json 오브젝트입니다. + */ + init(handler, config) { + this.handler = handler; + this.config = config; + } + setSerialPort(sp) { + let self = this; + this.sp = sp; + } + afterConnect(that, cb) { + that.connected = true; + if (cb) { + cb('connected'); + } + } + connect() { + this.isConnect = true; + } + /* + 연결 후 초기에 송신할 데이터가 필요한 경우 사용합니다. + requestInitialData 를 사용한 경우 checkInitialData 가 필수입니다. + 이 두 함수가 정의되어있어야 로직이 동작합니다. 필요없으면 작성하지 않아도 됩니다. + */ + requestInitialData() { + return this.paroduleEntry; + } + // 연결 후 초기에 수신받아서 정상연결인지를 확인해야하는 경우 사용합니다. + checkInitialData(data, config) { + if (data) { + return true; + } + else { + return false; + } + + } + // 주기적으로 하드웨어에서 받은 데이터의 검증이 필요한 경우 사용합니다. + validateLocalData(data) { + return true; + } + /* + 하드웨어 기기에 전달할 데이터를 반환합니다. + slave 모드인 경우 duration 속성 간격으로 지속적으로 기기에 요청을 보냅니다. + */ + requestLocalData() { + // 하드웨어로 보낼 데이터 로직 + if (!this.isConnect) { + return; + } + if (this.sendBuffers.length > 0) { + if (this.sp) { + this.sp.write(this.sendBuffers.shift(), () => { + this.sp.drain(() => { + }); + }); + } + } + return null; + } + + // 하드웨어에서 온 데이터 처리 + handleLocalData(data) { + let self = this; + let datas = this.getDataByBuffer(data); + // 데이터 처리 로직 + datas.forEach((data) => { + // 센서 데이터만 걸러냄 + if (data.length < 6) { + return; + } + else if (data[0] == 0xff && data[1] == 0x44) { + //console.log(data); + let temp = ['', '', '', '']; + let readData = data.subarray(2, data.length); + for (let i = 0; i < 4; i++) { + self.paroduleData.MODULE[i] = readData[i]; + } + for (let i = 0; i < 4; i++) { + let value = self.paroduleData.MODULE[i]; + if (value == this.PIXEL) { + temp[i] = '픽셀'; + } + else if (value == this.MOTOR) { + temp[i] = '모터'; + } + else if (value == this.BUZZER) { + temp[i] = '부저'; + } + else if (value == this.NONE) { + temp[i] = '없음'; + } else { + temp[i] = '모름'; + } + } + self.paroduleData.MODULE1 = temp[0]; + self.paroduleData.MODULE2 = temp[1]; + self.paroduleData.MODULE3 = temp[2]; + self.paroduleData.MODULE4 = temp[3]; + } + else if (data[0] == 0xff && data[1] == 0x66) { + let readData = data.subarray(2, data.length); + for (let i = 0; i < 4; i++) { + self.paroduleData.SENSOR[i] = readData[i]; + } + } + }); + } + // 엔트리로 전달할 데이터 + requestRemoteData(handler) { + let self = this; + if (!self.paroduleData) { + return; + } + this.lastSendTime = this.lastTime; + Object.keys(this.paroduleData).forEach(function (key) { + if (self.paroduleData[key] != undefined) { + handler.write(key, self.paroduleData[key]); + self.canSendData = false; + } + }); + } + // 엔트리에서 받은 데이터에 대한 처리 + handleRemoteData(handler) { + let self = this; + let cmdDatas = handler.read('CMD'); + let getDatas = handler.read('GET'); + let setDatas = handler.read('SET'); + let time = handler.read('TIME'); + let buffer = new Buffer([]); + // 입력 모듈일 경우 + if (getDatas) { + } + // 출력 모듈일 경우 + if (setDatas) { + let setKey = Object.keys(setDatas); + setKey.forEach(function (port) { + let data = setDatas[port]; + if (data) { + if (self.portTimeList[port] < data.time) { + self.portTimeList[port] = data.time + if (!self.isRecentData(port, data.type, data.data)) { + self.recentCheckData[port] = { + type: data.type, + data: data.data + } + self.updateTerminalBuffer(port); + buffer = new Buffer(self.makeOutputBuffer(data.type, null)); + } + } + } + }); + + } + // 커맨드 명령어 + if (cmdDatas) { + if (self.cmdTime < cmdDatas.time) { + self.cmdTime = cmdDatas.time; + + if (!self.isRecentData(cmdDatas.data)) { + self.recentCheckData = { + data: cmdDatas.data + } + buffer = new Buffer(cmdDatas.data); + } + } + } + if (buffer.length) { + this.sendBuffers.push(buffer); + } + else { + buffer = new Buffer([0xff, 0x55, 0xff, 0xff, 0xff, 0xff, 0x0a]); + if (this.isSend) { + this.isSend = false; + this.sendBuffers.push(buffer); + } + } + } + // recentCheckData 리스트에 있는 경우 true 반환 아니면 false + isRecentData(port, type, data) { + let isRecent = false; + const interval = 1000; + + if (port in this.recentCheckData) { + if (this.recentCheckData[port].type === type && this.recentCheckData[port].data === data) { + isRecent = true; + if (Date.now() - this.pre_time > interval) { // 같은 데이터가 연속인 경우 1초에 한번만 전송 + this.pre_time = Date.now(); + isRecent = false; + } + else { + isRecent = true; + } + } + } + return isRecent; + } + + updateTerminalBuffer(port) { + if (this.recentCheckData[port].data === 0) { + this.terminal[port] = 238; + } + else { + this.terminal[port] = this.recentCheckData[port].data; + } + } + makeOutputBuffer(dataType, data) { + let buffer; + if (dataType == this.controlTypes.STRING) { + buffer = new Buffer(data); + } + else if (dataType == this.controlTypes.DIGITAL) { + buffer = new Buffer([ + 0xff, + 0x55, + this.terminal[0], + this.terminal[1], + this.terminal[2], + this.terminal[3], + 0x0a + ]); + } + else { + } + return buffer; + } + // '\r\n' 을 기준으로 버퍼를 자른다 + getDataByBuffer(buffer) { + let datas = []; + let lastIndex = 0; + buffer.forEach(function (value, idx) { + if (value == 13 && buffer[idx + 1] == 10) { + datas.push(buffer.subarray(lastIndex, idx)); + lastIndex = idx + 2; + } + }); + return datas; + } + // 연결 해제되면 시리얼 포트 제거 + disconnect(connect) { + const spClose = this.paroduleClose; + if (this.sp) { + this.sp.write(spClose, () => { + this.sp.drain(() => { + connect.close(); + this.isConnect = false; + }) + }) + } + } + // 리셋 + reset() { + } +} +module.exports = new Parodule(); \ No newline at end of file diff --git a/app/modules/robotry_parodule.json b/app/modules/robotry_parodule.json new file mode 100644 index 0000000000..8509bb4580 --- /dev/null +++ b/app/modules/robotry_parodule.json @@ -0,0 +1,29 @@ +{ + "id": "4B0201", + "name": { + "en": "Parodule", + "ko": "파로듈" + }, + "category": "board", + "platform": ["win32", "darwin"], + "icon": "robotry_parodule.png", + "module": "robotry_parodule.js", + "url": "http://parob.io", + "email": "hee.kim6133@robotry.co.kr", + "reconnect": true, + "driver": [{ + "win32-x64": "robotry/Parodule_Firmware.exe", + "translate": "펌웨어 업데이트" + }], + "hardware": { + "type": "serial", + "control": "slave", + "duration": 25, + "pnpId": "1001", + "flowControl": "hardware", + "baudRate": 115200, + "lostTimer": 1000, + "byteDelimiter": [13, 10], + "firmwarecheck": false + } +} \ No newline at end of file diff --git a/app/modules/robotry_parodule.png b/app/modules/robotry_parodule.png new file mode 100644 index 0000000000..5837c68a3a Binary files /dev/null and b/app/modules/robotry_parodule.png differ diff --git a/app/modules/robotry_robitStage.js b/app/modules/robotry_robitStage.js index a903f15b61..41e86adbc6 100644 --- a/app/modules/robotry_robitStage.js +++ b/app/modules/robotry_robitStage.js @@ -19,11 +19,11 @@ class robitStage extends BaseModule { }; } - setSerialPort(sp){ + setSerialPort(sp) { var self = this; this.sp = sp; }; - + afterConnect(that, cb) { that.connected = true; if (cb) { @@ -33,7 +33,7 @@ class robitStage extends BaseModule { requestInitialData() { return this.makeSensorReadBuffer(this.sensorTypes.ANALOG, 0); } - + /* 하드웨어 기기에 전달할 데이터를 반환합니다. slave 모드인 경우 duration 속성 간격으로 지속적으로 기기에 요청을 보냅니다. @@ -44,9 +44,9 @@ class robitStage extends BaseModule { if (!this.isDraing && this.sendBuffers.length > 0) { this.isDraing = true; - this.sp.write(this.sendBuffers.shift(), function() { + this.sp.write(this.sendBuffers.shift(), function () { if (self.sp) { - self.sp.drain(function() { + self.sp.drain(function () { self.isDraing = false; }); } @@ -62,7 +62,7 @@ class robitStage extends BaseModule { var self = this; var datas = this.getDataByBuffer(data); - datas.forEach(function(data) { + datas.forEach(function (data) { if (data.length <= 4 || data[0] !== 255 || data[1] !== 85) { return; } @@ -122,7 +122,7 @@ class robitStage extends BaseModule { return; } this.lastSendTime = this.lastTime; - Object.keys(this.sensorData).forEach(function(key) { + Object.keys(this.sensorData).forEach(function (key) { if (self.sensorData[key] != undefined) { handler.write(key, self.sensorData[key]); self.canSendData = false; @@ -141,7 +141,7 @@ class robitStage extends BaseModule { if (getDatas) { var keys = Object.keys(getDatas); - keys.forEach(function(key) { + keys.forEach(function (key) { var isSend = false; var dataObj = getDatas[key]; if (typeof dataObj.port === 'string' || typeof dataObj.port === 'number') { @@ -151,13 +151,13 @@ class robitStage extends BaseModule { self.digitalPortTimeList[dataObj.port] = dataObj.time; } } else if (Array.isArray(dataObj.port)) { - isSend = dataObj.port.every(function(port) { + isSend = dataObj.port.every(function (port) { var time = self.digitalPortTimeList[port]; return dataObj.time > time; }); if (isSend) { - dataObj.port.forEach(function(port) { + dataObj.port.forEach(function (port) { self.digitalPortTimeList[port] = dataObj.time; }); } @@ -177,7 +177,7 @@ class robitStage extends BaseModule { if (setDatas) { var setKeys = Object.keys(setDatas); - setKeys.forEach(function(port) { + setKeys.forEach(function (port) { var data = setDatas[port]; if (data) { if (self.digitalPortTimeList[port] < data.time) { @@ -212,7 +212,7 @@ class robitStage extends BaseModule { return isRecent; } - + /* GET인 경우 SensorType 에 따라 버퍼를 생성 ff 55 len idx action device port slot data a @@ -263,7 +263,7 @@ class robitStage extends BaseModule { if (sensorIdx > 254) { sensorIdx = 0; } - + return buffer; } //0xff 0x55 0x6 0x0 0x1 0xa 0x9 0x0 0x0 0xa @@ -313,7 +313,7 @@ class robitStage extends BaseModule { case this.sensorTypes.TONE: { } } - + return buffer; } @@ -321,7 +321,7 @@ class robitStage extends BaseModule { getDataByBuffer(buffer) { var datas = []; var lastIndex = 0; - buffer.forEach(function(value, idx) { + buffer.forEach(function (value, idx) { if (value == 13 && buffer[idx + 1] == 10) { datas.push(buffer.subarray(lastIndex, idx)); lastIndex = idx + 2; @@ -335,13 +335,13 @@ class robitStage extends BaseModule { disconnect(connect) { var self = this; connect.close(); - if(self.sp) { + if (self.sp) { delete self.sp; } } // 리셋 - reset(){ + reset() { this.lastTime = 0; this.lastSendTime = 0; this.sensorData.PULSEIN = {}