From 489c96d9baaf2a85dd7f2185f9e6bf1a6aebb85a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 8 Feb 2023 09:30:07 +0000 Subject: [PATCH] Add LEGO remote control --- apps/legoremote/ChangeLog | 1 + apps/legoremote/README.md | 28 ++++++++++++++ apps/legoremote/app-icon.js | 1 + apps/legoremote/app.js | 70 ++++++++++++++++++++++++++++++++++ apps/legoremote/app.png | Bin 0 -> 14820 bytes apps/legoremote/metadata.json | 14 +++++++ 6 files changed, 114 insertions(+) create mode 100644 apps/legoremote/ChangeLog create mode 100644 apps/legoremote/README.md create mode 100644 apps/legoremote/app-icon.js create mode 100644 apps/legoremote/app.js create mode 100644 apps/legoremote/app.png create mode 100644 apps/legoremote/metadata.json diff --git a/apps/legoremote/ChangeLog b/apps/legoremote/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/legoremote/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/legoremote/README.md b/apps/legoremote/README.md new file mode 100644 index 000000000..a95b7b154 --- /dev/null +++ b/apps/legoremote/README.md @@ -0,0 +1,28 @@ +# LEGO Remote control + +This app allows you to control LEGO models from Bangle.js + +Right now the only supported control device is the Mould King M-0006 +Bluetooth remote for LEGO Power Functions: http://www.espruino.com/LEGO+Power+Functions+Clone + +LEGO Power Functions does not have an official Bluetooth remote controller. Hopefully +in the future this app will be able to support other types of remote (see below). + +## Usage + +Run the app, and ensure you're not connected to your watch via Bluetooth +(a warning will pop up if so). + +Now press the arrow keys on the screen to control the robot. + +It is expected that the robot is controlled by two motors, one on the left +side (connected to the `A` output) and one on the right (connected to the `B` output). + +## Future additions + +In the future it would be great to add: + +* Recording a series of movements and playing them back +* Support for official LEGO bluetooth remotes (via [Pybricks](https://pybricks.com/)) +* Support for different robot styles and configurations +* Using the Bangle's compass (or even GPS) to allow better robot control. diff --git a/apps/legoremote/app-icon.js b/apps/legoremote/app-icon.js new file mode 100644 index 000000000..850e0eda6 --- /dev/null +++ b/apps/legoremote/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4X/8H5A4NH/4ABJf4A/AFMC1Uq1QABhWqBYYJDAAegBYWqxWogWohQBCBYWpqsVqoABytVBYUC0u5rf5rf1q/xtQLBhWW/t//2/AYVKBYWX/u//z3B3//rRGCr/1/+H/v9/1/BYn2/lT+v6/oXDlF/4//9/78/5F4epv/X//f7/3+v9I4Wp38b/9v7//+4LD0P/HgN/7f/EgMoBYOlJ4IACDAP1O4QLH6ibCBYI7Br/+qf/iwLC1NIquhq2lquprWAWQVVoNVgtU0NVlQLCZQ7XDbgLWJEgOCdgLZBdwgA/AH4AaA")) diff --git a/apps/legoremote/app.js b/apps/legoremote/app.js new file mode 100644 index 000000000..1c76a54a8 --- /dev/null +++ b/apps/legoremote/app.js @@ -0,0 +1,70 @@ +var lego = require("mouldking"); +lego.start(); +E.on('kill', () => { + // return to normal Bluetooth advertising + NRF.setAdvertising({},{showName:true}); +}); +// You must leave one second after 'start' to allow the remote to be paired + +var arrowIcon = atob("IiiBAAAAwAAAAPwAAAB/gAAAP/AAAB/+AAAP/8AAB//4AAP//wAA///gAH///AA///8AH///4A////wH////gf////D////8f////5/////n////+f////4AP/8AAA//wAAD//AAAP/8AAA//wAAD//AAAH/8AAAf/wAAB//AAAH/8AAAf/gAAB/+AAAH/4AAAf/gAAB/+AAAH/4AAAP/gAAA/+AAAD/wAAAD8AA"); +var controlState = ""; + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +var R = Bangle.appRect; +// we'll divide up into 3x3 +function getBoxCoords(x,y) { + return { + x : R.x + R.w*x/3, + y : R.y + R.h*y/3 + }; +} + +function draw() { + g.reset().clearRect(R); + var c, ninety = Math.PI/2; + var colOn = "#f00", colOff = g.theme.fg; + c = getBoxCoords(1.5, 0.5); + g.setColor(controlState=="up"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:0}); + c = getBoxCoords(2.5, 1.5); + g.setColor(controlState=="right"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety}); + c = getBoxCoords(0.5, 1.5); + g.setColor(controlState=="left"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:-ninety}); + c = getBoxCoords(1.5, 1.5); + g.setColor(controlState=="down"?colOn:colOff).drawImage(arrowIcon,c.x,c.y,{rotate:ninety*2}); + if (NRF.getSecurityStatus().connected) { + c = getBoxCoords(1.5, 2.5); + g.setFontAlign(0,0).setFont("6x8").drawString("WARNING:\nBluetooth Connected\nYou must disconnect\nbefore LEGO will work",c.x,c.y); + } +} +draw(); +NRF.on('connect', draw); +NRF.on('disconnect', draw); + +function setControlState(s) { + controlState = s; + var c = {}; + var speed = 3; + if (s=="up") c={a:-speed,b:-speed}; + if (s=="down") c={a:speed,b:speed}; + if (s=="left") c={a:speed,b:-speed}; + if (s=="right") c={a:-speed,b:speed}; + draw(); + lego.set(c); +} + +Bangle.on('drag',e => { + var x = Math.floor(E.clip((e.x - R.x) * 3 / R.w,0,2.99)); + var y = Math.floor(E.clip((e.y - R.y) * 3 / R.h,0,2.99)); + if (!e.b) { + setControlState(""); + return; + } + if (y==0) { // top row + if (x==1) setControlState("up"); + } else if (y==1) { + if (x==0) setControlState("left"); + if (x==1) setControlState("down"); + if (x==2) setControlState("right"); + } +}); diff --git a/apps/legoremote/app.png b/apps/legoremote/app.png new file mode 100644 index 0000000000000000000000000000000000000000..727049e2ba713fe02ab9bcf97e8a9c52e0168b96 GIT binary patch literal 14820 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

3Dbt{R;TUJo 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