From a2427980262b89ae402a5542b78cee469b4dc1ec Mon Sep 17 00:00:00 2001
From: Murilo Polese <murilopolese@gmail.com>
Date: Thu, 29 Jun 2023 20:41:48 +0200
Subject: [PATCH 1/2] Replace `micropython.js` with `micropython-ctl` for
 performance test

---
 index.js            |  116 ++++-
 package-lock.json   | 1034 ++++++++++++++++++++++++++++++++++++-------
 package.json        |    4 +-
 preload.js          |   61 ++-
 ui/arduino/store.js |   21 +-
 5 files changed, 1019 insertions(+), 217 deletions(-)

diff --git a/index.js b/index.js
index 6bbe9ef..2b2db92 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,5 @@
 const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron')
+const Micropython = require('micropython-ctl-cont').MicroPythonDevice
 const path = require('path')
 const fs = require('fs')
 const openAboutWindow = require('about-window').default
@@ -98,8 +99,121 @@ ipcMain.handle('rename-file', (event, filePath, newFilePath) => {
   return true
 })
 
-// WINDOW MANAGEMENT
+// MICROPYTHON
+const board = new Micropython()
+ipcMain.handle('serial-list-ports', (event) => {
+  console.log('ipcMain', 'serial-list-ports')
+  return [
+    { path: '/dev/ttyACM0' },
+    { path: '/dev/ttyACM1' },
+    { path: '/dev/ttyUSB0' },
+    { path: '/dev/ttyUSB1' },
+  ]
+})
+ipcMain.handle('serial-connect', async (event, path) => {
+  console.log('ipcMain', 'serial-connect', path)
+  await board.disconnect()
+  await board.connectSerial(path)
+  board.sendData(Buffer.from('\x03\x03'))
+  board.onTerminalData = (d) => {
+    win.webContents.send('terminal-data', d)
+  }
+  board.onclose = () => {
+    win.webContents.send('disconnect')
+  }
+})
+ipcMain.handle('serial-disconnect', async (event) => {
+  console.log('ipcMain', 'serial-disconnect')
+  await board.disconnect()
+})
+ipcMain.handle('serial-run', async (event, code) => {
+  console.log('ipcMain', 'serial-run', code)
+  win.webContents.send('terminal-data', '\r\n')
+  try {
+    const output = await board.runScript(code , {
+      disableDedent: true,
+      broadcastOutputAsTerminalData: true
+    })
+  } catch(err) {
+    console.log(err)
+  }
+  board.sendData('\r\n')
+  return Promise.resolve(output)
+})
+ipcMain.handle('serial-stop', async (event) => {
+  console.log('ipcMain', 'serial-stop')
+  board.sendData(Buffer.from('\r\x03\x03'))
+  try {
+    await board.readUntil('>>>', 5)
+  } catch {
+    // Might be stuck in raw repl. Try to exit into friendly repl with Ctrl+B
+    try {
+      await board.sendData('\r\x03\x02\r')
+    } catch {
+      console.log('could not stop')
+    }
+  }
+})
+ipcMain.handle('serial-reset', async (event) => {
+  console.log('ipcMain', 'serial-reset')
+  await board.reset()
+})
+ipcMain.handle('serial-eval', async (event, data) => {
+  // console.log('ipcMain', 'serial-eval', data)
+  board.sendData(Buffer.from(data))
+})
+ipcMain.handle('serial-list-files', async (event, folder) => {
+  console.log('ipcMain', 'serial-list-files', folder)
+  return await board.listFiles(folder)
+})
+ipcMain.handle('serial-ilist-files', async (event, folder) => {
+  console.log('ipcMain', 'serial-ilist-files', folder)
+  let files = await board.listFiles(folder)
+  files = files.map(f => ({
+    path: f.filename.slice(1),
+    type: f.isDir ? 'folder' : 'file'
+  }))
+  // console.log('yolo', files)
+  return files
+})
+ipcMain.handle('serial-load-file', async (event, filePath) => {
+  console.log('ipcMain', 'serial-load-file', filePath)
+  const output = await board.getFile(filePath)
+  return output.toString()
+})
+ipcMain.handle('serial-remove-file', async (event, filePath) => {
+  console.log('ipcMain', 'serial-remove-file', filePath)
+  await board.remove(filePath)
+})
+ipcMain.handle('serial-save-file', async (event, filePath, content) => {
+  console.log('ipcMain', 'serial-save-file', filePath, content)
+  await board.putFile(filePath, Buffer.from(content))
+})
+ipcMain.handle('serial-upload', async (event, src, dest) => {
+  console.log('ipcMain', 'serial-upload-file', src, dest)
+  const content = fs.readFileSync(src)
+  await board.putFile(dest, content)
+})
+ipcMain.handle('serial-download', async (event, src, dest) => {
+  console.log('ipcMain', 'serial-download-file', src, dest)
+  const content = await board.getFile(src)
+  fs.writeFileSync(dest, content)
+})
+ipcMain.handle('serial-rename-file', async (event, src, dest) => {
+  console.log('ipcMain', 'serial-rename-file', src, dest)
+  await board.rename(src, dest)
+})
+ipcMain.handle('serial-create-folder', async (event, folder) => {
+  console.log('ipcMain', 'serial-create-folder', folder)
+  try {
+    await board.mkdir(folder)
+  } catch(err) {
+    console.log('error', err)
+  }
+})
+
 
+// WINDOW MANAGEMENT
 ipcMain.handle('set-window-size', (event, minWidth, minHeight) => {
   console.log('ipcMain', 'set-window-size', minWidth, minHeight)
   if (!win) {
diff --git a/package-lock.json b/package-lock.json
index e3c6390..84dd1e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
 			"license": "MIT",
 			"dependencies": {
 				"about-window": "^1.15.2",
-				"micropython.js": "github:arduino/micropython.js#v1.4.0"
+				"micropython-ctl-cont": "^1.15.3"
 			},
 			"devDependencies": {
 				"electron": "^19.0.10",
@@ -323,18 +323,6 @@
 				"node": ">=14"
 			}
 		},
-		"node_modules/@serialport/binding-mock": {
-			"version": "10.2.2",
-			"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
-			"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
-			"dependencies": {
-				"@serialport/bindings-interface": "^1.2.1",
-				"debug": "^4.3.3"
-			},
-			"engines": {
-				"node": ">=12.0.0"
-			}
-		},
 		"node_modules/@serialport/bindings-cpp": {
 			"version": "10.8.0",
 			"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.8.0.tgz",
@@ -354,42 +342,7 @@
 				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
-		"node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": {
-			"version": "5.1.0",
-			"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
-			"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
-		},
-		"node_modules/@serialport/bindings-interface": {
-			"version": "1.2.2",
-			"resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz",
-			"integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==",
-			"engines": {
-				"node": "^12.22 || ^14.13 || >=16"
-			}
-		},
-		"node_modules/@serialport/parser-byte-length": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.5.0.tgz",
-			"integrity": "sha512-eHhr4lHKboq1OagyaXAqkemQ1XyoqbLQC8XJbvccm95o476TmEdW5d7AElwZV28kWprPW68ZXdGF2VXCkJgS2w==",
-			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
-			}
-		},
-		"node_modules/@serialport/parser-cctalk": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.5.0.tgz",
-			"integrity": "sha512-Iwsdr03xmCKAiibLSr7b3w6ZUTBNiS+PwbDQXdKU/clutXjuoex83XvsOtYVcNZmwJlVNhAUbkG+FJzWwIa4DA==",
-			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
-			}
-		},
-		"node_modules/@serialport/parser-delimiter": {
+		"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": {
 			"version": "10.5.0",
 			"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.5.0.tgz",
 			"integrity": "sha512-/uR/yT3jmrcwnl2FJU/2ySvwgo5+XpksDUR4NF/nwTS5i3CcuKS+FKi/tLzy1k8F+rCx5JzpiK+koqPqOUWArA==",
@@ -400,26 +353,7 @@
 				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
-		"node_modules/@serialport/parser-inter-byte-timeout": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.5.0.tgz",
-			"integrity": "sha512-WPvVlSx98HmmUF9jjK6y9mMp3Wnv6JQA0cUxLeZBgS74TibOuYG3fuUxUWGJALgAXotOYMxfXSezJ/vSnQrkhQ==",
-			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
-			}
-		},
-		"node_modules/@serialport/parser-packet-length": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.5.0.tgz",
-			"integrity": "sha512-jkpC/8w4/gUBRa2Teyn7URv1D7T//0lGj27/4u9AojpDVXsR6dtdcTG7b7dNirXDlOrSLvvN7aS5/GNaRlEByw==",
-			"engines": {
-				"node": ">=8.6.0"
-			}
-		},
-		"node_modules/@serialport/parser-readline": {
+		"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": {
 			"version": "10.5.0",
 			"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.5.0.tgz",
 			"integrity": "sha512-0aXJknodcl94W9zSjvU+sLdXiyEG2rqjQmvBWZCr8wJZjWEtv3RgrnYiWq4i2OTOyC8C/oPK8ZjpBjQptRsoJQ==",
@@ -433,26 +367,25 @@
 				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
-		"node_modules/@serialport/parser-ready": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.5.0.tgz",
-			"integrity": "sha512-QIf65LTvUoxqWWHBpgYOL+soldLIIyD1bwuWelukem2yDZVWwEjR288cLQ558BgYxH4U+jLAQahhqoyN1I7BaA==",
+		"node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+			"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
+		},
+		"node_modules/@serialport/bindings-interface": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz",
+			"integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==",
 			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
+				"node": "^12.22 || ^14.13 || >=16"
 			}
 		},
-		"node_modules/@serialport/parser-regex": {
+		"node_modules/@serialport/parser-packet-length": {
 			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.5.0.tgz",
-			"integrity": "sha512-9jnr9+PCxRoLjtGs7uxwsFqvho+rxuJlW6ZWSB7oqfzshEZWXtTJgJRgac/RuLft4hRlrmRz5XU40i3uoL4HKw==",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.5.0.tgz",
+			"integrity": "sha512-jkpC/8w4/gUBRa2Teyn7URv1D7T//0lGj27/4u9AojpDVXsR6dtdcTG7b7dNirXDlOrSLvvN7aS5/GNaRlEByw==",
 			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
+				"node": ">=8.6.0"
 			}
 		},
 		"node_modules/@serialport/parser-slip-encoder": {
@@ -477,21 +410,6 @@
 				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
-		"node_modules/@serialport/stream": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.5.0.tgz",
-			"integrity": "sha512-gbcUdvq9Kyv2HsnywS7QjnEB28g+6OGB5Z8TLP7X+UPpoMIWoUsoQIq5Kt0ZTgMoWn3JGM2lqwTsSHF+1qhniA==",
-			"dependencies": {
-				"@serialport/bindings-interface": "1.2.2",
-				"debug": "^4.3.2"
-			},
-			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
-			}
-		},
 		"node_modules/@sindresorhus/is": {
 			"version": "0.14.0",
 			"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -656,6 +574,18 @@
 			"resolved": "https://registry.npmjs.org/about-window/-/about-window-1.15.2.tgz",
 			"integrity": "sha512-31mDAnLUfKm4uShfMzeEoS6a3nEto2tUt4zZn7qyAKedaTV4p0dGiW1n+YG8vtRh78mZiewghWJmoxDY+lHyYg=="
 		},
+		"node_modules/accepts": {
+			"version": "1.3.8",
+			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+			"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+			"dependencies": {
+				"mime-types": "~2.1.34",
+				"negotiator": "0.6.3"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
 		"node_modules/agent-base": {
 			"version": "6.0.2",
 			"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -876,6 +806,11 @@
 			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
 			"dev": true
 		},
+		"node_modules/array-flatten": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+			"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+		},
 		"node_modules/asar": {
 			"version": "3.2.0",
 			"resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz",
@@ -958,7 +893,6 @@
 			"version": "1.5.1",
 			"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 			"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-			"dev": true,
 			"funding": [
 				{
 					"type": "github",
@@ -1014,6 +948,53 @@
 				"bluebird": "^3.5.5"
 			}
 		},
+		"node_modules/body-parser": {
+			"version": "1.20.2",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+			"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"content-type": "~1.0.5",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"on-finished": "2.4.1",
+				"qs": "6.11.0",
+				"raw-body": "2.5.2",
+				"type-is": "~1.6.18",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8",
+				"npm": "1.2.8000 || >= 1.4.16"
+			}
+		},
+		"node_modules/body-parser/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/body-parser/node_modules/iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/body-parser/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
 		"node_modules/boolean": {
 			"version": "3.2.0",
 			"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
@@ -1174,6 +1155,14 @@
 				"node": ">= 10.0.0"
 			}
 		},
+		"node_modules/bytes": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+			"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/cacache": {
 			"version": "17.1.3",
 			"resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.3.tgz",
@@ -1207,16 +1196,16 @@
 			}
 		},
 		"node_modules/cacache/node_modules/glob": {
-			"version": "10.3.0",
-			"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.0.tgz",
-			"integrity": "sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg==",
+			"version": "10.3.1",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.1.tgz",
+			"integrity": "sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw==",
 			"dev": true,
 			"dependencies": {
 				"foreground-child": "^3.1.0",
 				"jackspeak": "^2.0.3",
 				"minimatch": "^9.0.1",
 				"minipass": "^5.0.0 || ^6.0.2",
-				"path-scurry": "^1.7.0"
+				"path-scurry": "^1.10.0"
 			},
 			"bin": {
 				"glob": "dist/cjs/src/bin.js"
@@ -1303,6 +1292,18 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/call-bind": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+			"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+			"dependencies": {
+				"function-bind": "^1.1.1",
+				"get-intrinsic": "^1.0.2"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
 		"node_modules/chalk": {
 			"version": "4.1.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1538,6 +1539,43 @@
 			"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
 			"dev": true
 		},
+		"node_modules/consolite": {
+			"version": "0.3.11",
+			"resolved": "https://registry.npmjs.org/consolite/-/consolite-0.3.11.tgz",
+			"integrity": "sha512-QXQMiTg6mUa8BrN/WH4sRGImS3qslhj0AzPUUN/osK1WE9R+sOo6Doc+0o1nGuFYK9aV7ZBfBRw0ofkwmzaSIw=="
+		},
+		"node_modules/content-disposition": {
+			"version": "0.5.4",
+			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+			"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+			"dependencies": {
+				"safe-buffer": "5.2.1"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/content-type": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+			"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie": {
+			"version": "0.5.0",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+			"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie-signature": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+			"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+		},
 		"node_modules/core-util-is": {
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -1650,11 +1688,19 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 			"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.8"
 			}
 		},
+		"node_modules/destroy": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+			"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+			"engines": {
+				"node": ">= 0.8",
+				"npm": "1.2.8000 || >= 1.4.16"
+			}
+		},
 		"node_modules/detect-libc": {
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
@@ -1815,6 +1861,11 @@
 			"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
 			"dev": true
 		},
+		"node_modules/ee-first": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+			"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+		},
 		"node_modules/ejs": {
 			"version": "3.1.9",
 			"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
@@ -2345,8 +2396,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
 			"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
-			"dev": true,
-			"optional": true,
 			"engines": {
 				"node": ">= 0.8"
 			}
@@ -2355,7 +2404,6 @@
 			"version": "0.1.13",
 			"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
 			"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-			"dev": true,
 			"optional": true,
 			"dependencies": {
 				"iconv-lite": "^0.6.2"
@@ -2401,6 +2449,11 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+		},
 		"node_modules/escape-string-regexp": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2414,12 +2467,122 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/etag": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+			"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
 		"node_modules/exponential-backoff": {
 			"version": "3.1.1",
 			"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
 			"integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==",
 			"dev": true
 		},
+		"node_modules/express": {
+			"version": "4.18.2",
+			"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+			"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+			"dependencies": {
+				"accepts": "~1.3.8",
+				"array-flatten": "1.1.1",
+				"body-parser": "1.20.1",
+				"content-disposition": "0.5.4",
+				"content-type": "~1.0.4",
+				"cookie": "0.5.0",
+				"cookie-signature": "1.0.6",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"finalhandler": "1.2.0",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"merge-descriptors": "1.0.1",
+				"methods": "~1.1.2",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"path-to-regexp": "0.1.7",
+				"proxy-addr": "~2.0.7",
+				"qs": "6.11.0",
+				"range-parser": "~1.2.1",
+				"safe-buffer": "5.2.1",
+				"send": "0.18.0",
+				"serve-static": "1.15.0",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"type-is": "~1.6.18",
+				"utils-merge": "1.0.1",
+				"vary": "~1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.10.0"
+			}
+		},
+		"node_modules/express/node_modules/body-parser": {
+			"version": "1.20.1",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+			"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"content-type": "~1.0.4",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"on-finished": "2.4.1",
+				"qs": "6.11.0",
+				"raw-body": "2.5.1",
+				"type-is": "~1.6.18",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8",
+				"npm": "1.2.8000 || >= 1.4.16"
+			}
+		},
+		"node_modules/express/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/express/node_modules/iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/express/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
+		"node_modules/express/node_modules/raw-body": {
+			"version": "2.5.1",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+			"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/extract-zip": {
 			"version": "1.7.0",
 			"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
@@ -2511,6 +2674,36 @@
 				"node": ">=10"
 			}
 		},
+		"node_modules/finalhandler": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+			"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"statuses": "2.0.1",
+				"unpipe": "~1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/finalhandler/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/finalhandler/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
 		"node_modules/foreground-child": {
 			"version": "3.1.1",
 			"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -2553,6 +2746,22 @@
 				"node": ">= 6"
 			}
 		},
+		"node_modules/forwarded": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+			"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/fresh": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+			"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
 		"node_modules/fs-extra": {
 			"version": "8.1.0",
 			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@@ -2588,9 +2797,7 @@
 		"node_modules/function-bind": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-			"dev": true,
-			"optional": true
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
 		},
 		"node_modules/gauge": {
 			"version": "4.0.4",
@@ -2624,8 +2831,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
 			"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
-			"dev": true,
-			"optional": true,
 			"dependencies": {
 				"function-bind": "^1.1.1",
 				"has": "^1.0.3",
@@ -2772,8 +2977,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
 			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-			"dev": true,
-			"optional": true,
 			"dependencies": {
 				"function-bind": "^1.1.1"
 			},
@@ -2807,8 +3010,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
 			"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
-			"dev": true,
-			"optional": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -2820,8 +3021,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
 			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-			"dev": true,
-			"optional": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -2853,6 +3052,21 @@
 			"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
 			"dev": true
 		},
+		"node_modules/http-errors": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+			"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+			"dependencies": {
+				"depd": "2.0.0",
+				"inherits": "2.0.4",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"toidentifier": "1.0.1"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/http-proxy-agent": {
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
@@ -2923,7 +3137,7 @@
 			"version": "0.6.3",
 			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 			"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"safer-buffer": ">= 2.1.2 < 3.0.0"
 			},
@@ -2935,7 +3149,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
 			"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-			"dev": true,
 			"funding": [
 				{
 					"type": "github",
@@ -2982,8 +3195,7 @@
 		"node_modules/inherits": {
 			"version": "2.0.4",
 			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-			"dev": true
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
 		},
 		"node_modules/ini": {
 			"version": "1.3.8",
@@ -2998,6 +3210,14 @@
 			"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
 			"dev": true
 		},
+		"node_modules/ipaddr.js": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+			"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
 		"node_modules/is-ci": {
 			"version": "3.0.1",
 			"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
@@ -3070,6 +3290,14 @@
 			"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
 			"dev": true
 		},
+		"node_modules/isomorphic-ws": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
+			"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
+			"peerDependencies": {
+				"ws": "*"
+			}
+		},
 		"node_modules/jackspeak": {
 			"version": "2.2.1",
 			"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz",
@@ -3302,14 +3530,209 @@
 				"node": ">=10"
 			}
 		},
-		"node_modules/micropython.js": {
-			"version": "1.4.0",
-			"resolved": "git+ssh://git@github.com/arduino/micropython.js.git#6bada690cd0709d3195a59af6b1a79fbf1b1af45",
+		"node_modules/media-typer": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+			"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/merge-descriptors": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+			"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+		},
+		"node_modules/methods": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+			"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/micropython-ctl-cont": {
+			"version": "1.15.3",
+			"resolved": "https://registry.npmjs.org/micropython-ctl-cont/-/micropython-ctl-cont-1.15.3.tgz",
+			"integrity": "sha512-TWwc8svLb3FmIiNPCwOBRvRsQiEN5NK7cEmEMcBciQmnzChCtSbDsC6jj9Bof+cn6OWYZzMndfFNFPGdCZnPcQ==",
 			"dependencies": {
-				"serialport": "^10.4.0"
+				"body-parser": "^1.19.0",
+				"buffer": "^6.0.3",
+				"commander": "^7.0.0",
+				"consolite": "^0.3.10",
+				"express": "^4.17.1",
+				"isomorphic-ws": "^4.0.1",
+				"node-fetch": "^2.6.1",
+				"serialport": "^10.4.0",
+				"ws": "^7.4.1"
 			},
 			"bin": {
-				"micropython.js": "cli.js"
+				"mctl": "dist-node/cli/index.js"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/binding-mock": {
+			"version": "10.2.2",
+			"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
+			"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
+			"dependencies": {
+				"@serialport/bindings-interface": "^1.2.1",
+				"debug": "^4.3.3"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-byte-length": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.5.0.tgz",
+			"integrity": "sha512-eHhr4lHKboq1OagyaXAqkemQ1XyoqbLQC8XJbvccm95o476TmEdW5d7AElwZV28kWprPW68ZXdGF2VXCkJgS2w==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-cctalk": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.5.0.tgz",
+			"integrity": "sha512-Iwsdr03xmCKAiibLSr7b3w6ZUTBNiS+PwbDQXdKU/clutXjuoex83XvsOtYVcNZmwJlVNhAUbkG+FJzWwIa4DA==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-delimiter": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.5.0.tgz",
+			"integrity": "sha512-/uR/yT3jmrcwnl2FJU/2ySvwgo5+XpksDUR4NF/nwTS5i3CcuKS+FKi/tLzy1k8F+rCx5JzpiK+koqPqOUWArA==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-inter-byte-timeout": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.5.0.tgz",
+			"integrity": "sha512-WPvVlSx98HmmUF9jjK6y9mMp3Wnv6JQA0cUxLeZBgS74TibOuYG3fuUxUWGJALgAXotOYMxfXSezJ/vSnQrkhQ==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-readline": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.5.0.tgz",
+			"integrity": "sha512-0aXJknodcl94W9zSjvU+sLdXiyEG2rqjQmvBWZCr8wJZjWEtv3RgrnYiWq4i2OTOyC8C/oPK8ZjpBjQptRsoJQ==",
+			"dependencies": {
+				"@serialport/parser-delimiter": "10.5.0"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-ready": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.5.0.tgz",
+			"integrity": "sha512-QIf65LTvUoxqWWHBpgYOL+soldLIIyD1bwuWelukem2yDZVWwEjR288cLQ558BgYxH4U+jLAQahhqoyN1I7BaA==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-regex": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.5.0.tgz",
+			"integrity": "sha512-9jnr9+PCxRoLjtGs7uxwsFqvho+rxuJlW6ZWSB7oqfzshEZWXtTJgJRgac/RuLft4hRlrmRz5XU40i3uoL4HKw==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/@serialport/stream": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.5.0.tgz",
+			"integrity": "sha512-gbcUdvq9Kyv2HsnywS7QjnEB28g+6OGB5Z8TLP7X+UPpoMIWoUsoQIq5Kt0ZTgMoWn3JGM2lqwTsSHF+1qhniA==",
+			"dependencies": {
+				"@serialport/bindings-interface": "1.2.2",
+				"debug": "^4.3.2"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/buffer": {
+			"version": "6.0.3",
+			"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+			"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			],
+			"dependencies": {
+				"base64-js": "^1.3.1",
+				"ieee754": "^1.2.1"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/commander": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+			"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+			"engines": {
+				"node": ">= 10"
+			}
+		},
+		"node_modules/micropython-ctl-cont/node_modules/serialport": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/serialport/-/serialport-10.5.0.tgz",
+			"integrity": "sha512-7OYLDsu5i6bbv3lU81pGy076xe0JwpK6b49G6RjNvGibstUqQkI+I3/X491yBGtf4gaqUdOgoU1/5KZ/XxL4dw==",
+			"dependencies": {
+				"@serialport/binding-mock": "10.2.2",
+				"@serialport/bindings-cpp": "10.8.0",
+				"@serialport/parser-byte-length": "10.5.0",
+				"@serialport/parser-cctalk": "10.5.0",
+				"@serialport/parser-delimiter": "10.5.0",
+				"@serialport/parser-inter-byte-timeout": "10.5.0",
+				"@serialport/parser-packet-length": "10.5.0",
+				"@serialport/parser-readline": "10.5.0",
+				"@serialport/parser-ready": "10.5.0",
+				"@serialport/parser-regex": "10.5.0",
+				"@serialport/parser-slip-encoder": "10.5.0",
+				"@serialport/parser-spacepacket": "10.5.0",
+				"@serialport/stream": "10.5.0",
+				"debug": "^4.3.3"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
 		"node_modules/mime": {
@@ -3328,7 +3751,6 @@
 			"version": "1.52.0",
 			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
 			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.6"
 			}
@@ -3337,7 +3759,6 @@
 			"version": "2.1.35",
 			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
 			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-			"dev": true,
 			"dependencies": {
 				"mime-db": "1.52.0"
 			},
@@ -3552,7 +3973,6 @@
 			"version": "0.6.3",
 			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
 			"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.6"
 			}
@@ -3615,6 +4035,25 @@
 				"node": ">=10"
 			}
 		},
+		"node_modules/node-fetch": {
+			"version": "2.6.11",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+			"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+			"dependencies": {
+				"whatwg-url": "^5.0.0"
+			},
+			"engines": {
+				"node": "4.x || >=6.0.0"
+			},
+			"peerDependencies": {
+				"encoding": "^0.1.0"
+			},
+			"peerDependenciesMeta": {
+				"encoding": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/node-gyp": {
 			"version": "9.4.0",
 			"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz",
@@ -3718,6 +4157,14 @@
 				"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
 			}
 		},
+		"node_modules/object-inspect": {
+			"version": "1.12.3",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+			"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
 		"node_modules/object-keys": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -3728,6 +4175,17 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/on-finished": {
+			"version": "2.4.1",
+			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+			"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+			"dependencies": {
+				"ee-first": "1.1.1"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/once": {
 			"version": "1.4.0",
 			"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -3799,6 +4257,14 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/parseurl": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+			"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/path-is-absolute": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -3818,12 +4284,12 @@
 			}
 		},
 		"node_modules/path-scurry": {
-			"version": "1.9.2",
-			"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz",
-			"integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==",
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.0.tgz",
+			"integrity": "sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g==",
 			"dev": true,
 			"dependencies": {
-				"lru-cache": "^9.1.1",
+				"lru-cache": "^9.1.1 || ^10.0.0",
 				"minipass": "^5.0.0 || ^6.0.2"
 			},
 			"engines": {
@@ -3834,14 +4300,19 @@
 			}
 		},
 		"node_modules/path-scurry/node_modules/lru-cache": {
-			"version": "9.1.2",
-			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz",
-			"integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==",
+			"version": "10.0.0",
+			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz",
+			"integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==",
 			"dev": true,
 			"engines": {
 				"node": "14 || >=16.14"
 			}
 		},
+		"node_modules/path-to-regexp": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+			"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+		},
 		"node_modules/pend": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -3915,6 +4386,18 @@
 			"dev": true,
 			"optional": true
 		},
+		"node_modules/proxy-addr": {
+			"version": "2.0.7",
+			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+			"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+			"dependencies": {
+				"forwarded": "0.2.0",
+				"ipaddr.js": "1.9.1"
+			},
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
 		"node_modules/pump": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -3934,6 +4417,20 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/qs": {
+			"version": "6.11.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+			"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+			"dependencies": {
+				"side-channel": "^1.0.4"
+			},
+			"engines": {
+				"node": ">=0.6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
 		"node_modules/quick-lru": {
 			"version": "5.1.1",
 			"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -3946,6 +4443,39 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/range-parser": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+			"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/raw-body": {
+			"version": "2.5.2",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+			"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/raw-body/node_modules/iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
 		"node_modules/read-config-file": {
 			"version": "6.2.0",
 			"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz",
@@ -3977,6 +4507,12 @@
 				"util-deprecate": "~1.0.1"
 			}
 		},
+		"node_modules/readable-stream/node_modules/safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+			"dev": true
+		},
 		"node_modules/require-directory": {
 			"version": "2.1.1",
 			"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -4057,16 +4593,28 @@
 			}
 		},
 		"node_modules/safe-buffer": {
-			"version": "5.1.2",
-			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-			"dev": true
+			"version": "5.2.1",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+			"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
 		},
 		"node_modules/safer-buffer": {
 			"version": "2.1.2",
 			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-			"dev": true
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
 		},
 		"node_modules/sanitize-filename": {
 			"version": "1.6.3",
@@ -4099,6 +4647,58 @@
 			"dev": true,
 			"optional": true
 		},
+		"node_modules/send": {
+			"version": "0.18.0",
+			"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+			"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"mime": "1.6.0",
+				"ms": "2.1.3",
+				"on-finished": "2.4.1",
+				"range-parser": "~1.2.1",
+				"statuses": "2.0.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/send/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/send/node_modules/debug/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
+		"node_modules/send/node_modules/mime": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+			"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+			"bin": {
+				"mime": "cli.js"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/send/node_modules/ms": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+			"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+		},
 		"node_modules/serialize-error": {
 			"version": "7.0.1",
 			"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
@@ -4115,31 +4715,18 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/serialport": {
-			"version": "10.5.0",
-			"resolved": "https://registry.npmjs.org/serialport/-/serialport-10.5.0.tgz",
-			"integrity": "sha512-7OYLDsu5i6bbv3lU81pGy076xe0JwpK6b49G6RjNvGibstUqQkI+I3/X491yBGtf4gaqUdOgoU1/5KZ/XxL4dw==",
+		"node_modules/serve-static": {
+			"version": "1.15.0",
+			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+			"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
 			"dependencies": {
-				"@serialport/binding-mock": "10.2.2",
-				"@serialport/bindings-cpp": "10.8.0",
-				"@serialport/parser-byte-length": "10.5.0",
-				"@serialport/parser-cctalk": "10.5.0",
-				"@serialport/parser-delimiter": "10.5.0",
-				"@serialport/parser-inter-byte-timeout": "10.5.0",
-				"@serialport/parser-packet-length": "10.5.0",
-				"@serialport/parser-readline": "10.5.0",
-				"@serialport/parser-ready": "10.5.0",
-				"@serialport/parser-regex": "10.5.0",
-				"@serialport/parser-slip-encoder": "10.5.0",
-				"@serialport/parser-spacepacket": "10.5.0",
-				"@serialport/stream": "10.5.0",
-				"debug": "^4.3.3"
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"parseurl": "~1.3.3",
+				"send": "0.18.0"
 			},
 			"engines": {
-				"node": ">=12.0.0"
-			},
-			"funding": {
-				"url": "https://opencollective.com/serialport/donate"
+				"node": ">= 0.8.0"
 			}
 		},
 		"node_modules/set-blocking": {
@@ -4148,6 +4735,11 @@
 			"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
 			"dev": true
 		},
+		"node_modules/setprototypeof": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+			"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+		},
 		"node_modules/shebang-command": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4169,6 +4761,19 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/side-channel": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+			"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+			"dependencies": {
+				"call-bind": "^1.0.0",
+				"get-intrinsic": "^1.0.2",
+				"object-inspect": "^1.9.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
 		"node_modules/signal-exit": {
 			"version": "3.0.7",
 			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -4296,6 +4901,14 @@
 				"node": ">= 6"
 			}
 		},
+		"node_modules/statuses": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+			"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/string_decoder": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -4305,6 +4918,12 @@
 				"safe-buffer": "~5.1.0"
 			}
 		},
+		"node_modules/string_decoder/node_modules/safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+			"dev": true
+		},
 		"node_modules/string-width": {
 			"version": "4.2.3",
 			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -4511,6 +5130,19 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/toidentifier": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+			"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+			"engines": {
+				"node": ">=0.6"
+			}
+		},
+		"node_modules/tr46": {
+			"version": "0.0.3",
+			"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+			"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+		},
 		"node_modules/truncate-utf8-bytes": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -4543,6 +5175,18 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/type-is": {
+			"version": "1.6.18",
+			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+			"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+			"dependencies": {
+				"media-typer": "0.3.0",
+				"mime-types": "~2.1.24"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
 		"node_modules/typedarray": {
 			"version": "0.0.6",
 			"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -4582,6 +5226,14 @@
 				"node": ">= 4.0.0"
 			}
 		},
+		"node_modules/unpipe": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+			"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/uri-js": {
 			"version": "4.4.1",
 			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -4615,6 +5267,22 @@
 			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
 			"dev": true
 		},
+		"node_modules/utils-merge": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+			"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+			"engines": {
+				"node": ">= 0.4.0"
+			}
+		},
+		"node_modules/vary": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+			"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
 		"node_modules/verror": {
 			"version": "1.10.1",
 			"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
@@ -4646,6 +5314,20 @@
 				"defaults": "^1.0.3"
 			}
 		},
+		"node_modules/webidl-conversions": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+			"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+		},
+		"node_modules/whatwg-url": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+			"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+			"dependencies": {
+				"tr46": "~0.0.3",
+				"webidl-conversions": "^3.0.0"
+			}
+		},
 		"node_modules/which": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4711,6 +5393,26 @@
 			"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
 			"dev": true
 		},
+		"node_modules/ws": {
+			"version": "7.5.9",
+			"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+			"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+			"engines": {
+				"node": ">=8.3.0"
+			},
+			"peerDependencies": {
+				"bufferutil": "^4.0.1",
+				"utf-8-validate": "^5.0.2"
+			},
+			"peerDependenciesMeta": {
+				"bufferutil": {
+					"optional": true
+				},
+				"utf-8-validate": {
+					"optional": true
+				}
+			}
+		},
 		"node_modules/xmlbuilder": {
 			"version": "15.1.1",
 			"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
diff --git a/package.json b/package.json
index 673e584..e49d723 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
 	"scripts": {
 		"post-set-shell": "npm config set script-shell bash",
 		"rebuild": "electron-rebuild",
-		"dev": "electron ./",
+		"dev": "electron --inspect ./",
 		"build": "npm run post-set-shell && electron-builder $(if [ $(uname -m) =  arm64 ]; then echo --mac --x64; fi)",
 		"postinstall": "npm run post-set-shell && npm run rebuild"
 	},
@@ -38,7 +38,7 @@
 	"license": "MIT",
 	"dependencies": {
 		"about-window": "^1.15.2",
-		"micropython.js": "github:arduino/micropython.js#v1.4.0"
+		"micropython-ctl-cont": "^1.15.3"
 	},
 	"engines": {
 		"node": "18"
diff --git a/preload.js b/preload.js
index b9b9d40..7a95094 100644
--- a/preload.js
+++ b/preload.js
@@ -2,76 +2,65 @@ console.log('preload')
 const { contextBridge, ipcRenderer } = require('electron')
 const path = require('path')
 
-const Micropython = require('micropython.js')
+const Micropython = require('micropython-ctl-cont').MicroPythonDevice
 const board = new Micropython()
-board.chunk_size = 192
-board.chunk_sleep = 200
 
 const Serial = {
   loadPorts: async () => {
-    let ports = await board.list_ports()
-    return ports.filter(p => p.vendorId && p.productId)
+    // let ports = await board.list_ports()
+    // return ports.filter(p => p.vendorId && p.productId)
+    return ipcRenderer.invoke('serial-list-ports')
   },
   connect: async (path) => {
-    return await board.open(path)
+    return ipcRenderer.invoke('serial-connect', path)
   },
   disconnect: async () => {
-    return await board.close()
+    return ipcRenderer.invoke('serial-disconnect')
   },
   run: async (code) => {
-    return board.run(code)
+    return ipcRenderer.invoke('serial-run', code)
   },
   stop: async () => {
-    await board.stop()
-    return Promise.resolve()
-  },
-  exit_raw_repl: async () => {
-    await board.exit_raw_repl()
-    return Promise.resolve()
+    return ipcRenderer.invoke('serial-stop')
   },
   reset: async () => {
-    await board.stop()
-    await board.exit_raw_repl()
-    await board.reset()
-    return Promise.resolve()
+    await ipcRenderer.invoke('serial-reset')
   },
   eval: (d) => {
-    return board.eval(d)
+    return ipcRenderer.invoke('serial-eval', d)
   },
   onData: (fn) => {
-    board.serial.on('data', fn)
+    ipcRenderer.on('terminal-data', (_e, data) => fn(data))
+  },
+  onDisconnect: async (fn) => {
+    ipcRenderer.on('disconnect', (_e) => fn())
   },
   listFiles: async (folder) => {
-    return board.fs_ls(folder)
+    return ipcRenderer.invoke('serial-list-files', folder)
   },
   ilistFiles: async (folder) => {
-    return board.fs_ils(folder)
+    return ipcRenderer.invoke('serial-ilist-files', folder)
   },
   loadFile: async (file) => {
-    const output = await board.fs_cat(file)
-    return output || ''
+    return ipcRenderer.invoke('serial-load-file', file)
   },
   removeFile: async (file) => {
-    return board.fs_rm(file)
+    return ipcRenderer.invoke('serial-remove-file', file)
   },
-  saveFileContent: async (filename, content, dataConsumer) => {
-    return board.fs_save(content || ' ', filename, dataConsumer)
+  saveFileContent: async (filename, content) => {
+    return ipcRenderer.invoke('serial-save-file', filename, content)
   },
-  uploadFile: async (src, dest, dataConsumer) => {
-    return board.fs_put(src, dest, dataConsumer)
+  uploadFile: async (src, dest) => {
+    return ipcRenderer.invoke('serial-upload', src, dest)
   },
   downloadFile: async (src, dest) => {
-    let contents = await Serial.loadFile(src)
-    return ipcRenderer.invoke('save-file', dest, contents)
+    return ipcRenderer.invoke('serial-download', src, dest)
   },
   renameFile: async (oldName, newName) => {
-    return board.fs_rename(oldName, newName)
-  },
-  onDisconnect: async (fn) => {
-    board.serial.on('close', fn)
+    return ipcRenderer.invoke('serial-rename-file', oldName, newName)
   },
   createFolder: async (folder) => {
-    return await board.fs_mkdir(folder)
+    return ipcRenderer.invoke('serial-create-folder', folder)
   },
   getNavigationPath: (navigation, target) => {
     return path.posix.join(navigation, target)
diff --git a/ui/arduino/store.js b/ui/arduino/store.js
index 3b5bedb..5ec5b91 100644
--- a/ui/arduino/store.js
+++ b/ui/arduino/store.js
@@ -93,8 +93,6 @@ function store(state, emitter) {
 
     // Stop whatever is going on
     await serial.stop()
-    // Recover from getting stuck in raw repl
-    await serial.exit_raw_repl()
 
     state.isConnected = true
     emitter.emit('close-port-dialog')
@@ -106,6 +104,7 @@ function store(state, emitter) {
     state.serialNavigation = ''
     emitter.emit('update-files')
 
+    state.blocking = false
     emitter.emit('message', 'Connected', 1000)
 
     if (!state.isFilesOpen) {
@@ -121,12 +120,14 @@ function store(state, emitter) {
         serial.eval(data)
         term.scrollToBottom()
       })
+      serial.onData((data) => {
+        console.log('received data', data)
+        term.write(data)
+        term.scrollToBottom()
+      })
     }
-    serial.onData((data) => {
-      term.write(data)
-      term.scrollToBottom()
-    })
     serial.onDisconnect(() => emitter.emit('disconnect'))
+    await serial.stop()
   })
 
   // CODE EXECUTION
@@ -142,11 +143,11 @@ function store(state, emitter) {
   emitter.on('stop', async () => {
     log('stop')
     await serial.stop()
-    await serial.exit_raw_repl()
     emitter.emit('render')
   })
   emitter.on('reset', async () => {
     log('reset')
+    await serial.stop()
     await serial.reset()
     emitter.emit('update-files')
     emitter.emit('render')
@@ -310,17 +311,13 @@ function store(state, emitter) {
     if (state.isConnected) {
       await serial.stop()
       try {
-        const files = await serial.ilistFiles(
+        state.serialFiles = await serial.ilistFiles(
           serial.getFullPath(
             state.serialPath,
             state.serialNavigation,
             ''
           )
         )
-        state.serialFiles = files.map(f => ({
-          path: f[0],
-          type: f[1] === 0x4000 ? 'folder' : 'file'
-        }))
         // Filter out dot files
         state.serialFiles = state.serialFiles.filter(
           f => f.path.indexOf('.') !== 0

From 75893c2ab48ce9173db55e7dde31d71d52b0499c Mon Sep 17 00:00:00 2001
From: Murilo Polese <murilopolese@gmail.com>
Date: Tue, 4 Jul 2023 09:11:53 +0200
Subject: [PATCH 2/2] Import serialport to list available boards

---
 index.js          |  34 +++----
 package-lock.json | 228 +++++++++++++++++++++++++++++++++++++++++++---
 package.json      |   3 +-
 preload.js        |  24 ++---
 4 files changed, 242 insertions(+), 47 deletions(-)

diff --git a/index.js b/index.js
index 2b2db92..0325a77 100644
--- a/index.js
+++ b/index.js
@@ -1,5 +1,6 @@
 const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron')
 const Micropython = require('micropython-ctl-cont').MicroPythonDevice
+const { SerialPort } = require('serialport')
 const path = require('path')
 const fs = require('fs')
 const openAboutWindow = require('about-window').default
@@ -42,7 +43,7 @@ function ilistFolder(folder) {
 }
 
 // LOCAL FILE SYSTEM ACCESS
-ipcMain.handle('open-folder', async (event) => {
+ipcMain.handle('disk-open-folder', async (event) => {
   console.log('ipcMain', 'open-folder')
   const folder = await openFolderDialog()
   let files = []
@@ -52,31 +53,31 @@ ipcMain.handle('open-folder', async (event) => {
   return { folder, files }
 })
 
-ipcMain.handle('list-files', async (event, folder) => {
+ipcMain.handle('disk-list-files', async (event, folder) => {
   console.log('ipcMain', 'list-files', folder)
   if (!folder) return []
   return listFolder(folder)
 })
 
-ipcMain.handle('ilist-files', async (event, folder) => {
+ipcMain.handle('disk-ilist-files', async (event, folder) => {
   console.log('ipcMain', 'ilist-files', folder)
   if (!folder) return []
   return ilistFolder(folder)
 })
 
-ipcMain.handle('load-file', (event, filePath) => {
+ipcMain.handle('disk-load-file', (event, filePath) => {
   console.log('ipcMain', 'load-file', filePath)
   let content = fs.readFileSync(filePath)
   return content
 })
 
-ipcMain.handle('save-file', (event, filePath, content) => {
+ipcMain.handle('disk-save-file', (event, filePath, content) => {
   console.log('ipcMain', 'save-file', filePath, content)
   fs.writeFileSync(filePath, content, 'utf8')
   return true
 })
 
-ipcMain.handle('update-folder', (event, folder) => {
+ipcMain.handle('disk-update-folder', (event, folder) => {
   console.log('ipcMain', 'update-folder', folder)
   let files = fs.readdirSync(path.resolve(folder))
   // Filter out directories
@@ -87,13 +88,13 @@ ipcMain.handle('update-folder', (event, folder) => {
   return { folder, files }
 })
 
-ipcMain.handle('remove-file', (event, filePath) => {
+ipcMain.handle('disk-remove-file', (event, filePath) => {
   console.log('ipcMain', 'remove-file', filePath)
   fs.unlinkSync(filePath)
   return true
 })
 
-ipcMain.handle('rename-file', (event, filePath, newFilePath) => {
+ipcMain.handle('disk-rename-file', (event, filePath, newFilePath) => {
   console.log('ipcMain', 'rename-file', filePath, newFilePath)
   fs.renameSync(filePath, newFilePath)
   return true
@@ -103,12 +104,7 @@ ipcMain.handle('rename-file', (event, filePath, newFilePath) => {
 const board = new Micropython()
 ipcMain.handle('serial-list-ports', (event) => {
   console.log('ipcMain', 'serial-list-ports')
-  return [
-    { path: '/dev/ttyACM0' },
-    { path: '/dev/ttyACM1' },
-    { path: '/dev/ttyUSB0' },
-    { path: '/dev/ttyUSB1' },
-  ]
+  return SerialPort.list()
 })
 ipcMain.handle('serial-connect', async (event, path) => {
   console.log('ipcMain', 'serial-connect', path)
@@ -134,11 +130,12 @@ ipcMain.handle('serial-run', async (event, code) => {
       disableDedent: true,
       broadcastOutputAsTerminalData: true
     })
+    board.sendData('\r\n')
+    return Promise.resolve(output)
   } catch(err) {
     console.log(err)
+    return Promise.reject(err)
   }
-  board.sendData('\r\n')
-  return Promise.resolve(output)
 })
 ipcMain.handle('serial-stop', async (event) => {
   console.log('ipcMain', 'serial-stop')
@@ -170,10 +167,9 @@ ipcMain.handle('serial-ilist-files', async (event, folder) => {
   console.log('ipcMain', 'serial-ilist-files', folder)
   let files = await board.listFiles(folder)
   files = files.map(f => ({
-    path: f.filename.slice(1),
+    path: f.filename.split('/').pop(),
     type: f.isDir ? 'folder' : 'file'
   }))
-  // console.log('yolo', files)
   return files
 })
 ipcMain.handle('serial-load-file', async (event, filePath) => {
@@ -214,7 +210,7 @@ ipcMain.handle('serial-create-folder', async (event, folder) => {
 
 
 // WINDOW MANAGEMENT
-ipcMain.handle('set-window-size', (event, minWidth, minHeight) => {
+ipcMain.handle('window-set-minimum-size', (event, minWidth, minHeight) => {
   console.log('ipcMain', 'set-window-size', minWidth, minHeight)
   if (!win) {
     console.log('No window defined')
diff --git a/package-lock.json b/package-lock.json
index 84dd1e9..16f1d69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,8 @@
 			"license": "MIT",
 			"dependencies": {
 				"about-window": "^1.15.2",
-				"micropython-ctl-cont": "^1.15.3"
+				"micropython-ctl-cont": "^1.15.3",
+				"serialport": "^11.0.0"
 			},
 			"devDependencies": {
 				"electron": "^19.0.10",
@@ -323,6 +324,18 @@
 				"node": ">=14"
 			}
 		},
+		"node_modules/@serialport/binding-mock": {
+			"version": "10.2.2",
+			"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
+			"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
+			"dependencies": {
+				"@serialport/bindings-interface": "^1.2.1",
+				"debug": "^4.3.3"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			}
+		},
 		"node_modules/@serialport/bindings-cpp": {
 			"version": "10.8.0",
 			"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.8.0.tgz",
@@ -380,6 +393,50 @@
 				"node": "^12.22 || ^14.13 || >=16"
 			}
 		},
+		"node_modules/@serialport/parser-byte-length": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-11.0.0.tgz",
+			"integrity": "sha512-rExsdFKdzOIHOBqTwzxUF1A9nrluVIZKZOtvMq5i0Hc3euooGdmkx0VXYNRlI2rd6kJLTL2P+uIR+ZtCTRyT+w==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/@serialport/parser-cctalk": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-11.0.0.tgz",
+			"integrity": "sha512-eN1MvEIFwI4GedWJhte6eWF+NZtrjchZbMf0CE6NV9TRzJI1KLnFf90ZOj/mhGuANojX4sqWfJKQXwN6E8VSHQ==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/@serialport/parser-delimiter": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz",
+			"integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/@serialport/parser-inter-byte-timeout": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-11.0.0.tgz",
+			"integrity": "sha512-RLgqZC50IET6FtEIt6Oi0vdRsesSBWLNwB7ldzR9OzyXKgK0XHRzqKqbB0u5Q+tC5OScdWeiQ2AO6jooKUZtsw==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
 		"node_modules/@serialport/parser-packet-length": {
 			"version": "10.5.0",
 			"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.5.0.tgz",
@@ -388,6 +445,42 @@
 				"node": ">=8.6.0"
 			}
 		},
+		"node_modules/@serialport/parser-readline": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz",
+			"integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==",
+			"dependencies": {
+				"@serialport/parser-delimiter": "11.0.0"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/@serialport/parser-ready": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-11.0.0.tgz",
+			"integrity": "sha512-lSsCPIctoc5kADCKnZDYBz1j69TsFqtnaWUicBzUAIAoUXpYKeYld8YX5NrvjViuVfIJeiqLZeGjxOWe5fqQqQ==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/@serialport/parser-regex": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-11.0.0.tgz",
+			"integrity": "sha512-aKuc/+/KE9swahTbYpSuOsQa7LggPx7jhfobJLPVVbAic80OpfCIY+MKr6Ax4R6UtQwF90O5Yk6OEmbbvtEmiA==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
 		"node_modules/@serialport/parser-slip-encoder": {
 			"version": "10.5.0",
 			"resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-10.5.0.tgz",
@@ -410,6 +503,21 @@
 				"url": "https://opencollective.com/serialport/donate"
 			}
 		},
+		"node_modules/@serialport/stream": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-11.0.0.tgz",
+			"integrity": "sha512-Zty7B8C1H2XRnay2mVmW1ygEHXRHXQDcaC5wAVvOZMbQSc7ye03rMlPvviDS+pGxU2t2A2bMo34CUrRduSBong==",
+			"dependencies": {
+				"@serialport/bindings-interface": "1.2.2",
+				"debug": "4.3.4"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
 		"node_modules/@sindresorhus/is": {
 			"version": "0.14.0",
 			"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -3570,18 +3678,6 @@
 				"mctl": "dist-node/cli/index.js"
 			}
 		},
-		"node_modules/micropython-ctl-cont/node_modules/@serialport/binding-mock": {
-			"version": "10.2.2",
-			"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
-			"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
-			"dependencies": {
-				"@serialport/bindings-interface": "^1.2.1",
-				"debug": "^4.3.3"
-			},
-			"engines": {
-				"node": ">=12.0.0"
-			}
-		},
 		"node_modules/micropython-ctl-cont/node_modules/@serialport/parser-byte-length": {
 			"version": "10.5.0",
 			"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.5.0.tgz",
@@ -4715,6 +4811,112 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/serialport": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/serialport/-/serialport-11.0.0.tgz",
+			"integrity": "sha512-bxs3XejQcOHWpzPAaXMhxVRlbem6fjNUrux3ToqrGvFR6BcjOYhqE5CsHOuutv37kmhmnuHrn+/hN+1BpTmaFg==",
+			"dependencies": {
+				"@serialport/binding-mock": "10.2.2",
+				"@serialport/bindings-cpp": "11.0.1",
+				"@serialport/parser-byte-length": "11.0.0",
+				"@serialport/parser-cctalk": "11.0.0",
+				"@serialport/parser-delimiter": "11.0.0",
+				"@serialport/parser-inter-byte-timeout": "11.0.0",
+				"@serialport/parser-packet-length": "11.0.0",
+				"@serialport/parser-readline": "11.0.0",
+				"@serialport/parser-ready": "11.0.0",
+				"@serialport/parser-regex": "11.0.0",
+				"@serialport/parser-slip-encoder": "11.0.0",
+				"@serialport/parser-spacepacket": "11.0.0",
+				"@serialport/stream": "11.0.0",
+				"debug": "4.3.4"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/bindings-cpp": {
+			"version": "11.0.1",
+			"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-11.0.1.tgz",
+			"integrity": "sha512-3I1mniVg3osYuIUXxU0jB5AHPsxWmErmc3JC3WfUSlfXsjWMHkHfFzbW9Scuv/z/6DLCJIDyltabRa2FoW2qsQ==",
+			"hasInstallScript": true,
+			"dependencies": {
+				"@serialport/bindings-interface": "1.2.2",
+				"@serialport/parser-readline": "10.5.0",
+				"debug": "4.3.4",
+				"node-addon-api": "6.1.0",
+				"node-gyp-build": "4.6.0"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.5.0.tgz",
+			"integrity": "sha512-/uR/yT3jmrcwnl2FJU/2ySvwgo5+XpksDUR4NF/nwTS5i3CcuKS+FKi/tLzy1k8F+rCx5JzpiK+koqPqOUWArA==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": {
+			"version": "10.5.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.5.0.tgz",
+			"integrity": "sha512-0aXJknodcl94W9zSjvU+sLdXiyEG2rqjQmvBWZCr8wJZjWEtv3RgrnYiWq4i2OTOyC8C/oPK8ZjpBjQptRsoJQ==",
+			"dependencies": {
+				"@serialport/parser-delimiter": "10.5.0"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/parser-packet-length": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-11.0.0.tgz",
+			"integrity": "sha512-6ZkOiaCooabpV/EM7ttSRbisbDWpGEf7Yxyr13t28LicYR43THRdjdMZcRnWxEM/jpwfskkLLXAR6wziVpKrlw==",
+			"engines": {
+				"node": ">=8.6.0"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/parser-slip-encoder": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-11.0.0.tgz",
+			"integrity": "sha512-3ZI/swd2it20vmu2tzqDbkyE4dqy+kExEDY6T33YQ210HDKPVhqj7FAVGo5P++MZ3dup1of11t4P9UPBNkuJnQ==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/@serialport/parser-spacepacket": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-11.0.0.tgz",
+			"integrity": "sha512-+hqRckrTEqz+/uAUZY0Tq6YIRyCl4oQOH1MeVzKiFiGNjZP7hDJCDoY7LTr9CeJhxvcT0ItTbtjGBqGumV8fxg==",
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/serialport/donate"
+			}
+		},
+		"node_modules/serialport/node_modules/node-addon-api": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
+			"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
+		},
 		"node_modules/serve-static": {
 			"version": "1.15.0",
 			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
diff --git a/package.json b/package.json
index e49d723..d83b16f 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,8 @@
 	"license": "MIT",
 	"dependencies": {
 		"about-window": "^1.15.2",
-		"micropython-ctl-cont": "^1.15.3"
+		"micropython-ctl-cont": "^1.15.3",
+		"serialport": "^11.0.0"
 	},
 	"engines": {
 		"node": "18"
diff --git a/preload.js b/preload.js
index 7a95094..0fffabe 100644
--- a/preload.js
+++ b/preload.js
@@ -2,14 +2,10 @@ console.log('preload')
 const { contextBridge, ipcRenderer } = require('electron')
 const path = require('path')
 
-const Micropython = require('micropython-ctl-cont').MicroPythonDevice
-const board = new Micropython()
-
 const Serial = {
   loadPorts: async () => {
-    // let ports = await board.list_ports()
-    // return ports.filter(p => p.vendorId && p.productId)
-    return ipcRenderer.invoke('serial-list-ports')
+    let ports = await ipcRenderer.invoke('serial-list-ports')
+    return ports.filter(p => p.vendorId && p.productId)
   },
   connect: async (path) => {
     return ipcRenderer.invoke('serial-connect', path)
@@ -75,26 +71,26 @@ const Serial = {
 
 const Disk = {
   openFolder: async () => {
-    return ipcRenderer.invoke('open-folder')
+    return ipcRenderer.invoke('disk-open-folder')
   },
   listFiles: async (folder) => {
-    return ipcRenderer.invoke('list-files', folder)
+    return ipcRenderer.invoke('disk-list-files', folder)
   },
   ilistFiles: async (folder) => {
-    return ipcRenderer.invoke('ilist-files', folder)
+    return ipcRenderer.invoke('disk-ilist-files', folder)
   },
   loadFile: async (filePath) => {
-    let content = await ipcRenderer.invoke('load-file', filePath)
+    let content = await ipcRenderer.invoke('disk-load-file', filePath)
     return new TextDecoder().decode(content)
   },
   removeFile: async (filePath) => {
-    return ipcRenderer.invoke('remove-file', filePath)
+    return ipcRenderer.invoke('disk-remove-file', filePath)
   },
   saveFileContent: async (filePath, content) => {
-    return ipcRenderer.invoke('save-file', filePath, content)
+    return ipcRenderer.invoke('disk-save-file', filePath, content)
   },
   renameFile: async (oldName, newName) => {
-    return ipcRenderer.invoke('rename-file', oldName, newName)
+    return ipcRenderer.invoke('disk-rename-file', oldName, newName)
   },
   getNavigationPath: (navigation, target) => {
     return path.join(navigation, target)
@@ -109,7 +105,7 @@ const Disk = {
 
 const Window = {
   setWindowSize: (minWidth, minHeight) => {
-    ipcRenderer.invoke('set-window-size', minWidth, minHeight)
+    ipcRenderer.invoke('window-set-minimum-size', minWidth, minHeight)
   }
 }
 
