From ec25b2256798fceaab80df53ec6646c8d6f0b8df Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 9 Feb 2025 13:46:46 +0100 Subject: [PATCH] Feat: UI Elements (#1) * adds a bunch of ui components with mock data * build docker image with yarn.lock & yarn instead of npm * easier to add topics, build options smarter from ENV vars * fallback to index.html on all requests for SPA * updates w/ black/white support, better mobile styling * adds test components to home & dashboard pages * adds chart.js dependency * mqtt payload examples * lint * drone ci config * eslint fixes --- .drone.yml | 105 ++++++++++++ Dockerfile | 8 +- README.md | 31 ++++ nginx.conf | 22 ++- package.json | 1 + src/lib/components/Badge.svelte | 55 ++++++ src/lib/components/Calendar.svelte | 110 ++++++++++++ src/lib/components/Card.svelte | 8 +- src/lib/components/DateSeparator.svelte | 2 +- src/lib/components/Dropdown.svelte | 99 +++++++++++ src/lib/components/FooterNavigation.svelte | 5 + src/lib/components/Graph.svelte | 159 ++++++++++++++++++ src/lib/components/HiveSummary.svelte | 2 + src/lib/components/Modal.svelte | 9 +- src/lib/components/Slider.svelte | 76 +++++++++ .../components/cards/HighTemperature.svelte | 13 +- src/lib/components/cards/LowBattery.svelte | 13 +- .../components/cards/LowTemperature.svelte | 13 +- src/lib/components/cards/NoData.svelte | 13 +- src/lib/components/cards/WeightChanged.svelte | 15 +- .../components/displays/BatteryDisplay.svelte | 17 +- .../displays/HumidityDisplay.svelte | 20 +-- .../components/displays/QueenDisplay.svelte | 17 +- .../components/displays/SyncDisplay.svelte | 38 ++--- .../displays/TemperatureDisplay.svelte | 22 ++- .../components/displays/WeightDisplay.svelte | 19 +-- src/lib/components/modals/GatewayModal.svelte | 4 +- src/lib/components/modals/ModalHeader.svelte | 2 +- src/lib/icons/AlarmIcon.svelte | 18 +- src/lib/icons/ArrowLeft.svelte | 19 ++- src/lib/icons/Battery.svelte | 25 ++- src/lib/icons/DashboardIcon.svelte | 21 ++- src/lib/icons/SettingIcon.svelte | 16 +- src/lib/icons/Sync.svelte | 20 ++- src/lib/icons/batteryLow.svelte | 23 ++- src/lib/icons/crown.svelte | 17 +- src/lib/icons/hive.svelte | 31 ++-- src/lib/icons/humidity.svelte | 35 +++- src/lib/icons/marker.svelte | 17 +- src/lib/icons/network.svelte | 21 ++- src/lib/icons/networkDisconnected.svelte | 30 +++- src/lib/icons/thermometer.svelte | 28 ++- src/lib/icons/thermometerCold.svelte | 34 +++- src/lib/icons/thermometerHot.svelte | 34 +++- src/lib/icons/weight.svelte | 22 ++- src/lib/interfaces/IModal.ts | 6 +- src/lib/interfaces/ITelemetry.ts | 24 +-- src/lib/mqttClient.ts | 101 ++++++----- src/lib/telemetryFormatters.ts | 20 +-- src/routes/+layout.svelte | 3 +- src/routes/+page.svelte | 31 +++- src/routes/alarms/+page.svelte | 31 ++-- src/routes/dashboard/+page.svelte | 84 ++++++++- src/routes/settings/+page.svelte | 12 +- static/app.css | 147 ++++++++-------- yarn.lock | 12 ++ 56 files changed, 1374 insertions(+), 406 deletions(-) create mode 100644 .drone.yml create mode 100644 src/lib/components/Badge.svelte create mode 100644 src/lib/components/Calendar.svelte create mode 100644 src/lib/components/Dropdown.svelte create mode 100644 src/lib/components/Graph.svelte create mode 100644 src/lib/components/Slider.svelte diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..0c5d267 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,105 @@ +--- +kind: pipeline +type: docker +name: Build + +platform: + os: linux + arch: amd64 + +volumes: + - name: cache + host: + path: /tmp/cache + +trigger: + event: + include: + - push + +steps: + - name: Load cached frontend packages + image: sinlead/drone-cache:1.0.0 + settings: + action: load + key: yarn.lock + mount: node_modules + prefix: yarn-modules-${DRONE_REPO_NAME} + volumes: + - name: cache + path: /cache + + - name: Frontend install + image: node:current-alpine + commands: + - node -v + - yarn --version + - yarn + + - name: Cache frontend packages + image: sinlead/drone-cache:1.0.0 + settings: + action: save + key: yarn.lock + mount: node_modules + prefix: yarn-modules-${DRONE_REPO_NAME} + volumes: + - name: cache + path: /cache + + - name: Frontend build + image: node:current-alpine + commands: + - yarn build + + - name: Lint project using eslint + image: node:current-alpine + commands: + - yarn lint + +--- +kind: pipeline +type: docker +name: Publish + +platform: + os: linux + arch: amd64 + +steps: + - name: Publish to ghcr + image: plugins/docker + environment: + PUBLIC_MQTT_BROKER_WS_URL: + from_secret: MQTT_BROKER_WS_URL + settings: + registry: ghcr.io + repo: ghcr.io/kevinmidboe/${DRONE_REPO_NAME} + dockerfile: Dockerfile + username: + from_secret: GITHUB_USERNAME + password: + from_secret: GHCR_UPLOAD_TOKEN + build_args: + - PUBLIC_MQTT_BROKER_WS_URL=wss://hive.schleppe.cloud/mqtt + build_args_from_env: + - PUBLIC_MQTT_BROKER_WS_URL + tags: + - latest + - ${DRONE_COMMIT_SHA} + +trigger: + event: + include: + - push + exclude: + - pull_request + branch: + - main + +depends_on: + - Build + +--- +kind: signature +hmac: 41d26f0c24afccaea20bfa929d51e7ac8bdaf5ebbc523349ed6934411af026c1 diff --git a/Dockerfile b/Dockerfile index a007b76..cb90705 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,14 @@ WORKDIR /app COPY package*.json . COPY *config* . -COPY .env . +COPY yarn.lock . COPY src/ src COPY static/ static -RUN npm install -RUN npm run build +ARG PUBLIC_MQTT_BROKER_WS_URL + +RUN yarn install +RUN yarn run build FROM nginx:alpine diff --git a/README.md b/README.md index b0881a6..a0d43a4 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,12 @@ Hive monitor is a frontend for displaying data from MQTT server receiving events from bee hives. The complementary project [hivemonitor-ESP32-firmware]() reads sensor values and publishes on MQTT topic. Complemetary hivemonitor repositories: + - [Hive monitor ESP32 Firmware](https://github.com/kevinmidboe/hivemonitor-esp32-firmware) - [Hive monitor ESP32 PCB design](https://github.com/kevinmidboe/hivemonitor-pcb) ## Preview + [Preview site](https://hive.schleppe.cloud) ![Hive monitor site, 4 pages](https://github.com/KevinMidboe/hivemonitor/assets/2287769/7fdb3f1c-3f2b-4e1f-b8c8-290b7e6d1d2b) @@ -74,3 +76,32 @@ Build and run using nginx in docker: docker build -t hivemonitor . docker run -p 8080:8080 --name hivemonitor-frontend hivemonitor ``` + +## Example mqtt payloads + +should automate generating interfaces or grpc. + +telemetry/gateway/[id]: + +```json +{ + "gateway_name": "Elisabeth", + "t": "2025-02-08 15:03:30", + "temperature": "22", + "battery_level": "20" +} +``` + +telemetry/hive/[id]: + +```json +{ + "hive_name": "Elisabeth", + "t": "2025-02-08 15:03:30", + "temperature": "22", + "humidity": "30", + "pressure": "30", + "weight": "30", + "battery_level": "20" +} +``` diff --git a/nginx.conf b/nginx.conf index 23c9966..05422b1 100644 --- a/nginx.conf +++ b/nginx.conf @@ -18,5 +18,25 @@ http { root /app/; gzip_static on; + + + # what file to server as index + index index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to redirecting to index.html + try_files $uri $uri/ $uri.html /index.html; + } + + location ~* \.(?:css|js|jpg|svg)$ { + expires 30d; + add_header Cache-Control "public"; + } + + location ~* \.(?:json)$ { + expires 1d; + add_header Cache-Control "public"; + } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 71470aa..d03b136 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "type": "module", "dependencies": { + "chart.js": "^4.4.7", "mqtt": "^5.0.0", "svelte-gestures": "^1.5.2" } diff --git a/src/lib/components/Badge.svelte b/src/lib/components/Badge.svelte new file mode 100644 index 0000000..9623291 --- /dev/null +++ b/src/lib/components/Badge.svelte @@ -0,0 +1,55 @@ + + +
+
+
+ {dataType} + {lastValue} +
+ + {lastUpdated} mins +
+ +
+ Up from last 2 days +
+
+ + diff --git a/src/lib/components/Calendar.svelte b/src/lib/components/Calendar.svelte new file mode 100644 index 0000000..98a71f2 --- /dev/null +++ b/src/lib/components/Calendar.svelte @@ -0,0 +1,110 @@ + + +
+

{monthString}

+
+ + {#each Array(firstDay).fill(null) as _} +
+ {/each} + + {#each Array(daysInMonth) + .fill(0) + .map((_, i) => i + 1) as _} +
+ +
+ {/each} +
+
+ + diff --git a/src/lib/components/Card.svelte b/src/lib/components/Card.svelte index bd6389e..3478c7a 100644 --- a/src/lib/components/Card.svelte +++ b/src/lib/components/Card.svelte @@ -4,8 +4,8 @@ export let title = ''; export let iconBackground = 'var(--background)'; - export let opens: any = null; - export let data: any = {}; + export let opens: any = null; // eslint-disable-line + export let data: any = {}; // eslint-disable-line export let borderLess = false; function openModal() { @@ -128,8 +128,8 @@ } .metric .icon { - max-width: 22px; - height: 22px; + max-width: 28px; + height: 28px; margin-right: 0.25rem; } } diff --git a/src/lib/components/DateSeparator.svelte b/src/lib/components/DateSeparator.svelte index bffe6a1..12c5975 100644 --- a/src/lib/components/DateSeparator.svelte +++ b/src/lib/components/DateSeparator.svelte @@ -2,7 +2,7 @@ export let date: Date; function convertDate() { - let [_, time] = date.toLocaleString('nb-NO').split(', '); + let [_, time] = date.toLocaleString('nb-NO').split(', '); // eslint-disable-line time = time.slice(0, time.length - 3); const diffDays = Math.floor((new Date().getTime() - date.getTime()) / 86400000); diff --git a/src/lib/components/Dropdown.svelte b/src/lib/components/Dropdown.svelte new file mode 100644 index 0000000..9eb465d --- /dev/null +++ b/src/lib/components/Dropdown.svelte @@ -0,0 +1,99 @@ + + + + + diff --git a/src/lib/components/FooterNavigation.svelte b/src/lib/components/FooterNavigation.svelte index 3a8b133..f741d76 100644 --- a/src/lib/components/FooterNavigation.svelte +++ b/src/lib/components/FooterNavigation.svelte @@ -43,6 +43,7 @@ display: flex; flex-direction: column; align-items: center; + width: 100%; cursor: pointer; padding: 0.6rem 0.5rem 0.2rem; color: var(--color); @@ -50,6 +51,10 @@ transition: all 200ms ease; will-change: font-weight; + &:hover { + background-color: var(--background-60); + } + .icon { width: 20px; height: 20px; diff --git a/src/lib/components/Graph.svelte b/src/lib/components/Graph.svelte new file mode 100644 index 0000000..c041574 --- /dev/null +++ b/src/lib/components/Graph.svelte @@ -0,0 +1,159 @@ + + +
+
+
+ {dataType} + {lastValue} +
+ + {date} days +
+ +
+ +
+
+ + diff --git a/src/lib/components/HiveSummary.svelte b/src/lib/components/HiveSummary.svelte index 13aced7..1c12dd1 100644 --- a/src/lib/components/HiveSummary.svelte +++ b/src/lib/components/HiveSummary.svelte @@ -53,4 +53,6 @@ {/each} + diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte index a371fc3..6b901a3 100644 --- a/src/lib/components/Modal.svelte +++ b/src/lib/components/Modal.svelte @@ -16,17 +16,17 @@ function toggleHandler(isOpen: boolean) { if (!browser || !modalElement) return; const app = document.getElementById('app'); - if (!app) return + if (!app) return; if (isOpen) { app.classList.add('no-scroll'); app.inert = true; - app.setAttribute('aria-hidden', 'true') + app.setAttribute('aria-hidden', 'true'); } else { setTimeout(() => (modalElement.scrollTop = 0), 500); app.classList.remove('no-scroll'); app.inert = false; - app.setAttribute('aria-hidden', 'false') + app.setAttribute('aria-hidden', 'false'); } } @@ -52,7 +52,7 @@ diff --git a/src/lib/components/cards/HighTemperature.svelte b/src/lib/components/cards/HighTemperature.svelte index 0ca0ccd..a2068bc 100644 --- a/src/lib/components/cards/HighTemperature.svelte +++ b/src/lib/components/cards/HighTemperature.svelte @@ -1,14 +1,13 @@ - + - Over: { temperature }°C + Over: {temperature}°C - diff --git a/src/lib/components/cards/LowBattery.svelte b/src/lib/components/cards/LowBattery.svelte index 9ca210f..2e8b4c8 100644 --- a/src/lib/components/cards/LowBattery.svelte +++ b/src/lib/components/cards/LowBattery.svelte @@ -1,15 +1,14 @@ - + - Under: { battery }% + Under: {battery}% - diff --git a/src/lib/components/cards/LowTemperature.svelte b/src/lib/components/cards/LowTemperature.svelte index 286a353..a7bc6d7 100644 --- a/src/lib/components/cards/LowTemperature.svelte +++ b/src/lib/components/cards/LowTemperature.svelte @@ -1,14 +1,13 @@ - + - Under: { temperature }°C + Under: {temperature}°C - diff --git a/src/lib/components/cards/NoData.svelte b/src/lib/components/cards/NoData.svelte index 62bb768..30d0b53 100644 --- a/src/lib/components/cards/NoData.svelte +++ b/src/lib/components/cards/NoData.svelte @@ -1,14 +1,13 @@ - + - More than: { time } hours + More than: {time} hours - diff --git a/src/lib/components/cards/WeightChanged.svelte b/src/lib/components/cards/WeightChanged.svelte index 63d579d..37ef510 100644 --- a/src/lib/components/cards/WeightChanged.svelte +++ b/src/lib/components/cards/WeightChanged.svelte @@ -1,15 +1,14 @@ - + - From { from }kg to { to }kg + From {from}kg to {to}kg - diff --git a/src/lib/components/displays/BatteryDisplay.svelte b/src/lib/components/displays/BatteryDisplay.svelte index 22d54a7..dcb75aa 100644 --- a/src/lib/components/displays/BatteryDisplay.svelte +++ b/src/lib/components/displays/BatteryDisplay.svelte @@ -1,18 +1,17 @@ - + - Battery - { formatBattery(battery) }% remaining + Battery + {formatBattery(battery)}% remaining - \ No newline at end of file + diff --git a/src/lib/components/displays/HumidityDisplay.svelte b/src/lib/components/displays/HumidityDisplay.svelte index 0f60b2c..143feb5 100644 --- a/src/lib/components/displays/HumidityDisplay.svelte +++ b/src/lib/components/displays/HumidityDisplay.svelte @@ -1,20 +1,18 @@ - + - Humidity - { formatHumidity(humidity) }% - Min 24 - Max 48 + Humidity + {formatHumidity(humidity)}% + Min 24 - Max 48 - \ No newline at end of file + diff --git a/src/lib/components/displays/QueenDisplay.svelte b/src/lib/components/displays/QueenDisplay.svelte index b8f6712..8f7aca6 100644 --- a/src/lib/components/displays/QueenDisplay.svelte +++ b/src/lib/components/displays/QueenDisplay.svelte @@ -1,18 +1,15 @@ - + - Resident Queen - Elisabeth - Age 453 days + Resident Queen + Elisabeth + Age 453 days - \ No newline at end of file + diff --git a/src/lib/components/displays/SyncDisplay.svelte b/src/lib/components/displays/SyncDisplay.svelte index b31c728..34442b1 100644 --- a/src/lib/components/displays/SyncDisplay.svelte +++ b/src/lib/components/displays/SyncDisplay.svelte @@ -1,33 +1,31 @@ - + - Last synced - { sinceUpdate }s ago + Last synced + {sinceUpdate}s ago - \ No newline at end of file + diff --git a/src/lib/components/displays/TemperatureDisplay.svelte b/src/lib/components/displays/TemperatureDisplay.svelte index 06733bc..f61641c 100644 --- a/src/lib/components/displays/TemperatureDisplay.svelte +++ b/src/lib/components/displays/TemperatureDisplay.svelte @@ -1,22 +1,20 @@ - + - Temperature - { formatTemperature(temperature) }°C + Temperature + {formatTemperature(temperature)}°C - {change} + {change} - \ No newline at end of file + diff --git a/src/lib/components/displays/WeightDisplay.svelte b/src/lib/components/displays/WeightDisplay.svelte index 3bbd515..0891c4c 100644 --- a/src/lib/components/displays/WeightDisplay.svelte +++ b/src/lib/components/displays/WeightDisplay.svelte @@ -1,19 +1,18 @@ - + - Weight - { formatWeight(weight) }kg - Min 22.4 - Max 24.4 + Weight + {formatWeight(weight)}kg + Min 22.4 - Max 24.4 \ No newline at end of file + diff --git a/src/lib/components/modals/GatewayModal.svelte b/src/lib/components/modals/GatewayModal.svelte index 8aaf3d1..c28e7ce 100644 --- a/src/lib/components/modals/GatewayModal.svelte +++ b/src/lib/components/modals/GatewayModal.svelte @@ -36,8 +36,8 @@ {#if selectedSection === segments[0]}
- - + +

diff --git a/src/lib/components/modals/ModalHeader.svelte b/src/lib/components/modals/ModalHeader.svelte index 7602661..13c6191 100644 --- a/src/lib/components/modals/ModalHeader.svelte +++ b/src/lib/components/modals/ModalHeader.svelte @@ -5,7 +5,7 @@ export let title: string; export let subtitle: string; - export let icon: any; + export let icon: any; // eslint-disable-line let bodyTitleEl: HTMLElement; let headerTitleEl: HTMLElement; diff --git a/src/lib/icons/AlarmIcon.svelte b/src/lib/icons/AlarmIcon.svelte index cdd8c1e..2b8537c 100644 --- a/src/lib/icons/AlarmIcon.svelte +++ b/src/lib/icons/AlarmIcon.svelte @@ -20,11 +20,18 @@ --> - - - - - + + + + - diff --git a/src/lib/icons/ArrowLeft.svelte b/src/lib/icons/ArrowLeft.svelte index e0eb41f..6305402 100644 --- a/src/lib/icons/ArrowLeft.svelte +++ b/src/lib/icons/ArrowLeft.svelte @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/src/lib/icons/Battery.svelte b/src/lib/icons/Battery.svelte index ad31322..38e76e6 100644 --- a/src/lib/icons/Battery.svelte +++ b/src/lib/icons/Battery.svelte @@ -1,5 +1,5 @@ - - - - - - - + + + + + diff --git a/src/lib/icons/DashboardIcon.svelte b/src/lib/icons/DashboardIcon.svelte index c64dd2f..410a26d 100644 --- a/src/lib/icons/DashboardIcon.svelte +++ b/src/lib/icons/DashboardIcon.svelte @@ -12,10 +12,19 @@ --> - - - - - - + + + + + diff --git a/src/lib/icons/SettingIcon.svelte b/src/lib/icons/SettingIcon.svelte index cd0eb87..a6ba374 100644 --- a/src/lib/icons/SettingIcon.svelte +++ b/src/lib/icons/SettingIcon.svelte @@ -5,8 +5,16 @@ --> - - - - + + + + diff --git a/src/lib/icons/Sync.svelte b/src/lib/icons/Sync.svelte index 82d36c4..c41e111 100644 --- a/src/lib/icons/Sync.svelte +++ b/src/lib/icons/Sync.svelte @@ -1,5 +1,5 @@ - - - - - + + + + diff --git a/src/lib/icons/batteryLow.svelte b/src/lib/icons/batteryLow.svelte index 111d3e9..c1f3a24 100644 --- a/src/lib/icons/batteryLow.svelte +++ b/src/lib/icons/batteryLow.svelte @@ -1,10 +1,21 @@ - - - - - + + + + + diff --git a/src/lib/icons/crown.svelte b/src/lib/icons/crown.svelte index f3d775c..8d47a65 100644 --- a/src/lib/icons/crown.svelte +++ b/src/lib/icons/crown.svelte @@ -8,8 +8,17 @@ --> - - - - + + + + diff --git a/src/lib/icons/hive.svelte b/src/lib/icons/hive.svelte index 9c419dd..6074ca6 100644 --- a/src/lib/icons/hive.svelte +++ b/src/lib/icons/hive.svelte @@ -1,13 +1,19 @@ - - - - + + - + - +-84 19 -138 19 -209 0z" + /> + diff --git a/src/lib/icons/humidity.svelte b/src/lib/icons/humidity.svelte index 9966b19..3e9f180 100644 --- a/src/lib/icons/humidity.svelte +++ b/src/lib/icons/humidity.svelte @@ -5,12 +5,31 @@ --> - - - - - - - - + + + + + + + + diff --git a/src/lib/icons/marker.svelte b/src/lib/icons/marker.svelte index e74ceda..cb2fb73 100644 --- a/src/lib/icons/marker.svelte +++ b/src/lib/icons/marker.svelte @@ -1,4 +1,15 @@ - - - + + + diff --git a/src/lib/icons/network.svelte b/src/lib/icons/network.svelte index 662bbaa..51684e3 100644 --- a/src/lib/icons/network.svelte +++ b/src/lib/icons/network.svelte @@ -12,9 +12,20 @@ --> - - - - - + + + + + diff --git a/src/lib/icons/networkDisconnected.svelte b/src/lib/icons/networkDisconnected.svelte index 5197e2a..a72d930 100644 --- a/src/lib/icons/networkDisconnected.svelte +++ b/src/lib/icons/networkDisconnected.svelte @@ -1,5 +1,5 @@ - - - - - - - + + + + + + diff --git a/src/lib/icons/thermometer.svelte b/src/lib/icons/thermometer.svelte index 6d244a7..a375837 100644 --- a/src/lib/icons/thermometer.svelte +++ b/src/lib/icons/thermometer.svelte @@ -1,5 +1,5 @@ - - - - - - + + + + + + diff --git a/src/lib/icons/thermometerCold.svelte b/src/lib/icons/thermometerCold.svelte index 0163e97..5e458f6 100644 --- a/src/lib/icons/thermometerCold.svelte +++ b/src/lib/icons/thermometerCold.svelte @@ -1,13 +1,29 @@ - - - - - - - - + + + + + + + diff --git a/src/lib/icons/thermometerHot.svelte b/src/lib/icons/thermometerHot.svelte index 4edccac..cf947c6 100644 --- a/src/lib/icons/thermometerHot.svelte +++ b/src/lib/icons/thermometerHot.svelte @@ -1,6 +1,7 @@ + - - - - - - - + + + + + + + diff --git a/src/lib/icons/weight.svelte b/src/lib/icons/weight.svelte index b836a3d..45a7901 100644 --- a/src/lib/icons/weight.svelte +++ b/src/lib/icons/weight.svelte @@ -1,6 +1,7 @@ + - - - - - + + + + + diff --git a/src/lib/interfaces/IModal.ts b/src/lib/interfaces/IModal.ts index 8765dcc..83a20bc 100644 --- a/src/lib/interfaces/IModal.ts +++ b/src/lib/interfaces/IModal.ts @@ -1,4 +1,4 @@ export default interface IModal { - opens: any - data: any -} \ No newline at end of file + opens: any; // eslint-disable-line + data: any; // eslint-disable-line +} diff --git a/src/lib/interfaces/ITelemetry.ts b/src/lib/interfaces/ITelemetry.ts index d648b48..93e0749 100644 --- a/src/lib/interfaces/ITelemetry.ts +++ b/src/lib/interfaces/ITelemetry.ts @@ -1,17 +1,17 @@ export interface IGatewayTelemetry { - gateway_name: string; - t: string; - temperature: string; - battery_level?: string; - last_sync?: string; + gateway_name: string; + t: string; + temperature: string; + battery_level?: string; + last_sync?: string; } export interface IHiveTelemetry { - hive_name: string; - t: string; - temperature: string; - humidity?: string; - pressure?: string; - weight?: string; - battery_level?: string; + hive_name: string; + t: string; + temperature: string; + humidity?: string; + pressure?: string; + weight?: string; + battery_level?: string; } diff --git a/src/lib/mqttClient.ts b/src/lib/mqttClient.ts index a2f3d47..cbeadd2 100644 --- a/src/lib/mqttClient.ts +++ b/src/lib/mqttClient.ts @@ -1,60 +1,73 @@ // NB! mqtt package is imported in from CDN import { addHiveMessage, addGatewayMessageQueue } from './store'; +import { env } from '$env/dynamic/public'; import type { IClientOptions, MqttClient } from 'mqtt'; import type { IGatewayTelemetry, IHiveTelemetry } from './interfaces/ITelemetry'; -const options: IClientOptions = { - port: 80, - protocolVersion: 5, - keepalive: 60, - clean: true, - reconnectPeriod: 1000, - connectTimeout: 30 * 1000, - properties: { - requestResponseInformation: true, - requestProblemInformation: true - } -}; +const fallbackMQTTBroker = 'ws://test.mosquitto.org:8080'; -function setupMQTTClient(host: string) { - const mqttClient: MqttClient = mqtt.connect(host, options); +function buildOptions() { + const url = env?.PUBLIC_MQTT_BROKER_WS_URL || fallbackMQTTBroker; - mqttClient.on('connect', () => { - console.log(`connected to mqtt ${new Date()}`); - mqttClient.subscribe('telemetry/hive/+', { qos: 0 }); - mqttClient.subscribe('telemetry/gateway/+', { qos: 0 }); - }); + const options: IClientOptions = { + protocolVersion: 5, + keepalive: 60, + clean: true, + reconnectPeriod: 1000, + connectTimeout: 30 * 1000, + properties: { + requestResponseInformation: true, + requestProblemInformation: true + } + }; - mqttClient.on('error', (err) => { - console.log(err); - }); + return { host: url, options }; +} - // For every message received, check if topic matches any of the message queues - mqttClient.on('message', (topic: string, message: string) => { - if (topic.includes('/hive/')) { - const json = parseJSONMessage(message); - if (!json) return; - addHiveMessage(json as IHiveTelemetry); - } else if (topic.includes('/gateway/')) { - const json = parseJSONMessage(message); - if (!json) return; - addGatewayMessageQueue(json as IGatewayTelemetry); - } else { - console.warn('got message for unexpected topic:', topic); - console.warn('unexpected message:', message); - } - }); +function messageForwarder(topic: string) { + if (topic.includes('/hive/')) { + return (d: object) => addHiveMessage(d as IHiveTelemetry); + } else if (topic.includes('/gateway/')) { + return (d: object) => addGatewayMessageQueue(d as IGatewayTelemetry); + } + + return () => console.log('WARN! No handler for topic:', topic); +} + +function setupMQTTClient() { + const { host, options } = buildOptions(); + const mqttClient: MqttClient = mqtt.connect(host, options); + + mqttClient.on('connect', () => { + console.log(`connected to mqtt ${new Date()}`); + mqttClient.subscribe('telemetry/hive/+', { qos: 0 }); + mqttClient.subscribe('telemetry/gateway/+', { qos: 0 }); + }); + + mqttClient.on('error', (err) => { + console.log('mqtt client error:'); + console.log(err); + }); + + // For every message received, check if topic matches any of the message queues + mqttClient.on('message', (topic: string, message: string) => { + const func = messageForwarder(topic); + const json = parseJSONMessage(message); + if (!json) return; + + func(json); + }); } function parseJSONMessage(message: string): IHiveTelemetry | IGatewayTelemetry | null { - try { - return JSON.parse(message); - } catch (error) { - console.error('Unable to parse mqtt message:'); - console.error(error); - return null; - } + try { + return JSON.parse(message); + } catch (error) { + console.error('Unable to parse mqtt message:'); + console.error(error); + return null; + } } export default setupMQTTClient; diff --git a/src/lib/telemetryFormatters.ts b/src/lib/telemetryFormatters.ts index e02fe67..8a699b7 100644 --- a/src/lib/telemetryFormatters.ts +++ b/src/lib/telemetryFormatters.ts @@ -1,24 +1,24 @@ export function formatWeight(weight: string | undefined) { - return weight || 24 + return weight || 24; } export function formatTemperature(temp: string | undefined) { - return temp ? temp.replace(".00", "") : '-' + return temp ? temp.replace('.00', '') : '-'; } export function formatHumidity(humidity: string | undefined) { - return humidity ? humidity.replace(".00", "") : '-' + return humidity ? humidity.replace('.00', '') : '-'; } export function formatBattery(batteryLevel: string | undefined) { - return batteryLevel || 89 + return batteryLevel || 89; } export function diffTime(date: Date | string | undefined): number { - if (!date) return new Date().getTime() - if (typeof date === 'string') date = new Date(date) + if (!date) return new Date().getTime(); + if (typeof date === 'string') date = new Date(date); - const seconds = (new Date().getTime() - date.getTime()) / 1000 - return Math.floor(seconds) - // return Math.round(seconds / 10) * 10 -} \ No newline at end of file + const seconds = (new Date().getTime() - date.getTime()) / 1000; + return Math.floor(seconds); + // return Math.round(seconds / 10) * 10 +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 19aaf22..02ffa01 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,11 +1,10 @@

diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6d5e524..f8c3586 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -6,13 +6,22 @@ import LowBattery from '$lib/components/cards/LowBattery.svelte'; import NoData from '$lib/components/cards/NoData.svelte'; import WeightChanged from '$lib/components/cards/WeightChanged.svelte'; + import Graph from '$lib/components/Graph.svelte';

Hive monitor

Hives - +
+ +
+ + Graphs +
+ + +
Gateways @@ -26,4 +35,24 @@
diff --git a/src/routes/alarms/+page.svelte b/src/routes/alarms/+page.svelte index b9eb47a..519fe35 100644 --- a/src/routes/alarms/+page.svelte +++ b/src/routes/alarms/+page.svelte @@ -1,27 +1,22 @@
-

Alarms

+

Alarms

- Alerts - - - - - - - -

- Click on the Vite and Svelte logos to learn more -

+ Alerts + + + + + +

Click on the Vite and Svelte logos to learn more

diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index dbbf131..3e7ab43 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -2,17 +2,15 @@
-

Settings

+

Settings

- - - - \ No newline at end of file +let battery_voltage = (ADC.read(battery)/4095)*2*3.3*1.1; --> + + diff --git a/static/app.css b/static/app.css index 3a4b5f7..f8d8872 100644 --- a/static/app.css +++ b/static/app.css @@ -1,113 +1,118 @@ :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - --color: rgba(255, 255, 255, 0.9); - --color-dim: rgba(255, 255, 255, 0.7); - --background: #242424; - --highlight: #101010; - --background: black; - --highlight: #242424; - --brand: #F6B138; + color-scheme: light dark; + --color: rgba(255, 255, 255, 1); + --color-dim: rgba(255, 255, 255, 0.7); + --background: #242424; + --highlight: #101010; + --background: #000000; + --background-60: #00000060; + --highlight: #242424; + --brand: #f6b138; - color: var(--color); - background-color: var(--background); + color: var(--color); + background-color: var(--background); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } body { - margin:0; + margin: 0; } .no-scroll { - overflow: hidden; + overflow: hidden; } #app { - display: flex; - min-width: 320px; - min-height: 100vh; + display: flex; + min-width: 320px; + min-height: 100vh; } main { - max-width: 550px; - margin: 0 auto 68px; - padding: 1rem; - display: flex; - flex-grow: 1; - flex-direction: column; + margin: 0 auto 68px; + padding: 1rem; + display: flex; + flex-grow: 1; + flex-direction: column; + + @media only screen and (min-width: 768px) { + max-width: 80vw; + } } - + main h1 { - margin: 0 0 2rem; - text-align: center; + margin: 0 0 2rem; + text-align: center; } main .header { - font-size: 1.2rem; - margin-bottom: 0.5rem; - margin-left: 0.5rem; - display: block; - font-weight: bold; + font-size: 1.2rem; + margin-bottom: 0.5rem; + margin-left: 0.5rem; + display: block; + font-weight: bold; - &:not(:first-of-type) { - margin-top: 2rem; - } + &:not(:first-of-type) { + margin-top: 2rem; + } } h1 { - font-size: 3.2em; - line-height: 1; + font-size: 3.2em; + line-height: 1; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; } button:hover { - border-color: #646cff; + border-color: #646cff; } button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { - :root { - --color: rgba(33, 53, 71, 1); - --color-dim: rgba(33, 53, 71, 0.7); - --background: #F3F3F3; - --highlight: white; - --background: white; - --highlight: #F3F3F3; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } + :root { + --color: rgba(33, 53, 71, 1); + --color-dim: rgba(33, 53, 71, 0.7); + --background: #f3f3f3; + --highlight: white; + --background: #ffffff; + --background-60: #ffffff60; + --highlight: #f3f3f3; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } } diff --git a/yarn.lock b/yarn.lock index 27b4666..6863423 100644 --- a/yarn.lock +++ b/yarn.lock @@ -213,6 +213,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf" + integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -553,6 +558,13 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chart.js@^4.4.7: + version "4.4.7" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.7.tgz#7a01ee0b4dac3c03f2ab0589af888db296d896fa" + integrity sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw== + dependencies: + "@kurkle/color" "^0.3.0" + "chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"