diff --git a/apps.json b/apps.json index 976c3c8ad..2e977475e 100644 --- a/apps.json +++ b/apps.json @@ -162,6 +162,28 @@ {"name":"*swatch","url":"stopwatch-icon.js","evaluate":true} ] }, + { "id": "hidmsic", + "name": "Bluetooth Music Controls", + "icon": "hid-music.png", + "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", + "tags": "bluetooth", + "storage": [ + {"name":"+hidmsic","url":"hid-music.json"}, + {"name":"-hidmsic","url":"hid-music.js"}, + {"name":"*hidmsic","url":"hid-music-icon.js","evaluate":true} + ] + }, + { "id": "hidkbd", + "name": "Bluetooth Keyboard", + "icon": "hid-keyboard.png", + "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", + "tags": "bluetooth", + "storage": [ + {"name":"+hidkbd","url":"hid-keyboard.json"}, + {"name":"-hidkbd","url":"hid-keyboard.js"}, + {"name":"*hidkbd","url":"hid-keyboard-icon.js","evaluate":true} + ] + }, { "id": "animals", "name": "Animals Game", "icon": "animals.png", diff --git a/apps/hid-keyboard-icon.js b/apps/hid-keyboard-icon.js new file mode 100644 index 000000000..ca63665de --- /dev/null +++ b/apps/hid-keyboard-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/AHeIDC+AC60IC/51ELoIXTCYMIDISLpC/67dxAAJHBYsLhAYKLhgXXHhWAdoQbBApAvhBgYcCAogXkI64XYhDtII/oMUERTyDABJ0FPI4A/AH4A/AGY=")) diff --git a/apps/hid-keyboard.js b/apps/hid-keyboard.js new file mode 100644 index 000000000..9ef95eb8a --- /dev/null +++ b/apps/hid-keyboard.js @@ -0,0 +1,86 @@ +var storage = require('Storage'); + +const settings = storage.readJSON('@settings') || { HID: false }; + +var sendHid, next, prev, toggle, up, down, profile; + +if (settings.HID) { + profile = 'Keyboard'; + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; + next = function (cb) { sendHid(0x4f, cb); }; + prev = function (cb) { sendHid(0x50, cb); }; + toggle = function (cb) { sendHid(0x2c, cb); }; + up = function (cb) {sendHid(0x52, cb); }; + down = function (cb) { sendHid(0x51, cb); }; +} else { + E.showMessage('HID disabled'); + setTimeout(load, 1000); +} + +function drawApp() { + g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + g.drawString(profile, 120, 120); + const d = g.getWidth() - 18; + + function c(a) { + return { + width: 8, + height: a.length, + bpp: 1, + buffer: (new Uint8Array(a)).buffer + }; + } + + g.drawImage(c([16,56,124,254,16,16,16,16]),d,40); + g.drawImage(c([16,16,16,16,254,124,56,16]),d,194); + g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); +} + +if (next) { + setWatch(function(e) { + var len = e.time - e.lastTime; + if (len > 0.3 && len < 0.9) { + E.showMessage('prev'); + setTimeout(drawApp, 1000); + prev(() => {}); + } else { + E.showMessage('up'); + setTimeout(drawApp, 1000); + up(() => {}); + } + }, BTN1, { edge:"falling",repeat:true,debounce:50}); + + setWatch(function(e) { + var len = e.time - e.lastTime; + if (len > 0.3 && len < 0.9) { + E.showMessage('next'); + setTimeout(drawApp, 1000); + next(() => {}); + } else { + E.showMessage('down'); + setTimeout(drawApp, 1000); + down(() => {}); + } + }, BTN3, { edge:"falling",repeat:true,debounce:50}); + + setWatch(function(e) { + E.showMessage('toggle') + setTimeout(drawApp, 1000); + toggle(); + }, BTN2, { edge:"falling",repeat:true,debounce:50}); + + drawApp(); +} + diff --git a/apps/hid-keyboard.json b/apps/hid-keyboard.json new file mode 100644 index 000000000..77e848a95 --- /dev/null +++ b/apps/hid-keyboard.json @@ -0,0 +1,5 @@ +{ + "name": "Keyboard Control", + "icon": "*hidkbd", + "src": "-hidkbd" +} diff --git a/apps/hid-keyboard.png b/apps/hid-keyboard.png new file mode 100644 index 000000000..19aec3c05 Binary files /dev/null and b/apps/hid-keyboard.png differ diff --git a/apps/hid-music-icon.js b/apps/hid-music-icon.js new file mode 100644 index 000000000..6a0c64b9c --- /dev/null +++ b/apps/hid-music-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au")) diff --git a/apps/hid-music.js b/apps/hid-music.js new file mode 100644 index 000000000..f106b67ef --- /dev/null +++ b/apps/hid-music.js @@ -0,0 +1,85 @@ +var storage = require('Storage'); + +const settings = storage.readJSON('@settings') || { HID: false }; + +var sendHid, next, prev, toggle, up, down, profile; + +if (settings.HID) { + profile = 'Music'; + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([1,code], () => { + NRF.sendHIDReport([1,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; + next = function (cb) { sendHid(0x01, cb); }; + prev = function (cb) { sendHid(0x02, cb); }; + toggle = function (cb) { sendHid(0x10, cb); }; + up = function (cb) {sendHid(0x40, cb); }; + down = function (cb) { sendHid(0x80, cb); }; +} else { + E.showMessage('HID disabled'); + setTimeout(load, 1000); +} + +function drawApp() { + g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + g.drawString(profile, 120, 120); + const d = g.getWidth() - 18; + + function c(a) { + return { + width: 8, + height: a.length, + bpp: 1, + buffer: (new Uint8Array(a)).buffer + }; + } + + g.drawImage(c([16,56,124,254,16,16,16,16]),d,40); + g.drawImage(c([16,16,16,16,254,124,56,16]),d,194); + g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); +} + +if (next) { + setWatch(function(e) { + var len = e.time - e.lastTime; + if (len > 0.3 && len < 0.9) { + E.showMessage('prev'); + setTimeout(drawApp, 1000); + prev(() => {}); + } else { + E.showMessage('up'); + setTimeout(drawApp, 1000); + up(() => {}); + } + }, BTN1, { edge:"falling",repeat:true,debounce:50}); + + setWatch(function(e) { + var len = e.time - e.lastTime; + if (len > 0.3 && len < 0.9) { + E.showMessage('next'); + setTimeout(drawApp, 1000); + next(() => {}); + } else { + E.showMessage('down'); + setTimeout(drawApp, 1000); + down(() => {}); + } + }, BTN3, { edge:"falling",repeat:true,debounce:50}); + + setWatch(function(e) { + E.showMessage('toggle') + setTimeout(drawApp, 1000); + toggle(); + }, BTN2, { edge:"falling",repeat:true,debounce:50}); + + drawApp(); +} diff --git a/apps/hid-music.json b/apps/hid-music.json new file mode 100644 index 000000000..23a1e39d2 --- /dev/null +++ b/apps/hid-music.json @@ -0,0 +1,5 @@ +{ + "name": "Music Control", + "icon": "*hidmsic", + "src": "-hidmsic" +} diff --git a/apps/hid-music.png b/apps/hid-music.png new file mode 100644 index 000000000..923b5aa0e Binary files /dev/null and b/apps/hid-music.png differ diff --git a/apps/settings-init.js b/apps/settings-init.js index c8e2e0c37..bf211a7a9 100644 --- a/apps/settings-init.js +++ b/apps/settings-init.js @@ -1,10 +1,14 @@ (function() { var s = require('Storage').readJSON('@settings'); + var adv = { uart: true }; if (s.HID) { + // Report from https://notes.iopush.net/custom-usb-hid-device-descriptor-media-keyboard/ Bangle.HID = new Uint8Array([ + // Keyboard Controls 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, + 0x85, 0x02, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, @@ -39,17 +43,42 @@ 0x75, 0x08, 0x95, 0x02, 0xB1, 0x02, + 0xC0, + + // Music Controls + 0x05, 0x0C, + 0x09, 0x01, + 0xA1, 0x01, + 0x85, 0x01, + 0x15, 0x00, + 0x25, 0x01, + 0x75, 0x01, + 0x95, 0x01, + 0x09, 0xB5, + 0x81, 0x02, + 0x09, 0xB6, + 0x81, 0x02, + 0x09, 0xB7, + 0x81, 0x02, + 0x09, 0xB8, + 0x81, 0x02, + 0x09, 0xCD, + 0x81, 0x02, + 0x09, 0xE2, + 0x81, 0x02, + 0x09, 0xE9, + 0x81, 0x02, + 0x09, 0xEA, + 0x81, 0x02, 0xC0 ]); - NRF.setServices(undefined, { - uart: true, hid: Bangle.HID, - }); + adv.hid = Bangle.HID; } + NRF.setServices(undefined, adv); if (!s.vibrate) Bangle.buzz=()=>Promise.resolve(); if (!s.beep) Bangle.beep=()=>Promise.resolve(); Bangle.setLCDTimeout(s.timeout); if (!s.timeout) Bangle.setLCDPower(1); E.setTimeZone(s.timezone); -})(); - +})()