summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Forlot <romain.forlot@iot.bzh>2017-04-29 18:17:08 +0200
committerRomain Forlot <romain.forlot@iot.bzh>2017-05-02 16:17:08 +0200
commit7e6d6d0a37e37804e3a751e2bfde11a9c1e85b0b (patch)
treedcc513f232f608021a55fd317ccd3b1f623ccb70
parent10e7cf8b0d84be658069f60e5dd4831ec202cd70 (diff)
Adding HTML5 UI with cpu stat binding
Change-Id: Id63b7d338140097a5f2f0943f1b63ee978141829 Signed-off-by: Romain Forlot <romain.forlot@iot.bzh>
-rw-r--r--CAN-binder/low-can-demo/.gitignore5
-rw-r--r--CAN-binder/low-can-demo/LICENSE201
-rw-r--r--CAN-binder/low-can-demo/README.md13
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/car-top-view.pngbin0 -> 2424 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/cluster.pngbin0 -> 409895 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/favicon.icobin0 -> 1150 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/gas-pump-black.pngbin0 -> 2429 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/gas-pump-green.pngbin0 -> 1826 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/gas-pump-red.pngbin0 -> 2174 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/images/logo_iot_bzh_lightgrey_325x90_50dpi.pngbin0 -> 14605 bytes
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/index.html137
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/js/AFB.js170
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/js/low-can-demo.js351
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/styles/bootstrap.scss56
-rw-r--r--CAN-binder/low-can-demo/app/Frontend/styles/low-can-demo.scss187
-rw-r--r--CAN-binder/low-can-demo/app/etc/AppDefaults.js33
-rw-r--r--CAN-binder/low-can-demo/app/etc/_Config.js44
-rw-r--r--CAN-binder/low-can-demo/app/etc/_Trace.js55
-rw-r--r--CAN-binder/low-can-demo/binding/CMakeLists.txt42
-rw-r--r--CAN-binder/low-can-demo/binding/export.map1
-rw-r--r--CAN-binder/low-can-demo/binding/stat-binding.c272
-rw-r--r--CAN-binder/low-can-demo/bower.json19
-rw-r--r--CAN-binder/low-can-demo/config.xml.in11
-rw-r--r--CAN-binder/low-can-demo/credits.md7
-rw-r--r--CAN-binder/low-can-demo/gulpfile.js484
-rw-r--r--CAN-binder/low-can-demo/icon_hybrid_html5_128.pngbin0 -> 4482 bytes
-rw-r--r--CAN-binder/low-can-demo/package.json60
27 files changed, 2148 insertions, 0 deletions
diff --git a/CAN-binder/low-can-demo/.gitignore b/CAN-binder/low-can-demo/.gitignore
new file mode 100644
index 00000000..46f791d2
--- /dev/null
+++ b/CAN-binder/low-can-demo/.gitignore
@@ -0,0 +1,5 @@
+dist.prod
+dist.dev
+build
+bower_components
+node_modules
diff --git a/CAN-binder/low-can-demo/LICENSE b/CAN-binder/low-can-demo/LICENSE
new file mode 100644
index 00000000..8dada3ed
--- /dev/null
+++ b/CAN-binder/low-can-demo/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/CAN-binder/low-can-demo/README.md b/CAN-binder/low-can-demo/README.md
new file mode 100644
index 00000000..bb8f2396
--- /dev/null
+++ b/CAN-binder/low-can-demo/README.md
@@ -0,0 +1,13 @@
+# Build
+
+Run:
+
+```
+# npm install
+# mkdir build
+# cd build
+# cmake ..
+# make
+```
+
+
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/car-top-view.png b/CAN-binder/low-can-demo/app/Frontend/images/car-top-view.png
new file mode 100644
index 00000000..a5ad0966
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/car-top-view.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/cluster.png b/CAN-binder/low-can-demo/app/Frontend/images/cluster.png
new file mode 100644
index 00000000..c6b8d908
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/cluster.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/favicon.ico b/CAN-binder/low-can-demo/app/Frontend/images/favicon.ico
new file mode 100644
index 00000000..eeb7ab7a
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/favicon.ico
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-black.png b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-black.png
new file mode 100644
index 00000000..46e875e1
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-black.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-green.png b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-green.png
new file mode 100644
index 00000000..c3aebad6
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-green.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-red.png b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-red.png
new file mode 100644
index 00000000..ad842839
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/gas-pump-red.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/images/logo_iot_bzh_lightgrey_325x90_50dpi.png b/CAN-binder/low-can-demo/app/Frontend/images/logo_iot_bzh_lightgrey_325x90_50dpi.png
new file mode 100644
index 00000000..7ba41960
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/images/logo_iot_bzh_lightgrey_325x90_50dpi.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/app/Frontend/index.html b/CAN-binder/low-can-demo/app/Frontend/index.html
new file mode 100644
index 00000000..b1b8a810
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/index.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]> <html lang="en" ng-app="@@APPNAME@@" class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html lang="en" ng-app="@@APPNAME@@" class="no-js"> <!--<![endif]-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>AGL Low CAN binding demo</title>
+ <base href="@@URLBASE@@">
+ <!-- bower:css -->
+ <!-- endinject -->
+ <!-- vendor:css -->
+ <!-- endinject -->
+ <!-- appli:css -->
+ <!-- endinject -->
+ <!-- inject:css -->
+ <!-- endinject -->
+ <!-- bower:js -->
+ <!-- endinject -->
+ <!-- inject:js -->
+ <!-- endinject -->
+</head>
+<body class="not-connected">
+ <!-- Navigation -->
+ <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+ <div class="container-fluid">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <a class="navbar-brand" href="http://iot.bzh" target="_blank" alt="IoT.bzh">
+ <img id="iotbzh-logo" src="/images/logo_iot_bzh_lightgrey_325x90_50dpi.png"/>
+ </a>
+ <div class="collapse navbar-collapse">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <p class="navbar-text navbar-right">AGL Low CAN binding</p>
+ <ul class="nav navbar-nav">
+ <li class="if-not-connected">
+ <a role="button" onclick="doConnect();">Connect</a>
+ </li>
+ <li class="if-connecting">
+ Connecting...
+ </li>
+ <li class="if-started">
+ <a role="button" onclick="doDisconnect();">Disconnect</a>
+ </li>
+ </ul>
+ </div>
+ <!-- /.navbar-collapse -->
+ </div>
+ <!-- /.container -->
+ </nav>
+
+ <div class="content">
+ <div id="quad1" class="quad">
+ <img id="view1"/>
+<!--
+ <div id="vspan">
+ <svg height="100" width="100">
+ <g>
+ <circle cx="50" cy="50" r="49" stroke="white" stroke-width="3"/>
+ <text x="50%" y="80%" fill="white" text-anchor="middle">Km/h</text>
+ </g>
+ </svg>
+ <div id="vspeed" class="number">0</div>
+ </div>
+ <div id="espan">
+ <svg height="100" width="100">
+ <g>
+ <circle cx="50" cy="50" r="49" stroke="white" stroke-width="3"/>
+ <text x="50%" y="80%" fill="white" text-anchor="middle">Rpm</text>
+ </g>
+ </svg>
+ <div id="espeed" class="number">0</div>
+ </div>
+-->
+ <div id="cluster">
+ <canvas id="torqueGauge"></canvas>
+ <canvas id="rpmGauge"></canvas>
+ <canvas id="MAFGauge"></canvas>
+ <canvas id="speedGauge"></canvas>
+ <canvas id="IATempGauge"></canvas>
+ </div>
+ </div>
+
+<!--
+ <div id="quad2" class="quad">
+ <div id="mapsat"></div>
+ <img id="car" class="invisible" src="images/car-top-view.png"/>
+ <div id="gaspan">
+ <div id="gas">
+ <div id="gpgreen"></div>
+ <div id="gpblack"></div>
+ <div id="gpred"></div>
+ </div>
+ <div id="copan">
+ <div id="con1"></div>
+ <div id="con2"></div>
+ <div id="con3"></div>
+ <div id="con4"></div>
+ <div id="con5"></div>
+ <div id="con6"></div>
+ <div id="con7"></div>
+ <div id="con8"></div>
+ <div id="con9"></div>
+ </div>
+ </div>
+ </div>
+-->
+ <div id="quad3" class="quad">
+ <table id="xcdata" class="table table-striped table-bordered table-condensed">
+ <thead>
+ <tr><th colspan="2">AGL CAN messages Details</th></tr>
+ <tr><th class="col-md-4">Data</th><th>Value</th></tr>
+ </thead>
+ <tbody>
+ <tr> <td>Vehicle Speed</td> <td><span id="vsp">?</span> km/h</td> </tr>
+ <tr> <td>Engine Speed</td> <td><span id="esp">?</span> tr/mn</td> </tr>
+ <tr> <td>Engine Load</td> <td><span id="trq">?</span> %</td> </tr>
+ <tr> <td>MAF air flow rate</td> <td><span id="fue">?</span> grams/sec</td> </tr>
+ <tr> <td>Intake Air temp</td> <td><span id="tem">?</span> °C</td> </tr>
+ <tr> <td>Messages rate</td> <td><span id="msg">?</span> received events/s</td> </tr>
+ <tr> <td>CPU load</td> <td><span id="stat">?</span> %</td> </tr>
+ </tbody>
+ </table>
+ </div>
+
+<!--
+ <div id="quad4" class="quad">
+ <div id="mapstreet"></div>
+ </div>
+-->
+ </div>
+</body>
+</html>
diff --git a/CAN-binder/low-can-demo/app/Frontend/js/AFB.js b/CAN-binder/low-can-demo/app/Frontend/js/AFB.js
new file mode 100644
index 00000000..aa1198cb
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/js/AFB.js
@@ -0,0 +1,170 @@
+AFB = function(base, initialtoken){
+
+var urlws = "ws://"+window.location.host+"/"+base;
+var urlhttp = "http://"+window.location.host+"/"+base;
+
+/*********************************************/
+/**** ****/
+/**** AFB_context ****/
+/**** ****/
+/*********************************************/
+var AFB_context = (function() {
+ var UUID;
+ var TOKEN = initialtoken;
+
+ var context = function(token, uuid) {
+ this.token = token;
+ this.uuid = uuid;
+ };
+
+ context.prototype = {
+ get token() {return TOKEN;},
+ set token(tok) {if(tok) TOKEN=tok;},
+ get uuid() {return UUID;},
+ set uuid(id) {if(id) UUID=id;}
+ };
+
+ return new context();
+})();
+/*********************************************/
+/**** ****/
+/**** AFB_websocket ****/
+/**** ****/
+/*********************************************/
+var AFB_websocket = (function() {
+ var CALL = 2;
+ var RETOK = 3;
+ var RETERR = 4;
+ var EVENT = 5;
+
+ var PROTO1 = "x-afb-ws-json1";
+
+ var result = function(onopen, onabort) {
+ var u = urlws;
+ if (AFB_context.token) {
+ u = u + '?x-afb-token=' + AFB_context.token;
+ if (AFB_context.uuid)
+ u = u + '&x-afb-uuid=' + AFB_context.uuid;
+ }
+ this.ws = new WebSocket(u, [ PROTO1 ]);
+ this.pendings = {};
+ this.awaitens = {};
+ this.counter = 0;
+ this.ws.onopen = onopen.bind(this);
+ this.ws.onerror = onerror.bind(this);
+ this.ws.onclose = onclose.bind(this);
+ this.ws.onmessage = onmessage.bind(this);
+ this.onopen = onopen;
+ this.onabort = onabort;
+ };
+
+ function onerror(event) {
+ var f = this.onabort;
+ if (f) {
+ delete this.onopen;
+ delete this.onabort;
+ f && f(this);
+ }
+ this.onerror && this.onerror(this);
+ }
+
+ function onopen(event) {
+ var f = this.onopen;
+ delete this.onopen;
+ delete this.onabort;
+ f && f(this);
+ }
+
+ function onclose(event) {
+ for (var id in this.pendings) {
+ var ferr = this.pendings[id].onerror;
+ ferr && ferr(null, this);
+ }
+ this.pendings = {};
+ this.onclose && this.onclose();
+ }
+
+ function fire(awaitens, name, data) {
+ var a = awaitens[name];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ var i = name.indexOf("/");
+ if (i >= 0) {
+ a = awaitens[name.substring(0,i)];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ }
+ a = awaitens["*"];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ }
+
+ function reply(pendings, id, ans, offset) {
+ if (id in pendings) {
+ var p = pendings[id];
+ delete pendings[id];
+ var f = p[offset];
+ f && f(ans);
+ }
+ }
+
+ function onmessage(event) {
+ var obj = JSON.parse(event.data);
+ var code = obj[0];
+ var id = obj[1];
+ var ans = obj[2];
+ AFB_context.token = obj[3];
+ switch (code) {
+ case RETOK:
+ reply(this.pendings, id, ans, 0);
+ break;
+ case RETERR:
+ reply(this.pendings, id, ans, 1);
+ break;
+ case EVENT:
+ default:
+ fire(this.awaitens, id, ans);
+ break;
+ }
+ }
+
+ function close() {
+ this.ws.close();
+ }
+
+ function call(method, request, onsuccess, onfailure) {
+ var id, arr;
+ do {
+ id = String(this.counter = 4095 & (this.counter + 1));
+ } while (id in this.pendings);
+ this.pendings[id] = [ onsuccess, onfailure ];
+ arr = [CALL, id, method, request ];
+ if (AFB_context.token) arr.push(AFB_context.token);
+ this.ws.send(JSON.stringify(arr));
+ }
+
+ function onevent(name, handler) {
+ var id = name;
+ var list = this.awaitens[id] || (this.awaitens[id] = []);
+ list.push(handler);
+ }
+
+ result.prototype = {
+ close: close,
+ call: call,
+ onevent: onevent
+ };
+
+ return result;
+})();
+/*********************************************/
+/**** ****/
+/**** ****/
+/**** ****/
+/*********************************************/
+return {
+ context: AFB_context,
+ ws: AFB_websocket
+};
+};
+
diff --git a/CAN-binder/low-can-demo/app/Frontend/js/low-can-demo.js b/CAN-binder/low-can-demo/app/Frontend/js/low-can-demo.js
new file mode 100644
index 00000000..2e8b99e6
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/js/low-can-demo.js
@@ -0,0 +1,351 @@
+// parse location to get security token
+var urlParams={};
+location.search.substr(1).split("&").forEach(function(item) {
+ var k = item.split("=")[0];
+ var v = decodeURIComponent(item.split("=")[1]);
+ if (k in urlParams) urlParams[k].push(v); else urlParams[k] = [v];
+});
+
+var afb = new AFB("api"/*root*/, urlParams.token[0]);
+var ws;
+var vspeed = 0, espeed = 0, torque = 0;
+var R2D = 180.0 / Math.PI;
+var D2R = Math.PI / 180.0;
+var fuel;
+var con,cons,consa = [ ];
+var minspeed = 5;
+var temp = 18;
+var wdgTem, wdgVsp, wdgEsp, wdgTrq;
+var wdgFue, wdgGpred, wdgGpblack;
+var conscale = 40;
+var condt = 60000;
+
+/* gauges creation */
+var gauges={};
+function initGauges() {
+ gauges.speed = new steelseries.Radial('speedGauge', {
+ gaugeType: steelseries.GaugeType.TYPE4,
+ frameDesign: steelseries.FrameDesign.BLACK_METAL,
+ backgroundColor: steelseries.BackgroundColor.CARBON,
+ size: 250,
+ titleString: "Speed",
+ unitString: "Km/h",
+ lcdVisible: true,
+ niceScale: true,
+ maxValue: 200,
+ maxMeasuredValue: 0,
+ maxMeasuredValueVisible: true,
+ thresholdVisible: false,
+ ledVisible: false,
+ pointerType: steelseries.PointerType.TYPE11,
+ useOdometer: false,
+ odometerParams: {
+ digits: 6
+ }
+ });
+
+ gauges.rpm = new steelseries.Radial('rpmGauge', {
+ gaugeType: steelseries.GaugeType.TYPE4,
+ frameDesign: steelseries.FrameDesign.BLACK_METAL,
+ backgroundColor: steelseries.BackgroundColor.CARBON,
+ size: 200,
+ titleString: "RPM",
+ unitString: "x1000",
+ lcdVisible: false,
+ niceScale: true,
+ maxValue: 5,
+ maxMeasuredValue: 0,
+ maxMeasuredValueVisible: false,
+ section: [
+ steelseries.Section(4, 8, 'rgba(255, 0, 0, 0.7)')
+ ],
+ area: [
+ steelseries.Section(5, 8, 'rgba(255, 0, 0, 0.3)')
+ ],
+ thresholdVisible: false,
+ ledVisible: false,
+ pointerType: steelseries.PointerType.TYPE11
+ });
+
+ gauges.maf = new steelseries.Radial('MAFGauge', {
+ gaugeType: steelseries.GaugeType.TYPE4,
+ frameDesign: steelseries.FrameDesign.BLACK_METAL,
+ backgroundColor: steelseries.BackgroundColor.CARBON,
+ size: 200,
+ titleString: "Air flow Rate",
+ unitString: "grams/sec",
+ lcdVisible: true,
+ lcdColor: steelseries.LcdColor.STANDARD,
+ lcdDecimals: 1,
+ niceScale: true,
+ minValue: 0,
+ maxValue: 655,
+ minMeasuredValue: 0,
+ maxMeasuredValue: conscale,
+ maxMeasuredValueVisible: true,
+ section: [
+ steelseries.Section(0, 255, 'rgba(0, 255, 0, 0.5)'),
+ steelseries.Section(256, 326, 'rgba(255, 255, 0, 0.5)'),
+ steelseries.Section(327, 600, 'rgba(255, 128, 0, 0.5)'),
+ steelseries.Section(601, 655, 'rgba(255, 0, 0, 0.5)')
+ ],
+ useValueGradient: true,
+ thresholdVisible: false,
+ ledVisible: false,
+ pointerType: steelseries.PointerType.TYPE11
+ });
+
+ gauges.iatemp = new steelseries.Radial('IATempGauge', {
+ gaugeType: steelseries.GaugeType.TYPE4,
+ frameDesign: steelseries.FrameDesign.BLACK_METAL,
+ backgroundColor: steelseries.BackgroundColor.CARBON,
+ size: 200,
+ titleString: "Intake air temp",
+ unitString: "°C",
+ lcdVisible: true,
+ lcdColor: steelseries.LcdColor.STANDARD,
+ lcdDecimals: 1,
+ niceScale: true,
+ minValue: 0,
+ maxValue: 100,
+ minMeasuredValue: 0,
+ maxMeasuredValue: 100,
+ maxMeasuredValueVisible: true,
+ section: [
+ steelseries.Section(0, 30, 'rgba(0, 255, 0, 0.5)'),
+ steelseries.Section(31, 50, 'rgba(255, 255, 0, 0.5)'),
+ steelseries.Section(51, 70, 'rgba(255, 128, 0, 0.5)'),
+ steelseries.Section(71, 100, 'rgba(255, 0, 0, 0.5)')
+ ],
+ useValueGradient: true,
+ thresholdVisible: false,
+ ledVisible: false,
+ pointerType: steelseries.PointerType.TYPE11
+ });
+
+ gauges.torque = new steelseries.Radial('torqueGauge', {
+ gaugeType: steelseries.GaugeType.TYPE2,
+ frameDesign: steelseries.FrameDesign.BLACK_METAL,
+ backgroundColor: steelseries.BackgroundColor.CARBON,
+ size: 200,
+ titleString: "Load",
+ unitString: "%",
+ lcdVisible: false,
+ niceScale: true,
+ minValue: 0,
+ maxValue: 100,
+ maxMeasuredValue: 0,
+ maxMeasuredValueVisible: false,
+ section: [
+ steelseries.Section(0, 0, 'rgba(0, 255, 0, 0.7)'),
+ steelseries.Section(50, 1500, 'rgba(255, 128, 0, 0.7)')
+ ],
+ area: [
+ steelseries.Section(0, 0, 'rgba(0, 255, 0, 0.3)'),
+ steelseries.Section(50, 1500, 'rgba(255, 128, 0, 0.3)')
+ ],
+ threshold: 0,
+ thresholdVisible: true,
+ ledVisible: false,
+ pointerType: steelseries.PointerType.TYPE4
+ });
+
+ /* adjust cluster background size upon resize */
+ // TODO: could be doable through CSS, but a bit tricky
+ function adjustCluster() {
+ var qh=$("#quad1").outerHeight();
+ var sh=$("#speedGauge").outerHeight();
+ var pct=Math.ceil((1000*sh/qh))/10+1;
+ $('#cluster').css("height",pct+"%");
+ }
+ $(window).resize(adjustCluster);
+ adjustCluster();
+}
+
+function clearGauges() {
+ for (var g in gauges) {
+ switch(g) {
+ case "clock":
+ gauges[g].setValue("-");
+ break;
+ case "speed":
+ gauges[g].setValue(0);
+ break;
+ default:
+ gauges[g].setValue(0);
+ break;
+ }
+ }
+}
+
+function gotVehicleSpeed(obj) {
+ vspeed = Math.round(obj.data.value);
+ wdgVsp.innerHTML = /* wdgVspeed.innerHTML = */ String(vspeed);
+ //gauges.speed.setValueAnimated(vspeed);
+ gauges.speed.setValue(vspeed);
+}
+
+function gotTorque(obj) {
+ torque=Math.round(obj.data.value);
+ wdgTrq.innerHTML=String(torque);
+ gauges.torque.setValue(torque);
+}
+
+function gotEngineSpeed(obj) {
+ espeed = Math.round(obj.data.value);
+ wdgEsp.innerHTML = /* wdgEspeed.innerHTML = */ String(espeed);
+ //gauges.rpm.setValueAnimated(espeed/1000);
+ gauges.rpm.setValue(espeed/1000);
+}
+
+function gotFuelLevel(obj) {
+ fuel = Math.round(obj.data.value);
+ wdgFue.innerHTML = fuel;
+ gauges.maf.setValue(fuel);
+}
+
+function gotTemp(obj) {
+ temp = Math.round(obj.data.value);
+ wdgTem.innerHTML = temp;
+ gauges.iatemp.setValue(temp);
+}
+
+function gotStart(obj) {
+ document.body.className = "started";
+ vspeed = 0;
+ espeed = 0;
+ heading = 0;
+ cons = undefined;
+ consa = [ ];
+
+ wdgVsp.innerHTML = /*wdgVspeed.innerHTML = */
+ wdgEsp.innerHTML = /*wdgEspeed.innerHTML = */
+ wdgTem.innerHTML = wdgFue.innerHTML = "?";
+ for (var i = 0 ; i < 9 ; i++) {
+ wdgConX[i].style.height = "0%";
+ wdgConX[i].innerHTML = "";
+ }
+}
+
+function gotStop(obj) {
+ document.body.className = "connected";
+}
+
+var msgcnt=0;
+var msgprv=0;
+var msgprvts=0;
+
+function gotAny(obj) {
+ if (obj.event != "low-can/STOP") {
+ document.body.className = "started";
+ }
+ msgcnt++;
+
+ wdgTem.innerHTML = temp;
+ gauges.iatemp.setValue(temp);
+// updateClock(obj.data.timestamp);
+}
+
+function updateMsgRate() {
+ var now=+new Date();
+ if (msgprvts) {
+ var dt=now-msgprvts;
+ msgrate=Math.round((msgcnt-msgprv)*10000/dt)/10;
+ wdgMsg.innerHTML=String(msgrate);
+ }
+
+ msgprv=msgcnt;
+ msgprvts=now;
+}
+
+function gotStat(obj) {
+ wdgStat.innerHTML = obj.data;
+}
+
+function onAbort() {
+ document.body.className = "not-connected";
+}
+
+function onOpen() {
+ ws.call("low-can/subscribe", {event:[
+ "diagnostic_messages.vehicle.speed",
+ "diagnostic_messages.mass.airflow",
+ "diagnostic_messages.engine.speed",
+ "diagnostic_messages.engine.load",
+ "diagnostic_messages.intake.air.temperature"]},
+ onSubscribed, onAbort);
+ ws.call("stat/subscribe", true);
+ ws.onevent("stat/stat", gotStat);
+}
+
+function onClose() {
+ ws.call("low-can/unsubscribe", {event:[
+ "diagnostic_messages.engine.speed",
+ "diagnostic_messages.mass.airflow",
+ "diagnostic_messages.vehicle.speed",
+ "diagnostic_messages.engine.load",
+ "diagnostic_messages.intake.air.temperature"]},
+ onUnsubscribed, onAbort);
+ ws.call("stat/unsubscribe", true);
+ ws.onevent("stat/stat", gotStat);
+}
+
+function onSubscribed() {
+ document.body.className = "connected";
+ ws.onevent("low-can/diagnostic_messages.engine.speed", gotEngineSpeed);
+ ws.onevent("low-can/diagnostic_messages.mass.airflow", gotFuelLevel);
+ ws.onevent("low-can/diagnostic_messages.vehicle.speed", gotVehicleSpeed);
+ ws.onevent("low-can/diagnostic_messages.engine.load", gotTorque);
+ ws.onevent("low-can/diagnostic_messages.intake.air.temperature", gotTemp);
+ ws.onevent("low-can",gotAny);
+}
+
+function onUnsubscribed() {
+ document.body.className = "disconnected";
+ ws.onevent("low-can/diagnostic_messages.engine.speed", gotEngineSpeed);
+ ws.onevent("low-can/diagnostic_messages.mass.airflow", gotFuelLevel);
+ ws.onevent("low-can/diagnostic_messages.vehicle.speed", gotVehicleSpeed);
+ ws.onevent("low-can/diagnostic_messages.engine.load", gotTorque);
+ ws.onevent("low-can/diagnostic_messages.intake.air.temperature", gotTemp);
+ ws.onevent("low-can",gotAny);
+}
+
+function replyok(obj) {
+ document.getElementById("output").innerHTML = "OK: "+JSON.stringify(obj);
+}
+function replyerr(obj) {
+ document.getElementById("output").innerHTML = "ERROR: "+JSON.stringify(obj);
+}
+function send(message) {
+ var api = document.getElementById("api").value;
+ var verb = document.getElementById("verb").value;
+ ws.call(api+"/"+verb, {data:message}, replyok, replyerr);
+}
+
+function doConnect() {
+ document.body.className = "connecting";
+ ws = new afb.ws(onOpen, onAbort);
+}
+
+function doDisconnect() {
+ document.body.className = "connecting";
+ ws = new afb.ws(onClose, onAbort);
+}
+
+$(function() {
+ wdgVsp = document.getElementById("vsp");
+ wdgEsp = document.getElementById("esp");
+ wdgTrq = document.getElementById("trq");
+ wdgFue = document.getElementById("fue");
+ wdgTem = document.getElementById("tem");
+ wdgStat = document.getElementById("stat");
+ wdgMsg = document.getElementById("msg");
+
+ initGauges();
+
+ doConnect();
+
+ // init interval to compute message rate
+ setInterval(updateMsgRate,250);
+});
+
diff --git a/CAN-binder/low-can-demo/app/Frontend/styles/bootstrap.scss b/CAN-binder/low-can-demo/app/Frontend/styles/bootstrap.scss
new file mode 100644
index 00000000..e72d1def
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/styles/bootstrap.scss
@@ -0,0 +1,56 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+// Core variables and mixins
+@import "bootstrap/variables";
+@import "bootstrap/mixins";
+
+// Reset and dependencies
+@import "bootstrap/normalize";
+@import "bootstrap/print";
+@import "bootstrap/glyphicons";
+
+// Core CSS
+@import "bootstrap/scaffolding";
+@import "bootstrap/type";
+@import "bootstrap/code";
+@import "bootstrap/grid";
+@import "bootstrap/tables";
+@import "bootstrap/forms";
+@import "bootstrap/buttons";
+
+// Components
+@import "bootstrap/component-animations";
+@import "bootstrap/dropdowns";
+@import "bootstrap/button-groups";
+@import "bootstrap/input-groups";
+@import "bootstrap/navs";
+@import "bootstrap/navbar";
+@import "bootstrap/breadcrumbs";
+@import "bootstrap/pagination";
+@import "bootstrap/pager";
+@import "bootstrap/labels";
+@import "bootstrap/badges";
+@import "bootstrap/jumbotron";
+@import "bootstrap/thumbnails";
+@import "bootstrap/alerts";
+@import "bootstrap/progress-bars";
+@import "bootstrap/media";
+@import "bootstrap/list-group";
+@import "bootstrap/panels";
+@import "bootstrap/responsive-embed";
+@import "bootstrap/wells";
+@import "bootstrap/close";
+
+// Components w/ JavaScript
+@import "bootstrap/modals";
+@import "bootstrap/tooltip";
+@import "bootstrap/popovers";
+@import "bootstrap/carousel";
+
+// Utility classes
+@import "bootstrap/utilities";
+@import "bootstrap/responsive-utilities";
diff --git a/CAN-binder/low-can-demo/app/Frontend/styles/low-can-demo.scss b/CAN-binder/low-can-demo/app/Frontend/styles/low-can-demo.scss
new file mode 100644
index 00000000..d7e52130
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/Frontend/styles/low-can-demo.scss
@@ -0,0 +1,187 @@
+html,body {
+ padding:0;
+ margin: 0 auto;
+ width:100%;
+ height:100%;
+ min-height:100%;
+ color: black;
+ background: #FFF;
+}
+
+/* http://stackoverflow.com/questions/18474564/bootstrap-3-navbar-with-logo */
+.navbar-brand {
+ padding: 0px;
+}
+.navbar-brand>img {
+ height: 100%;
+ padding: 5px;
+ width: auto;
+}
+.navbar-right {
+ margin-right: 0;
+}
+
+/* used in navbar to show/hide items depending on connection status */
+.not-connected .if-connecting { display: none; }
+.not-connected .if-connected { display: none; }
+.not-connected .if-started { display: none; }
+.connecting .if-not-connected { display: none; }
+.connecting .if-connected { display: none; }
+.connecting .if-started { display: none; }
+.connected .if-not-connected { display: none; }
+.connected .if-connecting { display: none; }
+.connected .if-started { display: none; }
+.started .if-not-connected { display: none; }
+.started .if-connecting { display: none; }
+.started .if-connected { display: none; }
+
+.content{
+ position: absolute;
+ left:0;
+ right:0;
+ bottom:0;
+ top: 50px;
+ padding:0;
+ overflow: hidden;
+}
+
+.quad {
+ position: relative;
+ overflow: hidden;
+ width:100%;
+ height:50%;
+ padding:0;
+ margin: 0;
+ box-sizing:border-box;
+ -moz-box-sizing:border-box;
+ -webkit-box-sizing:border-box;
+ border: 4px ridge silver;
+ border-radius: 15px;
+ text-align: left;
+}
+
+.center {
+ display: table-cell;
+ width:100%;
+ height:100%;
+ vertical-align: middle;
+}
+
+#quad1 { border-bottom-right-radius: 0;}
+#quad2 { border-bottom-left-radius: 0; }
+#quad3 { border-top-right-radius: 0; overflow: auto; }
+#quad4 { border-top-left-radius: 0; }
+
+/* in quad 1 */
+#view1,#view2 {
+ position: relative;
+ width: auto !important;
+ height: auto !important;
+ min-width: 100%;
+ min-height: 100%;
+ display: block;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%,-50%);
+ -webkit-transform: translate(-50%,-50%);
+}
+
+.number { text-align: center; vertical-align: middle; font: italic bold 12px monospace; }
+
+/*
+#espan { position: absolute; left: 1em; bottom: 1em; }
+#espeed { position: absolute; top: 30px; width: 100px; color: green; font-size: 28px; }
+
+#vspan { position: absolute; right: 1em; bottom: 1em; }
+#vspeed { position: absolute; top: 16px; width: 100px; color: cyan; font-size: 42px; }
+*/
+
+#cluster {
+ position: absolute;
+ bottom:0;
+ left:0;
+ right:0;
+ background: rgba(50,50,50,.4);
+ height: 43%;
+ border-top: 2px outset silver;
+ border-top-left-radius: 50%;
+ border-top-right-radius: 50%;
+}
+
+#torqueGauge { position: absolute; bottom: -2%; left: 1%; width: 14%; }
+#rpmGauge { position: absolute; bottom: -4%; left: 16%; width: 21%; }
+#speedGauge { position: absolute; bottom: -3%; left: 38%; width: 24%; }
+#MAFGauge { position: absolute; bottom: -4%; right: 16%; width: 21%; }
+#IATempGauge { position: absolute; bottom: -2%; right: 1%; width: 14%; }
+
+/* in quad 2 */
+#mapstreet {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+#car {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ z-index: 2000;
+
+}
+
+#gaspan { position: absolute; bottom:0; left:0; right:0; background: rgba(200,200,200,.3); padding: 1em; z-index: 2000;}
+
+#gas { position: relative; float: right; width: 69px; height: 92px; }
+#gas div { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; overflow: hidden; }
+
+#copan { overflow: hidden; min-height: 50px; position: relative; height: 92px;}
+#copan div {
+ display:inline;
+ position: absolute;
+ bottom: 0;
+ width: 10%;
+ height: 0%;
+ font-size: 10px;
+ color: black;
+ background: yellow;
+ border: 1px solid grey;
+ padding: 0;
+}
+#copan div p {
+ text-align: center;
+ position: absolute;
+ bottom: 2px;
+ margin: 0;
+ width: 100%;
+}
+
+#con1 { left: 0%; }
+#con2 { left: 11%; }
+#con3 { left: 22%; }
+#con4 { left: 33%; }
+#con5 { left: 44%; }
+#con6 { left: 55%; }
+#con7 { left: 66%; }
+#con8 { left: 77%; }
+#con9 { left: 88%; }
+
+#gpblack { background: url(/images/gas-pump-black.png); }
+#gpgreen { background: url(/images/gas-pump-green.png); }
+#gpred { background: url(/images/gas-pump-red.png); visibility: hidden; }
+
+/* quad 3 */
+#xcdata {
+ text-align: left;
+}
+
+.leaflet-control-layers-toggle {
+ background-image: url(/images/layers.png);
+}
+
+/* quad 4 */
+#mapsat {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
diff --git a/CAN-binder/low-can-demo/app/etc/AppDefaults.js b/CAN-binder/low-can-demo/app/etc/AppDefaults.js
new file mode 100644
index 00000000..164a6f21
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/etc/AppDefaults.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var SESSION_TIMEOUT= 3600000; // default is 1h loggin session
+
+// Default config will be superseaded by ProjectRoot/.config-l4a.js $HOME/.config-l4a.js /etc/default/config-l4a.js
+config = {
+
+ APPNAME : 'low-can-demo', // Application name, specified in config.xml
+ APPVER : '0.1', // version (config.xml)
+ FRONTEND: "Frontend", // HTML5 frontend [no leading ./]
+ BACKEND : "Backend", // NodeJS Rest API [no leading ./]
+ URLBASE : '/', // HTML basedir when running in production [should end with a /]
+ APIBASE : '/api/' // Api url base dir [should end with a /]
+};
+
+module.exports = config;
+
diff --git a/CAN-binder/low-can-demo/app/etc/_Config.js b/CAN-binder/low-can-demo/app/etc/_Config.js
new file mode 100644
index 00000000..ce93d434
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/etc/_Config.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 Fulup Ar Foll
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var fs = require('fs');
+
+function Config () {
+ 'use strict';
+ var values=[];
+ var extention='-l4a.js';
+ var conf;
+
+ // Configs file path last one supersead first one.
+ var files= [__dirname + "/AppDefaults.js", "/etc/default/noderc"+ extention, process.env.NODERC, process.env.HOME + "/.noderc"+ extention , __dirname +"/../../.noderc.js" ];
+
+ // Parse any existing files within config list & merge them
+ for (var idx in files) {
+ if (files[idx]) {
+ //console.log ("files=", files[idx]);
+ if (fs.existsSync (files[idx])) conf=require (files[idx]);
+ for (var i in conf) values[i] = conf[i];
+ }
+ }
+
+ // set path to search for node_module within parent directory
+ process.env.NODE_PATH= process.env.NODE_PATH + '../node_modules';
+
+ // console.log ("values=", values);
+ return values;
+}
+
+module.exports = Config();
diff --git a/CAN-binder/low-can-demo/app/etc/_Trace.js b/CAN-binder/low-can-demo/app/etc/_Trace.js
new file mode 100644
index 00000000..79ef4f5d
--- /dev/null
+++ b/CAN-binder/low-can-demo/app/etc/_Trace.js
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 Fulup Ar Foll
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var util = require("util");
+var path = require("path");
+var config= require('./_Config');
+
+function TracePoint () {
+ var saved = Error.prepareStackTrace; // save default prepareStack function
+ Error.prepareStackTrace = function(_, stack){ return stack; }; // overload err stack handling
+ Error.captureStackTrace(this, arguments.callee); // request a stack
+ this.trace = this.stack; // effectively build trace
+ Error.prepareStackTrace = saved; // restore original nodejs function
+}
+
+// ------- Public Methods --------------
+var dbgLevel = function(target, level, format) { //+ arguments
+ // try to get debugLevel from calling object or global config
+ if (target && target.dbgLevel) dbgLevel = target.dbgLevel;
+ else dbgLevel = config.DBG_LVL || 1;
+
+ if (dbgLevel >= level ) {
+
+ var args = [].slice.call(arguments, 2); // copy argument in a real array leaving out level
+ var message = util.format.apply(null, args);
+
+ var trace = new TracePoint().trace;
+ var info = {
+ fullpath : trace[1].getFileName(),
+ linenum : trace[1].getLineNumber(),
+ basename : path.basename (trace[1].getFileName())
+ };
+
+ if (dbgLevel >= 5) {
+ console.log("%s:%d", info.fullpath, info.linenum);
+ console.log("\t[%d] %j", dbgLevel, message);
+ }
+ else console.log("--%d-- [%s:%d] -- %j", dbgLevel, info.basename, info.linenum, message);
+ }
+};
+
+module.exports = dbgLevel;
diff --git a/CAN-binder/low-can-demo/binding/CMakeLists.txt b/CAN-binder/low-can-demo/binding/CMakeLists.txt
new file mode 100644
index 00000000..4078e2b1
--- /dev/null
+++ b/CAN-binder/low-can-demo/binding/CMakeLists.txt
@@ -0,0 +1,42 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(stat-binding)
+
+ # Define project Targets
+ add_library(${TARGET_NAME} MODULE ${TARGET_NAME}.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX ""
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ ${link_libraries})
+
+ # installation directory
+ INSTALL(TARGETS ${TARGET_NAME}
+ LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
+
+ populate_widget()
diff --git a/CAN-binder/low-can-demo/binding/export.map b/CAN-binder/low-can-demo/binding/export.map
new file mode 100644
index 00000000..52c1b4aa
--- /dev/null
+++ b/CAN-binder/low-can-demo/binding/export.map
@@ -0,0 +1 @@
+{ global: afbBindingV1*; local: *; };
diff --git a/CAN-binder/low-can-demo/binding/stat-binding.c b/CAN-binder/low-can-demo/binding/stat-binding.c
new file mode 100644
index 00000000..1e5b2dbd
--- /dev/null
+++ b/CAN-binder/low-can-demo/binding/stat-binding.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2016 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/timerfd.h>
+
+#include <json-c/json.h>
+
+#include <systemd/sd-event.h>
+
+#include <afb/afb-binding.h>
+#include <afb/afb-service-itf.h>
+
+/*
+ * the interface to afb-daemon
+ */
+const struct afb_binding_interface *afbitf;
+
+#define DEFAULT_PERIOD 1 /* 1 second */
+
+struct event;
+
+const char *cpu_fields[]= {
+ "user",
+ "nice",
+ "system",
+ "idle",
+ "iowait",
+ "irq",
+ "softirq",
+ "steal",
+ "guest",
+ "guest_nice"
+};
+
+#define CPU_FIELD_COUNT (sizeof cpu_fields / sizeof *cpu_fields)
+
+struct status {
+ uint64_t cpu[CPU_FIELD_COUNT];
+};
+
+static int fd_proc;
+static int fd_timer;
+static struct sd_event_source *source = NULL;
+
+static struct status older;
+static struct status newer;
+static struct status diff;
+static struct afb_event event;
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: BINDING VERBS IMPLEMENTATION **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+
+static int read_status(int fd, struct status *s)
+{
+ int n;
+ ssize_t sz;
+ off_t off;
+ char buffer[8192];
+ long long unsigned x[CPU_FIELD_COUNT];
+
+ off = lseek(fd, 0, SEEK_SET);
+ if (off == (off_t)-1)
+ return -1;
+
+ sz = read(fd, buffer, sizeof buffer);
+ if (sz == -1)
+ return -1;
+
+ n = sscanf(buffer, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &x[8], &x[9]);
+ if (n != CPU_FIELD_COUNT)
+ return -1;
+ while(n) {
+ n--;
+ s->cpu[n] = x[n];
+ }
+ return 0;
+}
+
+static void stop()
+{
+ sd_event_source_unref(source);
+ afb_event_drop(event);
+ close(fd_timer);
+ close(fd_proc);
+ fd_timer = -1;
+ fd_proc = -1;
+ event.itf = NULL;
+ source = NULL;
+}
+
+static int emit(sd_event_source *src, int fd, uint32_t revents, void *userdata)
+{
+ int f, rc, p;
+ struct json_object *obj;
+ uint64_t u, s, i;
+
+ read(fd, &i, sizeof i);
+
+ memcpy(&older, &newer, sizeof older);
+ rc = read_status(fd_proc, &newer);
+ for(f = 0; f < (int)CPU_FIELD_COUNT ; f++)
+ diff.cpu[f] = newer.cpu[f] - older.cpu[f];
+
+ u = 0;
+ s = 0;
+ i = 0;
+ u += diff.cpu[0]; // "user",
+ u += diff.cpu[1]; // "nice",
+ s += diff.cpu[2]; // "system",
+ i += diff.cpu[3]; // "idle",
+ i += diff.cpu[4]; // "iowait",
+ s += diff.cpu[5]; // "irq",
+ s += diff.cpu[6]; // "softirq",
+ i += diff.cpu[7]; // "steal",
+ u += diff.cpu[8]; // "guest",
+ u += diff.cpu[9]; // "guest_nice"
+ p = (int)(unsigned)((100 * (u + s)) / (u + s + i));
+
+ obj = json_object_new_int(p);
+ if (afb_event_push(event, obj) == 0)
+ stop();
+ return 0;
+}
+
+static int start()
+{
+ int fdp, fdt, rc;
+ struct itimerspec ts;
+ struct sd_event_source *src;
+
+ fdp = open("/proc/stat", O_RDONLY|O_CLOEXEC);
+ if (fdp >= 0) {
+ rc = read_status(fdp, &newer);
+ if (rc >= 0) {
+ fdt = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+ if (fdt >= 0) {
+ ts.it_interval.tv_sec = 1;
+ ts.it_value.tv_sec = 1;
+ ts.it_interval.tv_nsec = 0;
+ ts.it_value.tv_nsec = 0;
+ rc = timerfd_settime(fdt, 0, &ts, NULL);
+ if (rc >= 0) {
+ event = afb_daemon_make_event(afbitf->daemon, "stat");
+ if (afb_event_is_valid(event)) {
+ rc = sd_event_add_io(afb_daemon_get_event_loop(afbitf->daemon), &src, fdt, EPOLLIN, emit, NULL);
+ if (rc >= 0) {
+ fd_proc = fdp;
+ fd_timer = fdt;
+ source = src;
+ return 0;
+ }
+ afb_event_drop(event);
+ }
+ }
+ close(fdt);
+ }
+ }
+ close(fdp);
+ }
+ return -1;
+}
+
+static int ensure_started()
+{
+ return source == NULL ? start() : 0;
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: BINDING VERBS IMPLEMENTATION **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+
+/*
+ * subscribe to notification of stat
+ */
+static void subscribe(struct afb_req req)
+{
+ int rc;
+
+ rc = ensure_started();
+ if (rc < 0) {
+ afb_req_fail(req, "failed", "Can't start");
+ } else if (afb_req_subscribe(req, event) != 0) {
+ afb_req_fail_f(req, "failed", "afb_req_subscribe returned an error: %m");
+ } else {
+ afb_req_success(req, NULL, NULL);
+ }
+}
+
+/*
+ * unsubscribe a previous subscription
+ *
+ * parameters of the unsubscription are:
+ *
+ * id: integer: the numeric identifier of the event as returned when subscribing
+ */
+static void unsubscribe(struct afb_req req)
+{
+ if (afb_event_is_valid(event))
+ afb_req_unsubscribe(req, event);
+ afb_req_success(req, NULL, NULL);
+}
+
+/*
+ * array of the verbs exported to afb-daemon
+ */
+static const struct afb_verb_desc_v1 binding_verbs[] = {
+ /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */
+ { .name= "subscribe", .session= AFB_SESSION_NONE, .callback= subscribe, .info= "subscribe to notification of statistics" },
+ { .name= "unsubscribe", .session= AFB_SESSION_NONE, .callback= unsubscribe, .info= "unsubscribe a previous subscription" },
+ { .name= NULL } /* marker for end of the array */
+};
+
+/*
+ * description of the binding for afb-daemon
+ */
+static const struct afb_binding binding_description =
+{
+ /* description conforms to VERSION 1 */
+ .type= AFB_BINDING_VERSION_1,
+ .v1= { /* fills the v1 field of the union when AFB_BINDING_VERSION_1 */
+ .prefix= "stat", /* the API name (or binding name or prefix) */
+ .info= "Get system statistics", /* short description of of the binding */
+ .verbs = binding_verbs /* the array describing the verbs of the API */
+ }
+};
+
+/*
+ * activation function for registering the binding called by afb-daemon
+ */
+const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf)
+{
+ afbitf = itf; /* records the interface for accessing afb-daemon */
+ return &binding_description; /* returns the description of the binding */
+}
+
diff --git a/CAN-binder/low-can-demo/bower.json b/CAN-binder/low-can-demo/bower.json
new file mode 100644
index 00000000..f3f27899
--- /dev/null
+++ b/CAN-binder/low-can-demo/bower.json
@@ -0,0 +1,19 @@
+{
+ "name": "low-can-demo",
+ "version": "0.0.1",
+ "authors": "",
+ "private": true,
+ "dependencies": {
+ "bootstrap-sass": "~3",
+ "leaflet": "~1.0.0",
+ "steelseries": "https://github.com/iotbzh/SteelSeries-Canvas.git"
+ },
+ "overrides": {
+ "steelseries": {
+ "main": [
+ "tween.js",
+ "steelseries.js"
+ ]
+ }
+ }
+}
diff --git a/CAN-binder/low-can-demo/config.xml.in b/CAN-binder/low-can-demo/config.xml.in
new file mode 100644
index 00000000..f3ff5c9b
--- /dev/null
+++ b/CAN-binder/low-can-demo/config.xml.in
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="@@APPNAME@@" version="@@APPVER@@">
+ <name>App Framework - @@APPNAME@@</name>
+ <icon src="icon.png"/>
+ <content src="@@CONTENT@@" type="application/vnd.agl.html.hybrid"/>
+ <description>This is a demo using OpenXC trace</description>
+ <author>Jose Bollo &lt;jobol@iot.bzh&gt; / Stephane Desneux &lt;sdx@iot.bzh&gt;</author>
+ <license>APL 2.0</license>
+</widget>
+
+
diff --git a/CAN-binder/low-can-demo/credits.md b/CAN-binder/low-can-demo/credits.md
new file mode 100644
index 00000000..8f54fd5b
--- /dev/null
+++ b/CAN-binder/low-can-demo/credits.md
@@ -0,0 +1,7 @@
+Credits
+=======
+
+The data are coming from [OpenXC](http://openxcplatform.com/): [traces for developers](http://openxcplatform.com/resources/traces.html)
+
+The car is coming from [clipart.me](http://clipart.me): [Car Top View Vector Free](http://clipart.me/transportation/car-top-view-vector-free-26970)
+
diff --git a/CAN-binder/low-can-demo/gulpfile.js b/CAN-binder/low-can-demo/gulpfile.js
new file mode 100644
index 00000000..50dff813
--- /dev/null
+++ b/CAN-binder/low-can-demo/gulpfile.js
@@ -0,0 +1,484 @@
+// BUG Symlink not working
+
+var gulp = require('gulp');
+var debug = require('gulp-debug');
+var plugins = require('gulp-load-plugins')();
+var del = require('del');
+var es = require('event-stream');
+var bowerFiles = require('main-bower-files');
+var print = require('gulp-print');
+var Q = require('q');
+var imagemin = require('gulp-imagemin'), pngquant = require('imagemin-pngquant');
+var taskListing = require('gulp-task-listing');
+var symlink = require('gulp-sym');
+var rename = require("gulp-rename");
+var exec = require('child_process').exec
+var argv = require('yargs').argv;
+
+// == PATH STRINGS ========
+var appdir = "./app/"; // Warning to not forget trailling '/'
+config=require (appdir + "etc/_Config"); // upload user local preferences if any
+
+var frontend= appdir + config.FRONTEND;
+
+var paths = {
+ application : frontend,
+ scripts : frontend+'/**/*.js',
+ appStyles : [frontend+'/**/*.scss', '!'+frontend+'/styles/*.scss'],
+ globalStyles: [frontend+'/styles/*.scss'],
+ images : [
+ frontend+'/**/*.png',
+ frontend+'/**/*.jpg',
+ frontend+'/**/*.jpeg',
+ frontend+'/**/*.svg',
+ frontend+'/**/*.ttf',
+ 'bower_components/leaflet/dist/images/*.png'
+ ],
+ index : frontend+'/index.html',
+ partials : [frontend + '/**/*.html', '!' + frontend +'/index.html'],
+ distDev : './dist.dev',
+ distProd : './dist.prod',
+ sass: [
+ frontend+'/styles',
+ 'bower_components/bootstrap-sass/assets/stylesheets'
+ ],
+ fonts: ['bower_components/**/*.woff'],
+ favicon: frontend+'/images/favicon.ico',
+ wgtconfig: 'config.xml'
+};
+
+// add bower files to global styles
+bowerFiles('**/*.css').forEach(function(p) {
+ paths.globalStyles.unshift(p);
+});
+
+paths['distAppDev'] = paths.distDev + config.URLBASE;
+paths['distAppProd'] = paths.distProd + config.URLBASE;
+
+// Run node in debug mode in developpement mode ?
+var nodeopts = config.DEBUG !== undefined ? '--debug='+config.DEBUG : '';
+
+// == PIPE SEGMENTS ========
+var pipes = {};
+
+pipes.orderedVendorScripts = function() {
+ return plugins.order(['jquery.js', 'bootstrap.js','leaflet-src.js','tween.js','steelseries.js']);
+};
+
+pipes.minifiedFileName = function() {
+ return plugins.rename(function (path) {
+ path.extname = '.min' + path.extname;
+ });
+};
+
+pipes.validatedAppScripts = function() {
+ return gulp.src(paths.scripts)
+ .pipe(plugins.replace('@@APPNAME@@', config.APPNAME))
+ .pipe(plugins.replace('@@APPVER@@', config.APPVER))
+ .pipe(plugins.jshint())
+ .pipe(plugins.jshint.reporter('jshint-stylish'));
+};
+
+pipes.builtAppScriptsDev = function() {
+ return pipes.validatedAppScripts()
+ .pipe(gulp.dest(paths.distAppDev));
+};
+
+pipes.builtAppScriptsProd = function() {
+ var scriptedPartials = pipes.scriptedPartials();
+ var validatedAppScripts = pipes.validatedAppScripts();
+ return es.merge(scriptedPartials, validatedAppScripts)
+ .pipe(plugins.sourcemaps.init())
+ .pipe(plugins.concat(config.APPNAME+'.min.js'))
+ .pipe(plugins.uglify({compress: {drop_console: true}}))
+ .pipe(plugins.sourcemaps.write())
+ .pipe(gulp.dest(paths.distAppProd+'/js'));
+};
+
+pipes.builtVendorScriptsDev = function() {
+ return gulp.src(bowerFiles('**/*.js'))
+ .pipe(gulp.dest( paths.distDev +'/bower_components'));
+};
+
+pipes.builtVendorScriptsProd = function() {
+ return gulp.src(bowerFiles('**/*.js'))
+ .pipe(pipes.orderedVendorScripts())
+ .pipe(plugins.concat('vendor.min.js'))
+ .pipe(plugins.uglify())
+ .pipe(gulp.dest(paths.distProd+ '/bower_components'));
+};
+
+
+pipes.validatedPartials = function() {
+ return gulp.src(paths.partials)
+ .pipe(plugins.htmlhint({'doctype-first': false}))
+ .pipe(plugins.htmlhint.reporter());
+};
+
+pipes.builtPartialsDev = function() {
+ return pipes.validatedPartials()
+ .pipe(gulp.dest(paths.distAppDev));
+};
+
+pipes.scriptedPartials = function() {
+ return pipes.validatedPartials()
+ .pipe(plugins.htmlhint.failReporter())
+ .pipe(plugins.htmlmin({collapseWhitespace: true, removeComments: true}));
+};
+
+pipes.builtAppStylesDev = function() {
+ return gulp.src(paths.appStyles)
+ .pipe(plugins.sass({includePaths: paths.sass}))
+ .pipe(gulp.dest(paths.distAppDev + '/styles'));
+};
+
+pipes.builtglobalStylesDev = function() {
+ return gulp.src(paths.globalStyles)
+ .pipe(plugins.sass({includePaths: paths.sass}))
+ .pipe(gulp.dest(paths.distDev + '/global_styles'));
+};
+
+pipes.builtAppStylesProd = function() {
+ return gulp.src(paths.appStyles)
+ .pipe(plugins.sourcemaps.init())
+ .pipe(plugins.sass({includePaths: paths.sass}))
+ // .pipe(debug({title: '***** appStyle:'}))
+ .pipe(plugins.cleanCss())
+ .pipe(plugins.concat(config.APPNAME+'.css'))
+ .pipe(plugins.sourcemaps.write())
+ .pipe(pipes.minifiedFileName())
+ .pipe(gulp.dest(paths.distAppProd));
+};
+
+pipes.builtglobalStylesProd = function() {
+ return gulp.src(paths.globalStyles)
+ .pipe(plugins.sourcemaps.init())
+ .pipe(plugins.sass({includePaths: paths.sass}))
+ .pipe(plugins.cleanCss())
+ .pipe(plugins.sourcemaps.write())
+ .pipe(pipes.minifiedFileName())
+ .pipe(rename(function (path) {path.dirname="";return path;}))
+ .pipe(plugins.concat('output.min.css'))
+ .pipe(gulp.dest(paths.distProd + '/global_styles'));
+};
+
+pipes.processedFontsDev = function() {
+ return gulp.src(paths.fonts)
+ .pipe(rename(function (path) {path.dirname="";return path;}))
+ .pipe(gulp.dest(paths.distDev+'/bower_components'));
+};
+
+pipes.processedFontsProd = function() {
+ return gulp.src(paths.fonts)
+ .pipe(rename(function (path) {path.dirname="";return path;}))
+ .pipe(gulp.dest(paths.distProd+'/bower_components'));
+};
+
+
+pipes.processedImagesDev = function() {
+ return gulp.src(paths.images)
+ .pipe(rename(function(path) { path.dirname=""; return path; }))
+ .pipe(gulp.dest(paths.distAppDev+"/images/"));
+};
+
+pipes.processedFaviconDev = function() {
+ return gulp.src(paths.favicon)
+ .pipe(gulp.dest(paths.distDev));
+};
+
+pipes.processedImagesProd = function() {
+ return gulp.src(paths.images)
+ .pipe(rename(function(path) { path.dirname=""; return path; }))
+ .pipe(imagemin({
+ progressive: true,
+ svgoPlugins: [{removeViewBox: false}],
+ use: [pngquant()]
+ }))
+ .pipe(gulp.dest(paths.distAppProd+"/images/"));
+};
+
+pipes.processedFaviconProd = function() {
+ return gulp.src(paths.favicon)
+ .pipe(gulp.dest(paths.distProd));
+};
+
+// Create an Symlink when config.URLBASE exist
+pipes.createDevSymLink = function() {
+ return gulp.src(paths.distDev).pipe(symlink(paths.distDev+config.URLBASE, {force: true}));
+};
+
+pipes.createProdSymLink = function() {
+ return gulp.src(paths.distProd).pipe(symlink(paths.distDev+config.URLBASE,{force: true}));
+};
+
+pipes.validatedIndex = function() {
+ return gulp.src(paths.index)
+ .pipe(plugins.replace('@@APPNAME@@', config.APPNAME))
+ .pipe(plugins.replace('@@APPVER@@', config.APPVER))
+ .pipe(plugins.replace('@@URLBASE@@', config.URLBASE))
+ .pipe(plugins.htmlhint())
+ .pipe(plugins.htmlhint.reporter());
+};
+
+pipes.builtIndexDev = function() {
+
+ var orderedVendorScripts = pipes.builtVendorScriptsDev()
+ .pipe(pipes.orderedVendorScripts());
+
+ var orderedAppScripts = pipes.builtAppScriptsDev();
+
+ var appStyles = pipes.builtAppStylesDev();
+ var globalStyles = pipes.builtglobalStylesDev();
+
+ return pipes.validatedIndex()
+ // Vendor and Global should have absolute path to rootdir application one are relative to BaseURL
+ .pipe(plugins.inject(orderedVendorScripts, {relative: false, ignorePath: "/dist.dev", name: 'bower'}))
+ .pipe(plugins.inject(globalStyles, {relative: false, ignorePath: "/dist.dev", name:'vendor'}))
+ .pipe(gulp.dest(paths.distAppDev)) // write first to get relative path for inject
+ .pipe(plugins.inject(orderedAppScripts, {relative: true}))
+ .pipe(plugins.inject(appStyles, {relative: true, name: 'appli'}))
+ .pipe(gulp.dest(paths.distAppDev));
+};
+
+pipes.builtIndexProd = function() {
+
+ var vendorScripts= pipes.builtVendorScriptsProd();
+ var appScripts = pipes.builtAppScriptsProd();
+ var appStyles = pipes.builtAppStylesProd();
+ var globalStyles = pipes.builtglobalStylesProd();
+
+ return pipes.validatedIndex()
+ // Vendor and Global should have absolute path to rootdir application one are relative to BaseURL
+ .pipe(plugins.inject(vendorScripts, {relative: false, ignorePath: "/dist.prod", name: 'bower'}))
+ .pipe(plugins.inject(globalStyles, {relative: false, ignorePath: "/dist.prod", name:'vendor'}))
+ .pipe(gulp.dest(paths.distAppProd)) // write first to get relative path for inject
+ .pipe(plugins.inject(appScripts, {relative: true}))
+ .pipe(plugins.inject(appStyles, {relative: true, name:'appli'}))
+ .pipe(plugins.htmlmin({collapseWhitespace: true, removeComments: true}))
+ .pipe(gulp.dest(paths.distAppProd));
+};
+
+pipes.builtAppDev = function() {
+ return es.merge(pipes.builtIndexDev(), pipes.builtPartialsDev(), pipes.processedFaviconDev(), pipes.processedImagesDev(), pipes.processedFontsDev() )
+ .pipe(pipes.doRsync("Dev"));
+};
+
+pipes.builtAppProd = function() {
+ return es.merge(pipes.builtIndexProd(), pipes.processedFaviconProd(), pipes.processedImagesProd(), pipes.processedFontsProd())
+ .pipe(pipes.doRsync("Prod"));
+};
+
+pipes.widgetConfig = function(type) {
+ var dst=paths["dist"+type];
+ var content="."+config.URLBASE+"/index.html";
+ content=content.replace(/\/+/g,"/");
+ return gulp.src(paths.wgtconfig+".in")
+ .pipe(plugins.replace('@@APPNAME@@', config.APPNAME))
+ .pipe(plugins.replace('@@APPVER@@', config.APPVER))
+ .pipe(plugins.replace('@@CONTENT@@', content))
+ .pipe(plugins.rename("config.xml"))
+ .pipe(gulp.dest(dst))
+};
+
+pipes.widgetPack = function(type,cb) {
+ var dst=paths["dist"+type];
+ var wgtfile=config.APPNAME+"_"+type+".wgt"
+
+ exec(
+ "wgtpkg-pack -f -o "+wgtfile+" "+dst,
+ function(err,stdout,stderr) {
+ console.log(stdout);
+ console.log(stderr);
+ cb(err);
+ });
+};
+
+pipes.doRsync=function(type) {
+ var dst=paths["dist"+type];
+ if (!argv.host) return plugins.empty();
+
+ return plugins.rsync({
+ root: dst+"/",
+ hostname: argv.host,
+ username: "root",
+ destination: "/usr/share/afm/applications/"+config.APPNAME+"/"+config.APPVER+"/htdocs/",
+ archive: true,
+ compress: true,
+ recursive: true
+ });
+}
+
+// == TASKS ========
+
+// Add a task to render the output
+gulp.task('help', taskListing.withFilters(/-/));
+
+// clean, build of production environement
+gulp.task('build', ['clean-build-app-prod']);
+
+// removes all compiled dev files
+gulp.task('clean-dev', function() {
+ var deferred = Q.defer();
+ del(paths.distDev, function() {
+ deferred.resolve();
+ });
+ return deferred.promise;
+});
+
+// removes all compiled production files
+gulp.task('clean-prod', function() {
+ var deferred = Q.defer();
+ del(paths.distProd, function() {
+ deferred.resolve();
+ });
+ return deferred.promise;
+});
+
+// checks html source files for syntax errors
+gulp.task('validate-partials', pipes.validatedPartials);
+
+// checks index.html for syntax errors
+gulp.task('validate-index', pipes.validatedIndex);
+
+// moves html source files into the dev environment
+gulp.task('build-partials-dev', pipes.builtPartialsDev);
+
+// converts partials to javascript using html2js
+gulp.task('convert-partials-to-js', pipes.scriptedPartials);
+
+// runs jshint on the app scripts
+gulp.task('validate-app-scripts', pipes.validatedAppScripts);
+
+// moves app scripts into the dev environment
+gulp.task('build-app-scripts-dev', pipes.builtAppScriptsDev);
+
+// concatenates, uglifies, and moves app scripts and partials into the prod environment
+gulp.task('build-app-scripts-prod', pipes.builtAppScriptsProd);
+
+// compiles app sass and moves to the dev environment
+gulp.task('build-app-styles-dev', pipes.builtAppStylesDev);
+
+// compiles and minifies app sass to css and moves to the prod environment
+gulp.task('build-app-styles-prod', pipes.builtAppStylesProd);
+
+// moves vendor scripts into the dev environment
+gulp.task('build-vendor-scripts-dev', pipes.builtVendorScriptsDev);
+
+// concatenates, uglifies, and moves vendor scripts into the prod environment
+gulp.task('build-vendor-scripts-prod', pipes.builtVendorScriptsProd);
+
+// validates and injects sources into index.html and moves it to the dev environment
+gulp.task('build-index-dev', pipes.builtIndexDev);
+
+// validates and injects sources into index.html, minifies and moves it to the dev environment
+gulp.task('build-index-prod', pipes.builtIndexProd);
+
+// builds a complete dev environment
+gulp.task('build-app-dev', pipes.builtAppDev);
+
+// builds a complete prod environment
+gulp.task('build-app-prod', pipes.builtAppProd);
+
+// cleans and builds a complete dev environment
+gulp.task('clean-build-app-dev', ['clean-dev'], pipes.builtAppDev);
+
+// cleans and builds a complete prod environment
+gulp.task('clean-build-app-prod', ['clean-prod'], pipes.builtAppProd);
+
+// clean, build, and watch live changes to the dev environment
+gulp.task('watch-dev', ['clean-build-app-dev'], function() {
+
+ // watch index
+ gulp.watch(paths.index, function() {
+ return pipes.builtIndexDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch app scripts
+ gulp.watch(paths.scripts, function() {
+ return pipes.builtAppScriptsDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch html partials
+ gulp.watch(paths.partials, function() {
+ return pipes.builtPartialsDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch Images
+ gulp.watch(paths.images, function() {
+ return pipes.processedImagesDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch styles
+ gulp.watch(paths.appStyles, function() {
+ return pipes.builtAppStylesDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+ gulp.watch(paths.globalStyles, function() {
+ return pipes.builtglobalStylesDev()
+ .pipe(pipes.doRsync("Dev"))
+ .pipe(plugins.livereload());
+ });
+
+});
+
+// clean, build, and watch live changes to the prod environment
+gulp.task('watch-prod', ['clean-build-app-prod'], function() {
+
+ // watch index
+ gulp.watch(paths.index, function() {
+ return pipes.builtIndexProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch app scripts
+ gulp.watch(paths.scripts, function() {
+ return pipes.builtAppScriptsProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch hhtml partials
+ gulp.watch(paths.partials, function() {
+ return pipes.builtAppScriptsProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch Images
+ gulp.watch(paths.images, function() {
+ return pipes.processedImagesProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+
+ // watch styles
+ gulp.watch(paths.appStyles, function() {
+ return pipes.builtAppStylesProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+ gulp.watch(paths.globalStyles, function() {
+ return pipes.builtglobalStylesProd()
+ .pipe(pipes.doRsync("Prod"))
+ .pipe(plugins.livereload());
+ });
+
+});
+
+gulp.task('widget-config-dev', ['build-app-dev'], function() { return pipes.widgetConfig("Dev"); });
+gulp.task('widget-config-prod', ['build-app-prod'], function() { return pipes.widgetConfig("Prod"); });
+gulp.task('widget-dev', ['widget-config-dev'], function(cb) { return pipes.widgetPack("Dev",cb); });
+gulp.task('widget-prod', ['widget-config-prod'], function(cb) { return pipes.widgetPack("Prod",cb); });
+
+// default task builds for prod
+gulp.task('default', ['widget-prod']);
diff --git a/CAN-binder/low-can-demo/icon_hybrid_html5_128.png b/CAN-binder/low-can-demo/icon_hybrid_html5_128.png
new file mode 100644
index 00000000..bf39b231
--- /dev/null
+++ b/CAN-binder/low-can-demo/icon_hybrid_html5_128.png
Binary files differ
diff --git a/CAN-binder/low-can-demo/package.json b/CAN-binder/low-can-demo/package.json
new file mode 100644
index 00000000..87864734
--- /dev/null
+++ b/CAN-binder/low-can-demo/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "low-can-demo",
+ "private": true,
+ "version": "0.0.1",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/iotbzh/app-framework-templates"
+ },
+ "scripts": {
+ "postinstall": "bower install",
+ "build": "./node_modules/.bin/gulp clean-build-app-dev",
+ "start": "node node_modules/.bin/gulp watch-dev"
+ },
+ "devDependencies": {
+ "body-parser": "^1.5.2",
+ "bower": "^1.3.1",
+ "cookie-parser": "^1.4.0",
+ "del": "^1.1.1",
+ "event-stream": "^3.2.2",
+ "express": "^4.7.2",
+ "express-session": "^1.12.1",
+ "gulp": "^3.9.0",
+ "gulp-debug": "^2.1.2",
+ "gulp-concat": "^2.4.3",
+ "gulp-empty": "^0.1.1",
+ "gulp-htmlhint": "0.0.9",
+ "gulp-htmlmin": "^1.0.0",
+ "gulp-imagemin": "*",
+ "gulp-inject": "^1.1.1",
+ "gulp-jshint": "^1.9.2",
+ "gulp-livereload": "^3.7.0",
+ "gulp-load-plugins": "^0.8.0",
+ "gulp-clean-css": "^2.0.11",
+ "gulp-nodemon": "^2.0.0",
+ "gulp-order": "^1.1.1",
+ "gulp-print": "^1.1.0",
+ "gulp-rename": "^1.2.0",
+ "gulp-replace": "^0.5.4",
+ "gulp-rsync": "^0.0.6",
+ "gulp-sass": "^2.0.3",
+ "gulp-sourcemaps": "^1.3.0",
+ "gulp-task-listing": "^1.0.1",
+ "gulp-uglify": "^1.2.0",
+ "imagemin-pngquant": "*",
+ "jshint-stylish": "^1.0.0",
+ "karma": "^0.10",
+ "karma-junit-reporter": "^0.2.2",
+ "main-bower-files": "^2.5.0",
+ "method-override": "^2.1.2",
+ "protractor": "^1.1.1",
+ "q": "^1.1.2",
+ "shelljs": "^0.2.6",
+ "traceback": "^0.3.1",
+ "gulp-sym": "0.0.14",
+ "yargs":"5.0.0"
+ },
+ "dependencies": {
+ "multer": "^1.1.0"
+ }
+}