diff --git a/package-lock.json b/package-lock.json index 02e3556..f4125cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "lucide-vue-next": "^0.309.0", "pinia": "^2.1.7", "radix-vue": "^1.3.0", + "serialport": "^12.0.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "vee-validate": "^4.12.4", @@ -42,6 +43,7 @@ "@electron-forge/maker-zip": "^7.2.0", "@electron-forge/plugin-auto-unpack-natives": "^7.2.0", "@electron-forge/plugin-vite": "^7.2.0", + "@electron/rebuild": "^3.6.0", "@intlify/eslint-plugin-vue-i18n": "^2.0.0", "@types/node": "^20.11.0", "@vitejs/plugin-vue": "^5.0.3", @@ -2903,6 +2905,195 @@ ], "peer": true }, + "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": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/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/bindings-cpp/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/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": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "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": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -4870,7 +5061,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -8094,8 +8284,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -8164,6 +8353,11 @@ "node": ">=10" } }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + }, "node_modules/node-api-version": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.0.tgz", @@ -8218,6 +8412,16 @@ "node": "^12.13 || ^14.13 || >=16" } }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -9771,6 +9975,33 @@ "randombytes": "^2.1.0" } }, + "node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -13247,6 +13478,109 @@ "optional": true, "peer": true }, + "@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==", + "requires": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + } + }, + "@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "requires": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "dependencies": { + "@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==" + }, + "@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==", + "requires": { + "@serialport/parser-delimiter": "11.0.0" + } + } + } + }, + "@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==" + }, + "@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==" + }, + "@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==" + }, + "@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==" + }, + "@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==" + }, + "@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==" + }, + "@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "requires": { + "@serialport/parser-delimiter": "12.0.0" + } + }, + "@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==" + }, + "@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==" + }, + "@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==" + }, + "@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==" + }, + "@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "requires": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + } + }, "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -14707,7 +15041,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -17122,8 +17455,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mz": { "version": "2.7.0", @@ -17174,6 +17506,11 @@ "semver": "^7.3.5" } }, + "node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + }, "node-api-version": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.0.tgz", @@ -17211,6 +17548,11 @@ "which": "^2.0.2" } }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" + }, "node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -18278,6 +18620,27 @@ "randombytes": "^2.1.0" } }, + "serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "requires": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + } + }, "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 57a2489..b884892 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,6 @@ "description": "Haptic configuration tool", "private": true, "main": ".vite/build/main.js", - "XXtype": "module", - "XXmain": "background.js", "scripts": { "start": "electron-forge start", "package": "electron-forge package", @@ -31,6 +29,7 @@ "lucide-vue-next": "^0.309.0", "pinia": "^2.1.7", "radix-vue": "^1.3.0", + "serialport": "^12.0.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "vee-validate": "^4.12.4", @@ -49,6 +48,7 @@ "@electron-forge/maker-zip": "^7.2.0", "@electron-forge/plugin-auto-unpack-natives": "^7.2.0", "@electron-forge/plugin-vite": "^7.2.0", + "@electron/rebuild": "^3.6.0", "@intlify/eslint-plugin-vue-i18n": "^2.0.0", "@types/node": "^20.11.0", "@vitejs/plugin-vue": "^5.0.3", diff --git a/src/backend/nano.js b/src/backend/nano.js deleted file mode 100644 index a7dc0a4..0000000 --- a/src/backend/nano.js +++ /dev/null @@ -1,19 +0,0 @@ - -const nano = { - get(pid, tid, vid) { - return new Promise((resolve, reject) => { - resolve({ pid: pid, tid: tid, vid: vid, value: 0.0 }); - }); - }, - - set(pid, tid, vid, value) { - return new Promise((resolve, reject) => { - resolve({ pid: pid, tid: tid, vid: vid, value: value }); - }); - }, - - onValueReceived(listener) { - } -}; - -export default nano; \ No newline at end of file diff --git a/src/backend/nanodevices.js b/src/backend/nanodevices.js index f1266a2..655272f 100644 --- a/src/backend/nanodevices.js +++ b/src/backend/nanodevices.js @@ -1,23 +1,137 @@ +import { SerialPort } from 'serialport'; + + +const NANO_PRODUCT_ID = '7523'; +const NANO_VENDOR_ID = '1a86'; +const NANO_BAUD_RATE = 115200; const nanodevices = { - list() { - return []; + + all_nano_devices: {}, + connected_nano_devices: {}, + + _list() { + let p = new Promise(); + SerialPort.list().then((ports, err) => { + if (err) { + p.reject(err); // TODO format for errors? + } + else { + console.log('ports', ports) + let found_nano_devices = [] + for (let port of ports) { + if (port.productId === NANO_PRODUCT_ID && port.vendorId === NANO_VENDOR_ID) { + found_nano_devices.push(port.serialNumber); + if (this.all_nano_devices[port.serialNumber] === undefined) { + this.all_nano_devices[port.serialNumber] = port; + this.emit('nanodevices:device-attached', { id: port.serialNumber }); + } + } + } + p.resolve(found_nano_devices); + for (let serialNumber in this.all_nano_devices) { + if (found_nano_devices.indexOf(serialNumber) === -1) { + delete this.all_nano_devices[serialNumber]; + this.emit('nanodevices:device-detached', { id: serialNumber }); + } + } + } + }) + return p; }, - async connect(devicename) { - return true; + + _handle_data(connected_port, data) { + connected_port.data += data; + let lines = connected_port.data.split('\n'); + if (lines.length > 1) { + for (let i = 0; i < lines.length - 1; i++) { + this.emit('nanodevices:update', connected_port.serialNumber, lines[i]); + } + connected_port.data = lines[lines.length - 1]; + } }, - async disconnect() { - return true; + + list_devices() { + let result = []; + for (const [key, value] of Object.entries(this.all_nano_devices)) { + if (value.serialNumber) + result.push(key); + } + return result; }, - onAttach(listener) { + + async send(deviceid, jsonstr) { + let connected_port = this.connected_nano_devices[deviceid]; + if (connected_port === undefined) { + return Promise.reject('Device not connected'); + } + else { + connected_port.port.write(jsonstr+'\n'); + return Promise.resolve(); + } }, - - onDetach(listener) { + + + async connect(deviceid) { + let p = new Promise(); + let nano_device = this.all_nano_devices[deviceid]; + if (nano_device === undefined) { + p.reject('Device not attached'); + } + else { + let port = new SerialPort(nano_device.path, { baudRate: NANO_BAUD_RATE, autoOpen: false }); + port.on('error', (err) => { + // forward error to FE + this.emit('nanodevices:error', { id: nano_device.serialNumber }, err); + }); + port.on('close', (err) => { + if (err && err.disconnected) { + // forward close to FE + this.emit('nanodevices:disconnected', { id: nano_device.serialNumber }); + } + delete this.connected_nano_devices[nano_device.serialNumber]; + }); + port.on('open', () => { + p.resolve(nano_device.serialNumber); + this.connected_nano_devices[nano_device.serialNumber] = { port: port, data: '' }; + this.emit('nanodevices:connected', { id: nano_device.serialNumber }); + }); + port.on('data', (data) => { + let connected_port = this.connected_nano_devices[nano_device.serialNumber]; + this._handle_data(connected_port, data); + }); + port.open((err)=>{ + if (err) { + p.reject(err); + } + }); + } + return p; + }, + + + disconnect(deviceid) { + let p = new Promise(); + let nano_device = this.all_nano_devices[deviceid]; + if (nano_device === undefined) { + p.reject('Device not attached'); + } + else { + if (this.connected_nano_devices[nano_device.serialNumber] === undefined) { + p.reject('Device not connected'); + } + else { + nano_device.close(); + p.resolve(nano_device.serialNumber); + } + } + return p; } + }; export default nanodevices; diff --git a/src/main.js b/src/main.js index fb1d2d3..dbb3994 100644 --- a/src/main.js +++ b/src/main.js @@ -3,8 +3,6 @@ import path from 'path' import ess from 'electron-squirrel-startup' import { ipcMain } from 'electron' import nanodevices from './backend/nanodevices.js' -import nano from './backend/nano.js' - // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (ess) { @@ -130,11 +128,10 @@ const createLoadingWindow = (mainWindow) => { // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(() => { - ipcMain.handle('nanodevices:list', nanodevices.list) + ipcMain.handle('nanodevices:list_devices', nanodevices.list_devices) ipcMain.handle('nanodevices:connect', nanodevices.connect) ipcMain.handle('nanodevices:disconnect', nanodevices.disconnect) - ipcMain.handle('nano:get', nano.get) - ipcMain.handle('nano:set', nano.set) + ipcMain.handle('nanodevices:send', nanodevices.send) const mainWindow = createMainWindow() createLoadingWindow(mainWindow) ipcMain.on('electron:minimizeWindow', () => mainWindow.minimize()) @@ -149,17 +146,33 @@ app.whenReady().then(() => { ipcMain.on('electron:openExternal', (_event, url) => shell.openExternal(url)) ipcMain.on('electron:openDevTools', () => mainWindow.webContents.toggleDevTools()) ipcMain.on('electron:reload', () => mainWindow.webContents.reloadIgnoringCache()) - nanodevices.onAttach((device) => { - console.log('Attached device', device) - mainWindow.webContents.send('nanodevice-attached', device) - }) - nanodevices.onDetach((device) => { - console.log('Detached device', device) - mainWindow.webContents.send('nanodevice-detached', device) - }) - nano.onValueReceived((value) => { - console.log('Value received', value) - mainWindow.webContents.send('nano-onvalue', value) + // nanodevices.on('nanodevices:device-attached', (evt, device) => { // TODO cumbersome, is there a shorthand line nanodevice-* ? + // console.log('Attached device', device) + // mainWindow.webContents.send('nanodevices:device-attached', device) + // }) + // nanodevices.on('nanodevices:device-detached', (device) => { + // console.log('Detached device', device) + // mainWindow.webContents.send('nanodevices:device-detached', device) + // }) + // nanodevices.on('nanodevices:connected', (device) => { + // console.log('Connected device', device) + // mainWindow.webContents.send('nanodevices:connected', device) + // }) + // nanodevices.on('nanodevices:disconnected', (device) => { + // console.log('Disconnected device', device) + // mainWindow.webContents.send('nanodevices:disconnected', device) + // }) + // nanodevices.on('nanodevices:error', (device, error) => { + // console.log('Error on device', device, error) + // mainWindow.webContents.send('nanodevices:error', device, error) + // }) + // nanodevices.on('nanodevices:update', (deviceid, jsonstr) => { + // console.log('Nano update', jsonstr) + // mainWindow.webContents.send('nanodevices:update', deviceid, jsonstr) + // }) + nanodevices.on('nanodevices:event', (eventid, deviceid, ...data) => { + console.log('Nano event', eventid, deviceid, data) + mainWindow.webContents.send('nanodevices:event', eventid, deviceid, ...data) }) const menu = new Menu() for (const menuItem of Object.values(appMenu)) { diff --git a/src/preload.js b/src/preload.js index a2da09c..2f76f0e 100644 --- a/src/preload.js +++ b/src/preload.js @@ -3,40 +3,27 @@ const { contextBridge, ipcRenderer } = require('electron') - // expose an API to choose available devices contextBridge.exposeInMainWorld('nanodevices', { - list() { - ipcRenderer.invoke('nanodevices:list') + list_devices() { + ipcRenderer.invoke('nanodevices:list_devices'); }, - connect(devicename) { - ipcRenderer.invoke('nanodevices:connect', devicename) + connect(deviceid) { + ipcRenderer.invoke('nanodevices:connect', deviceid); }, - disconnect() { - ipcRenderer.invoke('nanodevices:disconnect') + disconnect(deviceid) { + ipcRenderer.invoke('nanodevices:disconnect', deviceid); }, - on_device_attached(listener) { - ipcRenderer.on('nanodevice-attached', (_event, value) => listener(value)) - }, - on_device_detached(listener) { - ipcRenderer.on('nanodevice-detached', (_event, value) => listener(value)) - }, -}) + on(eventid_filter, callback) { + ipcRenderer.on('nanodevices:event', (_event, eventid, deviceid, ...data) => { + if (eventid_filter=="*" || eventid_filter==eventid) { + callback(eventid, deviceid, ...data); + } + }); + } +}); -// expose an API to communicate with the nano device -contextBridge.exposeInMainWorld('nanodevice', { - get_value(pid, tid, vid) { - ipcRenderer.invoke('nano:get', pid, tid, vid) - }, - set_value(pid, tid, vid, value) { - ipcRenderer.invoke('nano:set', pid, tid, vid, value) - }, - on_value(listener) { - ipcRenderer.on('nano-onvalue', (_event, value) => listener(value)) - }, -}) - contextBridge.exposeInMainWorld('electron', { platform: process.platform, isDevelopment: process.env.NODE_ENV !== 'production', @@ -47,8 +34,8 @@ contextBridge.exposeInMainWorld('electron', { onMaximized: (callback) => ipcRenderer.on('electron:maximized', callback), onUnmaximized: (callback) => ipcRenderer.on('electron:unmaximized', callback), onMenu: (callback) => ipcRenderer.on('electron:menu', (event, key) => { - callback(key) + callback(key); }), openDevTools: () => ipcRenderer.send('electron:openDevTools'), reload: () => ipcRenderer.send('electron:reload'), -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/src/renderer.js b/src/renderer.js index 0a291ae..0082be3 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -50,4 +50,9 @@ app.use(i18n) app.config.globalProperties.window = window -app.mount('#app') \ No newline at end of file +app.mount('#app') + + +window.nanodevices.on('*', (eventid, deviceid, ...data) => { + console.log(eventid, deviceid, data) +});