diff --git a/app/config.xml b/app/config.xml index cb85e75..18ccb9e 100644 --- a/app/config.xml +++ b/app/config.xml @@ -22,8 +22,10 @@ + + + - diff --git a/app/package-lock.json b/app/package-lock.json index fb5edb4..59fe73e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -4,15 +4,10 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "android-versions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/android-versions/-/android-versions-1.2.1.tgz", - "integrity": "sha512-k6zlrtWbJ3tx1ZsyyJ0Bo3r6cqPA3JUnFGv7pnIaLr1XVxSi2Tcem2lg3kBebFp27v/A40tZqdlouPyakpyKrw==" - }, "cordova-android": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.3.0.tgz", - "integrity": "sha1-2lQYQz0lx1pZd7QoJEu+Q30BKNI=", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.4.0.tgz", + "integrity": "sha1-VK6NpXKKjX5e/MYXLT3MoXvH/n0=", "requires": { "android-versions": "1.2.1", "cordova-common": "2.1.0", @@ -27,6 +22,10 @@ "version": "1.1.0", "bundled": true }, + "android-versions": { + "version": "1.2.1", + "bundled": true + }, "ansi": { "version": "0.3.1", "bundled": true diff --git a/app/package.json b/app/package.json index b98c52b..b896cc6 100644 --- a/app/package.json +++ b/app/package.json @@ -10,7 +10,7 @@ "author": "M McWilliams", "license": "MIT", "dependencies": { - "cordova-android": "^6.3.0", + "cordova-android": "^6.4.0", "cordova-ios": "^4.5.4", "cordova-plugin-ble-central": "^1.1.4", "cordova-plugin-compat": "^1.2.0", @@ -30,8 +30,8 @@ } }, "platforms": [ - "ios", - "android" + "android", + "ios" ] } } \ No newline at end of file diff --git a/app/www/index.html b/app/www/index.html index 7a63684..7298e13 100644 --- a/app/www/index.html +++ b/app/www/index.html @@ -4,12 +4,15 @@ INTVAL - + +
+
+

INTVAL

@@ -27,7 +30,7 @@
Exposure 1/5
- + +
-

BLUETOOTH

- -

WIFI

-
- +
+

BLUETOOTH

+
-
- +
+

WIFI

+
+ +
+
+ +
@@ -92,6 +99,7 @@
+ diff --git a/app/www/static/css/index.css b/app/www/static/css/index.css index 730c2c5..d64fc43 100644 --- a/app/www/static/css/index.css +++ b/app/www/static/css/index.css @@ -46,6 +46,12 @@ body{ .page.selected{ display: block; } +.ble{ + display: none; +} +.ble.active{ + display: block; +} h2{ font-size: 18px; text-align: center; @@ -144,7 +150,8 @@ button{ padding: 5px 0; text-align: center; } -button:focus{ +button:focus, +button.focus{ background-color: #20ce45; border-color: #20ce45; color: #212121; @@ -162,7 +169,7 @@ button:focus{ right: 10%; } .label{ - text-align: center; + /*text-align: center;*/ color: #666; margin-top: 6px; margin-bottom: 9px; @@ -244,3 +251,26 @@ footer > div.selected{ #compile{ margin-top: 20px; } + +#seq{ + margin-top: 40px; +} + +#overlay{ + position: fixed; + z-index: 2001; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: none; +} +#overlay.active{ + display: block; +} +#spinner{ + margin-top: 200px; +} diff --git a/app/www/static/js/index.js b/app/www/static/js/index.js index d7c73a0..1c8de02 100644 --- a/app/www/static/js/index.js +++ b/app/www/static/js/index.js @@ -35,7 +35,6 @@ var app = { // 'pause', 'resume', etc. onDeviceReady: function() { mobile.init(); - getState(); }, onDeviceResume : function () { getState(); diff --git a/app/www/static/js/intval.core.js b/app/www/static/js/intval.core.js index c6335bc..81dee9c 100644 --- a/app/www/static/js/intval.core.js +++ b/app/www/static/js/intval.core.js @@ -12,6 +12,7 @@ const STATE = { delayScale : 'ms', counter : 0 }; + //functions window.frame = null; window.getState = null; @@ -93,7 +94,7 @@ var setExposureScale = function () { }; var setDelayScale = function () { - const scale = document.getElementById('scale').value; + const scale = document.getElementById('delayScale').value; const elem = document.getElementById('delay'); if (scale === 'ms') { elem.value = STATE.delay; @@ -138,6 +139,36 @@ var unsetPages = function () { } }; + +var setState = function (res) { + let exposure; + let exposureScale; + let delayScale; + + if (res.frame.dir !== true) { + document.getElementById('dir').checked = true; + STATE.dir = res.frame.dir; + setDirLabel(false); + } + document.getElementById('counter').value = res.counter; + STATE.counter = res.counter; + //Exposure + if (res.frame.exposure === 0) { + res.frame.exposure = BOLEX.expected; + } + STATE.exposure = res.frame.exposure; + exposure = shutter(STATE.exposure); + exposureScale = scaleAuto(STATE.exposure); + + document.getElementById('str').value = exposure.str; + document.getElementById('scale').value = exposureScale; + setExposureScale(); + + STATE.delay = res.frame.delay; + delayScale = scaleAuto(STATE.delay); + document.getElementById('delayScale').value = delayScale; + setDelayScale(); +}; var appPage = function () { unsetPages(); document.getElementById('app').classList.add('selected'); @@ -154,6 +185,44 @@ var mscriptPage = function () { document.getElementById('mscriptIcon').classList.add('selected'); editor.cm.refresh(); }; +var spinnerInit = function () { + const spinnerOpts = { + lines: 13 // The number of lines to draw + , length: 33 // The length of each line + , width: 11 // The line thickness + , radius: 30 // The radius of the inner circle + , scale: 0.5 // Scales overall size of the spinner + , corners: 1 // Corner roundness (0..1) + , color: '#fff' // #rgb or #rrggbb or array of colors + , opacity: 0.25 // Opacity of the lines + , rotate: 0 // The rotation offset + , direction: 1 // 1: clockwise, -1: counterclockwise + , speed: 1 // Rounds per second + , trail: 60 // Afterglow percentage + , fps: 20 // Frames per second when using setTimeout() as a fallback for CSS + , zIndex: 2e9 // The z-index (defaults to 2000000000) + , className: 'spinner' // The CSS class to assign to the spinner + , top: '50%' // Top position relative to parent + , left: '50%' // Left position relative to parent + , shadow: true // Whether to render a shadow + , hwaccel: true // Whether to use hardware acceleration + , position: 'relative' // Element positioning + }; + const target = document.getElementById('spinner'); + const spinner = new Spinner(spinnerOpts).spin(target); +}; +var spinnerShow = function () { + const elem = document.getElementById('overlay'); + if (!elem.classList.contains('active')) { + elem.classList.add('active'); + } +}; +var spinnerHide = function () { + const elem = document.getElementById('overlay'); + if (elem.classList.contains('active')) { + elem.classList.remove('active'); + } +} var isNumeric = function (n) { return !isNaN(parseFloat(n)) && isFinite(n); }; \ No newline at end of file diff --git a/app/www/static/js/intval.mobile.js b/app/www/static/js/intval.mobile.js index a067145..46669ce 100644 --- a/app/www/static/js/intval.mobile.js +++ b/app/www/static/js/intval.mobile.js @@ -7,27 +7,72 @@ mobile.ble = { SERVICE_ID : '149582bd-d49d-4b5c-acd1-1ae503d09e7a', CHAR_ID : '47bf69fb-f62f-4ef8-9be8-eb727a54fae4', //general data WIFI_ID : '3fe7d9cf-7bd2-4ff0-97c5-ebe87288c2cc', //wifi only - DEVICES : [] + devices : [], + device : {}, + connected : false, + active : false }; mobile.ble.scan = function () { - ble.scan([], 5, mobile.ble.onDiscover, BLE.onError); + spinnerShow(); + ble.scan([], 5, mobile.ble.onDiscover, mobile.ble.onError); + mobile.ble.devices = []; + setTimeout(() => { + if (!mobile.ble.connected) { + ble.stopScan(() => {}, mobile.ble.onError); + spinnerHide(); + alert('No INTVAL devices found.'); + } + }, 5000) }; mobile.ble.onDiscover = function (device) { - console.dir(device); - mobile.ble.connect(device.id); + if (device && device.name && device.name === 'intval3') { + console.log('BLE - Discovered INTVAL'); + console.dir(device); + mobile.ble.devices.push(device); + if (!mobile.ble.connected) { + mobile.ble.connect(device); + } + } else { + //console.log(`BLE - Discovered Other ${device.id}`); + } } -mobile.ble.connect = function (deviceId) { - ble.connect(deviceId, function (peripheral) { - mobile.ble.onConnect(peripheral, deviceId); +mobile.ble.connect = function (device) { + console.log(`BLE - Connecting to ${device.id}`) + ble.connect(device.id, (peripheral) => { + mobile.ble.onConnect(peripheral, device); }, mobile.ble.onError); }; -mobile.ble.onConnect = function (peripheral, deviceId) { +mobile.ble.onConnect = function (peripheral, device) { + spinnerHide(); + ble.stopScan(() => {}, moble.ble.onError); + console.log(`BLE - Connected to ${device.id}`); console.log(peripheral); - console.log(deviceId); + console.dir(device); + mobile.ble.device = device; + mobile.ble.connected = true; + + getState(); +}; + +mobile.ble.disconnect = function () { + let device + if (!mobile.ble.connected) { + console.warn('Not connected to any device') + return false + } + device = mobile.ble.device + console.log(`BLE - Disconnecting from ${device.id}`) + ble.disconnect(device.id, mobile.ble.onDisconnect, mobile.ble.onDisconnect); +}; + +mobile.ble.onDisconnect = function (res) { + console.log(`BLE - Disconnected from ${res}`); + mobile.ble.connected = false; + mobile.ble.device = {}; }; mobile.ble.onError = function (err) { @@ -35,15 +80,154 @@ mobile.ble.onError = function (err) { }; mobile.init = function () { - frame = mobile.frame; - getState = mobile.getState; - setDir = mobile.setDir; - setExposure = mobile.setExposure; - setCounter = mobile.setCounter; + const bleInputs = document.querySelectorAll('.ble') + + window.frame = mobile.frame; + window.getState = mobile.getState; + window.setDir = mobile.setDir; + window.setExposure = mobile.setExposure; + window.setDelay = mobile.setDelay; + window.setCounter = mobile.setCounter; + + //show ble-specific fields in settings + for (let i of bleInputs) { + i.classList.add('active'); + } + spinnerInit(); + mobile.ble.scan(); }; -mobile.frame = function () {}; -mobile.getState = function () {}; -mobile.setDir = function () {}; -mobile.setExposure = function () {}; -mobile.setCounter = function () {}; \ No newline at end of file +mobile.getState = function () { + if (!mobile.ble.connected) { + // + } + ble.read(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + mobile.stateSuccess, + mobile.ble.onError); +}; +mobile.stateSuccess = function (data) { + let str = bytesToString(data); + let res = JSON.parse(str); + setState(res) +}; + +mobile.frame = function () { + const opts = { + type : 'frame' + }; + if (!mobile.ble.connected) { + return alert('Not connected to an INTVAL device.'); + } + if (mobile.ble.active) { + return false; + } + ble.write(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + stringToBytes(JSON.stringify(opts)), //check length? + mobile.frameSuccess, + mobile.ble.onError); + document.getElementById('frame').classList.add('focus'); + mobile.ble.active = true; +}; + + +mobile.frameSuccess = function () { + console.log('Frame finished, getting state.'); + mobile.ble.active = false; + document.getElementById('frame').classList.remove('focus'); + mobile.getState(); +} +mobile.setDir = function () { + const opts = { + type : 'dir', + dir : !document.getElementById('dir').checked + }; + + ble.write(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + stringToBytes(JSON.stringify(opts)), //check length? + mobile.dirSuccess, + mobile.ble.onError); +}; +mobile.dirSuccess = function () { + console.log('Set direction'); + mobile.getState(); +}; +mobile.setExposure = function () { + let exposure = document.getElementById('exposure').value; + let scaledExposure; + let opts = { + type : 'exposure' + }; + if (exposure === '' || exposure === null) { + exposure = 0; + } + scaledExposure = scaleTime(exposure, STATE.scale); + opts.exposure = scaledExposure; + ble.write(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + stringToBytes(JSON.stringify(opts)), //check length? + mobile.exposureSuccess, + mobile.ble.onError); +}; +mobile.exposureSuccess = function () { + console.log('Set exposure'); + mobile.getState(); +}; + +mobile.setDelay = function () { + const delay = document.getElementById('delay').value; + const scaledDelay = scaleTime(delay, STATE.delayScale) + let opts = { + type : 'delay', + delay : scaledDelay + }; + ble.write(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + stringToBytes(JSON.stringify(opts)), //check length? + mobile.delaySuccess, + mobile.ble.onError); +} + +mobile.delaySuccess = function () { + console.log('Set delay') + mobile.getState(); +}; + +mobile.setCounter = function () { + const counter = document.getElementById('counter').value; + const change = prompt(`Change counter value?`, counter); + if (change === null || !isNumeric(change)) return false; + let opts = { + type : 'counter', + counter : change + }; + ble.write(mobile.ble.device.id, + mobile.ble.SERVICE_ID, + mobile.ble.CHAR_ID, + stringToBytes(JSON.stringify(opts)), //check length? + mobile.counterSuccess, + mobile.ble.onError); +}; + +mobile.counterSuccess = function () { + console.log('Set counter'); + mobile.getState(); +}; + +function bytesToString (buffer) { + return String.fromCharCode.apply(null, new Uint8Array(buffer)); +}; +function stringToBytes(string) { + var array = new Uint8Array(string.length); + for (var i = 0, l = string.length; i < l; i++) { + array[i] = string.charCodeAt(i); + } + return array.buffer; +}; \ No newline at end of file diff --git a/app/www/static/js/intval.web.js b/app/www/static/js/intval.web.js index c1851d4..66b9850 100644 --- a/app/www/static/js/intval.web.js +++ b/app/www/static/js/intval.web.js @@ -55,36 +55,12 @@ web.getState = function () { .then(res => { return res.json(); }) - .then(web.getStateSuccess) + .then(setState) .catch(err => { console.error('Error getting state'); console.error(err); }); }; -web.getStateSuccess = function (res) { - let exposure; - let scale; - if (res.frame.dir !== true) { - document.getElementById('dir').checked = true; - STATE.dir = res.frame.dir; - setDirLabel(false); - } - document.getElementById('counter').value = res.counter; - STATE.counter = res.counter; - //Exposure - if (res.frame.exposure === 0) { - res.frame.exposure = BOLEX.expected; - } - STATE.exposure = res.frame.exposure; - exposure = shutter(STATE.exposure); - scale = scaleAuto(STATE.exposure); - document.getElementById('str').value = exposure.str; - document.getElementById('scale').value = scale; - setExposureScale(); - - document.getElementById('delay').value = res.frame.delay; - STATE.delay = res.frame.delay; -}; web.setExposure = function () { let exposure = document.getElementById('exposure').value; let scaledExposure; @@ -165,6 +141,7 @@ web.init = function () { window.frame = web.frame; window.getState = web.getState; window.setDir = web.setDir; + window.setDelay = web.setDelay; window.setExposure = web.setExposure; window.setCounter = web.setCounter; console.log('started web') diff --git a/app/www/static/js/spin.min.js b/app/www/static/js/spin.min.js new file mode 100644 index 0000000..bd3ae4f --- /dev/null +++ b/app/www/static/js/spin.min.js @@ -0,0 +1,2 @@ +// http://spin.js.org/#v2.3.2 +!function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k