Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Turret Connection #12

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Demo of a P300-controlled turret.
## Setting Up

1. Clone this repo locally.
2. run "npm start" and wait.
2. Run neurostack client locally on port :8002
3. run "npm run robotserver"
4. in a new terminal, run "npm start" and wait.

## Developer Notes

Expand Down
441 changes: 436 additions & 5 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-scripts": "^2.1.8",
"serialport": "^8.0.6",
"socket.io": "2.1.1",
"uuid": "^3.3.2",
"wait-on": "^3.3.0"
Expand All @@ -19,7 +20,8 @@
"build": "react-scripts build",
"pretest": "npm install && eslint . --fix",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"robotserver": "node robotServer.js COM4"
},
"browserslist": [
">0.2%",
Expand Down
25 changes: 25 additions & 0 deletions robotServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const SerialPort = require("serialport");
const Readline = require('@serialport/parser-readline');
const io = require('socket.io')(8003) // FileServer will be run on http://localhost:8003

let portName = process.argv[2];

let myPort = new SerialPort(portName, {
baudRate:9600
});

const parser = myPort.pipe(new Readline({delimiter: '\n'}));

myPort.on('open', ()=>{console.log('Robot port open')});

parser.on('data', data=>{
console.log('Sending Robot Port data: ', data);
});

io.on('connection', (socket) => {
socket.on('data', data => {
console.log("Received: ", data);
myPort.write(data);
myPort.write('d');
});
});
22 changes: 2 additions & 20 deletions src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export default class App extends React.Component {
btnStates: Array(Arrows.BTN_VALS.length).fill("notSelected")
};
this.update = this.update.bind(this);
this.handleKeyChosen = this.handleKeyChosen.bind(this);
this.handleKeyNotChosen = this.handleKeyNotChosen.bind(this);
}

componentDidMount() {
Expand All @@ -42,6 +40,7 @@ export default class App extends React.Component {
this.setBtnState(curBtnIndex, "selected");
const curKey = Arrows.BTN_VALS[curBtnIndex];
this.props.updateCallback(curKey, (args) => {
this.props.handleData(args);
if(this.props.isChosen(curKey, args)){
this.setBtnState(curBtnIndex, "chosen");
this.props.handleSelection(curKey, args);
Expand All @@ -64,23 +63,6 @@ export default class App extends React.Component {

// HELPERS //

// Set the current key as highlighted and return it's index
selectCurrentKey(){
const curBtnIndex = this.getCurBtnIndex();
this.setBtnState(curBtnIndex, "selected");
return curBtnIndex;
}

handleKeyChosen(){
let curBtnIndex = this.selectCurrentKey();
this.setBtnState(curBtnIndex, "chosen");
this.shuffleOrder();
}

handleKeyNotChosen(){
this.updateCurIndices();
}

resetKeys() {
const btnStates = Array(Arrows.BTN_VALS.length).fill("notSelected");
this.setState({btnStates})
Expand Down Expand Up @@ -142,7 +124,7 @@ App.propTypes = {
updateCallback: PropTypes.func.isRequired,
isChosen: PropTypes.func.isRequired,
handleSelection: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
handleData: PropTypes.func.isRequired,
goBack: PropTypes.func.isRequired,
children: PropTypes.node
};
15 changes: 13 additions & 2 deletions src/components/KeyComponent/ArrowComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import Key from './KeyComponent';
import PropTypes from 'prop-types';

class Arrows extends Component {

static get BTN_VALS() {
return ["↖", "↑", "↗",
"←", "o", "→"]
}

static get BTN_ANGLES() {
return [45, 90, 135,
return [135, 90, 45,
180, 90, 0]
}

Expand All @@ -22,6 +21,18 @@ class Arrows extends Component {
return [[0,3], [1,4], [2,5]]
}

static ANGLE(arrow) {
let arrowAngles = {
"↖" : 135,
"↑" : 90,
"↗" : 45,
"←" : 180,
"o" : 90,
"→" : 0
};
return arrowAngles[arrow];
}

render() {
return (
<div className="userInput">
Expand Down
46 changes: 10 additions & 36 deletions src/containers/Training/Training.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import React from 'react';
import { getNextInstrPause } from '../../helpers/intervals';
import { sendTrainingFlashEvent, masterUUID } from '../../helpers/P300Communication';
import Sockets from "../../helpers/getSockets";
import { sendTrainingFlashEvent, masterUUID } from '../../helpers/SocketCommunication';
import App from '../../components/App/App';
import './Training.css';
import PropTypes from 'prop-types';

const client_socket = (new Sockets()).client_socket;
export default class Training extends React.Component {
constructor(props) {
super(props);
Expand All @@ -17,37 +14,19 @@ export default class Training extends React.Component {
accuracy: undefined
};
this.statement = '↖↑↖↑→';
this.trainingCompleted = this.trainingCompleted.bind(this);
this.updateAccuracy = this.updateAccuracy.bind(this);
this.onUpdate = this.onUpdate.bind(this);
}

onUpdate(selectedKey, handleChosen, handleNotChosen){
const curGoal = this.statement[this.state.lettersFound];
let P300 = selectedKey === curGoal;
sendTrainingFlashEvent(client_socket, masterUUID(), P300 ? 1 : 0, this.updateAccuracy);
if(P300){
handleChosen();
const newDisplay = this.state.displayText + selectedKey;
this.setState({
displayText : newDisplay,
lettersFound : this.state.lettersFound + 1,
});
if (this.state.lettersFound === this.statement.length) {
this.trainingCompleted();
}
} else {
handleNotChosen();
}
this.handleData = this.handleData.bind(this);
this.handleSelection = this.handleSelection.bind(this);
this.isP300 = this.isP300.bind(this);
}

render() {
return (
<App
updateCallback={(selection, handleResponse) =>
sendTrainingFlashEvent(client_socket, masterUUID(), this.isP300(selection), handleResponse)}
sendTrainingFlashEvent(masterUUID(), this.isP300(selection), handleResponse)}
isChosen={(selection) => this.isP300(selection)}
handleSelection={(selection) => {this.handleSelection(selection)}}
handleData={this.handleData}
goBack={this.props.goBack}
>
<div>
Expand All @@ -60,20 +39,14 @@ export default class Training extends React.Component {
)
}

// HELPERS //

// data is return value from Neurostack Client train event
updateAccuracy(data){
console.log("here", data)
// Neurostack client callback
handleData(data) {
if (data.acc) {
this.setState({accuracy: data.acc});
}
}

trainingCompleted(){
setTimeout(this.props.goBack, getNextInstrPause());
}

// Key is selected
handleSelection(selection){
const newDisplay = this.state.displayText + selection;
this.setState({
Expand All @@ -84,6 +57,7 @@ export default class Training extends React.Component {
setTimeout(this.props.goBack, getNextInstrPause());
}
}

isP300(curKey) {
const curGoal = this.statement[this.state.lettersFound];
return curKey === curGoal;
Expand Down
42 changes: 20 additions & 22 deletions src/containers/Turret/Turret.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import React from 'react';
import App from "../../components/App/App"
import Sockets from "../../helpers/getSockets";
import { sendPredictionEvent, masterUUID } from '../../helpers/P300Communication';
import App from "../../components/App/App";
import Arrows from "../../components/KeyComponent/ArrowComponent";
import { sendPredictionEvent, masterUUID, moveTurret } from '../../helpers/SocketCommunication';
import './Turret.css';
import PropTypes from 'prop-types';

const client_socket = (new Sockets()).client_socket;

export default class Turret extends React.Component {
constructor(props) {
super(props);
this.state = {
displayText: ""
};
this.handlePrediction = this.handlePrediction.bind(this);
this.handleData = this.handleData.bind(this);
this.handleSelection = this.handleSelection.bind(this);
}

render() {
return (
<App
updateCallback={(selection, handleResponse) =>
sendPredictionEvent(client_socket, masterUUID(), handleResponse)}
sendPredictionEvent(masterUUID(), handleResponse)}
isChosen={(selection, args) => args['p300']}
handleSelection={(selection, args) => {
console.log("P300 for key: " + selection + " with score: " + args['score'])}}
handleSelection={this.handleSelection}
handleData={this.handleData}
goBack={this.props.goBack}
>
<h3>Try to select a direction using your brain!</h3>
Expand All @@ -34,19 +33,18 @@ export default class Turret extends React.Component {

// HELPERS //

// data is return value from Neurostack Client predict event
handlePrediction(data, selectedKey, handleChosen, handleNotChosen){
if(data.p300){
console.log(selectedKey, " chosen. confidence: ", data.score*100, "%");
const newDisplay = this.state.displayText + selectedKey;
this.setState({
displayText : newDisplay
});
handleChosen();
} else {
console.log(selectedKey, " NOT chosen. confidence: ", data.score*100, "%");
handleNotChosen();
}
// Neurostack client callback
handleData(){
}

// key is selected
handleSelection(selection, args){
console.log("P300 for key: " + selection + " with score: " + args['score']);
const newDisplay = this.state.displayText + selection;
this.setState({
displayText : newDisplay
});
moveTurret(Arrows.ANGLE(selection));
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Sockets from "./getSockets";
const client_socket = (new Sockets()).client_socket;
const robot_socket = (new Sockets()).robot_socket;

function randomP300() {
// 10% chance of P300
return Math.floor(Math.random() * 10) === 0;
}

export function sendTrainingFlashEvent(client_socket, userId, p300, callback) {
export function sendTrainingFlashEvent(userId, p300, callback) {
let timestamp = Date.now() / 1000.0;
let json = {
'uuid': userId,
Expand All @@ -24,7 +28,7 @@ export function sendTrainingFlashEvent(client_socket, userId, p300, callback) {
}
}

export function sendPredictionEvent(client_socket, userId, callback) {
export function sendPredictionEvent(userId, callback) {
let timestamp = Date.now() / 1000.0;
let json = {
'uuid': userId,
Expand All @@ -37,7 +41,7 @@ export function sendPredictionEvent(client_socket, userId, callback) {
p300: randomP300(),
score: "0.4"
});
}else{
} else{
// Upon server respose, callback will execute
client_socket.once("predict", callback);
client_socket.emit("predict", JSON.stringify(json));
Expand All @@ -47,3 +51,7 @@ export function sendPredictionEvent(client_socket, userId, callback) {
export function masterUUID() {
return "masterUUID";
}

export function moveTurret(angle){
robot_socket.emit("data", angle.toString());
}
7 changes: 4 additions & 3 deletions src/helpers/getSockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ export default class Sockets {
"timeout" : 10000,
"transports" : ["websocket"]
};

this.client_socket = io('http://localhost:8002', connectionOptions); // Socket to connect to P300Client.
// this.robot_socket = io('http://localhost:8003'); // Socket to control Turret
// Socket to connect to P300Client.
this.client_socket = io('http://localhost:8002', connectionOptions);
// Socket to connect to Robot / Turret Server
this.robot_socket = io('http://localhost:8003', connectionOptions);

Sockets.instance = this;
}
Expand Down
11 changes: 0 additions & 11 deletions src/helpers/robotServer.js

This file was deleted.