Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c12ef332e | |||
| 481dfaf8e5 | |||
| 6769ffeb20 | |||
| 0586775849 | |||
| 4766f47e7e | |||
| d2d7d8e323 | |||
| c5155fe641 | |||
| f1312fb5c6 | |||
| a592848f34 | |||
| 06b05b2dc1 |
+3
-1
@@ -59,7 +59,9 @@
|
|||||||
"three": "^0.162.0",
|
"three": "^0.162.0",
|
||||||
"urdf-loader": "^0.12.1",
|
"urdf-loader": "^0.12.1",
|
||||||
"uzip": "^0.20201231.0",
|
"uzip": "^0.20201231.0",
|
||||||
"xacro-parser": "^0.3.9"
|
"xacro-parser": "^0.3.9",
|
||||||
|
"@types/msgpack-lite": "^0.1.11",
|
||||||
|
"msgpack-lite": "^0.1.26"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.3.0"
|
"packageManager": "pnpm@9.3.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+91
-32
@@ -13,10 +13,13 @@ importers:
|
|||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
'@sveltejs/adapter-auto':
|
'@sveltejs/adapter-auto':
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))
|
version: 4.0.0(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.0.12
|
specifier: ^4.0.12
|
||||||
version: 4.0.12(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
version: 4.0.12(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
|
'@types/msgpack-lite':
|
||||||
|
specifier: ^0.1.11
|
||||||
|
version: 0.1.11
|
||||||
chart.js:
|
chart.js:
|
||||||
specifier: ^4.4.2
|
specifier: ^4.4.2
|
||||||
version: 4.4.2
|
version: 4.4.2
|
||||||
@@ -32,6 +35,9 @@ importers:
|
|||||||
jwt-decode:
|
jwt-decode:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
|
msgpack-lite:
|
||||||
|
specifier: ^0.1.26
|
||||||
|
version: 0.1.26
|
||||||
nipplejs:
|
nipplejs:
|
||||||
specifier: ^0.10.1
|
specifier: ^0.10.1
|
||||||
version: 0.10.1
|
version: 0.10.1
|
||||||
@@ -65,13 +71,13 @@ importers:
|
|||||||
version: 1.49.1
|
version: 1.49.1
|
||||||
'@sveltejs/adapter-static':
|
'@sveltejs/adapter-static':
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))
|
version: 3.0.1(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))
|
||||||
'@sveltejs/kit':
|
'@sveltejs/kit':
|
||||||
specifier: ^2.5.27
|
specifier: ^2.5.27
|
||||||
version: 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
version: 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
'@sveltejs/vite-plugin-svelte':
|
'@sveltejs/vite-plugin-svelte':
|
||||||
specifier: ^5.0.3
|
specifier: ^5.0.3
|
||||||
version: 5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
version: 5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^8.56.0
|
specifier: ^8.56.0
|
||||||
version: 8.56.0
|
version: 8.56.0
|
||||||
@@ -128,10 +134,10 @@ importers:
|
|||||||
version: 0.18.5
|
version: 0.18.5
|
||||||
vite:
|
vite:
|
||||||
specifier: ^6.2.1
|
specifier: ^6.2.1
|
||||||
version: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
version: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0(jsdom@24.0.0)(lightningcss@1.29.2)
|
version: 1.2.0(@types/node@24.0.10)(jsdom@24.0.0)(lightningcss@1.29.2)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -760,6 +766,12 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
|
'@types/msgpack-lite@0.1.11':
|
||||||
|
resolution: {integrity: sha512-cdCZS/gw+jIN22I4SUZUFf1ZZfVv5JM1//Br/MuZcI373sxiy3eSSoiyLu0oz+BPatTbGGGBO5jrcvd0siCdTQ==}
|
||||||
|
|
||||||
|
'@types/node@24.0.10':
|
||||||
|
resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==}
|
||||||
|
|
||||||
'@types/semver@7.5.8':
|
'@types/semver@7.5.8':
|
||||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||||
|
|
||||||
@@ -1190,6 +1202,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
event-lite@0.1.3:
|
||||||
|
resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==}
|
||||||
|
|
||||||
execa@5.1.1:
|
execa@5.1.1:
|
||||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -1335,6 +1350,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
ieee754@1.2.1:
|
||||||
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
ignore@5.3.1:
|
ignore@5.3.1:
|
||||||
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -1356,6 +1374,9 @@ packages:
|
|||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
int64-buffer@0.1.10:
|
||||||
|
resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==}
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1394,6 +1415,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
|
isarray@1.0.0:
|
||||||
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
|
||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
@@ -1592,6 +1616,10 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
msgpack-lite@0.1.26:
|
||||||
|
resolution: {integrity: sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
nanoid@3.3.7:
|
nanoid@3.3.7:
|
||||||
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
@@ -2019,6 +2047,9 @@ packages:
|
|||||||
ufo@1.5.3:
|
ufo@1.5.3:
|
||||||
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
|
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
|
||||||
|
|
||||||
|
undici-types@7.8.0:
|
||||||
|
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
|
||||||
|
|
||||||
universalify@0.2.0:
|
universalify@0.2.0:
|
||||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
@@ -2613,18 +2644,18 @@ snapshots:
|
|||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
'@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))':
|
'@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/kit': 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
'@sveltejs/kit': 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
import-meta-resolve: 4.1.0
|
import-meta-resolve: 4.1.0
|
||||||
|
|
||||||
'@sveltejs/adapter-static@3.0.1(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))':
|
'@sveltejs/adapter-static@3.0.1(@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/kit': 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
'@sveltejs/kit': 2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
|
|
||||||
'@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
'@sveltejs/kit@2.17.3(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
'@types/cookie': 0.6.0
|
'@types/cookie': 0.6.0
|
||||||
cookie: 0.6.0
|
cookie: 0.6.0
|
||||||
devalue: 5.1.1
|
devalue: 5.1.1
|
||||||
@@ -2637,27 +2668,27 @@ snapshots:
|
|||||||
set-cookie-parser: 2.6.0
|
set-cookie-parser: 2.6.0
|
||||||
sirv: 3.0.1
|
sirv: 3.0.1
|
||||||
svelte: 5.20.4
|
svelte: 5.20.4
|
||||||
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
vite: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
|
|
||||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
svelte: 5.20.4
|
svelte: 5.20.4
|
||||||
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
vite: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)))(svelte@5.20.4)(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
deepmerge: 4.3.1
|
deepmerge: 4.3.1
|
||||||
kleur: 4.1.5
|
kleur: 4.1.5
|
||||||
magic-string: 0.30.17
|
magic-string: 0.30.17
|
||||||
svelte: 5.20.4
|
svelte: 5.20.4
|
||||||
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
vite: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
vitefu: 1.0.6(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
vitefu: 1.0.6(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -2714,13 +2745,13 @@ snapshots:
|
|||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.0.12
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.0.12
|
||||||
'@tailwindcss/oxide-win32-x64-msvc': 4.0.12
|
'@tailwindcss/oxide-win32-x64-msvc': 4.0.12
|
||||||
|
|
||||||
'@tailwindcss/vite@4.0.12(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
'@tailwindcss/vite@4.0.12(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.0.12
|
'@tailwindcss/node': 4.0.12
|
||||||
'@tailwindcss/oxide': 4.0.12
|
'@tailwindcss/oxide': 4.0.12
|
||||||
lightningcss: 1.29.2
|
lightningcss: 1.29.2
|
||||||
tailwindcss: 4.0.12
|
tailwindcss: 4.0.12
|
||||||
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
vite: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.2': {}
|
'@tweenjs/tween.js@23.1.2': {}
|
||||||
|
|
||||||
@@ -2737,6 +2768,14 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/msgpack-lite@0.1.11':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.0.10
|
||||||
|
|
||||||
|
'@types/node@24.0.10':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.8.0
|
||||||
|
|
||||||
'@types/semver@7.5.8': {}
|
'@types/semver@7.5.8': {}
|
||||||
|
|
||||||
'@types/stats.js@0.17.3': {}
|
'@types/stats.js@0.17.3': {}
|
||||||
@@ -3259,6 +3298,8 @@ snapshots:
|
|||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
|
event-lite@0.1.3: {}
|
||||||
|
|
||||||
execa@5.1.1:
|
execa@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.3
|
cross-spawn: 7.0.3
|
||||||
@@ -3414,6 +3455,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@5.3.1: {}
|
ignore@5.3.1: {}
|
||||||
|
|
||||||
import-fresh@3.3.0:
|
import-fresh@3.3.0:
|
||||||
@@ -3432,6 +3475,8 @@ snapshots:
|
|||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
|
int64-buffer@0.1.10: {}
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions: 2.3.0
|
binary-extensions: 2.3.0
|
||||||
@@ -3458,6 +3503,8 @@ snapshots:
|
|||||||
|
|
||||||
is-stream@3.0.0: {}
|
is-stream@3.0.0: {}
|
||||||
|
|
||||||
|
isarray@1.0.0: {}
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
@@ -3635,6 +3682,13 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
msgpack-lite@0.1.26:
|
||||||
|
dependencies:
|
||||||
|
event-lite: 0.1.3
|
||||||
|
ieee754: 1.2.1
|
||||||
|
int64-buffer: 0.1.10
|
||||||
|
isarray: 1.0.0
|
||||||
|
|
||||||
nanoid@3.3.7: {}
|
nanoid@3.3.7: {}
|
||||||
|
|
||||||
nanoid@3.3.8: {}
|
nanoid@3.3.8: {}
|
||||||
@@ -4010,6 +4064,8 @@ snapshots:
|
|||||||
|
|
||||||
ufo@1.5.3: {}
|
ufo@1.5.3: {}
|
||||||
|
|
||||||
|
undici-types@7.8.0: {}
|
||||||
|
|
||||||
universalify@0.2.0: {}
|
universalify@0.2.0: {}
|
||||||
|
|
||||||
unplugin-icons@0.18.5:
|
unplugin-icons@0.18.5:
|
||||||
@@ -4054,13 +4110,13 @@ snapshots:
|
|||||||
|
|
||||||
uzip@0.20201231.0: {}
|
uzip@0.20201231.0: {}
|
||||||
|
|
||||||
vite-node@1.2.0(lightningcss@1.29.2):
|
vite-node@1.2.0(@types/node@24.0.10)(lightningcss@1.29.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
vite: 5.4.14(lightningcss@1.29.2)
|
vite: 5.4.14(@types/node@24.0.10)(lightningcss@1.29.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- less
|
- less
|
||||||
@@ -4072,31 +4128,33 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
vite@5.4.14(lightningcss@1.29.2):
|
vite@5.4.14(@types/node@24.0.10)(lightningcss@1.29.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
rollup: 4.34.8
|
rollup: 4.34.8
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@types/node': 24.0.10
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
lightningcss: 1.29.2
|
lightningcss: 1.29.2
|
||||||
|
|
||||||
vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2):
|
vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.0
|
esbuild: 0.25.0
|
||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
rollup: 4.34.8
|
rollup: 4.34.8
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@types/node': 24.0.10
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.4.2
|
jiti: 2.4.2
|
||||||
lightningcss: 1.29.2
|
lightningcss: 1.29.2
|
||||||
yaml: 2.4.2
|
yaml: 2.4.2
|
||||||
|
|
||||||
vitefu@1.0.6(vite@6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)):
|
vitefu@1.0.6(vite@6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
vite: 6.2.1(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2)
|
||||||
|
|
||||||
vitest@1.2.0(jsdom@24.0.0)(lightningcss@1.29.2):
|
vitest@1.2.0(@types/node@24.0.10)(jsdom@24.0.0)(lightningcss@1.29.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 1.2.0
|
'@vitest/expect': 1.2.0
|
||||||
'@vitest/runner': 1.2.0
|
'@vitest/runner': 1.2.0
|
||||||
@@ -4116,10 +4174,11 @@ snapshots:
|
|||||||
strip-literal: 1.3.0
|
strip-literal: 1.3.0
|
||||||
tinybench: 2.8.0
|
tinybench: 2.8.0
|
||||||
tinypool: 0.8.4
|
tinypool: 0.8.4
|
||||||
vite: 5.4.14(lightningcss@1.29.2)
|
vite: 5.4.14(@types/node@24.0.10)(lightningcss@1.29.2)
|
||||||
vite-node: 1.2.0(lightningcss@1.29.2)
|
vite-node: 1.2.0(@types/node@24.0.10)(lightningcss@1.29.2)
|
||||||
why-is-node-running: 2.2.2
|
why-is-node-running: 2.2.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@types/node': 24.0.10
|
||||||
jsdom: 24.0.0
|
jsdom: 24.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- less
|
- less
|
||||||
|
|||||||
+128
-118
@@ -1,122 +1,132 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const;
|
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const
|
||||||
type SocketEvent = (typeof socketEvents)[number];
|
type SocketEvent = (typeof socketEvents)[number]
|
||||||
|
|
||||||
function createWebSocket() {
|
export enum Topics {
|
||||||
let listeners = new Map<string, Set<(data?: unknown) => void>>();
|
imu = 0,
|
||||||
const { subscribe, set } = writable(false);
|
mode = 1,
|
||||||
const reconnectTimeoutTime = 5000;
|
command = 2,
|
||||||
let unresponsiveTimeoutId: number;
|
servo = 3,
|
||||||
let reconnectTimeoutId: number;
|
input = 4,
|
||||||
let ws: WebSocket;
|
angles = 5,
|
||||||
let socketUrl: string | URL;
|
position = 6
|
||||||
|
|
||||||
function init(url: string | URL) {
|
|
||||||
socketUrl = url;
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
function disconnect(reason: SocketEvent, event?: Event) {
|
|
||||||
ws.close();
|
|
||||||
set(false);
|
|
||||||
clearTimeout(unresponsiveTimeoutId);
|
|
||||||
clearTimeout(reconnectTimeoutId);
|
|
||||||
listeners.get(reason)?.forEach((listener) => listener(event));
|
|
||||||
reconnectTimeoutId = setTimeout(connect, reconnectTimeoutTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
ws = new WebSocket(socketUrl);
|
|
||||||
ws.onopen = (ev) => {
|
|
||||||
set(true);
|
|
||||||
clearTimeout(reconnectTimeoutId);
|
|
||||||
listeners.get('open')?.forEach((listener) => listener(ev));
|
|
||||||
for (const event of listeners.keys()) {
|
|
||||||
if (socketEvents.includes(event as SocketEvent)) continue;
|
|
||||||
subscribeToEvent(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ws.onmessage = (message) => {
|
|
||||||
resetUnresponsiveCheck();
|
|
||||||
let data = message.data;
|
|
||||||
if (data instanceof ArrayBuffer) {
|
|
||||||
listeners.get('binary')?.forEach((listener) => listener(data));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data = data.substring(1);
|
|
||||||
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
let event = data.substring(data.indexOf('/') + 1, data.indexOf('['));
|
|
||||||
let payload = data.substring(data.indexOf('[') + 1, data.lastIndexOf(']'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(payload);
|
|
||||||
} catch (error) {}
|
|
||||||
if (event) listeners.get(event)?.forEach((listener) => listener(payload));
|
|
||||||
};
|
|
||||||
ws.onerror = (ev) => disconnect('error', ev);
|
|
||||||
ws.onclose = (ev) => disconnect('close', ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsubscribe(event: string, listener?: (data: any) => void) {
|
|
||||||
let eventListeners = listeners.get(event);
|
|
||||||
if (!eventListeners) return;
|
|
||||||
|
|
||||||
if (!eventListeners.size) {
|
|
||||||
unsubscribeToEvent(event);
|
|
||||||
}
|
|
||||||
if (listener) {
|
|
||||||
eventListeners?.delete(listener);
|
|
||||||
} else {
|
|
||||||
listeners.delete(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetUnresponsiveCheck() {
|
|
||||||
clearTimeout(unresponsiveTimeoutId);
|
|
||||||
unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), reconnectTimeoutTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendEvent(event: string, data: unknown) {
|
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
||||||
ws.send(`2/${event}[${JSON.stringify(data)}]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsubscribeToEvent(event: string) {
|
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
||||||
ws.send('1/' + event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeToEvent(event: string) {
|
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
||||||
ws.send('0/' + event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
sendEvent,
|
|
||||||
init,
|
|
||||||
on: <T>(event: string, listener: (data: T) => void): (() => void) => {
|
|
||||||
let eventListeners = listeners.get(event);
|
|
||||||
if (!eventListeners) {
|
|
||||||
if (!socketEvents.includes(event as SocketEvent)) {
|
|
||||||
subscribeToEvent(event);
|
|
||||||
}
|
|
||||||
eventListeners = new Set();
|
|
||||||
listeners.set(event, eventListeners);
|
|
||||||
}
|
|
||||||
eventListeners.add(listener as (data: any) => void);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe(event, listener);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
off: (event: string, listener?: (data: any) => void) => {
|
|
||||||
unsubscribe(event, listener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socket = createWebSocket();
|
function createWebSocket() {
|
||||||
|
let listeners = new Map<string | Topics, Set<(data?: unknown) => void>>()
|
||||||
|
const { subscribe, set } = writable(false)
|
||||||
|
const reconnectTimeoutTime = 5000
|
||||||
|
let unresponsiveTimeoutId: number
|
||||||
|
let reconnectTimeoutId: number
|
||||||
|
let ws: WebSocket
|
||||||
|
let socketUrl: string | URL
|
||||||
|
|
||||||
|
function init(url: string | URL) {
|
||||||
|
socketUrl = url
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect(reason: SocketEvent, event?: Event) {
|
||||||
|
ws.close()
|
||||||
|
set(false)
|
||||||
|
clearTimeout(unresponsiveTimeoutId)
|
||||||
|
clearTimeout(reconnectTimeoutId)
|
||||||
|
listeners.get(reason)?.forEach(listener => listener(event))
|
||||||
|
reconnectTimeoutId = setTimeout(connect, reconnectTimeoutTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
ws = new WebSocket(socketUrl)
|
||||||
|
ws.onopen = ev => {
|
||||||
|
set(true)
|
||||||
|
clearTimeout(reconnectTimeoutId)
|
||||||
|
listeners.get('open')?.forEach(listener => listener(ev))
|
||||||
|
for (const event of listeners.keys()) {
|
||||||
|
if (socketEvents.includes(event as SocketEvent)) continue
|
||||||
|
subscribeToEvent(event as unknown as Topics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.onmessage = message => {
|
||||||
|
resetUnresponsiveCheck()
|
||||||
|
let data = message.data
|
||||||
|
if (data instanceof ArrayBuffer) {
|
||||||
|
listeners.get('binary')?.forEach(listener => listener(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = data.substring(1)
|
||||||
|
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
|
let event = data.substring(data.indexOf('/') + 1, data.indexOf('['))
|
||||||
|
let payload = data.substring(data.indexOf('[') + 1, data.lastIndexOf(']'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(payload)
|
||||||
|
} catch (error) {}
|
||||||
|
if (event) listeners.get(event)?.forEach(listener => listener(payload))
|
||||||
|
}
|
||||||
|
ws.onerror = ev => disconnect('error', ev)
|
||||||
|
ws.onclose = ev => disconnect('close', ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe(event: Topics, listener?: (data: any) => void) {
|
||||||
|
let eventListeners = listeners.get(event)
|
||||||
|
if (!eventListeners) return
|
||||||
|
|
||||||
|
if (!eventListeners.size) {
|
||||||
|
unsubscribeToEvent(event)
|
||||||
|
}
|
||||||
|
if (listener) {
|
||||||
|
eventListeners?.delete(listener)
|
||||||
|
} else {
|
||||||
|
listeners.delete(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUnresponsiveCheck() {
|
||||||
|
clearTimeout(unresponsiveTimeoutId)
|
||||||
|
unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), reconnectTimeoutTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendEvent(event: Topics, data: unknown) {
|
||||||
|
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
||||||
|
ws.send(JSON.stringify([2, event, data]))
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribeToEvent(event: Topics) {
|
||||||
|
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
||||||
|
ws.send(`[1,${event}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeToEvent(event: Topics) {
|
||||||
|
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
||||||
|
ws.send(`[0,${event}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
sendEvent,
|
||||||
|
init,
|
||||||
|
on: <T>(event: Topics | SocketEvent, listener: (data: T) => void): (() => void) => {
|
||||||
|
let eventListeners = listeners.get(event)
|
||||||
|
if (!eventListeners) {
|
||||||
|
if (!socketEvents.includes(event)) {
|
||||||
|
subscribeToEvent(event)
|
||||||
|
}
|
||||||
|
eventListeners = new Set()
|
||||||
|
listeners.set(event, eventListeners)
|
||||||
|
}
|
||||||
|
eventListeners.add(listener as (data: any) => void)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe(event, listener)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
off: (event: Topics, listener?: (data: any) => void) => {
|
||||||
|
unsubscribe(event, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const socket = createWebSocket()
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
data/
|
data/
|
||||||
www/
|
www/
|
||||||
build/
|
build/
|
||||||
lib/ESP32-sveltekit/WWWData.h
|
include/WWWData.h
|
||||||
**/.vscode/c_cpp_properties.json
|
**/.vscode/c_cpp_properties.json
|
||||||
**/.vscode/launch.json
|
**/.vscode/launch.json
|
||||||
@@ -6,3 +6,5 @@ build_flags =
|
|||||||
-D SERVE_CONFIG_FILES
|
-D SERVE_CONFIG_FILES
|
||||||
-D CORS_ORIGIN=\"*\"
|
-D CORS_ORIGIN=\"*\"
|
||||||
-D ENABLE_CORS
|
-D ENABLE_CORS
|
||||||
|
-D USE_MSGPACK=1 ; Use either msgpack or json
|
||||||
|
-D USE_JSON=0 ; Use either msgpack or json
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
|
||||||
|
#include "comm_base.hpp"
|
||||||
|
#include "event_bus.hpp"
|
||||||
|
#include "topic.hpp"
|
||||||
|
|
||||||
|
class BluetoothService : public CommBase<> {
|
||||||
|
BLEServer* bleServer {nullptr};
|
||||||
|
BLECharacteristic* txCharacteristic {nullptr};
|
||||||
|
BLECharacteristic* rxCharacteristic {nullptr};
|
||||||
|
bool connected {false};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void begin(const char* name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleReceive(const std::string& data);
|
||||||
|
void send(size_t clientId, const char* data, size_t len) override;
|
||||||
|
|
||||||
|
struct ServerCb : BLEServerCallbacks {
|
||||||
|
BluetoothService* svc;
|
||||||
|
ServerCb(BluetoothService* s) : svc(s) {}
|
||||||
|
void onConnect(BLEServer*) override { svc->connected = true; }
|
||||||
|
void onDisconnect(BLEServer* s) override {
|
||||||
|
svc->connected = false;
|
||||||
|
for (size_t i = 0; i < static_cast<size_t>(Topic::COUNT); ++i) svc->unsubscribe(static_cast<Topic>(i), 0);
|
||||||
|
svc->bleServer->startAdvertising();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RxCb : BLECharacteristicCallbacks {
|
||||||
|
BluetoothService* svc;
|
||||||
|
RxCb(BluetoothService* s) : svc(s) {}
|
||||||
|
void onWrite(BLECharacteristic* c) override {
|
||||||
|
auto v = c->getValue();
|
||||||
|
if (!v.empty()) svc->handleReceive(v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "event_bus.hpp"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <array>
|
||||||
|
#include <bitset>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include "topic.hpp"
|
||||||
|
|
||||||
|
#ifndef MAX_CID
|
||||||
|
#define MAX_CID 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum class MsgKind : uint8_t { Connect = 0, Disconnect = 1, Event = 2, Ping = 3, Pong = 4 };
|
||||||
|
|
||||||
|
template <size_t MaxCid = MAX_CID, size_t NTopics = static_cast<size_t>(Topic::COUNT)>
|
||||||
|
class CommBase {
|
||||||
|
using Bits = std::bitset<MaxCid>;
|
||||||
|
std::array<Bits, NTopics> subs_;
|
||||||
|
portMUX_TYPE mux_ portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
std::array<void*, NTopics> subscriptionHandle {};
|
||||||
|
|
||||||
|
static constexpr size_t invalid = SIZE_MAX;
|
||||||
|
|
||||||
|
static constexpr size_t idx(Topic t) {
|
||||||
|
size_t i = static_cast<size_t>(t);
|
||||||
|
return i < NTopics ? i : invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Topic T>
|
||||||
|
void encode(JsonDocument& d, const typename TopicTraits<T>::Msg& m) {
|
||||||
|
auto a = d.to<JsonArray>();
|
||||||
|
a.add(static_cast<uint8_t>(MsgKind::Event));
|
||||||
|
a.add(static_cast<uint8_t>(T));
|
||||||
|
toJson(a.add<JsonVariant>(), m);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void send(size_t cid, const char* data, size_t len) = 0;
|
||||||
|
|
||||||
|
template <class Msg>
|
||||||
|
auto& getHandle(Topic topic) {
|
||||||
|
using H = typename EventBus<Msg>::Handle;
|
||||||
|
static H dummy;
|
||||||
|
auto* p = static_cast<H*>(subscriptionHandle[size_t(topic)]);
|
||||||
|
return p ? *p : dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Msg>
|
||||||
|
void setHandle(Topic topic, typename EventBus<Msg>::Handle&& h) {
|
||||||
|
using H = typename EventBus<Msg>::Handle;
|
||||||
|
subscriptionHandle[size_t(topic)] = new H(std::move(h));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void subscribe(Topic t, size_t cid) {
|
||||||
|
size_t i = idx(t);
|
||||||
|
if (i == invalid) return;
|
||||||
|
portENTER_CRITICAL(&mux_);
|
||||||
|
subs_[i].set(cid);
|
||||||
|
portEXIT_CRITICAL(&mux_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unsubscribe(Topic t, size_t cid) {
|
||||||
|
size_t i = idx(t);
|
||||||
|
if (i == invalid) return;
|
||||||
|
portENTER_CRITICAL(&mux_);
|
||||||
|
subs_[i].reset(cid);
|
||||||
|
portEXIT_CRITICAL(&mux_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has(Topic t) const {
|
||||||
|
size_t i = idx(t);
|
||||||
|
return i == invalid ? false : subs_[i].any();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Topic T>
|
||||||
|
void emit(const typename TopicTraits<T>::Msg& m) {
|
||||||
|
constexpr size_t i = idx(T);
|
||||||
|
if (i == invalid) return;
|
||||||
|
if (!subs_[i].any()) return;
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
encode<T>(doc, m);
|
||||||
|
String out;
|
||||||
|
#if USE_MSGPACK
|
||||||
|
serializeMsgPack(doc, out);
|
||||||
|
#else
|
||||||
|
serializeJson(doc, out);
|
||||||
|
#endif
|
||||||
|
auto& b = subs_[i];
|
||||||
|
for (size_t cid = 0; cid < MaxCid; ++cid)
|
||||||
|
if (b.test(cid)) send(cid, out.c_str(), out.length());
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef Socket_h
|
||||||
|
#define Socket_h
|
||||||
|
|
||||||
|
#include <PsychicHttp.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "event_bus.hpp"
|
||||||
|
#include "adapters/comm_base.hpp"
|
||||||
|
#include "topic.hpp"
|
||||||
|
|
||||||
|
class EventSocket : public CommBase<> {
|
||||||
|
PsychicWebSocketHandler _socket;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventSocket();
|
||||||
|
PsychicWebSocketHandler *getHandler() { return &_socket; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void send(size_t clientId, const char *data, size_t len) override;
|
||||||
|
|
||||||
|
void handleReceive(const std::string &data);
|
||||||
|
|
||||||
|
void onWSOpen(PsychicWebSocketClient *client);
|
||||||
|
void onWSClose(PsychicWebSocketClient *client);
|
||||||
|
esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern EventSocket socket;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
|
template <typename Sig, size_t MaxSize>
|
||||||
|
class FixedFn;
|
||||||
|
|
||||||
|
template <typename R, typename... A, size_t MaxSize>
|
||||||
|
class FixedFn<R(A...), MaxSize> {
|
||||||
|
alignas(void*) std::byte buf[MaxSize];
|
||||||
|
void (*call)(void*, A&&...) {};
|
||||||
|
void (*moveFn)(void*, void*) {};
|
||||||
|
void (*destroy)(void*) {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename Fun>
|
||||||
|
void set(Fun&& f) {
|
||||||
|
static_assert(sizeof(Fun) <= MaxSize);
|
||||||
|
new (buf) Fun(std::forward<Fun>(f));
|
||||||
|
call = [](void* p, A&&... as) { (*reinterpret_cast<Fun*>(p))(std::forward<A>(as)...); };
|
||||||
|
moveFn = [](void* d, void* s) { new (d) Fun(std::move(*reinterpret_cast<Fun*>(s))); };
|
||||||
|
destroy = [](void* p) { reinterpret_cast<Fun*>(p)->~Fun(); };
|
||||||
|
}
|
||||||
|
R operator()(A... as) const {
|
||||||
|
return call(const_cast<void*>(static_cast<const void*>(buf)), std::forward<A>(as)...);
|
||||||
|
}
|
||||||
|
FixedFn() = default;
|
||||||
|
FixedFn(FixedFn&& o) {
|
||||||
|
if (o.moveFn) o.moveFn(buf, o.buf);
|
||||||
|
call = o.call;
|
||||||
|
moveFn = o.moveFn;
|
||||||
|
destroy = o.destroy;
|
||||||
|
o.destroy = nullptr;
|
||||||
|
}
|
||||||
|
FixedFn(const FixedFn& o) {
|
||||||
|
std::memcpy(buf, o.buf, MaxSize);
|
||||||
|
call = o.call;
|
||||||
|
moveFn = o.moveFn;
|
||||||
|
destroy = o.destroy;
|
||||||
|
}
|
||||||
|
~FixedFn() {
|
||||||
|
if (destroy) destroy(buf);
|
||||||
|
}
|
||||||
|
FixedFn& operator=(const FixedFn&) = delete;
|
||||||
|
FixedFn& operator=(FixedFn&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EmitMode { Latest, Batch };
|
||||||
|
|
||||||
|
template <typename Msg, size_t QueueDepth = 64, size_t MaxSubs = 8, size_t BatchSize = 16>
|
||||||
|
class EventBus {
|
||||||
|
struct Item {
|
||||||
|
Msg payload;
|
||||||
|
size_t exclude;
|
||||||
|
};
|
||||||
|
static constexpr size_t NO_EX = MaxSubs;
|
||||||
|
struct Sub {
|
||||||
|
FixedFn<void(const Msg*, size_t), 48> cb;
|
||||||
|
TickType_t interval;
|
||||||
|
TickType_t last;
|
||||||
|
EmitMode mode;
|
||||||
|
std::array<Msg, BatchSize> buf;
|
||||||
|
size_t cnt;
|
||||||
|
};
|
||||||
|
inline static StaticQueue_t qbuf;
|
||||||
|
inline static Item qStorage[QueueDepth];
|
||||||
|
inline static QueueHandle_t queue =
|
||||||
|
xQueueCreateStatic(QueueDepth, sizeof(Item), reinterpret_cast<uint8_t*>(qStorage), &qbuf);
|
||||||
|
inline static std::array<std::optional<Sub>, MaxSubs> subs {};
|
||||||
|
inline static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
inline static Msg latest {};
|
||||||
|
inline static std::atomic<bool> hasLatest {false};
|
||||||
|
inline static std::atomic<size_t> subCount {0};
|
||||||
|
|
||||||
|
static void store(const Msg& m) {
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
latest = m;
|
||||||
|
hasLatest.store(true, std::memory_order_release);
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
}
|
||||||
|
static void storeISR(const Msg& m) {
|
||||||
|
UBaseType_t s = portSET_INTERRUPT_MASK_FROM_ISR();
|
||||||
|
latest = m;
|
||||||
|
hasLatest.store(true, std::memory_order_release);
|
||||||
|
portCLEAR_INTERRUPT_MASK_FROM_ISR(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dispatch(const Msg& m, size_t ex) {
|
||||||
|
TickType_t now = xTaskGetTickCount();
|
||||||
|
Sub* ready[MaxSubs];
|
||||||
|
size_t readyCnt = 0;
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
for (size_t i = 0; i < MaxSubs; ++i) {
|
||||||
|
auto& opt = subs[i];
|
||||||
|
if (!opt || i == ex) continue;
|
||||||
|
Sub& s = *opt;
|
||||||
|
TickType_t dt = now - s.last;
|
||||||
|
|
||||||
|
if (s.interval && dt < s.interval) {
|
||||||
|
if (s.mode == EmitMode::Batch && s.cnt < BatchSize)
|
||||||
|
s.buf[s.cnt++] = m;
|
||||||
|
else if (s.mode == EmitMode::Latest) {
|
||||||
|
s.buf[0] = m;
|
||||||
|
s.cnt = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.buf[s.cnt++] = m;
|
||||||
|
s.last = now;
|
||||||
|
ready[readyCnt++] = &s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < readyCnt; ++i) {
|
||||||
|
Sub* s = ready[i];
|
||||||
|
s->cb(s->buf.data(), s->cnt);
|
||||||
|
s->cnt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void worker(void*) {
|
||||||
|
Item it;
|
||||||
|
while (xQueueReceive(queue, &it, portMAX_DELAY) == pdTRUE) dispatch(it.payload, it.exclude);
|
||||||
|
}
|
||||||
|
static void ensureTask() {
|
||||||
|
static bool once = (xTaskCreatePinnedToCore(worker, "evtbus", 4096, nullptr, 6, nullptr, 1), true);
|
||||||
|
(void)once;
|
||||||
|
}
|
||||||
|
static bool push(const Msg& m, size_t ex = NO_EX, TickType_t to = 0) {
|
||||||
|
ensureTask();
|
||||||
|
Item it {m, ex};
|
||||||
|
return xQueueSend(queue, &it, to) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Handle {
|
||||||
|
size_t idx {NO_EX};
|
||||||
|
friend class EventBus;
|
||||||
|
explicit Handle(size_t i) : idx(i) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Handle() = default;
|
||||||
|
Handle(const Handle&) = delete;
|
||||||
|
Handle& operator=(const Handle&) = delete;
|
||||||
|
Handle(Handle&& o) noexcept : idx(o.idx) { o.idx = NO_EX; }
|
||||||
|
Handle& operator=(Handle&& o) noexcept {
|
||||||
|
if (this != &o) {
|
||||||
|
unsubscribe();
|
||||||
|
idx = o.idx;
|
||||||
|
o.idx = NO_EX;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
~Handle() { unsubscribe(); }
|
||||||
|
void unsubscribe() {
|
||||||
|
if (idx < MaxSubs) {
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
subs[idx].reset();
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
subCount.fetch_sub(1, std::memory_order_acq_rel);
|
||||||
|
idx = NO_EX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool valid() const { return idx < MaxSubs; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
static Handle subscribe(uint32_t ms, EmitMode mode, C fn) {
|
||||||
|
ensureTask();
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
for (size_t i = 0; i < MaxSubs; ++i)
|
||||||
|
if (!subs[i]) {
|
||||||
|
subs[i].emplace();
|
||||||
|
Sub& s = *subs[i];
|
||||||
|
s.cb.set(std::move(fn));
|
||||||
|
s.interval = pdMS_TO_TICKS(ms);
|
||||||
|
s.last = xTaskGetTickCount();
|
||||||
|
s.mode = mode;
|
||||||
|
s.cnt = 0;
|
||||||
|
subCount.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
return Handle(i);
|
||||||
|
}
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
return Handle(NO_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
static Handle subscribe(C fn) {
|
||||||
|
if constexpr (std::is_invocable_v<C, const Msg*, size_t>)
|
||||||
|
return subscribe(0, EmitMode::Latest, std::move(fn));
|
||||||
|
else
|
||||||
|
return subscribe(0, EmitMode::Latest, [fn = std::move(fn)](const Msg* p, size_t n) {
|
||||||
|
for (size_t i = 0; i < n; ++i) fn(p[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
static Handle subscribe(uint32_t ms, C fn) {
|
||||||
|
if constexpr (std::is_invocable_v<C, const Msg*, size_t>)
|
||||||
|
return subscribe(ms, EmitMode::Batch, std::move(fn));
|
||||||
|
else
|
||||||
|
return subscribe(ms, EmitMode::Batch, [fn = std::move(fn)](const Msg* p, size_t n) {
|
||||||
|
for (size_t i = 0; i < n; ++i) fn(p[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish(const Msg& m) {
|
||||||
|
store(m);
|
||||||
|
push(m, NO_EX, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
static void publish(const Msg& m, const Handle& h) {
|
||||||
|
if (h.valid())
|
||||||
|
dispatch(m, h.idx);
|
||||||
|
else
|
||||||
|
publish(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool publishAsync(const Msg& m) {
|
||||||
|
store(m);
|
||||||
|
return push(m);
|
||||||
|
}
|
||||||
|
static bool publishAsync(const Msg& m, const Handle& h) {
|
||||||
|
if (h.valid()) dispatch(m, h.idx);
|
||||||
|
return publishAsync(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publishISR(const Msg& m, BaseType_t* hpw = nullptr) {
|
||||||
|
storeISR(m);
|
||||||
|
Item it {m, NO_EX};
|
||||||
|
xQueueSendFromISR(queue, &it, hpw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool peek(Msg& out) {
|
||||||
|
if (!hasLatest.load(std::memory_order_acquire)) return false;
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
out = latest;
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool take(Msg& out) {
|
||||||
|
if (!hasLatest.load(std::memory_order_acquire)) return false;
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
out = latest;
|
||||||
|
hasLatest.store(false, std::memory_order_release);
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool hasSubscribers() { return subCount.load(std::memory_order_acquire) > 0; }
|
||||||
|
};
|
||||||
@@ -67,6 +67,18 @@
|
|||||||
#define USE_MDNS 1
|
#define USE_MDNS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ESP32 MSGPACK on by default
|
||||||
|
#ifndef USE_MSGPACK
|
||||||
|
#define USE_MSGPACK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ESP32 JSON off by default
|
||||||
|
#ifndef USE_JSON
|
||||||
|
#define USE_JSON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static_assert(!(USE_JSON == 1 && USE_MSGPACK == 1), "Cannot set both USE_JSON and USE_MSGPACK to 1 simultaneously");
|
||||||
|
|
||||||
namespace feature_service {
|
namespace feature_service {
|
||||||
|
|
||||||
void printFeatureConfiguration();
|
void printFeatureConfiguration();
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
|
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
#include <system_service.h>
|
#include <system_service.h>
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
|
|
||||||
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2 };
|
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2 };
|
||||||
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#ifndef MotionService_h
|
#ifndef MotionService_h
|
||||||
#define MotionService_h
|
#define MotionService_h
|
||||||
|
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
|
#include <topic.hpp>
|
||||||
#include <kinematics.h>
|
#include <kinematics.h>
|
||||||
#include <peripherals/servo_controller.h>
|
#include <peripherals/servo_controller.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
@@ -21,51 +22,37 @@ enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, CRAWL, WA
|
|||||||
|
|
||||||
class MotionService {
|
class MotionService {
|
||||||
public:
|
public:
|
||||||
MotionService(ServoController *servoController) : _servoController(servoController) {}
|
MotionService(ServoController* servoController) : _servoController(servoController) {}
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
socket.onEvent(INPUT_EVENT, [&](JsonObject &root, int originId) { handleInput(root, originId); });
|
setupEventBusSubscriptions();
|
||||||
|
|
||||||
socket.onEvent(MODE_EVENT, [&](JsonObject &root, int originId) { handleMode(root, originId); });
|
|
||||||
|
|
||||||
socket.onEvent(ANGLES_EVENT, [&](JsonObject &root, int originId) { anglesEvent(root, originId); });
|
|
||||||
|
|
||||||
socket.onEvent(POSITION_EVENT, [&](JsonObject &root, int originId) { positionEvent(root, originId); });
|
|
||||||
|
|
||||||
socket.onSubscribe(ANGLES_EVENT,
|
|
||||||
std::bind(&MotionService::syncAngles, this, std::placeholders::_1, std::placeholders::_2));
|
|
||||||
|
|
||||||
body_state.updateFeet(kinematics.default_feet_positions);
|
body_state.updateFeet(kinematics.default_feet_positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void anglesEvent(JsonObject &root, int originId) {
|
void anglesEvent(const MotionAnglesMsg& msg) {
|
||||||
JsonArray array = root["data"].as<JsonArray>();
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
angles[i] = array[i];
|
angles[i] = msg.angles[i];
|
||||||
}
|
}
|
||||||
syncAngles(String(originId));
|
syncAngles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void positionEvent(JsonObject &root, int originId) {
|
void positionEvent(const MotionPositionMsg& msg) {
|
||||||
JsonArray array = root["data"].as<JsonArray>();
|
body_state.omega = msg.omega;
|
||||||
body_state.omega = array[0];
|
body_state.phi = msg.phi;
|
||||||
body_state.phi = array[1];
|
body_state.psi = msg.psi;
|
||||||
body_state.psi = array[2];
|
body_state.xm = msg.xm;
|
||||||
body_state.xm = array[3];
|
body_state.ym = msg.ym;
|
||||||
body_state.ym = array[4];
|
body_state.zm = msg.zm;
|
||||||
body_state.zm = array[5];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleInput(JsonObject &root, int originId) {
|
void handleInput(const MotionInputMsg& msg) {
|
||||||
JsonArray array = root["data"].as<JsonArray>();
|
command.lx = msg.lx;
|
||||||
command.lx = array[1];
|
command.ly = msg.ly;
|
||||||
command.lx = array[1];
|
command.rx = msg.rx;
|
||||||
command.ly = array[2];
|
command.ry = msg.ry;
|
||||||
command.rx = array[3];
|
command.h = msg.h;
|
||||||
command.ry = array[4];
|
command.s = msg.s;
|
||||||
command.h = array[5];
|
command.s1 = msg.s1;
|
||||||
command.s = array[6];
|
|
||||||
command.s1 = array[7];
|
|
||||||
|
|
||||||
body_state.ym = (command.h + 127.f) * 0.35f / 100;
|
body_state.ym = (command.h + 127.f) * 0.35f / 100;
|
||||||
|
|
||||||
@@ -81,25 +68,27 @@ class MotionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMode(JsonObject &root, int originId) {
|
void handleMode(const MotionModeMsg& msg) {
|
||||||
motionState = (MOTION_STATE)root["data"].as<int>();
|
motionState = (MOTION_STATE)msg.mode;
|
||||||
ESP_LOGV("MotionService", "Mode %d", motionState);
|
ESP_LOGV("MotionService", "Mode %d", motionState);
|
||||||
char output[2];
|
|
||||||
itoa((int)motionState, output, 10);
|
|
||||||
motionState == MOTION_STATE::DEACTIVATED ? _servoController->deactivate() : _servoController->activate();
|
motionState == MOTION_STATE::DEACTIVATED ? _servoController->deactivate() : _servoController->activate();
|
||||||
socket.emit(MODE_EVENT, output, String(originId).c_str());
|
|
||||||
|
MotionModeMsg response;
|
||||||
|
response.mode = msg.mode;
|
||||||
|
EventBus<MotionModeMsg>::publishAsync(response, _modeHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitAngles(const String &originId = "", bool sync = false) {
|
void emitAngles() {
|
||||||
char output[100];
|
MotionAnglesMsg anglesMsg;
|
||||||
snprintf(output, sizeof(output), "[%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f]", angles[0],
|
for (int i = 0; i < 12; i++) {
|
||||||
angles[1], angles[2], angles[3], angles[4], angles[5], angles[6], angles[7], angles[8], angles[9],
|
anglesMsg.angles[i] = angles[i];
|
||||||
angles[10], angles[11]);
|
}
|
||||||
socket.emit(ANGLES_EVENT, output, originId.c_str());
|
EventBus<MotionAnglesMsg>::publishAsync(anglesMsg, _anglesHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncAngles(const String &originId = "", bool sync = false) {
|
void syncAngles() {
|
||||||
emitAngles(originId, sync);
|
emitAngles();
|
||||||
_servoController->setAngles(angles);
|
_servoController->setAngles(angles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +123,33 @@ class MotionService {
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
float *getAngles() { return angles; }
|
float* getAngles() { return angles; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServoController *_servoController;
|
void setupEventBusSubscriptions() {
|
||||||
|
_inputHandle = EventBus<MotionInputMsg>::subscribe([this](const MotionInputMsg* msg, size_t n) {
|
||||||
|
if (n > 0) handleInput(msg[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
_modeHandle = EventBus<MotionModeMsg>::subscribe([this](const MotionModeMsg* msg, size_t n) {
|
||||||
|
if (n > 0) handleMode(msg[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
_anglesHandle = EventBus<MotionAnglesMsg>::subscribe([this](const MotionAnglesMsg* msg, size_t n) {
|
||||||
|
if (n > 0) anglesEvent(msg[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
_positionHandle = EventBus<MotionPositionMsg>::subscribe([this](const MotionPositionMsg* msg, size_t n) {
|
||||||
|
if (n > 0) positionEvent(msg[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus<MotionInputMsg>::Handle _inputHandle;
|
||||||
|
EventBus<MotionModeMsg>::Handle _modeHandle;
|
||||||
|
EventBus<MotionAnglesMsg>::Handle _anglesHandle;
|
||||||
|
EventBus<MotionPositionMsg>::Handle _positionHandle;
|
||||||
|
|
||||||
|
ServoController* _servoController;
|
||||||
Kinematics kinematics;
|
Kinematics kinematics;
|
||||||
ControllerCommand command = {0, 0, 0, 0, 0, 0, 0, 0};
|
ControllerCommand command = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct CommandMsg {
|
||||||
|
float x, y;
|
||||||
|
friend void toJson(JsonVariant v, CommandMsg const &c) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
arr.add(c.x);
|
||||||
|
arr.add(c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
x = arr[0].as<float>();
|
||||||
|
y = arr[1].as<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct I2CScanMsg {
|
||||||
|
std::vector<uint8_t> addresses;
|
||||||
|
|
||||||
|
friend void toJson(JsonVariant v, I2CScanMsg const& c) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
for (uint8_t addr : c.addresses) arr.add(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
addresses.clear();
|
||||||
|
for (JsonVariantConst val : o.as<JsonArrayConst>()) addresses.push_back(val.as<uint8_t>());
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct ImuMsg {
|
||||||
|
float ypr[3];
|
||||||
|
friend void toJson(JsonVariant v, ImuMsg const &a) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
arr.add(a.ypr[0]);
|
||||||
|
arr.add(a.ypr[1]);
|
||||||
|
arr.add(a.ypr[2]);
|
||||||
|
}
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
ypr[0] = arr[0].as<float>();
|
||||||
|
ypr[1] = arr[1].as<float>();
|
||||||
|
ypr[2] = arr[2].as<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
enum class MotionState { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, CRAWL, WALK };
|
||||||
|
|
||||||
|
struct ModeMsg {
|
||||||
|
MotionState mode;
|
||||||
|
friend void toJson(JsonVariant v, ModeMsg const &m) { v.set(static_cast<int>(m.mode)); }
|
||||||
|
void fromJson(JsonVariantConst o) { mode = (MotionState)o.as<int>(); }
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct MotionAnglesMsg {
|
||||||
|
float angles[12];
|
||||||
|
|
||||||
|
friend void toJson(JsonVariant v, MotionAnglesMsg const &m) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
arr.add(m.angles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
for (int i = 0; i < 12 && i < arr.size(); i++) {
|
||||||
|
angles[i] = arr[i].as<float>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct MotionInputMsg {
|
||||||
|
float lx, ly, rx, ry, h, s, s1;
|
||||||
|
|
||||||
|
friend void toJson(JsonVariant v, MotionInputMsg const &m) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
arr.add(m.lx);
|
||||||
|
arr.add(m.ly);
|
||||||
|
arr.add(m.rx);
|
||||||
|
arr.add(m.ry);
|
||||||
|
arr.add(m.h);
|
||||||
|
arr.add(m.s);
|
||||||
|
arr.add(m.s1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
lx = arr[0].as<float>();
|
||||||
|
ly = arr[1].as<float>();
|
||||||
|
rx = arr[2].as<float>();
|
||||||
|
ry = arr[3].as<float>();
|
||||||
|
h = arr[4].as<float>();
|
||||||
|
s = arr[5].as<float>();
|
||||||
|
s1 = arr[6].as<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct MotionModeMsg {
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
friend void toJson(JsonVariant v, MotionModeMsg const &m) { v.set(m.mode); }
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) { mode = o.as<int>(); }
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
struct MotionPositionMsg {
|
||||||
|
float omega, phi, psi, xm, ym, zm;
|
||||||
|
|
||||||
|
friend void toJson(JsonVariant v, MotionPositionMsg const &m) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
arr.add(m.omega);
|
||||||
|
arr.add(m.phi);
|
||||||
|
arr.add(m.psi);
|
||||||
|
arr.add(m.xm);
|
||||||
|
arr.add(m.ym);
|
||||||
|
arr.add(m.zm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
omega = arr[0].as<float>();
|
||||||
|
phi = arr[1].as<float>();
|
||||||
|
psi = arr[2].as<float>();
|
||||||
|
xm = arr[3].as<float>();
|
||||||
|
ym = arr[4].as<float>();
|
||||||
|
zm = arr[5].as<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#ifndef NUM_SERVOS
|
||||||
|
#define NUM_SERVOS 12
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ServoMsg {
|
||||||
|
float angles[NUM_SERVOS];
|
||||||
|
friend void toJson(JsonVariant v, ServoMsg const &a) {
|
||||||
|
JsonArray arr = v.to<JsonArray>();
|
||||||
|
for (int i = 0; i < NUM_SERVOS; i++) {
|
||||||
|
arr.add(a.angles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void fromJson(JsonVariantConst o) {
|
||||||
|
JsonArrayConst arr = o.as<JsonArrayConst>();
|
||||||
|
for (int i = 0; i < NUM_SERVOS; i++) {
|
||||||
|
angles[i] = arr[i].as<float>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#define TOPIC_LIST \
|
||||||
|
X(Imu, ImuMsg) \
|
||||||
|
X(Mode, ModeMsg) \
|
||||||
|
X(Command, CommandMsg) \
|
||||||
|
X(Servo, ServoMsg) \
|
||||||
|
X(MotionInput, MotionInputMsg) \
|
||||||
|
X(MotionAngles, MotionAnglesMsg) \
|
||||||
|
X(MotionPosition, MotionPositionMsg) \
|
||||||
|
X(MotionMode, MotionModeMsg)
|
||||||
+11
-11
@@ -55,15 +55,15 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
_eventEndpoint.begin();
|
_eventEndpoint.begin();
|
||||||
_persistence.readFromFS();
|
_persistence.readFromFS();
|
||||||
|
|
||||||
socket.onEvent(EVENT_I2C_SCAN, [&](JsonObject &root, int originId) {
|
// socket.onEvent(EVENT_I2C_SCAN, [&](JsonObject &root, int originId) {
|
||||||
scanI2C();
|
// scanI2C();
|
||||||
emitI2C();
|
// emitI2C();
|
||||||
});
|
// });
|
||||||
|
|
||||||
socket.onSubscribe(EVENT_I2C_SCAN, [&](const String &originId, bool sync) {
|
// socket.onSubscribe(EVENT_I2C_SCAN, [&](const String &originId, bool sync) {
|
||||||
scanI2C();
|
// scanI2C();
|
||||||
emitI2C(originId, sync);
|
// emitI2C(originId, sync);
|
||||||
});
|
// });
|
||||||
|
|
||||||
updatePins();
|
updatePins();
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
}
|
}
|
||||||
serializeJson(root, output);
|
serializeJson(root, output);
|
||||||
ESP_LOGI("Peripherals", "Emitting I2C scan results, %s %d", originId.c_str(), sync);
|
ESP_LOGI("Peripherals", "Emitting I2C scan results, %s %d", originId.c_str(), sync);
|
||||||
socket.emit(EVENT_I2C_SCAN, output, originId.c_str(), sync);
|
// socket.emit(EVENT_I2C_SCAN, output, originId.c_str(), sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scanI2C(uint8_t lower = 1, uint8_t higher = 127) {
|
void scanI2C(uint8_t lower = 1, uint8_t higher = 127) {
|
||||||
@@ -191,14 +191,14 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
_bmp.readBarometer(root);
|
_bmp.readBarometer(root);
|
||||||
#endif
|
#endif
|
||||||
serializeJson(doc, message);
|
serializeJson(doc, message);
|
||||||
socket.emit(EVENT_IMU, message);
|
// socket.emit(EVENT_IMU, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitSonar() {
|
void emitSonar() {
|
||||||
#if FT_ENABLED(USE_USS)
|
#if FT_ENABLED(USE_USS)
|
||||||
char output[16];
|
char output[16];
|
||||||
snprintf(output, sizeof(output), "[%.1f,%.1f]", _left_distance, _right_distance);
|
snprintf(output, sizeof(output), "[%.1f,%.1f]", _left_distance, _right_distance);
|
||||||
socket.emit("sonar", output);
|
// socket.emit("sonar", output);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
+9
-9
@@ -2,7 +2,7 @@
|
|||||||
#define ServoController_h
|
#define ServoController_h
|
||||||
|
|
||||||
#include <Adafruit_PWMServoDriver.h>
|
#include <Adafruit_PWMServoDriver.h>
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
#include <template/stateful_persistence.h>
|
#include <template/stateful_persistence.h>
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
#include <template/stateful_endpoint.h>
|
#include <template/stateful_endpoint.h>
|
||||||
@@ -32,16 +32,16 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
_persistence(ServoSettings::read, ServoSettings::update, this, SERVO_SETTINGS_FILE) {}
|
_persistence(ServoSettings::read, ServoSettings::update, this, SERVO_SETTINGS_FILE) {}
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
// socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
||||||
[&](JsonObject &root, int originId) { servoEvent(root, originId); });
|
// [&](JsonObject &root, int originId) { servoEvent(root, originId); });
|
||||||
socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) { stateUpdate(root, originId); });
|
// socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) { stateUpdate(root, originId); });
|
||||||
_persistence.readFromFS();
|
_persistence.readFromFS();
|
||||||
|
|
||||||
initializePCA();
|
initializePCA();
|
||||||
socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) {
|
// socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) {
|
||||||
is_active = root["active"] | false;
|
// is_active = root["active"] | false;
|
||||||
is_active ? activate() : deactivate();
|
// is_active ? activate() : deactivate();
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcaWrite(int index, int value) {
|
void pcaWrite(int index, int value) {
|
||||||
@@ -91,7 +91,7 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
snprintf(output, sizeof(output), "[%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f]", angles[0],
|
snprintf(output, sizeof(output), "[%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f]", angles[0],
|
||||||
angles[1], angles[2], angles[3], angles[4], angles[5], angles[6], angles[7], angles[8], angles[9],
|
angles[1], angles[2], angles[3], angles[4], angles[5], angles[6], angles[7], angles[8], angles[9],
|
||||||
angles[10], angles[11]);
|
angles[10], angles[11]);
|
||||||
socket.emit("angles", output, String(originId).c_str());
|
// socket.emit("angles", output, String(originId).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateActiveState() { is_active ? activate() : deactivate(); }
|
void updateActiveState() { is_active ? activate() : deactivate(); }
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#include <peripherals/servo_controller.h>
|
#include <peripherals/servo_controller.h>
|
||||||
#include <peripherals/led_service.h>
|
#include <peripherals/led_service.h>
|
||||||
#include <peripherals/camera_service.h>
|
#include <peripherals/camera_service.h>
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
|
#include <adapters/websocket.hpp>
|
||||||
#include <features.h>
|
#include <features.h>
|
||||||
#include <motion.h>
|
#include <motion.h>
|
||||||
#include <task_manager.h>
|
#include <task_manager.h>
|
||||||
@@ -80,7 +81,6 @@ class Spot {
|
|||||||
PsychicHttpServer _server;
|
PsychicHttpServer _server;
|
||||||
WiFiService _wifiService;
|
WiFiService _wifiService;
|
||||||
APService _apService;
|
APService _apService;
|
||||||
EventSocket _socket;
|
|
||||||
MDNSService _mdnsService;
|
MDNSService _mdnsService;
|
||||||
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
|
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
|
||||||
FirmwareUploadService _uploadFirmwareService;
|
FirmwareUploadService _uploadFirmwareService;
|
||||||
@@ -103,7 +103,7 @@ class Spot {
|
|||||||
bool updatedMotion = false;
|
bool updatedMotion = false;
|
||||||
|
|
||||||
const char *_appName = APP_NAME;
|
const char *_appName = APP_NAME;
|
||||||
const u_int16_t _numberEndpoints = 120;
|
const u_int16_t _numberEndpoints = 130;
|
||||||
const u_int32_t _maxFileUpload = 2300000; // 2.3 MB
|
const u_int32_t _maxFileUpload = 2300000; // 2.3 MB
|
||||||
const uint16_t _port = 80;
|
const uint16_t _port = 80;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <task_manager.h>
|
#include <task_manager.h>
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <global.h>
|
#include <global.h>
|
||||||
|
|
||||||
+6
-6
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
|
|
||||||
#include <event_socket.h>
|
#include <event_bus.hpp>
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -15,10 +15,10 @@ class EventEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
socket.onEvent(_event,
|
// socket.onEvent(_event,
|
||||||
std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2));
|
// std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
socket.onSubscribe(_event,
|
// socket.onSubscribe(_event,
|
||||||
std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2));
|
// std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -38,6 +38,6 @@ class EventEndpoint {
|
|||||||
_statefulService->read(root, _stateReader);
|
_statefulService->read(root, _stateReader);
|
||||||
serializeJson(root, output);
|
serializeJson(root, output);
|
||||||
ESP_LOGV("EventEndpoint", "Syncing state: %s", output.c_str());
|
ESP_LOGV("EventEndpoint", "Syncing state: %s", output.c_str());
|
||||||
socket.emit(_event, output.c_str(), originId.c_str(), sync);
|
// socket.emit(_event, output.c_str(), originId.c_str(), sync);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "msgs/imu_msg.hpp"
|
||||||
|
#include "msgs/command_msg.hpp"
|
||||||
|
#include "msgs/mode_msg.hpp"
|
||||||
|
#include "msgs/servo_msg.hpp"
|
||||||
|
#include "msgs/motion_input_msg.hpp"
|
||||||
|
#include "msgs/motion_angles_msg.hpp"
|
||||||
|
#include "msgs/motion_position_msg.hpp"
|
||||||
|
#include "msgs/motion_mode_msg.hpp"
|
||||||
|
#include "msgs/topics.def"
|
||||||
|
|
||||||
|
enum class Topic : uint8_t {
|
||||||
|
#define X(e, t) e,
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
template <Topic>
|
||||||
|
struct TopicTraits;
|
||||||
|
|
||||||
|
#define X(e, t) \
|
||||||
|
template <> \
|
||||||
|
struct TopicTraits<Topic::e> { \
|
||||||
|
using Msg = t; \
|
||||||
|
};
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
template <typename Msg>
|
||||||
|
static Msg parse(JsonVariantConst v) {
|
||||||
|
Msg msg;
|
||||||
|
msg.fromJson(v);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
#include <event_socket.h>
|
|
||||||
|
|
||||||
SemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex();
|
|
||||||
|
|
||||||
message_type_t char_to_message_type(char c) {
|
|
||||||
switch (c) {
|
|
||||||
case '0': return CONNECT;
|
|
||||||
case '1': return DISCONNECT;
|
|
||||||
case '2': return EVENT;
|
|
||||||
case '3': return PING;
|
|
||||||
case '4': return PONG;
|
|
||||||
case '5': return BINARY_EVENT;
|
|
||||||
default: throw std::invalid_argument("Invalid message type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *getEventName(const char *msg) {
|
|
||||||
const char *start = strchr(msg, '/');
|
|
||||||
if (!start) return nullptr;
|
|
||||||
start++;
|
|
||||||
const char *end = strchr(start, '[');
|
|
||||||
if (!end) return start;
|
|
||||||
|
|
||||||
static char eventName[32];
|
|
||||||
int len = end - start;
|
|
||||||
strncpy(eventName, start, len);
|
|
||||||
eventName[len] = '\0';
|
|
||||||
return eventName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *getEventPayload(const char *msg) {
|
|
||||||
const char *start = strchr(msg + 2, '[');
|
|
||||||
const char *end = msg + strlen(msg) - 1;
|
|
||||||
if (*start == '[') {
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
int len = end - start;
|
|
||||||
if (len < 0) return nullptr;
|
|
||||||
char *payload = new char[len + 1];
|
|
||||||
strncpy(payload, start, len);
|
|
||||||
payload[len] = '\0';
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventSocket::EventSocket() {
|
|
||||||
_socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1)));
|
|
||||||
_socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1));
|
|
||||||
_socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::onWSOpen(PsychicWebSocketClient *client) {
|
|
||||||
ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::onWSClose(PsychicWebSocketClient *client) {
|
|
||||||
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY);
|
|
||||||
for (auto &event_subscriptions : client_subscriptions) {
|
|
||||||
event_subscriptions.second.remove(client->socket());
|
|
||||||
}
|
|
||||||
xSemaphoreGive(clientSubscriptionsMutex);
|
|
||||||
ESP_LOGI("EventSocket", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket());
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
|
||||||
ESP_LOGV("EventSocket", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(),
|
|
||||||
request->client()->socket(), frame->type);
|
|
||||||
|
|
||||||
if (frame->type != HTTPD_WS_TYPE_TEXT) {
|
|
||||||
ESP_LOGE("EventSocket", "Unsupported frame type");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV("EventSocket", "Received message: %s", (char *)frame->payload);
|
|
||||||
char *msg = (char *)frame->payload;
|
|
||||||
|
|
||||||
message_type_t message_type = char_to_message_type(msg[0]);
|
|
||||||
|
|
||||||
if (message_type == PING) {
|
|
||||||
ESP_LOGV("EventSocket", "Ping");
|
|
||||||
request->client()->sendMessage("3");
|
|
||||||
return ESP_OK;
|
|
||||||
} else if (message_type == PONG) {
|
|
||||||
ESP_LOGV("EventSocket", "Pong");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *event = getEventName(msg);
|
|
||||||
|
|
||||||
if (!event) {
|
|
||||||
ESP_LOGE("EventSocket", "Invalid event name");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message_type == CONNECT) {
|
|
||||||
ESP_LOGV("EventSocket", "Connect: %s", event);
|
|
||||||
client_subscriptions[event].push_back(request->client()->socket());
|
|
||||||
handleSubscribeCallbacks(event, String(request->client()->socket()));
|
|
||||||
} else if (message_type == DISCONNECT) {
|
|
||||||
ESP_LOGV("EventSocket", "Disconnect: %s", event);
|
|
||||||
client_subscriptions[event].remove(request->client()->socket());
|
|
||||||
} else if (message_type == EVENT) {
|
|
||||||
const char *payload = getEventPayload(msg);
|
|
||||||
if (!payload) {
|
|
||||||
ESP_LOGE("EventSocket", "Invalid event payload");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
JsonDocument doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, payload);
|
|
||||||
if (error) {
|
|
||||||
ESP_LOGE("EventSocket", "Failed to parse JSON payload");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
JsonObject jsonObject = doc.as<JsonObject>();
|
|
||||||
handleEventCallbacks(event, jsonObject, request->client()->socket());
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventSocket::hasSubscribers(const char *event) { return !client_subscriptions[event].empty(); }
|
|
||||||
|
|
||||||
void EventSocket::emit(const char *event, const char *payload, const char *originId, bool onlyToSameOrigin) {
|
|
||||||
int originSubscriptionId = originId[0] ? atoi(originId) : -1;
|
|
||||||
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY);
|
|
||||||
auto &subscriptions = client_subscriptions[event];
|
|
||||||
if (subscriptions.empty()) {
|
|
||||||
xSemaphoreGive(clientSubscriptionsMutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char msg[strlen(event) + strlen(payload) + 10];
|
|
||||||
snprintf(msg, sizeof(msg), "2/%s[%s]", event, payload);
|
|
||||||
|
|
||||||
// if onlyToSameOrigin == true, send the message back to the origin
|
|
||||||
if (onlyToSameOrigin && originSubscriptionId > 0) {
|
|
||||||
auto *client = _socket.getClient(originSubscriptionId);
|
|
||||||
if (client) {
|
|
||||||
ESP_LOGV("EventSocket", "Emitting event: %s to %s, Message: %s", event,
|
|
||||||
client->remoteIP().toString().c_str(), msg);
|
|
||||||
client->sendMessage(msg);
|
|
||||||
}
|
|
||||||
} else { // else send the message to all other clients
|
|
||||||
|
|
||||||
for (int subscription : client_subscriptions[event]) {
|
|
||||||
if (subscription == originSubscriptionId) continue;
|
|
||||||
auto *client = _socket.getClient(subscription);
|
|
||||||
if (!client) {
|
|
||||||
subscriptions.remove(subscription);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ESP_LOGV("EventSocket", "Emitting event: %s to %s, Message: %s", event,
|
|
||||||
client->remoteIP().toString().c_str(), msg);
|
|
||||||
client->sendMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xSemaphoreGive(clientSubscriptionsMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::handleEventCallbacks(String event, JsonObject &jsonObject, int originId) {
|
|
||||||
for (auto &callback : event_callbacks[event]) {
|
|
||||||
callback(jsonObject, originId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::handleSubscribeCallbacks(String event, const String &originId) {
|
|
||||||
for (auto &callback : subscribe_callbacks[event]) {
|
|
||||||
callback(originId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::onEvent(String event, EventCallback callback) {
|
|
||||||
event_callbacks[event].push_back(std::move(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventSocket::onSubscribe(String event, SubscribeCallback callback) {
|
|
||||||
subscribe_callbacks[event].push_back(std::move(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
EventSocket socket;
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#ifndef Socket_h
|
|
||||||
#define Socket_h
|
|
||||||
|
|
||||||
#include <PsychicHttp.h>
|
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
enum message_type_t { CONNECT = 0, DISCONNECT = 1, EVENT = 2, PING = 3, PONG = 4, BINARY_EVENT = 5 };
|
|
||||||
|
|
||||||
typedef std::function<void(JsonObject &root, int originId)> EventCallback;
|
|
||||||
typedef std::function<void(const String &originId, bool sync)> SubscribeCallback;
|
|
||||||
|
|
||||||
class EventSocket {
|
|
||||||
public:
|
|
||||||
EventSocket();
|
|
||||||
|
|
||||||
PsychicWebSocketHandler *getHandler() { return &_socket; }
|
|
||||||
|
|
||||||
bool hasSubscribers(const char *event);
|
|
||||||
|
|
||||||
void onEvent(String event, EventCallback callback);
|
|
||||||
|
|
||||||
void onSubscribe(String event, SubscribeCallback callback);
|
|
||||||
|
|
||||||
void emit(const char *event, const char *payload, const char *originId = "", bool onlyToSameOrigin = false);
|
|
||||||
// if onlyToSameOrigin == true, the message will be sent to the originId only, otherwise it will be broadcasted to
|
|
||||||
// all clients except the originId
|
|
||||||
|
|
||||||
private:
|
|
||||||
PsychicWebSocketHandler _socket;
|
|
||||||
|
|
||||||
std::map<String, std::list<int>> client_subscriptions;
|
|
||||||
std::map<String, std::list<EventCallback>> event_callbacks;
|
|
||||||
std::map<String, std::list<SubscribeCallback>> subscribe_callbacks;
|
|
||||||
void handleEventCallbacks(String event, JsonObject &jsonObject, int originId);
|
|
||||||
void handleSubscribeCallbacks(String event, const String &originId);
|
|
||||||
|
|
||||||
void onWSOpen(PsychicWebSocketClient *client);
|
|
||||||
void onWSClose(PsychicWebSocketClient *client);
|
|
||||||
esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern EventSocket socket;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -27,7 +27,7 @@ project_dir = env["PROJECT_DIR"]
|
|||||||
buildFlags = env.ParseFlags(env["BUILD_FLAGS"])
|
buildFlags = env.ParseFlags(env["BUILD_FLAGS"])
|
||||||
|
|
||||||
interface_dir = project_dir + "/app"
|
interface_dir = project_dir + "/app"
|
||||||
output_file = project_dir + "/esp32/lib/ESP32-sveltekit/WWWData.h"
|
output_file = project_dir + "/esp32/include/WWWData.h"
|
||||||
source_www_dir = interface_dir + "/src"
|
source_www_dir = interface_dir + "/src"
|
||||||
build_dir = interface_dir + "/build"
|
build_dir = interface_dir + "/build"
|
||||||
filesystem_dir = project_dir + "/data/www"
|
filesystem_dir = project_dir + "/data/www"
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include <adapters/bluetooth.hpp>
|
||||||
|
#include <topic.hpp>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
static constexpr auto SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
static constexpr auto TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
static constexpr auto RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
|
||||||
|
void BluetoothService::begin(const char* name) {
|
||||||
|
#define X(e, t) \
|
||||||
|
setHandle<t>(Topic::e, EventBus<t>::subscribe([this](const t* d, size_t n) { \
|
||||||
|
if (connected && n) this->emit<Topic::e>(d[0]); \
|
||||||
|
}));
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
BLEDevice::init(name);
|
||||||
|
bleServer = BLEDevice::createServer();
|
||||||
|
bleServer->setCallbacks(new ServerCb(this));
|
||||||
|
auto* svc = bleServer->createService(SERVICE_UUID);
|
||||||
|
txCharacteristic = svc->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
|
||||||
|
txCharacteristic->addDescriptor(new BLE2902());
|
||||||
|
rxCharacteristic = svc->createCharacteristic(RX_UUID, BLECharacteristic::PROPERTY_WRITE);
|
||||||
|
rxCharacteristic->setCallbacks(new RxCb(this));
|
||||||
|
svc->start();
|
||||||
|
bleServer->getAdvertising()->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothService::handleReceive(const std::string& data) {
|
||||||
|
JsonDocument doc;
|
||||||
|
#if USE_MSGPACK
|
||||||
|
if (deserializeMsgPack(doc, data)) return;
|
||||||
|
#else
|
||||||
|
if (deserializeJson(doc, data)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
serializeJson(doc, Serial);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
auto payload = doc.as<JsonArrayConst>();
|
||||||
|
|
||||||
|
MsgKind msgKind = static_cast<MsgKind>(payload[0].as<uint8_t>());
|
||||||
|
switch (msgKind) {
|
||||||
|
case MsgKind::Connect:
|
||||||
|
for (size_t i = 1; i < payload.size(); ++i) subscribe(static_cast<Topic>(payload[i].as<uint8_t>()), 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MsgKind::Disconnect:
|
||||||
|
for (size_t i = 1; i < payload.size(); ++i) {
|
||||||
|
Topic t = static_cast<Topic>(payload[i].as<uint8_t>());
|
||||||
|
unsubscribe(t, 0);
|
||||||
|
#define X(e, m) \
|
||||||
|
if (t == Topic::e) { \
|
||||||
|
auto& h = getHandle<m>(Topic::e); \
|
||||||
|
if (h.valid()) h.unsubscribe(); \
|
||||||
|
}
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MsgKind::Event:
|
||||||
|
if (payload.size() < 3) break;
|
||||||
|
switch (static_cast<Topic>(payload[1].as<uint8_t>())) {
|
||||||
|
#define X(e, m) \
|
||||||
|
case Topic::e: EventBus<m>::publishAsync(parse<m>(payload[2]), getHandle<m>(Topic::e)); break;
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothService::send(size_t, const char* data, size_t len) {
|
||||||
|
if (!connected) return;
|
||||||
|
txCharacteristic->setValue((uint8_t*)data, len);
|
||||||
|
txCharacteristic->notify();
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#include <adapters/websocket.hpp>
|
||||||
|
|
||||||
|
EventSocket::EventSocket() {
|
||||||
|
_socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1)));
|
||||||
|
_socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1));
|
||||||
|
_socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
|
||||||
|
#define X(e, t) \
|
||||||
|
setHandle<t>(Topic::e, EventBus<t>::subscribe([this](const t* d, size_t n) { \
|
||||||
|
if (n) this->emit<Topic::e>(d[0]); \
|
||||||
|
}));
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventSocket::onWSOpen(PsychicWebSocketClient* client) {
|
||||||
|
ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventSocket::onWSClose(PsychicWebSocketClient* client) {
|
||||||
|
for (size_t i = 0; i < static_cast<size_t>(Topic::COUNT); ++i) {
|
||||||
|
unsubscribe(static_cast<Topic>(i), client->socket());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI("EventSocket", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket());
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t EventSocket::onFrame(PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||||
|
ESP_LOGV("EventSocket", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(),
|
||||||
|
request->client()->socket(), frame->type);
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
|
||||||
|
#if USE_MSGPACK
|
||||||
|
if (frame->type != HTTPD_WS_TYPE_BINARY) {
|
||||||
|
ESP_LOGE("EventSocket", "Unsupported frame type: %d", frame->type);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
if (deserializeMsgPack(doc, frame->payload, frame->len)) {
|
||||||
|
ESP_LOGE("EventSocket", "Could not deserialize msgpack");
|
||||||
|
return ESP_OK;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
if (frame->type != HTTPD_WS_TYPE_TEXT) {
|
||||||
|
ESP_LOGE("EventSocket", "Unsupported frame type: %d", frame->type);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
if (deserializeJson(doc, frame->payload, frame->len)) {
|
||||||
|
ESP_LOGE("EventSocket", "Could not deserialize json");
|
||||||
|
return ESP_OK;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
serializeJson(doc, Serial);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
auto payload = doc.as<JsonArrayConst>();
|
||||||
|
|
||||||
|
MsgKind msgKind = static_cast<MsgKind>(payload[0].as<uint8_t>());
|
||||||
|
switch (msgKind) {
|
||||||
|
case MsgKind::Connect:
|
||||||
|
for (size_t i = 1; i < payload.size(); ++i) {
|
||||||
|
subscribe(static_cast<Topic>(payload[i].as<uint8_t>()), request->client()->socket());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MsgKind::Disconnect:
|
||||||
|
for (size_t i = 1; i < payload.size(); ++i) {
|
||||||
|
Topic t = static_cast<Topic>(payload[i].as<uint8_t>());
|
||||||
|
unsubscribe(t, request->client()->socket());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MsgKind::Event:
|
||||||
|
if (payload.size() < 3) break;
|
||||||
|
switch (static_cast<Topic>(payload[1].as<uint8_t>())) {
|
||||||
|
#define X(e, m) \
|
||||||
|
case Topic::e: EventBus<m>::publishAsync(parse<m>(payload[2]), getHandle<m>(Topic::e)); break;
|
||||||
|
TOPIC_LIST
|
||||||
|
#undef X
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MsgKind::Ping: ESP_LOGV("EventSocket", "Ping"); break;
|
||||||
|
|
||||||
|
case MsgKind::Pong: ESP_LOGV("EventSocket", "Pong"); break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventSocket::send(size_t clientId, const char* data, size_t len) {
|
||||||
|
auto* client = _socket.getClient(clientId);
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
#if USE_MSGPACK
|
||||||
|
client->sendMessage(HTTPD_WS_TYPE_BINARY, data, len);
|
||||||
|
#else
|
||||||
|
client->sendMessage(HTTPD_WS_TYPE_TEXT, data, len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
EventSocket socket;
|
||||||
+6
-6
@@ -22,7 +22,7 @@ void update_started() {
|
|||||||
String output;
|
String output;
|
||||||
doc["status"] = "preparing";
|
doc["status"] = "preparing";
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_progress(int currentBytes, int totalBytes) {
|
void update_progress(int currentBytes, int totalBytes) {
|
||||||
@@ -31,7 +31,7 @@ void update_progress(int currentBytes, int totalBytes) {
|
|||||||
int progress = ((currentBytes * 100) / totalBytes);
|
int progress = ((currentBytes * 100) / totalBytes);
|
||||||
if (progress > previousProgress) {
|
if (progress > previousProgress) {
|
||||||
doc["progress"] = progress;
|
doc["progress"] = progress;
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes,
|
ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes,
|
||||||
progress);
|
progress);
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ void update_finished() {
|
|||||||
String output;
|
String output;
|
||||||
doc["status"] = "finished";
|
doc["status"] = "finished";
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
|
|
||||||
// delay to allow the event to be sent out
|
// delay to allow the event to be sent out
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
@@ -70,7 +70,7 @@ void updateTask(void *param) {
|
|||||||
doc["status"] = "error";
|
doc["status"] = "error";
|
||||||
doc["error"] = httpUpdate.getLastErrorString().c_str();
|
doc["error"] = httpUpdate.getLastErrorString().c_str();
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
|
|
||||||
ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(),
|
ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(),
|
||||||
httpUpdate.getLastErrorString().c_str());
|
httpUpdate.getLastErrorString().c_str());
|
||||||
@@ -80,7 +80,7 @@ void updateTask(void *param) {
|
|||||||
doc["status"] = "error";
|
doc["status"] = "error";
|
||||||
doc["error"] = "Update failed, has same firmware version";
|
doc["error"] = "Update failed, has same firmware version";
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
|
|
||||||
ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version");
|
ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version");
|
||||||
break;
|
break;
|
||||||
@@ -106,7 +106,7 @@ esp_err_t DownloadFirmwareService::handleDownloadUpdate(PsychicRequest *request,
|
|||||||
String output;
|
String output;
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
|
|
||||||
socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
// socket.emit(EVENT_DOWNLOAD_OTA, output.c_str());
|
||||||
|
|
||||||
const BaseType_t taskResult = g_taskManager.createTask(&updateTask, "Firmware download", OTA_TASK_STACK_SIZE,
|
const BaseType_t taskResult = g_taskManager.createTask(&updateTask, "Firmware download", OTA_TASK_STACK_SIZE,
|
||||||
&downloadURL, (configMAX_PRIORITIES - 1), NULL, 1);
|
&downloadURL, (configMAX_PRIORITIES - 1), NULL, 1);
|
||||||
+3
-3
@@ -88,14 +88,14 @@ esp_err_t FirmwareUploadService::handleUpload(PsychicRequest *request, const Str
|
|||||||
char buffer[64];
|
char buffer[64];
|
||||||
snprintf(buffer, sizeof(buffer), "{\"status\":\"progress\",\"progress\":%.1f}",
|
snprintf(buffer, sizeof(buffer), "{\"status\":\"progress\",\"progress\":%.1f}",
|
||||||
(float)Update.progress() / (float)fsize * 100.f);
|
(float)Update.progress() / (float)fsize * 100.f);
|
||||||
socket.emit("otastatus", buffer);
|
// socket.emit("otastatus", buffer);
|
||||||
delay(20);
|
delay(20);
|
||||||
}
|
}
|
||||||
if (final) {
|
if (final) {
|
||||||
if (!Update.end(true)) {
|
if (!Update.end(true)) {
|
||||||
handleError(request, 500);
|
handleError(request, 500);
|
||||||
} else {
|
} else {
|
||||||
socket.emit("otastatus", "{\"status\":\"finished\",\"progress\":100}");
|
// socket.emit("otastatus", "{\"status\":\"finished\",\"progress\":100}");
|
||||||
ESP_LOGI(TAG, "Finish writing update");
|
ESP_LOGI(TAG, "Finish writing update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ esp_err_t FirmwareUploadService::uploadComplete(PsychicRequest *request) {
|
|||||||
esp_err_t FirmwareUploadService::handleError(PsychicRequest *request, int code) {
|
esp_err_t FirmwareUploadService::handleError(PsychicRequest *request, int code) {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
snprintf(buffer, sizeof(buffer), "{\"status\":\"error\",\"error\":\"%d\"}", Update.getError());
|
snprintf(buffer, sizeof(buffer), "{\"status\":\"error\",\"error\":\"%d\"}", Update.getError());
|
||||||
socket.emit("otastatus", buffer);
|
// socket.emit("otastatus", buffer);
|
||||||
// if we have had an error already, do nothing
|
// if we have had an error already, do nothing
|
||||||
if (request->_tempObject) {
|
if (request->_tempObject) {
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#include <spot.h>
|
#include <spot.h>
|
||||||
|
#include <event_bus.hpp>
|
||||||
|
#include <adapters/bluetooth.hpp>
|
||||||
|
|
||||||
DRAM_ATTR Spot spot;
|
DRAM_ATTR Spot spot;
|
||||||
|
BluetoothService bluetooth;
|
||||||
|
|
||||||
void IRAM_ATTR SpotControlLoopEntry(void*) {
|
void IRAM_ATTR SpotControlLoopEntry(void*) {
|
||||||
ESP_LOGI("main", "Setup complete now runing tsk");
|
ESP_LOGI("main", "Setup complete now runing tsk");
|
||||||
@@ -17,6 +20,7 @@ void IRAM_ATTR SpotControlLoopEntry(void*) {
|
|||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
bluetooth.begin("Hexapod");
|
||||||
|
|
||||||
spot.initialize();
|
spot.initialize();
|
||||||
|
|
||||||
|
|||||||
@@ -136,12 +136,12 @@ void metrics(JsonObject &root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void emitMetrics() {
|
void emitMetrics() {
|
||||||
if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
|
// if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
|
||||||
analyticsDoc.clear();
|
analyticsDoc.clear();
|
||||||
JsonObject root = analyticsDoc.to<JsonObject>();
|
JsonObject root = analyticsDoc.to<JsonObject>();
|
||||||
system_service::metrics(root);
|
system_service::metrics(root);
|
||||||
serializeJson(analyticsDoc, analyticsMessage);
|
serializeJson(analyticsDoc, analyticsMessage);
|
||||||
socket.emit(EVENT_ANALYTICS, analyticsMessage);
|
// socket.emit(EVENT_ANALYTICS, analyticsMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *resetReason(esp_reset_reason_t reason) {
|
const char *resetReason(esp_reset_reason_t reason) {
|
||||||
+5
-1
@@ -93,7 +93,11 @@ build_flags =
|
|||||||
-D CORE_DEBUG_LEVEL=4
|
-D CORE_DEBUG_LEVEL=4
|
||||||
-D register=
|
-D register=
|
||||||
-std=gnu++2a
|
-std=gnu++2a
|
||||||
build_unflags = -std=gnu++11
|
-flto=auto ; Enable Link Time Optimization
|
||||||
|
-O3
|
||||||
|
build_unflags =
|
||||||
|
-std=gnu++11
|
||||||
|
-fno-lto
|
||||||
build_src_flags =
|
build_src_flags =
|
||||||
-Wformat=2
|
-Wformat=2
|
||||||
-Wformat-truncation
|
-Wformat-truncation
|
||||||
|
|||||||
Reference in New Issue
Block a user