Skip to content

Commit ca849b2

Browse files
authored
refac: create a js class to the arduino Uno Q interaction (#37)
* feat: implement ArduinoUnoQ class for enhanced LED and matrix control * fix: update ArduinoUnoQ connection to use dynamic hostname * refactor: update ArduinoUnoQ integration to use default host and enhance object detection methods * refactor: update ArduinoUnoQ import statements for consistency * fix: revert DEFAULT_HOST to use dynamic hostname for improved flexibility * refactor: standardize import statement formatting for ArduinoUnoQ across multiple files * refactor: update ArduinoUnoQ import statements for consistency across multiple files * docs: update installation instructions for clarity and formatting consistency
1 parent 78b6ff9 commit ca849b2

File tree

5 files changed

+160
-78
lines changed

5 files changed

+160
-78
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,21 @@ Accessible from any device via a browser, it makes coding, electronics, and AI h
1111

1212
## Installation
1313

14-
- Connect the Arduino Uno Q board via USB
15-
- Open an `adb shell` into the board using [adb](https://docs.arduino.cc/software/app-lab/tutorials/cli/)
16-
- Copy and paste the following command into the terminal to install the latest `scratch-arduino-app` into the board:
14+
- Open a terminal inside the UNO Q board (you can also use the [adb](https://docs.arduino.cc/software/app-lab/tutorials/cli/) tool)
15+
- Copy and paste the following command into the terminal to install the latest `scratch-arduino-app`:
1716

1817
```
1918
curl -sSL https://raw.githubusercontent.com/dido18/scratch-arduino-app/main/install.sh | bash
2019
```
2120

22-
- Open the Scratch interface at the `<IP_OR_BOARD_NAME>:7000` address
21+
- Open the Scratch interface at the `https://<IP_OR_BOARD_NAME>:7000` address.
2322

24-
### Local development
23+
NOTE: the `https` is needed by the `getUserMedia()` method for security reason.
24+
25+
## Local development
2526

2627
- `task scratch:init`
2728
- `task scratch:local:start`
2829
- `ŧask board:upload`
29-
- change the `const wsServerURL =`ws://<YOUR_IP>:7000`;` in the `index.js`
30+
- change the `const DEFAULT_HOST =`<YOUR_IP|BOARD_NAME>`;` in the `scratch-arduino-extensions/packages/scratch-vm/src/extensions/ArduinoUnoQ.js`
3031
- Open local scratch on http://localhost:8601/
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const io = require("./socket.io.min.js");
2+
3+
const DEFAULT_HOST = window.location.hostname;
4+
5+
class ArduinoUnoQ {
6+
constructor() {
7+
this.serverURL = `wss://${DEFAULT_HOST}:7000`;
8+
9+
this.io = io(this.serverURL, {
10+
path: "/socket.io",
11+
transports: ["polling", "websocket"],
12+
autoConnect: true,
13+
});
14+
this.isConnected = false;
15+
16+
this._setupConnectionHandlers();
17+
}
18+
19+
on(event, callback) {
20+
if (this.io) {
21+
this.io.on(event, callback);
22+
console.log(`Registered event listener for: ${event}`);
23+
} else {
24+
console.error("Socket.io not initialized");
25+
}
26+
}
27+
28+
emit(event, data) {
29+
if (this.io && this.isConnected) {
30+
this.io.emit(event, data);
31+
console.log(`Emitted event: ${event}`, data);
32+
} else {
33+
console.warn(`Cannot emit ${event}: Not connected to Arduino UNO Q`);
34+
}
35+
}
36+
37+
_setupConnectionHandlers() {
38+
this.io.on("connect", () => {
39+
this.isConnected = true;
40+
console.log(`Connected to Arduino UNO Q at ${this.serverURL}`);
41+
});
42+
43+
this.io.on("disconnect", (reason) => {
44+
this.isConnected = false;
45+
console.log(`Disconnected from Arduino UNO Q: ${reason}`);
46+
});
47+
48+
this.io.on("connect_error", (error) => {
49+
console.error(`Connection error:`, error.message);
50+
});
51+
52+
this.io.on("reconnect", (attemptNumber) => {
53+
console.log(`Reconnected to Arduino UNO Q after ${attemptNumber} attempts`);
54+
});
55+
}
56+
57+
connect() {
58+
if (!this.io.connected) {
59+
console.log("Attempting to connect to Arduino UNO Q...");
60+
this.io.connect();
61+
}
62+
}
63+
64+
disconnect() {
65+
if (this.io.connected) {
66+
console.log("Disconnecting from Arduino UNO Q...");
67+
this.io.disconnect();
68+
}
69+
}
70+
71+
// ===== LED CONTROL METHODS =====
72+
/**
73+
* Set RGB LED color
74+
* @param {string} led - LED identifier ("LED3" or "LED4")
75+
* @param {number} r - Red value (0-255)
76+
* @param {number} g - Green value (0-255)
77+
* @param {number} b - Blue value (0-255)
78+
*/
79+
setLedRGB(led, r, g, b) {
80+
this.io.emit("set_led_rgb", {
81+
led: led,
82+
r: Math.max(0, Math.min(255, r)),
83+
g: Math.max(0, Math.min(255, g)),
84+
b: Math.max(0, Math.min(255, b)),
85+
});
86+
console.log(`Setting ${led} to RGB(${r}, ${g}, ${b})`);
87+
}
88+
89+
/**
90+
* Turn off LED
91+
* @param {string} led - LED identifier ("LED3" or "LED4")
92+
*/
93+
turnOffLed(led) {
94+
this.setLedRGB(led, 0, 0, 0);
95+
}
96+
97+
// ===== MATRIX CONTROL METHODS =====
98+
99+
/**
100+
* Draw frame on LED matrix
101+
* @param {string} frame - 25-character string representing 5x5 matrix (0s and 1s)
102+
*/
103+
matrixDraw(frame) {
104+
if (typeof frame !== "string" || frame.length !== 25) {
105+
console.error("Invalid frame format. Expected 25-character string of 0s and 1s");
106+
return;
107+
}
108+
// Validate frame contains only 0s and 1s
109+
if (!/^[01]+$/.test(frame)) {
110+
console.error("Frame must contain only 0s and 1s");
111+
return;
112+
}
113+
114+
this.io.emit("matrix_draw", { frame: frame });
115+
console.log(`Drawing matrix frame: ${frame}`);
116+
}
117+
118+
matrixClear() {
119+
const clearFrame = "0".repeat(25);
120+
this.matrixDraw(clearFrame);
121+
}
122+
123+
// AI object detection
124+
125+
detectObjects(imageData) {
126+
this.io.emit("detect_objects", { image: imageData });
127+
console.log("Emitted detect_objects event");
128+
}
129+
130+
// ===== EVENT HANDLING METHODS =====
131+
}
132+
133+
module.exports = ArduinoUnoQ;

scratch-arduino-extensions/packages/scratch-vm/src/extensions/arduino_basics/index.js

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
1-
// const formatMessage = require('../../../../../../scratch-editor/node_modules/format-message');
21
const BlockType = require("../../../../../../scratch-editor/packages/scratch-vm/src/extension-support/block-type");
32
const ArgumentType = require(
43
"../../../../../../scratch-editor/packages/scratch-vm/src/extension-support/argument-type",
54
);
6-
const io = require("../socket.io.min.js");
5+
const ArduinoUnoQ = require("../ArduinoUnoQ");
76

8-
/**
9-
* Url of icon to be displayed at the left edge of each extension block.
10-
* @type {string}
11-
*/
12-
// eslint-disable-next-line max-len
7+
// TODO: add icons
138
const iconURI = "";
14-
15-
/**
16-
* Url of icon to be displayed in the toolbox menu for the extension category.
17-
* @type {string}
18-
*/
19-
// eslint-disable-next-line max-len
209
const menuIconURI = "";
2110

22-
const wsServerURL = `${window.location.protocol}//${window.location.hostname}:7000`;
23-
2411
class ArduinoBasics {
2512
constructor(runtime) {
2613
this.runtime = runtime;
27-
28-
this.io = io(wsServerURL, {
29-
path: "/socket.io",
30-
transports: ["polling", "websocket"],
31-
autoConnect: true,
32-
});
14+
this.unoq = new ArduinoUnoQ();
15+
this.unoq.connect();
3316
}
3417
}
3518

@@ -88,27 +71,23 @@ ArduinoBasics.prototype.getInfo = function() {
8871
};
8972

9073
ArduinoBasics.prototype.matrixDraw = function(args) {
91-
console.log(`Drawing frame on matrix: ${args}`);
92-
this.io.emit("matrix_draw", { frame: args.FRAME });
74+
this.unoq.matrixDraw(args.FRAME);
9375
};
9476

9577
ArduinoBasics.prototype.matrixClear = function() {
96-
console.log("Clearing matrix");
97-
this.io.emit("matrix_draw", { frame: "0000000000000000000000000" });
78+
this.unoq.matrixClear();
9879
};
9980

10081
ArduinoBasics.prototype.setLed3 = function(args) {
10182
const hexColor = args.HEX;
10283
const rgb = this.hexToRgb(hexColor);
103-
console.log(`Setting led 3 to: r:${rgb.r}, g:${rgb.g}, b:${rgb.b} (HEX: ${hexColor})`);
104-
this.io.emit("set_led_rgb", { led: "LED3", r: rgb.r, g: rgb.g, b: rgb.b });
84+
this.unoq.setLedRGB("LED3", rgb.r, rgb.g, rgb.b);
10585
};
10686

10787
ArduinoBasics.prototype.setLed4 = function(args) {
10888
const hexColor = args.HEX;
10989
const rgb = this.hexToRgb(hexColor);
110-
console.log(`Setting led 4 to: r:${rgb.r}, g:${rgb.g}, b:${rgb.b} (HEX: ${hexColor})`);
111-
this.io.emit("set_led_rgb", { led: "LED4", r: rgb.r, g: rgb.g, b: rgb.b });
90+
this.unoq.setLedRGB("LED4", rgb.r, rgb.g, rgb.b);
11291
};
11392

11493
ArduinoBasics.prototype.hexToRgb = function(hex) {

scratch-arduino-extensions/packages/scratch-vm/src/extensions/arduino_modulino/index.js

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,21 @@ const BlockType = require("../../../../../../scratch-editor/packages/scratch-vm/
22
const ArgumentType = require(
33
"../../../../../../scratch-editor/packages/scratch-vm/src/extension-support/argument-type",
44
);
5-
const io = require("../socket.io.min.js");
5+
const ArduinoUnoQ = require("../ArduinoUnoQ");
66

7-
/**
8-
* Url of icon to be displayed at the left edge of each extension block.
9-
* @type {string}
10-
*/
11-
// eslint-disable-next-line max-len
7+
// TODO: add icons
128
const iconURI = "";
13-
14-
/**
15-
* Url of icon to be displayed in the toolbox menu for the extension category.
16-
* @type {string}
17-
*/
18-
// eslint-disable-next-line max-len
199
const menuIconURI = "";
2010

21-
const wsServerURL = `${window.location.protocol}//${window.location.hostname}:7000`;
22-
2311
class ArduinoModulino {
2412
constructor(runtime) {
2513
this.runtime = runtime;
26-
this.io = io(wsServerURL, {
27-
path: "/socket.io",
28-
transports: ["polling", "websocket"],
29-
autoConnect: true,
30-
});
14+
this.unoq = new ArduinoUnoQ();
15+
this.unoq.connect();
3116

3217
// TODO: move to ModulinoPeripheral
3318
this._button_pressed = "";
34-
this.io.on("modulino_buttons_pressed", (data) => {
19+
this.unoq.on("modulino_buttons_pressed", (data) => {
3520
console.log(`Modulino button pressed event received: ${data.btn}`);
3621
this._button_pressed = data.btn.toUpperCase();
3722
});

scratch-arduino-extensions/packages/scratch-vm/src/extensions/arduino_object_detection/index.js

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,16 @@ const BlockType = require("../../../../../../scratch-editor/packages/scratch-vm/
22
const ArgumentType = require(
33
"../../../../../../scratch-editor/packages/scratch-vm/src/extension-support/argument-type",
44
);
5-
const io = require("../socket.io.min.js");
65
const Video = require("../../../../../../scratch-editor/packages/scratch-vm/src/io/video");
76
const Rectangle = require("../../../../../../scratch-editor/packages/scratch-render/src/Rectangle.js");
87
const StageLayering = require("../../../../../../scratch-editor/packages/scratch-vm/src/engine/stage-layering.js");
98
const { Detection, MODEL_LABELS } = require("./object_detection");
9+
const ArduinoUnoQ = require("../ArduinoUnoQ");
1010

11-
/**
12-
* Url of icon to be displayed at the left edge of each extension block.
13-
* @type {string}
14-
*/
15-
// eslint-disable-next-line max-len
11+
// TODO add icons
1612
const iconURI = "";
17-
18-
/**
19-
* Url of icon to be displayed in the toolbox menu for the extension category.
20-
* @type {string}
21-
*/
22-
// eslint-disable-next-line max-len
2313
const menuIconURI = "";
2414

25-
const wsServerURL = `${window.location.protocol}//${window.location.hostname}:7000`;
26-
2715
/**
2816
* RGB color constants for confidence visualization
2917
*/
@@ -37,6 +25,9 @@ class ArduinoObjectDetection {
3725
constructor(runtime) {
3826
this.runtime = runtime;
3927

28+
this.unoq = new ArduinoUnoQ();
29+
this.unoq.connect();
30+
4031
/** @type {Array<Detection>} */
4132
this.detectedObjects = [];
4233

@@ -66,15 +57,8 @@ class ArduinoObjectDetection {
6657
}
6758
});
6859

69-
this.io = io(wsServerURL, {
70-
path: "/socket.io",
71-
transports: ["polling", "websocket"],
72-
autoConnect: true,
73-
});
74-
75-
this.io.on("detection_result", (data) => {
60+
this.unoq.on("detection_result", (data) => {
7661
this.detectedObjects = [];
77-
7862
this._clearBoundingBoxes();
7963

8064
data.detection.forEach((detection) => {
@@ -264,7 +248,7 @@ ArduinoObjectDetection.prototype._detectObjects = function(args) {
264248
}
265249
const dataUrl = canvas.toDataURL("image/png");
266250
const base64Frame = dataUrl.split(",")[1];
267-
this.io.emit("detect_objects", { image: base64Frame });
251+
this.unoq.detectObjects(base64Frame);
268252
};
269253

270254
ArduinoObjectDetection.prototype._clearBoundingBoxes = function(args) {

0 commit comments

Comments
 (0)