فهرست منبع

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
 Windows, Mac OS X and Linux.
 
 This tool flashes (installs) the Espruino JavaScript run time on ESP8266
 EP-12 devices like the Adafruit Huzzah and Adadfruit Feather Huzzah.
 
+-------
+
+## Run the GUI
+
+```bash
+npm install
+npm start
+```
+
+-------
 
 ## ROM Communication
+
 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.
@@ -42,7 +53,7 @@ We are using [Bunyan](https://github.com/trentm/node-bunyan) for logging, make s
 
 ## 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.
 
 ```bash

+ 2 - 0
back-end/boards.js

@@ -31,6 +31,7 @@ const FLASH_SIZES = {
 class EspBoard {
     constructor(port) {
         this.port = port;
+        this.isInBootLoader = false;
     }
 
     portSet(options) {
@@ -82,6 +83,7 @@ class Esp12 extends EspBoard {
             .then(() => this.portSet({rts: false, dtr: true}))
             .then(() => delay(50))
             .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 boards = require("./boards");
 const delay = require("./utilities").delay;
-const repeatPromise = require("./utilities").repeatPromise;
+const retryPromiseUntil = require("./utilities").retryPromiseUntil;
 const promiseChain = require("./utilities").promiseChain;
 
 
@@ -27,6 +27,7 @@ const commands = {
 };
 
 const validateCommandSuccess = [
+    commands.SYNC_FRAME,
     commands.FLASH_DOWNLOAD_BEGIN,
     commands.FLASH_DOWNLOAD_DATA,
     commands.FLASH_DOWNLOAD_DONE
@@ -45,6 +46,7 @@ const SYNC_FRAME = new Buffer([0x07, 0x07, 0x12, 0x20,
 
 const FLASH_BLOCK_SIZE = 0x400;
 const SUCCESS = new Buffer([0x00, 0x00]);
+const REQUIRED_SUCCESSFUL_SYNC_COUNT = 10;
 
 /**
  * An abstraction of talking to the ever so finicky ESP8266 ROM.
@@ -71,7 +73,6 @@ class RomComm {
         }
         this.board = new BoardFactory(this._port);
         this.config = config;
-        this.isInBootLoader = false;
     }
 
     bindPort() {
@@ -163,7 +164,7 @@ class RomComm {
             .then((response) => {
                 if (!ignoreResponse) {
                     log.info("Sync response completed!", response);
-                    this.isInBootLoader = true;
+                    this.board.isInBootLoader = true;
                 }
             });
     }
@@ -171,29 +172,14 @@ class RomComm {
     connect() {
         // Eventually responses calm down, but on initial contact, responses are not standardized.
         // 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() {
         return this.board.resetIntoBootLoader()
                 .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() {
@@ -209,6 +195,19 @@ class RomComm {
             }).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
      * 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))
             .then((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 message = Buffer.concat([sendHeader, data], sendHeader.length + data.length);
                 this.out.write(message, 'buffer', (err, res) => {
-                    delay(5).then(() => {
+                    delay(10).then(() => {
                         if (ignoreResponse) {
                             resolve("Response was ignored");
                         }
                         if (this.portIsOpen) {
                             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) {
     return new Promise((resolve) => {
         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}
  */
-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 = {
     delay: delay,
-    repeatPromise: repeatPromise,
+    retryPromiseUntil: retryPromiseUntil,
     promiseChain: promiseChain
 };

+ 9 - 3
front-end/index.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <title>Especially Flasher</title>
+    <title>Flasher.js</title>
 
     <!-- Stylesheets -->
     <link rel="stylesheet" href="css/photon.min.css">
@@ -10,14 +10,20 @@
 <div class="window">
 
     <header class="toolbar toolbar-header">
-        <h1 class="title">Especially Flasher</h1>
+        <h1 class="title">Flasher.js</h1>
     </header>
 
     <div class="window-content">
         <div class="padded-more">
             <label for="ports">Select Port:</label>
             <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>
         </div>
     </div>

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

@@ -1,22 +1,32 @@
 "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
 const SerialScanner = require("../back-end/serial_scanner");
 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); }
 
 const flashButton = $("flash-button");
 const appStatus = $("status");
 const portsSelect = new PortSelect($("ports"));
+const manifestsSelect = $("manifests");
 const serialScanner = new SerialScanner();
 const pollTime = 1000; // One second
 
 var last_notification = "";
 
 flashButton.addEventListener("click", event => {
-    var notification = new Notification("Flash Finished!");
+    fetch(manifestsSelect.value)
+        .then(processJSON)
+        .then(flashWithManifest);
 });
 
 serialScanner.on("ports", (ports) => {
@@ -64,12 +74,70 @@ function onError(error){
     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
  */
 function init() {
+    getManifests();
     serialScanner.scan();
     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) {
         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() {
   // Create the browser window.
   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-height': 550,
     'accept-first-mouse': true,

+ 7 - 5
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "especially-flasher",
+  "name": "flasher.js",
   "version": "1.0.0",
   "description": "A GUI tool for flashing the Espruino JavaScript runtime on Adafruit Huzzah (ESP8366 based) boards",
   "main": "index.js",
@@ -9,7 +9,7 @@
   },
   "repository": {
     "type": "git",
-    "url": "git+ssh://git@github.com/thingsSDK/especially-flasher.git"
+    "url": "git+ssh://git@github.com/thingsSDK/flasher.js.git"
   },
   "keywords": [
     "espruino",
@@ -21,13 +21,15 @@
   "author": "Andrew Chalkley <andrew@chalkley.org>",
   "license": "MIT",
   "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": {
     "bunyan": "^1.8.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": {
     "electron-packager": "^6.0.0",