Working BLE on S3
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
# PlatformIO
|
||||
.pio/
|
||||
.pioenvs/
|
||||
.piolibdeps/
|
||||
lib/readme.txt
|
||||
|
||||
# Python Virtual Environments
|
||||
venv/
|
||||
env/
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# IDEs and Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS Generated Files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
@@ -75,9 +75,9 @@ STEP_ANGLE = 360/2048 ≈ 0.17578125 °/step
|
||||
### UUIDs (must be identical in firmware AND `web/js/ble.js`)
|
||||
|
||||
```
|
||||
Service: a0b1c2d3-e4f5-6789-abcd-ef0123456700
|
||||
Command: a0b1c2d3-e4f5-6789-abcd-ef0123456701 (WRITE | WRITE_NR)
|
||||
Status: a0b1c2d3-e4f5-6789-abcd-ef0123456702 (NOTIFY)
|
||||
Service: 18f3b235-9831-4c75-8ec0-210469b820a0
|
||||
Command: cd083b06-4447-4cf3-a7c3-322ecf802ce4 (WRITE)
|
||||
Status: 82e38c5b-d3ab-41d1-861c-b84dc6bb1e03 (NOTIFY)
|
||||
Name: WijiBoard
|
||||
```
|
||||
|
||||
@@ -127,19 +127,8 @@ esp32s3-wiji/
|
||||
|
||||
### Module loading (index.html)
|
||||
|
||||
All ES modules are loaded with a **cache-buster** to prevent stale code on refresh:
|
||||
|
||||
```javascript
|
||||
const V = Date.now();
|
||||
const { default: Router } = await import(`./web/js/router.js?v=${V}`);
|
||||
const { default: BLE } = await import(`./web/js/ble.js?v=${V}`);
|
||||
const { default: UI } = await import(`./web/js/ui.js?v=${V}`);
|
||||
const { default: HomeSection } = await import(`./web/js/sections/home.js?v=${V}`);
|
||||
const { default: StepperSection } = await import(`./web/js/sections/stepper-test.js?v=${V}`);
|
||||
```
|
||||
|
||||
> The `?v=${V}` suffix on ES module imports is mandatory. Without it, browsers aggressively
|
||||
> cache modules and you will see stale code running despite file changes.
|
||||
All ES modules are loaded with static imports in `index.html`.
|
||||
**CRITICAL**: Do NOT use dynamic cache-busting imports (e.g. `import('./ble.js?v=123')`) for the BLE module. Doing so causes the browser to instantiate multiple copies of the `BLE` singleton, breaking the event emitter (UI will not update when BLE connects).
|
||||
|
||||
---
|
||||
|
||||
@@ -414,6 +403,8 @@ To add a new section:
|
||||
| **M1 at +d2, M2 at -d2** | This is the PositionControl.cpp convention. Reversing it causes visual arm crossing. Verified by tracing `xmd = x - d2` (offset from +d2 pivot) and `xpd = x + d2` (offset from -d2 pivot). |
|
||||
| **`forward()` picks upward EE** | Two circle-circle intersections exist; the mechanism always operates in the "above" configuration. The lower solution is physically blocked by the board. |
|
||||
| **`armsCrossed()` guard** | Prevents IK moves that would drive an elbow into the mechanism housing from the wrong side. Tunable via `LIMITS.ELBOW_BOX_X_INNER` and `ELBOW_BOX_Y_MAX`. |
|
||||
| **Linux/BlueZ stability** | `pAdv->setMaxPreferred(0x0C)` is mandatory in firmware. Without it, Linux/ChromeOS will drop the connection immediately after the handshake. |
|
||||
| **No dynamic BLE imports**| `index.html` must import `ble.js` via static `import`. Dynamic cache-busters create multiple instances of the BLE singleton, isolating UI event listeners! |
|
||||
| **`fivebarIKGame.js` is reference only** | The original site's game uses a different angle convention (absolute degrees, different home, with `ikElbowSigns` tracking). We use the C++ IK formula instead because it directly produces step deltas from home. `fivebarIKGame.js` is kept in the repo for reference and understanding, not imported. |
|
||||
| **Hash-based routing** | Keeps the SPA working from `file://` and simple static servers without needing a history API setup |
|
||||
|
||||
|
||||
+8
-8
@@ -121,12 +121,11 @@
|
||||
|
||||
<!-- ── App bootstrap (ES module entry point) ─────────────────── -->
|
||||
<script type="module">
|
||||
const V = Date.now(); // cache-buster – ensures fresh module load on every page refresh
|
||||
const { default: Router } = await import(`./web/js/router.js?v=${V}`);
|
||||
const { default: BLE } = await import(`./web/js/ble.js?v=${V}`);
|
||||
const { default: UI } = await import(`./web/js/ui.js?v=${V}`);
|
||||
const { default: HomeSection } = await import(`./web/js/sections/home.js?v=${V}`);
|
||||
const { default: StepperSection } = await import(`./web/js/sections/stepper-test.js?v=${V}`);
|
||||
import Router from './web/js/router.js';
|
||||
import BLE from './web/js/ble.js';
|
||||
import UI from './web/js/ui.js';
|
||||
import HomeSection from './web/js/sections/home.js';
|
||||
import StepperSection from './web/js/sections/stepper-test.js';
|
||||
|
||||
// ── Register routes ────────────────────────────────────────────
|
||||
Router.register('home', HomeSection);
|
||||
@@ -177,8 +176,9 @@
|
||||
BLE.disconnect();
|
||||
} else {
|
||||
BLE.connect().catch(e => {
|
||||
if (!e.message?.includes('cancelled') && !e.name?.includes('NotFound')) {
|
||||
UI.log(`Connect error: ${e.message}`, 'error');
|
||||
console.error('[BLE Connect Error]', e);
|
||||
if (!e.message?.includes('cancelled')) {
|
||||
UI.log(`Connect error: ${e.message} (${e.name})`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ build_flags =
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
board_upload.flash_size = 4MB
|
||||
board_build.partitions = default.csv
|
||||
|
||||
; Adjust to your COM port
|
||||
monitor_speed = 115200
|
||||
upload_protocol = esptool
|
||||
|
||||
+201
-202
@@ -26,12 +26,12 @@
|
||||
* Status : a0b1c2d3-e4f5-6789-abcd-ef0123456702
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <AccelStepper.h>
|
||||
#include <Arduino.h>
|
||||
#include <BLE2902.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLE2902.h>
|
||||
|
||||
// ─── Motor pin mapping (28BYJ-48 / ULN2003) ──────────────────────
|
||||
// Motor 1 – Shoulder
|
||||
@@ -47,32 +47,32 @@
|
||||
#define M2_IN4 11
|
||||
|
||||
// ─── Stepper constants ────────────────────────────────────────────
|
||||
#define STEPS_PER_REV 2048
|
||||
#define DEFAULT_SPEED 600.0f // steps/sec
|
||||
#define DEFAULT_ACCEL 100.0f // steps/sec²
|
||||
#define STEPS_PER_REV 2048
|
||||
#define DEFAULT_SPEED 600.0f // steps/sec
|
||||
#define DEFAULT_ACCEL 100.0f // steps/sec²
|
||||
|
||||
// ─── BLE UUIDs ────────────────────────────────────────────────────
|
||||
#define SERVICE_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456700"
|
||||
#define CMD_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456701"
|
||||
#define STATUS_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456702"
|
||||
#define DEVICE_NAME "WijiBoard"
|
||||
#define SERVICE_UUID "18f3b235-9831-4c75-8ec0-210469b820a0"
|
||||
#define CMD_UUID "cd083b06-4447-4cf3-a7c3-322ecf802ce4"
|
||||
#define STATUS_UUID "82e38c5b-d3ab-41d1-861c-b84dc6bb1e03"
|
||||
#define DEVICE_NAME "WijiBoard"
|
||||
|
||||
// ─── Stepper objects ──────────────────────────────────────────────
|
||||
AccelStepper stepper1(AccelStepper::FULL4WIRE, M1_IN1, M1_IN2, M1_IN3, M1_IN4);
|
||||
AccelStepper stepper2(AccelStepper::FULL4WIRE, M2_IN1, M2_IN2, M2_IN3, M2_IN4);
|
||||
|
||||
// ─── BLE globals ─────────────────────────────────────────────────
|
||||
BLEServer* pServer = nullptr;
|
||||
BLECharacteristic* pCmdChar = nullptr;
|
||||
BLECharacteristic* pStatusChar = nullptr;
|
||||
bool bleConnected = false;
|
||||
BLEServer *pServer = nullptr;
|
||||
BLECharacteristic *pCmdChar = nullptr;
|
||||
BLECharacteristic *pStatusChar = nullptr;
|
||||
bool bleConnected = false;
|
||||
|
||||
// ─── Status notify throttle ───────────────────────────────────────
|
||||
unsigned long lastNotify = 0;
|
||||
const unsigned long NOTIFY_INTERVAL_MS = 200;
|
||||
|
||||
// ─── Forward declarations ─────────────────────────────────────────
|
||||
void parseCommand(const String& cmd);
|
||||
void parseCommand(const String &cmd);
|
||||
void sendPosition();
|
||||
void performHomingAll();
|
||||
void performHoming1();
|
||||
@@ -80,241 +80,240 @@ void performHoming2();
|
||||
|
||||
// ─── BLE Server Callbacks ─────────────────────────────────────────
|
||||
class ServerCallbacks : public BLEServerCallbacks {
|
||||
void onConnect(BLEServer*) override {
|
||||
bleConnected = true;
|
||||
Serial.println("[BLE] Client connected");
|
||||
}
|
||||
void onDisconnect(BLEServer*) override {
|
||||
bleConnected = false;
|
||||
Serial.println("[BLE] Client disconnected – restarting advertising");
|
||||
BLEDevice::startAdvertising();
|
||||
}
|
||||
void onConnect(BLEServer *) override {
|
||||
bleConnected = true;
|
||||
Serial.println("[BLE] Client connected");
|
||||
}
|
||||
void onDisconnect(BLEServer *) override {
|
||||
bleConnected = false;
|
||||
Serial.println("[BLE] Client disconnected – restarting advertising");
|
||||
BLEDevice::startAdvertising();
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Command Characteristic Callbacks ────────────────────────────
|
||||
class CmdCallbacks : public BLECharacteristicCallbacks {
|
||||
void onWrite(BLECharacteristic* pChar) override {
|
||||
String val = pChar->getValue().c_str();
|
||||
val.trim();
|
||||
if (val.length() == 0) return;
|
||||
Serial.printf("[CMD] Received: '%s'\n", val.c_str());
|
||||
parseCommand(val);
|
||||
}
|
||||
void onWrite(BLECharacteristic *pChar) override {
|
||||
String val = pChar->getValue().c_str();
|
||||
val.trim();
|
||||
if (val.length() == 0)
|
||||
return;
|
||||
Serial.printf("[CMD] Received: '%s'\n", val.c_str());
|
||||
parseCommand(val);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Command parser ───────────────────────────────────────────────
|
||||
void parseCommand(const String& cmd) {
|
||||
// S1+<n> or S1-<n>
|
||||
if (cmd.startsWith("S1") && cmd.length() > 2) {
|
||||
long steps = cmd.substring(2).toInt(); // '+' or '-' prefix handled by toInt()
|
||||
stepper1.move(steps);
|
||||
Serial.printf("[S1] Move %+ld steps\n", steps);
|
||||
return;
|
||||
}
|
||||
// S2+<n> or S2-<n>
|
||||
if (cmd.startsWith("S2") && cmd.length() > 2) {
|
||||
long steps = cmd.substring(2).toInt();
|
||||
stepper2.move(steps);
|
||||
Serial.printf("[S2] Move %+ld steps\n", steps);
|
||||
return;
|
||||
}
|
||||
// SPD:<n>
|
||||
if (cmd.startsWith("SPD:")) {
|
||||
float spd = cmd.substring(4).toFloat();
|
||||
stepper1.setMaxSpeed(spd);
|
||||
stepper2.setMaxSpeed(spd);
|
||||
Serial.printf("[CFG] Max speed → %.0f steps/sec\n", spd);
|
||||
return;
|
||||
}
|
||||
// ACC:<n>
|
||||
if (cmd.startsWith("ACC:")) {
|
||||
float acc = cmd.substring(4).toFloat();
|
||||
stepper1.setAcceleration(acc);
|
||||
stepper2.setAcceleration(acc);
|
||||
Serial.printf("[CFG] Acceleration → %.0f steps/sec²\n", acc);
|
||||
return;
|
||||
}
|
||||
// HOME1
|
||||
if (cmd == "HOME1") {
|
||||
Serial.println("[S1] Homing Motor 1...");
|
||||
performHoming1();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// HOME2
|
||||
if (cmd == "HOME2") {
|
||||
Serial.println("[S2] Homing Motor 2...");
|
||||
performHoming2();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// HOMEALL
|
||||
if (cmd == "HOMEALL") {
|
||||
Serial.println("[SYS] Homing both motors...");
|
||||
performHomingAll();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// POS – explicit position request
|
||||
if (cmd == "POS") {
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
Serial.printf("[CMD] Unknown command: '%s'\n", cmd.c_str());
|
||||
void parseCommand(const String &cmd) {
|
||||
// S1+<n> or S1-<n>
|
||||
if (cmd.startsWith("S1") && cmd.length() > 2) {
|
||||
long steps =
|
||||
cmd.substring(2).toInt(); // '+' or '-' prefix handled by toInt()
|
||||
stepper1.move(steps);
|
||||
Serial.printf("[S1] Move %+ld steps\n", steps);
|
||||
return;
|
||||
}
|
||||
// S2+<n> or S2-<n>
|
||||
if (cmd.startsWith("S2") && cmd.length() > 2) {
|
||||
long steps = cmd.substring(2).toInt();
|
||||
stepper2.move(steps);
|
||||
Serial.printf("[S2] Move %+ld steps\n", steps);
|
||||
return;
|
||||
}
|
||||
// SPD:<n>
|
||||
if (cmd.startsWith("SPD:")) {
|
||||
float spd = cmd.substring(4).toFloat();
|
||||
stepper1.setMaxSpeed(spd);
|
||||
stepper2.setMaxSpeed(spd);
|
||||
Serial.printf("[CFG] Max speed → %.0f steps/sec\n", spd);
|
||||
return;
|
||||
}
|
||||
// ACC:<n>
|
||||
if (cmd.startsWith("ACC:")) {
|
||||
float acc = cmd.substring(4).toFloat();
|
||||
stepper1.setAcceleration(acc);
|
||||
stepper2.setAcceleration(acc);
|
||||
Serial.printf("[CFG] Acceleration → %.0f steps/sec²\n", acc);
|
||||
return;
|
||||
}
|
||||
// HOME1
|
||||
if (cmd == "HOME1") {
|
||||
Serial.println("[S1] Homing Motor 1...");
|
||||
performHoming1();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// HOME2
|
||||
if (cmd == "HOME2") {
|
||||
Serial.println("[S2] Homing Motor 2...");
|
||||
performHoming2();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// HOMEALL
|
||||
if (cmd == "HOMEALL") {
|
||||
Serial.println("[SYS] Homing both motors...");
|
||||
performHomingAll();
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
// POS – explicit position request
|
||||
if (cmd == "POS") {
|
||||
sendPosition();
|
||||
return;
|
||||
}
|
||||
Serial.printf("[CMD] Unknown command: '%s'\n", cmd.c_str());
|
||||
}
|
||||
|
||||
// ─── Send current positions via BLE NOTIFY ────────────────────────
|
||||
void sendPosition() {
|
||||
if (!bleConnected || !pStatusChar) return;
|
||||
String pos = "P:" + String(stepper1.currentPosition()) +
|
||||
"," + String(stepper2.currentPosition());
|
||||
pStatusChar->setValue(pos.c_str());
|
||||
pStatusChar->notify();
|
||||
Serial.printf("[POS] %s\n", pos.c_str());
|
||||
if (!bleConnected || !pStatusChar)
|
||||
return;
|
||||
String pos = "P:" + String(stepper1.currentPosition()) + "," +
|
||||
String(stepper2.currentPosition());
|
||||
pStatusChar->setValue(pos.c_str());
|
||||
pStatusChar->notify();
|
||||
Serial.printf("[POS] %s\n", pos.c_str());
|
||||
}
|
||||
|
||||
// ─── Setup ────────────────────────────────────────────────────────
|
||||
void setup() {
|
||||
// Fix: zero TX buffer to eliminate CDC post-write stall
|
||||
Serial.setTxBufferSize(0);
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.println("\n[BOOT] WijiBoard Stepper Controller");
|
||||
Serial.setTxTimeoutMs(0);
|
||||
Serial.begin(115200);
|
||||
delay(1000); // Wait for USB CDC to enumerate
|
||||
Serial.println("\n[BOOT] WijiBoard Stepper Controller");
|
||||
|
||||
// ── Init steppers ──────────────────────────────────────────
|
||||
stepper1.setMaxSpeed(DEFAULT_SPEED);
|
||||
stepper1.setAcceleration(DEFAULT_ACCEL);
|
||||
stepper1.setCurrentPosition(0);
|
||||
// ── Init steppers ──────────────────────────────────────────
|
||||
stepper1.setMaxSpeed(DEFAULT_SPEED);
|
||||
stepper1.setAcceleration(DEFAULT_ACCEL);
|
||||
stepper1.setCurrentPosition(0);
|
||||
|
||||
stepper2.setMaxSpeed(DEFAULT_SPEED);
|
||||
stepper2.setAcceleration(DEFAULT_ACCEL);
|
||||
stepper2.setCurrentPosition(0);
|
||||
stepper2.setMaxSpeed(DEFAULT_SPEED);
|
||||
stepper2.setAcceleration(DEFAULT_ACCEL);
|
||||
stepper2.setCurrentPosition(0);
|
||||
|
||||
Serial.println("[STEP] Steppers initialized");
|
||||
Serial.println("[STEP] Steppers initialized");
|
||||
|
||||
// ── Init BLE ───────────────────────────────────────────────
|
||||
BLEDevice::init(DEVICE_NAME);
|
||||
// ── Init BLE ───────────────────────────────────────────────
|
||||
BLEDevice::init(DEVICE_NAME);
|
||||
|
||||
pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new ServerCallbacks());
|
||||
pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new ServerCallbacks());
|
||||
|
||||
// Service
|
||||
BLEService* pService = pServer->createService(SERVICE_UUID);
|
||||
// Service
|
||||
BLEService *pService = pServer->createService(SERVICE_UUID);
|
||||
|
||||
// Command characteristic (Write without response)
|
||||
pCmdChar = pService->createCharacteristic(
|
||||
CMD_UUID,
|
||||
BLECharacteristic::PROPERTY_WRITE |
|
||||
BLECharacteristic::PROPERTY_WRITE_NR
|
||||
);
|
||||
pCmdChar->setCallbacks(new CmdCallbacks());
|
||||
// Command characteristic (Write without response)
|
||||
pCmdChar = pService->createCharacteristic(CMD_UUID,
|
||||
BLECharacteristic::PROPERTY_WRITE);
|
||||
pCmdChar->setCallbacks(new CmdCallbacks());
|
||||
|
||||
// Status characteristic (Notify)
|
||||
pStatusChar = pService->createCharacteristic(
|
||||
STATUS_UUID,
|
||||
BLECharacteristic::PROPERTY_NOTIFY
|
||||
);
|
||||
pStatusChar->addDescriptor(new BLE2902());
|
||||
// Status characteristic (Notify)
|
||||
pStatusChar = pService->createCharacteristic(
|
||||
STATUS_UUID, BLECharacteristic::PROPERTY_NOTIFY);
|
||||
// pStatusChar->addDescriptor(new BLE2902()); // Removed due to
|
||||
// deprecation/NimBLE conflicts
|
||||
|
||||
pService->start();
|
||||
pService->start();
|
||||
|
||||
// Advertising
|
||||
BLEAdvertising* pAdv = BLEDevice::getAdvertising();
|
||||
pAdv->addServiceUUID(SERVICE_UUID);
|
||||
pAdv->setScanResponse(true);
|
||||
pAdv->setMinPreferred(0x06);
|
||||
BLEDevice::startAdvertising();
|
||||
// Advertising
|
||||
BLEAdvertising *pAdv = BLEDevice::getAdvertising();
|
||||
pAdv->addServiceUUID(SERVICE_UUID);
|
||||
pAdv->setScanResponse(true);
|
||||
pAdv->setMinPreferred(0x06); // 7.5ms
|
||||
pAdv->setMaxPreferred(0x0C); // 15ms - CRITICAL for Linux/BlueZ stability
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
Serial.printf("[BLE] Advertising as '%s' – ready!\n", DEVICE_NAME);
|
||||
Serial.printf("[BLE] Advertising as '%s' – ready!\n", DEVICE_NAME);
|
||||
}
|
||||
|
||||
// ─── Loop ─────────────────────────────────────────────────────────
|
||||
void loop() {
|
||||
// Run steppers (non-blocking AccelStepper)
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
// Run steppers (non-blocking AccelStepper)
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
|
||||
// Periodic position notify while motors are moving
|
||||
if (bleConnected) {
|
||||
unsigned long now = millis();
|
||||
bool moving = stepper1.isRunning() || stepper2.isRunning();
|
||||
if (moving && (now - lastNotify >= NOTIFY_INTERVAL_MS)) {
|
||||
lastNotify = now;
|
||||
sendPosition();
|
||||
}
|
||||
// Periodic position notify while motors are moving
|
||||
if (bleConnected) {
|
||||
unsigned long now = millis();
|
||||
bool moving = stepper1.isRunning() || stepper2.isRunning();
|
||||
if (moving && (now - lastNotify >= NOTIFY_INTERVAL_MS)) {
|
||||
lastNotify = now;
|
||||
sendPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Homing Routines ──────────────────────────────────────────────
|
||||
|
||||
void performHomingAll() {
|
||||
// 1. Move against limits (M1 CCW, M2 CCW)
|
||||
stepper2.moveTo(1024);
|
||||
stepper1.moveTo(2048);
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
// 1. Move against limits (M1 CCW, M2 CCW)
|
||||
stepper2.moveTo(1024);
|
||||
stepper1.moveTo(2048);
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
|
||||
// 2. Move to negative limits (CW)
|
||||
stepper2.moveTo(-1050);
|
||||
stepper1.move(-1300); // equivalent to moveTo(-1300) since pos is 0
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(0);
|
||||
// 2. Move to negative limits (CW)
|
||||
stepper2.moveTo(-1050);
|
||||
stepper1.move(-1300); // equivalent to moveTo(-1300) since pos is 0
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(0);
|
||||
|
||||
// 3. Move to final home position
|
||||
stepper2.moveTo(550);
|
||||
stepper1.moveTo(-530);
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
// 3. Move to final home position
|
||||
stepper2.moveTo(550);
|
||||
stepper1.moveTo(-530);
|
||||
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
stepper2.run();
|
||||
}
|
||||
|
||||
// 4. Set origin relative to this final position
|
||||
stepper2.setCurrentPosition(-1024);
|
||||
stepper1.setCurrentPosition(0);
|
||||
// 4. Set origin relative to this final position
|
||||
stepper2.setCurrentPosition(-1024);
|
||||
stepper1.setCurrentPosition(0);
|
||||
|
||||
stepper1.disableOutputs();
|
||||
stepper2.disableOutputs();
|
||||
Serial.println("[SYS] Homing all complete.");
|
||||
stepper1.disableOutputs();
|
||||
stepper2.disableOutputs();
|
||||
Serial.println("[SYS] Homing all complete.");
|
||||
}
|
||||
|
||||
void performHoming1() {
|
||||
// M1 (Right) stalls going positive (CCW)
|
||||
stepper1.move(2048);
|
||||
while (stepper1.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
// M1 (Right) stalls going positive (CCW)
|
||||
stepper1.move(2048);
|
||||
while (stepper1.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
|
||||
// Move to final home position (CW)
|
||||
stepper1.moveTo(-530);
|
||||
while (stepper1.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
stepper1.disableOutputs();
|
||||
Serial.println("[S1] Homing complete.");
|
||||
// Move to final home position (CW)
|
||||
stepper1.moveTo(-530);
|
||||
while (stepper1.distanceToGo() != 0) {
|
||||
stepper1.run();
|
||||
}
|
||||
stepper1.setCurrentPosition(0);
|
||||
stepper1.disableOutputs();
|
||||
Serial.println("[S1] Homing complete.");
|
||||
}
|
||||
|
||||
void performHoming2() {
|
||||
// M2 (Left) stalls going negative (CW)
|
||||
stepper2.move(-2048);
|
||||
while (stepper2.distanceToGo() != 0) {
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(0);
|
||||
// M2 (Left) stalls going negative (CW)
|
||||
stepper2.move(-2048);
|
||||
while (stepper2.distanceToGo() != 0) {
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(0);
|
||||
|
||||
// Move to final home position (CCW)
|
||||
stepper2.moveTo(550);
|
||||
while (stepper2.distanceToGo() != 0) {
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(-1024);
|
||||
stepper2.disableOutputs();
|
||||
Serial.println("[S2] Homing complete.");
|
||||
// Move to final home position (CCW)
|
||||
stepper2.moveTo(550);
|
||||
while (stepper2.distanceToGo() != 0) {
|
||||
stepper2.run();
|
||||
}
|
||||
stepper2.setCurrentPosition(-1024);
|
||||
stepper2.disableOutputs();
|
||||
Serial.println("[S2] Homing complete.");
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import asyncio
|
||||
import sys
|
||||
try:
|
||||
from bleak import BleakScanner, BleakClient
|
||||
except ImportError:
|
||||
print("Please install bleak first: pip install bleak")
|
||||
sys.exit(1)
|
||||
|
||||
DEVICE_NAME = "WijiBoard"
|
||||
SERVICE_UUID = "18f3b235-9831-4c75-8ec0-210469b820a0"
|
||||
CMD_UUID = "cd083b06-4447-4cf3-a7c3-322ecf802ce4"
|
||||
STATUS_UUID = "82e38c5b-d3ab-41d1-861c-b84dc6bb1e03"
|
||||
|
||||
async def main():
|
||||
print(f"Scanning for {DEVICE_NAME}...")
|
||||
devices = await BleakScanner.discover(timeout=5.0)
|
||||
|
||||
target_device = None
|
||||
for d in devices:
|
||||
if d.name == DEVICE_NAME:
|
||||
target_device = d
|
||||
break
|
||||
|
||||
if not target_device:
|
||||
print(f"Could not find {DEVICE_NAME}. Make sure it is powered on and advertising.")
|
||||
return
|
||||
|
||||
print(f"Found {DEVICE_NAME} at {target_device.address}. Connecting...")
|
||||
|
||||
async with BleakClient(target_device.address) as client:
|
||||
print(f"Connected: {client.is_connected}")
|
||||
|
||||
# Setup notifications
|
||||
def notification_handler(sender, data):
|
||||
print(f"[STATUS UPDATE] {data.decode('utf-8')}")
|
||||
|
||||
print("Subscribing to status characteristic...")
|
||||
try:
|
||||
await client.start_notify(STATUS_UUID, notification_handler)
|
||||
print("Successfully subscribed!")
|
||||
except Exception as e:
|
||||
print(f"Failed to subscribe: {e}")
|
||||
|
||||
# Send a test command
|
||||
test_cmd = "S1+100"
|
||||
print(f"Sending test command: '{test_cmd}'...")
|
||||
await client.write_gatt_char(CMD_UUID, test_cmd.encode('utf-8'), response=False)
|
||||
|
||||
print("Waiting 5 seconds to receive any status updates...")
|
||||
await asyncio.sleep(5.0)
|
||||
|
||||
print("Disconnecting...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+25
-12
@@ -25,16 +25,16 @@
|
||||
*/
|
||||
|
||||
// ── UUIDs – must match src/main.cpp exactly ──────────────────────
|
||||
const SERVICE_UUID = 'a0b1c2d3-e4f5-6789-abcd-ef0123456700';
|
||||
const CMD_UUID = 'a0b1c2d3-e4f5-6789-abcd-ef0123456701';
|
||||
const STATUS_UUID = 'a0b1c2d3-e4f5-6789-abcd-ef0123456702';
|
||||
const DEVICE_NAME = 'WijiBoard';
|
||||
const SERVICE_UUID = '18f3b235-9831-4c75-8ec0-210469b820a0';
|
||||
const CMD_UUID = 'cd083b06-4447-4cf3-a7c3-322ecf802ce4';
|
||||
const STATUS_UUID = '82e38c5b-d3ab-41d1-861c-b84dc6bb1e03';
|
||||
const DEVICE_NAME = 'WijiBoard';
|
||||
|
||||
const BLE = (() => {
|
||||
// ── Internal state ──────────────────────────────────────────
|
||||
let device = null;
|
||||
let server = null;
|
||||
let cmdChar = null;
|
||||
let device = null;
|
||||
let server = null;
|
||||
let cmdChar = null;
|
||||
let statusChar = null;
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
@@ -60,9 +60,9 @@ const BLE = (() => {
|
||||
// ── Disconnect handler ──────────────────────────────────────
|
||||
function onDisconnected() {
|
||||
console.log('[BLE] Device disconnected');
|
||||
device = null;
|
||||
server = null;
|
||||
cmdChar = null;
|
||||
device = null;
|
||||
server = null;
|
||||
cmdChar = null;
|
||||
statusChar = null;
|
||||
emit('disconnected');
|
||||
}
|
||||
@@ -75,34 +75,47 @@ const BLE = (() => {
|
||||
|
||||
// ── connect() ───────────────────────────────────────────────
|
||||
async function connect() {
|
||||
console.log('[BLE] connect() called');
|
||||
if (!navigator.bluetooth) {
|
||||
throw new Error('Web Bluetooth API not available. Use Chrome/Edge over HTTPS or localhost.');
|
||||
}
|
||||
|
||||
emit('connecting');
|
||||
|
||||
console.log('[BLE] Requesting device...');
|
||||
device = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ name: DEVICE_NAME }],
|
||||
optionalServices: [SERVICE_UUID],
|
||||
});
|
||||
console.log('[BLE] Device selected:', device.name);
|
||||
|
||||
device.addEventListener('gattserverdisconnected', onDisconnected);
|
||||
|
||||
console.log('[BLE] Connecting to GATT server...');
|
||||
server = await device.gatt.connect();
|
||||
console.log('[BLE] GATT server connected');
|
||||
|
||||
console.log('[BLE] Getting primary service:', SERVICE_UUID);
|
||||
const service = await server.getPrimaryService(SERVICE_UUID);
|
||||
console.log('[BLE] Primary service found');
|
||||
|
||||
console.log('[BLE] Getting command characteristic:', CMD_UUID);
|
||||
cmdChar = await service.getCharacteristic(CMD_UUID);
|
||||
console.log('[BLE] Command characteristic found');
|
||||
|
||||
// Status notifications (optional – firmware may not have this char yet)
|
||||
try {
|
||||
console.log('[BLE] Getting status characteristic:', STATUS_UUID);
|
||||
statusChar = await service.getCharacteristic(STATUS_UUID);
|
||||
console.log('[BLE] Starting notifications...');
|
||||
await statusChar.startNotifications();
|
||||
statusChar.addEventListener('characteristicvaluechanged', onStatusNotification);
|
||||
} catch {
|
||||
console.warn('[BLE] Status characteristic not available – notifications disabled');
|
||||
console.log('[BLE] Notifications started');
|
||||
} catch (err) {
|
||||
console.warn('[BLE] Status characteristic not available – notifications disabled', err);
|
||||
}
|
||||
|
||||
console.log('[BLE] Connection fully established, emitting connected');
|
||||
emit('connected', device.name);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user