Add multiple TPU/CPU models

Change-Id: I4182961367e090b507f7e04850d12901a303bcf6
diff --git a/Makefile b/Makefile
index bd21562..9729a88 100644
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 MAKEFILE_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
+TEST_DATA_URL := https://github.com/google-coral/edgetpu/raw/master/test_data
 
 .PHONY: wasm download zip server dfu-util
 
@@ -48,11 +49,15 @@
         "$(MAKEFILE_DIR)/bazel-bin/tflite/interpreter-wasm/interpreter.worker.js" \
         "$(MAKEFILE_DIR)/site"
 
-download:
-	cd $(MAKEFILE_DIR)/site && \
-    rm -f *.tflite* && \
-    wget https://github.com/google-coral/test_data/raw/master/mobilenet_v1_1.0_224_quant.tflite \
-         https://github.com/google-coral/test_data/raw/master/mobilenet_v1_1.0_224_quant_edgetpu.tflite
+%.tflite:
+	cd $(dir $@) && wget "$(TEST_DATA_URL)/$(notdir $@)"
+
+download: site/mobilenet_v1_1.0_224_quant.tflite \
+          site/mobilenet_v1_1.0_224_quant_edgetpu.tflite \
+          site/ssd_mobilenet_v2_coco_quant_postprocess.tflite \
+          site/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite \
+          site/ssd_mobilenet_v2_face_quant_postprocess.tflite \
+          site/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite
 
 dfu-util:
 	git clone git://git.code.sf.net/p/dfu-util/dfu-util || true
diff --git a/README.md b/README.md
index a7bd081..558a81a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,8 @@
 #  WebCoral
 
-emscripten_toolchain is copied from
+Firmware upload is done via webdfu
+
+emscripten_toolchain for Bazel is taken from
 https://github.com/emscripten-core/emsdk/tree/master/bazel/emscripten_toolchain
+
+libusb.cc -- universal shim, inspired by
diff --git a/site/index.html b/site/index.html
index fba98ca..e8aa5a2 100644
--- a/site/index.html
+++ b/site/index.html
@@ -5,23 +5,64 @@
     <title>Coral USB Accelerator Demo</title>
     <script src="dfu.js"></script>
     <script src="dfuse.js"></script>
-    <script src="imagenet_labels.js"></script>
+    <script src="labels.js"></script>
     <script src="tflite.js"></script>
     <script src="interpreter.js"></script>
   </head>
   <body>
     <h1>Coral USB Accelerator Demo</h1>
     <button id="button-firmware">1. Flash Device Firmware</button>&nbsp;<span id="firmware-status"></span><br/><br/>
-    <button id="button-init">2. Initialize Device</button><input type="checkbox" id="cpu-model">CPU</input><br/><br/>
+    <button id="button-init">2. Initialize Device</button>
+    <select id="model">
+      <option value="mobilenet_cpu">MobileNet V1 (CPU)</option>
+      <option value="mobilenet_tpu">MobileNet V1 (TPU)</option>
+      <option value="ssd_mobilenet_coco_cpu">SSD MobileNet V2 - Coco (CPU)</option>
+      <option value="ssd_mobilenet_coco_tpu">SSD MobileNet V2 - Coco (TPU)</option>
+      <option value="ssd_mobilenet_face_cpu">SSD MobileNet V2 - Face (CPU)</option>
+      <option value="ssd_mobilenet_face_tpu">SSD MobileNet V2 - Face (TPU)</option>
+    </select><br/><br/>
     <button id="button-image" onclick="document.getElementById('file').click();" disabled>3. Choose Image File</button><br/><br/>
     <input type="file" id="file" style="display:none;"/><br/>
-    <canvas id="canvas"></canvas>
     <h2 id="result"></h2>
+    <canvas id="canvas"></canvas>
     <br/><br/>
 
     <!--button id="test">Inference Test</button><br/><br/-->
 
     <script>
+      const models = {
+        'mobilenet_cpu': {
+          'url': '/mobilenet_v1_1.0_224_quant.tflite',
+          'type': 'classification',
+          'labels': imagenet_labels,
+        },
+        'mobilenet_tpu': {
+          'url': '/mobilenet_v1_1.0_224_quant_edgetpu.tflite',
+          'type': 'classification',
+          'labels': imagenet_labels,
+        },
+        'ssd_mobilenet_coco_cpu': {
+          'url': '/ssd_mobilenet_v2_coco_quant_postprocess.tflite',
+          'type': 'detection',
+          'labels': coco_labels,
+        },
+        'ssd_mobilenet_coco_tpu': {
+          'url': '/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite',
+          'type': 'detection',
+          'labels': coco_labels,
+        },
+        'ssd_mobilenet_face_cpu': {
+          'url': '/ssd_mobilenet_v2_face_quant_postprocess.tflite',
+          'type': 'detection',
+          'labels': ['face'],
+        },
+        'ssd_mobilenet_face_tpu': {
+          'url': '/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite',
+          'type': 'detection',
+          'labels': ['face'],
+        },
+      };
+
       async function loadLocalImage(file) {
         return new Promise(resolve => {
           var reader = new FileReader();
@@ -145,49 +186,80 @@
         }
 
         let interpreter;
+        let model;
 
         document.querySelector("#button-init").addEventListener('click', async () => {
-          let modelName = '/mobilenet_v1_1.0_224_quant_edgetpu.tflite';
-          if (document.getElementById("cpu-model").checked)
-            modelName = '/mobilenet_v1_1.0_224_quant.tflite';
-
-          let model = new Uint8Array(await loadFile(modelName));
-          let modelBufferSize = model.length * model.BYTES_PER_ELEMENT;
-          let modelBuffer = Module._malloc(modelBufferSize);
-          Module.HEAPU8.set(model, modelBuffer);
-
+          model = models[document.getElementById('model').value];
+          console.log(model);
           interpreter = new tflite.Interpreter();
-          if (await interpreter.create(modelBuffer, modelBufferSize)) {
+          if (await interpreter.createFromBuffer(await loadFile(model.url))) {
             document.getElementById("button-firmware").disabled = true;
             document.getElementById("button-init").disabled = true;
-            document.getElementById("cpu-model").disabled = true;
+            document.getElementById("model").disabled = true;
             document.getElementById("button-image").disabled = false;
           }
         });
 
         document.querySelector("#file").addEventListener('change', async () => {
-          var input = document.getElementById("file");
-          var file = input.files[0];
+          const input = document.getElementById("file");
+          const file = input.files[0];
           if (!file) return;
 
-          [_depth, height, width, _channels] = interpreter.inputShape();
+          [_, height, width, _] = interpreter.inputShape(0);
+          console.log('SHAPE:', width, 'x', height);
 
-          let img = await loadLocalImage(file);
-          var c = document.getElementById("canvas");
+          const img = await loadLocalImage(file);
+          const c = document.getElementById("canvas");
           c.width = width;
           c.height = height;
-          var ctx = c.getContext('2d');
-          ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);
+          const ctx = c.getContext('2d');
+          const color = 'red';
+          ctx.strokeStyle = color;
+          ctx.fillStyle = color;
+          ctx.textBaseline = 'top';
 
-          var imageData = ctx.getImageData(0, 0, width, height);
-          interpreter.setRgbaInput(0, imageData.data);
+          switch (model.type) {
+            case 'classification':
+              ctx.drawImage(img, 0, 0, img.width, img.height,
+                                 0, 0, width, height);
+              break;
+            case 'detection':
+              const alpha = Math.min(width / img.width, height / img.height);
+              ctx.drawImage(img, 0, 0, img.width, img.height,
+                                 0, 0, alpha * img.width, alpha * img.height);
+              break;
+          }
+
+          const imageData = ctx.getImageData(0, 0, width, height);
+          tflite.setRgbaInput(interpreter, imageData.data);
           document.getElementById("result").textContent = "Recognizing...";
-          let inference_start = Date.now();
-          interpreter.invoke(() => {
-            let inference_time = Date.now() - inference_start;
-            let maxIndex = interpreter.getClassificationOutput(0);
-            document.getElementById("result").textContent = `${imagenet_labels[maxIndex]} (${inference_time} ms)`;
-          });
+          const inferenceStart = Date.now();
+          await interpreter.invoke();
+          const inferenceTime = Date.now() - inferenceStart;
+
+          let label = null;
+          switch (model.type) {
+            case 'classification':
+              const maxIndex = tflite.getClassificationOutput(interpreter);
+              console.log('Class:', maxIndex);
+              label = model.labels[maxIndex];
+              break;
+            case 'detection':
+              const objects = tflite.getDetectionOutput(interpreter, threshold=0.5);
+              console.log('Detected objects:', objects);
+              for (obj of objects) {
+                console.log(obj);
+                const x = obj.bbox.xmin * width;
+                const y = obj.bbox.ymin * height;
+                const w = obj.bbox.xmax * width - x;
+                const h = obj.bbox.ymax * height - y;
+                ctx.strokeRect(x, y, w, h);
+                ctx.fillText(model.labels[obj.id], x + 5, y + 5);
+              }
+              label = `${objects.length} ${objects.length == 1 ? 'object' : 'objects'}`;
+              break;
+          }
+          document.getElementById("result").textContent = `${label}: ${inferenceTime} ms`;
         });
       };
       Module['print'] = txt => console.log(txt);
diff --git a/site/imagenet_labels.js b/site/labels.js
similarity index 95%
rename from site/imagenet_labels.js
rename to site/labels.js
index fb03f1c..b47648c 100644
--- a/site/imagenet_labels.js
+++ b/site/labels.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  * =============================================================================
  */
-let imagenet_labels = [
+const imagenet_labels = [
   "background",
   "tench, Tinca tinca",
   "goldfish, Carassius auratus",
@@ -1017,3 +1017,96 @@
   "ear, spike, capitulum",
   "toilet tissue, toilet paper, bathroom tissue"
 ];
+
+const coco_labels = [
+  'person',
+  'bicycle',
+  'car',
+  'motorcycle',
+  'airplane',
+  'bus',
+  'train',
+  'truck',
+  'boat',
+  'traffic light',
+  'fire hydrant',
+  'n/a',
+  'stop sign',
+  'parking meter',
+  'bench',
+  'bird',
+  'cat',
+  'dog',
+  'horse',
+  'sheep',
+  'cow',
+  'elephant',
+  'bear',
+  'zebra',
+  'giraffe',
+  'n/a',
+  'backpack',
+  'umbrella',
+  'n/a',
+  'n/a',
+  'handbag',
+  'tie',
+  'suitcase',
+  'frisbee',
+  'skis',
+  'snowboard',
+  'sports ball',
+  'kite',
+  'baseball bat',
+  'baseball glove',
+  'skateboard',
+  'surfboard',
+  'tennis racket',
+  'bottle',
+  'n/a',
+  'wine glass',
+  'cup',
+  'fork',
+  'knife',
+  'spoon',
+  'bowl',
+  'banana',
+  'apple',
+  'sandwich',
+  'orange',
+  'broccoli',
+  'carrot',
+  'hot dog',
+  'pizza',
+  'donut',
+  'cake',
+  'chair',
+  'couch',
+  'potted plant',
+  'bed',
+  'n/a',
+  'dining table',
+  'n/a',
+  'n/a',
+  'toilet',
+  'n/a',
+  'tv',
+  'laptop',
+  'mouse',
+  'remote',
+  'keyboard',
+  'cell phone',
+  'microwave',
+  'oven',
+  'toaster',
+  'sink',
+  'refrigerator',
+  'n/a',
+  'book',
+  'clock',
+  'vase',
+  'scissors',
+  'teddy bear',
+  'hair drier',
+  'toothbrush',
+];
diff --git a/site/tflite.js b/site/tflite.js
index 97d373f..1547317 100644
--- a/site/tflite.js
+++ b/site/tflite.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  * =============================================================================
  */
-let tflite = {};
+const tflite = {};
 
 (function() {
   'use strict';
@@ -22,8 +22,8 @@
   let nextId = 0;
 
   function rgbaArrayToRgbArray(rgbaArray) {
-    var rgbArray = new Uint8Array(new ArrayBuffer(3 * rgbaArray.length / 4));
-    for (var i = 0, j = 0; i < rgbaArray.length; i += 4, j += 3) {
+    const rgbArray = new Uint8Array(new ArrayBuffer(3 * rgbaArray.length / 4));
+    for (let i = 0, j = 0; i < rgbaArray.length; i += 4, j += 3) {
       rgbArray[j + 0] = rgbaArray[i + 0];
       rgbArray[j + 1] = rgbaArray[i + 1];
       rgbArray[j + 2] = rgbaArray[i + 2];
@@ -35,6 +35,55 @@
     return 'interpreter_' + id;
   }
 
+  tflite.setRgbInput = function(interpreter, rgbArray, index=0) {
+    const shape = interpreter.inputShape(index);
+    if (rgbArray.length != shape.reduce((a, b) => a * b))
+      throw new Error('Invalid input array size');
+
+    writeArrayToMemory(rgbArray,  interpreter.inputBuffer(index));
+  }
+
+  tflite.setRgbaInput = function(interpreter, rgbaArray) {
+    tflite.setRgbInput(interpreter, rgbaArrayToRgbArray(rgbaArray));
+  }
+
+  tflite.getClassificationOutput = function(interpreter, index=0) {
+    const count = interpreter.outputShape(index).reduce((a, b) => a * b);
+    const scoresPtr = interpreter.outputBuffer(index) / Module.HEAPU8.BYTES_PER_ELEMENT;
+    const scores = Module.HEAPU8.slice(scoresPtr, scoresPtr + count);
+    return scores.indexOf(Math.max(...scores));
+  }
+
+  tflite.getDetectionOutput = function(interpreter, threshold=0.0) {
+    const bboxesPtr = interpreter.outputBuffer(0) / Module.HEAPF32.BYTES_PER_ELEMENT;
+    const idsPtr = interpreter.outputBuffer(1) / Module.HEAPF32.BYTES_PER_ELEMENT;
+    const scoresPtr = interpreter.outputBuffer(2) / Module.HEAPF32.BYTES_PER_ELEMENT;
+    const countPtr = interpreter.outputBuffer(3) / Module.HEAPF32.BYTES_PER_ELEMENT;
+
+    const count = Math.round(Module.HEAPF32[countPtr]);
+    const bboxes = Module.HEAPF32.slice(bboxesPtr, bboxesPtr + 4 * count);
+    const ids = Module.HEAPF32.slice(idsPtr, idsPtr + count);
+    const scores = Module.HEAPF32.slice(scoresPtr, scoresPtr + count);
+
+    const objects = [];
+    for (let i = 0; i < count; ++i) {
+      if (scores[i] < threshold)
+        break;
+
+      objects.push({
+        'id': ids[i],
+        'score': scores[i],
+        'bbox' : {
+          'ymin': Math.max(0.0, bboxes[4 * i]),
+          'xmin': Math.max(0.0, bboxes[4 * i + 1]),
+          'ymax': Math.min(1.0, bboxes[4 * i + 2]),
+          'xmax': Math.min(1.0, bboxes[4 * i + 3]),
+        },
+      });
+    }
+    return objects;
+  }
+
   tflite.Interpreter = function() {
     this.interpreter_create       = Module.cwrap('interpreter_create',  'number', ['number'], { async: true });
     this.interpreter_destroy      = Module.cwrap('interpreter_destroy', null,     ['number']);
@@ -54,16 +103,52 @@
     this.id = nextId++;
 
     Module['invokeDone'] = function(id) {
-      let key = callbackKey(id);
-      let callback = Module[key];
+      const key = callbackKey(id);
+      const callback = Module[key];
       if (callback) callback();
       delete Module[key];
     };
   }
 
-  tflite.Interpreter.prototype.create = async function(modelBuffer, modelBufferSize) {
-    this.interpreter = await this.interpreter_create(modelBuffer, modelBufferSize, 0);
-    return this.interpreter != null;
+  tflite.Interpreter.prototype.createFromBuffer = async function(buffer) {
+    const model = new Uint8Array(buffer);
+    const modelBufferSize = model.length * model.BYTES_PER_ELEMENT;
+    const modelBufferPtr = Module._malloc(modelBufferSize);
+    Module.HEAPU8.set(model, modelBufferPtr);
+    this.interpreter = await this.interpreter_create(modelBufferPtr, modelBufferSize, 0);
+
+    if (this.interpreter == null)
+      return false;
+
+    this.input_shapes = [];
+    this.input_buffers = [];
+    const num_inputs = this.interpreter_num_inputs(this.interpreter);
+    for (let ti = 0; ti < num_inputs; ++ti) {
+      const shape = [];
+      const dims = this.interpreter_num_input_dims(this.interpreter, ti);
+      for (let i = 0; i < dims; ++i)
+        shape.push(this.interpreter_input_dim(this.interpreter, ti, i));
+      this.input_shapes.push(shape);
+
+      const buffer = this.interpreter_input_buffer(this.interpreter, ti);
+      this.input_buffers.push(buffer);
+    }
+
+    this.output_shapes = [];
+    this.output_buffers = [];
+    const num_outputs = this.interpreter_num_outputs(this.interpreter);
+    for (let ti = 0; ti < num_outputs; ++ti) {
+      const shape = [];
+      const dims = this.interpreter_num_output_dims(this.interpreter, ti);
+      for (let i = 0; i < dims; ++i)
+        shape.push(this.interpreter_output_dim(this.interpreter, ti, i));
+      this.output_shapes.push(shape);
+
+      const buffer = this.interpreter_output_buffer(this.interpreter, ti);
+      this.output_buffers.push(buffer);
+    }
+
+    return true;
   }
 
   tflite.Interpreter.prototype.destroy = function() {
@@ -71,58 +156,34 @@
   }
 
   tflite.Interpreter.prototype.numInputs = function() {
-    return this.interpreter_num_inputs(this.interpreter);
+    return this.input_shapes.length;
   }
 
   tflite.Interpreter.prototype.inputBuffer = function(index) {
-    return this.interpreter_input_buffer(this.interpreter, index);
+    return this.input_buffers[index];
   }
 
   tflite.Interpreter.prototype.inputShape = function(index) {
-    let dims = this.interpreter_num_input_dims(this.interpreter, index);
-    let shape = [];
-    for (let i = 0; i < dims; ++i)
-      shape.push(this.interpreter_input_dim(this.interpreter, index, i));
-    return shape;
+    return this.input_shapes[index];
   }
 
   tflite.Interpreter.prototype.numOutputs = function() {
-    return this.interpreter_num_outputs(this.interpreter);
+    return this.output_shapes.length;
   }
 
   tflite.Interpreter.prototype.outputBuffer = function(index) {
-    return this.interpreter_output_buffer(this.interpreter, index);
+    return this.output_buffers[index];
   }
 
   tflite.Interpreter.prototype.outputShape = function(index) {
-    let dims = this.interpreter_num_output_dims(this.interpreter, index);
-    let shape = [];
-    for (let i = 0; i < dims; ++i)
-      shape.push(this.interpreter_output_dim(this.interpreter, index, i));
-    return shape;
+    return this.output_shapes[index];
   }
 
-  tflite.Interpreter.prototype.invoke = function(callback) {
-    Module[callbackKey(this.id)] = callback;
-    this.interpreter_invoke_async(this.interpreter, this.id);
-  }
-
-  tflite.Interpreter.prototype.setRgbInput = function(index, rgbArray) {
-    let shape = this.inputShape(index);
-    if (rgbArray.length != shape.reduce((a, b) => a * b))
-      throw new Error('Invalid input array size');
-
-    writeArrayToMemory(rgbArray,  this.inputBuffer(index));
-  }
-
-  tflite.Interpreter.prototype.setRgbaInput = function(index, rgbaArray) {
-    this.setRgbInput(index, rgbaArrayToRgbArray(rgbaArray));
-  }
-
-  tflite.Interpreter.prototype.getClassificationOutput = function(index, rgbaArray) {
-    let size = this.outputShape(index).reduce((a, b) => a * b);
-    let buf = this.outputBuffer(index);
-    let tensor = Module.HEAPU8.slice(buf, buf + size);
-    return tensor.indexOf(Math.max(...tensor))
+  tflite.Interpreter.prototype.invoke = function() {
+    const self = this;
+    return new Promise(resolve => {
+      Module[callbackKey(self.id)] = resolve;
+      self.interpreter_invoke_async(self.interpreter, self.id);
+    });
   }
 })();
diff --git a/tflite/libusb.cc b/tflite/libusb.cc
index 6195c93..18ac6f6 100644
--- a/tflite/libusb.cc
+++ b/tflite/libusb.cc
@@ -25,6 +25,18 @@
 #define LIBUSB_NANO 0
 #define LIBUSB_RC ""
 
+//#define LIBUSB_ENABLE_LOG
+
+#ifdef LIBUSB_ENABLE_LOG
+#define LIBUSB_LOG(...)           \
+  do {                            \
+    fprintf(stdout, __VA_ARGS__); \
+    fflush(stdout);               \
+  } while (false)
+#else  // LIBUSB_ENABLE_LOG
+#define LIBUSB_LOG(...)
+#endif  // LIBUSB_ENABLE_LOG
+
 struct libusb_device {
   uint8_t bus_number;
   uint8_t port_number;
@@ -54,7 +66,8 @@
 static int js_request_device(struct libusb_device *dev) {
   return MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
-      console.log('js_request_device');
+      //console.log('js_request_device');
+
       // Bus 001 Device 005: ID 1a6e:089a Global Unichip Corp.
       // Bus 002 Device 007: ID 18d1:9302 Google Inc.
       let options = {'filters': [{'vendorId': 0x18d1, 'productId': 0x9302}]};
@@ -69,7 +82,7 @@
         }
       }
 
-      console.log('js_request_device, device list:', devices);
+      //console.log('js_request_device, device list:', devices);
 
       if (devices.length === 1) {
         let d = devices[0];
@@ -94,7 +107,7 @@
                          uint16_t wIndex, uint8_t *data, uint16_t wLength, unsigned int timeout) {
   return MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
-      console.log('js_control_transfer');
+      //console.log('js_control_transfer');
 
       let bmRequestType = $0;
       let bRequest = $1;
@@ -160,7 +173,7 @@
 extern "C" {
 
 int libusb_init(libusb_context **ctx) {
-  std::cout << "libusb_init" << std::endl;
+  LIBUSB_LOG("libusb_init");
 
   if (!EM_ASM_INT(return navigator.usb !== undefined))
     return LIBUSB_ERROR_NOT_SUPPORTED;
@@ -170,21 +183,21 @@
 }
 
 void LIBUSB_CALL libusb_exit(libusb_context *ctx) {
-  std::cout << "libusb_exit" << std::endl;
+  LIBUSB_LOG("libusb_exit");
   delete ctx;
 }
 
 void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level) {
-  std::cout << "<NOT IMPLEMENTED> libusb_set_debug" << std::endl;
+  LIBUSB_LOG("<NOT IMPLEMENTED> libusb_set_debug");
 }
 
 const struct libusb_version* LIBUSB_CALL libusb_get_version() {
-  std::cout << "libusb_get_version" << std::endl;
+  LIBUSB_LOG("libusb_get_version");
   return &libusb_version_internal;
 }
 
 struct libusb_transfer* LIBUSB_CALL libusb_alloc_transfer(int iso_packets) {
-  std::cout << "libusb_alloc_transfer" << std::endl;
+  LIBUSB_LOG("libusb_alloc_transfer");
 
   size_t size = sizeof(struct libusb_transfer) +
                 sizeof(struct libusb_iso_packet_descriptor) * iso_packets;
@@ -193,7 +206,7 @@
 }
 
 int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer) {
-  std::cout << "libusb_submit_transfer" << std::endl;
+  LIBUSB_LOG("libusb_submit_transfer");
 
   bool dir_in = (transfer->endpoint & 0x80) == 0x80;
   uint8_t endpoint = transfer->endpoint & 0x7f;
@@ -203,7 +216,8 @@
     case LIBUSB_TRANSFER_TYPE_INTERRUPT:
       if (dir_in) {
         MAIN_THREAD_ASYNC_EM_ASM({
-          console.log('js_submit_bulk_in_transfer, endpoint: ', $0);
+          //console.log('js_submit_bulk_in_transfer, endpoint: ', $0);
+
           this.libusb_device.transferIn($0, $2).then(function(result) {
             var data = new Uint8Array(result.data.buffer);
             writeArrayToMemory(data, $1);
@@ -215,7 +229,7 @@
         }, endpoint, transfer->buffer, transfer->length, transfer);
       } else {
         MAIN_THREAD_ASYNC_EM_ASM({
-          console.log('js_submit_bulk_out_transfer, endpoint: ', $0);
+          //console.log('js_submit_bulk_out_transfer, endpoint: ', $0);
 
           var data = new Uint8Array($2);
           for(let i = 0; i < $2; ++i)
@@ -231,29 +245,29 @@
       }
       break;
     default:
-      printf("Transfer type not implemented: %u\n", transfer->type);
+      LIBUSB_LOG("Transfer type not implemented: %u\n", transfer->type);
       return LIBUSB_ERROR_IO;
   }
   return LIBUSB_SUCCESS;
 }
 
 int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer) {
-  std::cout << "<NOT IMPLEMENTED> libusb_cancel_transfer" << std::endl;
+  LIBUSB_LOG("<NOT IMPLEMENTED> libusb_cancel_transfer");
   return 0;
 }
 
 void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer) {
-  std::cout << "libusb_free_transfer" << std::endl;
+  LIBUSB_LOG("libusb_free_transfer");
   free(transfer);
 }
 
 uint8_t libusb_get_port_number(libusb_device * dev) {
-  std::cout << "libusb_get_port_number" << std::endl;
+  LIBUSB_LOG("libusb_get_port_number");
   return dev->port_number;
 }
 
 int libusb_get_port_numbers(libusb_device *dev,uint8_t *port_numbers, int port_numbers_len) {
-  std::cout << "libusb_get_port_numbers" << std::endl;
+  LIBUSB_LOG("libusb_get_port_numbers");
 
   if (port_numbers_len <= 0)
     return LIBUSB_ERROR_INVALID_PARAM;
@@ -270,7 +284,7 @@
   while (true) {
     if (auto item = ctx->completed_transfers.Pop(25)) {
       auto* transfer = item.value();
-      std::cout << "==  Running callback ==" << std::endl;
+      //std::cout << "==  Running callback ==" << std::endl;
       transfer->callback(transfer);
     } else {
       break;
@@ -280,11 +294,12 @@
 }
 
 int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev) {
-  std::cout << "libusb_reset_device" << std::endl;
+  LIBUSB_LOG("libusb_reset_device");
+
   return MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
       try {
-        console.log('js_reset_device');
+        //console.log('js_reset_device');
         await this.libusb_device.reset();
         return 0;  // LIBUSB_SUCCESS
       } catch (error) {
@@ -299,31 +314,31 @@
 int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle,
   uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
   unsigned char *data, uint16_t wLength, unsigned int timeout) {
-  std::cout << "libusb_control_transfer" << std::endl;
+  LIBUSB_LOG("libusb_control_transfer");
   return js_control_transfer(request_type, bRequest, wValue, wIndex, data, wLength, timeout);
 }
 
 int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle,
   unsigned char endpoint, unsigned char *data, int length,
   int *actual_length, unsigned int timeout) {
-  std::cout << "<NOT IMPLEMENTED> libusb_bulk_transfer" << std::endl;
+  LIBUSB_LOG("<NOT IMPLEMENTED> libusb_bulk_transfer");
   return 0;
 }
 
 int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle,
   unsigned char endpoint, unsigned char *data, int length,
   int *actual_length, unsigned int timeout) {
-  std::cout << "<NOT IMPLEMENTED> libusb_interrupt_transfer" << std::endl;
+  LIBUSB_LOG("<NOT IMPLEMENTED> libusb_interrupt_transfer");
   return 0;
 }
 
 int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **handle) {
-  std::cout << "libusb_open" << std::endl;
+  LIBUSB_LOG("libusb_open");
   // dev->descriptor.idVendor, dev->descriptor.idProduct
 
   MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
-      console.log('js_open_device');
+      //console.log('js_open_device');
       await this.libusb_device.open();
       try {
         await this.libusb_device.reset(); // TODO(dkovalev)
@@ -343,11 +358,11 @@
 }
 
 void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle) {
-  std::cout << "libusb_close" << std::endl;
+  LIBUSB_LOG("libusb_close");
 
   MAIN_THREAD_EM_ASM_INT({
     Asyncify.handleAsync(async () => {
-      console.log('js_close_device');
+      //console.log('js_close_device');
       return await this.libusb_device.close();
     });
   });
@@ -356,12 +371,12 @@
 }
 
 libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle) {
-  std::cout << "libusb_get_device" << std::endl;
+  LIBUSB_LOG("libusb_get_device");
   return dev_handle->dev;
 }
 
 ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, libusb_device ***list) {
-  std::cout << "libusb_get_device_list" << std::endl;
+  LIBUSB_LOG("libusb_get_device_list");
 
   auto* dev = &ctx->dev;
   dev->ctx = ctx;
@@ -377,7 +392,7 @@
 }
 
 void LIBUSB_CALL libusb_free_device_list(libusb_device* *list, int unref_devices) {
-  std::cout << "libusb_free_device_list: unref_devices=" << unref_devices << std::endl;
+  LIBUSB_LOG("libusb_free_device_list: unref_devices=%d", unref_devices);
 
   int i = 0;
   while (true) {
@@ -390,32 +405,32 @@
 }
 
 int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc) {
-  std::cout << "libusb_get_device_descriptor" << std::endl;
+  LIBUSB_LOG("libusb_get_device_descriptor");
   *desc = dev->descriptor;
   return 0;
 }
 
 int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev) {
-  std::cout << "libusb_get_device_speed" << std::endl;
+  LIBUSB_LOG("libusb_get_device_speed");
   return LIBUSB_SPEED_SUPER;  // TODO(dkovalev)
 }
 
 uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev) {
-  std::cout << "libusb_get_bus_number" << std::endl;
+  LIBUSB_LOG("libusb_get_bus_number");
   return dev->bus_number;
 }
 
 int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev, int configuration) {
-  std::cout << "<NOT IMPLEMENTED> libusb_set_configuration" << std::endl;
+  LIBUSB_LOG("<NOT IMPLEMENTED> libusb_set_configuration");
   return 0;
 }
 
 int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev, int interface_number) {
-  std::cout << "libusb_claim_interface: " << interface_number << std::endl;
+  LIBUSB_LOG("libusb_claim_interface: %d", interface_number);
   return MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
       try {
-        console.log('js_claim_interface: ', $0);
+        //console.log('js_claim_interface: ', $0);
         await this.libusb_device.claimInterface($0);
         return 0;  // LIBUSB_SUCCESS
       } catch (error) {
@@ -427,11 +442,11 @@
 }
 
 int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev, int interface_number) {
-  std::cout << "libusb_release_interface: " << interface_number << std::endl;
+  LIBUSB_LOG("libusb_release_interface: %d", interface_number);
   return MAIN_THREAD_EM_ASM_INT({
     return Asyncify.handleAsync(async () => {
       try {
-        console.log('js_release_interface: ', $0, this.libusb_device);
+        //console.log('js_release_interface: ', $0, this.libusb_device);
         await this.libusb_device.releaseInterface($0);
         return 0;  // LIBUSB_SUCCESS
       } catch (error) {
@@ -444,7 +459,7 @@
 
 
 EMSCRIPTEN_KEEPALIVE void set_transfer_error(struct libusb_transfer* transfer) {
-  std::cout << "==> set_tansfer_error [" << transfer << "]"  << std::endl;
+  LIBUSB_LOG("==> set_tansfer_error [%p]", transfer);
   libusb_context* ctx = transfer->dev_handle->dev->ctx;
 
   transfer->status = LIBUSB_TRANSFER_CANCELLED;
@@ -454,7 +469,7 @@
 }
 
 EMSCRIPTEN_KEEPALIVE void set_transfer_completed(struct libusb_transfer* transfer, int actual_length) {
-  std::cout << "==> set_tansfer_completed [" << transfer << "]: "  << actual_length << std::endl;
+  LIBUSB_LOG("==> set_tansfer_completed [%p]: %d", transfer, actual_length);
   libusb_context* ctx = transfer->dev_handle->dev->ctx;
 
   transfer->status = LIBUSB_TRANSFER_COMPLETED;