mecoffee.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /*
  2. Bluetooth
  3. */
  4. function ab2str(buf) {
  5. return String.fromCharCode.apply(null, new Uint8Array(buf));
  6. }
  7. function str2ab(str) {
  8. var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
  9. var bufView = new Uint16Array(buf);
  10. for (var i=0, strLen=str.length; i<strLen; i++) {
  11. bufView[i] = str.charCodeAt(i);
  12. }
  13. return buf;
  14. }
  15. var receive_buffer = "";
  16. var received_log = "";
  17. var disconnect_timer;
  18. var legacy = true;
  19. var slider_pistrt ;
  20. function onReceiveTimeout() {
  21. //mecoffee_disconnect();
  22. mecoffee_start();
  23. disconnect_timer = null;
  24. }
  25. var onReceive = function(receiveInfo) {
  26. receive_buffer += ab2str( receiveInfo.data );
  27. var pos = 0, p = 0, i =0 ;
  28. while( ( p = receive_buffer.indexOf("\n") ) > 0 ) {
  29. line = receive_buffer.substring( 0, p - 1 );
  30. // console.log( line );
  31. receive_buffer = receive_buffer.substring( p + 1, receive_buffer.length );
  32. // Keep all received lines in a variable
  33. // received_log.concat( line + "\r\n" );
  34. mecoffee_terminal( line );
  35. i++;
  36. if( i > 25 )
  37. break;
  38. }
  39. // Disconnect timer
  40. if( disconnect_timer )
  41. clearTimeout( disconnect_timer );
  42. disconnect_timer = setTimeout( onReceiveTimeout, 5000 );
  43. };
  44. var onConnectedCallback = function() {
  45. if (chrome.runtime.lastError)
  46. console.log("Connection failed: " + chrome.runtime.lastError.message);
  47. //else
  48. //chrome.bluetoothSocket.onReceive.addListener( onReceive );
  49. };
  50. chrome.bluetoothSocket.onReceive.addListener( onReceive );
  51. var mecoffee_socketId;
  52. function mecoffee_connect( device_name ) {
  53. var device;
  54. chrome.bluetooth.getDevices(function(devices) {
  55. for (var i = 0; i < devices.length; i++) {
  56. if( devices[i].name == device_name ) {
  57. console.log(devices[i].name);
  58. device = devices[i];
  59. var uuid = '1106';
  60. uuid= "00001101-0000-1000-8000-00805F9B34FB";
  61. chrome.bluetoothSocket.create( function(createInfo) {
  62. mecoffee_socketId = createInfo.socketId;
  63. chrome.bluetoothSocket.connect(createInfo.socketId,
  64. device.address, uuid, onConnectedCallback);
  65. chrome.bluetoothSocket.onAcceptError.addListener( function() { console.log('onaccepterror'); } );
  66. chrome.bluetoothSocket.onReceiveError.addListener( function() { console.log('onreceiveerror'); } );
  67. chrome.runtime.sendMessage( { type: 'socket', socket: mecoffee_socketId }, function(response) { console.log( 'socket antwoord' ); } );
  68. });
  69. break;
  70. }
  71. }
  72. });
  73. }
  74. function mecoffee_disconnect( ) {
  75. chrome.bluetoothSocket.disconnect( mecoffee_socketId );
  76. chrome.bluetoothSocket.close( mecoffee_socketId );
  77. mecoffee_timestamp_start = null;
  78. }
  79. function mecoffee_bt_enum_devices( ) {
  80. if( !chrome || !chrome.bluetooth )
  81. return ;
  82. chrome.bluetooth.getDevices(function(devices) {
  83. $('#device option[value="none"]').remove()
  84. for (var i = 0; i < devices.length; i++) {
  85. $('#devices').append('<option value="device_' + devices[i].name + '">' + devices[i].name + '</option>');
  86. }
  87. $('#device')[0].selectedIndex = 0;
  88. $('#device').selectpicker("refresh")
  89. });
  90. }
  91. /*
  92. meCoffee protocol
  93. */
  94. var mecoffee_timestamp_start;
  95. function mecoffee_terminal_tmp( items ) {
  96. var ts = parseInt( items[1] );
  97. var tmp = parseInt( items[3] ) / 100;
  98. var tmp2 = parseInt( items[4] ) / 100;
  99. var tmpsp = parseInt( items[2] ) / 100;
  100. if( !mecoffee_timestamp_start && mecoffee_timestamp_start != 0 ) {
  101. mecoffee_timestamp_start = ts ;
  102. mecoffee_terminal_write( "\r\ncmd dump OK\r\n" );
  103. }
  104. if( ts - mecoffee_timestamp_start == 2 ) {
  105. var d = new Date(), e = new Date(d);
  106. var msSinceMidnight = e - d.setHours(0,0,0,0);
  107. mecoffee_terminal_write( "\r\ncmd clock set " + parseInt( msSinceMidnight / 1000 ) + " OK\r\n" );
  108. console.log( 'clock set to ' + parseInt( msSinceMidnight / 1000 ) );
  109. }
  110. if( ts - mecoffee_timestamp_start == 5 ) {
  111. mecoffee_terminal_write( "\r\ncmd uname OK\r\n");
  112. }
  113. graph.tick( tmp, tmp2, tmpsp, ts - mecoffee_timestamp_start );
  114. //mecoffee_cur_tmp = tmp;
  115. //mecoffee_cur_sp = tmpsp;
  116. document.querySelector('#mc_tmp').value = tmp;
  117. document.querySelector('#mc_tmpsp').value = tmpsp;
  118. }
  119. var gauge_boiler;
  120. function mecoffee_terminal_pid( items ) {
  121. var pid = parseInt( items[1] ) + parseInt( items[2] ) + parseInt( items[3] );
  122. gauge_boiler.write( pid / 65536 * 100);
  123. }
  124. var gauge_shottimer;
  125. var gauge_shottimer_time;
  126. var gauge_shottimer_timeout;
  127. var gauge_shottimer_interval = null;
  128. function mecoffee_terminal_shot( items ) {
  129. var value = parseInt( items[ items.length - 2 ] );
  130. var speed = 1;
  131. if( mecoffee_demo_interval > 0 ) {
  132. speed = $('#demo_speed')[0].value;
  133. }
  134. if( gauge_shottimer_timeout ) {
  135. clearTimeout( gauge_shottimer_timeout );
  136. gauge_shottimer_timeout = null;
  137. }
  138. $('#gauge_shottimer').show();
  139. // start interval to update shot timer every sec
  140. if( value == 0 && gauge_shottimer_interval == null ) {
  141. gauge_shottimer_time = 0;
  142. console.log( 'started interval ' );
  143. gauge_shottimer_interval = setInterval( function() {
  144. gauge_shottimer.write( ++gauge_shottimer_time );
  145. //console.log( 'update shot timer to ' + gauge_shottimer_time );
  146. },
  147. 1000 / speed );
  148. }
  149. // clean up shot timer after 30 secs
  150. if( value > 0 ) {
  151. if( gauge_shottimer_interval ) {
  152. clearInterval( gauge_shottimer_interval );
  153. gauge_shottimer_interval = null;
  154. gauge_shottimer.write( value / 1000.0 );
  155. }
  156. gauge_shottimer_timeout = setTimeout( function() {
  157. gauge_shottimer.write( 0 );
  158. $('#gauge_shottimer').hide();
  159. gauge_shottimer_timeout = null;
  160. },
  161. Math.min( 30000, 30000 / speed )
  162. );
  163. }
  164. gauge_shottimer.write( value / 1000 );
  165. }
  166. function mecoffee_terminal_cmd( items ) {
  167. if( items[1] == 'get' ) {
  168. var elems = $( '#mc_' + items[2] );
  169. var value = items[3];
  170. if( elems.length == 0 ) {
  171. console.log( '#mc_' + items[2] + ' not found' );
  172. return;
  173. }
  174. var elem = elems[0];
  175. var scale = elem.getAttribute('data-scale');
  176. if( scale )
  177. value /= scale;
  178. if( elems[0].className.indexOf('timepicker') >= 0) {
  179. var hour = Math.floor( value / ( 60*60 ) );
  180. var min = value % ( 60*60 );
  181. elems[0].value = hour + ':' + Math.floor( min / 60 );
  182. return;
  183. }
  184. if( elems[0].type == "checkbox" ) {
  185. elems.bootstrapSwitch('state' , items[3] == "1" );
  186. return;
  187. }
  188. if( elems.attr( 'data-slider-value' ) ) {
  189. elems.slider( 'setValue', parseInt( items[3] ) );
  190. return;
  191. }
  192. elems[0].value = value;
  193. }
  194. }
  195. function mecoffee_terminal_uname( line ) {
  196. legacy = line.indexOf( 'V4' ) > 0;
  197. if( legacy ) {
  198. console.log( 'meCoffee is legacy ( V4 )' );
  199. $('#mc_pistrt')[0].setAttribute( 'data-scale', 1 );
  200. $('#mc_pistrt')[0].setAttribute( 'data-slider-step', 1 );
  201. $('#mc_pistrt').slider( 'destroy' );
  202. $('#mc_pistrt').slider( );
  203. $( '#mc_pistrt' ).change( mecoffee_control_change );
  204. $('#mc_piprd')[0].setAttribute( 'data-scale', 1 );
  205. $('#mc_piprd')[0].setAttribute( 'data-slider-step', 1 );
  206. $('#mc_piprd').slider( 'destroy' );
  207. $('#mc_piprd').slider( );
  208. $( '#mc_piprd' ).change( mecoffee_control_change );
  209. }
  210. else {
  211. console.log( 'meCoffee has newish firmware' );
  212. $('#mc_pistrt')[0].setAttribute( 'data-scale', 1000 );
  213. $('#mc_pistrt')[0].setAttribute( 'data-slider-step', "0.1" );
  214. $('#mc_pistrt').slider( 'destroy' );
  215. $('#mc_pistrt').slider( );
  216. $( '#mc_pistrt' ).change( mecoffee_control_change );
  217. $('#mc_piprd')[0].setAttribute( 'data-scale', 1000 );
  218. $('#mc_piprd')[0].setAttribute( 'data-slider-step', 0.1 );
  219. $('#mc_piprd').slider( 'destroy' );
  220. $('#mc_piprd').slider( );
  221. $( '#mc_piprd' ).change( mecoffee_control_change );
  222. }
  223. // $('.slider').destroy();
  224. // $('.slider').slider();
  225. // $('#mc_pistrt')[0].setAttribute( 'data-scala', 1000 );
  226. }
  227. function mecoffee_terminal( line ) {
  228. received_log = received_log.concat( line + "\n" );
  229. var items = line.split(' ');
  230. if( items[ items.length - 1 ] != 'OK' )
  231. return;
  232. if( items[0] == 'tmp' ) {
  233. mecoffee_terminal_tmp( items );
  234. }
  235. if( items[1] == 'uname' ) {
  236. mecoffee_terminal_uname( line );
  237. return;
  238. }
  239. if( items[0] == 'cmd' )
  240. mecoffee_terminal_cmd( items );
  241. if( items[0] == 'pid' )
  242. mecoffee_terminal_pid( items );
  243. if( items[0] == 'sht' )
  244. mecoffee_terminal_shot( items );
  245. }
  246. function mecoffee_terminal_write( line ) {
  247. if( !mecoffee_socketId )
  248. return ;
  249. chrome.bluetoothSocket.send( mecoffee_socketId, str2ab( line ), function(bytes_sent) {
  250. if (chrome.runtime.lastError) {
  251. console.log("Send failed: " + chrome.runtime.lastError.message);
  252. } else {
  253. console.log("Sent " + bytes_sent + " bytes")
  254. }
  255. });
  256. }
  257. /*
  258. Demo
  259. */
  260. var mecoffee_demo_log = "";
  261. var mecoffee_demo_interval = 0;
  262. var mecoffee_demo_cnt = 0;
  263. function mecoffee_demo_timer( ) {
  264. if( mecoffee_demo_cnt >= mecoffee_demo_log.length ) {
  265. mecoffee_demo_stop();
  266. //clearInterval( mecoffee_demo_interval );
  267. // TODO: change start button state
  268. //mecoffee_demo_cnt = 0;
  269. }
  270. for( ; mecoffee_demo_cnt < mecoffee_demo_log.length ; mecoffee_demo_cnt++ ) {
  271. var line = mecoffee_demo_log[mecoffee_demo_cnt];
  272. mecoffee_terminal( line );
  273. if( line.indexOf('tmp ') == 0 ) {
  274. mecoffee_demo_cnt++;
  275. break;
  276. }
  277. }
  278. }
  279. function mecoffee_demo( log ) {
  280. log = log.replace( "share_", "" );
  281. $.get( "https://mecoffee.nl/share/logs/" + log, function( data ) {
  282. mecoffee_demo_log = data.split("\n");
  283. mecoffee_demo_interval = setInterval( mecoffee_demo_timer, 1000 / $('#demo_speed')[0].value );
  284. });
  285. return false;
  286. }
  287. function mecoffee_demo_speed( e ) {
  288. if( !mecoffee_demo_interval )
  289. return ;
  290. clearInterval( mecoffee_demo_interval );
  291. mecoffee_demo_interval = setInterval( mecoffee_demo_timer, 1000 / e.target.value );
  292. }
  293. function mecoffee_demo_stop( e ) {
  294. if( mecoffee_demo_interval )
  295. clearInterval( mecoffee_demo_interval );
  296. mecoffee_demo_interval = 0;
  297. mecoffee_demo_cnt = 0;
  298. $('#start_icon')[0].className = "glyphicon glyphicon-play";
  299. $('#start_label')[0].innerHTML = "Start";
  300. start.className ="btn btn-primary";
  301. }
  302. /*
  303. App
  304. */
  305. function mecoffee_start( e ) {
  306. var start = $('#start')[0];
  307. if( start.className.indexOf("danger") > 0 ) {
  308. // Stop
  309. start.className ="btn btn-primary";
  310. $('#start_icon')[0].className = "glyphicon glyphicon-play";
  311. $('#start_label')[0].innerHTML = "Start";
  312. mecoffee_disconnect( );
  313. mecoffee_demo_stop( );
  314. }
  315. else {
  316. // Start
  317. start.className ="btn btn-danger";
  318. $('#start_icon')[0].className = "glyphicon glyphicon-stop";
  319. $('#start_label')[0].innerHTML = "Stop";
  320. var value = $('#device')[0].value;
  321. if( value.indexOf('device_') == 0 )
  322. mecoffee_connect( value.replace( 'device_', '' ) );
  323. if( value.indexOf('share_') == 0 )
  324. mecoffee_demo( value );
  325. }
  326. return false;
  327. }
  328. function mecoffee_share( e ) {
  329. $.post( "https://mecoffee.nl/share/post.php", { logfile: received_log }, function( data ) {
  330. $( "#share_result" ).html( '<a target="_blank" href=""' + data + '">' + data + '</a>');
  331. window.open( data, "_blank" );
  332. });
  333. return false;
  334. }
  335. function mecoffee_control_change( e ) {
  336. var elem = e.target;
  337. if( elem.id.indexOf('mc_') != 0 )
  338. return;
  339. var value = elem.value;
  340. if( e.value && e.value.newValue )
  341. value = e.value.newValue;
  342. if( elem.type == "checkbox" )
  343. value = elem.checked ? 1 : 0;
  344. if( value.indexOf && value.indexOf(':') > 0 ) {
  345. var p = value.split(':');
  346. value = 60*60*p[0] + 60*p[1];
  347. }
  348. var scale = elem.getAttribute('data-scale');
  349. if( scale )
  350. value *= scale;
  351. var line = "\r\ncmd set " + elem.id.replace( "mc_", "" ) + " " + value + " OK\r\n";
  352. console.log( line );
  353. mecoffee_terminal_write( line );
  354. }
  355. var graph;
  356. function mecoffee_app_startup() {
  357. //document.querySelector("#doit").addEventListener( 'click', doit_doit );
  358. $('#start').click( mecoffee_start );
  359. $('#share').click( mecoffee_share );
  360. window.addEventListener("resize", function() { graph.draw(); });
  361. graph = new meCoffeeGraph(); graph.draw();
  362. $("input[type='checkbox']").bootstrapSwitch( { onSwitchChange: function ( e ) { mecoffee_control_change( e ); }});
  363. // slider_pistrt = new Slider( '#mc_pistrt' );
  364. $('.slider').slider();
  365. $('.timepicker').timepicker( { showMeridian: false } );
  366. $( ".target" ).change( mecoffee_control_change );
  367. $( "#demo_speed").change( mecoffee_demo_speed );
  368. //var
  369. gauge_boiler = new Gauge();
  370. gauge_boiler.draw( $('#gauge_boiler')[0] , { label: 'Boiler %' } );
  371. gauge_shottimer = new Gauge();
  372. gauge_shottimer.draw( $('#gauge_shottimer')[0] , { label: 'Shot (s)', max: 30 } );
  373. mecoffee_bt_enum_devices( );
  374. }
  375. window.addEventListener("DOMContentLoaded", mecoffee_app_startup );