🎨 Moving project to use event bus

This commit is contained in:
Rune Harlyk
2025-07-08 21:47:06 +02:00
parent 0586775849
commit 6769ffeb20
69 changed files with 497 additions and 496 deletions
+3 -1
View File
@@ -59,7 +59,9 @@
"three": "^0.162.0",
"urdf-loader": "^0.12.1",
"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"
}
+91 -32
View File
@@ -13,10 +13,13 @@ importers:
version: 1.1.2
'@sveltejs/adapter-auto':
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':
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:
specifier: ^4.4.2
version: 4.4.2
@@ -32,6 +35,9 @@ importers:
jwt-decode:
specifier: ^4.0.0
version: 4.0.0
msgpack-lite:
specifier: ^0.1.26
version: 0.1.26
nipplejs:
specifier: ^0.10.1
version: 0.10.1
@@ -65,13 +71,13 @@ importers:
version: 1.49.1
'@sveltejs/adapter-static':
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':
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':
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':
specifier: ^8.56.0
version: 8.56.0
@@ -128,10 +134,10 @@ importers:
version: 0.18.5
vite:
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:
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:
@@ -760,6 +766,12 @@ packages:
'@types/json-schema@7.0.15':
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':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@@ -1190,6 +1202,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
event-lite@0.1.3:
resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==}
execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@@ -1335,6 +1350,9 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
@@ -1356,6 +1374,9 @@ packages:
inherits@2.0.4:
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:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -1394,6 +1415,9 @@ packages:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
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:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -1592,6 +1616,10 @@ packages:
ms@2.1.3:
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:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -2019,6 +2047,9 @@ packages:
ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
undici-types@7.8.0:
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@@ -2613,18 +2644,18 @@ snapshots:
'@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:
'@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
'@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:
'@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:
'@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
cookie: 0.6.0
devalue: 5.1.1
@@ -2637,27 +2668,27 @@ snapshots:
set-cookie-parser: 2.6.0
sirv: 3.0.1
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:
'@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
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:
- 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:
'@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
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.17
svelte: 5.20.4
vite: 6.2.1(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))
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(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -2714,13 +2745,13 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-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:
'@tailwindcss/node': 4.0.12
'@tailwindcss/oxide': 4.0.12
lightningcss: 1.29.2
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': {}
@@ -2737,6 +2768,14 @@ snapshots:
'@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/stats.js@0.17.3': {}
@@ -3259,6 +3298,8 @@ snapshots:
esutils@2.0.3: {}
event-lite@0.1.3: {}
execa@5.1.1:
dependencies:
cross-spawn: 7.0.3
@@ -3414,6 +3455,8 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
ieee754@1.2.1: {}
ignore@5.3.1: {}
import-fresh@3.3.0:
@@ -3432,6 +3475,8 @@ snapshots:
inherits@2.0.4: {}
int64-buffer@0.1.10: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@@ -3458,6 +3503,8 @@ snapshots:
is-stream@3.0.0: {}
isarray@1.0.0: {}
isexe@2.0.0: {}
jiti@2.4.2: {}
@@ -3635,6 +3682,13 @@ snapshots:
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.8: {}
@@ -4010,6 +4064,8 @@ snapshots:
ufo@1.5.3: {}
undici-types@7.8.0: {}
universalify@0.2.0: {}
unplugin-icons@0.18.5:
@@ -4054,13 +4110,13 @@ snapshots:
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:
cac: 6.7.14
debug: 4.4.0
pathe: 1.1.2
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:
- '@types/node'
- less
@@ -4072,31 +4128,33 @@ snapshots:
- supports-color
- terser
vite@5.4.14(lightningcss@1.29.2):
vite@5.4.14(@types/node@24.0.10)(lightningcss@1.29.2):
dependencies:
esbuild: 0.21.5
postcss: 8.5.3
rollup: 4.34.8
optionalDependencies:
'@types/node': 24.0.10
fsevents: 2.3.3
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:
esbuild: 0.25.0
postcss: 8.5.3
rollup: 4.34.8
optionalDependencies:
'@types/node': 24.0.10
fsevents: 2.3.3
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)):
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:
'@vitest/expect': 1.2.0
'@vitest/runner': 1.2.0
@@ -4116,10 +4174,11 @@ snapshots:
strip-literal: 1.3.0
tinybench: 2.8.0
tinypool: 0.8.4
vite: 5.4.14(lightningcss@1.29.2)
vite-node: 1.2.0(lightningcss@1.29.2)
vite: 5.4.14(@types/node@24.0.10)(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
optionalDependencies:
'@types/node': 24.0.10
jsdom: 24.0.0
transitivePeerDependencies:
- less
+128 -118
View File
@@ -1,122 +1,132 @@
import { writable } from 'svelte/store';
import { writable } from 'svelte/store'
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const;
type SocketEvent = (typeof socketEvents)[number];
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const
type SocketEvent = (typeof socketEvents)[number]
function createWebSocket() {
let listeners = new Map<string, 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);
}
};
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 enum Topics {
imu = 0,
mode = 1,
command = 2,
servo = 3,
input = 4,
angles = 5,
position = 6
}
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
View File
@@ -2,6 +2,6 @@
data/
www/
build/
lib/ESP32-sveltekit/WWWData.h
include/WWWData.h
**/.vscode/c_cpp_properties.json
**/.vscode/launch.json
+2 -19
View File
@@ -14,21 +14,8 @@ class BluetoothService : public CommBase<> {
BLECharacteristic* rxCharacteristic {nullptr};
bool connected {false};
protected:
template <typename Msg>
using EventBusHandle = typename EventBus<Msg>::Handle;
template <typename Msg>
EventBusHandle<Msg>& getHandle(Topic topic) {
return *static_cast<EventBusHandle<Msg>*>(subscriptionHandle[static_cast<size_t>(topic)]);
}
template <typename Msg>
void setHandle(Topic topic, EventBusHandle<Msg>&& handle) {
subscriptionHandle[static_cast<size_t>(topic)] = new EventBusHandle<Msg>(std::move(handle));
}
std::array<void*, static_cast<size_t>(Topic::COUNT)> subscriptionHandle {};
public:
void begin(const char* name);
private:
void handleReceive(const std::string& data);
@@ -53,8 +40,4 @@ class BluetoothService : public CommBase<> {
if (!v.empty()) svc->handleReceive(v);
}
};
public:
void begin(const char* name);
void loop() {}
};
+15
View File
@@ -1,4 +1,5 @@
#pragma once
#include "event_bus.hpp"
#include <ArduinoJson.h>
#include <array>
#include <bitset>
@@ -17,6 +18,8 @@ class CommBase {
std::array<Bits, NTopics> subs_;
portMUX_TYPE mux_ portMUX_INITIALIZER_UNLOCKED;
std::array<void*, static_cast<size_t>(Topic::COUNT)> subscriptionHandle {};
static constexpr size_t idx(Topic t) { return static_cast<size_t>(t); }
template <Topic T>
@@ -30,6 +33,18 @@ class CommBase {
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;
return *static_cast<H*>(subscriptionHandle[static_cast<size_t>(topic)]);
}
template <class Msg>
void setHandle(Topic topic, typename EventBus<Msg>::Handle&& h) {
using H = typename EventBus<Msg>::Handle;
subscriptionHandle[static_cast<size_t>(topic)] = new H(std::move(h));
}
public:
void subscribe(Topic t, size_t cid) {
portENTER_CRITICAL(&mux_);
+42
View File
@@ -0,0 +1,42 @@
#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"
// #include "msgs/motion_input_msg.hpp"
// #include "msgs/motion_angles_msg.hpp"
// #include "msgs/motion_position_msg.hpp"
// #include "msgs/motion_mode_msg.hpp"
// typedef std::function<void(JsonObject &root, int originId)> EventCallback;
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 handleTypedMessage(const std::string &data);
// void handleLegacyMessage(const std::string &data, int originId);
// void handleEventCallbacks(String event, JsonObject &jsonObject, int originId);
void onWSOpen(PsychicWebSocketClient *client);
void onWSClose(PsychicWebSocketClient *client);
esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
};
extern EventSocket socket;
#endif
+3 -1
View File
@@ -3,6 +3,7 @@
#include <bitset>
#include <functional>
#include <optional>
#include <atomic>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
@@ -102,11 +103,12 @@ class EventBus {
public:
class Handle {
size_t index;
size_t index {MaxSubs};
friend class EventBus;
explicit Handle(size_t i) : index(i) {}
public:
Handle() = default;
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
Handle(Handle&& h) noexcept : index(h.index) { h.index = MaxSubs; }
@@ -4,7 +4,7 @@
#include <WiFi.h>
#include <ArduinoJson.h>
#include <event_socket.h>
#include <event_bus.hpp>
#include <PsychicHttp.h>
#include <HTTPClient.h>
@@ -8,7 +8,7 @@
#include <PsychicHttp.h>
#include <system_service.h>
#include <event_socket.h>
#include <event_bus.hpp>
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2 };
@@ -1,7 +1,8 @@
#ifndef MotionService_h
#define MotionService_h
#include <event_socket.h>
#include <event_bus.hpp>
#include <topic.hpp>
#include <kinematics.h>
#include <peripherals/servo_controller.h>
#include <utils/timing.h>
@@ -21,51 +22,37 @@ enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, CRAWL, WA
class MotionService {
public:
MotionService(ServoController *servoController) : _servoController(servoController) {}
MotionService(ServoController* servoController) : _servoController(servoController) {}
void begin() {
socket.onEvent(INPUT_EVENT, [&](JsonObject &root, int originId) { handleInput(root, originId); });
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));
setupEventBusSubscriptions();
body_state.updateFeet(kinematics.default_feet_positions);
}
void anglesEvent(JsonObject &root, int originId) {
JsonArray array = root["data"].as<JsonArray>();
void anglesEvent(const MotionAnglesMsg& msg) {
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) {
JsonArray array = root["data"].as<JsonArray>();
body_state.omega = array[0];
body_state.phi = array[1];
body_state.psi = array[2];
body_state.xm = array[3];
body_state.ym = array[4];
body_state.zm = array[5];
void positionEvent(const MotionPositionMsg& msg) {
body_state.omega = msg.omega;
body_state.phi = msg.phi;
body_state.psi = msg.psi;
body_state.xm = msg.xm;
body_state.ym = msg.ym;
body_state.zm = msg.zm;
}
void handleInput(JsonObject &root, int originId) {
JsonArray array = root["data"].as<JsonArray>();
command.lx = array[1];
command.lx = array[1];
command.ly = array[2];
command.rx = array[3];
command.ry = array[4];
command.h = array[5];
command.s = array[6];
command.s1 = array[7];
void handleInput(const MotionInputMsg& msg) {
command.lx = msg.lx;
command.ly = msg.ly;
command.rx = msg.rx;
command.ry = msg.ry;
command.h = msg.h;
command.s = msg.s;
command.s1 = msg.s1;
body_state.ym = (command.h + 127.f) * 0.35f / 100;
@@ -81,25 +68,27 @@ class MotionService {
}
}
void handleMode(JsonObject &root, int originId) {
motionState = (MOTION_STATE)root["data"].as<int>();
void handleMode(const MotionModeMsg& msg) {
motionState = (MOTION_STATE)msg.mode;
ESP_LOGV("MotionService", "Mode %d", motionState);
char output[2];
itoa((int)motionState, output, 10);
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) {
char output[100];
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[10], angles[11]);
socket.emit(ANGLES_EVENT, output, originId.c_str());
void emitAngles() {
MotionAnglesMsg anglesMsg;
for (int i = 0; i < 12; i++) {
anglesMsg.angles[i] = angles[i];
}
EventBus<MotionAnglesMsg>::publishAsync(anglesMsg, _anglesHandle);
}
void syncAngles(const String &originId = "", bool sync = false) {
emitAngles(originId, sync);
void syncAngles() {
emitAngles();
_servoController->setAngles(angles);
}
@@ -134,10 +123,33 @@ class MotionService {
return updated;
}
float *getAngles() { return angles; }
float* getAngles() { return angles; }
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;
ControllerCommand command = {0, 0, 0, 0, 0, 0, 0, 0};
@@ -55,15 +55,15 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
_eventEndpoint.begin();
_persistence.readFromFS();
socket.onEvent(EVENT_I2C_SCAN, [&](JsonObject &root, int originId) {
scanI2C();
emitI2C();
});
// socket.onEvent(EVENT_I2C_SCAN, [&](JsonObject &root, int originId) {
// scanI2C();
// emitI2C();
// });
socket.onSubscribe(EVENT_I2C_SCAN, [&](const String &originId, bool sync) {
scanI2C();
emitI2C(originId, sync);
});
// socket.onSubscribe(EVENT_I2C_SCAN, [&](const String &originId, bool sync) {
// scanI2C();
// emitI2C(originId, sync);
// });
updatePins();
@@ -115,7 +115,7 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
}
serializeJson(root, output);
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) {
@@ -191,14 +191,14 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
_bmp.readBarometer(root);
#endif
serializeJson(doc, message);
socket.emit(EVENT_IMU, message);
// socket.emit(EVENT_IMU, message);
}
void emitSonar() {
#if FT_ENABLED(USE_USS)
char output[16];
snprintf(output, sizeof(output), "[%.1f,%.1f]", _left_distance, _right_distance);
socket.emit("sonar", output);
// socket.emit("sonar", output);
#endif
}
@@ -2,7 +2,7 @@
#define ServoController_h
#include <Adafruit_PWMServoDriver.h>
#include <event_socket.h>
#include <event_bus.hpp>
#include <template/stateful_persistence.h>
#include <template/stateful_service.h>
#include <template/stateful_endpoint.h>
@@ -32,16 +32,16 @@ class ServoController : public StatefulService<ServoSettings> {
_persistence(ServoSettings::read, ServoSettings::update, this, SERVO_SETTINGS_FILE) {}
void begin() {
socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
[&](JsonObject &root, int originId) { servoEvent(root, originId); });
socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) { stateUpdate(root, originId); });
// socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
// [&](JsonObject &root, int originId) { servoEvent(root, originId); });
// socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) { stateUpdate(root, originId); });
_persistence.readFromFS();
initializePCA();
socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) {
is_active = root["active"] | false;
is_active ? activate() : deactivate();
});
// socket.onEvent(EVENT_SERVO_STATE, [&](JsonObject &root, int originId) {
// is_active = root["active"] | false;
// is_active ? activate() : deactivate();
// });
}
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],
angles[1], angles[2], angles[3], angles[4], angles[5], angles[6], angles[7], angles[8], angles[9],
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(); }
+3 -3
View File
@@ -14,7 +14,8 @@
#include <peripherals/servo_controller.h>
#include <peripherals/led_service.h>
#include <peripherals/camera_service.h>
#include <event_socket.h>
#include <event_bus.hpp>
#include <adapters/websocket.hpp>
#include <features.h>
#include <motion.h>
#include <task_manager.h>
@@ -80,7 +81,6 @@ class Spot {
PsychicHttpServer _server;
WiFiService _wifiService;
APService _apService;
EventSocket _socket;
MDNSService _mdnsService;
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
FirmwareUploadService _uploadFirmwareService;
@@ -103,7 +103,7 @@ class Spot {
bool updatedMotion = false;
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 uint16_t _port = 80;
@@ -5,7 +5,7 @@
#include <PsychicHttp.h>
#include <WiFi.h>
#include <task_manager.h>
#include <event_socket.h>
#include <event_bus.hpp>
#include <filesystem.h>
#include <global.h>
@@ -2,7 +2,7 @@
#include <PsychicHttp.h>
#include <event_socket.h>
#include <event_bus.hpp>
#include <template/stateful_service.h>
template <class T>
@@ -15,10 +15,10 @@ class EventEndpoint {
}
void begin() {
socket.onEvent(_event,
std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2));
socket.onSubscribe(_event,
std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2));
// socket.onEvent(_event,
// std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2));
// socket.onSubscribe(_event,
// std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2));
}
private:
@@ -38,6 +38,6 @@ class EventEndpoint {
_statefulService->read(root, _stateReader);
serializeJson(root, output);
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);
}
};
-178
View File
@@ -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;
-47
View File
@@ -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
+1 -1
View File
@@ -27,7 +27,7 @@ project_dir = env["PROJECT_DIR"]
buildFlags = env.ParseFlags(env["BUILD_FLAGS"])
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"
build_dir = interface_dir + "/build"
filesystem_dir = project_dir + "/data/www"
+5 -5
View File
@@ -9,7 +9,7 @@ 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) emit<Topic::e>(d[0]); \
if (connected && n) this->emit<Topic::e>(d[0]); \
}));
TOPIC_LIST
#undef X
@@ -49,10 +49,10 @@ void BluetoothService::handleReceive(const std::string& data) {
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(); \
#define X(e, m) \
if (t == Topic::e) { \
auto& h = getHandle<m>(Topic::e); \
if (h.valid()) h.unsubscribe(); \
}
TOPIC_LIST
#undef X
+101
View File
@@ -0,0 +1,101 @@
#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));
}
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
// ESP_LOGV("EventSocket", "Sending to client %zu: %.*s", clientId, len, data);
// client->sendMessage(data);
}
EventSocket socket;
@@ -22,7 +22,7 @@ void update_started() {
String output;
doc["status"] = "preparing";
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) {
@@ -31,7 +31,7 @@ void update_progress(int currentBytes, int totalBytes) {
int progress = ((currentBytes * 100) / totalBytes);
if (progress > previousProgress) {
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,
progress);
}
@@ -42,7 +42,7 @@ void update_finished() {
String output;
doc["status"] = "finished";
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
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -70,7 +70,7 @@ void updateTask(void *param) {
doc["status"] = "error";
doc["error"] = httpUpdate.getLastErrorString().c_str();
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(),
httpUpdate.getLastErrorString().c_str());
@@ -80,7 +80,7 @@ void updateTask(void *param) {
doc["status"] = "error";
doc["error"] = "Update failed, has same firmware version";
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");
break;
@@ -106,7 +106,7 @@ esp_err_t DownloadFirmwareService::handleDownloadUpdate(PsychicRequest *request,
String 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,
&downloadURL, (configMAX_PRIORITIES - 1), NULL, 1);
@@ -88,14 +88,14 @@ esp_err_t FirmwareUploadService::handleUpload(PsychicRequest *request, const Str
char buffer[64];
snprintf(buffer, sizeof(buffer), "{\"status\":\"progress\",\"progress\":%.1f}",
(float)Update.progress() / (float)fsize * 100.f);
socket.emit("otastatus", buffer);
// socket.emit("otastatus", buffer);
delay(20);
}
if (final) {
if (!Update.end(true)) {
handleError(request, 500);
} else {
socket.emit("otastatus", "{\"status\":\"finished\",\"progress\":100}");
// socket.emit("otastatus", "{\"status\":\"finished\",\"progress\":100}");
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) {
char buffer[64];
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 (request->_tempObject) {
return ESP_OK;
@@ -136,12 +136,12 @@ void metrics(JsonObject &root) {
}
void emitMetrics() {
if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
// if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
analyticsDoc.clear();
JsonObject root = analyticsDoc.to<JsonObject>();
system_service::metrics(root);
serializeJson(analyticsDoc, analyticsMessage);
socket.emit(EVENT_ANALYTICS, analyticsMessage);
// socket.emit(EVENT_ANALYTICS, analyticsMessage);
}
const char *resetReason(esp_reset_reason_t reason) {