diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e48bc14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.pio +.vscode + +node_modules + +src/secrets.h + +data diff --git a/README.md b/README.md new file mode 100644 index 0000000..5107154 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# CrustCraft + diff --git a/index.html b/index.html new file mode 100644 index 0000000..b80780e --- /dev/null +++ b/index.html @@ -0,0 +1,63 @@ + + + + + CrustCraft + + + + + + +
+
+
+

+ Temperature

+

°C

+
+
+

Relais

+
+ + +
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+

+ Target temperature

+

+

+ +
+ +
+
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..24307ee --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1049 @@ +{ + "name": "crust-craft", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "highcharts": "^12.3.0", + "typescript": "^5.9.2" + }, + "devDependencies": { + "vite": "^7.1.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/highcharts": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.3.0.tgz", + "integrity": "sha512-QIKmaemgheRa1K2Ia9MLj1KLtBU1Tu/VQ6KAMqtMBMsAC4NzcFq6g96LF03ZO3IFFiSifmZx8ItEyRcz4w75cg==", + "license": "https://www.highcharts.com/license" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d15165 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "highcharts": "^12.3.0", + "typescript": "^5.9.2" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^7.1.3" + } +} diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..925384f --- /dev/null +++ b/platformio.ini @@ -0,0 +1,29 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32doit-devkit-v1] +platform = espressif32 +board = esp32doit-devkit-v1 + +framework = arduino +lib_compat_mode = strict +lib_ldf_mode = chain + +lib_deps = + adafruit/MAX6675 library@^1.1.2 + bblanchon/ArduinoJson@^7.4.2 + ayushsharma82/ElegantOTA@^3.1.7 + ESP32Async/AsyncTCP + ESP32Async/ESPAsyncWebServer + +board_build.filesystem = littlefs + +build_flags = + -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..facfa89 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/oven.png b/public/oven.png new file mode 100644 index 0000000..cd85248 Binary files /dev/null and b/public/oven.png differ diff --git a/public/oven_small.png b/public/oven_small.png new file mode 100644 index 0000000..c6b333c Binary files /dev/null and b/public/oven_small.png differ diff --git a/src/globals.d.ts b/src/globals.d.ts new file mode 100644 index 0000000..35306c6 --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1 @@ +declare module '*.css'; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9696c14 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include "LittleFS.h" +#include +#include "max6675.h" +#include + +#include "secrets.h" + +// Create AsyncWebServer object on port 80 +AsyncWebServer server(80); + +// Create a WebSocket object +AsyncWebSocket ws("/ws"); + +// Json Variable to Hold Sensor Readings +JsonDocument readings; +String jsonString; + +// Timer variables +unsigned long lastTime = 0; +unsigned long timerDelay = 250; + +int relay = 21; +int thermoDO = 19; +int thermoCS = 23; +int thermoCLK = 5; + +MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); + +int targetTemp = 300; +int overShoot = 10; +int underShoot = 5; +unsigned long lastSwitch = 0; +unsigned long switchDelay = 60000; +bool autoSwitch = true; + +// Get Sensor Readings and return JSON object +String getSensorReadings() +{ + readings["temperature"] = thermocouple.readCelsius(); + if (digitalRead(relay) == HIGH) + { + readings["relais"] = 1; + } + else + { + readings["relais"] = 0; + } + serializeJson(readings, jsonString); + return jsonString; +} + +// Initialize LittleFS +void initLittleFS() +{ + if (!LittleFS.begin(true)) + { + Serial.println("An error has occurred while mounting LittleFS"); + } + Serial.println("LittleFS mounted successfully"); +} + +// Initialize WiFi +void initWiFi() +{ + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.print("Connecting to WiFi .."); + while (WiFi.status() != WL_CONNECTED) + { + Serial.print('.'); + delay(1000); + } + Serial.println(WiFi.localIP()); +} + +void notifyClients(String sensorReadings) +{ + ws.textAll(sensorReadings); +} + +void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) +{ + AwsFrameInfo *info = (AwsFrameInfo *)arg; + if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) + { + data[len] = 0; + String message = (char *)data; + if (strcmp((char *)data, "getReadings") == 0) + { + String sensorReadings = getSensorReadings(); + notifyClients(sensorReadings); + }; + if (strcmp((char *)data, "switchRelais") == 0) + { + Serial.printf("\nSwitch Relais"); + if (digitalRead(relay) == HIGH) + { + digitalWrite(relay, LOW); + lastSwitch = millis(); + } + else + { + digitalWrite(relay, HIGH); + lastSwitch = millis(); + } + } + if (message.startsWith("setTargetTemp")) + { + String target = message.substring(15, 18); + targetTemp = target.toInt(); + Serial.printf("\nTarget temp set to "); + Serial.print(targetTemp); + Serial.printf("°C"); + } + } +} + +void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) +{ + switch (type) + { + case WS_EVT_CONNECT: + Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + break; + case WS_EVT_DISCONNECT: + Serial.printf("WebSocket client #%u disconnected\n", client->id()); + break; + case WS_EVT_DATA: + handleWebSocketMessage(arg, data, len); + break; + case WS_EVT_PONG: + case WS_EVT_ERROR: + break; + } +} + +void initWebSocket() +{ + ws.onEvent(onEvent); + server.addHandler(&ws); +} + +void setup() +{ + Serial.begin(9600); + pinMode(relay, OUTPUT); + + initWiFi(); + initLittleFS(); + initWebSocket(); + + // Web Server Root URL + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/index.html", "text/html"); }); + + server.serveStatic("/", LittleFS, "/"); + + ElegantOTA.begin(&server); + + // Start server + server.begin(); +} + +void loop() +{ + if ((millis() - lastTime) > timerDelay) + { + String sensorReadings = getSensorReadings(); + notifyClients(sensorReadings); + if (((millis() - lastSwitch) > switchDelay) && thermocouple.readCelsius() >= (targetTemp - overShoot)) // >= 285°C + { + digitalWrite(relay, LOW); + lastSwitch = millis(); + } + if (((millis() - lastSwitch) > switchDelay) && thermocouple.readCelsius() < (targetTemp - underShoot)) // < 270°C + { + digitalWrite(relay, HIGH); + lastSwitch = millis(); + } + lastTime = millis(); + } + + ws.cleanupClients(); + ElegantOTA.loop(); +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..bd2ee0f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,345 @@ +import Highcharts, { type DashStyleValue } from 'highcharts/highstock' + +import 'highcharts/modules/accessibility' + +import './styles.css' + +export const pad = (n: string | number, amount = 2) => { + if (amount == 2) { + return ('0' + n).slice(-2); + } else { + return ('00' + n).slice(-3); + } +}; + + +let gateway = `ws://${window.location.hostname}/ws`; +let ws: WebSocket; + +const now = new Date() + +let container: HTMLElement | null = document.querySelector('#plot') +let temperatureData = [ + [now.getTime(), 25] +] + +let relais = 0 +let relaisData = [ + [now.getTime(), 0] +] + +let temp = 25; +let targetTemp = 350 + +const oven = document.querySelector('#oven') +const temperature = document.querySelector('#temperature') +const relaisSwitch = document.querySelector('#relais_switch') +const relaisSwitchInput: HTMLInputElement | null = document.querySelector('#relais_switch_input') +const targetTempInput: HTMLInputElement | null = document.querySelector('#target_temperature') + +let maxValues = 4 * 60 * 60 // 60 minutes (4 values/sec) + +let hs: Highcharts.StockChart; +let hsOptions: Highcharts.Options + +const getRelaisBands = () => { + let relaisBands = [] + let active = false; + + for (let r of relaisData) { + if (r[1] === 1 && !active) { + active = true + relaisBands.push({ + from: r[0], + to: relaisData[relaisData.length - 1][0] + 1000, + color: 'rgba(255, 0, 0, 0.25)' + + }) + } + if (r[1] === 0 && active) { + relaisBands[relaisBands.length - 1]['to'] = r[0] + active = false + } + } + return relaisBands +} + +const dashStyle: DashStyleValue = 'Dash' +const getTargetLine = () => { + return [ + { value: targetTemp, width: 3, dashStyle: dashStyle } + ] +} + +const createChart = () => { + hsOptions = { + chart: { + animation: { + duration: 250, + easing: 'linear' + }, + }, + + credits: { + enabled: false + }, + + title: { + text: 'Temperature', + align: 'left', + }, + + rangeSelector: { + enabled: false, + inputDateFormat: '%H:%M:%S' + }, + + yAxis: { + title: { + text: '°C' + }, + opposite: false, + min: 0, + max: 600, + tickInterval: 100, + plotBands: [{ + from: 0, + to: 250, + color: 'rgba(69, 171, 255, 0.34)' + }, { + from: 250, + to: 420, + color: 'rgba(0, 255, 60, 0.34)' + }, + { + from: 420, + to: 530, + color: 'rgba(255, 162, 0, 0.4)' + }, + { + from: 530, + to: 600, + color: 'rgba(255, 0, 0, 0.4)' + }], + plotLines: getTargetLine() + }, + + xAxis: { + type: 'datetime', + tickInterval: 30 * 1000, // 30 seconds + labels: { + rotation: -45, + formatter: (f) => { + let d = new Date(f.pos) + return pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + }, + style: { + fontSize: '0.55em' + } + }, + plotBands: getRelaisBands(), + + }, + + legend: { + enabled: false, + }, + + tooltip: { + formatter: function () { + let d = new Date(this.x) + return [pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()), `${this.y?.toFixed(1)}°C`] + } + }, + + plotOptions: { + series: { + animation: false, + tooltip: { + valueDecimals: 2, + }, + } + }, + data: { + enablePolling: true, + dataRefreshRate: 0.5 + }, + + series: [{ + type: 'areaspline', + name: 'Temperature', + data: temperatureData + }], + + responsive: { + rules: [{ + condition: { + maxWidth: 1000 + }, + chartOptions: { + legend: { + layout: 'horizontal', + align: 'center', + verticalAlign: 'bottom' + } + } + }] + } + } + + if (container && hsOptions) + hs = Highcharts.stockChart(container, hsOptions) +} + +const onLoad = () => { + initWebSocket(); + + + if (container) { + createChart() + + relaisSwitch?.addEventListener('click', switchRelais) + targetTempInput?.addEventListener('change', changeTargetTemp) + + + if (import.meta.env.DEV) { + // spoof some test data + setInterval(() => { + if (temperatureData.length > maxValues) { + temperatureData.shift() + relaisData.shift() + } + let d = new Date() + + if (temp < 150) { + temp = temp + Math.random() * 4 - 1 + } else if (temp < 20) { + temp = temp + Math.random() * 4 - 1.3 + } else if (temp < targetTemp) { + temp = temp + Math.random() * 4 - 1.5 + } else if (temp >= targetTemp) { + temp = temp + Math.random() * 3 - 1.6 + } + if (relais) { + temp = temp + 0.2 + } else { + temp = temp - 0.1 + } + + if (temperature) + temperature.innerHTML = temp.toFixed(2); + temperatureData.push([d.getTime(), temp]) + relaisData.push([d.getTime(), relais]) + + + hs.xAxis[0].update({ plotBands: getRelaisBands() }) + hs.yAxis[0].update({ plotLines: getTargetLine() }) + hs.series[0].setData(temperatureData, true) + }, 250) + } + } +} + + +window.addEventListener('load', onLoad); + +const getReadings = () => { + ws.send("getReadings"); +} + +const initWebSocket = () => { + if (!import.meta.env.DEV) { + console.log('Trying to open a WebSocket connection…'); + ws = new WebSocket(gateway); + ws.onopen = onOpen; + ws.onclose = onClose; + ws.onmessage = onMessage; + } +} + +// When websocket is established, call the getReadings() function +const onOpen = () => { + console.log('Connection opened'); + getReadings(); +} + +const onClose = () => { + console.log('Connection closed'); + setTimeout(initWebSocket, 2000); +} + +// Function that receives the message from the ESP32 with the readings +const onMessage = (event: MessageEvent) => { + let myObj = JSON.parse(event.data); + let keys = Object.keys(myObj); + + for (var i = 0; i < keys.length; i++) { + let key = keys[i]; + let element = document.getElementById(key) + if (element) { + element.innerHTML = myObj[key].toFixed(2); + } + let d = new Date() + if (key === 'temperature') { + if (temperatureData.length > maxValues) { + temperatureData.shift() + } + temperatureData.push([d.getTime(), myObj[key]]) + + } else if (key === 'relais') { + if (relaisData.length > maxValues) { + relaisData.shift() + } + relaisData.push([d.getTime(), myObj[key]]) + if (myObj[key]) { + oven?.classList.add('on') + relaisSwitch?.classList.add('on') + if (relaisSwitchInput) + relaisSwitchInput.checked = true + } else { + oven?.classList.remove('on') + relaisSwitch?.classList.remove('on') + if (relaisSwitchInput) + relaisSwitchInput.checked = false + } + } + + hs.xAxis[0].update({ plotBands: getRelaisBands() }) + hs.yAxis[0].update({ plotLines: getTargetLine() }) + hs.series[0].setData(temperatureData, true) + } +} + +const switchRelais = () => { + console.log('Send switch relais') + if (!import.meta.env.DEV) { + ws.send("switchRelais"); + } else { + // spoof some test data + if (!relais) { + relais = 1 + oven?.classList.add('on') + relaisSwitch?.classList.add('on') + if (relaisSwitchInput) + relaisSwitchInput.checked = true + } else { + relais = 0 + oven?.classList.remove('on') + + relaisSwitch?.classList.remove('on') + if (relaisSwitchInput) + relaisSwitchInput.checked = false + } + } +} + +const changeTargetTemp = (e: Event) => { + const target = e.target as HTMLTextAreaElement; + let value = Number(target?.value) + targetTemp = value + console.log('Changed target temp to ' + targetTemp) + if (!import.meta.env.DEV) { + ws.send("setTargetTemp: " + pad(targetTemp, 3)); + } +} + diff --git a/src/secrets.example.h b/src/secrets.example.h new file mode 100644 index 0000000..caba719 --- /dev/null +++ b/src/secrets.example.h @@ -0,0 +1,3 @@ +// Replace with your network credentials +const char *ssid = "WIFI_SSID"; +const char *password = "WIFI_PWD"; diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..c0997ca --- /dev/null +++ b/src/styles.css @@ -0,0 +1,204 @@ +html, +body { + font-family: + ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + margin: 0 +} + +p { + margin: 0; +} + +.nav { + background-color: black; + color: white; + width: full; + text-align: center; + padding: 1em; +} + +.nav-title { + font-size: xx-large; +} + +.content { + max-width: 1000px; + margin: auto; +} + +.card-grid { + margin-top: 15px; + display: flex; + justify-content: center; + align-items: center; + gap: 2em; +} + +.graph { + height: 310px; + overflow: hidden; + display: flex; + justify-content: center; +} + +.plot-wrapper { + height: 400px; +} + +.card { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-width: 25%; + padding: 1em; + min-height: 120px; + box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5); + border-radius: 10px; +} + +.reading { + font-weight: bold; + margin-top: 18px; +} + + +@property --ovenColor1 { + syntax: ''; + initial-value: rgba(255, 0, 0, 0); + inherits: false; +} + + +@property --ovenColor2 { + syntax: ''; + initial-value: rgba(255, 0, 0, 0); + inherits: false; +} + +.oven { + display: flex; + justify-content: center; + margin: auto; +} + +.oven-wrapper { + background: radial-gradient(var(--ovenColor1), var(--ovenColor2), rgba(255, 0, 0, 0), rgba(255, 0, 0, 0)); + width: 50%; + display: flex; + justify-content: center; + overflow: visible; + padding: 30px; + transition: --ovenColor1 3s, --ovenColor2 3s; +} + +.oven.on .oven-wrapper { + --ovenColor1: rgb(255, 60, 0); + --ovenColor2: rgba(255, 60, 0, 0.4); +} + +.oven .oven-img-wrapper { + width: 65%; +} + +.oven img { + width: 100%; +} + +.relais { + cursor: pointer; +} + +.onoffswitch { + pointer-events: none; + margin-top: 10px; + position: relative; + width: 90px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.onoffswitch-checkbox { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.onoffswitch-label { + display: block; + overflow: hidden; + cursor: pointer; + border: 2px solid #999999; + border-radius: 20px; +} + +.onoffswitch-inner { + display: block; + width: 200%; + margin-left: -100%; + transition: margin 0.3s ease-in 0s; +} + +.onoffswitch-inner:before, +.onoffswitch-inner:after { + display: block; + float: left; + width: 50%; + height: 30px; + padding: 0; + line-height: 30px; + font-size: 14px; + color: white; + font-family: Trebuchet, Arial, sans-serif; + font-weight: bold; + box-sizing: border-box; +} + +.onoffswitch-inner:before { + content: "ON"; + padding-left: 10px; + background-color: #C43535; + color: #FFFFFF; +} + +.onoffswitch-inner:after { + content: "OFF"; + padding-right: 10px; + background-color: #EEEEEE; + color: #999999; + text-align: right; +} + +.onoffswitch-switch { + display: block; + width: 18px; + margin: 6px; + background: #FFFFFF; + position: absolute; + top: 0; + bottom: 0; + right: 56px; + border: 2px solid #999999; + border-radius: 20px; + transition: all 0.3s ease-in 0s; +} + +.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-inner { + margin-left: 0; +} + +.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-switch { + right: 0px; +} + +.settings { + margin-top: 50px; +} + +.settings input { + max-width: 50px; + font-size: larger; + font-weight: bold; +} \ No newline at end of file diff --git a/src/vite.dev.d.ts b/src/vite.dev.d.ts new file mode 100644 index 0000000..704b833 --- /dev/null +++ b/src/vite.dev.d.ts @@ -0,0 +1,7 @@ +interface ImportMetaEnv { + readonly DEV: boolean; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8b62f07 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..e56f581 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + outDir: 'data', + chunkSizeWarningLimit: 1000 + } +});