diff --git a/clay.d.ts b/clay.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html index 8d2d68f..3d01ce0 100644 --- a/index.html +++ b/index.html @@ -48,6 +48,9 @@ + + +   @@ -77,6 +83,23 @@ + + diff --git a/js/scenes/scenes.js b/js/scenes/scenes.js index 1b5c892..f34fc37 100644 --- a/js/scenes/scenes.js +++ b/js/scenes/scenes.js @@ -42,6 +42,8 @@ export default () => { { name: "arrange2" , path: "./arrange2.js" , public: true }, { name: "widgets" , path: "./widgets.js" , public: true }, { name: "transfer" , path: "./transfer.js" , public: true }, + { name: "watchTest" , path: "./watchTest.js" , public: true }, + { name: "watchTest2" , path: "./watchTest2.js" , public: true }, { name: "transferBiciWidgets" , path: "./transferBiciWidgets.js" , public: true }, ] }; diff --git a/js/scenes/watchTest.js b/js/scenes/watchTest.js new file mode 100644 index 0000000..0c17d7f --- /dev/null +++ b/js/scenes/watchTest.js @@ -0,0 +1,81 @@ +import {ControllerBeam} from "../render/core/controllerInput.js"; +//import {Puck} from "../third-party/puck.js"; + +let beams = null; + +function getDistance(x1,y1,z1,x2,y2,z2) +{ + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + return Math.sqrt(dx*dx + dy*dy + dz*dz); +} + +export const init = async model => { + //Create a box + let targetBox = model.add('cube').move(0,1.5,-0.5).scale(0.15).color(1,1,1); + beams = { left : new ControllerBeam(model, 'left' ), + right: new ControllerBeam(model, 'right') + }; + let debugTextNode = model.add(); + let targetBox2 = model.add('sphere').move(0,1.5,-0.5).scale(0.25).color(1,1,1); + + + model.animate(() => { + + beams.left.update(); + beams.right.update(); + + // Use the global inputEvents to get the controller position + let rightHandPos = inputEvents.pos('right'); + + // Clear the previous frame's text geometry + while (debugTextNode.nChildren() > 0) { + debugTextNode.remove(0); + } + debugTextNode.add(clay.text("HELLO WORLD")).move(1, 1.8, -1).scale(10); + console.log(rightHandPos); + + /* + if (rightHandPos) { + // Get the current [X, Y, Z] position of the target box (the cube) + let boxMatrix = targetBox.getGlobalMatrix(); + //Transformation matrix + let boxX = boxMatrix[12]; + let boxY = boxMatrix[13]; + let boxZ = boxMatrix[14]; + + //Calculate how far the hand is from the box + let distance = getDistance(boxX, boxY, boxZ, rightHandPos[0], rightHandPos[1], rightHandPos[2]); + + let coordString = `Pos: ${rightHandPos[0].toFixed(2)}, ${rightHandPos[1].toFixed(2)}, ${rightHandPos[2].toFixed(2)}\nDist: ${distance.toFixed(2)}`; + + // Add text with color and a larger scale (0.1) to ensure it is visible in VR + debugTextNode.add(clay.text(coordString)).move(0, 1.8, -1).scale(0.1).color(1, 1, 1); + + while (debugTextNode.nChildren() > 0) { + debugTextNode.remove(0); + } + //let coordString = `X: ${rightHandPos[0].toFixed(2)} Y: ${rightHandPos[1].toFixed(2)} Z: ${rightHandPos[2].toFixed(2)} X: ${distance.toFixed(2)}`; + let coordString = "Hello"; + //debugTextNode.add(clay.text(coordString)).move(0, 1.8, -1).scale(0.04); + debugTextNode.add(clay.text("HELLO WORLD")).move(0, 1.8, -1).scale(0.04); + // Trigger visual feedback (red color) when touching the box + targetBox.color(distance <= 0.15 ? [1, 0, 0] : [1, 1, 1]); + + + if (distance <= 0.15) + { + targetBox.color(1,0,0); + } + else + { + targetBox.color(1,1,1) + } + } else { + // Fallback text if the controller isn't detected + debugTextNode.add(clay.text("Waiting for controller...")).move(0, 1.8, -1).scale(0.1).color(1, 1, 1); + }*/ + + }); +}; \ No newline at end of file diff --git a/js/scenes/watchTest2.js b/js/scenes/watchTest2.js new file mode 100644 index 0000000..defaab3 --- /dev/null +++ b/js/scenes/watchTest2.js @@ -0,0 +1,157 @@ +/* + This is a very simple example of how to use the + inputEvents object. + + When the scene is in XR mode, the x position of + the left controller controls the red component + of the cube's color, and the x position of the + right controller controls the blue component of + the cube's color. +*/ + +import {Puck} from "../third-party/puck.js"; + +function getDistance(x1,y1,z1,x2,y2,z2) +{ + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + return Math.sqrt(dx*dx + dy*dy + dz*dz); +} + +export const init = async model => { + console.log("Loaded watchTest2"); + let watchCommand = + "\x10" + + "Bangle.buzz(10,1);\n" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 10);\n" + // 80 might be too big for the screen! + "g.drawString('Connected to watchTest2', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n"; // Forces the screen on, just in case it went to sleep + Puck.write(watchCommand); + watchCommand = + "function makeWatchBuzz() {Bangle.buzz(90,0.2);}\n"; + Puck.write(watchCommand); + + + let obj1 = model.add('cube'); + let debugTextNode = model.add(); + let wasInside = false; + let lastBuzzTime = 0; + + // USING THE GLOBAL inputEvents OBJECT + + //inputEvents.onPress = hand => color = [0,0,1]; + //inputEvents.onRelease = hand => color = [1,0,0]; + + inputEvents.onMove = hand => { + if (isXR()) { + //Should I put code in animate or onMove? + let handPos = inputEvents.pos('right'); + console.log("Cur handPos" + handPos); + + //let boxMatrix = obj1.getGlobalMatrix(); + // //Transformation matrix + // let boxX = boxMatrix[12]; + // let boxY = boxMatrix[13]; + // let boxZ = boxMatrix[14]; + //let dist = getDistance(rightHandPos[0], rightHandPos[1], rightHandPos[2], boxX, boxY, boxZ); + //console.log(dist);*/ + + + } + } + + let color = [.5,.5,.5]; + model.move(0,1.5,0).scale(.1).animate(() => { + + obj1.identity().color(color); + if (true)//(isXR()) + { + //Should I put code in animate or onMove? + //let handPos = inputEvents.pos('right'); + let handPos = inputEvents.pos('right') ?? [0, 0, 0]; + console.log("Cur handPos" + handPos); + let boxMatrix = obj1.getGlobalMatrix(); + //Transformation matrix + let boxX = boxMatrix[12]; + let boxY = boxMatrix[13]; + let boxZ = boxMatrix[14]; + let dist = getDistance(handPos[0], handPos[1], handPos[2], boxX, boxY, boxZ); + + while (debugTextNode.nChildren() > 0) { + debugTextNode.remove(0); + } + //debugTextNode.add(clay.text("HELLO WORLD")).move(1, 1.8, -1).scale(30); + debugTextNode.add(clay.text("hand pos "+ handPos)).move(1, 1.8, -1).scale(30); + debugTextNode.add(clay.text("Distance "+ dist)).move(1, 1.0, -1).scale(30); + + if(dist < 0.4) + { + obj1.color(1,0,0); + if(!wasInside) + { + let command = + "\x10" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 20);\n" + + "g.drawString('Touching box', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n" + + "clearInterval(global.counterInterval);\n" + + "global.counterInterval = setInterval(makeWatchBuzz, 100);\n"; + Puck.write(command); + wasInside = true; + } + } + else + { + obj1.color(.5,.5,.5); + if(wasInside) + { + let exitCommand = + "\x10" + + "g.clear(1);\n" + + "clearInterval(global.counterInterval);\n" + + "Bangle.setLCDPower(0);\n" + + "g.drawString('Not touching box', g.getWidth()/2, g.getHeight()/2);\n" + Puck.write(exitCommand); + wasInside = false; + + } + } + + /* + if(dist < 0.4) + { + obj1.color(1,1,0); + if(!wasInside) + { + let screenCommand = + "\x10" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 40);\n" + + "g.drawString('Inside!', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n"; + Puck.write(screenCommand); + wasInside = true; + } + + let curTime = Date.now(); + if(curTime - lastBuzzTime > 200) + { + let watchCommand = + "\x10" + + "Bangle.buzz(160,0.2);\n"; + Puck.write(watchCommand); + lastBuzzTime = curTime; + } + + + } + */ + } + }); +} diff --git a/js/scenes/watchTest3.js b/js/scenes/watchTest3.js new file mode 100644 index 0000000..aeea83c --- /dev/null +++ b/js/scenes/watchTest3.js @@ -0,0 +1,179 @@ +/* + This is a very simple example of how to use the + inputEvents object. + + When the scene is in XR mode, the x position of + the left controller controls the red component + of the cube's color, and the x position of the + right controller controls the blue component of + the cube's color. +*/ + +import {Puck} from "../third-party/puck.js"; + +function getDistance(x1,y1,z1,x2,y2,z2) +{ + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + return Math.sqrt(dx*dx + dy*dy + dz*dz); +} + +export const init = async model => { + console.log("Loaded watchTest2"); + let watchCommand = + "\x10" + + "reset(); Bangle.buzz(10,1);\n" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 10);\n" + // 80 might be too big for the screen! + "g.drawString('Connected to watchTest2', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n"; // Forces the screen on, just in case it went to sleep + Puck.write(watchCommand); + //I tried sending over longer functions ahead of time, startVib and endVib, in this version. + //These are called below. My thinking was it is cleaner and also easier to send a few bytes just calling the function to the watch + //This doesn't work well though. It does work, but sometimes the watch freaks out when trying to clear the ram and restart the program, + //and then the vibration motor stops working until the watch is rebooted. I am not sure why, but I suspect the functions are + //somehow not clearing fully. + watchCommand = + "function makeWatchBuzz() {Bangle.buzz(500,0.2);}\n" + + "function startVib() {\n" + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 20);\n" + + "g.drawString('Touching box', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n" + + "clearInterval(global.counterInterval);\n" + + "global.counterInterval = setInterval(makeWatchBuzz, 510);}\n" + + "function endVib() {\n" + + "g.clear(1);\n" + + "clearInterval(global.counterInterval);\n" + + "Bangle.setLCDPower(1);\n" + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 20);\n" + + "g.drawString('Not touching box', g.getWidth()/2, g.getHeight()/2);}\n"; + Puck.write(watchCommand); + + + let obj1 = model.add('cube'); + let debugTextNode = model.add(); + let wasInside = false; + let lastBuzzTime = 0; + + // USING THE GLOBAL inputEvents OBJECT + + //inputEvents.onPress = hand => color = [0,0,1]; + //inputEvents.onRelease = hand => color = [1,0,0]; + + inputEvents.onMove = hand => { + if (isXR()) { + //Should I put code in animate or onMove? + let handPos = inputEvents.pos('right'); + console.log("Cur handPos" + handPos); + + //let boxMatrix = obj1.getGlobalMatrix(); + // //Transformation matrix + // let boxX = boxMatrix[12]; + // let boxY = boxMatrix[13]; + // let boxZ = boxMatrix[14]; + //let dist = getDistance(rightHandPos[0], rightHandPos[1], rightHandPos[2], boxX, boxY, boxZ); + //console.log(dist);*/ + + + } + } + + let color = [.5,.5,.5]; + model.move(0,1.5,0).scale(.1).animate(() => { + + obj1.identity().color(color); + if (true)//(isXR()) + { + //Should I put code in animate or onMove? + //let handPos = inputEvents.pos('right'); + let handPos = inputEvents.pos('right') ?? [0, 0, 0]; + console.log("Cur handPos" + handPos); + let boxMatrix = obj1.getGlobalMatrix(); + //Transformation matrix + let boxX = boxMatrix[12]; + let boxY = boxMatrix[13]; + let boxZ = boxMatrix[14]; + let dist = getDistance(handPos[0], handPos[1], handPos[2], boxX, boxY, boxZ); + + while (debugTextNode.nChildren() > 0) { + debugTextNode.remove(0); + } + //debugTextNode.add(clay.text("HELLO WORLD")).move(1, 1.8, -1).scale(30); + debugTextNode.add(clay.text("hand pos "+ handPos)).move(1, 1.8, -1).scale(30); + debugTextNode.add(clay.text("Distance "+ dist)).move(1, 1.0, -1).scale(30); + + if(dist < 0.4) + { + obj1.color(1,0,0); + if(!wasInside) + { + /*let command = + "\x10" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 20);\n" + + "g.drawString('Touching box', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n" + + "clearInterval(global.counterInterval);\n" + + "global.counterInterval = setInterval(makeWatchBuzz, 100);\n"; + */ + let command = "\x10" + "startVib();\n"; + Puck.write(command); + wasInside = true; + } + } + else + { + obj1.color(.5,.5,.5); + if(wasInside) + { + /*let exitCommand = + "\x10" + + "g.clear(1);\n" + + "clearInterval(global.counterInterval);\n" + + "Bangle.setLCDPower(0);\n" + + "g.drawString('Not touching box', g.getWidth()/2, g.getHeight()/2);\n" + */ + let exitCommand = "\x10" + "endVib();\n"; + Puck.write(exitCommand); + wasInside = false; + + } + } + + /* + if(dist < 0.4) + { + obj1.color(1,1,0); + if(!wasInside) + { + let screenCommand = + "\x10" + + "g.clear(1);\n" + + "g.setFontAlign(0,0);\n" + + "g.setFont('Vector', 40);\n" + + "g.drawString('Inside!', g.getWidth()/2, g.getHeight()/2);\n" + + "Bangle.setLCDPower(1);\n"; + Puck.write(screenCommand); + wasInside = true; + } + + let curTime = Date.now(); + if(curTime - lastBuzzTime > 200) + { + let watchCommand = + "\x10" + + "Bangle.buzz(160,0.2);\n"; + Puck.write(watchCommand); + lastBuzzTime = curTime; + } + + + } + */ + } + }); +} diff --git a/js/third-party/puck.js b/js/third-party/puck.js new file mode 100644 index 0000000..307ef7f --- /dev/null +++ b/js/third-party/puck.js @@ -0,0 +1,480 @@ +/* +-------------------------------------------------------------------- +Puck.js BLE Interface library for Nordic UART + Copyright 2021 Gordon Williams (gw@pur3.co.uk) + https://github.com/espruino/EspruinoWebTools +-------------------------------------------------------------------- +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +-------------------------------------------------------------------- +This creates a 'Puck' object that can be used from the Web Browser. + +Simple usage: + + Puck.write("LED1.set()\n") + +Execute expression and return the result: + + Puck.eval("BTN.read()", function(d) { + alert(d); + }); + +Or write and wait for a result - this will return all characters, +including echo and linefeed from the REPL so you may want to send +`echo(0)` and use `console.log` when doing this. + + Puck.write("1+2\n", function(d) { + alert(d); + }); + +Both `eval` and `write` will return a promise if no callback +function is given as an argument. + + alert( await Puck.eval("BTN.read()") ) + + alert( await Puck.write("1+2\n") ) + + +Or more advanced usage with control of the connection + - allows multiple connections + + Puck.connect(function(connection) { + if (!connection) throw "Error!"; + connection.on('data', function(d) { ... }); + connection.on('close', function() { ... }); + connection.write("1+2\n", function() { + connection.close(); + }); + }); + +ChangeLog: + +... +1v00 : Added Promises to write/eval +1v01 : Raise default Chunk Size to 20 + Auto-adjust chunk size up if we receive >20 bytes in a packet + +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.Puck = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + + if (typeof navigator == "undefined") return; + + var isBusy; + var queue = []; + + function checkIfSupported() { + // Hack for windows + if (navigator.platform.indexOf("Win")>=0 && + (navigator.userAgent.indexOf("Chrome/54")>=0 || + navigator.userAgent.indexOf("Chrome/55")>=0 || + navigator.userAgent.indexOf("Chrome/56")>=0) + ) { + console.warn("Chrome <56 in Windows has navigator.bluetooth but it's not implemented properly"); + if (confirm("Web Bluetooth on Windows is not yet available.\nPlease click Ok to see other options for using Web Bluetooth")) + window.location = "https://www.espruino.com/Puck.js+Quick+Start"; + return false; + } + if (navigator.bluetooth) return true; + console.warn("No Web Bluetooth on this platform"); + var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; + if (iOS) { + if (confirm("To use Web Bluetooth on iOS you'll need the WebBLE App.\nPlease click Ok to go to the App Store and download it.")) + window.location = "https://itunes.apple.com/us/app/webble/id1193531073"; + } else { + if (confirm("This Web Browser doesn't support Web Bluetooth.\nPlease click Ok to see instructions for enabling it.")) + window.location = "https://www.espruino.com/Quick+Start+BLE#with-web-bluetooth"; + } + return false; + } + + var NORDIC_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; + var NORDIC_TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; + var NORDIC_RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; + var DEFAULT_CHUNKSIZE = 20; + + function log(level, s) { + if (puck.log) puck.log(level, s); + } + + function ab2str(buf) { + return String.fromCharCode.apply(null, new Uint8Array(buf)); + } + + function str2ab(str) { + var buf = new ArrayBuffer(str.length); + var bufView = new Uint8Array(buf); + for (var i=0, strLen=str.length; i Device UUIDs: ' + device.uuids.join('\n' + ' '.repeat(21))); + device.addEventListener('gattserverdisconnected', function() { + log(1, "Disconnected (gattserverdisconnected)"); + connection.close(); + }); + connection.device = device; + connection.reconnect(callback); + }).catch(function(error) { + log(1, 'ERROR: ' + error); + connection.close(); + }); + + connection.reconnect = function(callback) { + connection.device.gatt.connect().then(function(server) { + log(1, "Connected"); + btServer = server; + return server.getPrimaryService(NORDIC_SERVICE); + }).then(function(service) { + log(2, "Got service"); + btService = service; + return btService.getCharacteristic(NORDIC_RX); + }).then(function (characteristic) { + rxCharacteristic = characteristic; + log(2, "RX characteristic:"+JSON.stringify(rxCharacteristic)); + rxCharacteristic.addEventListener('characteristicvaluechanged', function(event) { + var dataview = event.target.value; + var data = ab2str(dataview.buffer); + if (puck.increaseMTU && (data.length > chunkSize)) { + log(2, "Received packet of length "+data.length+", increasing chunk size"); + chunkSize = data.length; + } + if (puck.flowControl) { + for (var i=0;i pause upload"); + flowControlXOFF = true; + } else if (ch==17) {// XON + log(2,"XON received => resume upload"); + flowControlXOFF = false; + } else + remove = false; + if (remove) { // remove character + data = data.substr(0,i-1)+data.substr(i+1); + i--; + } + } + } + log(3, "Received "+JSON.stringify(data)); + connection.emit('data', data); + }); + return rxCharacteristic.startNotifications(); + }).then(function() { + return btService.getCharacteristic(NORDIC_TX); + }).then(function (characteristic) { + txCharacteristic = characteristic; + log(2, "TX characteristic:"+JSON.stringify(txCharacteristic)); + }).then(function() { + connection.txInProgress = false; + connection.isOpen = true; + connection.isOpening = false; + isBusy = false; + queue = []; + callback(connection); + connection.emit('open'); + // if we had any writes queued, do them now + connection.write(); + }).catch(function(error) { + log(1, 'ERROR: ' + error); + connection.close(); + }); + }; + + return connection; + }; + + // ---------------------------------------------------------- + var connection; + /* convenience function... Write data, call the callback with data: + callbackNewline = false => if no new data received for ~0.2 sec + callbackNewline = true => after a newline */ + function write(data, callback, callbackNewline) { + if (!checkIfSupported()) return; + + let result; + /// If there wasn't a callback function, then promisify + if (typeof callback !== 'function') { + callbackNewline = callback; + + result = new Promise((resolve, reject) => callback = (value, err) => { + if (err) reject(err); + else resolve(value); + }); + } + + if (isBusy) { + log(3, "Busy - adding Puck.write to queue"); + queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline}); + return result; + } + + var cbTimeout; + function onWritten() { + if (callbackNewline) { + connection.cb = function(d) { + var newLineIdx = connection.received.indexOf("\n"); + if (newLineIdx>=0) { + var l = connection.received.substr(0,newLineIdx); + connection.received = connection.received.substr(newLineIdx+1); + connection.cb = undefined; + if (cbTimeout) clearTimeout(cbTimeout); + cbTimeout = undefined; + if (callback) + callback(l); + isBusy = false; + handleQueue(); + } + }; + } + // wait for any received data if we have a callback... + var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data + var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/; + var maxDataTime = dataWaitTime; // max time we wait after having received data + cbTimeout = setTimeout(function timeout() { + cbTimeout = undefined; + if (maxTime) maxTime--; + if (maxDataTime) maxDataTime--; + if (connection.hadData) maxDataTime=dataWaitTime; + if (maxDataTime && maxTime) { + cbTimeout = setTimeout(timeout, 100); + } else { + connection.cb = undefined; + if (callback) + callback(connection.received); + isBusy = false; + handleQueue(); + connection.received = ""; + } + connection.hadData = false; + }, 100); + } + + if (connection && (connection.isOpen || connection.isOpening)) { + if (!connection.txInProgress) connection.received = ""; + isBusy = true; + connection.write(data, onWritten); + return result + } + + connection = connect(function(puck) { + if (!puck) { + connection = undefined; + if (callback) callback(null); + return; + } + connection.received = ""; + connection.on('data', function(d) { + connection.received += d; + connection.hadData = true; + if (connection.cb) connection.cb(d); + }); + connection.on('close', function(d) { + connection = undefined; + }); + isBusy = true; + connection.write(data, onWritten); + }); + + return result + } + + // ---------------------------------------------------------- + + var puck = { + /// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all. + debug : 1, + /** When we receive more than 20 bytes, should we increase the chunk size we use + for writing to match it? Normally this is fine but it seems some phones have + a broken bluetooth implementation that doesn't allow it. */ + increaseMTU : true, + /// Should we use flow control? Default is true + flowControl : true, + /// Used internally to write log information - you can replace this with your own function + log : function(level, s) { if (level <= this.debug) console.log(" "+s)}, + /// Called with the current send progress or undefined when done - you can replace this with your own function + writeProgress : function (charsSent, charsTotal) { + //console.log(charsSent + "/" + charsTotal); + }, + /** Connect to a new device - this creates a separate + connection to the one `write` and `eval` use. */ + connect : connect, + /// Write to Puck.js and call back when the data is written. Creates a connection if it doesn't exist + write : write, + /// Evaluate an expression and call cb with the result. Creates a connection if it doesn't exist + eval : function(expr, cb) { + + const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true) + .then(function (d) { + try { + return JSON.parse(d); + } catch (e) { + log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString()); + return Promise.reject(d); + } + }); + + + if (cb) { + return void response.then(cb, (err) => cb(null, err)); + } else { + return response; + } + + }, + /// Write the current time to the Puck + setTime : function(cb) { + var d = new Date(); + var cmd = 'setTime('+(d.getTime()/1000)+');'; + // in 1v93 we have timezones too + cmd += 'if (E.setTimeZone) E.setTimeZone('+d.getTimezoneOffset()/-60+');\n'; + write(cmd, cb); + }, + /// Did `write` and `eval` manage to create a connection? + isConnected : function() { + return connection!==undefined; + }, + /// get the connection used by `write` and `eval` + getConnection : function() { + return connection; + }, + /// Close the connection used by `write` and `eval` + close : function() { + if (connection) + connection.close(); + }, + /** Utility function to fade out everything on the webpage and display + a window saying 'Click to continue'. When clicked it'll disappear and + 'callback' will be called. This is useful because you can't initialise + Web Bluetooth unless you're doing so in response to a user input.*/ + modal : function(callback) { + var e = document.createElement('div'); + e.style = 'position:absolute;top:0px;left:0px;right:0px;bottom:0px;opacity:0.5;z-index:100;background:black;'; + e.innerHTML = '
Click to Continue...
'; + e.onclick = function(evt) { + callback(); + evt.preventDefault(); + document.body.removeChild(e); + }; + document.body.appendChild(e); + } + }; + return puck; +})); + +export const Puck = self.Puck; diff --git a/python/__pycache__/aruco_detector.cpython-313.pyc b/python/__pycache__/aruco_detector.cpython-313.pyc new file mode 100644 index 0000000..1183168 Binary files /dev/null and b/python/__pycache__/aruco_detector.cpython-313.pyc differ diff --git a/server/incoming_form.js b/server/incoming_form.js new file mode 100644 index 0000000..e69de29