Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
112 changes: 109 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,111 @@
# Readme
# Neuroscope

TODO
This project builds on [this boiler plate](https://github.com/a133xz/electron-vuejs-parcel-boilerplate) and adds functionality to control a Sphero Bolt using WebSocket commands.

Project builds on [this boiler plate](https://github.com/a133xz/electron-vuejs-parcel-boilerplate).
## Getting Started

### Prerequisites

- Node.js
- npm or yarn
- Python 3.x
- `websockets` Python package

### Installation

1. Clone the repository:
```sh
git clone https://github.com/yourusername/neuroscope.git
cd neuroscope
```

2. Install the dependencies:
```sh
yarn install
```

3. Start the Sphero server:
```sh
python SpheroServer.py
```

4. Start the Electron application:
```sh
yarn serve
```

## Usage

### Controlling the Sphero

The application sends WebSocket commands to control the Sphero Bolt. The following commands are available:

- **drone-up**: Moves the Sphero up.
- **drone-down**: Moves the Sphero down.
- **drone-forward**: Moves the Sphero forward.

### Code Changes

The main changes are in the `index.js` file:

1. **WebSocket Setup**:
```javascript
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8765');

ws.on('open', function open() {
console.log('WebSocket connection opened');
});

ws.on('error', function error(err) {
console.error('WebSocket error:', err);
});
```

2. **Command Sending with Debounce**:
```javascript
let lastCommandTime = 0;
const commandInterval = 3000; // 3 seconds

function sendCommand(command) {
const currentTime = Date.now();
if (currentTime - lastCommandTime >= commandInterval) {
ws.send(JSON.stringify(command));
console.log("Command sent:", command);
lastCommandTime = currentTime;
} else {
console.log("Command skipped to avoid spamming:", command);
}
}
```

3. **IPC Event Handlers**:
```javascript
ipcMain.on("drone-up", (event, response) => {
let recent_val = parseInt(response);
let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone up", upVal, "sent", response);
const moveCommand = { action: "move", heading: 0, speed: upVal, duration: 1 };
sendCommand(moveCommand);
});

ipcMain.on("drone-down", (event, response) => {
let recent_val = parseInt(response);
let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone down", downVal, "sent", response);
const moveCommand = { action: "move", heading: 180, speed: downVal, duration: 1 };
sendCommand(moveCommand);
});

ipcMain.on("drone-forward", (event, response) => {
let recent_val = parseInt(response);
let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone forward", forwardVal, "sent", response);
const moveCommand = { action: "move", heading: 90, speed: forwardVal, duration: 1 };
sendCommand(moveCommand);
});
```

## Work in Progress

This project is a work in progress. The current implementation allows basic control of the Sphero Bolt using WebSocket commands. Further improvements and features are planned for future updates.
76 changes: 76 additions & 0 deletions SpheroServer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import asyncio
import websockets
import json
from spherov2 import scanner
from spherov2.sphero_edu import SpheroEduAPI
from spherov2.types import Color

# Function to find the Sphero BOLT synchronously
async def find_toy():
try:
print("Scanning for Sphero BOLT...")
# Wrapping the synchronous `find_toy` method in a coroutine
loop = asyncio.get_event_loop()
toy = await loop.run_in_executor(None, scanner.find_toy)
if not toy:
print("No Sphero BOLT found.")
return None
print("Sphero BOLT found!")
return toy
except Exception as e:
print(f"Error during toy scanning: {e}")
return None

# Command handler for Sphero
async def handle_command(droid, command):
try:
if command["action"] == "led_on":
color = command.get("color", {"r": 0, "g": 255, "b": 0}) # Default green
print(f"Turning LED on with color: {color}")
droid.set_main_led(Color(r=color["r"], g=color["g"], b=color["b"]))
elif command["action"] == "led_off":
print("Turning LED off")
droid.set_main_led(Color(r=0, g=0, b=0)) # Turn off LED
elif command["action"] == "move":
print("Moving Sphero BOLT")
heading = command.get("heading", 0) # Default to up
speed = command.get("speed", 60)
duration = command.get("duration", 2)
droid.roll(heading, speed, duration)
else:
print(f"Unknown command: {command}")
except Exception as e:
print(f"Error handling command {command}: {e}")
raise e # Propagate the exception for better debugging

async def handle_connection(websocket):
print("Client connected")
toy = await find_toy() # Find the Sphero BOLT asynchronously
if not toy:
print("Sphero BOLT not found!")
await websocket.send(json.dumps({"error": "Sphero BOLT not found!"}))
return

with SpheroEduAPI(toy) as droid:
droid.set_main_led(Color(r=0, g=0, b=255)) # Set LED to blue for idle
try:
async for message in websocket:
print(f"Received message: {message}")
try:
command = json.loads(message)
await handle_command(droid, command)
except json.JSONDecodeError:
print(f"Invalid JSON received: {message}")
await websocket.send(json.dumps({"error": "Invalid JSON format"}))
except websockets.exceptions.ConnectionClosed:
print("Client disconnected")
except Exception as e:
print(f"Unexpected server error: {e}")

async def main():
print("Starting WebSocket server on ws://localhost:8765")
async with websockets.serve(handle_connection, "localhost", 8765):
await asyncio.Future() # Keep the server running indefinitely

if __name__ == "__main__":
asyncio.run(main())
31 changes: 31 additions & 0 deletions SpheroTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from spherov2 import scanner
from spherov2.sphero_edu import SpheroEduAPI
from spherov2.types import Color

def main():
print("Scanning for Sphero BOLT...")
toy = scanner.find_toy() # Synchronously find the Sphero BOLT
if not toy:
print("No Sphero BOLT found!")
return

print("Sphero BOLT found! Connecting...")
with SpheroEduAPI(toy) as droid:
print("Connected to Sphero BOLT!")

# Set LED color to green
print("Setting LED color to green...")
droid.set_main_led(Color(r=0, g=255, b=0))

# Move forward with heading 0, speed 60, for 2 seconds
print("Moving forward...")
droid.roll(heading=0, speed=60, duration=2)

# Set LED color to red after moving
print("Setting LED color to red...")
droid.set_main_led(Color(r=255, g=0, b=0))

print("Done!")

if __name__ == "__main__":
main()
57 changes: 44 additions & 13 deletions src/main/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const path = require("path");
const tello = require("./tello.js");
const WebSocket = require('ws');

const isProduction =
process.env.NODE_ENV === "production" || !process || !process.env || !process.env.NODE_ENV;
Expand Down Expand Up @@ -56,7 +57,7 @@ async function createWindow() {
// Reload
try {
require("electron-reloader")(module);
} catch (_) {}
} catch (_) { }
// Errors are thrown if the dev tools are opened
// before the DOM is ready
win.webContents.once("dom-ready", async () => {
Expand Down Expand Up @@ -125,33 +126,63 @@ async function createWindow() {
}
});

const ws = new WebSocket('ws://localhost:8765');

ws.on('open', function open() {
console.log('WebSocket connection opened');
});

ws.on('error', function error(err) {
console.error('WebSocket error:', err);
});

let lastCommandTime = 0;
const commandInterval = 3000; // 3 seconds

function sendCommand(command) {
const currentTime = Date.now();
if (currentTime - lastCommandTime >= commandInterval) {
ws.send(JSON.stringify(command));
console.log("Command sent:", command);
lastCommandTime = currentTime;
} else {
console.log("Command skipped to avoid spamming:", command);
}
}

ipcMain.on("drone-up", (event, response) => {
let recent_val = parseInt(response);
let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone up", upVal, "sent", response);
tello.up(upVal);
let rightVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("Sphero right", rightVal, "sent", response);
const moveCommand = { action: "move", heading: 90, speed: rightVal, duration: 3 };
sendCommand(moveCommand);
});

ipcMain.on("drone-down", (event, response) => {
let recent_val = parseInt(response);
let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone down", downVal, "sent", response);
tello.down(downVal);
console.log("Sphero Left", downVal, "sent", response);
const moveCommand = { action: "move", heading: 270, speed: downVal, duration: 3 };
sendCommand(moveCommand);
});

ipcMain.on("drone-forward", (event, response) => {
let recent_val = parseInt(response);
_maxSpeed = 80;
let val = recent_val > _maxSpeed ? _maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone forward", val, "sent", response);
tello.forward(val);
let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone forward", forwardVal, "sent", response);
const moveCommand = { action: "move", heading: 0, speed: forwardVal, duration: 1 };
sendCommand(moveCommand);
});

ipcMain.on("drone-back", (event, response) => {
let recent_val = parseInt(response);
let val = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone back", val, "sent", response);
tello.back(val);
let backVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
console.log("drone back", backVal, "sent", response);
// let val = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val;
// console.log("drone back", val, "sent", response);
const moveCommand = { action: "move", heading: 180, speed: backVal, duration: 1 };
sendCommand(moveCommand);
// tello.back(val);
});

ipcMain.on("cw", (event, response) => {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/js/customblock.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ javascriptGenerator.forBlock["wait_seconds"] = function (block) {
/* droneUp() */
var droneUp = {
type: "drone_up",
message0: "up %1 cm",
message0: "Right %1 cm",
args0: [{ type: "input_value", name: "value", check: "Number" }],
previousStatement: null,
nextStatement: null,
Expand All @@ -236,7 +236,7 @@ javascriptGenerator.forBlock["drone_up"] = function (block, generator) {
/* droneDown() */
var droneDown = {
type: "drone_down",
message0: "down %1 cm",
message0: "Left %1 cm",
args0: [{ type: "input_value", name: "value", check: "Number" }],
previousStatement: null,
nextStatement: null,
Expand Down
Loading