浏览代码

Merge branch 'master' into auto-updater

craigsdennis 9 年之前
父节点
当前提交
db92c9b97f
共有 10 个文件被更改,包括 208 次插入57 次删除
  1. 14 3
      README.md
  2. 2 0
      back-end/boards.js
  3. 47 0
      back-end/prepare_binaries.js
  4. 23 24
      back-end/rom_comm.js
  5. 27 15
      back-end/utilities.js
  6. 9 3
      front-end/index.html
  7. 71 3
      front-end/js/app.js
  8. 4 0
      front-end/js/port_select.js
  9. 4 4
      index.js
  10. 7 5
      package.json

+ 14 - 3
README.md

@@ -1,14 +1,25 @@
-# Especially Flasher
+# Flasher.js
 
 
-__Especially Flasher_ is a tool to get JavaScript running natively on
+_Flasher.js_ is a tool to get JavaScript running natively on
 the Internet of Things device, ESP8266. This application runs on
 the Internet of Things device, ESP8266. This application runs on
 Windows, Mac OS X and Linux.
 Windows, Mac OS X and Linux.
 
 
 This tool flashes (installs) the Espruino JavaScript run time on ESP8266
 This tool flashes (installs) the Espruino JavaScript run time on ESP8266
 EP-12 devices like the Adafruit Huzzah and Adadfruit Feather Huzzah.
 EP-12 devices like the Adafruit Huzzah and Adadfruit Feather Huzzah.
 
 
+-------
+
+## Run the GUI
+
+```bash
+npm install
+npm start
+```
+
+-------
 
 
 ## ROM Communication
 ## ROM Communication
+
 The ESP8266 is notoriously finicky about being flashed, we've done our best to abstract that for you.
 The ESP8266 is notoriously finicky about being flashed, we've done our best to abstract that for you.
 
 
 Here is an example of flashing the ESP8266 with the latest Espruino build.
 Here is an example of flashing the ESP8266 with the latest Espruino build.
@@ -42,7 +53,7 @@ We are using [Bunyan](https://github.com/trentm/node-bunyan) for logging, make s
 
 
 ## Contributing
 ## Contributing
 
 
-If you want to contribute to the Especially Flasher clone this repo and
+If you want to contribute to the Flasher.js clone this repo and
  run the following commands.
  run the following commands.
 
 
 ```bash
 ```bash

+ 2 - 0
back-end/boards.js

@@ -31,6 +31,7 @@ const FLASH_SIZES = {
 class EspBoard {
 class EspBoard {
     constructor(port) {
     constructor(port) {
         this.port = port;
         this.port = port;
+        this.isInBootLoader = false;
     }
     }
 
 
     portSet(options) {
     portSet(options) {
@@ -82,6 +83,7 @@ class Esp12 extends EspBoard {
             .then(() => this.portSet({rts: false, dtr: true}))
             .then(() => this.portSet({rts: false, dtr: true}))
             .then(() => delay(50))
             .then(() => delay(50))
             .then(() => this.portSet({rts: false, dtr: false}));
             .then(() => this.portSet({rts: false, dtr: false}));
+
     }
     }
 }
 }
 
 

+ 47 - 0
back-end/prepare_binaries.js

@@ -0,0 +1,47 @@
+"use strict";
+const http = require("http");
+const unzip = require("unzip");
+const fs = require("fs");
+
+function isBinaryFileRequired(flashSpecification, fileName) {
+    return flashSpecification.map(binary => binary.path).indexOf(fileName) !== -1;
+}
+
+function addBufferToBinary(flashSpecification, fileName, buffer) {
+    flashSpecification.forEach((element, index) => {
+        if (flashSpecification[index].path === fileName) {
+            flashSpecification[index].buffer = buffer;
+        }
+    });
+}
+
+
+function prepareBinaries(manifest, callback) {
+    const flashContents = manifest.flash;
+    const downloadRequest = http.get(manifest.download, (response) => {
+        response.pipe(unzip.Parse()).on('entry', (entry) => {
+            const fileName = entry.path;
+            if (isBinaryFileRequired(flashContents, fileName)) {
+                let body;
+                entry.on("data", function(data){
+                    if(body) {
+                        body = Buffer.concat([body, data])
+                    } else {
+                        body = data;
+                    }
+                }).on("end", () => {
+                    addBufferToBinary(flashContents, fileName, body);
+                }).on("error", callback);
+
+            } else {
+                entry.autodrain();
+            }
+        }).on("close", () => {
+            console.log("close");
+            callback(null, flashContents);
+        });
+        response.on("error", callback);
+    });
+}
+
+module.exports = prepareBinaries;

+ 23 - 24
back-end/rom_comm.js

@@ -6,7 +6,7 @@ const log = require("./logger");
 const slip = require("./streams/slip");
 const slip = require("./streams/slip");
 const boards = require("./boards");
 const boards = require("./boards");
 const delay = require("./utilities").delay;
 const delay = require("./utilities").delay;
-const repeatPromise = require("./utilities").repeatPromise;
+const retryPromiseUntil = require("./utilities").retryPromiseUntil;
 const promiseChain = require("./utilities").promiseChain;
 const promiseChain = require("./utilities").promiseChain;
 
 
 
 
@@ -27,6 +27,7 @@ const commands = {
 };
 };
 
 
 const validateCommandSuccess = [
 const validateCommandSuccess = [
+    commands.SYNC_FRAME,
     commands.FLASH_DOWNLOAD_BEGIN,
     commands.FLASH_DOWNLOAD_BEGIN,
     commands.FLASH_DOWNLOAD_DATA,
     commands.FLASH_DOWNLOAD_DATA,
     commands.FLASH_DOWNLOAD_DONE
     commands.FLASH_DOWNLOAD_DONE
@@ -45,6 +46,7 @@ const SYNC_FRAME = new Buffer([0x07, 0x07, 0x12, 0x20,
 
 
 const FLASH_BLOCK_SIZE = 0x400;
 const FLASH_BLOCK_SIZE = 0x400;
 const SUCCESS = new Buffer([0x00, 0x00]);
 const SUCCESS = new Buffer([0x00, 0x00]);
+const REQUIRED_SUCCESSFUL_SYNC_COUNT = 10;
 
 
 /**
 /**
  * An abstraction of talking to the ever so finicky ESP8266 ROM.
  * An abstraction of talking to the ever so finicky ESP8266 ROM.
@@ -71,7 +73,6 @@ class RomComm {
         }
         }
         this.board = new BoardFactory(this._port);
         this.board = new BoardFactory(this._port);
         this.config = config;
         this.config = config;
-        this.isInBootLoader = false;
     }
     }
 
 
     bindPort() {
     bindPort() {
@@ -163,7 +164,7 @@ class RomComm {
             .then((response) => {
             .then((response) => {
                 if (!ignoreResponse) {
                 if (!ignoreResponse) {
                     log.info("Sync response completed!", response);
                     log.info("Sync response completed!", response);
-                    this.isInBootLoader = true;
+                    this.board.isInBootLoader = true;
                 }
                 }
             });
             });
     }
     }
@@ -171,29 +172,14 @@ class RomComm {
     connect() {
     connect() {
         // Eventually responses calm down, but on initial contact, responses are not standardized.
         // Eventually responses calm down, but on initial contact, responses are not standardized.
         // This tries until break through is made.
         // This tries until break through is made.
-        return repeatPromise(5, () => {
-            if (this.isInBootLoader) {
-                log.info("In Bootloader not re-connecting");
-                return true;
-            } else {
-                return this._connectAttempt();
-            }
-        }).then(() => this.sync())
-        .then(() => this.isInBootLoader);
+        this._listenForSuccessfulSync();
+        return retryPromiseUntil(() => this._connectAttempt(), () => this.board.isInBootLoader);
     }
     }
 
 
     _connectAttempt() {
     _connectAttempt() {
         return this.board.resetIntoBootLoader()
         return this.board.resetIntoBootLoader()
                 .then(() => delay(100))
                 .then(() => delay(100))
-                // And a 5x loop here
-                .then(() => repeatPromise(5, () => {
-                    if (this.isInBootLoader) {
-                        log.info("In Bootloader not syncing");
-                        return true;
-                    } else {
-                        return this._flushAndSync();
-                    }
-                }));
+                .then(() => retryPromiseUntil(() => this._flushAndSync(), () => this.board.isInBootLoader, 10));
     }
     }
 
 
     _flushAndSync() {
     _flushAndSync() {
@@ -209,6 +195,19 @@ class RomComm {
             }).then(() => this.sync(true));
             }).then(() => this.sync(true));
     }
     }
 
 
+    _listenForSuccessfulSync() {
+        let commandName = commandToKey(commands.SYNC_FRAME);
+        let successfulSyncs = 0;
+        this.in.on(commandName, (response) => {
+            successfulSyncs++;
+            if (successfulSyncs >= REQUIRED_SUCCESSFUL_SYNC_COUNT) {
+                log.info("Got enough successful syncs");
+                this.board.isInBootLoader = true;
+                this.in.removeAllListeners(commandName);
+            }
+        });
+    }
+
     /**
     /**
      * Send appropriate C struct header along with command as required
      * Send appropriate C struct header along with command as required
      * SEE:  https://github.com/igrr/esptool-ck/blob/master/espcomm/espcomm.h#L49
      * SEE:  https://github.com/igrr/esptool-ck/blob/master/espcomm/espcomm.h#L49
@@ -345,7 +344,7 @@ class RomComm {
         return this.sendCommand(commands.FLASH_DOWNLOAD_DONE, new Buffer(buffer))
         return this.sendCommand(commands.FLASH_DOWNLOAD_DONE, new Buffer(buffer))
             .then((result) => {
             .then((result) => {
                 log.info("Received result", result);
                 log.info("Received result", result);
-                this.isInBootLoader = false;
+                this.board.isInBootLoader = false;
             });
             });
     }
     }
 
 
@@ -361,13 +360,13 @@ class RomComm {
                 let sendHeader = this.headerPacketFor(command, data);
                 let sendHeader = this.headerPacketFor(command, data);
                 let message = Buffer.concat([sendHeader, data], sendHeader.length + data.length);
                 let message = Buffer.concat([sendHeader, data], sendHeader.length + data.length);
                 this.out.write(message, 'buffer', (err, res) => {
                 this.out.write(message, 'buffer', (err, res) => {
-                    delay(5).then(() => {
+                    delay(10).then(() => {
                         if (ignoreResponse) {
                         if (ignoreResponse) {
                             resolve("Response was ignored");
                             resolve("Response was ignored");
                         }
                         }
                         if (this.portIsOpen) {
                         if (this.portIsOpen) {
                             this._port.drain((drainErr, results) => {
                             this._port.drain((drainErr, results) => {
-                                log.info("Draining", drainErr, results);
+                                log.info("Draining after write", drainErr, results);
                             });
                             });
                         }
                         }
                     });
                     });

+ 27 - 15
back-end/utilities.js

@@ -11,26 +11,38 @@ const log = require("./logger");
 function delay(time) {
 function delay(time) {
     return new Promise((resolve) => {
     return new Promise((resolve) => {
         log.info("Delaying for %d ms", time);
         log.info("Delaying for %d ms", time);
-        setTimeout(resolve, time);
+        setTimeout(() => resolve(time), time);
     });
     });
 }
 }
 
 
 /**
 /**
- * Repeats a promise a given number of times.
- * @param times. The number of times to repeat a given promise.
- * @param callback is a no parameter based function that returns a {Promise}.
+ * Repeats a promise until a condition is met, or `maxAttempts` have occurred
+ * @param callback This is a function that should return the promise to repeat
+ * @param checkFn A function that will run on each go, truthy values will stop the loop
+ * @param maxAttempts [OPTIONAL] Number of times this should loop.
  * @returns {Promise}
  * @returns {Promise}
  */
  */
-function repeatPromise(times, callback) {
-    let chain = Promise.resolve();
-    // Range just used for closure based loop
-    let range = new Array(times)
-        .fill(0)
-        .map((value, index) => index);
-    range.forEach(() => {
-        chain = chain.then(() => callback());
-    });
-    return chain;
+function retryPromiseUntil(callback, checkFn, maxAttempts) {
+    if (!callback.hasOwnProperty("attemptCount")) {
+        callback.attemptCount = 0;
+    }
+
+    let result = checkFn();
+    log.debug("Retrying promise...");
+    if (result) {
+        log.info("Check function returned", result);
+        return result;
+    }
+    callback.attemptCount++;
+    log.debug("Performing attempt", callback.attemptCount);
+    if (maxAttempts && callback.attemptCount > maxAttempts) {
+        log.warn("Max attempts reached exiting");
+        return;
+    }
+    // Recursively return the promise
+    return Promise.resolve()
+        .then(() => callback())
+        .then(() => retryPromiseUntil(callback, checkFn, maxAttempts));
 }
 }
 
 
 /**
 /**
@@ -47,6 +59,6 @@ function promiseChain(promiseFunctions) {
 
 
 module.exports = {
 module.exports = {
     delay: delay,
     delay: delay,
-    repeatPromise: repeatPromise,
+    retryPromiseUntil: retryPromiseUntil,
     promiseChain: promiseChain
     promiseChain: promiseChain
 };
 };

+ 9 - 3
front-end/index.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html>
 <html>
 <head>
 <head>
-    <title>Especially Flasher</title>
+    <title>Flasher.js</title>
 
 
     <!-- Stylesheets -->
     <!-- Stylesheets -->
     <link rel="stylesheet" href="css/photon.min.css">
     <link rel="stylesheet" href="css/photon.min.css">
@@ -10,14 +10,20 @@
 <div class="window">
 <div class="window">
 
 
     <header class="toolbar toolbar-header">
     <header class="toolbar toolbar-header">
-        <h1 class="title">Especially Flasher</h1>
+        <h1 class="title">Flasher.js</h1>
     </header>
     </header>
 
 
     <div class="window-content">
     <div class="window-content">
         <div class="padded-more">
         <div class="padded-more">
             <label for="ports">Select Port:</label>
             <label for="ports">Select Port:</label>
             <select title="Select a Serial/COM Port" id="ports" name="ports" class="form-control" disabled>
             <select title="Select a Serial/COM Port" id="ports" name="ports" class="form-control" disabled>
-
+            
+            </select>
+        </div>
+        <div class="padded-more">
+            <label for="manifests">Select Binaries to Flash:</label>
+            <select title="Select Binaries to Flash" id="manifests" name="manifests" class="form-control" disabled>
+            
             </select>
             </select>
         </div>
         </div>
     </div>
     </div>

+ 71 - 3
front-end/js/app.js

@@ -1,22 +1,32 @@
 "use strict";
 "use strict";
 
 
-const os = require('os').platform();
+
+const URLS = {
+    manifestList: "http://flasher.thingssdk.com/v1/manifest-list.json"
+};
+
 //Relative to index.html not app.js
 //Relative to index.html not app.js
 const SerialScanner = require("../back-end/serial_scanner");
 const SerialScanner = require("../back-end/serial_scanner");
 const PortSelect = require("./js/port_select");
 const PortSelect = require("./js/port_select");
+const prepareBinaries = require("../back-end/prepare_binaries");
+const log = require("../back-end/logger");
+const RomComm = require("../back-end/rom_comm");
 
 
 function $(id) { return document.getElementById(id); }
 function $(id) { return document.getElementById(id); }
 
 
 const flashButton = $("flash-button");
 const flashButton = $("flash-button");
 const appStatus = $("status");
 const appStatus = $("status");
 const portsSelect = new PortSelect($("ports"));
 const portsSelect = new PortSelect($("ports"));
+const manifestsSelect = $("manifests");
 const serialScanner = new SerialScanner();
 const serialScanner = new SerialScanner();
 const pollTime = 1000; // One second
 const pollTime = 1000; // One second
 
 
 var last_notification = "";
 var last_notification = "";
 
 
 flashButton.addEventListener("click", event => {
 flashButton.addEventListener("click", event => {
-    var notification = new Notification("Flash Finished!");
+    fetch(manifestsSelect.value)
+        .then(processJSON)
+        .then(flashWithManifest);
 });
 });
 
 
 serialScanner.on("ports", (ports) => {
 serialScanner.on("ports", (ports) => {
@@ -64,12 +74,70 @@ function onError(error){
     appStatus.textContent = error.message;
     appStatus.textContent = error.message;
 }
 }
 
 
+function processJSON(response) {
+    return response.json();
+}
+
+function generateManifestList(manifestsJSON) {
+    console.log(manifestsJSON);
+    manifestsJSON.options.forEach((option) => {
+        option.versions.forEach((version) => {
+            const optionElement = document.createElement("option");
+            optionElement.textContent = `${option.name} - ${version.version}`;
+            optionElement.value = version.manifest;
+            manifestsSelect.appendChild(optionElement);
+            manifestsSelect.disabled = false;
+        });
+    });
+}
+
+function getManifests() {
+    fetch(URLS.manifestList)
+        .then(processJSON)
+        .then(generateManifestList).catch((error) => {
+            console.log(error);
+            setTimeout(getManifests, pollTime);
+        });
+}
+
 /**
 /**
  * Sets up UI
  * Sets up UI
  */
  */
 function init() {
 function init() {
+    getManifests();
     serialScanner.scan();
     serialScanner.scan();
     setInterval(serialScanner.checkForChanges.bind(serialScanner), pollTime);
     setInterval(serialScanner.checkForChanges.bind(serialScanner), pollTime);
 }
 }
 
 
-init();
+init();
+
+function flashWithManifest(manifest) {
+    console.log(portsSelect.value);
+    prepareBinaries(manifest, (err, flashSpec) => {
+        if(err) throw err;
+
+        const esp = new RomComm({
+            portName: portsSelect.value,
+            baudRate: 115200
+        });
+
+        esp.open().then((result) => {
+            log.info("ESP is open", result);
+            let promise = Promise.resolve();
+
+            flashSpec.forEach((spec) => {
+               promise = promise.then(()=> {
+                   return esp.flashAddress(Number.parseInt(spec.address), spec.buffer)
+               });
+            });
+
+            return promise.then(() => esp.close())
+                .then((result) => {
+                    var notification = new Notification("Flash Finished!");
+                    log.info("Flashed to latest Espruino build!", result);
+                });
+        }).catch((error) => {
+            log.error("Oh noes!", error);
+        });
+    });
+}

+ 4 - 0
front-end/js/port_select.js

@@ -58,4 +58,8 @@ module.exports = class PortSelect {
     set disabled (value) {
     set disabled (value) {
         this.selectElement.disabled = value;
         this.selectElement.disabled = value;
     }
     }
+    
+    get value() {
+        return this.selectElement.value;
+    }
 };
 };

+ 4 - 4
index.js

@@ -29,10 +29,10 @@ autoUpdater.on("checking-for-update", () => {
 app.on('ready', function() {
 app.on('ready', function() {
   // Create the browser window.
   // Create the browser window.
   mainWindow = new BrowserWindow({
   mainWindow = new BrowserWindow({
-    width: 256,
-    height: 200,
-    'min-width': 256,
-    'min-height': 200,
+    width: 450,
+    height: 160,
+    'min-width': 450,
+    'min-height': 160,
     'max-width': 500,
     'max-width': 500,
     'max-height': 550,
     'max-height': 550,
     'accept-first-mouse': true,
     'accept-first-mouse': true,

+ 7 - 5
package.json

@@ -1,5 +1,5 @@
 {
 {
-  "name": "especially-flasher",
+  "name": "flasher.js",
   "version": "1.0.0",
   "version": "1.0.0",
   "description": "A GUI tool for flashing the Espruino JavaScript runtime on Adafruit Huzzah (ESP8366 based) boards",
   "description": "A GUI tool for flashing the Espruino JavaScript runtime on Adafruit Huzzah (ESP8366 based) boards",
   "main": "index.js",
   "main": "index.js",
@@ -9,7 +9,7 @@
   },
   },
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",
-    "url": "git+ssh://git@github.com/thingsSDK/especially-flasher.git"
+    "url": "git+ssh://git@github.com/thingsSDK/flasher.js.git"
   },
   },
   "keywords": [
   "keywords": [
     "espruino",
     "espruino",
@@ -21,13 +21,15 @@
   "author": "Andrew Chalkley <andrew@chalkley.org>",
   "author": "Andrew Chalkley <andrew@chalkley.org>",
   "license": "MIT",
   "license": "MIT",
   "bugs": {
   "bugs": {
-    "url": "https://github.com/thingsSDK/especially-flasher/issues"
+    "url": "https://github.com/thingsSDK/flasher.js/issues"
   },
   },
-  "homepage": "https://github.com/thingsSDK/especially-flasher#readme",
+  "homepage": "https://github.com/thingsSDK/flasher.js#readme",
   "dependencies": {
   "dependencies": {
     "bunyan": "^1.8.0",
     "bunyan": "^1.8.0",
     "node-binary": "^1.1.0",
     "node-binary": "^1.1.0",
-    "serialport": "^2.0.7-beta1"
+    "serialport": "^2.0.7-beta1",
+    "tar.gz": "^1.0.3",
+    "unzip": "^0.1.11"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "electron-packager": "^6.0.0",
     "electron-packager": "^6.0.0",