95 Commits

Author SHA1 Message Date
fa9c852b8d docs(reports): final tidy up before presentation
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-14 16:24:40 +02:00
8e85c5e20d docs(reports): add A3 co2 curves of the presentation day
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-14 16:17:24 +02:00
4bce5e5ee2 docs(reports): sequence helper in chronos
Co-authored-by: LordBaryhobal <lordbaryhobal@gmail.com>
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-10 22:01:44 +02:00
26f1553e26 docs(reports): backup from typst online
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-10 22:00:34 +02:00
5902a47605 docs(reports): add ui + db
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-10 13:33:45 +02:00
09ee22ff50 docs(reports): init final presentation
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-10 13:27:38 +02:00
964a2d3375 chores: remove release file from repo
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:45:20 +02:00
5ca64315eb chores: remove zephyr sample file
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:44:53 +02:00
9b06b9f6f7 chores: remove useless gitkeep file
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:44:31 +02:00
2ec7eefaa0 chore: remove .DS_STORE
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:20:38 +02:00
3293d9194c docs(GULAG): fix typo
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:20:38 +02:00
adrien balleyguier
d593dfd759 feat(nodes): setting the fastest BLE advertising frequency to 1 minute instead of 2
This is motivated by the measure period by the css811 sensor, which is 1 minute and cannot be set to 2.
Hence, we adverstise every minute to avoid trashing half of the measurements
2026-06-04 21:01:43 +02:00
adrien balleyguier
83705f4b5e fix(nodes): reduce battery consumption
co-authored-by: Klagarge <remi@heredero.ch>
co-authored-by: Terrazed <simon.donnet-monay@nordicsemi.no>
2026-06-04 21:01:43 +02:00
adrien balleyguier
dffdcd36db feat(nodes): using an explicit thread, as it may cause the huge battery usage 2026-06-04 21:01:43 +02:00
adrien balleyguier
676913e280 feat(nodes): adding battery percent as new key/value in the BLE frame
Closes: #66
2026-06-04 21:01:43 +02:00
adrien balleyguier
8a559024a6 feat(nodes): add the battery level in the ble frame
wip: The battery_setup fails for some unknown reasons
2026-06-04 21:01:43 +02:00
adrien balleyguier
7c19b5dadb fix(nodes): using MAC address as BT_ADDR 2026-06-04 21:01:42 +02:00
adrien balleyguier
c4e1315478 feat(nodes): adding management of read error on each values, hence
Closes: #65
2026-06-04 21:01:42 +02:00
adrien balleyguier
6b12612580 fix(nodes): changing incorrect comment 2026-06-04 21:01:42 +02:00
adrien balleyguier
c32728ccbe doc(nodes): adding comments in the nodes code
close #62
2026-06-04 21:01:42 +02:00
adrien balleyguier
78167ceb38 feat(nodes): adding nodes firmware v1.0
Refs: #3
2026-06-04 21:01:42 +02:00
adrien balleyguier
3cdfbef680 fix(nodes): changing window status sensor to have unequipped nodes return 'window is closed' status
Refs: #3
2026-06-04 21:01:42 +02:00
adrien balleyguier
94e0518fa6 fix(nodes): co2 level fetched as desibed in the documentation
The first samples (up to 3) sends co2 level 0xffffffff since the sensor is not ready yet
Refs: #3
2026-06-04 20:59:58 +02:00
adrien balleyguier
f4ac6e91b0 fix(nodes): properly fetching data from temp/hygro sensor
Refs: #3
2026-06-04 20:59:36 +02:00
adrien balleyguier
6fd9959329 fix(nodes): fixing switch device tree and wrong ordering of ble values 2026-06-04 20:59:20 +02:00
adrien balleyguier
35fbe7c488 feat(nodes): adding window status reading 2026-06-04 20:59:20 +02:00
adrien balleyguier
158767deec fix(nodes): adjusting broadcasted data to fit definition
Refs: #3
2026-06-04 20:59:20 +02:00
adrien balleyguier
bce2417ded feat(nodes): adding co2_level retrieval
Refs: #3
2026-06-04 20:59:10 +02:00
adrien balleyguier
53ccda306b feat(nodes): WIP adding first implementation for BLE advertising.
wip: Not tested for now

Refs: #3
2026-06-04 20:58:59 +02:00
adrien balleyguier
71ed4c4bf9 feat(nodes): adding thermometer and hygrometer
Changed sensor value retrieval to return error_code
2026-06-04 20:58:14 +02:00
adrien balleyguier
e1d274470b feat(nodes): WIP supervisor first implementation
wip: untested
2026-06-04 20:58:14 +02:00
adrien balleyguier
f0bc4ee373 feat(nodes) : setting folder construction 2026-06-04 20:47:48 +02:00
adrien balleyguier
f71db42f47 feat(nodes) : adding blinky as starting point for the node implementation 2026-06-04 20:47:48 +02:00
adrien balleyguier
8b9e215cc6 doc(nodes): adding battery to class diagram
linked #63
2026-06-04 15:06:21 +02:00
adrien balleyguier
caee92c595 fix(doc) : changing small mishaps following discussions 2026-06-04 15:06:21 +02:00
2f7c88b701 refactor(db): add constante for defaultTimeout 2026-06-04 14:46:36 +02:00
0f30749534 refactor(db): add constante for max QOS 2026-06-04 14:46:36 +02:00
f4ab3093c3 fix(db): return error directly 2026-06-04 14:46:36 +02:00
89023b86ac fix(db): return only high co2 room for co2-status endpoint
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:36 +02:00
811641c58b refactor(db): change order for history
Get now value from the most recent to the most ancien requested value

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:36 +02:00
be5772e488 refactor(db): using moving average for room history
History is now a moving average over 5min by slice of 1min

Assisted-by: Junie:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:36 +02:00
53fbc87af6 feat(db): add co2-status endpoint in REST API
This endpoint get the co2 status and return for each room if the co2 is too high or in the acceptable level

Assisted-by: Junie:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:36 +02:00
2b766d3d96 feat(db): add co2 watchdog on each room
Assisted-by: Junie:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
7c000f3b9c fix(db): return window tag in room status
Breakchange in Influx, it's now window_open. Change this tag in the returned json to previous tag "window" to avoir a breaking change

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
5a3f8c3c5c feat(server): add traefik entry for ui dashboard
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
2f57e886c0 fix(db): return time in RFC3339 to avoid breakchange
Assisted-by: Junie:claude-opus-4.8
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
fab79aa6b6 fix(db): add the room in the returned json
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
3ff484359e refactor(db): round averages in SQL queries
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
5198784c37 refactor(db): adapt SQL query for 5 min average
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
eecb4a196b refactor(db): add flag to run without MQTT part
Use for local test for developement

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:35 +02:00
8c3b00edd8 feat(db): add endpoint to export influx data to csv
Assisted-by: Junie:claude-sonnet-4.6
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
25c8327662 fix(db): mqtt hostname to keep internal config identique in case of rebuild
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
2355c8f0e9 chore(db): set 30min before offline for battery endpoint
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
c78f1e1509 fix(db): filter random value
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
9776695228 fix(db): push data with partial mapping
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
bde8184ad1 fix(db): window -> window_open
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
022fb97153 fix(db): remove authorization for battery status endpoint
This endpoint is now publicly available without the need of an authorization

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
c34ec94a6b feat(db): get mapping dynamically from file
Assisted-by: Junie:claude-sonnet-4.6
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
679f6fece2 feat(db): add battery REST endpoint
Get latest value of battery for each node

Assisted-by: Junie:claude-sonnet-4.6
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:34 +02:00
ef51f9b3ed refactor(db): map room and node on REST API
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
04c3883744 feat(db): add battery field
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
1bcaad895d fix(db): set higher file limit for influx limitations
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
56c6299417 fix(db): set time interval for influx limitations
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
abf19fb4e2 fix(db): get rooms from mapping file
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
641f6af1f0 feat(db): remove CO2PPM data over 1'000'000'000
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
ccec4efca6 feat(db): add node tag for influx
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
77574e1dfa refactor(db): split campus and room mapping error detection
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
c472064451 fix(db): url for api start with https
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:33 +02:00
c4cf1ba704 fix(db): CORS request
Cross-Origin Resource Sharing now allow all *.e.kb28.ch

Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
980b43e669 fix(db): proper api url instead of using swagger doc url
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
1678ac535b chore(db): add local gitignore
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
b8ddfefc2c feat(db): add deploiement stack
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
50583bb79b chore(db): add pre-commit for swagger documentation
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
790961b15a chore(db): update critical dependancy
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
b4110b81eb refactor(db): unify env getters
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
bf7d0a7005 feat(db): add basic auth
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
fcdb5b5485 feat(db): add parameter for time window in history endpoint
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:32 +02:00
9163fd494b feat(db): add swagger doc
Assisted-by: Junie:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
c2a67684ed feat(db): add mapping
MQTT topic gateway-id/node-id is now mapped to campus/room for influx

Assisted-by: Junie:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
25438f085e chore(db): get InfluxDB token by secrets
Assisted-by: Gemini:gemini-3-flash
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
cea4435bbc fix(db): allow gRPC proxy in traefik
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
a49c3a8472 feat(db): add GET history endpoint
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
2e8f92888e feat(db): add GET current value endpoint
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
3ed1e36c56 feat(db): add rest gateway
- Implement GET rooms list endpoint only

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
3587e10671 feat(db): add initial main implementation
- connect to MQTTS broker
- connect to Influx DB
- subscribe to +/+/update MQTT topic and send receive message to influx
- add traefik config for ui and db

Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:31 +02:00
0086a31f73 feat(db): add SubscribeTyped function and refactor DataPoint structure
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:30 +02:00
a11573609e refactor(db): move package
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:30 +02:00
979c502e27 feat(db): merge datapoint and message
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:30 +02:00
1fb294f495 chore(db): typo and respect go guidelines
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 14:46:30 +02:00
567e9162e2 feat(db): add mqtt gateway from previous project
Co-authored-by: Aydong <coudray@nathan.ch>
Signed-off-by: Klagarge <remi@heredero.ch>
Signed-off-by: Aydong <coudray@nathan.ch>
2026-06-04 14:46:30 +02:00
5a1bfefffb feat(db): add influx gateway from previous project
Co-authored-by: fastium <fastium.pro@proton.me>
Signed-off-by: Klagarge <remi@heredero.ch>
Signed-off-by: fastium <fastium.pro@proton.me>
2026-06-04 14:46:30 +02:00
28a8377231 chore(model): tidy up
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 12:38:11 +02:00
Alison Lecointre
a84ad89e02 created models 2026-06-04 12:38:11 +02:00
Alison Lecointre
35f990daa5 created physic model folder 2026-06-04 12:38:11 +02:00
DjeAvd
3668ef4520 docs(gateway): remove store measurement arrow from sequence diagram
broker -> db storage is handled by the database manager, not the gateway
2026-06-04 12:32:48 +02:00
94 changed files with 6644 additions and 30 deletions

2
.github/CODEOWNERS vendored
View File

@@ -2,4 +2,4 @@
/gateway @DjeAvd
/db/ @Klagarge
/ui/ @khalil-bot
/ml @imfeldd
/model @AlisonLec

9
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,9 @@
repos:
- repo: local
hooks:
- id: swag-init
name: swag init
entry: bash -c 'export PATH=$PATH:$(go env GOPATH)/bin && cd db/src && swag init'
language: system
files: ^db/src/.*\.go$
pass_filenames: false

View File

@@ -2,7 +2,7 @@
## General
- Git history **has** to be kept linear. Branches **have** to be __rebased__ before being merged.
- Merge commit is stricly forbidden.
- Merge commit is strictly forbidden.
- Force pushes are disallowed on shared branches unless discussed beforehand with affected people.
## Branches

View File

@@ -1,4 +1,10 @@
INFLUX_PORT=8181
UI_PORT=8093
INFLUX_DATABASE=provence
REST_USERNAME=
REST_PASSWORD=
MQTT_BROKER_URL=tls://mqtt.e.kb28.ch:8883
MQTT_USERNAME=
MQTT_PASSWORD=

28
db/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
### Go template
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
mapping.json

View File

26
db/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY ./src/go.mod ./src/go.sum ./
RUN go mod tidy
COPY ./src .
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o /gateway .
FROM alpine:latest AS certs
RUN apk --no-cache add ca-certificates
FROM scratch AS final
LABEL org.opencontainers.image.authors="remi.heredero@hevs.ch" \
org.opencontainers.image.title="PI-E2EEDA Gateway MQTT/Influx/REST" \
org.opencontainers.image.description="This container is an application for the E2EEDA PI. Use MQTT to communicate with devices, enabling real-time updates and control. Time-series data, such as device states, is stored in InfluxDB for analytics and monitoring. A REST API provides external access for managing devices and retrieving data." \
org.opencontainers.image.source="https://github.com/PI-E2EEDA/Plein-de-eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-project"
COPY --from=builder /gateway /gateway
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/gateway"]

View File

@@ -1,4 +1,4 @@
# Deployement
# Deployment
1. Create the Influx token offline:
@@ -18,5 +18,24 @@ cp .env.template .env
docker compose up -d
```
## Development
### Swagger Documentation
To ensure the Swagger documentation is always up to date, we use [pre-commit](https://pre-commit.com/). It runs `swag init` automatically before each commit if any Go files in `db/src` have changed.
To install the hooks:
```bash
pre-commit install
```
Alternatively, you can run it manually:
```bash
pre-commit run --all-files
```
or from `db/src`:
```bash
swag init
```
## Traefik
A traefik config file is available on [traefik.yml](./traefik.yml). It can be used with an OIDC provider ([Authentik](https://github.com/goauthentik/authentik) in our case) to control access to the database explorer.

View File

@@ -12,6 +12,7 @@ services:
--object-store=file \
--data-dir=/var/lib/influxdb3/data \
--admin-token-file=/tmp/admin-token.json \
--query-file-limit=1000 \
--disable-authz=health
'
secrets:
@@ -63,6 +64,7 @@ services:
mqtt:
image: rabbitmq:4-management-alpine
container_name: mqtt
hostname: "mqtt"
restart: unless-stopped
ports:
- "15672:15672" # Management plugin HTTP port
@@ -74,6 +76,36 @@ services:
- RABBITMQ_DEFAULT_PASS=${MQTT_PASSWORD:?MQTT_PASSWORD is required}
command: sh -c "rabbitmq-plugins enable rabbitmq_mqtt && rabbitmq-server"
gateway:
build:
context: .
dockerfile: Dockerfile
depends_on:
influxdb:
condition: service_healthy
mqtt:
condition: service_started
environment:
- MQTT_BROKER_URL=${MQTT_BROKER_URL:-mqtt://mqtt:1883}
- MQTT_USERNAME=${MQTT_USERNAME:?MQTT_USERNAME is required}
- MQTT_PASSWORD=${MQTT_PASSWORD:?MQTT_PASSWORD is required}
- INFLUX_URL=${INFLUX_URL:-http://influxdb:${INFLUX_PORT:-8181}}
- INFLUX_DATABASE=${INFLUX_DATABASE}
- INFLUX_TOKEN_FILE=/run/secrets/admin-token
- REST_PORT=${REST_PORT:-8080}
- REST_USERNAME=${REST_USERNAME}
- REST_PASSWORD=${REST_PASSWORD}
- MAPPING_CONFIG_PATH=/config/mapping.json
- CO2_THREASHOLD_MAX=1400
- CO2_THREASHOLD_MIN=1000
secrets:
- admin-token
volumes:
- ./mapping.json:/config/mapping.json:ro
ports:
- "${REST_PORT:-8080}:8080"
restart: unless-stopped
volumes:
influxdb3_data:
rabbitmq_data:

49
db/export-csv-loop.http Normal file
View File

@@ -0,0 +1,49 @@
@host = localhost:8080
@username = user
@password = password
@from = 2026-05-27T14:00:00+02:00
@to = 2026-05-27T18:00:00+02:00
### Export CSV - Room A2 - loop over all nodes
< {%
request.variables.set("nodesA2", [
{"room": "A2", "mac": "E1:C0:30:15:4E:89"},
{"room": "A2", "mac": "C6:7E:0A:DE:DA:74"},
{"room": "A2", "mac": "E8:F3:0A:F7:3B:F3"},
{"room": "A2", "mac": "C2:64:0F:68:35:3E"},
{"room": "A2", "mac": "F5:80:05:76:53:F0"},
{"room": "A2", "mac": "C6:95:1B:A6:49:E6"}
])
%}
GET {{host}}/api/v1/export/csv?node={{$.nodesA2..mac}}&from={{from}}&to={{to}}
Authorization: Basic {{username}} {{password}}
> {%
let current = request.variables.get("nodesA2")[request.iteration()]
client.test(`Export CSV - Room ${current.room} - Node ${current.mac}`, () => {
client.assert(response.status === 200, `Expected 200, got ${response.status}`)
})
%}
### Export CSV - Room A3 - loop over all nodes
< {%
request.variables.set("nodesA3", [
{"room": "A3", "mac": "ED:B2:F3:74:3E:C2"},
{"room": "A3", "mac": "CE:25:63:38:34:05"},
{"room": "A3", "mac": "E6:8A:79:C8:87:25"},
{"room": "A3", "mac": "DC:06:D9:40:7A:CB"},
{"room": "A3", "mac": "D5:2F:7E:30:10:5A"},
{"room": "A3", "mac": "EA:1A:AD:15:5E:9F"}
])
%}
GET {{host}}/api/v1/export/csv?node={{$.nodesA3..mac}}&from={{from}}&to={{to}}
Authorization: Basic {{username}} {{password}}
> {%
let current = request.variables.get("nodesA3")[request.iteration()]
client.test(`Export CSV - Room ${current.room} - Node ${current.mac}`, () => {
client.assert(response.status === 200, `Expected 200, got ${response.status}`)
})
%}

31
db/get-db.http Normal file
View File

@@ -0,0 +1,31 @@
@host = https://api.db.e.kb28.ch
@room-id = B3
@username = PIE2EEDA
@password =
### GET last value of temp, co2, humidity, windows states
GET {{host}}/api/v1/rooms/{{room-id}}/current
Authorization: Basic {{username}} {{password}}
### GET history of a room
@window = 1 day
GET {{host}}/api/v1/rooms/{{room-id}}/history?window={{window}}
Authorization: Basic {{username}} {{password}}
### GET CO2 status of all rooms
GET {{host}}/api/v1/rooms/high-co2
Authorization: Basic {{username}} {{password}}
### GET all rooms
GET {{host}}/api/v1/rooms
Authorization: Basic {{username}} {{password}}
### GET battery status of all devices
GET {{host}}/api/v1/battery
### Export sensor data as CSV for a node over a time range
@node = C2:64:0F:68:35:3E
@from = 2026-05-27T14:00:00+02:00
@to = 2026-05-27T18:00:00+02:00
GET {{host}}/api/v1/export/csv?node={{node}}&from={{from}}&to={{to}}
Authorization: Basic {{username}} {{password}}

8
db/mapping.json.template Normal file
View File

@@ -0,0 +1,8 @@
{
"campus": {
"provence": ["gw-01"]
},
"room": {
"B3": ["E1:C0:30:15:4E:89", "F9:CE:0C:A4:7C:A4"]
}
}

337
db/src/docs/docs.go Normal file
View File

@@ -0,0 +1,337 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/battery": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get the last battery level for each node grouped by room",
"produces": [
"application/json"
],
"tags": [
"battery"
],
"summary": "Get last battery level for each node",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/export/csv": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Export CO2, temperature, humidity, window status and battery data for a node over a time range",
"produces": [
"text/csv"
],
"tags": [
"export"
],
"summary": "Export sensor data as CSV",
"parameters": [
{
"type": "string",
"description": "Node MAC address (e.g. E8:F3:0A:F7:3B:F3)",
"name": "node",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Start time in RFC3339 format (e.g. 2026-05-27T13:00:00Z)",
"name": "from",
"in": "query",
"required": true
},
{
"type": "string",
"description": "End time in RFC3339 format (e.g. 2026-05-27T15:00:00Z)",
"name": "to",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "CSV file",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get a list of all unique rooms from the measurement",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get all unique rooms",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms/high-co2": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get a list of rooms where CO2 levels are above the threshold",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get rooms with high CO2 levels",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/rest.RoomCO2Status"
}
}
}
}
}
},
"/rooms/{room-id}/current": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get the latest record for a specific room",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get current data for a room",
"parameters": [
{
"type": "string",
"description": "Room ID",
"name": "room-id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms/{room-id}/history": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get history for a specific room",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get history for a room",
"parameters": [
{
"type": "string",
"description": "Room ID",
"name": "room-id",
"in": "path",
"required": true
},
{
"type": "string",
"default": "1 day",
"description": "Time window (e.g., 1 day, 1 hour, 30 min)",
"name": "window",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
}
},
"definitions": {
"rest.RoomCO2Status": {
"type": "object",
"properties": {
"co2": {
"type": "integer"
},
"is_high": {
"type": "boolean"
},
"room": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"BasicAuth": {
"type": "basic"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "api.db.e.kb28.ch",
BasePath: "/api/v1",
Schemes: []string{},
Title: "Gateway API",
Description: "This is a gateway API for IoT data.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

313
db/src/docs/swagger.json Normal file
View File

@@ -0,0 +1,313 @@
{
"swagger": "2.0",
"info": {
"description": "This is a gateway API for IoT data.",
"title": "Gateway API",
"contact": {},
"version": "1.0"
},
"host": "api.db.e.kb28.ch",
"basePath": "/api/v1",
"paths": {
"/battery": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get the last battery level for each node grouped by room",
"produces": [
"application/json"
],
"tags": [
"battery"
],
"summary": "Get last battery level for each node",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/export/csv": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Export CO2, temperature, humidity, window status and battery data for a node over a time range",
"produces": [
"text/csv"
],
"tags": [
"export"
],
"summary": "Export sensor data as CSV",
"parameters": [
{
"type": "string",
"description": "Node MAC address (e.g. E8:F3:0A:F7:3B:F3)",
"name": "node",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Start time in RFC3339 format (e.g. 2026-05-27T13:00:00Z)",
"name": "from",
"in": "query",
"required": true
},
{
"type": "string",
"description": "End time in RFC3339 format (e.g. 2026-05-27T15:00:00Z)",
"name": "to",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "CSV file",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get a list of all unique rooms from the measurement",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get all unique rooms",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms/high-co2": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get a list of rooms where CO2 levels are above the threshold",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get rooms with high CO2 levels",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/rest.RoomCO2Status"
}
}
}
}
}
},
"/rooms/{room-id}/current": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get the latest record for a specific room",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get current data for a room",
"parameters": [
{
"type": "string",
"description": "Room ID",
"name": "room-id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/rooms/{room-id}/history": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "Get history for a specific room",
"produces": [
"application/json"
],
"tags": [
"rooms"
],
"summary": "Get history for a room",
"parameters": [
{
"type": "string",
"description": "Room ID",
"name": "room-id",
"in": "path",
"required": true
},
{
"type": "string",
"default": "1 day",
"description": "Time window (e.g., 1 day, 1 hour, 30 min)",
"name": "window",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
}
},
"definitions": {
"rest.RoomCO2Status": {
"type": "object",
"properties": {
"co2": {
"type": "integer"
},
"is_high": {
"type": "boolean"
},
"room": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"BasicAuth": {
"type": "basic"
}
}
}

201
db/src/docs/swagger.yaml Normal file
View File

@@ -0,0 +1,201 @@
basePath: /api/v1
definitions:
rest.RoomCO2Status:
properties:
co2:
type: integer
is_high:
type: boolean
room:
type: string
type: object
host: api.db.e.kb28.ch
info:
contact: {}
description: This is a gateway API for IoT data.
title: Gateway API
version: "1.0"
paths:
/battery:
get:
description: Get the last battery level for each node grouped by room
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
additionalProperties:
additionalProperties: true
type: object
type: object
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
security:
- BasicAuth: []
summary: Get last battery level for each node
tags:
- battery
/export/csv:
get:
description: Export CO2, temperature, humidity, window status and battery data
for a node over a time range
parameters:
- description: Node MAC address (e.g. E8:F3:0A:F7:3B:F3)
in: query
name: node
required: true
type: string
- description: Start time in RFC3339 format (e.g. 2026-05-27T13:00:00Z)
in: query
name: from
required: true
type: string
- description: End time in RFC3339 format (e.g. 2026-05-27T15:00:00Z)
in: query
name: to
required: true
type: string
produces:
- text/csv
responses:
"200":
description: CSV file
schema:
type: string
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
security:
- BasicAuth: []
summary: Export sensor data as CSV
tags:
- export
/rooms:
get:
description: Get a list of all unique rooms from the measurement
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
security:
- BasicAuth: []
summary: Get all unique rooms
tags:
- rooms
/rooms/{room-id}/current:
get:
description: Get the latest record for a specific room
parameters:
- description: Room ID
in: path
name: room-id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"404":
description: Not Found
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
security:
- BasicAuth: []
summary: Get current data for a room
tags:
- rooms
/rooms/{room-id}/history:
get:
description: Get history for a specific room
parameters:
- description: Room ID
in: path
name: room-id
required: true
type: string
- default: 1 day
description: Time window (e.g., 1 day, 1 hour, 30 min)
in: query
name: window
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
additionalProperties: true
type: object
type: array
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
security:
- BasicAuth: []
summary: Get history for a room
tags:
- rooms
/rooms/high-co2:
get:
description: Get a list of rooms where CO2 levels are above the threshold
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/rest.RoomCO2Status'
type: array
security:
- BasicAuth: []
summary: Get rooms with high CO2 levels
tags:
- rooms
securityDefinitions:
BasicAuth:
type: basic
swagger: "2.0"

71
db/src/go.mod Normal file
View File

@@ -0,0 +1,71 @@
module gateway
go 1.25.0
require (
github.com/InfluxCommunity/influxdb3-go/v2 v2.13.0
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.12.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.1
github.com/swaggo/swag v1.16.4
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/apache/arrow-go/v18 v18.5.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/influxdata/line-protocol/v2 v2.2.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

258
db/src/go.sum Normal file
View File

@@ -0,0 +1,258 @@
github.com/InfluxCommunity/influxdb3-go/v2 v2.13.0 h1:IQVpiJ0t92OsJXf/RJ0+HHoIbK3mgJaMntEfLRgxS9Q=
github.com/InfluxCommunity/influxdb3-go/v2 v2.13.0/go.mod h1:fXhSEgDgX7iv++4t5cFVKRB6kqqeLjRRf5d2IlUdiWw=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apache/arrow-go/v18 v18.5.1 h1:yaQ6zxMGgf9YCYw4/oaeOU3AULySDlAYDOcnr4LdHdI=
github.com/apache/arrow-go/v18 v18.5.1/go.mod h1:OCCJsmdq8AsRm8FkBSSmYTwL/s4zHW9CqxeBxEytkNE=
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98=
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig=
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo=
github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY=
github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY=
github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE=
github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo=
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

96
db/src/influx/influx.go Normal file
View File

@@ -0,0 +1,96 @@
// Package influx provides an abstraction to the client influx.
package influx
import (
"context"
datapoint "gateway/point"
"github.com/InfluxCommunity/influxdb3-go/v2/influxdb3"
"github.com/InfluxCommunity/influxdb3-go/v2/influxdb3/batching"
)
const BatchSize = 50 // Number of points to batch before pushing to InfluxDB
const BatchCapacity = 50 // Capacity maximum of the buffer
// Gateway provides the abstraction of all gateways to send data points
type Gateway interface {
AddDatapoint(dp datapoint.DataPointInfo) error
Close() error
Flush() error
Query(ctx context.Context, query string) (*influxdb3.QueryIterator, error)
}
// An InfluxGateway is the abstracted influx gateway.
// It provides the influx parameters to initialize the connection
// and the batcher to batch the points before pushing to Influx.
type InfluxGateway struct {
client *influxdb3.Client
batcher *batching.Batcher
}
// NewInfluxGateway creates a new InfluxGateway with the given parameters.
func NewInfluxGateway(url string, token string, database string) (*InfluxGateway, error) {
client, err := influxdb3.New(influxdb3.ClientConfig{
Host: url,
Token: token,
Database: database,
})
if err != nil {
return nil, err
}
return &InfluxGateway{
client: client,
batcher: batching.NewBatcher(
batching.WithSize(BatchSize),
batching.WithInitialCapacity(BatchCapacity),
),
}, nil
}
// AddDatapoint is used to add a datapoint in the batcher. It uses the
// DataPointInfo interface for abstracting the generic type of the DataPoint.
// It pushes the batch of point when the number of points >= batch size.
func (g *InfluxGateway) AddDatapoint(dp datapoint.DataPointInfo) error {
tagsList := map[string]string{}
for _, t := range dp.Tags() {
tagsList[t.Subject] = t.Content
}
g.batcher.Add(
influxdb3.NewPoint(
dp.MeasurementName(),
tagsList,
dp.PayloadAsAny(),
dp.Timestamp(),
),
)
// if not ready, we are done
if g.batcher.Ready() {
return nil
}
// If ready, flush and return the result directly
return g.Flush()
}
// Flush sends the current batch of points to InfluxDB.
func (g *InfluxGateway) Flush() error {
// Send batch to influx DB
err := g.client.WritePoints(context.Background(), g.batcher.Emit())
return err
}
// Close closes the InfluxGateway client.
func (g *InfluxGateway) Close() error {
return g.client.Close()
}
// Query executes a SQL query against InfluxDB using the Arrow Flight (gRPC) API.
func (g *InfluxGateway) Query(ctx context.Context, query string) (*influxdb3.QueryIterator, error) {
return g.client.Query(ctx, query)
}

195
db/src/main.go Normal file
View File

@@ -0,0 +1,195 @@
package main
import (
"fmt"
"gateway/influx"
"gateway/mqtt"
point "gateway/point"
"gateway/rest"
"log"
"os"
"slices"
"strings"
"time"
)
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
type ProvenceData struct {
CO2PPM int `json:"co2_ppm"`
Temp float64 `json:"temp"`
Humidity int `json:"humidity"`
Battery int `json:"battery"`
Window bool `json:"window_open"`
}
func mqttConnection() *mqtt.MqttGateway {
BrokerUrl := getEnv("MQTT_BROKER_URL", "tls://localhost:8883")
Username := getEnv("MQTT_USERNAME", "user")
Password := getEnv("MQTT_PASSWORD", "password")
ClientId := "mqtt-gateway-test-client_" + fmt.Sprint(time.Now().Unix())
// Create config & gateway
mqttP := &mqtt.MqttParams{
Broker: BrokerUrl,
ClientId: ClientId,
Qos: 1,
Username: Username,
Password: Password,
TlsConfig: nil,
OnConnect: nil,
OnConnectionLost: nil,
Timeout: 1 * time.Second,
}
gateway, err := mqtt.NewMqttGateway(*mqttP)
if err != nil {
log.Fatal(err)
}
return gateway
}
func influxConnection() *influx.InfluxGateway {
influxUrl := getEnv("INFLUX_URL", "http://influxdb:8181")
influxDatabase := getEnv("INFLUX_DATABASE", "provence")
influxToken := getEnv("INFLUX_TOKEN", "")
if influxToken == "" {
if tokenFile := getEnv("INFLUX_TOKEN_FILE", "/run/secrets/admin-token"); tokenFile != "" {
content, err := os.ReadFile(tokenFile)
if err == nil {
influxToken = strings.TrimSpace(string(content))
} else {
log.Printf("[Main] Warning: could not read token file %s: %v\n", tokenFile, err)
}
}
}
if influxToken == "" {
influxToken = "password"
}
log.Printf("[Main] InfluxDB config: URL=%s, DB=%s\n", influxUrl, influxDatabase)
// Create the gateway
gateway, err := influx.NewInfluxGateway(influxUrl, influxToken, influxDatabase)
if err != nil {
log.Fatalf("Creating gateway failed !, %v", err)
}
return gateway
}
// @title Gateway API
// @version 1.0
// @description This is a gateway API for IoT data.
// @host api.db.e.kb28.ch
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
func main() {
noMqtt := slices.Contains(os.Args[1:], "--no-mqtt")
// Load mapping configuration (reloaded dynamically on each access)
mappingPath := getEnv("MAPPING_CONFIG_PATH", "mapping.json")
mapping := NewDynamicMapping(mappingPath)
influxGateway := influxConnection()
defer influxGateway.Close()
measurementName := getEnv("CAMPUS", "provence")
if !noMqtt {
mqttGateway := mqttConnection()
defer mqttGateway.Disconnect()
// Create measurement for provence topic
provenceMeasurement := point.CreateMeasurement[ProvenceData](measurementName)
// The incoming MQTT topic structure is: <gateway_id>/<node_id>/update
topicStructur := []string{"gateway", "node"}
err := mqtt.SubscribeTyped(mqttGateway, "+/+/update", provenceMeasurement, topicStructur, func(dp point.DataPoint[ProvenceData]) {
var gatewayID, nodeID string
for _, tag := range dp.Tags() {
switch tag.Subject {
case "gateway":
gatewayID = tag.Content
case "node":
nodeID = tag.Content
}
}
campus, campusOk := mapping.GetCampus(gatewayID)
room, roomOk := mapping.GetRoom(nodeID)
batteryLevel := dp.GetValues().Battery
log.Printf("[Main] Received gateway=%s node=%s -> campus=%s room=%s (Battery %d%%)\n", gatewayID, nodeID, campus, room, batteryLevel)
var influxTags []point.Topic
if !campusOk {
log.Printf("[Main] No mapping found for gateway=%s\n", gatewayID)
influxTags = []point.Topic{
{Subject: "node", Content: nodeID},
}
} else if !roomOk {
log.Printf("[Main] No mapping found for gateway=%s\n", gatewayID)
influxTags = []point.Topic{
{Subject: "campus", Content: campus},
{Subject: "node", Content: nodeID},
}
} else {
influxTags = []point.Topic{
{Subject: "campus", Content: campus},
{Subject: "room", Content: room},
{Subject: "node", Content: nodeID},
}
}
// If CO2PPM value is present and over 1,000,000,000 delete the field, it's calibration value
values := dp.GetValues()
if values.CO2PPM > 1000000000 {
log.Printf("[Main] Warning: CO2PPM value %d is calibrating, setting to 0\n", values.CO2PPM)
values.CO2PPM = 0
}
// Still too high value, something wrong, dropping datapoint
if values.CO2PPM > 10000 {
log.Printf("[Main] Error: CO2PPM value %d is over threshold, dropping Datapoint\n", values.CO2PPM)
return
}
translatedDp := provenceMeasurement.CreateDataPoint(influxTags, values, dp.Timestamp())
if err := influxGateway.AddDatapoint(&translatedDp); err != nil {
log.Printf("[Main] Error adding datapoint to influx: %v\n", err)
}
if err := influxGateway.Flush(); err != nil {
log.Printf("[Main] Error flushing to influx: %v\n", err)
}
})
if err != nil {
log.Fatal(err)
}
} else {
log.Println("[Main] MQTT disabled (--no-mqtt flag set)")
}
// Initialize and start REST Gateway
restUsername := getEnv("REST_USERNAME", "user")
restPassword := getEnv("REST_PASSWORD", "password")
restGateway := rest.NewRestGateway(influxGateway, mapping, measurementName, restUsername, restPassword)
port := getEnv("REST_PORT", "8080")
log.Printf("[Main] Starting REST Gateway on port %s\n", port)
if err := restGateway.Run(":" + port); err != nil {
log.Fatalf("[Main] Failed to start REST Gateway: %v", err)
}
select {}
}

152
db/src/mapping.go Normal file
View File

@@ -0,0 +1,152 @@
package main
import (
"encoding/json"
"fmt"
"os"
)
// mappingFile is the structure of the JSON config file.
// Campus names map to the list of gateway IDs that belong to them.
// Room names map to the list of node IDs that belong to them.
type mappingFile struct {
Campus map[string][]string `json:"campus"`
Room map[string][]string `json:"room"`
}
// MappingConfig holds the reverse lookup maps built from the config file:
//
// gateway_id -> campus name
// node_id -> room name
type MappingConfig struct {
gatewayToCampus map[string]string
nodeToRoom map[string]string
}
// DynamicMapping reloads the mapping file on every access so that changes
// to the JSON file take effect without restarting the programme.
type DynamicMapping struct {
path string
}
// NewDynamicMapping creates a DynamicMapping that reads from the given path.
func NewDynamicMapping(path string) *DynamicMapping {
return &DynamicMapping{path: path}
}
func (d *DynamicMapping) load() *MappingConfig {
cfg, err := LoadMapping(d.path)
if err != nil {
return EmptyMapping()
}
return cfg
}
func (d *DynamicMapping) GetCampus(gatewayID string) (string, bool) {
return d.load().GetCampus(gatewayID)
}
func (d *DynamicMapping) GetRoom(nodeID string) (string, bool) {
return d.load().GetRoom(nodeID)
}
func (d *DynamicMapping) NodesForRoom(room string) []string {
return d.load().NodesForRoom(room)
}
func (d *DynamicMapping) AllNodes() []string {
return d.load().AllNodes()
}
func (d *DynamicMapping) Rooms() []string {
return d.load().Rooms()
}
// EmptyMapping returns a MappingConfig with no entries.
// GetCampus and GetRoom will return their "unknown_*" fallback values.
func EmptyMapping() *MappingConfig {
return &MappingConfig{
gatewayToCampus: make(map[string]string),
nodeToRoom: make(map[string]string),
}
}
// LoadMapping reads the mapping configuration from a JSON file and builds
// the internal reverse-lookup tables.
func LoadMapping(path string) (*MappingConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read mapping file %q: %w", path, err)
}
var raw mappingFile
if err := json.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("failed to parse mapping JSON: %w", err)
}
cfg := &MappingConfig{
gatewayToCampus: make(map[string]string),
nodeToRoom: make(map[string]string),
}
for campus, gateways := range raw.Campus {
for _, gwID := range gateways {
cfg.gatewayToCampus[gwID] = campus
}
}
for room, nodes := range raw.Room {
for _, nodeID := range nodes {
cfg.nodeToRoom[nodeID] = room
}
}
return cfg, nil
}
// GetCampus returns the campus name for a given gateway ID.
// The boolean is false if the gateway ID has no mapping.
func (c *MappingConfig) GetCampus(gatewayID string) (string, bool) {
campus, ok := c.gatewayToCampus[gatewayID]
return campus, ok
}
// GetRoom returns the room name for a given node ID.
// The boolean is false if the node ID has no mapping.
func (c *MappingConfig) GetRoom(nodeID string) (string, bool) {
room, ok := c.nodeToRoom[nodeID]
return room, ok
}
// NodesForRoom returns the list of node IDs that belong to the given room.
func (c *MappingConfig) NodesForRoom(room string) []string {
var nodes []string
for nodeID, r := range c.nodeToRoom {
if r == room {
nodes = append(nodes, nodeID)
}
}
return nodes
}
// AllNodes returns all node IDs defined in the mapping.
func (c *MappingConfig) AllNodes() []string {
nodes := make([]string, 0, len(c.nodeToRoom))
for nodeID := range c.nodeToRoom {
nodes = append(nodes, nodeID)
}
return nodes
}
// Rooms returns the list of all room names defined in the mapping.
func (c *MappingConfig) Rooms() []string {
seen := make(map[string]struct{})
for _, room := range c.nodeToRoom {
seen[room] = struct{}{}
}
rooms := make([]string, 0, len(seen))
for room := range seen {
rooms = append(rooms, room)
}
return rooms
}

248
db/src/mqtt/mqtt.go Normal file
View File

@@ -0,0 +1,248 @@
// Package mqtt_gateway provides an abstraction to an MQTT broker client.
package mqtt
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
dp "gateway/point"
"log"
"strings"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
const (
maxQoS = 2
defaultTimeout = 5 * time.Second
)
// A MqttParams is the abstracted MQTT gateway.
// It provides the MQTT parameters to initialize the connection and the method to add data.
type MqttParams struct {
Broker string
ClientId string
Qos byte
Username string
Password string
TlsConfig *tls.Config
OnConnect mqtt.OnConnectHandler
OnConnectionLost mqtt.ConnectionLostHandler
Timeout time.Duration
}
// MqttGateway is the abstracted MQTT gateway.
// It connects to the MQTT broker and provides the method to send data to the broker.
type MqttGateway struct {
MqttParams MqttParams
Client mqtt.Client
}
// mqttPayload is the JSON structure published to the broker in a specific topic.
// It contains the values and the timestamp of the data.
type mqttPayload map[string]any
// connectHandler is called when the client connects to the broker. It prints a message to the console.
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
log.Println("[MQTT Gateway] Connected to MQTT Broker")
}
// connectLostHandler is called when the client loses connection to the broker. It prints a message to the console with the error.
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
log.Printf("[MQTT Gateway] Connection lost: %v\n", err)
}
func getTopic(t []dp.Topic) string {
var topic []string
for _, t := range t {
topic = append(topic, t.Content)
}
return strings.Join(topic, "/")
}
// NewMqttGateway creates a new MqttGateway with the given parameters.
// And establishes the connection
func NewMqttGateway(p MqttParams) (*MqttGateway, error) {
// Verify input variable
if p.Broker == "" {
return nil, errors.New("[MQTT Gateway] Invalid broker address")
}
if p.ClientId == "" {
return nil, errors.New("[MQTT Gateway] Invalid client id")
}
if p.Qos > maxQoS {
return nil, errors.New("[MQTT Gateway] Invalid QoS level")
}
if p.Timeout == 0 {
// Set to default value
p.Timeout = defaultTimeout
}
opts := mqtt.NewClientOptions()
opts.AddBroker(p.Broker)
opts.SetClientID(p.ClientId)
if p.TlsConfig != nil {
opts.SetTLSConfig(p.TlsConfig)
}
if p.OnConnect != nil {
opts.SetOnConnectHandler(p.OnConnect)
} else {
opts.SetOnConnectHandler(connectHandler)
}
if p.OnConnectionLost != nil {
opts.SetConnectionLostHandler(p.OnConnectionLost)
} else {
opts.SetConnectionLostHandler(connectLostHandler)
}
if p.Username != "" {
opts.SetUsername(p.Username)
opts.SetPassword(p.Password)
}
client := mqtt.NewClient(opts)
token := client.Connect()
if !token.WaitTimeout(p.Timeout) {
return nil, fmt.Errorf("[MQTT Gateway] Mqtt connect timed out")
}
if err := token.Error(); err != nil {
return nil, fmt.Errorf("[MQTT Gateway] Mqtt connect failed: %w", err)
}
return &MqttGateway{
MqttParams: p,
Client: client,
}, nil
}
// SendData is used to send data in the MQTT gateway.
// It uses the DataPointInfo interface for abstracting the generic type of the DataPoint
func (g *MqttGateway) SendData(msg dp.DataPointInfo) error {
topic := getTopic(msg.Tags())
if topic == "" {
return errors.New("[MQTT Gateway] Invalid topic")
}
payload := mqttPayload{
"timestamp": msg.Timestamp().Unix(),
}
for key, value := range msg.PayloadAsAny() {
payload[key] = value
}
payloadJson, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("[MQTT Gateway] Failed to marshal payload: %w", err)
}
token := g.Client.Publish(topic, g.MqttParams.Qos, false, payloadJson)
if !token.WaitTimeout(g.MqttParams.Timeout) {
return fmt.Errorf("[MQTT Gateway] Mqtt connect timed out")
}
if token.Error() != nil {
return fmt.Errorf("[MQTT Gateway] Failed to publish message: %w", token.Error())
}
return nil
}
// Disconnect is used to disconnect the MQTT gateway from the broker.
// It prints a message to the console when the disconnection is successful.
func (g *MqttGateway) Disconnect() {
g.Client.Disconnect(0)
log.Println("[MQTT Gateway] Disconnected from MQTT Broker")
}
// Subscribe is used to subscribe to a topic in the MQTT gateway.
// It takes a topic and a callback function as parameters.
// The callback function is called when a message is received on the subscribed topic.
func (g *MqttGateway) Subscribe(topic string, callback mqtt.MessageHandler) error {
token := g.Client.Subscribe(topic, g.MqttParams.Qos, callback)
if !token.WaitTimeout(g.MqttParams.Timeout) {
return fmt.Errorf("[MQTT Gateway] MQTT gateway timed out")
}
if token.Error() != nil {
return fmt.Errorf("[MQTT Gateway] MQTT gateway failed to subscribe: %w", token.Error())
}
log.Printf("[MQTT Gateway] Subscribed to topic: %s\n", topic)
return nil
}
// Unsubscribe is used to unsubscribe from a topic in the MQTT gateway.
func (g *MqttGateway) Unsubscribe(topic string) error {
token := g.Client.Unsubscribe(topic)
if !token.WaitTimeout(g.MqttParams.Timeout) {
return fmt.Errorf("[MQTT Gateway] MQTT gateway timed out")
}
if token.Error() != nil {
return fmt.Errorf("[MQTT Gateway] MQTT gateway failed to unsubscribe: %w", token.Error())
}
log.Printf("[MQTT Gateway] Unsubscribed from topic: %s\n", topic)
return nil
}
// SubscribeTyped is a helper to subscribe to a topic and automatically convert
// the received JSON message to a DataPoint of type T.
// T should be a struct or a map that matches the JSON payload (excluding timestamp).
// tagSubjects is a list of tag subjects that correspond to the parts of the topic.
// For example, if the topic is "provence/B3/update" and tagSubjects is ["city", "room"],
// it will create tags {Subject: "city", Content: "provence"} and {Subject: "room", Content: "B3"}.
func SubscribeTyped[T any](g *MqttGateway, topic string, m dp.Measurement[T], tagSubjects []string, handler func(dp.DataPoint[T])) error {
return g.Subscribe(topic, func(client mqtt.Client, msg mqtt.Message) {
// Unmarshal into T for fields
var fields T
if err := json.Unmarshal(msg.Payload(), &fields); err != nil {
log.Printf("[MQTT Gateway] Error unmarshaling fields: %v", err)
return
}
// Unmarshal into a map to extract timestamp
var raw map[string]any
if err := json.Unmarshal(msg.Payload(), &raw); err != nil {
log.Printf("[MQTT Gateway] Error unmarshaling raw: %v", err)
return
}
ts := time.Now()
if tsRaw, ok := raw["timestamp"].(string); ok {
// Try RFC3339 first (default)
if parsedTs, err := time.Parse(time.RFC3339, tsRaw); err == nil {
ts = parsedTs
} else {
log.Printf("[MQTT Gateway] Failed to parse timestamp '%s' as RFC3339: %v", tsRaw, err)
}
} else if tsRaw, ok := raw["timestamp"].(float64); ok {
// Handle Unix timestamp in seconds
ts = time.Unix(int64(tsRaw), 0)
}
// Extract tags from topic
parts := strings.Split(msg.Topic(), "/")
var tags []dp.Topic
for i, subject := range tagSubjects {
if i < len(parts) && subject != "" {
tags = append(tags, dp.Topic{Subject: subject, Content: parts[i]})
}
}
// Fallback for backward compatibility if no tagSubjects provided
if len(tagSubjects) == 0 && len(parts) > 1 {
tags = append(tags, dp.Topic{Subject: "id", Content: parts[1]})
}
dp := m.CreateDataPoint(tags, fields, ts)
handler(dp)
})
}

82
db/src/point/datapoint.go Normal file
View File

@@ -0,0 +1,82 @@
// Package datapoint implements measurement and datapoint
// for database gateways.
package datapoint
import (
"encoding/json"
"time"
)
type Topic struct {
Subject string
Content string
}
// DataPointInfo provides an interface for accessing
// all fields of a DataPoint.
type DataPointInfo interface {
MeasurementName() string
Tags() []Topic
PayloadAsAny() map[string]any
Timestamp() time.Time
}
// A Measurement represents a type of measurement such as
// temperature, humidity, ...
type Measurement[T any] struct {
name string
}
// A DataPoint represents values associated with a measurement, along with tags,
// values, and a timestamp. It contains a pointer to its parent Measurement
type DataPoint[T any] struct {
measurement *Measurement[T]
tags []Topic
values T
timestamp time.Time
}
func CreateMeasurement[T any](name string) Measurement[T] {
return Measurement[T]{
name: name,
}
}
// CreateDataPoint forces the creation of DataPoint with the type according
// to the generic.
func (m *Measurement[T]) CreateDataPoint(tags []Topic, values T, timestamp time.Time) DataPoint[T] {
return DataPoint[T]{
measurement: m,
tags: tags,
values: values,
timestamp: timestamp,
}
}
func (dp *DataPoint[T]) MeasurementName() string {
return dp.measurement.name
}
func (dp *DataPoint[T]) Tags() []Topic {
return dp.tags
}
func (dp *DataPoint[T]) GetValues() T {
return dp.values
}
// PayloadAsAny returns the DataPoint value with type any.
// (useful for adding the DataPoint for batching)
func (dp *DataPoint[T]) PayloadAsAny() map[string]any {
b, err := json.Marshal(dp.values)
if err != nil {
return nil
}
var res map[string]any
_ = json.Unmarshal(b, &res)
return res
}
func (dp *DataPoint[T]) Timestamp() time.Time {
return dp.timestamp
}

598
db/src/rest/rest.go Normal file
View File

@@ -0,0 +1,598 @@
package rest
import (
"context"
"encoding/csv"
"fmt"
_ "gateway/docs"
"gateway/influx"
"log"
"net/http"
"os"
"regexp"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/apache/arrow-go/v18/arrow"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// RoomMapper is an interface for mapping node IDs to rooms and listing rooms.
type RoomMapper interface {
Rooms() []string
NodesForRoom(room string) []string
AllNodes() []string
GetRoom(nodeID string) (string, bool)
}
type RestGateway struct {
influxGateway *influx.InfluxGateway
mapping RoomMapper
engine *gin.Engine
measurementName string
username string
password string
co2ThresholdMax int
co2ThresholdMin int
roomStatus map[string]*RoomCO2Status
statusMu sync.RWMutex
}
type RoomCO2Status struct {
RoomID string `json:"room"`
IsHigh bool `json:"is_high"`
CO2 int `json:"co2"`
}
func NewRestGateway(influxGateway *influx.InfluxGateway, mapping RoomMapper, measurementName string, username, password string) *RestGateway {
maxThreshold, _ := strconv.Atoi(os.Getenv("CO2_THREASHOLD_MAX"))
if maxThreshold == 0 {
maxThreshold = 1400
}
minThreshold, _ := strconv.Atoi(os.Getenv("CO2_THREASHOLD_MIN"))
if minThreshold == 0 {
minThreshold = 1000
}
g := &RestGateway{
influxGateway: influxGateway,
mapping: mapping,
engine: gin.Default(),
measurementName: measurementName,
username: username,
password: password,
co2ThresholdMax: maxThreshold,
co2ThresholdMin: minThreshold,
roomStatus: make(map[string]*RoomCO2Status),
}
g.setupRoutes()
go g.runWatchdog()
return g
}
func (g *RestGateway) setupRoutes() {
// Setup CORS middleware to allow all *.e.kb28.ch origins
corsConfig := cors.Config{
AllowOriginFunc: func(origin string) bool {
// Match any origin like *.e.kb28.ch
pattern := regexp.MustCompile(`^https://.*\.e\.kb28\.ch$`)
return pattern.MatchString(origin)
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}
g.engine.Use(cors.New(corsConfig))
v1 := g.engine.Group("/api/v1")
// Public endpoints (no auth required)
v1.GET("/battery", g.getBattery)
if g.username != "" && g.password != "" {
v1.Use(gin.BasicAuth(gin.Accounts{
g.username: g.password,
}))
}
{
v1.GET("/rooms", g.getRooms)
v1.GET("/rooms/:room-id/current", g.getRoomCurrent)
v1.GET("/rooms/:room-id/history", g.getRoomHistory)
v1.GET("/rooms/high-co2", g.getHighCO2Rooms)
v1.GET("/export/csv", g.getExportCSV)
}
g.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
// buildNodeFilter builds a SQL WHERE clause fragment matching any of the given node IDs.
func buildNodeFilter(nodes []string) string {
quotedNodes := make([]string, len(nodes))
for i, n := range nodes {
quotedNodes[i] = fmt.Sprintf("'%s'", n)
}
return fmt.Sprintf(`"node" IN (%s)`, strings.Join(quotedNodes, ", "))
}
// GET /api/v1/battery
// getBattery godoc
// @Summary Get last battery level for each node
// @Description Get the last battery level for each node grouped by room
// @Tags battery
// @Produce json
// @Success 200 {object} map[string]map[string]map[string]any
// @Failure 500 {object} map[string]string
// @Security BasicAuth
// @Router /battery [get]
func (g *RestGateway) getBattery(c *gin.Context) {
allNodes := g.mapping.AllNodes()
if len(allNodes) == 0 {
c.JSON(http.StatusOK, gin.H{})
return
}
nodeFilter := buildNodeFilter(allNodes)
query := fmt.Sprintf(`
SELECT
"node",
"battery"
FROM "%s"
WHERE %s
AND time > now() - INTERVAL '30 minutes'
ORDER BY time DESC
`, g.measurementName, nodeFilter,
)
it, err := g.influxGateway.Query(context.Background(), query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Collect last battery per node (first occurrence = most recent due to DESC order)
nodeBattery := make(map[string]any)
for it.Next() {
row := it.Value()
nodeVal, ok := row["node"]
if !ok {
continue
}
nodeID, ok := nodeVal.(string)
if !ok {
continue
}
if _, seen := nodeBattery[nodeID]; !seen {
nodeBattery[nodeID] = row["battery"]
}
}
if err := it.Err(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Group by room
result := make(map[string]map[string]map[string]any)
for _, nodeID := range allNodes {
room, ok := g.mapping.GetRoom(nodeID)
if !ok {
room = "not-attributed"
}
if result[room] == nil {
result[room] = make(map[string]map[string]any)
}
battery, hasData := nodeBattery[nodeID]
if !hasData {
battery = "offline (30 min)"
}
result[room][nodeID] = map[string]any{"battery": battery}
}
c.JSON(http.StatusOK, result)
}
// This function is 100% AI generated with Junie (Claude Sonnet 4.6)
// GET /api/v1/export/csv
// getExportCSV godoc
// @Summary Export sensor data as CSV
// @Description Export CO2, temperature, humidity, window status and battery data for a node over a time range
// @Tags export
// @Produce text/csv
// @Param node query string true "Node MAC address (e.g. E8:F3:0A:F7:3B:F3)"
// @Param from query string true "Start time in RFC3339 format (e.g. 2026-05-27T13:00:00Z)"
// @Param to query string true "End time in RFC3339 format (e.g. 2026-05-27T15:00:00Z)"
// @Success 200 {string} string "CSV file"
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BasicAuth
// @Router /export/csv [get]
func (g *RestGateway) getExportCSV(c *gin.Context) {
node := c.Query("node")
from := c.Query("from")
to := c.Query("to")
if node == "" || from == "" || to == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "node, from and to query parameters are required"})
return
}
if _, err := time.Parse(time.RFC3339, from); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "from must be a valid RFC3339 timestamp"})
return
}
if _, err := time.Parse(time.RFC3339, to); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "to must be a valid RFC3339 timestamp"})
return
}
query := fmt.Sprintf(`
SELECT
time,
co2_ppm,
temp,
battery,
humidity,
window_open
FROM "%s"
WHERE time >= '%s'
AND time <= '%s'
AND "node" = '%s'
ORDER BY time ASC
`, g.measurementName, from, to, node,
)
it, err := g.influxGateway.Query(context.Background(), query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="export-%s.csv"`, strings.ReplaceAll(node, ":", "_")))
c.Header("Content-Type", "text/csv")
w := csv.NewWriter(c.Writer)
_ = w.Write([]string{"time", "co2", "temperature", "humidity", "windows", "battery"})
for it.Next() {
row := it.Value()
var tsStr string
if t, ok := row["time"]; ok {
var tt time.Time
switch v := t.(type) {
case time.Time:
tt = v
case int64:
tt = time.Unix(0, v)
}
loc := time.FixedZone("UTC+2", 2*60*60)
tsStr = tt.In(loc).Format("2006-01-02T15:04:05")
}
co2 := formatField(row["co2_ppm"])
temp := formatField(row["temp"])
humidity := formatField(row["humidity"])
windowOpen := formatField(row["window_open"])
battery := formatField(row["battery"])
_ = w.Write([]string{
tsStr,
co2,
temp,
humidity,
windowOpen,
battery,
})
}
if err := it.Err(); err != nil {
// Headers already sent; best effort
_ = w.Write([]string{"error", err.Error()})
}
w.Flush()
}
// formatField converts an any value to its string representation for CSV output.
func formatField(v any) string {
if v == nil {
return ""
}
switch val := v.(type) {
case float64:
return strconv.FormatFloat(val, 'f', -1, 64)
case float32:
return strconv.FormatFloat(float64(val), 'f', -1, 32)
case int64:
return strconv.FormatInt(val, 10)
case int32:
return strconv.FormatInt(int64(val), 10)
case bool:
if val {
return "1"
}
return "0"
default:
return fmt.Sprintf("%v", v)
}
}
// formatTime normalizes a time value (possibly a nanosecond int64) into an RFC3339 UTC string.
func formatTime(v any) any {
switch t := v.(type) {
case time.Time:
return t.UTC().Format(time.RFC3339)
case arrow.Timestamp:
return time.Unix(0, int64(t)).UTC().Format(time.RFC3339)
case int64:
return time.Unix(0, t).UTC().Format(time.RFC3339)
default:
return v
}
}
func (g *RestGateway) Run(addr string) error {
return g.engine.Run(addr)
}
// GET /api/v1/rooms
// getRooms godoc
// @Summary Get all unique rooms
// @Description Get a list of all unique rooms from the measurement
// @Tags rooms
// @Produce json
// @Success 200 {array} string
// @Failure 500 {object} map[string]string
// @Security BasicAuth
// @Router /rooms [get]
func (g *RestGateway) getRooms(c *gin.Context) {
rooms := g.mapping.Rooms()
slices.Sort(rooms)
c.JSON(http.StatusOK, rooms)
}
// GET /api/v1/rooms/{room-id}/current
// getRoomCurrent godoc
// @Summary Get current data for a room
// @Description Get the latest record for a specific room
// @Tags rooms
// @Produce json
// @Param room-id path string true "Room ID"
// @Success 200 {object} map[string]any
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BasicAuth
// @Router /rooms/{room-id}/current [get]
func (g *RestGateway) getRoomCurrent(c *gin.Context) {
roomID := c.Param("room-id")
nodes := g.mapping.NodesForRoom(roomID)
if len(nodes) == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Room not found in mapping"})
return
}
nodeFilter := buildNodeFilter(nodes)
// Get the last record for the specific room by matching node IDs, aggregated by 5m intervals
query := fmt.Sprintf(`
SELECT
date_bin(INTERVAL '5 minutes', time)::TIMESTAMP AS time,
ROUND(AVG(co2_ppm)) AS co2_ppm,
ROUND(AVG(temp), 2) AS temp,
ROUND(AVG(humidity), 2) AS humidity,
MAX(window_open) AS window_open
FROM "%s"
WHERE time > now() - INTERVAL '1 day'
AND %s
GROUP BY date_bin(INTERVAL '5 minutes', time)
ORDER BY time DESC
LIMIT 1
`, g.measurementName, nodeFilter,
)
// Using context.Background() as seen in working snippet
it, err := g.influxGateway.Query(context.Background(), query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if it.Next() {
val := it.Value()
val["room"] = roomID
val["window"] = val["window_open"]
delete(val, "window_open")
if t, ok := val["time"]; ok {
val["time"] = formatTime(t)
}
c.JSON(http.StatusOK, val)
return
}
if err := it.Err(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusNotFound, gin.H{"error": "Room not found or no data available"})
}
// GET /api/v1/rooms/{room-id}/history
// getRoomHistory godoc
// @Summary Get history for a room
// @Description Get history for a specific room
// @Tags rooms
// @Produce json
// @Param room-id path string true "Room ID"
// @Param window query string false "Time window (e.g., 1 day, 1 hour, 30 min)" default(1 day)
// @Success 200 {array} map[string]any
// @Failure 500 {object} map[string]string
// @Security BasicAuth
// @Router /rooms/{room-id}/history [get]
func (g *RestGateway) getRoomHistory(c *gin.Context) {
roomID := c.Param("room-id")
window := c.DefaultQuery("window", "1 day")
nodes := g.mapping.NodesForRoom(roomID)
if len(nodes) == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Room not found in mapping"})
return
}
nodeFilter := buildNodeFilter(nodes)
query := fmt.Sprintf(`
WITH binned AS (
SELECT
date_bin(INTERVAL '1 minute', time)::TIMESTAMP AS time,
AVG(co2_ppm) AS co2_ppm,
AVG(temp) AS temp,
AVG(humidity) AS humidity,
MAX(window_open) AS window_open
FROM "%s"
WHERE time > now() - INTERVAL '%s' - INTERVAL '5 minutes'
AND %s
GROUP BY date_bin(INTERVAL '1 minute', time)
)
SELECT
time,
ROUND(AVG(co2_ppm) OVER (ORDER BY time RANGE BETWEEN INTERVAL '4 minutes' PRECEDING AND CURRENT ROW)) AS co2_ppm,
ROUND(AVG(temp) OVER (ORDER BY time RANGE BETWEEN INTERVAL '4 minutes' PRECEDING AND CURRENT ROW), 2) AS temp,
ROUND(AVG(humidity) OVER (ORDER BY time RANGE BETWEEN INTERVAL '4 minutes' PRECEDING AND CURRENT ROW), 2) AS humidity,
window_open
FROM binned
WHERE time > now() - INTERVAL '%s'
ORDER BY time DESC
`, g.measurementName, window, nodeFilter, window,
)
// Using context.Background() as seen in working snippet
it, err := g.influxGateway.Query(context.Background(), query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var history []map[string]any
for it.Next() {
val := it.Value()
val["room"] = roomID
val["window"] = val["window_open"]
delete(val, "window_open")
if t, ok := val["time"]; ok {
val["time"] = formatTime(t)
}
history = append(history, val)
}
if err := it.Err(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, history)
}
func (g *RestGateway) runWatchdog() {
// Initial check
g.checkCO2()
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for range ticker.C {
g.checkCO2()
}
}
func (g *RestGateway) checkCO2() {
rooms := g.mapping.Rooms()
for _, roomID := range rooms {
nodes := g.mapping.NodesForRoom(roomID)
if len(nodes) == 0 {
continue
}
nodeFilter := buildNodeFilter(nodes)
// Get the last record for the specific room by matching node IDs, aggregated by 5m intervals
// Same logic as getRoomCurrent
query := fmt.Sprintf(`
SELECT
date_bin(INTERVAL '5 minutes', time)::TIMESTAMP AS time,
ROUND(AVG(co2_ppm)) AS co2_ppm
FROM "%s"
WHERE time > now() - INTERVAL '1 day'
AND %s
GROUP BY date_bin(INTERVAL '5 minutes', time)
ORDER BY time ASC
LIMIT 1
`, g.measurementName, nodeFilter,
)
it, err := g.influxGateway.Query(context.Background(), query)
if err != nil {
log.Printf("[Watchdog] Error querying CO2 for room %s: %v\n", roomID, err)
continue
}
if it.Next() {
val := it.Value()
if co2Val, ok := val["co2_ppm"]; ok {
co2 := int(co2Val.(float64))
g.statusMu.Lock()
status, ok := g.roomStatus[roomID]
if !ok {
status = &RoomCO2Status{RoomID: roomID}
g.roomStatus[roomID] = status
}
status.CO2 = co2
if co2 > g.co2ThresholdMax {
status.IsHigh = true
} else if co2 < g.co2ThresholdMin {
status.IsHigh = false
}
g.statusMu.Unlock()
}
}
if err := it.Err(); err != nil {
log.Printf("[Watchdog] Error in iterator for room %s: %v\n", roomID, err)
}
}
}
// GET /api/v1/rooms/high-co2
// getHighCO2Rooms godoc
// @Summary Get rooms with high CO2 levels
// @Description Get a list of rooms where CO2 levels are above the threshold
// @Tags rooms
// @Produce json
// @Success 200 {array} RoomCO2Status
// @Security BasicAuth
// @Router /rooms/high-co2 [get]
func (g *RestGateway) getHighCO2Rooms(c *gin.Context) {
g.statusMu.RLock()
defer g.statusMu.RUnlock()
var result = []RoomCO2Status{}
for _, status := range g.roomStatus {
if status.IsHigh {
result = append(result, *status)
}
}
// Sort by room ID for stability
slices.SortFunc(result, func(a, b RoomCO2Status) int {
return strings.Compare(a.RoomID, b.RoomID)
})
c.JSON(http.StatusOK, result)
}

View File

@@ -1,4 +1,9 @@
http:
middlewares:
pi-db-doc-redirect:
redirectRegex:
regex: "^https://doc.db.e.kb28.ch/$"
replacement: "https://doc.db.e.kb28.ch/swagger/index.html"
# middlewares:
# oidc-auth-pi-db:
# plugin:
@@ -10,15 +15,22 @@ http:
# ClientId: ""
# ClientSecret: ""
routers:
pi-db-ui:
rule: "Host(`ui.db.e.kb28.ch`)"
entryPoints:
- websecure
service: pi-db-ui
tls:
certResolver: letsencrypt
# middlewares:
# - oidc-auth-pi-db@file
pi-db:
rule: "Host(`ui.e.kb28.ch`)"
rule: "Host(`db.e.kb28.ch`)"
entryPoints:
- websecure
service: pi-db
tls:
certResolver: letsencrypt
# middlewares:
# - oidc-auth-pi-db@file
pi-mqtt-management:
rule: "Host(`mqtt.e.kb28.ch`)"
entryPoints:
@@ -26,18 +38,55 @@ http:
service: pi-mqtt-management
tls:
certResolver: letsencrypt
pi-db-api:
rule: "Host(`api.db.e.kb28.ch`)"
entryPoints:
- websecure
service: pi-db-api
tls:
certResolver: letsencrypt
pi-db-doc:
rule: "Host(`doc.db.e.kb28.ch`)"
entryPoints:
- websecure
service: pi-db-api
tls:
certResolver: letsencrypt
middlewares:
- pi-db-doc-redirect
pi-ui:
rule: "Host(`ui.e.kb28.ch`)"
entryPoints:
- websecure
service: pi-ui
tls:
certResolver: letsencrypt
services:
pi-db:
pi-db-ui:
loadBalancer:
servers:
- url: "http://192.168.42.211:8093"
passHostHeader: true
pi-db:
loadBalancer:
servers:
- url: "h2c://192.168.42.211:8181"
passHostHeader: true
pi-mqtt-management:
loadBalancer:
servers:
- url: "http://192.168.42.211:15672"
passHostHeader: true
pi-db-api:
loadBalancer:
servers:
- url: "http://192.168.42.211:8080"
passHostHeader: true
pi-ui:
loadBalancer:
servers:
- url: "http://192.168.42.211:80"
tcp:
routers:

View File

@@ -32,7 +32,6 @@ else
gw -> gw : add UTC timestamp
gw -> broker : publish JSON\n{gateway_id}/{mac}/update
broker --> gw : on_publish confirmed
broker -> db : store measurement
end
end
end

View File

@@ -15,6 +15,7 @@ class "Window_status" as win{}
class "Hygrometer" as hygro{}
class "Thermometer" as thermo{}
class "CO2_level" as co2{}
class "Battery_level" as batt{}
sup o-d- ble
sup o-u- sens
@@ -22,5 +23,6 @@ sens <|-l- win
sens <|-u- hygro
sens <|-u- thermo
sens <|-r- co2
sens <|-- batt
@enduml

View File

View File

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 26 16:19:28 2026
@author: alisonlecointre
"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import glob
import os
# %%=============================
# File loading
# ===============================
def load_file(file_name):
sheet= pd.read_excel(file_name, sheet_name=None) #loading file, sheat_name=None : return dictionary containing df for each sheet
for sheet_name, df in sheet.items(): #loading all sheets from the file
df = df.dropna(axis=1, how="all") #remove empty column "all" : all elements missing
df.columns = df.columns.str.strip() #remove leading character
sheet[sheet_name]=df
return sheet
def load_all_files(folder): #load all .xlsx files from the folder
files = glob.glob(f"{folder}/*.xlsx")
all_data={} #create dictionnary of all dictionnary of each file
for file in files:
name = os.path.basename(file)
all_data[name] = load_file(file)
return all_data
folder = os.getcwd()
data = load_all_files(folder)
# %%=============================
# Parameters Provence classrooms
# ===============================
df_Provence_rooms = pd.DataFrame({
"classrooms": ["A2", "A3","A4","A5","A6","A7"],
"volume": [308, 480, 326,274,323,272],
"n_student_max": [40, 78, 48,32,58,36]
})
print (df_Provence_rooms)
# %%=============================
# Calculation
# ===============================
# ===============================
# Air volume per person
# ===============================
def calculation_air_volum_per_pers (df) :
air_volume_per_pers = df["Mean room volume (m3)"]/ df["Mean number of students (-)"]
return air_volume_per_pers
calculation = {} #new dictionnary to store the air volum per person
for file_name, sheet in data.items() : #for each file including sheets from this file and all included in data
calculation [file_name] = {}
for sheet_name, df in sheet.items() : #df in all sheets
calculation[file_name][sheet_name] = calculation_air_volum_per_pers(df)
# ================================
# New data frame for simulation model of CO2 concentration increase over time
# ================================
def model_no_windows_opening (CO2_t0=None, N=None, V=None, df=None) :
CO2_prod_th = 0.0155/60 #m3/min <=> ajusted at 15.5 l/h after crossing data with simaria model [20 l/h CO2 production per person during normal activity (cf. article 16 822.113 Ordonnance 3 du 18 août 1993 relative à la loi sur le travail (OLT 3) (Protection de la santé))
t = np.arange(0, 181, 1)
if df is not None :
CO2_t0 = df["CO2 rate (ppm)"].iloc[0]
N = df["Mean number of students (-)"].iloc[0]
V = df["Mean room volume (m3)"].iloc[0]
CO2_t_model = CO2_t0 + ((N * CO2_prod_th * t *1e6) / V)
return t, CO2_t_model
model = {}
for file_name, sheet in data.items() : #for each file including sheets from this file and all included in data
model [file_name] = {}
for sheet_name, df in sheet.items() : #df in all sheets
t, CO2_t_model = model_no_windows_opening(df=df)
model [file_name][sheet_name] = {"t": t, "CO2_t_model": CO2_t_model}
# ================================
# Find threshold time
# ================================
threshold = 1400
def find_threshold_time(t, CO2_t_model, threshold):
indices = np.where(CO2_t_model >= threshold)[0]
if len(indices) == 0:
return None # never reached
return t[indices[0]]
threshold_time = {}
for file_name, sheet in model.items():
threshold_time[file_name] = {}
for sheet_name, values in sheet.items():
t = values["t"]
CO2_t_model = values["CO2_t_model"]
threshold_time[file_name][sheet_name] = find_threshold_time(t, CO2_t_model, threshold)
# ================================
# Air quality thresholds for graphs
# ================================
thresholds = {
"Good air quality":(0, 1400,"green"),
"Bad air quality": (1400, 2000,"orange"),
"Really bad air quality": (2000, np.inf,"red"),
}
def plot_air_quality_zones (ax, thresholds):
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
for label, (y_min, y_max, color) in thresholds.items():
if y_max != np.inf:
ax.axhline(y=y_max, linestyle="--", color=color)
y_top = min(y_max, ymax)
y_bottom = max(y_min, ymin)
y_mid = (y_bottom + y_top) / 2
ax.axhspan(y_bottom, y_top, color=color, alpha=0.2)
ax.text(
(xmin + xmax) / 2,
y_mid,
label,
ha="center",
va="center",
color="black",
bbox=dict(facecolor="white", alpha=0.5)
)
# %% =============================
# Visualisation
# ================================
# ================================
# Graph time evolution of CO2 concentration open data
# ================================
for file_name, sheet in data.items():
fig0, ax = plt.subplots(figsize=(11, 7))
for sheet_name, df in sheet.items():
t = model[file_name][sheet_name]["t"]
CO2_t_model = model[file_name][sheet_name]["CO2_t_model"]
t_threshold = threshold_time[file_name][sheet_name]
ax.plot(df["time (min)"], df["CO2 rate (ppm)"], label=f"{file_name} - {sheet_name}")
ax.plot(t,CO2_t_model, label = f"{file_name} - {sheet_name} simulated", linestyle="--")
# if t_threshold is not None :
# ax.axvline(x = t_threshold, label = "time reaching threshold", linestyle=":", color="red")
ax.legend(
loc="upper center",
bbox_to_anchor=(0.45, -0.08),
ncol=3 if len(ax.get_legend_handles_labels()[1]) > 1 else 1
)
fig0.tight_layout()
ax.set_ylim(400, 4000)
plot_air_quality_zones(ax, thresholds)
ax.grid(True, alpha=0.7)
ax.set_xlabel("time (min)")
ax.set_ylabel("CO2 concentration (ppm)")
ax.set_title("CO2 concentration over time")
# %%==========================================================================================
# User
# ==========================================================================================
print("\n")
volume_room = float(input("Total volume (m3): "))
n_student = float(input("Nomber of students : "))
initial_CO2_level = float(input("Initial CO2 level (ppm): "))
while initial_CO2_level < 400:
print("The value must be >= 400 ppm, which correspond to the normal CO2 concentration in air. Try again.")
initial_CO2_level = float(input("Initial CO2 level (ppm): "))
#Create CO2 concentration evolution over time using simulation model and give time reaching threshold
t_user, CO2_t_model_user = model_no_windows_opening(CO2_t0=initial_CO2_level, N=n_student, V=volume_room, df=None)
t_threshold = find_threshold_time(t_user, CO2_t_model_user, threshold)
print(f"Time reaching threshold of {threshold} ppm =", round(t_threshold),"minutes")
#Display CO2 concentration evolution over time
reponse = input("Display CO2 evolution over time? (yes/no) : ").strip().lower()
if reponse == "yes":
fig10, ax = plt.subplots(figsize=(10, 6))
ax.plot(t_user, CO2_t_model_user , label="CO2 concentration over time (simulated)")
if t_threshold is not None :
ax.axvline(x = t_threshold, label = "time reaching threshold", linestyle=":", color="red")
ax.legend(
loc="upper center",
bbox_to_anchor=(0.45, -0.07),
ncol=3 if len(ax.get_legend_handles_labels()[1]) > 1 else 1
)
fig10.tight_layout()
plot_air_quality_zones(ax, thresholds)
ax.set_title("Time evolution of CO2 concentration - Simulated")
ax.set_xlabel("Time (min)")
ax.set_ylabel("CO2 concentration (ppm)")
ax.set_ylim(500, 5000)
ax.grid(True)
else:
print("Answer only with yes or no")

View File

@@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 26 16:19:28 2026
@author: alisonlecointre
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.pyplot as ticker
import numpy as np
import glob
import os
# %%=============================
# File loading
# ===============================
def load_file(file_name):
sheet= pd.read_excel(file_name, sheet_name=None) #loading file, sheat_name=None : return dictionary containing df for each sheet
for sheet_name, df in sheet.items(): #loading all sheets from the file
df = df.dropna(axis=1, how="all") #remove empty column "all" : all elements missing
df.columns = df.columns.str.strip() #remove leading character
sheet[sheet_name]=df
return sheet
def load_all_files(folder): #load all .xlsx files from the folder
files = glob.glob(f"{folder}/*.xlsx")
all_data={} #create dictionnary of all dictionnary of each file
for file in files:
name = os.path.basename(file)
all_data[name] = load_file(file)
return all_data
folder = os.getcwd()
data = load_all_files(folder)
# %%=============================
# Parameters Provence classrooms
# ===============================
df_Provence_rooms = pd.DataFrame({
"classrooms": ["A2","A3","A4","A5","A6","A7"],
"volume": [308, 480, 326,274,323,272],
"n_student_max": [40, 78, 48,32,58,36],
"windows_surface": [3.23, 22.12, 3.23, 22.12, 3.23, 5.53]
})
print (df_Provence_rooms)
# %%=============================
# Calculation
# ===============================
# ===============================
# Air volume per person
# ===============================
def calculation_air_volum_per_pers (df) :
air_volume_per_pers = df["Mean room volume (m3)"]/ df["Mean number of students (-)"]
return air_volume_per_pers
calculation = {} #new dictionnary to store the air volum per person
for file_name, sheet in data.items() : #for each file including sheets from this file and all included in data
calculation [file_name] = {}
for sheet_name, df in sheet.items() : #df in all sheets
calculation[file_name][sheet_name] = calculation_air_volum_per_pers(df)
# ================================
# New data frame for simulation model of CO2 concentration increase over time
# ================================
CO2_prod_th = 0.0155/60 #m3/min <=> ajusted at 15.5 l/h after crossing data with simaria model [20 l/h CO2 production per person during normal activity (cf. article 16 822.113 Ordonnance 3 du 18 août 1993 relative à la loi sur le travail (OLT 3) (Protection de la santé))
def model_no_windows_opening (CO2_t0=None, N=None, V=None, df=None) :
t = np.arange(0, 181, 1)
if df is not None :
CO2_t0 = df["CO2 rate (ppm)"].iloc[0]
N = df["Mean number of students (-)"].iloc[0]
V = df["Mean room volume (m3)"].iloc[0]
CO2_t_model = CO2_t0 + ((N * CO2_prod_th * t *1e6) / V)
return t, CO2_t_model
model = {}
for file_name, sheet in data.items() : #for each file including sheets from this file and all included in data
model [file_name] = {}
for sheet_name, df in sheet.items() : #df in all sheets
t, CO2_t_model = model_no_windows_opening(df=df)
model [file_name][sheet_name] = {"t": t, "CO2_t_model": CO2_t_model}
# ================================
# Find threshold time
# ================================
threshold = 1400
def find_threshold_time(t, CO2_t_model, threshold):
indices = np.where(CO2_t_model >= threshold)[0]
if len(indices) == 0:
return None # never reached
return t[indices[0]]
threshold_time = {}
for file_name, sheet in model.items():
threshold_time[file_name] = {}
for sheet_name, values in sheet.items():
t = values["t"]
CO2_t_model = values["CO2_t_model"]
threshold_time[file_name][sheet_name] = find_threshold_time(t, CO2_t_model, threshold)
# ================================
# New data frame for simulation model of CO2 concentration decrease over time (using SIA 382/1 norm)
# ================================
v_air = 0.5 #m/s Assumption (cf. article)
def model_windows_opening_CO2 (S_windows=None, volume=None, df=None):
t = np.arange(0, 1, 0.01)
if df is not None:
for i, row in df.iterrows():
S_windows = df_Provence_rooms["windows_surface"].iloc[i]
volume = df_Provence_rooms["volume"].iloc[i]
q_air_change = S_windows * v_air * 3600 #m3/h
CO2_ext = 400 #ppm
CO2_int_0 = threshold #ppm
CO2_t_model_ventilation = ((CO2_int_0 - CO2_ext - ((0.001 * (CO2_prod_th * 1000*60))/q_air_change))* np.exp((-q_air_change * t)/volume) + CO2_ext + ((0.001 * (CO2_prod_th * 1000*60))/q_air_change) ) #ppm CO2_t_model_ventilation = ((CO2_int_0 - CO2_ext - ((0.001 * (CO2_prod_thx))/q_air_change))* np.exp((-q_air_change * t)/volume) + CO2_ext + ((0.001 * (CO2_prod_thx))/q_air_change) ) #ppm
t = t*60
return t, CO2_t_model_ventilation
model_with_windows = {}
for i, row in df.iterrows():
t, CO2_t_model_ventilation = model_windows_opening_CO2(df=row.to_frame().T)
model_with_windows[i] = pd.DataFrame({"CO2_t_model_ventilation" : CO2_t_model_ventilation, "t" : t})
# ================================
# Find window opening duration
# ================================
def find_windows_opening_time (t,CO2_t_model_ventilation):
windows_opening_duration = np.interp(405, CO2_t_model_ventilation[::-1] , t[::-1])
return windows_opening_duration
# ================================
# Air quality thresholds for graphs
# ================================
thresholds = {
"Good air quality":(0, 1400,"green"),
"Bad air quality": (1400, 2000,"orange"),
"Really bad air quality": (2000, np.inf,"red"),
}
def plot_air_quality_zones (ax, thresholds):
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
for label, (y_min, y_max, color) in thresholds.items():
if y_max != np.inf:
ax.axhline(y=y_max, linestyle="--", color=color)
y_top = min(y_max, ymax)
y_bottom = max(y_min, ymin)
y_mid = (y_bottom + y_top) / 2
ax.axhspan(y_bottom, y_top, color=color, alpha=0.2)
ax.text(
(xmin + xmax) / 2,
y_mid,
label,
ha="center",
va="center",
color="black",
bbox=dict(facecolor="white", alpha=0.5)
)
# %% =============================
# Visualisation
# ================================
# ================================
# Graph time evolution of CO2 concentration open data
# ================================
for file_name, sheet in data.items():
fig0, ax = plt.subplots(figsize=(10, 7))
for sheet_name, df in sheet.items():
t = model[file_name][sheet_name]["t"]
CO2_t_model = model[file_name][sheet_name]["CO2_t_model"]
t_threshold = threshold_time[file_name][sheet_name]
ax.plot(df["time (min)"], df["CO2 rate (ppm)"], label=f"{file_name} - {sheet_name}")
ax.plot(t,CO2_t_model, label = f"{file_name} - {sheet_name} simulated", linestyle="--")
if t_threshold is not None :
ax.axvline(x = t_threshold, label = "time reaching threshold", linestyle=":", color="red")
ax.legend(
loc="upper center",
bbox_to_anchor=(0.45, -0.08),
ncol=3 if len(ax.get_legend_handles_labels()[1]) > 1 else 1
)
fig0.tight_layout()
ax.set_ylim(0, 5000)
plot_air_quality_zones (ax, thresholds)
ax.grid(True, alpha=0.7)
ax.set_xlabel("time (min)")
ax.set_ylabel("CO2 concentration (ppm)")
ax.set_title("CO2 concentration over time")
# ================================
# Graph time evolution of CO2 concentration after window opening
# ================================
fig1, ax= plt.subplots(figsize=(10, 6))
for i in model_with_windows:
t = model_with_windows [i]["t"]
CO2_t_model_ventilation = model_with_windows[i]["CO2_t_model_ventilation"]
ax.plot(t, CO2_t_model_ventilation, label=df_Provence_rooms.loc[i, "classrooms"])
ax.legend()
ax.xaxis.set_major_locator(ticker.MultipleLocator(2))
ax.set_xlim(0, 20)
ax.grid(True, alpha=0.7)
ax.set_xlabel("time (min)")
ax.set_ylabel("CO2 concentration (ppm)")
ax.set_title("CO2 concentration over time after window opening - with open data")
# %%==========================================================================================
# User
# ==========================================================================================
print("\n")
name_classroom = input("Classroom number (A2 to A7) : ")
volume_room = df_Provence_rooms.loc[df_Provence_rooms["classrooms"] == name_classroom, "volume"].values
S_windows_room = df_Provence_rooms.loc[df_Provence_rooms["classrooms"] == name_classroom, "windows_surface"].values
n_student = float(input("Number of students : "))
initial_CO2_level = float(input("Initial CO2 level (ppm): "))
while initial_CO2_level < 400:
print("The value must be >= 400 ppm, which correspond to the normal CO2 concentration in air. Try again.")
initial_CO2_level = float(input("Initial CO2 level (ppm): "))
#Create CO2 concentration evolution over time using simulation model and give time reaching threshold
t_user, CO2_t_model_user = model_no_windows_opening(CO2_t0=initial_CO2_level, N=n_student, V=volume_room, df=None)
t_threshold = find_threshold_time(t_user, CO2_t_model_user, threshold)
t_user2, CO2_t_model_ventilation_user = model_windows_opening_CO2(S_windows =S_windows_room, volume = volume_room, df=None)
t_user3 = t_user2 + t_threshold #start at the time reaching threshold
windows_opening_duration = find_windows_opening_time (t_user2, CO2_t_model_ventilation_user)
print(f"Time reaching threshold of {threshold} ppm =", round(t_threshold),"minutes")
print(f"Open windows for {windows_opening_duration:.0f} min after reaching threshold")
#Display CO2 concentration evolution over time
reponse = input("Display CO2 evolution over time? (yes/no) : ").strip().lower()
while reponse not in ["yes", "no"]:
print("Answer with yes or no")
reponse = input("Display CO2 evolution over time? (yes/no) : ").strip().lower()
if reponse == "yes":
fig10, ax = plt.subplots(figsize=(10, 6))
ax.plot(t_user, CO2_t_model_user , label="CO2 concentration over time (simulated)")
ax.plot(t_user3, CO2_t_model_ventilation_user, label="with opened windows", color="brown")
if t_threshold is not None :
ax.axvline(x = t_threshold, label = "time reaching threshold", linestyle=":", color="red")
ax.legend(
loc="upper center",
bbox_to_anchor=(0.45, -0.07),
ncol=3 if len(ax.get_legend_handles_labels()[1]) > 1 else 1
)
fig10.tight_layout()
plot_air_quality_zones(ax, thresholds)
ax.set_title("Time evolution of CO2 concentration - Simulated")
ax.set_xlabel("Time (min)")
ax.set_ylabel("CO2 concentration (ppm)")
ax.set_xlim(0, 150)
ax.set_ylim(400, 5000)
ax.grid(True)

4
model/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
pandas
matplotlib
numpy
openpyxl

View File

8
nodes/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(plein_de_eeeeeeeeeeee)
target_sources(app PRIVATE src/main.c src/window_status.c src/window_status.h src/thermometer.c src/thermometer.h src/hygrometer.c src/hygrometer.h src/co2_level.c src/co2_level.h src/supervisor.c src/supervisor.h src/ble_advertiser.c src/ble_advertiser.h src/battery_percent.h src/battery_percent.c)
target_sources(app PRIVATE src/modules/battery.h src/modules/battery.c)

11
nodes/app.overlay Normal file
View File

@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: Apache-2.0 */
/ {
buttons{
compatible = "gpio-keys";
window_switch: window_switch {
gpios = <&sx1509b 0 GPIO_ACTIVE_LOW>;
label = "Window Switch";
};
};
};

16
nodes/prj.conf Normal file
View File

@@ -0,0 +1,16 @@
CONFIG_ADC=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_BT=y
CONFIG_GPIO=y
CONFIG_LOG=n
CONFIG_CONSOLE=n
CONFIG_SERIAL=n
CONFIG_PRINTK=n
CONFIG_UART_CONSOLE=n
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y
CONFIG_CCS811_DRIVE_MODE_3=y

View File

@@ -0,0 +1,42 @@
#include "battery_percent.h"
/**
* Taken from sample/board/nordic/battery/src/main.c
* A discharge curve specific to the power source.
*/
static const struct battery_level_point levels[] = {
/* "Curve" here eyeballed from captured data for the [Adafruit
* 3.7v 2000 mAh](https://www.adafruit.com/product/2011) LIPO
* under full load that started with a charge of 3.96 V and
* dropped about linearly to 3.58 V over 15 hours. It then
* dropped rapidly to 3.10 V over one hour, at which point it
* stopped transmitting.
*
* Based on eyeball comparisons we'll say that 15/16 of life
* goes between 3.95 and 3.55 V, and 1/16 goes between 3.55 V
* and 3.1 V.
*/
{ 10000, 3950 },
{ 625, 3550 },
{ 0, 3100 },
};
enum error_code battery_init(){
enum error_code ret = init_failed;
if(0 == battery_measure_enable(true)){
ret = success;
}else{}
return ret;
}
enum error_code battery_get_value(int* holder){
enum error_code ret = read_failed;
// battery_voltage in [mV]
int battery_voltage = battery_sample();
if(battery_voltage >= 0){
*holder = (battery_level_pptt(battery_voltage, levels)/100);
ret = success;
}else{}
return ret;
}

View File

@@ -0,0 +1,27 @@
#ifndef BATTERY_PERCENT_H
#define BATTERY_PERCENT_H
#include <zephyr/kernel.h>
#include "modules/battery.h"
#include "error_code.h"
/**
* @brief init the battery module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code battery_init();
/**
* @brief Retrieve the battery charge percentage and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code battery_get_value(int* holder);
#endif //BATTERY_PERCENT_H

View File

@@ -0,0 +1,79 @@
#include "ble_advertiser.h"
static const int BT_MS_ADV_UP = 500; // [ms] time during which the advertising is active
// keys as defined in the specs
static const char BT_KEY_SIZE = 1; // [B]
static const char BT_KEY_WINDOW = 0x01;
static const char BT_KEY_HUMIDITY = 0x02;
static const char BT_KEY_TEMP = 0x03;
static const char BT_KEY_CO2_LVL = 0x04;
static const char BT_KEY_BATT = 0x05;
// value size [B]
static const char BT_PREAMBLE_SIZE = 2;
static const char BT_VALUE_SIZE_WINDOW = 1;
static const char BT_VALUE_SIZE_HUMIDITY = 1;
static const char BT_VALUE_SIZE_TEMP = 2;
static const char BT_VALUE_SIZE_CO2_LVL = 4;
static const char BT_VALUE_SIZE_BATT = 1;
// value start index in the frame - equal to {prior field index + prior value size + key size}
static const int BT_AD_DATA_INDEX_WINDOW = BT_PREAMBLE_SIZE + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_HUMIDITY = BT_AD_DATA_INDEX_WINDOW + BT_VALUE_SIZE_WINDOW + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_TEMP = BT_AD_DATA_INDEX_HUMIDITY + BT_VALUE_SIZE_HUMIDITY + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_CO2_LVL = BT_AD_DATA_INDEX_TEMP + BT_VALUE_SIZE_TEMP + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_BATT = BT_AD_DATA_INDEX_CO2_LVL + BT_VALUE_SIZE_CO2_LVL + BT_KEY_SIZE;
// sum of all value size + size for all keys + size preamble
static const char BT_AD_TOTAL_SIZE =
BT_PREAMBLE_SIZE + (5 * BT_KEY_SIZE) +
BT_VALUE_SIZE_WINDOW + BT_VALUE_SIZE_HUMIDITY + BT_VALUE_SIZE_TEMP + BT_VALUE_SIZE_CO2_LVL + BT_VALUE_SIZE_BATT;
// data field in the broadcasted frame
// all values are set to 0 and the "company id" field is set to 0xffff
static uint8_t ad_data[] = {
0xff, 0xff,
BT_KEY_WINDOW, 0x00,
BT_KEY_HUMIDITY, 0x00,
BT_KEY_TEMP, 0x00, 0x00,
BT_KEY_CO2_LVL, 0x00, 0x00, 0x00, 0x00,
BT_KEY_BATT, 0x00
};
// frame that will be broadcasted
static const struct bt_data ad[] = {
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data, BT_AD_TOTAL_SIZE),
};
enum error_code ble_init(){
return ((0 == bt_enable(NULL)) ? success : init_failed);
}
enum error_code ble_advertise(
enum window_status window_value,
int hygro_value,
int thermo_value,
int co2_lvl_value,
int batt_value
){
enum error_code ret = write_failed;
int shift = 0;
ad_data[BT_AD_DATA_INDEX_WINDOW] = (uint8_t)(window_value == open ? 0x1 : 0x0);
ad_data[BT_AD_DATA_INDEX_HUMIDITY] = (uint8_t)(hygro_value & 0xff);
ad_data[BT_AD_DATA_INDEX_BATT] = (uint8_t)(batt_value & 0xff);
for(int i=0;i<BT_VALUE_SIZE_TEMP;i++){
shift = 8 * (BT_VALUE_SIZE_TEMP - i - 1);
ad_data[BT_AD_DATA_INDEX_TEMP + i] = (uint8_t)((thermo_value>>(shift)) & 0xff);
}
for(int i=0;i<BT_VALUE_SIZE_CO2_LVL;i++){
shift = 8 * (BT_VALUE_SIZE_CO2_LVL - i - 1);
ad_data[BT_AD_DATA_INDEX_CO2_LVL + i] = (uint8_t)((co2_lvl_value>>(shift)) & 0xff);
}
if(0 == bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad, ARRAY_SIZE(ad), NULL, 0)){
k_msleep(BT_MS_ADV_UP);
if(0 == bt_le_adv_stop()){
ret = success;
}else{}
}else{}
return ret;
}

View File

@@ -0,0 +1,37 @@
#ifndef BLE_ADVERTISER_H
#define BLE_ADVERTISER_H
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/assigned_numbers.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "error_code.h"
#include "window_status.h"
/**
* @brief Initialise the ble module
* @return init_failed upon any error occuring during init
* @return success otherwise
*/
enum error_code ble_init();
/**
* @brief Sets the given values as data and broadcast the BLE frame
* @param window_value : window opening status in {open/closed}
* @param hygro_value : humidity percentage [%]
* @param thermo_value : temperature in [d°C]
* @param co2_lvl_value : co2 level in [ppm]
* @param battery_percent_value : battery current level of charge [%]
* @return write_failed if any problem occur during the BLE broadcast
* @return success otherwise
* @note The broadcast is stopped at the return time
*/
enum error_code ble_advertise(
enum window_status window_value,
int hygro_value,
int thermo_value,
int co2_lvl_value,
int batt_value
);
#endif //BLE_ADVERTISER_H

37
nodes/src/co2_level.c Normal file
View File

@@ -0,0 +1,37 @@
#include "co2_level.h"
static const struct device* dev = DEVICE_DT_GET_ONE(ams_ccs811);
const int CO2_LEVEL_EMPTY_ROOM = 400; // [ppm]
enum error_code co2_lvl_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code co2_lvl_get_value(int* holder){
struct sensor_value temp, humidity, co2;
enum error_code ret = read_failed;
int temp_value, humidity_value;
if( (success == thermo_get_value(&temp_value)) && (success == hygro_get_value(&humidity_value)) ){
// temperature conversion from deci, no function in the API
temp.val1 = temp_value/10;
temp.val2 = temp_value%10;
// humidity conversion is straight away
humidity.val1 = humidity_value;
if(
// co2 measurement requires temperature and humidity
(0 == ccs811_envdata_update(dev, &temp, &humidity)) &&
// fetch is required to update sensor read data
(0 == sensor_sample_fetch(dev)) &&
(0 == sensor_channel_get(dev, SENSOR_CHAN_CO2, &co2))
){
*holder = co2.val1; // taking only the integer part
ret = success;
}else{}
}else{}
return success;
}

30
nodes/src/co2_level.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef CO2_LEVEL_H
#define CO2_LEVEL_H
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/ccs811.h>
#include "error_code.h"
#include "hygrometer.h"
#include "thermometer.h"
extern const int CO2_LEVEL_EMPTY_ROOM; // [ppm]
/**
* @brief init the co2 level measurement module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code co2_lvl_init();
/**
* @brief Retrieve the CO2 level measurement and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
* @note the value is not read for the first few measurements after a reboot
*/
enum error_code co2_lvl_get_value(int* holder);
#endif //CO2_LEVEL_H

16
nodes/src/error_code.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef ERROR_CODE_H
#define ERROR_CODE_H
#include <limits.h>
// enum of all error code that may occur during the plein_de_eeeeeeeeeeeeeee application runtime
enum error_code{
success = 0, // any kind of success
init_failed, // error related to initialization
read_failed, // error related to reading action
write_failed, // error related to writing action
error_unknown, // any error not linked to other code
error_code_last, // iteration purpose
};
#endif //ERROR_CODE_H

25
nodes/src/hygrometer.c Normal file
View File

@@ -0,0 +1,25 @@
#include "hygrometer.h"
static const struct device* dev = DEVICE_DT_GET_ONE(st_hts221);
enum error_code hygro_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code hygro_get_value(int* holder){
enum error_code ret = read_failed;
struct sensor_value humidity;
if(
// fetch is required to update sensor read data
(sensor_sample_fetch(dev) >= 0) &&
(sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity) >= 0)
){
*holder = humidity.val1; //taking only the integer part
ret = success;
}else{}
return ret;
}

26
nodes/src/hygrometer.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef HYGROMETER_H
#define HYGROMETER_H
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include "error_code.h"
/**
* @brief init the hygrometer module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code hygro_init();
/**
* @brief Retrieve the humidity level and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code hygro_get_value(int* holder);
#endif //HYGROMETER_H

18
nodes/src/main.c Normal file
View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "supervisor.h"
// size of a stack used by the supervisor thread
#define STACKSIZE 1024
// scheduling priority used by the supervisor thread
#define PRIORITY 7
K_THREAD_DEFINE(supervisor_id, STACKSIZE, thread_supervisor, NULL, NULL, NULL, PRIORITY, 0, 0);

231
nodes/src/modules/battery.c Normal file
View File

@@ -0,0 +1,231 @@
/*
* Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
* Copyright (c) 2019-2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include "battery.h"
LOG_MODULE_REGISTER(BATTERY, CONFIG_ADC_LOG_LEVEL);
#define VBATT DT_PATH(vbatt)
#define ZEPHYR_USER DT_PATH(zephyr_user)
#ifdef CONFIG_BOARD_THINGY52_NRF52832
/* This board uses a divider that reduces max voltage to
* reference voltage (600 mV).
*/
#define BATTERY_ADC_GAIN ADC_GAIN_1
#else
/* Other boards may use dividers that only reduce battery voltage to
* the maximum supported by the hardware (3.6 V)
*/
#define BATTERY_ADC_GAIN ADC_GAIN_1_6
#endif
struct io_channel_config {
uint8_t channel;
};
struct divider_config {
struct io_channel_config io_channel;
struct gpio_dt_spec power_gpios;
/* output_ohm is used as a flag value: if it is nonzero then
* the battery is measured through a voltage divider;
* otherwise it is assumed to be directly connected to Vdd.
*/
uint32_t output_ohm;
uint32_t full_ohm;
};
static const struct divider_config divider_config = {
#if DT_NODE_HAS_STATUS_OKAY(VBATT)
.io_channel = {
DT_IO_CHANNELS_INPUT(VBATT),
},
.power_gpios = GPIO_DT_SPEC_GET_OR(VBATT, power_gpios, {}),
.output_ohm = DT_PROP(VBATT, output_ohms),
.full_ohm = DT_PROP(VBATT, full_ohms),
#else /* /vbatt exists */
.io_channel = {
DT_IO_CHANNELS_INPUT(ZEPHYR_USER),
},
#endif /* /vbatt exists */
};
struct divider_data {
const struct device *adc;
struct adc_channel_cfg adc_cfg;
struct adc_sequence adc_seq;
int16_t raw;
};
static struct divider_data divider_data = {
#if DT_NODE_HAS_STATUS_OKAY(VBATT)
.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBATT)),
#else
.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)),
#endif
};
static int divider_setup(void)
{
const struct divider_config *cfg = &divider_config;
const struct io_channel_config *iocp = &cfg->io_channel;
const struct gpio_dt_spec *gcp = &cfg->power_gpios;
struct divider_data *ddp = &divider_data;
struct adc_sequence *asp = &ddp->adc_seq;
struct adc_channel_cfg *accp = &ddp->adc_cfg;
int rc;
if (!device_is_ready(ddp->adc)) {
LOG_ERR("ADC device is not ready %s", ddp->adc->name);
return -ENOENT;
}
if (gcp->port) {
if (!device_is_ready(gcp->port)) {
LOG_ERR("%s: device not ready", gcp->port->name);
return -ENOENT;
}
rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE);
if (rc != 0) {
LOG_ERR("Failed to control feed %s.%u: %d",
gcp->port->name, gcp->pin, rc);
return rc;
}
}
*asp = (struct adc_sequence){
.channels = BIT(0),
.buffer = &ddp->raw,
.buffer_size = sizeof(ddp->raw),
.oversampling = 4,
.calibrate = true,
};
#ifdef CONFIG_ADC_NRFX_SAADC
*accp = (struct adc_channel_cfg){
.gain = BATTERY_ADC_GAIN,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
};
if (cfg->output_ohm != 0) {
accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0
+ iocp->channel;
} else {
accp->input_positive = SAADC_CH_PSELP_PSELP_VDD;
}
asp->resolution = 14;
#else /* CONFIG_ADC_var */
#error Unsupported ADC
#endif /* CONFIG_ADC_var */
rc = adc_channel_setup(ddp->adc, accp);
LOG_INF("Setup AIN%u got %d", iocp->channel, rc);
return rc;
}
static bool battery_ok;
static int battery_setup(void)
{
int rc = divider_setup();
battery_ok = (rc == 0);
LOG_INF("Battery setup: %d %d", rc, battery_ok);
return rc;
}
SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
int battery_measure_enable(bool enable)
{
int rc = -ENOENT;
if (battery_ok) {
const struct gpio_dt_spec *gcp = &divider_config.power_gpios;
rc = 0;
if (gcp->port) {
rc = gpio_pin_set_dt(gcp, enable);
}
}
return rc;
}
int battery_sample(void)
{
int rc = -ENOENT;
if (battery_ok) {
struct divider_data *ddp = &divider_data;
const struct divider_config *dcp = &divider_config;
struct adc_sequence *sp = &ddp->adc_seq;
rc = adc_read(ddp->adc, sp);
sp->calibrate = false;
if (rc == 0) {
int32_t val = ddp->raw;
adc_raw_to_millivolts(adc_ref_internal(ddp->adc),
ddp->adc_cfg.gain,
sp->resolution,
&val);
if (dcp->output_ohm != 0) {
rc = val * (uint64_t)dcp->full_ohm
/ dcp->output_ohm;
LOG_INF("raw %u ~ %u mV => %d mV\n",
ddp->raw, val, rc);
} else {
rc = val;
LOG_INF("raw %u ~ %u mV\n", ddp->raw, val);
}
}
}
return rc;
}
unsigned int battery_level_pptt(unsigned int batt_mV,
const struct battery_level_point *curve)
{
const struct battery_level_point *pb = curve;
if (batt_mV >= pb->lvl_mV) {
/* Measured voltage above highest point, cap at maximum. */
return pb->lvl_pptt;
}
/* Go down to the last point at or below the measured voltage. */
while ((pb->lvl_pptt > 0)
&& (batt_mV < pb->lvl_mV)) {
++pb;
}
if (batt_mV < pb->lvl_mV) {
/* Below lowest point, cap at minimum */
return pb->lvl_pptt;
}
/* Linear interpolation between below and above points. */
const struct battery_level_point *pa = pb - 1;
return pb->lvl_pptt
+ ((pa->lvl_pptt - pb->lvl_pptt)
* (batt_mV - pb->lvl_mV)
/ (pa->lvl_mV - pb->lvl_mV));
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef APPLICATION_BATTERY_H_
#define APPLICATION_BATTERY_H_
/** Enable or disable measurement of the battery voltage.
*
* @param enable true to enable, false to disable
*
* @return zero on success, or a negative error code.
*/
int battery_measure_enable(bool enable);
/** Measure the battery voltage.
*
* @return the battery voltage in millivolts, or a negative error
* code.
*/
int battery_sample(void);
/** A point in a battery discharge curve sequence.
*
* A discharge curve is defined as a sequence of these points, where
* the first point has #lvl_pptt set to 10000 and the last point has
* #lvl_pptt set to zero. Both #lvl_pptt and #lvl_mV should be
* monotonic decreasing within the sequence.
*/
struct battery_level_point {
/** Remaining life at #lvl_mV. */
uint16_t lvl_pptt;
/** Battery voltage at #lvl_pptt remaining life. */
uint16_t lvl_mV;
};
/** Calculate the estimated battery level based on a measured voltage.
*
* @param batt_mV a measured battery voltage level.
*
* @param curve the discharge curve for the type of battery installed
* on the system.
*
* @return the estimated remaining capacity in parts per ten
* thousand.
*/
unsigned int battery_level_pptt(unsigned int batt_mV,
const struct battery_level_point *curve);
#endif /* APPLICATION_BATTERY_H_ */

75
nodes/src/supervisor.c Normal file
View File

@@ -0,0 +1,75 @@
#include "supervisor.h"
const int SLEEP_GRANULARITY = 1; // [min]
const int SLEEP_MIN_DURATION = SLEEP_GRANULARITY; // [min]
const int SLEEP_MAX_DURATION = 30; // [min]
// Zephyr related stuff
void thread_supervisor(){
supervisor_init();
supervisor_run();
//should never return
}
// supervisor stuff
enum error_code supervisor_init(){
enum error_code ret = init_failed;
if(
success == ble_init() &&
success == co2_lvl_init() &&
success == hygro_init() &&
success == thermo_init() &&
success == window_init() &&
success == battery_init()
){
ret = success;
}else{}
return ret;
}
enum error_code supervisor_run(){
int co2_lvl_value = -1;
int hygro_value = -1;
int thermo_value = -1;
int batt_value = -1;
enum window_status window_value = unknown;
enum error_code co2_lvl_status, hygro_status, thermo_status, window_status, batt_status;
int current_sleep_time = SLEEP_MIN_DURATION;
while(1){
co2_lvl_status = co2_lvl_get_value(&co2_lvl_value);
hygro_status = hygro_get_value(&hygro_value);
thermo_status = thermo_get_value(&thermo_value);
window_status = window_get_value(&window_value);
batt_status = battery_get_value(&batt_value);
if(success != window_status){
window_value = unknown;
}else{}
if(success != hygro_status){
hygro_value = -1;
}else{}
if(success != thermo_status){
thermo_value = -1;
}else{}
if(success != co2_lvl_status){
co2_lvl_value = -1;
}else{}
if(success != batt_status){
batt_value = -1;
}else{}
ble_advertise(window_value, hygro_value, thermo_value, co2_lvl_value, batt_value);
if((co2_lvl_value > CO2_LEVEL_EMPTY_ROOM) || (window_value == open)){
// there are people in the room, or someone forgot to close the window
current_sleep_time = SLEEP_MIN_DURATION;
}else{
// no one is in the room, we can wait a litle bit longer before getting the next data point
current_sleep_time += SLEEP_GRANULARITY;
if(current_sleep_time > SLEEP_MAX_DURATION){
current_sleep_time = SLEEP_MAX_DURATION;
}else{}
}
k_sleep(K_MINUTES(current_sleep_time));
}
return error_unknown; // should never return
}

38
nodes/src/supervisor.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef SUPERVISOR_H
#define SUPERVISOR_H
#include <zephyr/kernel.h>
#include "error_code.h"
#include "ble_advertiser.h"
#include "window_status.h"
#include "thermometer.h"
#include "hygrometer.h"
#include "co2_level.h"
#include "battery_percent.h"
extern const int SLEEP_GRANULARITY; // [min]
extern const int SLEEP_MIN_DURATION; // [min]
extern const int SLEEP_MAX_DURATION; // [min]
/**
* @brief thread function to run the supervisor in zephyr environment
*/
void thread_supervisor();
/**
* @brief init the supervisor, and thus the complete plein_de_ee application
* @return init_failed upon any error occurring during the initialisation of any module
* @return success otherwise
*/
enum error_code supervisor_init();
/**
* @brief exexcute the plein_de_eeeeeee application
* @return error_unknown
* @note should never return
*/
enum error_code supervisor_run();
#endif //SUPERVISOR_H

23
nodes/src/thermometer.c Normal file
View File

@@ -0,0 +1,23 @@
#include "thermometer.h"
static const struct device* dev = DEVICE_DT_GET_ONE(st_hts221);
enum error_code thermo_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code thermo_get_value(int* holder){
enum error_code ret = read_failed;
struct sensor_value temp;
if( (sensor_sample_fetch(dev) >= 0) &&
(sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp) >= 0)
){
*holder = sensor_value_to_deci(&temp);
ret = success;
}else{}
return ret;
}

26
nodes/src/thermometer.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef THERMOMETER_H
#define THERMOMETER_H
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include "error_code.h"
/**
* @brief init the thermometer module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code thermo_init();
/**
* @brief Retrieve the temperature and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code thermo_get_value(int* holder);
#endif //THERMOMETER_H

17
nodes/src/window_status.c Normal file
View File

@@ -0,0 +1,17 @@
#include "window_status.h"
static const struct gpio_dt_spec window_switch = GPIO_DT_SPEC_GET(DT_NODELABEL(window_switch), gpios);
enum error_code window_init(){
enum error_code ret = init_failed;
if(gpio_is_ready_dt(&window_switch) && (0 == gpio_pin_configure_dt(&window_switch, GPIO_INPUT))){
ret = success;
}else{}
return ret;
}
enum error_code window_get_value(enum window_status* holder){
// active high
*holder = (gpio_pin_get_dt(&window_switch) ? closed : open);
return success;
}

31
nodes/src/window_status.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef WINDOW_STATUS_H
#define WINDOW_STATUS_H
#include <zephyr/drivers/gpio.h>
#include "error_code.h"
enum window_status{
closed = 0, // window is closed or sensor is not connected
open, // window is opened
unknown, // window opening status could not be read
windows_status_last, // iteration purpose
};
/**
* @brief init the window opening status module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code window_init();
/**
* @brief Retrieve the window opening status and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code window_get_value(enum window_status* holder);
#endif //WINDOW_STATUS_

View File

@@ -1,11 +1,11 @@
#import "../../resources/helper.typ": *
#let b = actor("broker",
disp_name: [@mqtt\ broker],
disp_name: [@mqtt:short\ broker],
shape: "queue",
show-bottom:false
)
#let mg = actor("mqtt",
disp_name: [@mqtt\ gateway],
disp_name: [@mqtt:short\ gateway],
show-bottom:false
)
#let ig = actor("influx",

View File

@@ -0,0 +1,106 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
#import "/main/database/sequence.typ": *
// server
// mqtt->db
// db->rest
== Database & API - Server
#let server = [
#figure(
image("server.png", width: 100%),
caption: [Server implementation],
) <fig:server>
]
#grid(
columns: (1.5fr, 1fr),
column-gutter: 2em,
server,
align(top+left)[
#v(3em) #pause
- LXC Debian #pause
- @ssh:short certificate by user #pause
- Docker #pause
- Traefik
],
)
== Database & API - Save measures in DB
#let toDB = {
import chronos: *
b.display
mg.display
main.display
ig.display
db.display
_col(b.name, mg.name, width: 5cm)
_col(mg.name, main.name, width: 5cm)
_col(main.name, ig.name, width: 5cm)
_col(ig.name, db.name, width: 5cm)
}
#let toDB-seq = (
async(b, mg, "message"),
sync(mg, main, "DataPoint"),
sync(main, main, "map topics"),
sync(main, ig, "DataPoint"),
async(ig, db, "flush")
)
#set align(center+top)
#v(1em)
#figure(
box(height: 10cm, {
let diag = toDB
for (i, step) in toDB-seq.enumerate(start: 1) {
diag += step
only(i, chronos.diagram(diag, width: 90%))
}
}),
caption: [Sequence from broker to DB]
) <fig:seq:toDB>
== Database & API - Get data from DB
#let fromDB = {
import chronos: *
db.display
ig.display
main.display
rg.display
u.display
_col(db.name, ig.name, width: 5cm)
_col(ig.name, main.name, width: 5cm)
_col(main.name, rg.name, width: 5cm)
_col(rg.name, u.name, width: 5cm)
}
#let fromDB-seq = (
async(u, rg, "Request"),
sync(rg, main, "getNodes"),
sync(main, rg, "", dashed: true),
sync(rg, ig, "Query"),
sync(ig, db, ""),
sync(db, ig, "", dashed: true),
sync(ig, rg, "", dashed: true),
sync(rg, u, "", dashed: true),
)
#set align(center+top)
#v(1em)
#figure(
box(height: 11.75cm, {
let diag = fromDB
for (i, step) in fromDB-seq.enumerate(start: 1) {
diag += step
only(i, chronos.diagram(diag, width: 90%))
}
}),
caption: [Sequence to REST from DB]
) <fig:seq:fromDB>

View File

@@ -0,0 +1,67 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
== Gateway — BLE to MQTT Bridge
#slide[
#grid(
columns: (1fr, 1fr),
gutter: 2em,
align: top+left,
[
*Architecture*
- Raspberry Pi 4 Python
- Passive @ble:short scan (bleak)
- MQTTS publisher (paho-mqtt)
- systemd service auto-restart
- Remote access via Tailscale
],
[
// *Data flow*\
// Thingy:52
// $->$ BLE advertising
// Raspberry Pi
// $->$ MQTTS (TLS)
// RabbitMQ broker
// $->$
// InfluxDB
]
)
]
== Gateway — Overview
#slide[
#align(center)[
#figure(
image("/resources/img/gateway_overview.svg", height: 40%),
caption: [Gateway communication chain from @ble:short advertising to database storage]
)
]
]
== Gateway — Key Challenges
#slide[
#grid(
columns: (1fr, 1fr),
gutter: 2em,
align: top+left,
[
*BLE filtering*
- GATT $->$ passive advertising
- Filter: `company_id = 0xffff`
- exactly 14 bytes
- Continuous BLE scan (100% duty cycle)
- Deduplication: 10s cache per MAC address
],
[
*Reliability — validated in production*
- MQTT auto-restart via systemd
- `os._exit(1)` on disconnection
- Validated with 2 cases:
- Network disconnection
- Wrong MQTT credentials
- Multiple nodes publishing
simultaneously confirmed by broker
- No duplicate data
]
)
]

View File

@@ -0,0 +1,60 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
---
Output:
- Evolution of @co2:short concentration without considering air ventilation #pause
- Time required to reach a threshold of 1400 ppm #pause
- Evolution of @co2:short concentration decrease under natural ventilation #pause
- Time required to reach outdoor-equivalent concentration level #pause
Input:
- Provence classrooms specifications - Space A #pause
- Open data #pause
- Ventilation standard formula #pause
- User parameters : Room name, number of students, initial @co2:short concentration
---
=== Modelling the temporal evolution of @co2:short concentration
#grid(
columns: (1fr, 1fr),
[
#figure(
image("../../resources/img/Physical model/CO2 concentration over time Simaria.png"),
caption: [Comparison of modelled and open data #cite(<Simaria>) @co2:short concentration evolution]
) <fig:comparison_open_data_model_no_window_opening>
],
[
#figure(
image("../../resources/img/Physical model/CO2 concentration over time INRS secondary school.png"),
caption: [Comparison of modelled and open data #cite(<INRS_Study>) @co2:short concentration evolution]
)
]
)
---
=== Modelling the @co2:short concentration increase without air ventilation and @co2:short decrease under natural ventilation
#grid(
columns: (1fr, 1fr),
[
#figure(
image("../../resources/img/Physical model/Window user window opening model.png"),
caption: [User input parameters and results]
) <fig:user_input_parameters_results>
],pause,
[
#figure(
image("../../resources/img/Physical model/CO2 concentration over time user window opening model.png"),
caption: [Temporal evolution of @co2:short level using input parameters from @fig:user_input_parameters_results]
)
]
)
---
=== Comparison of experimental data and the physical model
#figure(
image("../../resources/img/Physical model/Comparison_expdata_model.png", width: 59%),
caption: [Comparison of experimental data and the physical model (A2 classroom, 11 students)]
)

View File

@@ -0,0 +1,45 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
---
== Nodes | Class diagram
#let nodes_class_diagram_impl = [
#figure(
image("../..//resources/img/nodes_class_diagram_impl.svg"),
caption: [Nodes class diagram following implementation]
) <fig:nodes_class_diagram_impl>
]
#nodes_class_diagram_impl
== Nodes | Sequence diagram
#let nodes_sequence_diagram = [
#figure(
image("../../resources/img/nodes_sequence_diagram.svg", width: 60%),
caption: [Nodes sequence diagram]
) <fig:nodes_sequence_diagram>
]
#nodes_sequence_diagram
== Nodes | BLE data
#figure(
table(
columns: (auto, auto, auto),
align: center,
table.header("name", "key", "data size"),
[Window opening status],[0x01],[1B],
[Humidity],[0x02],[1B],
[Temperature],[0x03],[2B],
[@co2:short level],[0x04],[4B],
[Battery percent of charge],[0x05],[1B],
),
caption: [Data communicated in the nodes_interface],
)<tab:nodes_interface_content>
== Nodes | Takeaways
#align(top+left)[
#v(5em)
- Breadboard validation #pause
- 28 days later #pause
- Improve @ble:short reliability
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -0,0 +1,13 @@
<diagram program="umletino" version="15.1"><zoom_level>10</zoom_level><element><id>UMLDeployment</id><coordinates><x>310</x><y>280</y><w>330</w><h>350</h></coordinates><panel_attributes>Server</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLGeneric</id><coordinates><x>340</x><y>390</y><w>100</w><h>40</h></coordinates><panel_attributes>&lt;&lt;Broker MQTT&gt;&gt;
RabbitMQ</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLGeneric</id><coordinates><x>490</x><y>320</y><w>100</w><h>40</h></coordinates><panel_attributes>&lt;&lt;Database&gt;&gt;
InfluxDB3</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLGeneric</id><coordinates><x>490</x><y>390</y><w>100</w><h>40</h></coordinates><panel_attributes>&lt;&lt;API DB&gt;&gt;
Go service</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>430</x><y>400</y><w>80</w><h>30</h></coordinates><panel_attributes>lt=&lt;-</panel_attributes><additional_attributes>60;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>560</x><y>350</y><w>30</w><h>60</h></coordinates><panel_attributes>lt=-&gt;</panel_attributes><additional_attributes>10;10;10;40</additional_attributes></element><element><id>Relation</id><coordinates><x>510</x><y>350</y><w>30</w><h>60</h></coordinates><panel_attributes>lt=-&gt;</panel_attributes><additional_attributes>10;40;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>230</x><y>390</y><w>130</w><h>50</h></coordinates><panel_attributes>lt=-()
m2=MQTT
</panel_attributes><additional_attributes>110;20;10;20</additional_attributes></element><element><id>Relation</id><coordinates><x>530</x><y>430</y><w>190</w><h>50</h></coordinates><panel_attributes>lt=-()
m2=REST
</panel_attributes><additional_attributes>10;20;170;20</additional_attributes></element><element><id>UMLDeployment</id><coordinates><x>330</x><y>510</y><w>110</w><h>50</h></coordinates><panel_attributes>&lt;&lt;Dashboard&gt;&gt;
Angular</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>430</x><y>490</y><w>140</w><h>60</h></coordinates><panel_attributes>lt=)-
m1=
</panel_attributes><additional_attributes>110;20;110;40;10;40</additional_attributes></element><element><id>UMLDeployment</id><coordinates><x>330</x><y>570</y><w>110</w><h>50</h></coordinates><panel_attributes>&lt;&lt;Notification&gt;&gt;
Java</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>430</x><y>520</y><w>130</w><h>90</h></coordinates><panel_attributes></panel_attributes><additional_attributes>110;10;110;70;10;70</additional_attributes></element><element><id>Relation</id><coordinates><x>530</x><y>420</y><w>60</w><h>90</h></coordinates><panel_attributes>lt=-()
m2=REST</panel_attributes><additional_attributes>10;10;10;70</additional_attributes></element></diagram>

View File

@@ -1,14 +1,14 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
#show:make-glossary
#register-glossary(entry-list)
#let HANDOUT = true
#let NOTES = false
#let HANDOUT = false
#let NOTES = true
#show: metropolis-theme.with(
aspect-ratio: "16-9",
@@ -33,7 +33,7 @@
#title-slide()
// 20 min presentation
// 5 (students) + 25 (teacher) min Q&A
// 5 (students) + 25 (teachers) min Q&A
/*
technical -> each section should go for around (3min / pers)
@@ -58,16 +58,138 @@ TECHNIQUE
*/
= Intro
= Intro // (50s) Rémi
// Context of the project
// Dimitri missing
#speaker-note[
This is a personal note
]
---
== foo
Yolo
== Architecture // (50s) Ibrahima
#let top_level_architecture = [
#figure(
image("../../resources/img/ui_images/architecture.png"),
caption: [Top level architecture]
) <fig:top_level_architecture>
]
#top_level_architecture
== bar
Hello world
== Organisation & Task Management // (50s) Djelal
#slide[
#grid(
columns: (1fr, 1fr),
gutter: 2em,
align: left+top,
[
*Project management*
- Weekly meetings
- PV after each meeting
- GitHub Issues & sub-issues
- Pull Requests with code review
- Teams for daily communication
- GULAG Git conventions
],
[
*Work distribution*
- Adrien Nodes firmware (Zephyr)
- Djelal Gateway (BLE-to-MQTT)
- Rémi Database & API
- Ibrahima User Interface
- Alison Physical model
]
)
]
= Nodes // (3min) Adrien
#include "nodes.typ"
= Gateway // (3min) Djelal
#include "gateway.typ"
= Database & API // (3min) Rémi
#include "db.typ"
= User interface // (3min) Ibrahima
#include "ui.typ"
= Physical model // (3min) Alison
#include "models.typ"
= Conclusion
---
It's the end of the world
== Project's takeaways // (50s) Adrien (Regard critique)
- @trl:long (@trl:short) 4 #pause
- Forecasting // and Teams notifications
== Future perspectives // (50s) Alison
- Deployment in every room #pause
- Equip the door with sensors #pause
- Calibrate the sensors and, if necessary, replace them with higher-performance devices #pause
- Conduct multiple measurement campaigns knowing the number of students to adjust the physical model #pause
- Display the predicted time to reach the threshold and the window opening duration on the board #pause
- Teams notifications #pause
- Implement a forecasting using machine learning
#focus-slide[Questions?]
#show: appendix
== Glossary
#print-glossary(
entry-list,
// show all term even if they are not referenced, default to true
show-all: false,
// disable the back ref at the end of the descriptions
disable-back-references: true
)
== Bibliography
#bibliography(title: i18n("bib-title", lang: option.lang), bib.path, style:bib.style)
= Annexes
== Description of the model
#grid(
columns: (1.7fr,0.1fr,1fr),
[#figure(
image("../../resources/img/Physical model/data flow diagram window opening .png"),
caption: [Description of the model]
) <fig:physical_model_no_ventilation>],
[
],
[
Formulas for determining the evolution of @co2 concentration :
#text(size: 12pt)[
- $C_"CO2" (t) = C_"CO2" (t=0) + frac(N.Q_"CO2_prod".t,V)
$
- $C_"CO2" (t) = (C_"CO2_indoor" (t=0) - C_"CO2_outdoor" - frac(0.001 . Q_"CO2_prod", Q_"air")) . \ exp (frac(-Q_"air", V) . t)
+ C_"CO2_outdoor" + frac(0.001 . Q_"CO2_prod", Q_"air")
$
where,
$C_"CO2" (t=0)$ #h(1.5cm) initial co2 concentration [ppm]
N #h(3.4cm) number of students
$Q_"CO2_prod"$ #h(2cm) co2 flow rate per person [l/h]
$C_"CO2_indoor" (t=0)$ #h(0.6cm) indoor co2 level before window-opening [ppm]
$C_"CO2_outdoor"$ #h(1.7cm) outdoor air concentration [ppm]
$Q_"air"$ #h(3cm) incoming air flow rate [$m^3$/h]
$V$ #h(3.3cm) room volume [$m^3$]
$t$ #h(3.5cm) time [h]]
]
)

View File

@@ -0,0 +1,197 @@
#import "/metadata.typ": *
#import "/tail/bibliography.typ": *
#import "/tail/glossary.typ": *
#import "/main/architecture/description.typ": *
#import "/resources/slides.typ": *
// Chemin racine des images — adapter selon ta structure de projet
#let img-root = "../../resources/img/ui_images/images/"
// ── Palette ───────────────────────────────────────────────────────────────────
#let c-dark = rgb("#0F172A")
#let c-teal = rgb("#0EA5E9")
#let c-text = rgb("#1E293B")
#let c-muted = rgb("#64748B")
#let c-white = rgb("#FFFFFF")
#let c-border = rgb("#E2E8F0")
// ── Helpers ───────────────────────────────────────────────────────────────────
#let icon-circle(img-path, size: 38pt, bg: rgb("#0EA5E9")) = {
box(
width: size, height: size,
fill: bg, radius: (size / 2),
inset: 0pt, clip: true,
)[
#align(center + horizon)[
#image(img-path, width: (size * 0.62), height: (size * 0.62), fit: "contain")
]
]
}
#let badge(label, fill: rgb("#EF4444")) = {
box(fill: fill, radius: 3pt, inset: (x: 5pt, y: 2pt))[
#text(size: 6.5pt, weight: "bold", fill: rgb("#FFFFFF"))[#label]
]
}
#let devsec-col(phase-title, items) = {
block(width: 100%)[
#block(
width: 100%, height: 28pt,
fill: c-dark,
radius: (top-left: 5pt, top-right: 5pt, bottom-left: 0pt, bottom-right: 0pt),
inset: 0pt,
)[
#pad(x: 7pt)[
#align(horizon + center)[
#text(size: 8.5pt, weight: "bold", fill: rgb("#FFFFFF"))[#phase-title]
]
]
]
#block(
width: 100%,
fill: rgb("#F1F5F9"),
radius: (top-left: 0pt, top-right: 0pt, bottom-left: 5pt, bottom-right: 5pt),
inset: 8pt,
)[
#for item in items {
grid(columns: (30pt, 1fr), gutter: 6pt,
icon-circle(item.at("icon"), size: 28pt, bg: item.at("bg", default: c-teal)),
align(left + horizon)[
#text(size: 8pt, weight: "bold", fill: c-text)[#item.at("name")]
#linebreak()
#item.at("extra", default: [])
],
)
v(5pt)
}
]
]
}
== Cycle DevSecOps
#slide[
#grid(columns: (1fr, 1fr, 1fr, 1fr), gutter: 8pt,
devsec-col("① Code & PR Gate", (
(icon: img-root + "image10.png", bg: rgb("#3178C6"), name: "TypeScript",
extra: [#text(size: 6.5pt, fill: c-muted)[tsc / ESLint]]),
(icon: img-root + "image11.png", bg: rgb("#DD0031"), name: "Angular CI",
extra: [#text(size: 6.5pt, fill: c-muted)[Build check]]),
(icon: img-root + "image19.png", bg: rgb("#1F2328"), name: "GitHub Actions",
extra: [#text(size: 6.5pt, fill: c-muted)[Coverage]]),
)),pause,
devsec-col("② SAST · SCA", (
(icon: img-root + "image7.png", bg: rgb("#000000"), name: "SpotBugs",
extra: [#badge("BLOCKING")]),
(icon: img-root + "image13.png", bg: rgb("#1F2328"), name: "CodeQL",
extra: [#badge("BLOCKING")]),
(icon: img-root + "image14.png", bg: rgb("#F97316"), name: "Dep. Check",
extra: [#badge("NON-BLOCK", fill: rgb("#F97316"))]),
)),pause,
devsec-col("③ DAST · Tests", (
(icon: img-root + "image15.png", bg: rgb("#00549E"), name: "OWASP ZAP",
extra: [#badge("BLOCKING")]),
(icon: img-root + "image16.png", bg: rgb("#DD0031"), name: "Karma Tests",
extra: [#badge("BLOCKING")]),
(icon: img-root + "image17.png", bg: rgb("#64748B"), name: "Runtime check",
extra: [#text(size: 6.5pt, fill: c-muted)[HTTP headers]]),
)),pause,
devsec-col("④ Build · Deploy", (
(icon: img-root + "image18.png", bg: rgb("#0DB7ED"), name: "Docker",
extra: [#text(size: 6.5pt, fill: c-muted)[SHA-tagged]]),
(icon: img-root + "image19.png", bg: rgb("#1F2328"), name: "GHCR Push",
extra: [#text(size: 6.5pt, fill: c-muted)[main only]]),
(icon: img-root + "image17.png", bg: rgb("#10B981"), name: "SSH Deploy",
extra: [#text(size: 6.5pt, fill: c-muted)[cert-auth]]),
)),pause,
)
#v(7pt)
#rect(width: 100%, height: 26pt, fill: c-dark, radius: 4pt, inset: 0pt)[
#pad(x: 20pt)[
#align(horizon)[
#grid(columns: (1fr, 1fr, 1fr),
align(center + horizon)[#text(size: 8pt, fill: rgb("#FFFFFF"))[🔐 #h(2pt) Shift-left security]],
align(center + horizon)[#text(size: 8pt, fill: rgb("#FFFFFF"))[🔑 #h(2pt) Zero secret in code]],
align(center + horizon)[#text(size: 8pt, fill: rgb("#FFFFFF"))[🔄 #h(2pt) Automated Deployment]],
)
]
]
]
#v(6pt)
]
// ── SLIDE 3 — Dashboard ───────────────────────────────────────────────────────
== Dashboard
#slide[
#align(center + horizon)[
#figure(
image(img-root + "image20.png", width: 100%, fit: "contain"),
caption: [Dashboard]
)
]
]
// ── SLIDE 4 — Details Page ────────────────────────────────────────────────────
== Details Page
#slide[
#align(center + horizon)[
#figure(
image(img-root + "image21.png", width: 100%, fit: "contain"),
caption: [Details page]
)
]
]
// ── SLIDE 5 — Notification ────────────────────────────────────────────────────
== Notification
#slide[
#grid(columns: (1fr, 1fr), gutter: 16pt,
// Screenshot Telegram
align(center + horizon)[
#figure(
image(img-root + "image22.png", height: 300pt, fit: "contain"),
caption: [Telegram notification]
)
],
// Carte descriptive
rect(width: 100%, radius: 6pt, stroke: 0.5pt + c-border, fill: rgb("#FFFFFF"), inset: 12pt)[
#text(size: 10pt, weight: "bold", fill: c-text)[CO₂ Alerts System]
#v(5pt)
#line(length: 100%, stroke: 0.5pt + c-border)
#v(6pt)
#let alert-row(col, level, desc) = {
grid(columns: (10pt, 52pt, 1fr), gutter: 5pt,
box(width: 8pt, height: 8pt, fill: col, radius: 4pt),
text(size: 8.5pt, weight: "bold")[#level],
text(size: 8pt, fill: c-muted)[#desc],
)
v(4pt)
}
#alert-row(rgb("#f44336"), "Critical", "> 2000 ppm")
#alert-row(rgb("#F97316"), "Very Poor", "1500-2000 ppm")
#alert-row(rgb("#ff9800"), "Poor", "1200-1500 ppm")
#alert-row(rgb("#ffc107"), "Moderate", "1000-1200 ppm")
#alert-row(rgb("#8bc34a"), "Good", "800-1000 ppm")
#alert-row(rgb("#4caf50"), "Excellent", "< 800 ppm")
#v(6pt)
#line(length: 100%, stroke: 0.5pt + c-border)
#v(6pt)
#v(6pt)
],
)
]

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="238px" preserveAspectRatio="none" style="width:1106px;height:238px;" version="1.1" viewBox="0 0 1106 238" width="1106px" zoomAndPan="magnify"><defs><filter height="300%" id="fdgkvirjllmq" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--MD5=[d638d2e8639369a4c8d274dd926c65e9]
entity thingy--><rect fill="#ADD8E6" filter="url(#fdgkvirjllmq)" height="117.7813" style="stroke: #A80036; stroke-width: 1.5;" width="100" x="6" y="11.5"/><rect fill="#ADD8E6" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1" y="16.5"/><rect fill="#ADD8E6" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1" y="119.2813"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="69" x="16" y="34.4951">Thingy:52</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="80" x="16" y="50.792">──────────</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="78" x="16" y="67.0889">CO2, Temp</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="59" x="16" y="83.3857">Humidity</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="49" x="16" y="99.6826">Battery</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="54" x="16" y="115.9795">Window</text><!--MD5=[7b30460008fc71f9986330c33ee2d290]
entity gateway--><rect fill="#90EE90" filter="url(#fdgkvirjllmq)" height="101.4844" style="stroke: #A80036; stroke-width: 1.5;" width="151" x="310" y="20"/><rect fill="#90EE90" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="305" y="25"/><rect fill="#90EE90" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="305" y="111.4844"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="99" x="320" y="42.9951">Raspberry Pi 4</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="80" x="320" y="59.292">──────────</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="77" x="320" y="75.5889">gateway.py</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="129" x="320" y="91.8857">bleak + paho-mqtt</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="131" x="320" y="108.1826">asyncio + systemd</text><!--MD5=[e701bbac852ce3c7f1c3d2193e52d58f]
entity broker--><rect fill="#FFFFE0" filter="url(#fdgkvirjllmq)" height="85.1875" style="stroke: #A80036; stroke-width: 1.5;" width="119" x="710" y="28"/><rect fill="#FFFFE0" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="705" y="33"/><rect fill="#FFFFE0" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="705" y="103.1875"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="67" x="720" y="50.9951">RabbitMQ</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="80" x="720" y="67.292">──────────</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="99" x="720" y="83.5889">MQTTS broker</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="76" x="720" y="99.8857">TLS + auth</text><!--MD5=[7468467f3b6b392f1d176d828c753b77]
entity db--><rect fill="#FFA07A" filter="url(#fdgkvirjllmq)" height="36.2969" style="stroke: #A80036; stroke-width: 1.5;" width="76" x="1019" y="52.5"/><rect fill="#FFA07A" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1014" y="57.5"/><rect fill="#FFA07A" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1014" y="78.7969"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="1029" y="75.4951">InfluxDB</text><path d="M307,156 L307,226.5313 A0,0 0 0 0 307,226.5313 L464,226.5313 A0,0 0 0 0 464,226.5313 L464,166 L454,156 L389.5,156 L385.5,121.02 L381.5,156 L307,156 A0,0 0 0 0 307,156 " fill="#FBFB77" filter="url(#fdgkvirjllmq)" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M454,156 L454,166 L464,166 L454,156 " fill="#FBFB77" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="42" x="313" y="173.0669">Filters:</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="136" x="313" y="188.1997">- company_id = 0xffff</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="133" x="313" y="203.3325">- payload = 14 bytes</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="134" x="313" y="218.4653">- dedup 10s per MAC</text><!--MD5=[72a710db11e8fbeffe0299f3833be780]
link thingy to gateway--><path d="M106.03,70.5 C158.53,70.5 242.65,70.5 304.72,70.5 " fill="none" id="thingy-&gt;gateway" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="309.9,70.5,300.9,66.5,304.9,70.5,300.9,74.5,309.9,70.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="98" x="157.5" y="21.5669">BLE advertising</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="112" x="150.5" y="36.6997">company_id 0xffff</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="55" x="179" y="51.8325">14 bytes</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="139" x="137" y="66.9653">3 channels (37/38/39)</text><!--MD5=[d5fc4fe9659f4c010806cb130a744287]
link gateway to broker--><path d="M461.06,70.5 C531.95,70.5 637.45,70.5 704.59,70.5 " fill="none" id="gateway-&gt;broker" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="709.76,70.5,700.76,66.5,704.76,70.5,700.76,74.5,709.76,70.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="94" x="540" y="36.5669">MQTTS port 80</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="86" x="544" y="51.6997">JSON payload</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="184" x="495" y="66.8325">{gateway_id}/{mac}/update</text><!--MD5=[b33725c11cd6fb0cd801e7a81b9a0575]
link broker to db--><path d="M829.31,70.5 C884.12,70.5 964.53,70.5 1013.4,70.5 " fill="none" id="broker-&gt;db" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1018.61,70.5,1009.61,66.5,1013.61,70.5,1009.61,74.5,1018.61,70.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="128" x="860" y="66.5669">store measurement</text><!--MD5=[9109db2b88df5a8c8bdd4041f3fe8ffc]
@startuml
skinparam linestyle ortho
skinparam componentStyle rectangle
skinparam backgroundColor white
left to right direction
component "Thingy:52\n──────────\nCO2, Temp\nHumidity\nBattery\nWindow" as thingy #LightBlue
component "Raspberry Pi 4\n──────────\ngateway.py\nbleak + paho-mqtt\nasyncio + systemd" as gateway #LightGreen
component "RabbitMQ\n──────────\nMQTTS broker\nTLS + auth" as broker #LightYellow
component "InfluxDB" as db #LightSalmon
thingy - -> gateway : BLE advertising\ncompany_id 0xffff\n14 bytes\n3 channels (37/38/39)
gateway - -> broker : MQTTS port 80\nJSON payload\n{gateway_id}/{mac}/update
broker - -> db : store measurement
note bottom of gateway
Filters:
- company_id = 0xffff
- payload = 14 bytes
- dedup 10s per MAC
end note
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 21.0.11+10-1-deb13u2-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: GB
--></g></svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,441 @@
time,co2,temperature,humidity,windows,battery
2026-06-11T08:45:04,1766,27.7,38,0,25
2026-06-11T08:46:04,1815,28.1,37,0,27
2026-06-11T08:48:05,1836,28.7,37,0,25
2026-06-11T08:49:06,1844,29,36,0,25
2026-06-11T08:50:06,1830,29.2,36,0,25
2026-06-11T08:51:07,1837,29.5,35,0,27
2026-06-11T08:53:08,1903,29.8,35,0,27
2026-06-11T08:54:08,1903,30,34,0,25
2026-06-11T08:55:09,1868,30.2,34,0,27
2026-06-11T08:56:10,1900,30.3,34,0,25
2026-06-11T08:57:10,1914,30.5,34,0,27
2026-06-11T08:58:10,1925,30.6,33,0,27
2026-06-11T08:59:11,1951,30.7,33,0,25
2026-06-11T09:00:11,1975,30.8,33,0,27
2026-06-11T09:01:12,1975,31,32,0,27
2026-06-11T09:02:13,1942,31,33,0,25
2026-06-11T09:03:13,1938,31.1,32,0,27
2026-06-11T09:04:14,1965,31.2,32,0,27
2026-06-11T09:05:14,1989,31.3,32,0,25
2026-06-11T09:07:15,1978,31.5,32,0,27
2026-06-11T09:08:15,1989,31.6,32,0,27
2026-06-11T09:09:16,1989,31.6,31,0,27
2026-06-11T09:10:16,2007,31.6,32,0,25
2026-06-11T09:11:17,1978,31.3,32,0,27
2026-06-11T09:12:18,1989,31.1,32,0,25
2026-06-11T09:13:18,1965,30.7,32,0,25
2026-06-11T09:14:18,1980,30.3,32,0,25
2026-06-11T09:15:19,1946,30,32,0,25
2026-06-11T09:16:19,1914,29.6,33,0,25
2026-06-11T09:18:21,1926,29.1,33,0,25
2026-06-11T09:19:21,1895,28.7,33,0,25
2026-06-11T09:20:21,1937,28.6,33,0,25
2026-06-11T09:21:22,1937,28.3,33,0,25
2026-06-11T09:22:22,1937,28.2,34,0,27
2026-06-11T09:23:23,1902,28,34,0,25
2026-06-11T09:24:23,1890,27.8,34,0,25
2026-06-11T09:25:24,1939,27.7,34,0,27
2026-06-11T09:26:24,1960,27.6,35,0,25
2026-06-11T09:27:25,1934,27.5,34,0,25
2026-06-11T09:28:25,1933,27.3,35,0,25
2026-06-11T09:29:26,1917,27.2,35,0,25
2026-06-11T09:30:27,1908,27.2,35,0,25
2026-06-11T09:31:27,1897,27.1,35,0,25
2026-06-11T09:32:28,1879,27,35,0,25
2026-06-11T09:33:28,1879,26.8,35,0,25
2026-06-11T09:34:29,1900,26.7,35,0,25
2026-06-11T09:35:29,1900,26.7,36,0,25
2026-06-11T09:36:29,1885,26.6,36,0,25
2026-06-11T09:37:30,1894,26.6,36,0,25
2026-06-11T09:38:30,1885,26.5,36,0,27
2026-06-11T09:39:31,1894,26.5,36,0,25
2026-06-11T09:40:31,1885,26.5,36,0,27
2026-06-11T09:41:32,1885,26.3,36,0,27
2026-06-11T09:42:32,1875,26.3,36,0,27
2026-06-11T09:43:33,1875,26.3,36,0,25
2026-06-11T09:44:34,1902,26.2,37,0,27
2026-06-11T09:45:34,1852,26.2,37,0,27
2026-06-11T09:46:35,1852,26.2,37,0,25
2026-06-11T09:47:35,1834,26.1,37,0,27
2026-06-11T09:48:35,1843,26.1,37,0,25
2026-06-11T09:49:36,1852,26.1,37,0,25
2026-06-11T09:50:37,1843,26.1,37,0,25
2026-06-11T09:51:37,1834,26,37,0,25
2026-06-11T09:52:37,1825,26,37,0,25
2026-06-11T09:53:38,1825,26,38,0,25
2026-06-11T09:54:38,1811,25.8,37,0,27
2026-06-11T09:55:39,1897,26,37,0,25
2026-06-11T09:56:40,1819,25.8,38,0,25
2026-06-11T09:57:40,1875,25.8,38,0,25
2026-06-11T09:58:41,1855,25.8,38,0,25
2026-06-11T09:59:41,1865,25.8,38,0,25
2026-06-11T10:00:41,1847,25.7,38,0,27
2026-06-11T10:01:42,1847,25.7,38,0,25
2026-06-11T10:02:42,1847,25.7,38,0,25
2026-06-11T10:03:43,1855,25.7,38,0,25
2026-06-11T10:04:43,1855,25.7,39,0,25
2026-06-11T10:05:44,1842,25.7,38,0,25
2026-06-11T10:06:45,1855,25.7,39,0,25
2026-06-11T10:07:45,1833,25.6,38,0,25
2026-06-11T10:08:46,1837,25.6,39,0,25
2026-06-11T10:09:46,1833,25.6,39,0,25
2026-06-11T10:10:47,1833,25.5,39,0,25
2026-06-11T10:11:47,1833,25.6,39,0,25
2026-06-11T10:12:48,1833,25.3,41,0,25
2026-06-11T10:13:48,1833,25.5,38,0,25
2026-06-11T10:14:49,1847,25.5,39,0,25
2026-06-11T10:15:49,1833,25.5,39,0,25
2026-06-11T10:16:50,1833,25.3,39,0,25
2026-06-11T10:17:50,1824,25.3,39,0,25
2026-06-11T10:18:51,1824,25.3,40,0,25
2026-06-11T10:19:51,1811,25.3,39,0,25
2026-06-11T10:20:52,1860,25.5,39,0,25
2026-06-11T10:21:52,1866,25.3,39,0,25
2026-06-11T10:22:53,1897,25.5,39,0,25
2026-06-11T10:23:53,1914,25.5,40,0,25
2026-06-11T10:24:54,1915,25.5,40,0,25
2026-06-11T10:25:54,1915,25.5,40,0,25
2026-06-11T10:26:55,1935,25.5,40,0,25
2026-06-11T10:27:55,1946,25.5,40,0,25
2026-06-11T10:28:56,1956,25.5,39,0,25
2026-06-11T10:29:56,1971,25.5,40,0,27
2026-06-11T10:30:57,1956,25.5,40,0,25
2026-06-11T10:31:57,1976,25.5,39,0,25
2026-06-11T10:33:58,1965,25.5,40,0,25
2026-06-11T10:34:59,1976,25.5,40,0,25
2026-06-11T10:35:59,1965,25.5,40,0,25
2026-06-11T10:37:00,1956,25.5,40,0,25
2026-06-11T10:38:00,1956,25.5,39,0,25
2026-06-11T10:39:01,1971,25.5,40,0,25
2026-06-11T10:40:01,1935,25.3,40,0,25
2026-06-11T10:41:02,1935,25.5,39,0,25
2026-06-11T10:42:02,1950,25.3,39,0,25
2026-06-11T10:43:03,1950,25.3,40,0,25
2026-06-11T10:44:03,1915,25.3,40,0,25
2026-06-11T10:45:04,1915,25.3,40,0,25
2026-06-11T10:46:04,1915,25.3,40,0,25
2026-06-11T10:47:05,1928,25.3,40,0,25
2026-06-11T10:48:05,1928,25.3,40,0,25
2026-06-11T10:49:06,1928,25.3,40,0,25
2026-06-11T10:50:06,1928,25.3,40,0,25
2026-06-11T10:51:07,1928,25.3,40,0,25
2026-06-11T10:52:07,1915,25.2,40,0,25
2026-06-11T10:53:08,1928,25.3,40,0,25
2026-06-11T10:54:08,1928,25.3,40,0,25
2026-06-11T10:55:09,1928,25.2,40,0,25
2026-06-11T10:56:09,1928,25.2,40,0,25
2026-06-11T10:57:10,1909,25.2,40,0,25
2026-06-11T11:00:11,1899,25.2,39,0,25
2026-06-11T11:01:12,1914,25.3,40,0,25
2026-06-11T11:03:13,1899,25.2,39,0,25
2026-06-11T11:04:13,1903,25.2,40,0,25
2026-06-11T11:05:14,1899,25.2,40,0,25
2026-06-11T11:06:14,1899,25.2,40,0,25
2026-06-11T11:07:15,1899,25.2,39,0,25
2026-06-11T11:08:15,1914,25.2,39,0,25
2026-06-11T11:09:16,1914,25.2,39,0,25
2026-06-11T11:10:16,1914,25.2,39,0,25
2026-06-11T11:12:17,1922,25.2,40,0,25
2026-06-11T11:13:18,1915,25.2,40,0,25
2026-06-11T11:14:18,1915,25.2,40,0,25
2026-06-11T11:15:19,1928,25.2,39,0,25
2026-06-11T11:16:19,1940,25.2,40,0,25
2026-06-11T11:17:20,1928,25.2,40,0,25
2026-06-11T11:18:20,1915,25.2,40,0,25
2026-06-11T11:19:21,1915,25.1,40,0,25
2026-06-11T11:20:21,1909,25.2,39,0,25
2026-06-11T11:21:22,1909,25.2,40,0,22
2026-06-11T11:22:22,1899,25.2,39,0,25
2026-06-11T11:23:23,1922,25.2,39,0,25
2026-06-11T11:24:23,1922,25.2,39,0,25
2026-06-11T11:26:24,1914,25.2,39,0,25
2026-06-11T11:27:25,1903,25.2,40,0,25
2026-06-11T11:28:26,1899,25.2,40,0,25
2026-06-11T11:29:26,1899,25.2,40,0,25
2026-06-11T11:30:26,1899,25.2,40,0,25
2026-06-11T11:31:27,1889,25.2,40,0,25
2026-06-11T11:32:27,1889,25.2,40,0,25
2026-06-11T11:33:28,1889,25.1,40,0,25
2026-06-11T11:34:28,1889,25.1,40,0,25
2026-06-11T11:35:29,1871,25.1,39,0,25
2026-06-11T11:36:29,1897,25.1,39,0,25
2026-06-11T11:37:30,1903,25.1,39,0,25
2026-06-11T11:38:30,1922,25.2,39,0,25
2026-06-11T11:39:31,1930,25.2,40,0,25
2026-06-11T11:40:31,1928,25.2,40,0,25
2026-06-11T11:41:32,1946,25.2,39,0,25
2026-06-11T11:43:33,1980,25.3,39,0,25
2026-06-11T11:44:33,2001,25.3,39,0,25
2026-06-11T11:45:34,1980,25.3,39,0,25
2026-06-11T11:46:34,2001,25.3,40,0,25
2026-06-11T11:47:35,2004,25.3,39,0,25
2026-06-11T11:48:35,2028,25.5,40,0,25
2026-06-11T11:49:36,2025,25.5,40,0,25
2026-06-11T11:50:36,2025,25.3,40,0,25
2026-06-11T11:51:37,2025,25.5,40,0,25
2026-06-11T11:52:37,2035,25.5,40,0,25
2026-06-11T11:53:38,2054,25.5,40,0,25
2026-06-11T11:54:38,2054,25.5,40,0,25
2026-06-11T11:55:39,2054,25.5,39,0,25
2026-06-11T11:56:39,2070,25.5,39,0,25
2026-06-11T11:57:40,2070,25.5,40,0,25
2026-06-11T11:58:40,2035,25.5,39,0,25
2026-06-11T11:59:41,2070,25.5,39,0,25
2026-06-11T12:00:41,2080,25.3,40,0,25
2026-06-11T12:01:42,2066,25.5,39,0,25
2026-06-11T12:02:42,2060,25.5,40,0,25
2026-06-11T12:03:43,2054,25.5,40,0,25
2026-06-11T12:04:43,2054,25.5,40,0,25
2026-06-11T12:05:44,2035,25.3,40,0,25
2026-06-11T12:06:44,2054,25.5,39,0,25
2026-06-11T12:07:45,2080,25.5,40,0,25
2026-06-11T12:08:45,2054,25.5,40,0,25
2026-06-11T12:09:46,2054,25.5,40,0,25
2026-06-11T12:10:46,2066,25.5,40,0,25
2026-06-11T12:11:47,2066,25.5,39,0,25
2026-06-11T12:12:48,2080,25.5,39,0,25
2026-06-11T12:13:48,2080,25.5,39,0,25
2026-06-11T12:14:48,2088,25.5,39,0,25
2026-06-11T12:15:49,2080,25.3,39,0,25
2026-06-11T12:16:49,2080,25.3,39,0,25
2026-06-11T12:17:50,2060,25.5,39,0,25
2026-06-11T12:18:50,2048,25.5,39,0,25
2026-06-11T12:19:51,2048,25.3,38,0,25
2026-06-11T12:20:51,2074,25.5,38,0,25
2026-06-11T12:21:52,2074,25.5,38,0,25
2026-06-11T12:22:53,2066,25.5,39,0,25
2026-06-11T12:23:53,2048,25.5,39,0,25
2026-06-11T12:24:53,2048,25.5,39,0,25
2026-06-11T12:25:54,2028,25.5,39,0,25
2026-06-11T12:26:55,2028,25.3,38,0,25
2026-06-11T12:27:55,2028,25.5,38,0,22
2026-06-11T12:28:56,2052,25.5,39,0,25
2026-06-11T12:29:56,2028,25.5,39,0,25
2026-06-11T12:30:57,2028,25.5,38,0,25
2026-06-11T12:31:57,2052,25.5,38,0,25
2026-06-11T12:32:57,2052,25.5,38,0,25
2026-06-11T12:33:58,2052,25.5,38,0,25
2026-06-11T12:35:59,2052,25.5,38,0,25
2026-06-11T12:37:00,2052,25.5,38,0,25
2026-06-11T12:38:00,2066,25.5,39,0,25
2026-06-11T12:39:00,2048,25.5,38,0,25
2026-06-11T12:40:01,2074,25.5,38,0,25
2026-06-11T12:41:01,2074,25.5,38,0,25
2026-06-11T12:42:02,2084,25.5,38,0,25
2026-06-11T12:43:02,2094,25.6,38,0,25
2026-06-11T12:44:03,2094,25.6,38,0,25
2026-06-11T12:45:04,2084,25.6,38,0,25
2026-06-11T12:46:04,2094,25.6,38,0,25
2026-06-11T12:47:04,2094,25.6,38,0,25
2026-06-11T12:48:05,2094,25.6,38,0,25
2026-06-11T12:49:05,2094,25.6,38,0,25
2026-06-11T12:50:06,2094,25.6,38,0,25
2026-06-11T12:51:07,2084,25.6,38,0,25
2026-06-11T12:52:07,2084,25.6,39,0,25
2026-06-11T12:53:07,2060,25.7,38,0,25
2026-06-11T12:54:08,2094,25.6,39,0,25
2026-06-11T12:55:08,2132,25.7,38,0,25
2026-06-11T12:56:09,2094,25.6,38,0,25
2026-06-11T12:57:10,2094,25.7,39,0,25
2026-06-11T12:58:10,2098,25.7,38,0,25
2026-06-11T12:59:10,2138,25.7,38,0,25
2026-06-11T13:00:11,2116,25.7,39,0,22
2026-06-11T13:01:12,2098,25.7,38,0,25
2026-06-11T13:02:12,2116,25.7,38,0,25
2026-06-11T13:03:12,2116,25.7,38,0,25
2026-06-11T13:04:13,2116,25.7,38,0,25
2026-06-11T13:05:13,2104,25.7,38,0,22
2026-06-11T13:06:14,2116,25.7,38,0,25
2026-06-11T13:07:15,2126,25.7,38,0,25
2026-06-11T13:08:15,2126,25.8,38,0,25
2026-06-11T13:09:16,2138,25.8,39,0,25
2026-06-11T13:10:16,2120,25.8,39,0,25
2026-06-11T13:11:16,2144,25.8,39,0,22
2026-06-11T13:12:17,2110,25.8,39,0,25
2026-06-11T13:13:18,2120,25.8,38,0,25
2026-06-11T13:14:18,2150,25.8,38,0,25
2026-06-11T13:15:18,2162,25.8,38,0,25
2026-06-11T13:16:19,2162,25.8,38,0,25
2026-06-11T13:17:19,2172,25.8,38,0,25
2026-06-11T13:18:20,2184,26,38,0,25
2026-06-11T13:19:21,2102,26,38,0,25
2026-06-11T13:20:21,2102,26.1,39,0,25
2026-06-11T13:21:21,2098,26,39,0,25
2026-06-11T13:22:22,2098,26,39,0,25
2026-06-11T13:23:22,2120,26.1,38,0,22
2026-06-11T13:24:23,2136,26.1,39,0,25
2026-06-11T13:25:24,2120,26.1,38,0,25
2026-06-11T13:26:24,2136,26.1,38,0,25
2026-06-11T13:27:25,2150,26.1,38,0,25
2026-06-11T13:28:25,2162,26.1,38,0,25
2026-06-11T13:29:26,2162,26.1,38,0,25
2026-06-11T13:30:26,2150,26.1,38,0,25
2026-06-11T13:31:27,2162,26.1,38,0,25
2026-06-11T13:32:27,2172,26.2,38,0,25
2026-06-11T13:33:28,2162,26.1,38,0,25
2026-06-11T13:34:28,2136,26.2,38,0,25
2026-06-11T13:35:29,2136,26.2,39,0,25
2026-06-11T13:36:29,2120,26.1,38,0,25
2026-06-11T13:37:30,2136,26.1,38,0,22
2026-06-11T13:38:30,2136,26.1,38,0,22
2026-06-11T13:39:31,2150,26.1,38,0,22
2026-06-11T13:40:31,2150,26.2,38,0,22
2026-06-11T13:41:32,2150,26.2,38,0,25
2026-06-11T13:42:32,2150,26.2,38,0,25
2026-06-11T13:43:33,2162,26.2,37,0,22
2026-06-11T13:44:33,2166,26.2,38,0,25
2026-06-11T13:45:34,2150,26.2,38,0,25
2026-06-11T13:46:34,2136,26.2,38,0,25
2026-06-11T13:47:35,2150,26.2,38,0,25
2026-06-11T13:48:35,2136,26.2,38,0,25
2026-06-11T13:49:36,2136,26.2,38,0,25
2026-06-11T13:50:36,2124,26.2,38,0,25
2026-06-11T13:51:37,2124,26.2,38,0,25
2026-06-11T13:52:37,2124,26.2,39,0,22
2026-06-11T13:53:38,2120,26.1,38,0,25
2026-06-11T13:54:38,2136,26.2,38,0,25
2026-06-11T13:55:39,2150,26.1,38,0,25
2026-06-11T13:56:39,2150,26.2,38,0,25
2026-06-11T13:57:40,2124,26.2,38,0,25
2026-06-11T13:58:40,2124,26.2,38,0,25
2026-06-11T13:59:41,2124,26.2,38,0,25
2026-06-11T14:00:41,2124,26.2,37,0,25
2026-06-11T14:01:42,2154,26.2,38,0,25
2026-06-11T14:02:42,2136,26.2,37,0,25
2026-06-11T14:03:43,2142,26.2,38,0,25
2026-06-11T14:04:43,2136,26.2,38,0,25
2026-06-11T14:05:44,2136,26.2,37,0,25
2026-06-11T14:06:44,2142,26.2,37,0,25
2026-06-11T14:08:45,2142,26.2,37,0,25
2026-06-11T14:09:46,2154,26.2,37,0,25
2026-06-11T14:11:47,2166,26.2,38,0,25
2026-06-11T14:12:48,2136,26.2,37,0,25
2026-06-11T14:13:48,2154,26.2,38,0,25
2026-06-11T14:14:48,2136,26.2,38,0,22
2026-06-11T14:16:49,2124,26.2,38,0,22
2026-06-11T14:17:50,2124,26.2,38,0,25
2026-06-11T14:18:50,2124,26.2,38,0,25
2026-06-11T14:19:51,2114,26.2,38,0,25
2026-06-11T14:20:51,2102,26.2,37,0,25
2026-06-11T14:21:52,2118,26.2,37,0,25
2026-06-11T14:22:52,2130,26.2,37,0,25
2026-06-11T14:23:53,2154,26.2,37,0,25
2026-06-11T14:24:53,2154,26.2,37,0,25
2026-06-11T14:25:54,2142,26.2,37,0,22
2026-06-11T14:26:54,2130,26.3,37,0,25
2026-06-11T14:27:55,2142,26.2,37,0,25
2026-06-11T14:28:55,2166,26.2,37,0,22
2026-06-11T14:29:56,2180,26.3,37,0,25
2026-06-11T14:30:56,2189,26.3,37,0,22
2026-06-11T14:31:57,2203,26.3,37,0,22
2026-06-11T14:32:57,2203,26.2,37,0,25
2026-06-11T14:33:58,2203,26.3,37,0,22
2026-06-11T14:34:58,2214,26.3,37,0,25
2026-06-11T14:35:59,2203,26.3,37,0,25
2026-06-11T14:36:59,2214,26.3,37,0,25
2026-06-11T14:38:00,2214,26.3,37,0,25
2026-06-11T14:39:00,2214,26.3,37,0,25
2026-06-11T14:40:01,2214,26.3,37,0,22
2026-06-11T14:41:01,2203,26.3,37,0,22
2026-06-11T14:42:02,2203,26.3,37,0,22
2026-06-11T14:43:02,2203,26.3,37,0,22
2026-06-11T14:44:03,2214,26.3,37,0,25
2026-06-11T14:45:03,2189,26.3,37,0,25
2026-06-11T14:46:04,2189,26.3,37,0,22
2026-06-11T14:47:04,2189,26.3,37,0,25
2026-06-11T14:48:05,2189,26.3,37,0,22
2026-06-11T14:49:05,2189,26.3,37,0,22
2026-06-11T14:50:06,2189,26.3,37,0,25
2026-06-11T14:51:06,2189,26.3,37,0,25
2026-06-11T14:52:07,2180,26.3,37,0,22
2026-06-11T14:53:07,2203,26.3,37,0,22
2026-06-11T14:54:08,2203,26.3,38,0,22
2026-06-11T14:55:08,2197,26.3,37,0,22
2026-06-11T14:56:09,2214,26.3,37,0,25
2026-06-11T14:57:09,2214,26.5,37,0,22
2026-06-11T14:58:10,2240,26.5,37,0,22
2026-06-11T14:59:10,2255,26.5,38,0,25
2026-06-11T15:00:11,2236,26.5,37,0,22
2026-06-11T15:01:11,2255,26.5,38,0,25
2026-06-11T15:02:12,2220,26.6,37,0,25
2026-06-11T15:03:12,2228,26.6,38,0,22
2026-06-11T15:04:13,2236,26.6,38,0,25
2026-06-11T15:05:13,2249,26.6,37,0,25
2026-06-11T15:06:14,2265,26.6,37,0,22
2026-06-11T15:07:14,2279,26.6,37,0,25
2026-06-11T15:08:15,2265,26.7,37,0,22
2026-06-11T15:09:15,2292,26.7,37,0,22
2026-06-11T15:10:16,2279,26.7,37,0,22
2026-06-11T15:11:17,2265,26.7,37,0,22
2026-06-11T15:12:17,2265,26.7,37,0,25
2026-06-11T15:13:17,2265,26.7,37,0,22
2026-06-11T15:14:18,2279,26.7,37,0,22
2026-06-11T15:15:18,2265,26.7,37,0,22
2026-06-11T15:16:19,2279,26.7,37,0,22
2026-06-11T15:17:20,2292,26.7,37,0,25
2026-06-11T15:18:20,2265,26.7,37,0,25
2026-06-11T15:20:21,2255,26.7,37,0,22
2026-06-11T15:21:22,2265,26.7,37,0,25
2026-06-11T15:22:22,2255,26.7,37,0,22
2026-06-11T15:23:22,2265,26.7,37,0,22
2026-06-11T15:24:23,2255,26.7,37,0,22
2026-06-11T15:25:23,2279,26.7,37,0,22
2026-06-11T15:26:24,2279,26.7,37,0,22
2026-06-11T15:27:24,2279,26.7,37,0,25
2026-06-11T15:28:25,2292,26.7,37,0,22
2026-06-11T15:29:26,2279,26.7,37,0,22
2026-06-11T15:30:26,2292,26.8,37,0,25
2026-06-11T15:31:27,2334,26.7,37,0,22
2026-06-11T15:32:27,2292,26.8,36,0,22
2026-06-11T15:33:28,2324,26.7,37,0,22
2026-06-11T15:34:28,2292,26.8,37,0,22
2026-06-11T15:35:28,2318,26.8,36,0,25
2026-06-11T15:36:29,2265,26.8,37,0,22
2026-06-11T15:37:30,2242,26.8,37,0,25
2026-06-11T15:38:30,2270,26.8,37,0,22
2026-06-11T15:39:30,2255,26.8,37,0,22
2026-06-11T15:40:31,2259,26.8,37,0,22
2026-06-11T15:41:32,2281,26.8,37,0,25
2026-06-11T15:42:32,2285,26.8,37,0,25
2026-06-11T15:44:33,2281,26.8,37,0,22
2026-06-11T15:45:33,2285,26.8,37,0,25
2026-06-11T15:47:34,2249,26.8,36,0,22
2026-06-11T15:48:35,2226,26.8,37,0,22
2026-06-11T15:49:36,2226,26.8,37,0,22
2026-06-11T15:50:36,2259,26.8,37,0,22
2026-06-11T15:51:37,2255,26.8,37,0,22
2026-06-11T15:52:37,2249,26.8,37,0,22
2026-06-11T15:53:37,2263,26.8,37,0,22
2026-06-11T15:54:38,2259,26.8,36,0,22
2026-06-11T15:55:38,2278,26.8,36,0,22
2026-06-11T15:56:39,2287,27,37,0,22
2026-06-11T15:57:39,2222,26.8,36,0,22
2026-06-11T15:58:40,2281,26.8,37,0,22
2026-06-11T15:59:40,2276,27,36,0,22
2026-06-11T16:00:41,2222,27,37,0,22
2026-06-11T16:01:41,2189,27,36,0,22
2026-06-11T16:03:43,2205,27,37,0,22
2026-06-11T16:04:43,2195,27,36,0,22
2026-06-11T16:05:43,2218,27,36,0,25
2026-06-11T16:06:44,2207,26.8,37,0,22
2026-06-11T16:07:44,2259,27,37,0,22
2026-06-11T16:08:45,2189,26.8,37,0,22
2026-06-11T16:09:46,2259,26.8,37,0,22
2026-06-11T16:10:46,2276,26.8,37,0,22
2026-06-11T16:11:47,2281,27,37,0,22
2026-06-11T16:13:48,2210,27,37,0,22
2026-06-11T16:14:48,2205,27,37,0,22
2026-06-11T16:15:49,2210,27.1,37,0,22
2026-06-11T16:16:49,2210,26.8,37,0,22
2026-06-11T16:17:50,2296,27.3,37,0,22
2026-06-11T16:18:50,2210,27,37,0,22
2026-06-11T16:19:50,2245,27,37,0,22
2026-06-11T16:20:51,2232,27.1,37,0,22
2026-06-11T16:21:51,2212,27,37,0,22
2026-06-11T16:22:52,2222,27,37,0,22
2026-06-11T16:23:53,2251,27,37,0,22
2026-06-11T16:25:53,2222,27.1,37,0,22
2026-06-11T16:26:54,2222,27.1,37,1,22
2026-06-11T16:28:55,2224,27.1,37,1,22
2026-06-11T16:29:56,2224,27.1,37,1,22
1 time co2 temperature humidity windows battery
2 2026-06-11T08:45:04 1766 27.7 38 0 25
3 2026-06-11T08:46:04 1815 28.1 37 0 27
4 2026-06-11T08:48:05 1836 28.7 37 0 25
5 2026-06-11T08:49:06 1844 29 36 0 25
6 2026-06-11T08:50:06 1830 29.2 36 0 25
7 2026-06-11T08:51:07 1837 29.5 35 0 27
8 2026-06-11T08:53:08 1903 29.8 35 0 27
9 2026-06-11T08:54:08 1903 30 34 0 25
10 2026-06-11T08:55:09 1868 30.2 34 0 27
11 2026-06-11T08:56:10 1900 30.3 34 0 25
12 2026-06-11T08:57:10 1914 30.5 34 0 27
13 2026-06-11T08:58:10 1925 30.6 33 0 27
14 2026-06-11T08:59:11 1951 30.7 33 0 25
15 2026-06-11T09:00:11 1975 30.8 33 0 27
16 2026-06-11T09:01:12 1975 31 32 0 27
17 2026-06-11T09:02:13 1942 31 33 0 25
18 2026-06-11T09:03:13 1938 31.1 32 0 27
19 2026-06-11T09:04:14 1965 31.2 32 0 27
20 2026-06-11T09:05:14 1989 31.3 32 0 25
21 2026-06-11T09:07:15 1978 31.5 32 0 27
22 2026-06-11T09:08:15 1989 31.6 32 0 27
23 2026-06-11T09:09:16 1989 31.6 31 0 27
24 2026-06-11T09:10:16 2007 31.6 32 0 25
25 2026-06-11T09:11:17 1978 31.3 32 0 27
26 2026-06-11T09:12:18 1989 31.1 32 0 25
27 2026-06-11T09:13:18 1965 30.7 32 0 25
28 2026-06-11T09:14:18 1980 30.3 32 0 25
29 2026-06-11T09:15:19 1946 30 32 0 25
30 2026-06-11T09:16:19 1914 29.6 33 0 25
31 2026-06-11T09:18:21 1926 29.1 33 0 25
32 2026-06-11T09:19:21 1895 28.7 33 0 25
33 2026-06-11T09:20:21 1937 28.6 33 0 25
34 2026-06-11T09:21:22 1937 28.3 33 0 25
35 2026-06-11T09:22:22 1937 28.2 34 0 27
36 2026-06-11T09:23:23 1902 28 34 0 25
37 2026-06-11T09:24:23 1890 27.8 34 0 25
38 2026-06-11T09:25:24 1939 27.7 34 0 27
39 2026-06-11T09:26:24 1960 27.6 35 0 25
40 2026-06-11T09:27:25 1934 27.5 34 0 25
41 2026-06-11T09:28:25 1933 27.3 35 0 25
42 2026-06-11T09:29:26 1917 27.2 35 0 25
43 2026-06-11T09:30:27 1908 27.2 35 0 25
44 2026-06-11T09:31:27 1897 27.1 35 0 25
45 2026-06-11T09:32:28 1879 27 35 0 25
46 2026-06-11T09:33:28 1879 26.8 35 0 25
47 2026-06-11T09:34:29 1900 26.7 35 0 25
48 2026-06-11T09:35:29 1900 26.7 36 0 25
49 2026-06-11T09:36:29 1885 26.6 36 0 25
50 2026-06-11T09:37:30 1894 26.6 36 0 25
51 2026-06-11T09:38:30 1885 26.5 36 0 27
52 2026-06-11T09:39:31 1894 26.5 36 0 25
53 2026-06-11T09:40:31 1885 26.5 36 0 27
54 2026-06-11T09:41:32 1885 26.3 36 0 27
55 2026-06-11T09:42:32 1875 26.3 36 0 27
56 2026-06-11T09:43:33 1875 26.3 36 0 25
57 2026-06-11T09:44:34 1902 26.2 37 0 27
58 2026-06-11T09:45:34 1852 26.2 37 0 27
59 2026-06-11T09:46:35 1852 26.2 37 0 25
60 2026-06-11T09:47:35 1834 26.1 37 0 27
61 2026-06-11T09:48:35 1843 26.1 37 0 25
62 2026-06-11T09:49:36 1852 26.1 37 0 25
63 2026-06-11T09:50:37 1843 26.1 37 0 25
64 2026-06-11T09:51:37 1834 26 37 0 25
65 2026-06-11T09:52:37 1825 26 37 0 25
66 2026-06-11T09:53:38 1825 26 38 0 25
67 2026-06-11T09:54:38 1811 25.8 37 0 27
68 2026-06-11T09:55:39 1897 26 37 0 25
69 2026-06-11T09:56:40 1819 25.8 38 0 25
70 2026-06-11T09:57:40 1875 25.8 38 0 25
71 2026-06-11T09:58:41 1855 25.8 38 0 25
72 2026-06-11T09:59:41 1865 25.8 38 0 25
73 2026-06-11T10:00:41 1847 25.7 38 0 27
74 2026-06-11T10:01:42 1847 25.7 38 0 25
75 2026-06-11T10:02:42 1847 25.7 38 0 25
76 2026-06-11T10:03:43 1855 25.7 38 0 25
77 2026-06-11T10:04:43 1855 25.7 39 0 25
78 2026-06-11T10:05:44 1842 25.7 38 0 25
79 2026-06-11T10:06:45 1855 25.7 39 0 25
80 2026-06-11T10:07:45 1833 25.6 38 0 25
81 2026-06-11T10:08:46 1837 25.6 39 0 25
82 2026-06-11T10:09:46 1833 25.6 39 0 25
83 2026-06-11T10:10:47 1833 25.5 39 0 25
84 2026-06-11T10:11:47 1833 25.6 39 0 25
85 2026-06-11T10:12:48 1833 25.3 41 0 25
86 2026-06-11T10:13:48 1833 25.5 38 0 25
87 2026-06-11T10:14:49 1847 25.5 39 0 25
88 2026-06-11T10:15:49 1833 25.5 39 0 25
89 2026-06-11T10:16:50 1833 25.3 39 0 25
90 2026-06-11T10:17:50 1824 25.3 39 0 25
91 2026-06-11T10:18:51 1824 25.3 40 0 25
92 2026-06-11T10:19:51 1811 25.3 39 0 25
93 2026-06-11T10:20:52 1860 25.5 39 0 25
94 2026-06-11T10:21:52 1866 25.3 39 0 25
95 2026-06-11T10:22:53 1897 25.5 39 0 25
96 2026-06-11T10:23:53 1914 25.5 40 0 25
97 2026-06-11T10:24:54 1915 25.5 40 0 25
98 2026-06-11T10:25:54 1915 25.5 40 0 25
99 2026-06-11T10:26:55 1935 25.5 40 0 25
100 2026-06-11T10:27:55 1946 25.5 40 0 25
101 2026-06-11T10:28:56 1956 25.5 39 0 25
102 2026-06-11T10:29:56 1971 25.5 40 0 27
103 2026-06-11T10:30:57 1956 25.5 40 0 25
104 2026-06-11T10:31:57 1976 25.5 39 0 25
105 2026-06-11T10:33:58 1965 25.5 40 0 25
106 2026-06-11T10:34:59 1976 25.5 40 0 25
107 2026-06-11T10:35:59 1965 25.5 40 0 25
108 2026-06-11T10:37:00 1956 25.5 40 0 25
109 2026-06-11T10:38:00 1956 25.5 39 0 25
110 2026-06-11T10:39:01 1971 25.5 40 0 25
111 2026-06-11T10:40:01 1935 25.3 40 0 25
112 2026-06-11T10:41:02 1935 25.5 39 0 25
113 2026-06-11T10:42:02 1950 25.3 39 0 25
114 2026-06-11T10:43:03 1950 25.3 40 0 25
115 2026-06-11T10:44:03 1915 25.3 40 0 25
116 2026-06-11T10:45:04 1915 25.3 40 0 25
117 2026-06-11T10:46:04 1915 25.3 40 0 25
118 2026-06-11T10:47:05 1928 25.3 40 0 25
119 2026-06-11T10:48:05 1928 25.3 40 0 25
120 2026-06-11T10:49:06 1928 25.3 40 0 25
121 2026-06-11T10:50:06 1928 25.3 40 0 25
122 2026-06-11T10:51:07 1928 25.3 40 0 25
123 2026-06-11T10:52:07 1915 25.2 40 0 25
124 2026-06-11T10:53:08 1928 25.3 40 0 25
125 2026-06-11T10:54:08 1928 25.3 40 0 25
126 2026-06-11T10:55:09 1928 25.2 40 0 25
127 2026-06-11T10:56:09 1928 25.2 40 0 25
128 2026-06-11T10:57:10 1909 25.2 40 0 25
129 2026-06-11T11:00:11 1899 25.2 39 0 25
130 2026-06-11T11:01:12 1914 25.3 40 0 25
131 2026-06-11T11:03:13 1899 25.2 39 0 25
132 2026-06-11T11:04:13 1903 25.2 40 0 25
133 2026-06-11T11:05:14 1899 25.2 40 0 25
134 2026-06-11T11:06:14 1899 25.2 40 0 25
135 2026-06-11T11:07:15 1899 25.2 39 0 25
136 2026-06-11T11:08:15 1914 25.2 39 0 25
137 2026-06-11T11:09:16 1914 25.2 39 0 25
138 2026-06-11T11:10:16 1914 25.2 39 0 25
139 2026-06-11T11:12:17 1922 25.2 40 0 25
140 2026-06-11T11:13:18 1915 25.2 40 0 25
141 2026-06-11T11:14:18 1915 25.2 40 0 25
142 2026-06-11T11:15:19 1928 25.2 39 0 25
143 2026-06-11T11:16:19 1940 25.2 40 0 25
144 2026-06-11T11:17:20 1928 25.2 40 0 25
145 2026-06-11T11:18:20 1915 25.2 40 0 25
146 2026-06-11T11:19:21 1915 25.1 40 0 25
147 2026-06-11T11:20:21 1909 25.2 39 0 25
148 2026-06-11T11:21:22 1909 25.2 40 0 22
149 2026-06-11T11:22:22 1899 25.2 39 0 25
150 2026-06-11T11:23:23 1922 25.2 39 0 25
151 2026-06-11T11:24:23 1922 25.2 39 0 25
152 2026-06-11T11:26:24 1914 25.2 39 0 25
153 2026-06-11T11:27:25 1903 25.2 40 0 25
154 2026-06-11T11:28:26 1899 25.2 40 0 25
155 2026-06-11T11:29:26 1899 25.2 40 0 25
156 2026-06-11T11:30:26 1899 25.2 40 0 25
157 2026-06-11T11:31:27 1889 25.2 40 0 25
158 2026-06-11T11:32:27 1889 25.2 40 0 25
159 2026-06-11T11:33:28 1889 25.1 40 0 25
160 2026-06-11T11:34:28 1889 25.1 40 0 25
161 2026-06-11T11:35:29 1871 25.1 39 0 25
162 2026-06-11T11:36:29 1897 25.1 39 0 25
163 2026-06-11T11:37:30 1903 25.1 39 0 25
164 2026-06-11T11:38:30 1922 25.2 39 0 25
165 2026-06-11T11:39:31 1930 25.2 40 0 25
166 2026-06-11T11:40:31 1928 25.2 40 0 25
167 2026-06-11T11:41:32 1946 25.2 39 0 25
168 2026-06-11T11:43:33 1980 25.3 39 0 25
169 2026-06-11T11:44:33 2001 25.3 39 0 25
170 2026-06-11T11:45:34 1980 25.3 39 0 25
171 2026-06-11T11:46:34 2001 25.3 40 0 25
172 2026-06-11T11:47:35 2004 25.3 39 0 25
173 2026-06-11T11:48:35 2028 25.5 40 0 25
174 2026-06-11T11:49:36 2025 25.5 40 0 25
175 2026-06-11T11:50:36 2025 25.3 40 0 25
176 2026-06-11T11:51:37 2025 25.5 40 0 25
177 2026-06-11T11:52:37 2035 25.5 40 0 25
178 2026-06-11T11:53:38 2054 25.5 40 0 25
179 2026-06-11T11:54:38 2054 25.5 40 0 25
180 2026-06-11T11:55:39 2054 25.5 39 0 25
181 2026-06-11T11:56:39 2070 25.5 39 0 25
182 2026-06-11T11:57:40 2070 25.5 40 0 25
183 2026-06-11T11:58:40 2035 25.5 39 0 25
184 2026-06-11T11:59:41 2070 25.5 39 0 25
185 2026-06-11T12:00:41 2080 25.3 40 0 25
186 2026-06-11T12:01:42 2066 25.5 39 0 25
187 2026-06-11T12:02:42 2060 25.5 40 0 25
188 2026-06-11T12:03:43 2054 25.5 40 0 25
189 2026-06-11T12:04:43 2054 25.5 40 0 25
190 2026-06-11T12:05:44 2035 25.3 40 0 25
191 2026-06-11T12:06:44 2054 25.5 39 0 25
192 2026-06-11T12:07:45 2080 25.5 40 0 25
193 2026-06-11T12:08:45 2054 25.5 40 0 25
194 2026-06-11T12:09:46 2054 25.5 40 0 25
195 2026-06-11T12:10:46 2066 25.5 40 0 25
196 2026-06-11T12:11:47 2066 25.5 39 0 25
197 2026-06-11T12:12:48 2080 25.5 39 0 25
198 2026-06-11T12:13:48 2080 25.5 39 0 25
199 2026-06-11T12:14:48 2088 25.5 39 0 25
200 2026-06-11T12:15:49 2080 25.3 39 0 25
201 2026-06-11T12:16:49 2080 25.3 39 0 25
202 2026-06-11T12:17:50 2060 25.5 39 0 25
203 2026-06-11T12:18:50 2048 25.5 39 0 25
204 2026-06-11T12:19:51 2048 25.3 38 0 25
205 2026-06-11T12:20:51 2074 25.5 38 0 25
206 2026-06-11T12:21:52 2074 25.5 38 0 25
207 2026-06-11T12:22:53 2066 25.5 39 0 25
208 2026-06-11T12:23:53 2048 25.5 39 0 25
209 2026-06-11T12:24:53 2048 25.5 39 0 25
210 2026-06-11T12:25:54 2028 25.5 39 0 25
211 2026-06-11T12:26:55 2028 25.3 38 0 25
212 2026-06-11T12:27:55 2028 25.5 38 0 22
213 2026-06-11T12:28:56 2052 25.5 39 0 25
214 2026-06-11T12:29:56 2028 25.5 39 0 25
215 2026-06-11T12:30:57 2028 25.5 38 0 25
216 2026-06-11T12:31:57 2052 25.5 38 0 25
217 2026-06-11T12:32:57 2052 25.5 38 0 25
218 2026-06-11T12:33:58 2052 25.5 38 0 25
219 2026-06-11T12:35:59 2052 25.5 38 0 25
220 2026-06-11T12:37:00 2052 25.5 38 0 25
221 2026-06-11T12:38:00 2066 25.5 39 0 25
222 2026-06-11T12:39:00 2048 25.5 38 0 25
223 2026-06-11T12:40:01 2074 25.5 38 0 25
224 2026-06-11T12:41:01 2074 25.5 38 0 25
225 2026-06-11T12:42:02 2084 25.5 38 0 25
226 2026-06-11T12:43:02 2094 25.6 38 0 25
227 2026-06-11T12:44:03 2094 25.6 38 0 25
228 2026-06-11T12:45:04 2084 25.6 38 0 25
229 2026-06-11T12:46:04 2094 25.6 38 0 25
230 2026-06-11T12:47:04 2094 25.6 38 0 25
231 2026-06-11T12:48:05 2094 25.6 38 0 25
232 2026-06-11T12:49:05 2094 25.6 38 0 25
233 2026-06-11T12:50:06 2094 25.6 38 0 25
234 2026-06-11T12:51:07 2084 25.6 38 0 25
235 2026-06-11T12:52:07 2084 25.6 39 0 25
236 2026-06-11T12:53:07 2060 25.7 38 0 25
237 2026-06-11T12:54:08 2094 25.6 39 0 25
238 2026-06-11T12:55:08 2132 25.7 38 0 25
239 2026-06-11T12:56:09 2094 25.6 38 0 25
240 2026-06-11T12:57:10 2094 25.7 39 0 25
241 2026-06-11T12:58:10 2098 25.7 38 0 25
242 2026-06-11T12:59:10 2138 25.7 38 0 25
243 2026-06-11T13:00:11 2116 25.7 39 0 22
244 2026-06-11T13:01:12 2098 25.7 38 0 25
245 2026-06-11T13:02:12 2116 25.7 38 0 25
246 2026-06-11T13:03:12 2116 25.7 38 0 25
247 2026-06-11T13:04:13 2116 25.7 38 0 25
248 2026-06-11T13:05:13 2104 25.7 38 0 22
249 2026-06-11T13:06:14 2116 25.7 38 0 25
250 2026-06-11T13:07:15 2126 25.7 38 0 25
251 2026-06-11T13:08:15 2126 25.8 38 0 25
252 2026-06-11T13:09:16 2138 25.8 39 0 25
253 2026-06-11T13:10:16 2120 25.8 39 0 25
254 2026-06-11T13:11:16 2144 25.8 39 0 22
255 2026-06-11T13:12:17 2110 25.8 39 0 25
256 2026-06-11T13:13:18 2120 25.8 38 0 25
257 2026-06-11T13:14:18 2150 25.8 38 0 25
258 2026-06-11T13:15:18 2162 25.8 38 0 25
259 2026-06-11T13:16:19 2162 25.8 38 0 25
260 2026-06-11T13:17:19 2172 25.8 38 0 25
261 2026-06-11T13:18:20 2184 26 38 0 25
262 2026-06-11T13:19:21 2102 26 38 0 25
263 2026-06-11T13:20:21 2102 26.1 39 0 25
264 2026-06-11T13:21:21 2098 26 39 0 25
265 2026-06-11T13:22:22 2098 26 39 0 25
266 2026-06-11T13:23:22 2120 26.1 38 0 22
267 2026-06-11T13:24:23 2136 26.1 39 0 25
268 2026-06-11T13:25:24 2120 26.1 38 0 25
269 2026-06-11T13:26:24 2136 26.1 38 0 25
270 2026-06-11T13:27:25 2150 26.1 38 0 25
271 2026-06-11T13:28:25 2162 26.1 38 0 25
272 2026-06-11T13:29:26 2162 26.1 38 0 25
273 2026-06-11T13:30:26 2150 26.1 38 0 25
274 2026-06-11T13:31:27 2162 26.1 38 0 25
275 2026-06-11T13:32:27 2172 26.2 38 0 25
276 2026-06-11T13:33:28 2162 26.1 38 0 25
277 2026-06-11T13:34:28 2136 26.2 38 0 25
278 2026-06-11T13:35:29 2136 26.2 39 0 25
279 2026-06-11T13:36:29 2120 26.1 38 0 25
280 2026-06-11T13:37:30 2136 26.1 38 0 22
281 2026-06-11T13:38:30 2136 26.1 38 0 22
282 2026-06-11T13:39:31 2150 26.1 38 0 22
283 2026-06-11T13:40:31 2150 26.2 38 0 22
284 2026-06-11T13:41:32 2150 26.2 38 0 25
285 2026-06-11T13:42:32 2150 26.2 38 0 25
286 2026-06-11T13:43:33 2162 26.2 37 0 22
287 2026-06-11T13:44:33 2166 26.2 38 0 25
288 2026-06-11T13:45:34 2150 26.2 38 0 25
289 2026-06-11T13:46:34 2136 26.2 38 0 25
290 2026-06-11T13:47:35 2150 26.2 38 0 25
291 2026-06-11T13:48:35 2136 26.2 38 0 25
292 2026-06-11T13:49:36 2136 26.2 38 0 25
293 2026-06-11T13:50:36 2124 26.2 38 0 25
294 2026-06-11T13:51:37 2124 26.2 38 0 25
295 2026-06-11T13:52:37 2124 26.2 39 0 22
296 2026-06-11T13:53:38 2120 26.1 38 0 25
297 2026-06-11T13:54:38 2136 26.2 38 0 25
298 2026-06-11T13:55:39 2150 26.1 38 0 25
299 2026-06-11T13:56:39 2150 26.2 38 0 25
300 2026-06-11T13:57:40 2124 26.2 38 0 25
301 2026-06-11T13:58:40 2124 26.2 38 0 25
302 2026-06-11T13:59:41 2124 26.2 38 0 25
303 2026-06-11T14:00:41 2124 26.2 37 0 25
304 2026-06-11T14:01:42 2154 26.2 38 0 25
305 2026-06-11T14:02:42 2136 26.2 37 0 25
306 2026-06-11T14:03:43 2142 26.2 38 0 25
307 2026-06-11T14:04:43 2136 26.2 38 0 25
308 2026-06-11T14:05:44 2136 26.2 37 0 25
309 2026-06-11T14:06:44 2142 26.2 37 0 25
310 2026-06-11T14:08:45 2142 26.2 37 0 25
311 2026-06-11T14:09:46 2154 26.2 37 0 25
312 2026-06-11T14:11:47 2166 26.2 38 0 25
313 2026-06-11T14:12:48 2136 26.2 37 0 25
314 2026-06-11T14:13:48 2154 26.2 38 0 25
315 2026-06-11T14:14:48 2136 26.2 38 0 22
316 2026-06-11T14:16:49 2124 26.2 38 0 22
317 2026-06-11T14:17:50 2124 26.2 38 0 25
318 2026-06-11T14:18:50 2124 26.2 38 0 25
319 2026-06-11T14:19:51 2114 26.2 38 0 25
320 2026-06-11T14:20:51 2102 26.2 37 0 25
321 2026-06-11T14:21:52 2118 26.2 37 0 25
322 2026-06-11T14:22:52 2130 26.2 37 0 25
323 2026-06-11T14:23:53 2154 26.2 37 0 25
324 2026-06-11T14:24:53 2154 26.2 37 0 25
325 2026-06-11T14:25:54 2142 26.2 37 0 22
326 2026-06-11T14:26:54 2130 26.3 37 0 25
327 2026-06-11T14:27:55 2142 26.2 37 0 25
328 2026-06-11T14:28:55 2166 26.2 37 0 22
329 2026-06-11T14:29:56 2180 26.3 37 0 25
330 2026-06-11T14:30:56 2189 26.3 37 0 22
331 2026-06-11T14:31:57 2203 26.3 37 0 22
332 2026-06-11T14:32:57 2203 26.2 37 0 25
333 2026-06-11T14:33:58 2203 26.3 37 0 22
334 2026-06-11T14:34:58 2214 26.3 37 0 25
335 2026-06-11T14:35:59 2203 26.3 37 0 25
336 2026-06-11T14:36:59 2214 26.3 37 0 25
337 2026-06-11T14:38:00 2214 26.3 37 0 25
338 2026-06-11T14:39:00 2214 26.3 37 0 25
339 2026-06-11T14:40:01 2214 26.3 37 0 22
340 2026-06-11T14:41:01 2203 26.3 37 0 22
341 2026-06-11T14:42:02 2203 26.3 37 0 22
342 2026-06-11T14:43:02 2203 26.3 37 0 22
343 2026-06-11T14:44:03 2214 26.3 37 0 25
344 2026-06-11T14:45:03 2189 26.3 37 0 25
345 2026-06-11T14:46:04 2189 26.3 37 0 22
346 2026-06-11T14:47:04 2189 26.3 37 0 25
347 2026-06-11T14:48:05 2189 26.3 37 0 22
348 2026-06-11T14:49:05 2189 26.3 37 0 22
349 2026-06-11T14:50:06 2189 26.3 37 0 25
350 2026-06-11T14:51:06 2189 26.3 37 0 25
351 2026-06-11T14:52:07 2180 26.3 37 0 22
352 2026-06-11T14:53:07 2203 26.3 37 0 22
353 2026-06-11T14:54:08 2203 26.3 38 0 22
354 2026-06-11T14:55:08 2197 26.3 37 0 22
355 2026-06-11T14:56:09 2214 26.3 37 0 25
356 2026-06-11T14:57:09 2214 26.5 37 0 22
357 2026-06-11T14:58:10 2240 26.5 37 0 22
358 2026-06-11T14:59:10 2255 26.5 38 0 25
359 2026-06-11T15:00:11 2236 26.5 37 0 22
360 2026-06-11T15:01:11 2255 26.5 38 0 25
361 2026-06-11T15:02:12 2220 26.6 37 0 25
362 2026-06-11T15:03:12 2228 26.6 38 0 22
363 2026-06-11T15:04:13 2236 26.6 38 0 25
364 2026-06-11T15:05:13 2249 26.6 37 0 25
365 2026-06-11T15:06:14 2265 26.6 37 0 22
366 2026-06-11T15:07:14 2279 26.6 37 0 25
367 2026-06-11T15:08:15 2265 26.7 37 0 22
368 2026-06-11T15:09:15 2292 26.7 37 0 22
369 2026-06-11T15:10:16 2279 26.7 37 0 22
370 2026-06-11T15:11:17 2265 26.7 37 0 22
371 2026-06-11T15:12:17 2265 26.7 37 0 25
372 2026-06-11T15:13:17 2265 26.7 37 0 22
373 2026-06-11T15:14:18 2279 26.7 37 0 22
374 2026-06-11T15:15:18 2265 26.7 37 0 22
375 2026-06-11T15:16:19 2279 26.7 37 0 22
376 2026-06-11T15:17:20 2292 26.7 37 0 25
377 2026-06-11T15:18:20 2265 26.7 37 0 25
378 2026-06-11T15:20:21 2255 26.7 37 0 22
379 2026-06-11T15:21:22 2265 26.7 37 0 25
380 2026-06-11T15:22:22 2255 26.7 37 0 22
381 2026-06-11T15:23:22 2265 26.7 37 0 22
382 2026-06-11T15:24:23 2255 26.7 37 0 22
383 2026-06-11T15:25:23 2279 26.7 37 0 22
384 2026-06-11T15:26:24 2279 26.7 37 0 22
385 2026-06-11T15:27:24 2279 26.7 37 0 25
386 2026-06-11T15:28:25 2292 26.7 37 0 22
387 2026-06-11T15:29:26 2279 26.7 37 0 22
388 2026-06-11T15:30:26 2292 26.8 37 0 25
389 2026-06-11T15:31:27 2334 26.7 37 0 22
390 2026-06-11T15:32:27 2292 26.8 36 0 22
391 2026-06-11T15:33:28 2324 26.7 37 0 22
392 2026-06-11T15:34:28 2292 26.8 37 0 22
393 2026-06-11T15:35:28 2318 26.8 36 0 25
394 2026-06-11T15:36:29 2265 26.8 37 0 22
395 2026-06-11T15:37:30 2242 26.8 37 0 25
396 2026-06-11T15:38:30 2270 26.8 37 0 22
397 2026-06-11T15:39:30 2255 26.8 37 0 22
398 2026-06-11T15:40:31 2259 26.8 37 0 22
399 2026-06-11T15:41:32 2281 26.8 37 0 25
400 2026-06-11T15:42:32 2285 26.8 37 0 25
401 2026-06-11T15:44:33 2281 26.8 37 0 22
402 2026-06-11T15:45:33 2285 26.8 37 0 25
403 2026-06-11T15:47:34 2249 26.8 36 0 22
404 2026-06-11T15:48:35 2226 26.8 37 0 22
405 2026-06-11T15:49:36 2226 26.8 37 0 22
406 2026-06-11T15:50:36 2259 26.8 37 0 22
407 2026-06-11T15:51:37 2255 26.8 37 0 22
408 2026-06-11T15:52:37 2249 26.8 37 0 22
409 2026-06-11T15:53:37 2263 26.8 37 0 22
410 2026-06-11T15:54:38 2259 26.8 36 0 22
411 2026-06-11T15:55:38 2278 26.8 36 0 22
412 2026-06-11T15:56:39 2287 27 37 0 22
413 2026-06-11T15:57:39 2222 26.8 36 0 22
414 2026-06-11T15:58:40 2281 26.8 37 0 22
415 2026-06-11T15:59:40 2276 27 36 0 22
416 2026-06-11T16:00:41 2222 27 37 0 22
417 2026-06-11T16:01:41 2189 27 36 0 22
418 2026-06-11T16:03:43 2205 27 37 0 22
419 2026-06-11T16:04:43 2195 27 36 0 22
420 2026-06-11T16:05:43 2218 27 36 0 25
421 2026-06-11T16:06:44 2207 26.8 37 0 22
422 2026-06-11T16:07:44 2259 27 37 0 22
423 2026-06-11T16:08:45 2189 26.8 37 0 22
424 2026-06-11T16:09:46 2259 26.8 37 0 22
425 2026-06-11T16:10:46 2276 26.8 37 0 22
426 2026-06-11T16:11:47 2281 27 37 0 22
427 2026-06-11T16:13:48 2210 27 37 0 22
428 2026-06-11T16:14:48 2205 27 37 0 22
429 2026-06-11T16:15:49 2210 27.1 37 0 22
430 2026-06-11T16:16:49 2210 26.8 37 0 22
431 2026-06-11T16:17:50 2296 27.3 37 0 22
432 2026-06-11T16:18:50 2210 27 37 0 22
433 2026-06-11T16:19:50 2245 27 37 0 22
434 2026-06-11T16:20:51 2232 27.1 37 0 22
435 2026-06-11T16:21:51 2212 27 37 0 22
436 2026-06-11T16:22:52 2222 27 37 0 22
437 2026-06-11T16:23:53 2251 27 37 0 22
438 2026-06-11T16:25:53 2222 27.1 37 0 22
439 2026-06-11T16:26:54 2222 27.1 37 1 22
440 2026-06-11T16:28:55 2224 27.1 37 1 22
441 2026-06-11T16:29:56 2224 27.1 37 1 22

View File

@@ -0,0 +1,444 @@
time,co2,temperature,humidity,windows,battery
2026-06-11T08:45:43,496,27.7,38,0,57
2026-06-11T08:46:43,496,27.7,38,0,55
2026-06-11T08:47:44,491,27.8,37,0,57
2026-06-11T08:49:45,527,28,37,0,57
2026-06-11T08:50:45,459,28,37,0,57
2026-06-11T08:51:46,448,28.1,37,0,57
2026-06-11T08:52:46,486,28.2,37,0,57
2026-06-11T08:53:47,463,28.2,37,0,57
2026-06-11T08:54:47,445,28.2,37,0,57
2026-06-11T08:55:48,454,28.3,37,0,57
2026-06-11T08:56:48,486,28.3,37,0,57
2026-06-11T08:57:49,459,28.3,37,0,57
2026-06-11T08:58:49,466,28.5,37,0,57
2026-06-11T08:59:50,476,28.5,37,0,57
2026-06-11T09:00:50,491,28.6,37,0,57
2026-06-11T09:01:51,481,28.6,37,0,55
2026-06-11T09:02:51,481,28.6,37,0,57
2026-06-11T09:03:52,486,28.7,37,0,57
2026-06-11T09:04:52,481,28.7,36,0,57
2026-06-11T09:05:53,518,28.8,36,0,57
2026-06-11T09:06:53,502,28.7,37,0,57
2026-06-11T09:07:54,502,28.8,36,0,57
2026-06-11T09:08:54,536,29,37,0,57
2026-06-11T09:09:55,450,29,36,0,57
2026-06-11T09:10:55,472,29,36,0,57
2026-06-11T09:11:56,477,29,36,0,57
2026-06-11T09:12:56,445,29.1,36,0,55
2026-06-11T09:13:57,464,29.1,36,0,57
2026-06-11T09:15:58,455,29.2,36,0,57
2026-06-11T09:16:58,472,29.3,36,0,57
2026-06-11T09:17:59,498,29.3,35,0,57
2026-06-11T09:18:59,493,29.5,36,0,57
2026-06-11T09:20:00,464,29.5,35,0,57
2026-06-11T09:21:00,478,29.5,35,0,57
2026-06-11T09:22:01,493,29.6,35,0,57
2026-06-11T09:23:01,478,29.6,35,0,57
2026-06-11T09:24:02,519,29.6,35,0,57
2026-06-11T09:25:02,478,29.5,35,0,57
2026-06-11T09:26:03,472,29.6,35,0,57
2026-06-11T09:27:03,514,29.6,35,0,57
2026-06-11T09:28:04,514,29.5,35,0,57
2026-06-11T09:29:05,500,29.7,35,0,57
2026-06-11T09:30:05,493,29.7,35,0,57
2026-06-11T09:31:05,519,29.7,34,0,57
2026-06-11T09:32:06,503,29.7,35,0,57
2026-06-11T09:33:07,487,29.6,35,0,57
2026-06-11T09:34:07,500,29.7,34,0,57
2026-06-11T09:35:07,580,29.7,34,0,57
2026-06-11T09:36:08,520,29.7,34,0,57
2026-06-11T09:37:08,514,29.7,34,0,57
2026-06-11T09:38:09,520,29.7,35,0,57
2026-06-11T09:39:09,500,29.7,34,0,57
2026-06-11T09:40:10,498,29.7,35,0,57
2026-06-11T09:41:10,493,29.7,34,0,57
2026-06-11T09:42:11,520,29.7,35,0,57
2026-06-11T09:43:11,527,29.7,34,0,57
2026-06-11T09:44:12,537,29.7,34,0,57
2026-06-11T09:45:12,537,29.7,34,0,55
2026-06-11T09:46:13,526,29.7,34,0,57
2026-06-11T09:47:13,531,29.7,34,0,57
2026-06-11T09:48:14,503,29.7,34,0,57
2026-06-11T09:49:15,514,29.7,34,0,57
2026-06-11T09:50:15,520,29.7,34,0,57
2026-06-11T09:51:15,514,29.7,35,0,57
2026-06-11T09:52:16,500,29.8,34,0,57
2026-06-11T09:53:17,514,29.7,35,0,57
2026-06-11T09:54:17,514,29.8,34,0,57
2026-06-11T09:55:17,531,29.8,34,0,57
2026-06-11T09:56:18,520,29.8,34,0,57
2026-06-11T09:57:18,537,29.8,34,0,57
2026-06-11T09:58:19,520,29.8,34,0,57
2026-06-11T09:59:19,537,29.7,34,0,57
2026-06-11T10:00:20,545,29.8,34,0,55
2026-06-11T10:01:20,551,29.8,34,0,57
2026-06-11T10:02:21,574,29.8,34,0,57
2026-06-11T10:03:22,520,29.8,34,0,57
2026-06-11T10:04:22,537,29.8,34,0,57
2026-06-11T10:05:22,537,29.8,34,0,57
2026-06-11T10:06:23,545,29.8,34,0,57
2026-06-11T10:07:23,551,29.8,34,0,57
2026-06-11T10:08:24,545,29.8,34,0,57
2026-06-11T10:09:25,537,29.8,34,0,57
2026-06-11T10:10:25,556,29.8,34,0,55
2026-06-11T10:11:25,531,29.8,35,0,57
2026-06-11T10:12:26,514,29.8,34,0,57
2026-06-11T10:13:26,537,29.8,34,0,57
2026-06-11T10:15:28,551,29.8,34,0,57
2026-06-11T10:16:28,526,29.8,34,0,57
2026-06-11T10:17:29,537,29.8,34,0,57
2026-06-11T10:18:29,537,29.8,34,0,57
2026-06-11T10:19:29,498,29.8,34,0,57
2026-06-11T10:20:30,537,29.8,35,0,57
2026-06-11T10:21:30,527,29.7,34,0,57
2026-06-11T10:22:31,514,29.7,34,0,57
2026-06-11T10:23:32,545,29.6,34,0,57
2026-06-11T10:24:32,537,29.6,35,0,57
2026-06-11T10:25:32,533,29.5,35,0,57
2026-06-11T10:26:33,539,29.5,35,0,57
2026-06-11T10:27:33,482,29.5,35,0,57
2026-06-11T10:28:34,500,29.5,35,0,57
2026-06-11T10:29:35,539,29.5,35,0,57
2026-06-11T10:30:35,487,29.5,35,0,57
2026-06-11T10:31:36,500,29.5,34,0,57
2026-06-11T10:32:36,514,29.5,35,0,57
2026-06-11T10:33:37,527,29.5,35,0,55
2026-06-11T10:34:37,500,29.5,35,0,57
2026-06-11T10:35:38,503,29.5,34,0,57
2026-06-11T10:36:38,537,29.5,35,0,57
2026-06-11T10:37:39,539,29.6,35,0,57
2026-06-11T10:38:39,493,29.6,35,0,57
2026-06-11T10:39:40,561,29.6,35,0,57
2026-06-11T10:40:40,500,29.7,35,0,57
2026-06-11T10:41:41,533,29.6,35,0,57
2026-06-11T10:42:41,533,29.7,35,0,57
2026-06-11T10:43:42,493,29.7,34,0,57
2026-06-11T10:44:42,526,29.7,35,0,57
2026-06-11T10:45:43,533,29.7,35,0,55
2026-06-11T10:46:43,545,29.8,35,0,55
2026-06-11T10:47:44,545,29.7,34,0,57
2026-06-11T10:48:44,531,29.7,34,0,57
2026-06-11T10:49:45,520,29.7,35,0,57
2026-06-11T10:50:45,520,29.8,34,0,55
2026-06-11T10:51:46,611,29.8,35,0,57
2026-06-11T10:52:46,503,29.8,35,0,55
2026-06-11T10:53:47,478,29.8,34,0,55
2026-06-11T10:54:47,537,29.8,34,0,55
2026-06-11T10:55:48,514,29.7,35,0,57
2026-06-11T10:56:48,519,29.8,35,0,55
2026-06-11T10:57:49,539,29.8,34,0,55
2026-06-11T10:58:49,588,30,34,0,55
2026-06-11T10:59:50,526,29.8,35,0,55
2026-06-11T11:00:50,514,29.8,34,0,55
2026-06-11T11:01:51,545,30,34,0,57
2026-06-11T11:02:51,523,29.8,35,0,57
2026-06-11T11:03:52,533,29.8,35,0,55
2026-06-11T11:04:52,561,29.8,34,0,57
2026-06-11T11:05:53,545,29.8,34,0,57
2026-06-11T11:06:53,574,29.8,35,0,57
2026-06-11T11:07:54,519,30,34,0,57
2026-06-11T11:08:54,481,30,35,0,57
2026-06-11T11:09:55,491,29.8,35,0,57
2026-06-11T11:11:56,514,30,34,0,57
2026-06-11T11:12:56,476,29.8,34,0,57
2026-06-11T11:13:57,556,30,34,0,57
2026-06-11T11:14:58,534,30,35,0,57
2026-06-11T11:15:58,463,30,34,0,57
2026-06-11T11:16:58,506,30,35,0,57
2026-06-11T11:17:59,496,30,35,0,57
2026-06-11T11:18:59,496,30,34,0,57
2026-06-11T11:20:00,463,30,34,0,57
2026-06-11T11:21:00,488,30,34,0,57
2026-06-11T11:22:01,488,30,34,0,57
2026-06-11T11:23:02,470,30,34,0,57
2026-06-11T11:24:02,488,30,34,0,57
2026-06-11T11:25:02,500,30,34,0,57
2026-06-11T11:26:03,495,30,34,0,57
2026-06-11T11:27:03,481,30,34,0,57
2026-06-11T11:28:04,457,30,34,0,57
2026-06-11T11:29:04,500,30.1,34,0,57
2026-06-11T11:30:05,529,30,34,0,57
2026-06-11T11:31:05,476,30,34,0,57
2026-06-11T11:32:06,506,30,34,0,57
2026-06-11T11:33:06,452,30.1,34,0,57
2026-06-11T11:34:07,495,30,34,0,57
2026-06-11T11:36:08,463,30,35,0,57
2026-06-11T11:37:09,438,30,34,0,57
2026-06-11T11:38:09,517,30.1,34,0,57
2026-06-11T11:40:10,481,30,34,0,57
2026-06-11T11:41:10,470,30.1,34,0,57
2026-06-11T11:42:11,463,30.1,34,0,57
2026-06-11T11:43:11,470,30,35,0,57
2026-06-11T11:44:12,473,30,34,0,57
2026-06-11T11:45:12,488,30,34,0,57
2026-06-11T11:46:13,470,30,34,0,57
2026-06-11T11:47:13,523,30,34,0,57
2026-06-11T11:48:14,488,30,34,0,57
2026-06-11T11:49:14,452,30,34,0,57
2026-06-11T11:50:15,476,30,34,0,57
2026-06-11T11:51:15,488,30,35,0,57
2026-06-11T11:52:16,501,30.1,35,0,57
2026-06-11T11:53:16,454,30.1,34,0,57
2026-06-11T11:55:17,470,30,34,0,55
2026-06-11T11:56:18,470,30,35,0,57
2026-06-11T11:57:19,459,30,35,0,57
2026-06-11T11:58:19,463,30,34,0,57
2026-06-11T11:59:19,529,30,35,0,57
2026-06-11T12:00:20,478,30.1,35,0,57
2026-06-11T12:01:20,473,30,35,0,57
2026-06-11T12:02:21,463,30,34,0,57
2026-06-11T12:03:21,506,30.1,35,0,57
2026-06-11T12:04:22,473,30.1,35,0,57
2026-06-11T12:05:23,496,30.1,34,0,57
2026-06-11T12:06:23,546,30.1,34,0,57
2026-06-11T12:07:23,534,30.1,34,0,57
2026-06-11T12:08:24,517,30.1,34,0,57
2026-06-11T12:09:24,500,30.1,35,0,57
2026-06-11T12:10:25,506,30.1,34,0,57
2026-06-11T12:11:25,500,30.1,34,0,57
2026-06-11T12:12:26,561,30.1,34,0,57
2026-06-11T12:13:27,529,30.1,35,0,57
2026-06-11T12:14:27,478,30.1,34,0,57
2026-06-11T12:15:28,517,30.1,34,0,57
2026-06-11T12:16:28,523,30,34,0,57
2026-06-11T12:17:28,517,30.1,35,0,57
2026-06-11T12:18:29,491,30.1,35,0,57
2026-06-11T12:19:29,506,30.2,34,0,57
2026-06-11T12:20:30,506,30.1,34,0,57
2026-06-11T12:21:30,567,30.1,34,0,57
2026-06-11T12:22:31,517,30.1,34,0,57
2026-06-11T12:23:31,523,30.1,34,0,57
2026-06-11T12:24:32,506,30.1,34,0,57
2026-06-11T12:25:32,523,30.1,34,0,57
2026-06-11T12:26:33,517,30.1,34,0,57
2026-06-11T12:27:33,523,30.1,34,0,57
2026-06-11T12:28:34,567,30.2,34,0,57
2026-06-11T12:29:35,517,30.2,35,0,57
2026-06-11T12:30:35,506,30.2,34,0,57
2026-06-11T12:31:36,529,30.1,34,0,57
2026-06-11T12:32:36,534,30.2,35,0,57
2026-06-11T12:33:36,501,30.2,34,0,57
2026-06-11T12:34:37,529,30.2,34,0,57
2026-06-11T12:35:38,599,30.2,34,0,57
2026-06-11T12:36:38,567,30.2,34,0,57
2026-06-11T12:37:39,534,30.2,34,0,57
2026-06-11T12:38:39,552,30.1,34,0,57
2026-06-11T12:39:40,534,30.2,34,0,60
2026-06-11T12:40:40,546,30.2,34,0,57
2026-06-11T12:41:41,592,30.2,34,0,57
2026-06-11T12:42:41,534,30.2,34,0,57
2026-06-11T12:43:42,552,30.2,34,0,57
2026-06-11T12:44:42,546,30.2,34,0,57
2026-06-11T12:45:43,546,30.3,34,0,57
2026-06-11T12:46:43,592,30.2,34,0,57
2026-06-11T12:47:44,529,30.2,34,0,57
2026-06-11T12:48:44,534,30.2,34,0,57
2026-06-11T12:49:45,540,30.3,34,0,57
2026-06-11T12:50:45,561,30.3,34,0,57
2026-06-11T12:51:46,552,30.3,35,0,57
2026-06-11T12:52:46,517,30.2,34,0,57
2026-06-11T12:53:47,546,30.2,34,0,57
2026-06-11T12:54:47,567,30.3,34,0,57
2026-06-11T12:55:48,534,30.2,34,0,57
2026-06-11T12:56:48,552,30.2,34,0,57
2026-06-11T12:57:49,552,30.2,34,0,57
2026-06-11T12:58:49,546,30.3,34,0,57
2026-06-11T12:59:50,534,30.3,34,0,57
2026-06-11T13:00:50,580,30.3,34,0,57
2026-06-11T13:01:51,534,30.3,34,0,57
2026-06-11T13:02:51,561,30.3,34,0,57
2026-06-11T13:03:52,561,30.3,34,0,57
2026-06-11T13:04:52,592,30.3,34,0,57
2026-06-11T13:06:53,537,30.3,34,0,57
2026-06-11T13:07:54,574,30.3,34,0,57
2026-06-11T13:09:55,517,30.3,34,0,57
2026-06-11T13:10:55,529,30.3,34,0,57
2026-06-11T13:11:56,546,30.3,34,0,57
2026-06-11T13:12:56,561,30.3,34,0,57
2026-06-11T13:13:57,540,30.3,33,0,57
2026-06-11T13:14:57,569,30.2,34,0,57
2026-06-11T13:15:58,580,30.2,34,0,57
2026-06-11T13:16:58,552,30.2,34,0,57
2026-06-11T13:17:59,534,30.1,34,0,55
2026-06-11T13:18:59,523,30.1,35,0,57
2026-06-11T13:20:00,542,30,34,0,57
2026-06-11T13:21:00,523,29.8,35,0,57
2026-06-11T13:22:01,567,29.8,34,0,57
2026-06-11T13:23:01,611,29.8,35,0,55
2026-06-11T13:24:02,589,29.7,35,0,57
2026-06-11T13:25:03,553,29.8,34,0,57
2026-06-11T13:26:03,565,29.8,34,0,57
2026-06-11T13:27:03,580,29.7,34,0,57
2026-06-11T13:28:04,551,29.7,34,0,57
2026-06-11T13:29:04,580,29.7,35,0,57
2026-06-11T13:30:05,539,29.7,35,0,57
2026-06-11T13:31:05,539,29.7,35,0,57
2026-06-11T13:32:06,567,29.7,35,0,57
2026-06-11T13:34:07,556,29.8,35,0,57
2026-06-11T13:35:07,545,29.8,34,0,57
2026-06-11T13:36:08,551,30,34,0,57
2026-06-11T13:37:09,500,30,35,0,57
2026-06-11T13:38:09,496,30,35,0,55
2026-06-11T13:39:09,506,30,35,0,57
2026-06-11T13:40:10,478,30,34,0,57
2026-06-11T13:41:10,546,30,35,0,57
2026-06-11T13:42:11,501,30,34,0,57
2026-06-11T13:44:12,461,29.8,35,0,55
2026-06-11T13:45:12,539,29.3,34,0,57
2026-06-11T13:46:13,556,29.1,35,0,57
2026-06-11T13:47:14,567,28.7,35,0,55
2026-06-11T13:48:14,607,28.6,36,0,57
2026-06-11T13:49:14,588,28.3,35,0,57
2026-06-11T13:50:15,580,28.2,36,0,57
2026-06-11T13:51:16,561,28,36,0,57
2026-06-11T13:52:16,553,27.8,36,0,55
2026-06-11T13:53:16,653,27.7,36,0,57
2026-06-11T13:54:17,618,27.7,36,0,57
2026-06-11T13:55:17,612,27.5,37,0,57
2026-06-11T13:56:18,575,27.5,37,0,57
2026-06-11T13:57:18,556,27.3,37,0,55
2026-06-11T13:58:19,570,27.2,37,0,55
2026-06-11T13:59:19,591,27.2,37,0,55
2026-06-11T14:00:20,533,27.1,37,0,55
2026-06-11T14:01:20,549,27.1,38,0,57
2026-06-11T14:02:21,501,27,37,0,55
2026-06-11T14:03:22,523,27,37,0,55
2026-06-11T14:04:22,523,26.8,38,0,57
2026-06-11T14:05:22,537,26.7,38,0,55
2026-06-11T14:06:23,589,26.7,38,0,57
2026-06-11T14:07:23,542,26.7,38,0,57
2026-06-11T14:08:24,567,26.6,38,0,57
2026-06-11T14:09:24,564,26.6,38,0,57
2026-06-11T14:10:25,567,26.6,38,0,57
2026-06-11T14:11:26,575,26.6,38,0,57
2026-06-11T14:12:26,575,26.6,37,0,57
2026-06-11T14:13:27,575,26.5,38,0,55
2026-06-11T14:15:28,508,26.5,38,0,55
2026-06-11T14:16:28,549,26.3,38,0,55
2026-06-11T14:17:28,529,26.3,38,0,55
2026-06-11T14:18:29,529,26.5,39,0,57
2026-06-11T14:19:30,476,26.3,38,0,57
2026-06-11T14:20:30,514,26.3,39,0,55
2026-06-11T14:21:31,492,26.2,38,0,55
2026-06-11T14:22:31,501,26.2,38,0,55
2026-06-11T14:23:32,514,26.2,39,0,57
2026-06-11T14:24:32,495,26.2,38,0,57
2026-06-11T14:25:32,581,26.2,39,0,55
2026-06-11T14:26:33,624,26.2,38,0,55
2026-06-11T14:27:33,682,26.2,39,0,55
2026-06-11T14:28:34,675,26.2,39,0,55
2026-06-11T14:29:35,671,26.2,39,0,55
2026-06-11T14:30:35,629,26.1,39,0,55
2026-06-11T14:31:36,638,26.2,39,0,57
2026-06-11T14:32:36,638,26.1,38,0,55
2026-06-11T14:33:37,615,26.2,38,0,55
2026-06-11T14:34:37,607,26.2,39,0,55
2026-06-11T14:35:37,638,26.1,39,0,55
2026-06-11T14:36:38,600,26.1,39,0,55
2026-06-11T14:37:39,575,26.2,39,0,55
2026-06-11T14:38:39,575,26.1,39,0,55
2026-06-11T14:39:40,596,26.1,39,0,57
2026-06-11T14:40:40,624,26.1,39,0,55
2026-06-11T14:41:41,575,26.1,39,0,57
2026-06-11T14:42:41,565,26.1,38,0,55
2026-06-11T14:43:42,637,26.1,39,0,55
2026-06-11T14:44:42,581,26.1,39,0,57
2026-06-11T14:45:43,581,26.1,39,0,55
2026-06-11T14:46:43,556,26.1,39,0,55
2026-06-11T14:49:45,565,26.1,39,0,57
2026-06-11T14:50:45,565,26.1,39,0,55
2026-06-11T14:51:46,545,26,39,0,55
2026-06-11T14:52:46,526,26.1,39,0,55
2026-06-11T14:53:47,549,26.1,39,0,55
2026-06-11T14:54:47,549,26,38,0,55
2026-06-11T14:55:48,575,26.1,39,0,55
2026-06-11T14:56:48,533,26.1,39,0,55
2026-06-11T14:58:49,526,26.1,39,0,55
2026-06-11T14:59:50,526,26.1,39,0,55
2026-06-11T15:00:50,565,26.1,39,0,57
2026-06-11T15:01:51,520,26,39,0,55
2026-06-11T15:02:51,526,26.1,38,0,57
2026-06-11T15:03:52,526,26.1,38,0,55
2026-06-11T15:04:52,549,26.1,39,0,55
2026-06-11T15:05:53,539,26.1,39,0,57
2026-06-11T15:06:53,549,26.2,39,0,55
2026-06-11T15:07:54,565,26.2,39,0,55
2026-06-11T15:08:54,533,26.1,39,0,57
2026-06-11T15:09:55,526,26.2,40,0,55
2026-06-11T15:10:55,505,26.2,39,0,57
2026-06-11T15:11:56,526,26.2,39,0,57
2026-06-11T15:12:57,526,26.2,39,0,55
2026-06-11T15:13:57,549,26.2,39,0,55
2026-06-11T15:14:57,549,26.2,39,0,55
2026-06-11T15:15:58,539,26.2,39,0,57
2026-06-11T15:16:58,556,26.2,39,0,55
2026-06-11T15:17:59,545,26.2,39,0,55
2026-06-11T15:18:59,565,26.2,40,0,57
2026-06-11T15:21:00,517,26.3,39,0,55
2026-06-11T15:22:01,565,26.3,39,0,55
2026-06-11T15:23:01,533,26.3,39,0,57
2026-06-11T15:24:02,533,26.3,40,0,55
2026-06-11T15:25:02,546,26.3,40,0,55
2026-06-11T15:26:03,523,26.3,39,0,57
2026-06-11T15:27:03,533,26.3,39,0,55
2026-06-11T15:28:04,549,26.3,39,0,57
2026-06-11T15:29:04,545,26.3,39,0,55
2026-06-11T15:30:05,549,26.3,39,0,57
2026-06-11T15:31:05,575,26.3,39,0,55
2026-06-11T15:32:06,539,26.3,39,0,57
2026-06-11T15:33:06,565,26.3,39,0,55
2026-06-11T15:34:07,526,26.5,39,0,55
2026-06-11T15:35:07,575,26.3,39,0,55
2026-06-11T15:37:08,565,26.3,39,0,55
2026-06-11T15:38:09,549,26.5,39,0,55
2026-06-11T15:39:10,533,26.5,40,0,55
2026-06-11T15:40:10,565,26.3,40,0,55
2026-06-11T15:41:10,565,26.5,39,0,55
2026-06-11T15:42:11,549,26.3,40,0,55
2026-06-11T15:43:11,529,26.5,39,0,55
2026-06-11T15:45:12,581,26.5,39,0,55
2026-06-11T15:46:13,596,26.5,40,0,55
2026-06-11T15:47:13,546,26.5,39,0,55
2026-06-11T15:48:14,575,26.3,39,0,55
2026-06-11T15:49:14,589,26.5,40,0,55
2026-06-11T15:50:15,540,26.5,39,0,55
2026-06-11T15:51:15,596,26.3,40,0,55
2026-06-11T15:52:16,529,26.5,40,0,57
2026-06-11T15:53:16,540,26.5,39,0,55
2026-06-11T15:54:17,575,26.5,39,0,55
2026-06-11T15:55:17,545,26.5,39,0,55
2026-06-11T15:56:18,581,26.3,39,0,55
2026-06-11T15:57:18,545,26.5,39,0,57
2026-06-11T15:58:19,545,26.5,40,0,57
2026-06-11T15:59:19,529,26.5,40,0,57
2026-06-11T16:00:20,546,26.5,40,0,55
2026-06-11T16:01:20,546,26.5,39,0,57
2026-06-11T16:02:21,556,26.3,39,0,57
2026-06-11T16:03:21,545,26.5,39,0,57
2026-06-11T16:04:22,556,26.5,38,0,55
2026-06-11T16:05:23,575,26.6,39,0,55
2026-06-11T16:06:23,565,26.6,38,0,57
2026-06-11T16:07:23,629,26.6,39,0,57
2026-06-11T16:08:24,549,26.5,39,0,57
2026-06-11T16:09:24,556,26.5,39,0,55
2026-06-11T16:10:25,570,26.5,40,0,55
2026-06-11T16:11:25,565,26.5,39,0,55
2026-06-11T16:12:26,575,26.5,38,0,55
2026-06-11T16:13:26,615,26.6,39,0,57
2026-06-11T16:14:27,556,26.5,39,0,55
2026-06-11T16:15:27,570,26.6,39,0,55
2026-06-11T16:16:28,575,26.6,39,0,55
2026-06-11T16:17:28,556,26.6,39,0,55
2026-06-11T16:18:29,575,26.5,40,0,55
2026-06-11T16:19:29,565,26.6,39,0,55
2026-06-11T16:20:30,556,26.5,39,0,55
2026-06-11T16:21:31,565,26.6,40,0,55
2026-06-11T16:22:31,533,26.6,39,0,57
2026-06-11T16:23:31,575,26.6,40,0,55
2026-06-11T16:24:32,570,26.6,39,0,55
2026-06-11T16:25:33,556,26.6,39,0,57
2026-06-11T16:26:33,545,26.6,39,0,55
2026-06-11T16:27:33,589,26.5,39,0,57
2026-06-11T16:28:34,549,26.6,39,0,55
2026-06-11T16:29:34,581,26.6,39,0,55
1 time co2 temperature humidity windows battery
2 2026-06-11T08:45:43 496 27.7 38 0 57
3 2026-06-11T08:46:43 496 27.7 38 0 55
4 2026-06-11T08:47:44 491 27.8 37 0 57
5 2026-06-11T08:49:45 527 28 37 0 57
6 2026-06-11T08:50:45 459 28 37 0 57
7 2026-06-11T08:51:46 448 28.1 37 0 57
8 2026-06-11T08:52:46 486 28.2 37 0 57
9 2026-06-11T08:53:47 463 28.2 37 0 57
10 2026-06-11T08:54:47 445 28.2 37 0 57
11 2026-06-11T08:55:48 454 28.3 37 0 57
12 2026-06-11T08:56:48 486 28.3 37 0 57
13 2026-06-11T08:57:49 459 28.3 37 0 57
14 2026-06-11T08:58:49 466 28.5 37 0 57
15 2026-06-11T08:59:50 476 28.5 37 0 57
16 2026-06-11T09:00:50 491 28.6 37 0 57
17 2026-06-11T09:01:51 481 28.6 37 0 55
18 2026-06-11T09:02:51 481 28.6 37 0 57
19 2026-06-11T09:03:52 486 28.7 37 0 57
20 2026-06-11T09:04:52 481 28.7 36 0 57
21 2026-06-11T09:05:53 518 28.8 36 0 57
22 2026-06-11T09:06:53 502 28.7 37 0 57
23 2026-06-11T09:07:54 502 28.8 36 0 57
24 2026-06-11T09:08:54 536 29 37 0 57
25 2026-06-11T09:09:55 450 29 36 0 57
26 2026-06-11T09:10:55 472 29 36 0 57
27 2026-06-11T09:11:56 477 29 36 0 57
28 2026-06-11T09:12:56 445 29.1 36 0 55
29 2026-06-11T09:13:57 464 29.1 36 0 57
30 2026-06-11T09:15:58 455 29.2 36 0 57
31 2026-06-11T09:16:58 472 29.3 36 0 57
32 2026-06-11T09:17:59 498 29.3 35 0 57
33 2026-06-11T09:18:59 493 29.5 36 0 57
34 2026-06-11T09:20:00 464 29.5 35 0 57
35 2026-06-11T09:21:00 478 29.5 35 0 57
36 2026-06-11T09:22:01 493 29.6 35 0 57
37 2026-06-11T09:23:01 478 29.6 35 0 57
38 2026-06-11T09:24:02 519 29.6 35 0 57
39 2026-06-11T09:25:02 478 29.5 35 0 57
40 2026-06-11T09:26:03 472 29.6 35 0 57
41 2026-06-11T09:27:03 514 29.6 35 0 57
42 2026-06-11T09:28:04 514 29.5 35 0 57
43 2026-06-11T09:29:05 500 29.7 35 0 57
44 2026-06-11T09:30:05 493 29.7 35 0 57
45 2026-06-11T09:31:05 519 29.7 34 0 57
46 2026-06-11T09:32:06 503 29.7 35 0 57
47 2026-06-11T09:33:07 487 29.6 35 0 57
48 2026-06-11T09:34:07 500 29.7 34 0 57
49 2026-06-11T09:35:07 580 29.7 34 0 57
50 2026-06-11T09:36:08 520 29.7 34 0 57
51 2026-06-11T09:37:08 514 29.7 34 0 57
52 2026-06-11T09:38:09 520 29.7 35 0 57
53 2026-06-11T09:39:09 500 29.7 34 0 57
54 2026-06-11T09:40:10 498 29.7 35 0 57
55 2026-06-11T09:41:10 493 29.7 34 0 57
56 2026-06-11T09:42:11 520 29.7 35 0 57
57 2026-06-11T09:43:11 527 29.7 34 0 57
58 2026-06-11T09:44:12 537 29.7 34 0 57
59 2026-06-11T09:45:12 537 29.7 34 0 55
60 2026-06-11T09:46:13 526 29.7 34 0 57
61 2026-06-11T09:47:13 531 29.7 34 0 57
62 2026-06-11T09:48:14 503 29.7 34 0 57
63 2026-06-11T09:49:15 514 29.7 34 0 57
64 2026-06-11T09:50:15 520 29.7 34 0 57
65 2026-06-11T09:51:15 514 29.7 35 0 57
66 2026-06-11T09:52:16 500 29.8 34 0 57
67 2026-06-11T09:53:17 514 29.7 35 0 57
68 2026-06-11T09:54:17 514 29.8 34 0 57
69 2026-06-11T09:55:17 531 29.8 34 0 57
70 2026-06-11T09:56:18 520 29.8 34 0 57
71 2026-06-11T09:57:18 537 29.8 34 0 57
72 2026-06-11T09:58:19 520 29.8 34 0 57
73 2026-06-11T09:59:19 537 29.7 34 0 57
74 2026-06-11T10:00:20 545 29.8 34 0 55
75 2026-06-11T10:01:20 551 29.8 34 0 57
76 2026-06-11T10:02:21 574 29.8 34 0 57
77 2026-06-11T10:03:22 520 29.8 34 0 57
78 2026-06-11T10:04:22 537 29.8 34 0 57
79 2026-06-11T10:05:22 537 29.8 34 0 57
80 2026-06-11T10:06:23 545 29.8 34 0 57
81 2026-06-11T10:07:23 551 29.8 34 0 57
82 2026-06-11T10:08:24 545 29.8 34 0 57
83 2026-06-11T10:09:25 537 29.8 34 0 57
84 2026-06-11T10:10:25 556 29.8 34 0 55
85 2026-06-11T10:11:25 531 29.8 35 0 57
86 2026-06-11T10:12:26 514 29.8 34 0 57
87 2026-06-11T10:13:26 537 29.8 34 0 57
88 2026-06-11T10:15:28 551 29.8 34 0 57
89 2026-06-11T10:16:28 526 29.8 34 0 57
90 2026-06-11T10:17:29 537 29.8 34 0 57
91 2026-06-11T10:18:29 537 29.8 34 0 57
92 2026-06-11T10:19:29 498 29.8 34 0 57
93 2026-06-11T10:20:30 537 29.8 35 0 57
94 2026-06-11T10:21:30 527 29.7 34 0 57
95 2026-06-11T10:22:31 514 29.7 34 0 57
96 2026-06-11T10:23:32 545 29.6 34 0 57
97 2026-06-11T10:24:32 537 29.6 35 0 57
98 2026-06-11T10:25:32 533 29.5 35 0 57
99 2026-06-11T10:26:33 539 29.5 35 0 57
100 2026-06-11T10:27:33 482 29.5 35 0 57
101 2026-06-11T10:28:34 500 29.5 35 0 57
102 2026-06-11T10:29:35 539 29.5 35 0 57
103 2026-06-11T10:30:35 487 29.5 35 0 57
104 2026-06-11T10:31:36 500 29.5 34 0 57
105 2026-06-11T10:32:36 514 29.5 35 0 57
106 2026-06-11T10:33:37 527 29.5 35 0 55
107 2026-06-11T10:34:37 500 29.5 35 0 57
108 2026-06-11T10:35:38 503 29.5 34 0 57
109 2026-06-11T10:36:38 537 29.5 35 0 57
110 2026-06-11T10:37:39 539 29.6 35 0 57
111 2026-06-11T10:38:39 493 29.6 35 0 57
112 2026-06-11T10:39:40 561 29.6 35 0 57
113 2026-06-11T10:40:40 500 29.7 35 0 57
114 2026-06-11T10:41:41 533 29.6 35 0 57
115 2026-06-11T10:42:41 533 29.7 35 0 57
116 2026-06-11T10:43:42 493 29.7 34 0 57
117 2026-06-11T10:44:42 526 29.7 35 0 57
118 2026-06-11T10:45:43 533 29.7 35 0 55
119 2026-06-11T10:46:43 545 29.8 35 0 55
120 2026-06-11T10:47:44 545 29.7 34 0 57
121 2026-06-11T10:48:44 531 29.7 34 0 57
122 2026-06-11T10:49:45 520 29.7 35 0 57
123 2026-06-11T10:50:45 520 29.8 34 0 55
124 2026-06-11T10:51:46 611 29.8 35 0 57
125 2026-06-11T10:52:46 503 29.8 35 0 55
126 2026-06-11T10:53:47 478 29.8 34 0 55
127 2026-06-11T10:54:47 537 29.8 34 0 55
128 2026-06-11T10:55:48 514 29.7 35 0 57
129 2026-06-11T10:56:48 519 29.8 35 0 55
130 2026-06-11T10:57:49 539 29.8 34 0 55
131 2026-06-11T10:58:49 588 30 34 0 55
132 2026-06-11T10:59:50 526 29.8 35 0 55
133 2026-06-11T11:00:50 514 29.8 34 0 55
134 2026-06-11T11:01:51 545 30 34 0 57
135 2026-06-11T11:02:51 523 29.8 35 0 57
136 2026-06-11T11:03:52 533 29.8 35 0 55
137 2026-06-11T11:04:52 561 29.8 34 0 57
138 2026-06-11T11:05:53 545 29.8 34 0 57
139 2026-06-11T11:06:53 574 29.8 35 0 57
140 2026-06-11T11:07:54 519 30 34 0 57
141 2026-06-11T11:08:54 481 30 35 0 57
142 2026-06-11T11:09:55 491 29.8 35 0 57
143 2026-06-11T11:11:56 514 30 34 0 57
144 2026-06-11T11:12:56 476 29.8 34 0 57
145 2026-06-11T11:13:57 556 30 34 0 57
146 2026-06-11T11:14:58 534 30 35 0 57
147 2026-06-11T11:15:58 463 30 34 0 57
148 2026-06-11T11:16:58 506 30 35 0 57
149 2026-06-11T11:17:59 496 30 35 0 57
150 2026-06-11T11:18:59 496 30 34 0 57
151 2026-06-11T11:20:00 463 30 34 0 57
152 2026-06-11T11:21:00 488 30 34 0 57
153 2026-06-11T11:22:01 488 30 34 0 57
154 2026-06-11T11:23:02 470 30 34 0 57
155 2026-06-11T11:24:02 488 30 34 0 57
156 2026-06-11T11:25:02 500 30 34 0 57
157 2026-06-11T11:26:03 495 30 34 0 57
158 2026-06-11T11:27:03 481 30 34 0 57
159 2026-06-11T11:28:04 457 30 34 0 57
160 2026-06-11T11:29:04 500 30.1 34 0 57
161 2026-06-11T11:30:05 529 30 34 0 57
162 2026-06-11T11:31:05 476 30 34 0 57
163 2026-06-11T11:32:06 506 30 34 0 57
164 2026-06-11T11:33:06 452 30.1 34 0 57
165 2026-06-11T11:34:07 495 30 34 0 57
166 2026-06-11T11:36:08 463 30 35 0 57
167 2026-06-11T11:37:09 438 30 34 0 57
168 2026-06-11T11:38:09 517 30.1 34 0 57
169 2026-06-11T11:40:10 481 30 34 0 57
170 2026-06-11T11:41:10 470 30.1 34 0 57
171 2026-06-11T11:42:11 463 30.1 34 0 57
172 2026-06-11T11:43:11 470 30 35 0 57
173 2026-06-11T11:44:12 473 30 34 0 57
174 2026-06-11T11:45:12 488 30 34 0 57
175 2026-06-11T11:46:13 470 30 34 0 57
176 2026-06-11T11:47:13 523 30 34 0 57
177 2026-06-11T11:48:14 488 30 34 0 57
178 2026-06-11T11:49:14 452 30 34 0 57
179 2026-06-11T11:50:15 476 30 34 0 57
180 2026-06-11T11:51:15 488 30 35 0 57
181 2026-06-11T11:52:16 501 30.1 35 0 57
182 2026-06-11T11:53:16 454 30.1 34 0 57
183 2026-06-11T11:55:17 470 30 34 0 55
184 2026-06-11T11:56:18 470 30 35 0 57
185 2026-06-11T11:57:19 459 30 35 0 57
186 2026-06-11T11:58:19 463 30 34 0 57
187 2026-06-11T11:59:19 529 30 35 0 57
188 2026-06-11T12:00:20 478 30.1 35 0 57
189 2026-06-11T12:01:20 473 30 35 0 57
190 2026-06-11T12:02:21 463 30 34 0 57
191 2026-06-11T12:03:21 506 30.1 35 0 57
192 2026-06-11T12:04:22 473 30.1 35 0 57
193 2026-06-11T12:05:23 496 30.1 34 0 57
194 2026-06-11T12:06:23 546 30.1 34 0 57
195 2026-06-11T12:07:23 534 30.1 34 0 57
196 2026-06-11T12:08:24 517 30.1 34 0 57
197 2026-06-11T12:09:24 500 30.1 35 0 57
198 2026-06-11T12:10:25 506 30.1 34 0 57
199 2026-06-11T12:11:25 500 30.1 34 0 57
200 2026-06-11T12:12:26 561 30.1 34 0 57
201 2026-06-11T12:13:27 529 30.1 35 0 57
202 2026-06-11T12:14:27 478 30.1 34 0 57
203 2026-06-11T12:15:28 517 30.1 34 0 57
204 2026-06-11T12:16:28 523 30 34 0 57
205 2026-06-11T12:17:28 517 30.1 35 0 57
206 2026-06-11T12:18:29 491 30.1 35 0 57
207 2026-06-11T12:19:29 506 30.2 34 0 57
208 2026-06-11T12:20:30 506 30.1 34 0 57
209 2026-06-11T12:21:30 567 30.1 34 0 57
210 2026-06-11T12:22:31 517 30.1 34 0 57
211 2026-06-11T12:23:31 523 30.1 34 0 57
212 2026-06-11T12:24:32 506 30.1 34 0 57
213 2026-06-11T12:25:32 523 30.1 34 0 57
214 2026-06-11T12:26:33 517 30.1 34 0 57
215 2026-06-11T12:27:33 523 30.1 34 0 57
216 2026-06-11T12:28:34 567 30.2 34 0 57
217 2026-06-11T12:29:35 517 30.2 35 0 57
218 2026-06-11T12:30:35 506 30.2 34 0 57
219 2026-06-11T12:31:36 529 30.1 34 0 57
220 2026-06-11T12:32:36 534 30.2 35 0 57
221 2026-06-11T12:33:36 501 30.2 34 0 57
222 2026-06-11T12:34:37 529 30.2 34 0 57
223 2026-06-11T12:35:38 599 30.2 34 0 57
224 2026-06-11T12:36:38 567 30.2 34 0 57
225 2026-06-11T12:37:39 534 30.2 34 0 57
226 2026-06-11T12:38:39 552 30.1 34 0 57
227 2026-06-11T12:39:40 534 30.2 34 0 60
228 2026-06-11T12:40:40 546 30.2 34 0 57
229 2026-06-11T12:41:41 592 30.2 34 0 57
230 2026-06-11T12:42:41 534 30.2 34 0 57
231 2026-06-11T12:43:42 552 30.2 34 0 57
232 2026-06-11T12:44:42 546 30.2 34 0 57
233 2026-06-11T12:45:43 546 30.3 34 0 57
234 2026-06-11T12:46:43 592 30.2 34 0 57
235 2026-06-11T12:47:44 529 30.2 34 0 57
236 2026-06-11T12:48:44 534 30.2 34 0 57
237 2026-06-11T12:49:45 540 30.3 34 0 57
238 2026-06-11T12:50:45 561 30.3 34 0 57
239 2026-06-11T12:51:46 552 30.3 35 0 57
240 2026-06-11T12:52:46 517 30.2 34 0 57
241 2026-06-11T12:53:47 546 30.2 34 0 57
242 2026-06-11T12:54:47 567 30.3 34 0 57
243 2026-06-11T12:55:48 534 30.2 34 0 57
244 2026-06-11T12:56:48 552 30.2 34 0 57
245 2026-06-11T12:57:49 552 30.2 34 0 57
246 2026-06-11T12:58:49 546 30.3 34 0 57
247 2026-06-11T12:59:50 534 30.3 34 0 57
248 2026-06-11T13:00:50 580 30.3 34 0 57
249 2026-06-11T13:01:51 534 30.3 34 0 57
250 2026-06-11T13:02:51 561 30.3 34 0 57
251 2026-06-11T13:03:52 561 30.3 34 0 57
252 2026-06-11T13:04:52 592 30.3 34 0 57
253 2026-06-11T13:06:53 537 30.3 34 0 57
254 2026-06-11T13:07:54 574 30.3 34 0 57
255 2026-06-11T13:09:55 517 30.3 34 0 57
256 2026-06-11T13:10:55 529 30.3 34 0 57
257 2026-06-11T13:11:56 546 30.3 34 0 57
258 2026-06-11T13:12:56 561 30.3 34 0 57
259 2026-06-11T13:13:57 540 30.3 33 0 57
260 2026-06-11T13:14:57 569 30.2 34 0 57
261 2026-06-11T13:15:58 580 30.2 34 0 57
262 2026-06-11T13:16:58 552 30.2 34 0 57
263 2026-06-11T13:17:59 534 30.1 34 0 55
264 2026-06-11T13:18:59 523 30.1 35 0 57
265 2026-06-11T13:20:00 542 30 34 0 57
266 2026-06-11T13:21:00 523 29.8 35 0 57
267 2026-06-11T13:22:01 567 29.8 34 0 57
268 2026-06-11T13:23:01 611 29.8 35 0 55
269 2026-06-11T13:24:02 589 29.7 35 0 57
270 2026-06-11T13:25:03 553 29.8 34 0 57
271 2026-06-11T13:26:03 565 29.8 34 0 57
272 2026-06-11T13:27:03 580 29.7 34 0 57
273 2026-06-11T13:28:04 551 29.7 34 0 57
274 2026-06-11T13:29:04 580 29.7 35 0 57
275 2026-06-11T13:30:05 539 29.7 35 0 57
276 2026-06-11T13:31:05 539 29.7 35 0 57
277 2026-06-11T13:32:06 567 29.7 35 0 57
278 2026-06-11T13:34:07 556 29.8 35 0 57
279 2026-06-11T13:35:07 545 29.8 34 0 57
280 2026-06-11T13:36:08 551 30 34 0 57
281 2026-06-11T13:37:09 500 30 35 0 57
282 2026-06-11T13:38:09 496 30 35 0 55
283 2026-06-11T13:39:09 506 30 35 0 57
284 2026-06-11T13:40:10 478 30 34 0 57
285 2026-06-11T13:41:10 546 30 35 0 57
286 2026-06-11T13:42:11 501 30 34 0 57
287 2026-06-11T13:44:12 461 29.8 35 0 55
288 2026-06-11T13:45:12 539 29.3 34 0 57
289 2026-06-11T13:46:13 556 29.1 35 0 57
290 2026-06-11T13:47:14 567 28.7 35 0 55
291 2026-06-11T13:48:14 607 28.6 36 0 57
292 2026-06-11T13:49:14 588 28.3 35 0 57
293 2026-06-11T13:50:15 580 28.2 36 0 57
294 2026-06-11T13:51:16 561 28 36 0 57
295 2026-06-11T13:52:16 553 27.8 36 0 55
296 2026-06-11T13:53:16 653 27.7 36 0 57
297 2026-06-11T13:54:17 618 27.7 36 0 57
298 2026-06-11T13:55:17 612 27.5 37 0 57
299 2026-06-11T13:56:18 575 27.5 37 0 57
300 2026-06-11T13:57:18 556 27.3 37 0 55
301 2026-06-11T13:58:19 570 27.2 37 0 55
302 2026-06-11T13:59:19 591 27.2 37 0 55
303 2026-06-11T14:00:20 533 27.1 37 0 55
304 2026-06-11T14:01:20 549 27.1 38 0 57
305 2026-06-11T14:02:21 501 27 37 0 55
306 2026-06-11T14:03:22 523 27 37 0 55
307 2026-06-11T14:04:22 523 26.8 38 0 57
308 2026-06-11T14:05:22 537 26.7 38 0 55
309 2026-06-11T14:06:23 589 26.7 38 0 57
310 2026-06-11T14:07:23 542 26.7 38 0 57
311 2026-06-11T14:08:24 567 26.6 38 0 57
312 2026-06-11T14:09:24 564 26.6 38 0 57
313 2026-06-11T14:10:25 567 26.6 38 0 57
314 2026-06-11T14:11:26 575 26.6 38 0 57
315 2026-06-11T14:12:26 575 26.6 37 0 57
316 2026-06-11T14:13:27 575 26.5 38 0 55
317 2026-06-11T14:15:28 508 26.5 38 0 55
318 2026-06-11T14:16:28 549 26.3 38 0 55
319 2026-06-11T14:17:28 529 26.3 38 0 55
320 2026-06-11T14:18:29 529 26.5 39 0 57
321 2026-06-11T14:19:30 476 26.3 38 0 57
322 2026-06-11T14:20:30 514 26.3 39 0 55
323 2026-06-11T14:21:31 492 26.2 38 0 55
324 2026-06-11T14:22:31 501 26.2 38 0 55
325 2026-06-11T14:23:32 514 26.2 39 0 57
326 2026-06-11T14:24:32 495 26.2 38 0 57
327 2026-06-11T14:25:32 581 26.2 39 0 55
328 2026-06-11T14:26:33 624 26.2 38 0 55
329 2026-06-11T14:27:33 682 26.2 39 0 55
330 2026-06-11T14:28:34 675 26.2 39 0 55
331 2026-06-11T14:29:35 671 26.2 39 0 55
332 2026-06-11T14:30:35 629 26.1 39 0 55
333 2026-06-11T14:31:36 638 26.2 39 0 57
334 2026-06-11T14:32:36 638 26.1 38 0 55
335 2026-06-11T14:33:37 615 26.2 38 0 55
336 2026-06-11T14:34:37 607 26.2 39 0 55
337 2026-06-11T14:35:37 638 26.1 39 0 55
338 2026-06-11T14:36:38 600 26.1 39 0 55
339 2026-06-11T14:37:39 575 26.2 39 0 55
340 2026-06-11T14:38:39 575 26.1 39 0 55
341 2026-06-11T14:39:40 596 26.1 39 0 57
342 2026-06-11T14:40:40 624 26.1 39 0 55
343 2026-06-11T14:41:41 575 26.1 39 0 57
344 2026-06-11T14:42:41 565 26.1 38 0 55
345 2026-06-11T14:43:42 637 26.1 39 0 55
346 2026-06-11T14:44:42 581 26.1 39 0 57
347 2026-06-11T14:45:43 581 26.1 39 0 55
348 2026-06-11T14:46:43 556 26.1 39 0 55
349 2026-06-11T14:49:45 565 26.1 39 0 57
350 2026-06-11T14:50:45 565 26.1 39 0 55
351 2026-06-11T14:51:46 545 26 39 0 55
352 2026-06-11T14:52:46 526 26.1 39 0 55
353 2026-06-11T14:53:47 549 26.1 39 0 55
354 2026-06-11T14:54:47 549 26 38 0 55
355 2026-06-11T14:55:48 575 26.1 39 0 55
356 2026-06-11T14:56:48 533 26.1 39 0 55
357 2026-06-11T14:58:49 526 26.1 39 0 55
358 2026-06-11T14:59:50 526 26.1 39 0 55
359 2026-06-11T15:00:50 565 26.1 39 0 57
360 2026-06-11T15:01:51 520 26 39 0 55
361 2026-06-11T15:02:51 526 26.1 38 0 57
362 2026-06-11T15:03:52 526 26.1 38 0 55
363 2026-06-11T15:04:52 549 26.1 39 0 55
364 2026-06-11T15:05:53 539 26.1 39 0 57
365 2026-06-11T15:06:53 549 26.2 39 0 55
366 2026-06-11T15:07:54 565 26.2 39 0 55
367 2026-06-11T15:08:54 533 26.1 39 0 57
368 2026-06-11T15:09:55 526 26.2 40 0 55
369 2026-06-11T15:10:55 505 26.2 39 0 57
370 2026-06-11T15:11:56 526 26.2 39 0 57
371 2026-06-11T15:12:57 526 26.2 39 0 55
372 2026-06-11T15:13:57 549 26.2 39 0 55
373 2026-06-11T15:14:57 549 26.2 39 0 55
374 2026-06-11T15:15:58 539 26.2 39 0 57
375 2026-06-11T15:16:58 556 26.2 39 0 55
376 2026-06-11T15:17:59 545 26.2 39 0 55
377 2026-06-11T15:18:59 565 26.2 40 0 57
378 2026-06-11T15:21:00 517 26.3 39 0 55
379 2026-06-11T15:22:01 565 26.3 39 0 55
380 2026-06-11T15:23:01 533 26.3 39 0 57
381 2026-06-11T15:24:02 533 26.3 40 0 55
382 2026-06-11T15:25:02 546 26.3 40 0 55
383 2026-06-11T15:26:03 523 26.3 39 0 57
384 2026-06-11T15:27:03 533 26.3 39 0 55
385 2026-06-11T15:28:04 549 26.3 39 0 57
386 2026-06-11T15:29:04 545 26.3 39 0 55
387 2026-06-11T15:30:05 549 26.3 39 0 57
388 2026-06-11T15:31:05 575 26.3 39 0 55
389 2026-06-11T15:32:06 539 26.3 39 0 57
390 2026-06-11T15:33:06 565 26.3 39 0 55
391 2026-06-11T15:34:07 526 26.5 39 0 55
392 2026-06-11T15:35:07 575 26.3 39 0 55
393 2026-06-11T15:37:08 565 26.3 39 0 55
394 2026-06-11T15:38:09 549 26.5 39 0 55
395 2026-06-11T15:39:10 533 26.5 40 0 55
396 2026-06-11T15:40:10 565 26.3 40 0 55
397 2026-06-11T15:41:10 565 26.5 39 0 55
398 2026-06-11T15:42:11 549 26.3 40 0 55
399 2026-06-11T15:43:11 529 26.5 39 0 55
400 2026-06-11T15:45:12 581 26.5 39 0 55
401 2026-06-11T15:46:13 596 26.5 40 0 55
402 2026-06-11T15:47:13 546 26.5 39 0 55
403 2026-06-11T15:48:14 575 26.3 39 0 55
404 2026-06-11T15:49:14 589 26.5 40 0 55
405 2026-06-11T15:50:15 540 26.5 39 0 55
406 2026-06-11T15:51:15 596 26.3 40 0 55
407 2026-06-11T15:52:16 529 26.5 40 0 57
408 2026-06-11T15:53:16 540 26.5 39 0 55
409 2026-06-11T15:54:17 575 26.5 39 0 55
410 2026-06-11T15:55:17 545 26.5 39 0 55
411 2026-06-11T15:56:18 581 26.3 39 0 55
412 2026-06-11T15:57:18 545 26.5 39 0 57
413 2026-06-11T15:58:19 545 26.5 40 0 57
414 2026-06-11T15:59:19 529 26.5 40 0 57
415 2026-06-11T16:00:20 546 26.5 40 0 55
416 2026-06-11T16:01:20 546 26.5 39 0 57
417 2026-06-11T16:02:21 556 26.3 39 0 57
418 2026-06-11T16:03:21 545 26.5 39 0 57
419 2026-06-11T16:04:22 556 26.5 38 0 55
420 2026-06-11T16:05:23 575 26.6 39 0 55
421 2026-06-11T16:06:23 565 26.6 38 0 57
422 2026-06-11T16:07:23 629 26.6 39 0 57
423 2026-06-11T16:08:24 549 26.5 39 0 57
424 2026-06-11T16:09:24 556 26.5 39 0 55
425 2026-06-11T16:10:25 570 26.5 40 0 55
426 2026-06-11T16:11:25 565 26.5 39 0 55
427 2026-06-11T16:12:26 575 26.5 38 0 55
428 2026-06-11T16:13:26 615 26.6 39 0 57
429 2026-06-11T16:14:27 556 26.5 39 0 55
430 2026-06-11T16:15:27 570 26.6 39 0 55
431 2026-06-11T16:16:28 575 26.6 39 0 55
432 2026-06-11T16:17:28 556 26.6 39 0 55
433 2026-06-11T16:18:29 575 26.5 40 0 55
434 2026-06-11T16:19:29 565 26.6 39 0 55
435 2026-06-11T16:20:30 556 26.5 39 0 55
436 2026-06-11T16:21:31 565 26.6 40 0 55
437 2026-06-11T16:22:31 533 26.6 39 0 57
438 2026-06-11T16:23:31 575 26.6 40 0 55
439 2026-06-11T16:24:32 570 26.6 39 0 55
440 2026-06-11T16:25:33 556 26.6 39 0 57
441 2026-06-11T16:26:33 545 26.6 39 0 55
442 2026-06-11T16:27:33 589 26.5 39 0 57
443 2026-06-11T16:28:34 549 26.6 39 0 55
444 2026-06-11T16:29:34 581 26.6 39 0 55

View File

@@ -0,0 +1,415 @@
time,co2,temperature,humidity,windows,battery
2026-06-11T08:46:55,400,25.1,39,0,36
2026-06-11T08:49:55,405,25.1,39,0,36
2026-06-11T08:50:55,413,25.1,38,0,36
2026-06-11T08:51:56,425,25.1,39,0,36
2026-06-11T08:53:57,408,25.2,39,0,36
2026-06-11T08:54:57,418,25.2,38,0,36
2026-06-11T08:55:58,425,25.2,39,0,36
2026-06-11T08:56:58,418,25.2,39,0,36
2026-06-11T08:57:59,423,25.2,38,0,36
2026-06-11T08:59:00,442,25.2,39,0,36
2026-06-11T09:00:00,426,25.2,39,0,36
2026-06-11T09:01:01,426,25.1,38,0,33
2026-06-11T09:02:01,434,25.3,39,0,36
2026-06-11T09:03:01,423,25.3,39,0,36
2026-06-11T09:05:02,434,25.3,39,0,36
2026-06-11T09:06:03,439,25.3,38,0,36
2026-06-11T09:07:04,454,25.3,39,0,36
2026-06-11T09:08:04,439,25.3,39,0,36
2026-06-11T09:10:05,454,25.3,39,0,36
2026-06-11T09:11:05,439,25.5,38,0,36
2026-06-11T09:12:06,454,25.3,38,0,36
2026-06-11T09:13:06,454,25.5,39,0,36
2026-06-11T09:14:07,439,25.3,39,0,36
2026-06-11T09:15:07,439,25.3,39,0,36
2026-06-11T09:16:08,434,25.3,39,0,36
2026-06-11T09:17:09,445,25.3,38,0,36
2026-06-11T09:18:09,459,25.3,38,0,36
2026-06-11T09:19:09,459,25.2,39,0,36
2026-06-11T09:21:10,434,25.3,39,0,36
2026-06-11T09:22:11,434,25.2,39,0,36
2026-06-11T09:23:12,426,25.2,39,0,36
2026-06-11T09:24:12,423,25.2,38,0,36
2026-06-11T09:25:12,442,25.2,39,0,36
2026-06-11T09:26:13,426,25.2,38,0,36
2026-06-11T09:27:13,438,25.1,38,0,36
2026-06-11T09:28:14,438,25.1,38,0,36
2026-06-11T09:29:15,438,25.1,39,0,36
2026-06-11T09:30:15,418,25.2,39,0,36
2026-06-11T09:31:16,418,25.1,38,0,36
2026-06-11T09:32:16,418,25.2,39,0,36
2026-06-11T09:33:17,415,25.1,39,0,36
2026-06-11T09:34:17,418,25.1,38,0,36
2026-06-11T09:35:18,427,25,39,0,36
2026-06-11T09:36:18,413,25.1,39,0,36
2026-06-11T09:37:19,415,25,39,0,36
2026-06-11T09:39:20,408,25,39,0,36
2026-06-11T09:40:20,405,25.1,39,0,36
2026-06-11T09:41:20,408,25,38,0,36
2026-06-11T09:42:21,422,25,38,0,36
2026-06-11T09:43:21,418,25,38,0,36
2026-06-11T09:44:22,418,25.1,39,0,36
2026-06-11T09:45:23,408,25.1,39,0,36
2026-06-11T09:46:23,408,25.1,38,0,36
2026-06-11T09:47:24,422,25.1,38,0,36
2026-06-11T09:48:24,414,25.1,39,0,36
2026-06-11T09:49:24,408,25,38,0,36
2026-06-11T09:50:25,414,25.1,39,0,36
2026-06-11T09:51:26,405,25,38,0,36
2026-06-11T09:52:26,418,25.1,39,0,36
2026-06-11T09:53:27,408,25.1,38,0,36
2026-06-11T09:54:27,422,25.1,39,0,36
2026-06-11T09:55:28,400,25,39,0,36
2026-06-11T09:57:28,400,25,38,0,36
2026-06-11T10:00:29,414,25,39,0,36
2026-06-11T10:01:29,414,25,39,0,36
2026-06-11T10:02:30,400,25,38,0,36
2026-06-11T10:04:30,420,25,39,0,36
2026-06-11T10:05:31,400,25,40,0,36
2026-06-11T10:07:31,400,25,39,0,36
2026-06-11T10:10:32,414,25,39,0,36
2026-06-11T10:11:32,407,25,39,0,36
2026-06-11T10:12:33,414,24.8,39,0,36
2026-06-11T10:13:33,488,25,38,0,36
2026-06-11T10:14:34,424,24.8,39,0,36
2026-06-11T10:16:35,414,24.8,39,0,36
2026-06-11T10:17:35,488,25,39,0,36
2026-06-11T10:18:36,414,24.8,39,0,36
2026-06-11T10:19:36,485,24.8,39,0,36
2026-06-11T10:20:37,477,25,39,0,36
2026-06-11T10:21:37,400,24.8,39,0,36
2026-06-11T10:23:38,480,24.8,39,0,36
2026-06-11T10:24:38,480,25,39,0,36
2026-06-11T10:25:39,409,25,39,0,36
2026-06-11T10:26:39,414,25,39,0,36
2026-06-11T10:27:40,420,25,39,0,36
2026-06-11T10:28:40,420,25,39,0,36
2026-06-11T10:29:41,420,24.8,40,0,31
2026-06-11T10:30:41,475,25,39,0,36
2026-06-11T10:31:42,420,25,39,0,36
2026-06-11T10:32:42,420,25,39,0,36
2026-06-11T10:33:43,427,25,40,0,36
2026-06-11T10:34:43,420,25.1,39,0,36
2026-06-11T10:35:44,432,25.1,40,0,36
2026-06-11T10:36:44,415,25,39,0,36
2026-06-11T10:37:45,436,25.1,40,0,36
2026-06-11T10:38:45,417,25.1,39,0,36
2026-06-11T10:39:46,432,25.1,39,0,36
2026-06-11T10:40:46,436,25.1,39,0,36
2026-06-11T10:41:47,441,25,39,0,36
2026-06-11T10:42:47,427,25.1,40,0,36
2026-06-11T10:43:48,425,25.1,39,0,33
2026-06-11T10:44:49,436,25.1,39,0,36
2026-06-11T10:45:49,436,25.1,39,0,36
2026-06-11T10:46:49,441,25.1,39,0,36
2026-06-11T10:47:50,441,25.1,39,0,36
2026-06-11T10:48:50,436,25.1,40,0,36
2026-06-11T10:49:51,417,25.1,39,0,36
2026-06-11T10:50:52,441,25,39,0,36
2026-06-11T10:51:52,424,25.1,39,0,36
2026-06-11T10:52:52,441,25.1,40,0,36
2026-06-11T10:53:53,425,25.1,39,0,36
2026-06-11T10:54:54,441,25.1,39,0,36
2026-06-11T10:55:54,436,25.1,40,0,36
2026-06-11T10:56:54,420,25.1,39,0,36
2026-06-11T10:57:55,441,25.1,39,0,36
2026-06-11T10:58:55,441,25.1,39,0,36
2026-06-11T10:59:56,441,25,39,0,33
2026-06-11T11:00:56,427,25.1,39,0,36
2026-06-11T11:02:57,441,25.1,40,0,36
2026-06-11T11:03:58,420,25.1,40,0,36
2026-06-11T11:04:58,428,25.1,40,0,36
2026-06-11T11:05:59,428,25.1,39,0,36
2026-06-11T11:06:59,445,25.1,39,0,36
2026-06-11T11:08:00,441,25.1,39,0,36
2026-06-11T11:09:01,436,25.1,39,0,36
2026-06-11T11:10:01,436,25.1,39,0,36
2026-06-11T11:11:01,445,25.1,39,0,36
2026-06-11T11:12:02,445,25.1,39,0,36
2026-06-11T11:13:02,445,25.1,39,0,36
2026-06-11T11:14:03,445,25.1,39,0,36
2026-06-11T11:15:03,441,25.1,39,0,36
2026-06-11T11:16:04,441,25.1,40,0,36
2026-06-11T11:17:04,428,25.1,39,0,36
2026-06-11T11:18:05,441,25.1,39,0,36
2026-06-11T11:19:05,441,25.1,40,0,36
2026-06-11T11:20:06,428,25.1,39,0,36
2026-06-11T11:21:06,432,25.1,39,0,36
2026-06-11T11:22:07,441,25.1,39,0,36
2026-06-11T11:23:07,441,25.1,39,0,36
2026-06-11T11:25:09,441,25.1,39,0,36
2026-06-11T11:26:09,436,25.1,39,0,36
2026-06-11T11:27:09,436,25.1,39,0,36
2026-06-11T11:29:10,459,25.1,39,0,36
2026-06-11T11:30:11,459,25.1,39,0,33
2026-06-11T11:32:12,441,25.1,38,0,36
2026-06-11T11:33:12,455,25.1,39,0,36
2026-06-11T11:34:13,432,25.1,40,0,33
2026-06-11T11:35:13,420,25.1,39,0,36
2026-06-11T11:36:14,432,25.2,38,0,36
2026-06-11T11:37:14,436,25.1,39,0,36
2026-06-11T11:38:15,427,25.1,39,0,36
2026-06-11T11:39:16,432,25.2,39,0,36
2026-06-11T11:40:16,441,25.2,39,0,36
2026-06-11T11:41:16,432,25.2,39,0,36
2026-06-11T11:42:17,432,25.2,38,0,36
2026-06-11T11:43:17,455,25.2,39,0,36
2026-06-11T11:44:18,441,25.2,39,0,36
2026-06-11T11:45:19,441,25.2,39,0,33
2026-06-11T11:46:19,441,25.2,38,0,36
2026-06-11T11:47:19,459,25.2,39,0,36
2026-06-11T11:48:20,450,25.2,39,0,36
2026-06-11T11:49:21,450,25.3,38,0,36
2026-06-11T11:50:21,464,25.3,40,0,36
2026-06-11T11:51:21,428,25.3,39,0,36
2026-06-11T11:52:22,450,25.3,39,0,36
2026-06-11T11:53:23,450,25.3,39,0,36
2026-06-11T11:54:23,450,25.3,39,0,36
2026-06-11T11:55:23,456,25.3,39,0,36
2026-06-11T11:56:24,456,25.3,39,0,36
2026-06-11T11:57:25,456,25.3,40,0,36
2026-06-11T11:58:25,447,25.3,38,0,36
2026-06-11T11:59:26,482,25.3,39,0,36
2026-06-11T12:00:26,482,25.3,39,0,33
2026-06-11T12:01:26,461,25.3,39,0,36
2026-06-11T12:02:27,470,25.2,39,0,36
2026-06-11T12:03:28,465,25.2,39,0,36
2026-06-11T12:04:28,461,25.3,39,0,36
2026-06-11T12:07:30,470,25.3,39,0,33
2026-06-11T12:10:31,465,25.3,39,0,36
2026-06-11T12:11:32,470,25.3,39,0,36
2026-06-11T12:12:32,470,25.3,39,0,36
2026-06-11T12:13:33,470,25.3,39,0,36
2026-06-11T12:14:33,470,25.2,39,0,36
2026-06-11T12:15:34,470,25.3,39,0,36
2026-06-11T12:16:34,470,25.3,40,0,36
2026-06-11T12:17:34,454,25.3,39,0,36
2026-06-11T12:18:35,465,25.3,39,0,36
2026-06-11T12:19:36,470,25.3,39,0,36
2026-06-11T12:20:36,461,25.3,38,0,36
2026-06-11T12:21:37,478,25.3,38,0,36
2026-06-11T12:22:37,478,25.3,38,0,36
2026-06-11T12:23:38,485,25.3,39,0,36
2026-06-11T12:24:38,465,25.3,38,0,36
2026-06-11T12:25:39,482,25.3,38,0,36
2026-06-11T12:27:40,470,25.5,39,0,36
2026-06-11T12:28:40,473,25.5,38,0,36
2026-06-11T12:30:41,473,25.3,39,0,33
2026-06-11T12:31:42,473,25.5,38,0,36
2026-06-11T12:32:42,485,25.3,38,0,36
2026-06-11T12:33:43,490,25.5,38,0,36
2026-06-11T12:34:43,485,25.5,39,0,36
2026-06-11T12:35:44,473,25.5,38,0,36
2026-06-11T12:36:44,490,25.5,39,0,36
2026-06-11T12:37:45,473,25.5,39,0,36
2026-06-11T12:38:45,478,25.5,38,0,36
2026-06-11T12:39:46,490,25.5,39,0,36
2026-06-11T12:40:46,473,25.5,38,0,33
2026-06-11T12:41:47,493,25.5,39,0,33
2026-06-11T12:42:47,473,25.6,39,0,36
2026-06-11T12:43:48,478,25.5,38,0,33
2026-06-11T12:44:48,493,25.5,38,0,36
2026-06-11T12:45:49,493,25.5,39,0,33
2026-06-11T12:46:49,490,25.6,38,0,36
2026-06-11T12:47:50,493,25.6,39,0,36
2026-06-11T12:48:50,481,25.6,38,0,36
2026-06-11T12:49:51,493,25.6,38,0,36
2026-06-11T12:50:51,496,25.6,38,0,36
2026-06-11T12:51:52,505,25.6,38,0,36
2026-06-11T12:52:52,505,25.6,38,0,36
2026-06-11T12:53:53,505,25.6,38,0,33
2026-06-11T12:54:53,496,25.6,38,0,36
2026-06-11T12:55:54,496,25.6,38,0,36
2026-06-11T12:56:54,505,25.7,38,0,33
2026-06-11T12:57:55,505,25.6,39,0,33
2026-06-11T12:58:55,492,25.6,39,0,36
2026-06-11T12:59:56,490,25.6,38,0,36
2026-06-11T13:00:56,505,25.7,39,0,36
2026-06-11T13:01:57,505,25.6,38,0,33
2026-06-11T13:02:57,507,25.6,39,0,33
2026-06-11T13:03:58,492,25.6,38,0,33
2026-06-11T13:04:58,507,25.7,39,0,33
2026-06-11T13:05:59,492,25.7,38,0,33
2026-06-11T13:06:59,507,25.7,39,0,36
2026-06-11T13:08:00,481,25.7,38,0,36
2026-06-11T13:09:00,505,25.7,39,0,36
2026-06-11T13:10:01,492,25.7,39,0,33
2026-06-11T13:11:02,492,25.7,38,0,33
2026-06-11T13:12:02,507,25.7,38,0,36
2026-06-11T13:13:02,521,25.6,38,0,36
2026-06-11T13:14:03,521,25.7,38,0,36
2026-06-11T13:15:03,521,25.7,39,0,36
2026-06-11T13:16:04,501,25.7,38,0,33
2026-06-11T13:17:04,517,25.7,38,0,33
2026-06-11T13:18:05,530,25.7,39,0,36
2026-06-11T13:19:05,508,25.7,39,0,33
2026-06-11T13:20:06,508,25.7,39,0,33
2026-06-11T13:21:06,518,25.8,39,0,36
2026-06-11T13:22:07,523,25.8,38,0,36
2026-06-11T13:23:07,530,25.8,39,0,33
2026-06-11T13:24:08,523,25.8,38,0,36
2026-06-11T13:25:09,542,25.8,38,0,33
2026-06-11T13:26:09,542,25.8,39,0,33
2026-06-11T13:27:09,526,25.8,38,0,33
2026-06-11T13:28:10,542,26,39,0,36
2026-06-11T13:29:11,441,26,38,0,36
2026-06-11T13:30:11,455,26,39,0,33
2026-06-11T13:31:11,451,26,38,0,33
2026-06-11T13:32:12,460,26,39,0,36
2026-06-11T13:33:13,441,26,38,0,36
2026-06-11T13:34:13,441,26,39,0,36
2026-06-11T13:35:13,441,26,38,0,36
2026-06-11T13:36:14,465,26,38,0,33
2026-06-11T13:37:14,460,26,39,0,36
2026-06-11T13:38:15,441,26,38,0,33
2026-06-11T13:39:16,455,26.1,39,0,33
2026-06-11T13:40:16,451,26,38,0,36
2026-06-11T13:41:16,465,26.1,39,0,33
2026-06-11T13:42:17,451,26,38,0,36
2026-06-11T13:43:18,465,26,38,0,33
2026-06-11T13:44:18,460,26.1,38,0,33
2026-06-11T13:45:18,465,26,38,0,33
2026-06-11T13:46:19,465,26.1,39,0,36
2026-06-11T13:47:19,451,26,38,0,36
2026-06-11T13:48:20,465,26.1,38,0,33
2026-06-11T13:49:20,465,26,39,0,36
2026-06-11T13:50:21,446,26,38,0,36
2026-06-11T13:51:22,460,26,38,0,33
2026-06-11T13:52:22,460,26,38,0,36
2026-06-11T13:53:22,460,26,39,0,36
2026-06-11T13:54:23,446,26,38,0,33
2026-06-11T13:55:23,460,26,39,0,36
2026-06-11T13:56:24,438,26,39,0,36
2026-06-11T13:57:24,438,26,38,0,33
2026-06-11T13:58:25,455,26,38,0,33
2026-06-11T13:59:25,460,26,39,0,33
2026-06-11T14:01:26,438,26,39,0,36
2026-06-11T14:02:27,438,26,37,0,36
2026-06-11T14:03:28,470,26.1,38,0,33
2026-06-11T14:04:28,455,26,38,0,36
2026-06-11T14:05:29,482,26,38,0,36
2026-06-11T14:08:30,452,26,38,0,36
2026-06-11T14:09:31,455,26.1,38,0,36
2026-06-11T14:10:31,455,26.1,38,0,33
2026-06-11T14:11:32,452,26.1,39,0,36
2026-06-11T14:12:32,441,26.1,39,0,36
2026-06-11T14:14:33,452,26.1,38,0,36
2026-06-11T14:15:34,455,26.1,38,0,33
2026-06-11T14:17:34,460,26.1,38,0,33
2026-06-11T14:19:36,455,26.1,38,0,33
2026-06-11T14:20:36,452,26,38,0,33
2026-06-11T14:21:36,455,26.1,38,0,36
2026-06-11T14:22:37,455,26,38,0,36
2026-06-11T14:23:38,452,26.1,38,0,36
2026-06-11T14:24:38,460,26.1,38,0,36
2026-06-11T14:25:39,465,26,37,0,36
2026-06-11T14:26:39,495,26.1,38,0,33
2026-06-11T14:27:40,475,26.1,38,0,36
2026-06-11T14:28:40,475,26.1,38,0,36
2026-06-11T14:29:40,471,26.1,38,0,33
2026-06-11T14:30:41,475,26.2,38,0,36
2026-06-11T14:31:41,475,26.1,39,0,36
2026-06-11T14:32:42,459,26.1,37,0,33
2026-06-11T14:33:43,492,26.2,38,0,36
2026-06-11T14:34:43,471,26.1,38,0,33
2026-06-11T14:35:44,475,26.1,38,0,33
2026-06-11T14:36:44,475,26.2,38,0,36
2026-06-11T14:37:45,475,26.1,38,0,33
2026-06-11T14:38:45,475,26.1,38,0,33
2026-06-11T14:39:46,471,26.2,37,0,33
2026-06-11T14:40:46,492,26.2,38,0,33
2026-06-11T14:41:47,465,26.1,37,0,36
2026-06-11T14:42:47,488,26.1,38,0,36
2026-06-11T14:43:48,475,26.2,38,0,36
2026-06-11T14:44:48,465,26.2,38,0,36
2026-06-11T14:45:49,465,26.2,38,0,36
2026-06-11T14:47:50,465,26.2,38,0,36
2026-06-11T14:48:50,475,26.2,38,0,33
2026-06-11T14:50:51,488,26.2,37,0,33
2026-06-11T14:52:52,475,26.2,38,0,33
2026-06-11T14:53:53,471,26.2,37,0,36
2026-06-11T14:54:53,488,26.2,38,0,33
2026-06-11T14:55:54,471,26.2,37,0,33
2026-06-11T14:56:54,483,26.2,38,0,33
2026-06-11T14:57:55,471,26.3,38,0,36
2026-06-11T14:58:55,475,26.3,38,0,33
2026-06-11T14:59:56,475,26.3,37,0,33
2026-06-11T15:00:56,488,26.3,38,0,33
2026-06-11T15:01:57,475,26.3,38,0,36
2026-06-11T15:02:57,475,26.3,37,0,36
2026-06-11T15:03:58,500,26.2,37,0,36
2026-06-11T15:04:58,495,26.2,37,0,33
2026-06-11T15:05:59,506,26.3,37,0,36
2026-06-11T15:06:59,515,26.3,38,0,33
2026-06-11T15:08:00,493,26.3,38,0,36
2026-06-11T15:09:00,497,26.3,38,0,36
2026-06-11T15:10:01,493,26.3,39,0,33
2026-06-11T15:11:01,493,26.5,37,0,33
2026-06-11T15:12:02,523,26.5,38,0,33
2026-06-11T15:13:02,506,26.5,38,0,33
2026-06-11T15:14:03,506,26.5,38,0,33
2026-06-11T15:15:03,506,26.5,38,0,33
2026-06-11T15:16:04,521,26.5,38,0,36
2026-06-11T15:17:04,526,26.5,37,0,36
2026-06-11T15:18:05,544,26.5,38,0,33
2026-06-11T15:21:07,529,26.3,38,0,33
2026-06-11T15:22:07,526,26.5,38,0,33
2026-06-11T15:23:07,529,26.5,38,0,36
2026-06-11T15:25:08,518,26.6,38,0,33
2026-06-11T15:26:09,538,26.5,38,0,36
2026-06-11T15:28:10,541,26.5,37,0,33
2026-06-11T15:29:10,556,26.5,39,0,33
2026-06-11T15:31:12,541,26.5,38,0,36
2026-06-11T15:33:13,541,26.5,38,0,36
2026-06-11T15:34:13,541,26.6,38,0,36
2026-06-11T15:35:13,546,26.5,38,0,33
2026-06-11T15:36:14,541,26.5,38,0,33
2026-06-11T15:38:15,529,26.6,38,0,33
2026-06-11T15:39:15,546,26.5,38,0,33
2026-06-11T15:40:16,546,26.5,37,0,36
2026-06-11T15:41:16,566,26.5,38,0,36
2026-06-11T15:42:17,541,26.5,39,0,36
2026-06-11T15:44:18,534,26.5,39,0,31
2026-06-11T15:45:18,524,26.5,39,0,33
2026-06-11T15:46:19,524,26.6,38,0,33
2026-06-11T15:47:20,551,26.5,38,0,36
2026-06-11T15:48:20,546,26.5,38,0,33
2026-06-11T15:49:20,541,26.5,38,0,33
2026-06-11T15:50:21,546,26.3,38,0,33
2026-06-11T15:51:22,541,26.5,38,0,33
2026-06-11T15:53:23,546,26.5,38,0,33
2026-06-11T15:54:23,541,26.5,38,0,33
2026-06-11T15:55:23,546,26.5,39,0,33
2026-06-11T15:56:24,534,26.6,38,0,33
2026-06-11T15:57:24,551,26.6,38,0,33
2026-06-11T15:58:25,551,26.6,37,0,33
2026-06-11T15:59:25,578,26.6,37,0,33
2026-06-11T16:00:26,566,26.6,39,0,36
2026-06-11T16:01:26,534,26.5,39,0,33
2026-06-11T16:02:27,529,26.6,37,0,36
2026-06-11T16:03:27,566,26.6,38,0,33
2026-06-11T16:04:28,551,26.6,39,0,33
2026-06-11T16:05:29,534,26.6,39,0,33
2026-06-11T16:06:29,540,26.6,38,0,33
2026-06-11T16:07:29,551,26.6,37,0,36
2026-06-11T16:08:30,571,26.6,38,0,33
2026-06-11T16:09:31,556,26.6,38,0,36
2026-06-11T16:10:31,546,26.6,38,0,33
2026-06-11T16:11:31,551,26.5,38,0,33
2026-06-11T16:12:32,551,26.6,38,0,36
2026-06-11T16:13:32,551,26.6,39,0,36
2026-06-11T16:14:33,529,26.6,38,0,33
2026-06-11T16:15:33,551,26.6,37,0,36
2026-06-11T16:16:34,571,26.6,38,0,33
2026-06-11T16:17:34,571,26.6,39,0,31
2026-06-11T16:18:35,529,26.6,37,0,33
2026-06-11T16:19:35,566,26.6,38,0,36
2026-06-11T16:22:37,551,26.6,38,0,33
2026-06-11T16:23:38,556,26.6,38,0,33
2026-06-11T16:24:38,562,26.6,39,0,33
2026-06-11T16:25:38,534,26.6,38,0,33
2026-06-11T16:26:39,562,26.6,39,0,33
2026-06-11T16:27:40,540,26.6,38,0,33
2026-06-11T16:28:40,551,26.6,39,0,33
2026-06-11T16:29:41,542,26.6,38,0,33
1 time co2 temperature humidity windows battery
2 2026-06-11T08:46:55 400 25.1 39 0 36
3 2026-06-11T08:49:55 405 25.1 39 0 36
4 2026-06-11T08:50:55 413 25.1 38 0 36
5 2026-06-11T08:51:56 425 25.1 39 0 36
6 2026-06-11T08:53:57 408 25.2 39 0 36
7 2026-06-11T08:54:57 418 25.2 38 0 36
8 2026-06-11T08:55:58 425 25.2 39 0 36
9 2026-06-11T08:56:58 418 25.2 39 0 36
10 2026-06-11T08:57:59 423 25.2 38 0 36
11 2026-06-11T08:59:00 442 25.2 39 0 36
12 2026-06-11T09:00:00 426 25.2 39 0 36
13 2026-06-11T09:01:01 426 25.1 38 0 33
14 2026-06-11T09:02:01 434 25.3 39 0 36
15 2026-06-11T09:03:01 423 25.3 39 0 36
16 2026-06-11T09:05:02 434 25.3 39 0 36
17 2026-06-11T09:06:03 439 25.3 38 0 36
18 2026-06-11T09:07:04 454 25.3 39 0 36
19 2026-06-11T09:08:04 439 25.3 39 0 36
20 2026-06-11T09:10:05 454 25.3 39 0 36
21 2026-06-11T09:11:05 439 25.5 38 0 36
22 2026-06-11T09:12:06 454 25.3 38 0 36
23 2026-06-11T09:13:06 454 25.5 39 0 36
24 2026-06-11T09:14:07 439 25.3 39 0 36
25 2026-06-11T09:15:07 439 25.3 39 0 36
26 2026-06-11T09:16:08 434 25.3 39 0 36
27 2026-06-11T09:17:09 445 25.3 38 0 36
28 2026-06-11T09:18:09 459 25.3 38 0 36
29 2026-06-11T09:19:09 459 25.2 39 0 36
30 2026-06-11T09:21:10 434 25.3 39 0 36
31 2026-06-11T09:22:11 434 25.2 39 0 36
32 2026-06-11T09:23:12 426 25.2 39 0 36
33 2026-06-11T09:24:12 423 25.2 38 0 36
34 2026-06-11T09:25:12 442 25.2 39 0 36
35 2026-06-11T09:26:13 426 25.2 38 0 36
36 2026-06-11T09:27:13 438 25.1 38 0 36
37 2026-06-11T09:28:14 438 25.1 38 0 36
38 2026-06-11T09:29:15 438 25.1 39 0 36
39 2026-06-11T09:30:15 418 25.2 39 0 36
40 2026-06-11T09:31:16 418 25.1 38 0 36
41 2026-06-11T09:32:16 418 25.2 39 0 36
42 2026-06-11T09:33:17 415 25.1 39 0 36
43 2026-06-11T09:34:17 418 25.1 38 0 36
44 2026-06-11T09:35:18 427 25 39 0 36
45 2026-06-11T09:36:18 413 25.1 39 0 36
46 2026-06-11T09:37:19 415 25 39 0 36
47 2026-06-11T09:39:20 408 25 39 0 36
48 2026-06-11T09:40:20 405 25.1 39 0 36
49 2026-06-11T09:41:20 408 25 38 0 36
50 2026-06-11T09:42:21 422 25 38 0 36
51 2026-06-11T09:43:21 418 25 38 0 36
52 2026-06-11T09:44:22 418 25.1 39 0 36
53 2026-06-11T09:45:23 408 25.1 39 0 36
54 2026-06-11T09:46:23 408 25.1 38 0 36
55 2026-06-11T09:47:24 422 25.1 38 0 36
56 2026-06-11T09:48:24 414 25.1 39 0 36
57 2026-06-11T09:49:24 408 25 38 0 36
58 2026-06-11T09:50:25 414 25.1 39 0 36
59 2026-06-11T09:51:26 405 25 38 0 36
60 2026-06-11T09:52:26 418 25.1 39 0 36
61 2026-06-11T09:53:27 408 25.1 38 0 36
62 2026-06-11T09:54:27 422 25.1 39 0 36
63 2026-06-11T09:55:28 400 25 39 0 36
64 2026-06-11T09:57:28 400 25 38 0 36
65 2026-06-11T10:00:29 414 25 39 0 36
66 2026-06-11T10:01:29 414 25 39 0 36
67 2026-06-11T10:02:30 400 25 38 0 36
68 2026-06-11T10:04:30 420 25 39 0 36
69 2026-06-11T10:05:31 400 25 40 0 36
70 2026-06-11T10:07:31 400 25 39 0 36
71 2026-06-11T10:10:32 414 25 39 0 36
72 2026-06-11T10:11:32 407 25 39 0 36
73 2026-06-11T10:12:33 414 24.8 39 0 36
74 2026-06-11T10:13:33 488 25 38 0 36
75 2026-06-11T10:14:34 424 24.8 39 0 36
76 2026-06-11T10:16:35 414 24.8 39 0 36
77 2026-06-11T10:17:35 488 25 39 0 36
78 2026-06-11T10:18:36 414 24.8 39 0 36
79 2026-06-11T10:19:36 485 24.8 39 0 36
80 2026-06-11T10:20:37 477 25 39 0 36
81 2026-06-11T10:21:37 400 24.8 39 0 36
82 2026-06-11T10:23:38 480 24.8 39 0 36
83 2026-06-11T10:24:38 480 25 39 0 36
84 2026-06-11T10:25:39 409 25 39 0 36
85 2026-06-11T10:26:39 414 25 39 0 36
86 2026-06-11T10:27:40 420 25 39 0 36
87 2026-06-11T10:28:40 420 25 39 0 36
88 2026-06-11T10:29:41 420 24.8 40 0 31
89 2026-06-11T10:30:41 475 25 39 0 36
90 2026-06-11T10:31:42 420 25 39 0 36
91 2026-06-11T10:32:42 420 25 39 0 36
92 2026-06-11T10:33:43 427 25 40 0 36
93 2026-06-11T10:34:43 420 25.1 39 0 36
94 2026-06-11T10:35:44 432 25.1 40 0 36
95 2026-06-11T10:36:44 415 25 39 0 36
96 2026-06-11T10:37:45 436 25.1 40 0 36
97 2026-06-11T10:38:45 417 25.1 39 0 36
98 2026-06-11T10:39:46 432 25.1 39 0 36
99 2026-06-11T10:40:46 436 25.1 39 0 36
100 2026-06-11T10:41:47 441 25 39 0 36
101 2026-06-11T10:42:47 427 25.1 40 0 36
102 2026-06-11T10:43:48 425 25.1 39 0 33
103 2026-06-11T10:44:49 436 25.1 39 0 36
104 2026-06-11T10:45:49 436 25.1 39 0 36
105 2026-06-11T10:46:49 441 25.1 39 0 36
106 2026-06-11T10:47:50 441 25.1 39 0 36
107 2026-06-11T10:48:50 436 25.1 40 0 36
108 2026-06-11T10:49:51 417 25.1 39 0 36
109 2026-06-11T10:50:52 441 25 39 0 36
110 2026-06-11T10:51:52 424 25.1 39 0 36
111 2026-06-11T10:52:52 441 25.1 40 0 36
112 2026-06-11T10:53:53 425 25.1 39 0 36
113 2026-06-11T10:54:54 441 25.1 39 0 36
114 2026-06-11T10:55:54 436 25.1 40 0 36
115 2026-06-11T10:56:54 420 25.1 39 0 36
116 2026-06-11T10:57:55 441 25.1 39 0 36
117 2026-06-11T10:58:55 441 25.1 39 0 36
118 2026-06-11T10:59:56 441 25 39 0 33
119 2026-06-11T11:00:56 427 25.1 39 0 36
120 2026-06-11T11:02:57 441 25.1 40 0 36
121 2026-06-11T11:03:58 420 25.1 40 0 36
122 2026-06-11T11:04:58 428 25.1 40 0 36
123 2026-06-11T11:05:59 428 25.1 39 0 36
124 2026-06-11T11:06:59 445 25.1 39 0 36
125 2026-06-11T11:08:00 441 25.1 39 0 36
126 2026-06-11T11:09:01 436 25.1 39 0 36
127 2026-06-11T11:10:01 436 25.1 39 0 36
128 2026-06-11T11:11:01 445 25.1 39 0 36
129 2026-06-11T11:12:02 445 25.1 39 0 36
130 2026-06-11T11:13:02 445 25.1 39 0 36
131 2026-06-11T11:14:03 445 25.1 39 0 36
132 2026-06-11T11:15:03 441 25.1 39 0 36
133 2026-06-11T11:16:04 441 25.1 40 0 36
134 2026-06-11T11:17:04 428 25.1 39 0 36
135 2026-06-11T11:18:05 441 25.1 39 0 36
136 2026-06-11T11:19:05 441 25.1 40 0 36
137 2026-06-11T11:20:06 428 25.1 39 0 36
138 2026-06-11T11:21:06 432 25.1 39 0 36
139 2026-06-11T11:22:07 441 25.1 39 0 36
140 2026-06-11T11:23:07 441 25.1 39 0 36
141 2026-06-11T11:25:09 441 25.1 39 0 36
142 2026-06-11T11:26:09 436 25.1 39 0 36
143 2026-06-11T11:27:09 436 25.1 39 0 36
144 2026-06-11T11:29:10 459 25.1 39 0 36
145 2026-06-11T11:30:11 459 25.1 39 0 33
146 2026-06-11T11:32:12 441 25.1 38 0 36
147 2026-06-11T11:33:12 455 25.1 39 0 36
148 2026-06-11T11:34:13 432 25.1 40 0 33
149 2026-06-11T11:35:13 420 25.1 39 0 36
150 2026-06-11T11:36:14 432 25.2 38 0 36
151 2026-06-11T11:37:14 436 25.1 39 0 36
152 2026-06-11T11:38:15 427 25.1 39 0 36
153 2026-06-11T11:39:16 432 25.2 39 0 36
154 2026-06-11T11:40:16 441 25.2 39 0 36
155 2026-06-11T11:41:16 432 25.2 39 0 36
156 2026-06-11T11:42:17 432 25.2 38 0 36
157 2026-06-11T11:43:17 455 25.2 39 0 36
158 2026-06-11T11:44:18 441 25.2 39 0 36
159 2026-06-11T11:45:19 441 25.2 39 0 33
160 2026-06-11T11:46:19 441 25.2 38 0 36
161 2026-06-11T11:47:19 459 25.2 39 0 36
162 2026-06-11T11:48:20 450 25.2 39 0 36
163 2026-06-11T11:49:21 450 25.3 38 0 36
164 2026-06-11T11:50:21 464 25.3 40 0 36
165 2026-06-11T11:51:21 428 25.3 39 0 36
166 2026-06-11T11:52:22 450 25.3 39 0 36
167 2026-06-11T11:53:23 450 25.3 39 0 36
168 2026-06-11T11:54:23 450 25.3 39 0 36
169 2026-06-11T11:55:23 456 25.3 39 0 36
170 2026-06-11T11:56:24 456 25.3 39 0 36
171 2026-06-11T11:57:25 456 25.3 40 0 36
172 2026-06-11T11:58:25 447 25.3 38 0 36
173 2026-06-11T11:59:26 482 25.3 39 0 36
174 2026-06-11T12:00:26 482 25.3 39 0 33
175 2026-06-11T12:01:26 461 25.3 39 0 36
176 2026-06-11T12:02:27 470 25.2 39 0 36
177 2026-06-11T12:03:28 465 25.2 39 0 36
178 2026-06-11T12:04:28 461 25.3 39 0 36
179 2026-06-11T12:07:30 470 25.3 39 0 33
180 2026-06-11T12:10:31 465 25.3 39 0 36
181 2026-06-11T12:11:32 470 25.3 39 0 36
182 2026-06-11T12:12:32 470 25.3 39 0 36
183 2026-06-11T12:13:33 470 25.3 39 0 36
184 2026-06-11T12:14:33 470 25.2 39 0 36
185 2026-06-11T12:15:34 470 25.3 39 0 36
186 2026-06-11T12:16:34 470 25.3 40 0 36
187 2026-06-11T12:17:34 454 25.3 39 0 36
188 2026-06-11T12:18:35 465 25.3 39 0 36
189 2026-06-11T12:19:36 470 25.3 39 0 36
190 2026-06-11T12:20:36 461 25.3 38 0 36
191 2026-06-11T12:21:37 478 25.3 38 0 36
192 2026-06-11T12:22:37 478 25.3 38 0 36
193 2026-06-11T12:23:38 485 25.3 39 0 36
194 2026-06-11T12:24:38 465 25.3 38 0 36
195 2026-06-11T12:25:39 482 25.3 38 0 36
196 2026-06-11T12:27:40 470 25.5 39 0 36
197 2026-06-11T12:28:40 473 25.5 38 0 36
198 2026-06-11T12:30:41 473 25.3 39 0 33
199 2026-06-11T12:31:42 473 25.5 38 0 36
200 2026-06-11T12:32:42 485 25.3 38 0 36
201 2026-06-11T12:33:43 490 25.5 38 0 36
202 2026-06-11T12:34:43 485 25.5 39 0 36
203 2026-06-11T12:35:44 473 25.5 38 0 36
204 2026-06-11T12:36:44 490 25.5 39 0 36
205 2026-06-11T12:37:45 473 25.5 39 0 36
206 2026-06-11T12:38:45 478 25.5 38 0 36
207 2026-06-11T12:39:46 490 25.5 39 0 36
208 2026-06-11T12:40:46 473 25.5 38 0 33
209 2026-06-11T12:41:47 493 25.5 39 0 33
210 2026-06-11T12:42:47 473 25.6 39 0 36
211 2026-06-11T12:43:48 478 25.5 38 0 33
212 2026-06-11T12:44:48 493 25.5 38 0 36
213 2026-06-11T12:45:49 493 25.5 39 0 33
214 2026-06-11T12:46:49 490 25.6 38 0 36
215 2026-06-11T12:47:50 493 25.6 39 0 36
216 2026-06-11T12:48:50 481 25.6 38 0 36
217 2026-06-11T12:49:51 493 25.6 38 0 36
218 2026-06-11T12:50:51 496 25.6 38 0 36
219 2026-06-11T12:51:52 505 25.6 38 0 36
220 2026-06-11T12:52:52 505 25.6 38 0 36
221 2026-06-11T12:53:53 505 25.6 38 0 33
222 2026-06-11T12:54:53 496 25.6 38 0 36
223 2026-06-11T12:55:54 496 25.6 38 0 36
224 2026-06-11T12:56:54 505 25.7 38 0 33
225 2026-06-11T12:57:55 505 25.6 39 0 33
226 2026-06-11T12:58:55 492 25.6 39 0 36
227 2026-06-11T12:59:56 490 25.6 38 0 36
228 2026-06-11T13:00:56 505 25.7 39 0 36
229 2026-06-11T13:01:57 505 25.6 38 0 33
230 2026-06-11T13:02:57 507 25.6 39 0 33
231 2026-06-11T13:03:58 492 25.6 38 0 33
232 2026-06-11T13:04:58 507 25.7 39 0 33
233 2026-06-11T13:05:59 492 25.7 38 0 33
234 2026-06-11T13:06:59 507 25.7 39 0 36
235 2026-06-11T13:08:00 481 25.7 38 0 36
236 2026-06-11T13:09:00 505 25.7 39 0 36
237 2026-06-11T13:10:01 492 25.7 39 0 33
238 2026-06-11T13:11:02 492 25.7 38 0 33
239 2026-06-11T13:12:02 507 25.7 38 0 36
240 2026-06-11T13:13:02 521 25.6 38 0 36
241 2026-06-11T13:14:03 521 25.7 38 0 36
242 2026-06-11T13:15:03 521 25.7 39 0 36
243 2026-06-11T13:16:04 501 25.7 38 0 33
244 2026-06-11T13:17:04 517 25.7 38 0 33
245 2026-06-11T13:18:05 530 25.7 39 0 36
246 2026-06-11T13:19:05 508 25.7 39 0 33
247 2026-06-11T13:20:06 508 25.7 39 0 33
248 2026-06-11T13:21:06 518 25.8 39 0 36
249 2026-06-11T13:22:07 523 25.8 38 0 36
250 2026-06-11T13:23:07 530 25.8 39 0 33
251 2026-06-11T13:24:08 523 25.8 38 0 36
252 2026-06-11T13:25:09 542 25.8 38 0 33
253 2026-06-11T13:26:09 542 25.8 39 0 33
254 2026-06-11T13:27:09 526 25.8 38 0 33
255 2026-06-11T13:28:10 542 26 39 0 36
256 2026-06-11T13:29:11 441 26 38 0 36
257 2026-06-11T13:30:11 455 26 39 0 33
258 2026-06-11T13:31:11 451 26 38 0 33
259 2026-06-11T13:32:12 460 26 39 0 36
260 2026-06-11T13:33:13 441 26 38 0 36
261 2026-06-11T13:34:13 441 26 39 0 36
262 2026-06-11T13:35:13 441 26 38 0 36
263 2026-06-11T13:36:14 465 26 38 0 33
264 2026-06-11T13:37:14 460 26 39 0 36
265 2026-06-11T13:38:15 441 26 38 0 33
266 2026-06-11T13:39:16 455 26.1 39 0 33
267 2026-06-11T13:40:16 451 26 38 0 36
268 2026-06-11T13:41:16 465 26.1 39 0 33
269 2026-06-11T13:42:17 451 26 38 0 36
270 2026-06-11T13:43:18 465 26 38 0 33
271 2026-06-11T13:44:18 460 26.1 38 0 33
272 2026-06-11T13:45:18 465 26 38 0 33
273 2026-06-11T13:46:19 465 26.1 39 0 36
274 2026-06-11T13:47:19 451 26 38 0 36
275 2026-06-11T13:48:20 465 26.1 38 0 33
276 2026-06-11T13:49:20 465 26 39 0 36
277 2026-06-11T13:50:21 446 26 38 0 36
278 2026-06-11T13:51:22 460 26 38 0 33
279 2026-06-11T13:52:22 460 26 38 0 36
280 2026-06-11T13:53:22 460 26 39 0 36
281 2026-06-11T13:54:23 446 26 38 0 33
282 2026-06-11T13:55:23 460 26 39 0 36
283 2026-06-11T13:56:24 438 26 39 0 36
284 2026-06-11T13:57:24 438 26 38 0 33
285 2026-06-11T13:58:25 455 26 38 0 33
286 2026-06-11T13:59:25 460 26 39 0 33
287 2026-06-11T14:01:26 438 26 39 0 36
288 2026-06-11T14:02:27 438 26 37 0 36
289 2026-06-11T14:03:28 470 26.1 38 0 33
290 2026-06-11T14:04:28 455 26 38 0 36
291 2026-06-11T14:05:29 482 26 38 0 36
292 2026-06-11T14:08:30 452 26 38 0 36
293 2026-06-11T14:09:31 455 26.1 38 0 36
294 2026-06-11T14:10:31 455 26.1 38 0 33
295 2026-06-11T14:11:32 452 26.1 39 0 36
296 2026-06-11T14:12:32 441 26.1 39 0 36
297 2026-06-11T14:14:33 452 26.1 38 0 36
298 2026-06-11T14:15:34 455 26.1 38 0 33
299 2026-06-11T14:17:34 460 26.1 38 0 33
300 2026-06-11T14:19:36 455 26.1 38 0 33
301 2026-06-11T14:20:36 452 26 38 0 33
302 2026-06-11T14:21:36 455 26.1 38 0 36
303 2026-06-11T14:22:37 455 26 38 0 36
304 2026-06-11T14:23:38 452 26.1 38 0 36
305 2026-06-11T14:24:38 460 26.1 38 0 36
306 2026-06-11T14:25:39 465 26 37 0 36
307 2026-06-11T14:26:39 495 26.1 38 0 33
308 2026-06-11T14:27:40 475 26.1 38 0 36
309 2026-06-11T14:28:40 475 26.1 38 0 36
310 2026-06-11T14:29:40 471 26.1 38 0 33
311 2026-06-11T14:30:41 475 26.2 38 0 36
312 2026-06-11T14:31:41 475 26.1 39 0 36
313 2026-06-11T14:32:42 459 26.1 37 0 33
314 2026-06-11T14:33:43 492 26.2 38 0 36
315 2026-06-11T14:34:43 471 26.1 38 0 33
316 2026-06-11T14:35:44 475 26.1 38 0 33
317 2026-06-11T14:36:44 475 26.2 38 0 36
318 2026-06-11T14:37:45 475 26.1 38 0 33
319 2026-06-11T14:38:45 475 26.1 38 0 33
320 2026-06-11T14:39:46 471 26.2 37 0 33
321 2026-06-11T14:40:46 492 26.2 38 0 33
322 2026-06-11T14:41:47 465 26.1 37 0 36
323 2026-06-11T14:42:47 488 26.1 38 0 36
324 2026-06-11T14:43:48 475 26.2 38 0 36
325 2026-06-11T14:44:48 465 26.2 38 0 36
326 2026-06-11T14:45:49 465 26.2 38 0 36
327 2026-06-11T14:47:50 465 26.2 38 0 36
328 2026-06-11T14:48:50 475 26.2 38 0 33
329 2026-06-11T14:50:51 488 26.2 37 0 33
330 2026-06-11T14:52:52 475 26.2 38 0 33
331 2026-06-11T14:53:53 471 26.2 37 0 36
332 2026-06-11T14:54:53 488 26.2 38 0 33
333 2026-06-11T14:55:54 471 26.2 37 0 33
334 2026-06-11T14:56:54 483 26.2 38 0 33
335 2026-06-11T14:57:55 471 26.3 38 0 36
336 2026-06-11T14:58:55 475 26.3 38 0 33
337 2026-06-11T14:59:56 475 26.3 37 0 33
338 2026-06-11T15:00:56 488 26.3 38 0 33
339 2026-06-11T15:01:57 475 26.3 38 0 36
340 2026-06-11T15:02:57 475 26.3 37 0 36
341 2026-06-11T15:03:58 500 26.2 37 0 36
342 2026-06-11T15:04:58 495 26.2 37 0 33
343 2026-06-11T15:05:59 506 26.3 37 0 36
344 2026-06-11T15:06:59 515 26.3 38 0 33
345 2026-06-11T15:08:00 493 26.3 38 0 36
346 2026-06-11T15:09:00 497 26.3 38 0 36
347 2026-06-11T15:10:01 493 26.3 39 0 33
348 2026-06-11T15:11:01 493 26.5 37 0 33
349 2026-06-11T15:12:02 523 26.5 38 0 33
350 2026-06-11T15:13:02 506 26.5 38 0 33
351 2026-06-11T15:14:03 506 26.5 38 0 33
352 2026-06-11T15:15:03 506 26.5 38 0 33
353 2026-06-11T15:16:04 521 26.5 38 0 36
354 2026-06-11T15:17:04 526 26.5 37 0 36
355 2026-06-11T15:18:05 544 26.5 38 0 33
356 2026-06-11T15:21:07 529 26.3 38 0 33
357 2026-06-11T15:22:07 526 26.5 38 0 33
358 2026-06-11T15:23:07 529 26.5 38 0 36
359 2026-06-11T15:25:08 518 26.6 38 0 33
360 2026-06-11T15:26:09 538 26.5 38 0 36
361 2026-06-11T15:28:10 541 26.5 37 0 33
362 2026-06-11T15:29:10 556 26.5 39 0 33
363 2026-06-11T15:31:12 541 26.5 38 0 36
364 2026-06-11T15:33:13 541 26.5 38 0 36
365 2026-06-11T15:34:13 541 26.6 38 0 36
366 2026-06-11T15:35:13 546 26.5 38 0 33
367 2026-06-11T15:36:14 541 26.5 38 0 33
368 2026-06-11T15:38:15 529 26.6 38 0 33
369 2026-06-11T15:39:15 546 26.5 38 0 33
370 2026-06-11T15:40:16 546 26.5 37 0 36
371 2026-06-11T15:41:16 566 26.5 38 0 36
372 2026-06-11T15:42:17 541 26.5 39 0 36
373 2026-06-11T15:44:18 534 26.5 39 0 31
374 2026-06-11T15:45:18 524 26.5 39 0 33
375 2026-06-11T15:46:19 524 26.6 38 0 33
376 2026-06-11T15:47:20 551 26.5 38 0 36
377 2026-06-11T15:48:20 546 26.5 38 0 33
378 2026-06-11T15:49:20 541 26.5 38 0 33
379 2026-06-11T15:50:21 546 26.3 38 0 33
380 2026-06-11T15:51:22 541 26.5 38 0 33
381 2026-06-11T15:53:23 546 26.5 38 0 33
382 2026-06-11T15:54:23 541 26.5 38 0 33
383 2026-06-11T15:55:23 546 26.5 39 0 33
384 2026-06-11T15:56:24 534 26.6 38 0 33
385 2026-06-11T15:57:24 551 26.6 38 0 33
386 2026-06-11T15:58:25 551 26.6 37 0 33
387 2026-06-11T15:59:25 578 26.6 37 0 33
388 2026-06-11T16:00:26 566 26.6 39 0 36
389 2026-06-11T16:01:26 534 26.5 39 0 33
390 2026-06-11T16:02:27 529 26.6 37 0 36
391 2026-06-11T16:03:27 566 26.6 38 0 33
392 2026-06-11T16:04:28 551 26.6 39 0 33
393 2026-06-11T16:05:29 534 26.6 39 0 33
394 2026-06-11T16:06:29 540 26.6 38 0 33
395 2026-06-11T16:07:29 551 26.6 37 0 36
396 2026-06-11T16:08:30 571 26.6 38 0 33
397 2026-06-11T16:09:31 556 26.6 38 0 36
398 2026-06-11T16:10:31 546 26.6 38 0 33
399 2026-06-11T16:11:31 551 26.5 38 0 33
400 2026-06-11T16:12:32 551 26.6 38 0 36
401 2026-06-11T16:13:32 551 26.6 39 0 36
402 2026-06-11T16:14:33 529 26.6 38 0 33
403 2026-06-11T16:15:33 551 26.6 37 0 36
404 2026-06-11T16:16:34 571 26.6 38 0 33
405 2026-06-11T16:17:34 571 26.6 39 0 31
406 2026-06-11T16:18:35 529 26.6 37 0 33
407 2026-06-11T16:19:35 566 26.6 38 0 36
408 2026-06-11T16:22:37 551 26.6 38 0 33
409 2026-06-11T16:23:38 556 26.6 38 0 33
410 2026-06-11T16:24:38 562 26.6 39 0 33
411 2026-06-11T16:25:38 534 26.6 38 0 33
412 2026-06-11T16:26:39 562 26.6 39 0 33
413 2026-06-11T16:27:40 540 26.6 38 0 33
414 2026-06-11T16:28:40 551 26.6 39 0 33
415 2026-06-11T16:29:41 542 26.6 38 0 33

View File

@@ -0,0 +1,441 @@
time,co2,temperature,humidity,windows,battery
2026-06-11T08:45:47,552,25,40,0,31
2026-06-11T08:46:47,485,25,41,0,31
2026-06-11T08:47:48,463,25,40,0,31
2026-06-11T08:48:48,491,25,41,0,31
2026-06-11T08:49:49,463,25,41,0,31
2026-06-11T08:50:49,463,25.1,42,0,31
2026-06-11T08:51:50,461,25.1,40,0,31
2026-06-11T08:52:51,493,25.1,41,0,31
2026-06-11T08:53:51,478,25.1,40,0,31
2026-06-11T08:54:52,493,25.2,40,0,31
2026-06-11T08:55:52,493,25.2,40,0,31
2026-06-11T08:56:53,501,25.2,40,0,31
2026-06-11T08:57:53,514,25.2,41,0,31
2026-06-11T08:59:54,492,25.2,40,0,31
2026-06-11T09:00:55,514,25.2,41,0,31
2026-06-11T09:01:55,500,25.2,41,0,31
2026-06-11T09:02:56,508,25.2,41,0,31
2026-06-11T09:03:56,495,25.3,41,0,31
2026-06-11T09:04:57,508,25.3,40,0,31
2026-06-11T09:05:57,529,25.3,40,0,31
2026-06-11T09:06:58,529,25.3,41,0,31
2026-06-11T09:07:58,515,25.3,40,0,31
2026-06-11T09:08:59,537,25.3,41,0,31
2026-06-11T09:09:59,520,25.3,41,0,31
2026-06-11T09:11:00,520,25.5,42,0,31
2026-06-11T09:12:00,497,25.3,41,0,31
2026-06-11T09:13:01,527,25.3,41,0,31
2026-06-11T09:14:01,520,25.3,41,0,31
2026-06-11T09:15:02,515,25.3,41,0,31
2026-06-11T09:16:02,520,25.3,40,0,31
2026-06-11T09:17:03,537,25.3,40,0,31
2026-06-11T09:18:03,537,25.3,40,0,31
2026-06-11T09:20:04,531,25.2,41,0,31
2026-06-11T09:22:05,529,25.2,41,0,31
2026-06-11T09:23:06,500,25.2,40,0,31
2026-06-11T09:24:06,514,25.2,41,0,31
2026-06-11T09:25:07,495,25.2,41,0,31
2026-06-11T09:26:07,492,25.2,41,0,31
2026-06-11T09:27:08,486,25.2,41,0,31
2026-06-11T09:28:08,486,25.2,40,0,31
2026-06-11T09:29:09,506,25.2,40,0,31
2026-06-11T09:30:09,501,25.2,40,0,31
2026-06-11T09:31:10,501,25.1,40,0,31
2026-06-11T09:32:10,501,25.1,39,0,31
2026-06-11T09:33:11,508,25.2,40,0,31
2026-06-11T09:34:11,501,25.1,40,0,31
2026-06-11T09:35:12,501,25.1,40,0,31
2026-06-11T09:36:12,491,25.1,40,0,31
2026-06-11T09:37:13,493,25.1,40,0,29
2026-06-11T09:38:13,493,25.1,40,0,31
2026-06-11T09:40:14,493,25.1,40,0,29
2026-06-11T09:41:15,485,25.1,40,0,31
2026-06-11T09:42:15,491,25.1,40,0,31
2026-06-11T09:43:16,493,25.1,40,0,31
2026-06-11T09:44:16,493,25.1,40,0,31
2026-06-11T09:45:17,491,25.1,41,0,31
2026-06-11T09:46:17,478,25.1,41,0,31
2026-06-11T09:47:18,478,25.1,40,0,29
2026-06-11T09:48:18,493,25.1,40,0,31
2026-06-11T09:49:19,485,25.1,41,0,29
2026-06-11T09:50:19,476,25.1,39,0,29
2026-06-11T09:51:20,500,25.1,40,0,31
2026-06-11T09:52:20,485,25.1,41,0,31
2026-06-11T09:53:21,476,25.1,40,0,31
2026-06-11T09:54:21,491,25.1,40,0,31
2026-06-11T09:55:22,491,25.1,40,0,31
2026-06-11T09:56:22,493,25.1,40,0,31
2026-06-11T09:57:23,491,25.1,41,0,31
2026-06-11T09:58:23,470,25.1,40,0,31
2026-06-11T09:59:24,491,25.1,41,0,31
2026-06-11T10:00:25,476,25.1,41,0,31
2026-06-11T10:01:25,476,25.1,41,0,31
2026-06-11T10:03:26,470,25.1,41,0,31
2026-06-11T10:04:26,470,25.1,41,0,31
2026-06-11T10:05:27,476,25.1,41,0,31
2026-06-11T10:06:28,476,25.2,41,0,31
2026-06-11T10:07:28,470,25.1,40,0,31
2026-06-11T10:08:28,485,25.1,40,0,31
2026-06-11T10:09:29,491,25.1,40,0,31
2026-06-11T10:10:29,491,25.1,41,0,31
2026-06-11T10:11:30,476,25.1,41,0,31
2026-06-11T10:12:30,476,25,41,0,31
2026-06-11T10:13:31,476,25.1,40,0,33
2026-06-11T10:14:31,493,25.1,41,0,31
2026-06-11T10:15:32,470,25.1,41,0,31
2026-06-11T10:16:32,476,25.1,40,0,31
2026-06-11T10:17:33,485,25,41,0,31
2026-06-11T10:18:34,478,25,41,0,31
2026-06-11T10:19:34,454,25,41,0,31
2026-06-11T10:20:34,463,25.1,41,0,31
2026-06-11T10:21:35,454,25,40,0,31
2026-06-11T10:22:36,466,25.1,41,0,31
2026-06-11T10:23:36,470,25.1,41,0,31
2026-06-11T10:24:36,463,25.1,41,0,31
2026-06-11T10:25:37,478,25.1,41,0,31
2026-06-11T10:26:37,478,25.1,41,0,31
2026-06-11T10:28:38,492,25.1,41,0,31
2026-06-11T10:29:39,486,25.1,41,0,29
2026-06-11T10:30:39,486,25.2,41,0,29
2026-06-11T10:31:40,492,25.2,40,0,31
2026-06-11T10:32:40,506,25.2,41,0,29
2026-06-11T10:33:41,495,25.2,41,0,31
2026-06-11T10:34:41,495,25.2,42,0,31
2026-06-11T10:36:42,519,25.2,41,0,31
2026-06-11T10:37:43,508,25.2,41,0,31
2026-06-11T10:38:44,508,25.2,41,0,29
2026-06-11T10:39:44,515,25.2,41,0,31
2026-06-11T10:40:44,500,25.2,41,0,31
2026-06-11T10:41:45,508,25.2,41,0,31
2026-06-11T10:42:45,500,25.2,41,0,31
2026-06-11T10:43:46,508,25.1,41,0,29
2026-06-11T10:44:46,508,25.2,41,0,31
2026-06-11T10:45:47,500,25.2,41,0,31
2026-06-11T10:46:47,508,25.2,42,0,31
2026-06-11T10:47:48,486,25.2,42,0,31
2026-06-11T10:48:48,486,25.2,40,0,29
2026-06-11T10:49:49,519,25.1,40,0,31
2026-06-11T10:50:50,519,25.2,42,0,31
2026-06-11T10:51:50,486,25.2,42,0,31
2026-06-11T10:52:51,486,25.1,40,0,31
2026-06-11T10:53:51,514,25.1,40,0,29
2026-06-11T10:54:52,514,25.2,41,0,31
2026-06-11T10:55:52,508,25.1,42,0,31
2026-06-11T10:56:53,495,25.1,41,0,31
2026-06-11T10:57:53,508,25.2,41,0,29
2026-06-11T10:58:54,500,25.1,41,0,29
2026-06-11T10:59:54,500,25.1,41,0,31
2026-06-11T11:00:55,500,25.1,41,0,31
2026-06-11T11:01:55,508,25.2,42,0,31
2026-06-11T11:02:55,486,25.1,41,0,31
2026-06-11T11:03:56,500,25.1,42,0,31
2026-06-11T11:04:57,495,25.2,42,0,31
2026-06-11T11:05:57,495,25.2,41,0,29
2026-06-11T11:06:57,508,25.2,41,0,29
2026-06-11T11:07:58,508,25.2,42,0,31
2026-06-11T11:08:59,486,25.2,41,0,29
2026-06-11T11:09:59,508,25.2,42,0,29
2026-06-11T11:11:00,486,25.2,42,0,31
2026-06-11T11:12:00,495,25.2,41,0,31
2026-06-11T11:13:01,500,25.2,40,0,29
2026-06-11T11:14:01,531,25.2,40,0,31
2026-06-11T11:15:02,519,25.1,41,0,31
2026-06-11T11:16:02,508,25.2,41,0,31
2026-06-11T11:17:03,508,25.2,41,0,29
2026-06-11T11:18:03,500,25.2,41,0,31
2026-06-11T11:19:04,500,25.2,42,0,29
2026-06-11T11:20:04,497,25.2,41,0,31
2026-06-11T11:21:05,508,25.2,41,0,31
2026-06-11T11:22:05,500,25.2,41,0,31
2026-06-11T11:23:06,515,25.2,41,0,31
2026-06-11T11:24:06,508,25.2,41,0,31
2026-06-11T11:25:07,500,25.2,40,0,31
2026-06-11T11:26:07,531,25.2,41,0,31
2026-06-11T11:27:08,508,25.2,40,0,29
2026-06-11T11:28:08,529,25.2,41,0,31
2026-06-11T11:29:09,500,25.2,41,0,31
2026-06-11T11:30:09,500,25.2,40,0,29
2026-06-11T11:31:10,531,25.2,41,0,29
2026-06-11T11:32:10,508,25.2,41,0,31
2026-06-11T11:33:11,508,25.2,41,0,31
2026-06-11T11:34:11,500,25.2,41,0,31
2026-06-11T11:35:12,508,25.2,40,0,31
2026-06-11T11:36:12,514,25.2,41,0,31
2026-06-11T11:37:13,486,25.2,40,0,29
2026-06-11T11:38:13,501,25.2,41,0,31
2026-06-11T11:39:14,495,25.2,42,0,29
2026-06-11T11:40:14,495,25.2,39,0,29
2026-06-11T11:41:15,545,25.2,40,0,29
2026-06-11T11:42:15,531,25.2,41,0,31
2026-06-11T11:43:16,508,25.3,41,0,29
2026-06-11T11:44:16,520,25.3,42,0,31
2026-06-11T11:45:17,502,25.3,41,0,31
2026-06-11T11:47:18,520,25.5,41,0,31
2026-06-11T11:48:18,536,25.3,40,0,31
2026-06-11T11:49:19,543,25.5,40,0,31
2026-06-11T11:50:19,555,25.3,41,0,29
2026-06-11T11:51:20,536,25.3,42,0,29
2026-06-11T11:52:20,508,25.3,40,0,31
2026-06-11T11:54:21,520,25.3,40,0,29
2026-06-11T11:56:22,555,25.5,41,0,31
2026-06-11T11:57:23,539,25.5,40,0,31
2026-06-11T11:58:23,564,25.5,41,0,31
2026-06-11T11:59:24,551,25.5,41,0,29
2026-06-11T12:00:24,556,25.3,40,0,31
2026-06-11T12:02:25,556,25.5,42,0,31
2026-06-11T12:04:26,551,25.5,41,0,29
2026-06-11T12:05:27,551,25.5,41,0,29
2026-06-11T12:06:27,551,25.3,41,0,29
2026-06-11T12:07:28,556,25.3,41,0,31
2026-06-11T12:08:28,551,25.5,40,0,31
2026-06-11T12:09:29,575,25.5,41,0,31
2026-06-11T12:10:29,551,25.5,41,0,31
2026-06-11T12:11:30,551,25.3,41,0,31
2026-06-11T12:12:30,545,25.5,41,0,31
2026-06-11T12:13:31,551,25.5,42,0,29
2026-06-11T12:15:32,530,25.3,42,0,29
2026-06-11T12:16:32,536,25.5,42,0,29
2026-06-11T12:17:33,536,25.5,42,0,29
2026-06-11T12:18:33,530,25.5,41,0,31
2026-06-11T12:19:34,551,25.5,40,0,31
2026-06-11T12:20:34,564,25.5,41,0,29
2026-06-11T12:21:35,539,25.5,41,0,31
2026-06-11T12:22:35,545,25.5,40,0,29
2026-06-11T12:23:36,569,25.5,40,0,29
2026-06-11T12:24:36,575,25.5,42,0,31
2026-06-11T12:25:37,530,25.5,40,0,29
2026-06-11T12:26:37,569,25.5,41,0,31
2026-06-11T12:27:38,551,25.5,40,0,29
2026-06-11T12:28:38,564,25.5,40,0,29
2026-06-11T12:29:39,564,25.5,41,0,31
2026-06-11T12:30:39,545,25.5,40,0,29
2026-06-11T12:31:40,569,25.5,39,0,29
2026-06-11T12:32:40,588,25.6,42,0,31
2026-06-11T12:33:41,536,25.5,39,0,29
2026-06-11T12:34:41,588,25.5,40,0,29
2026-06-11T12:35:42,575,25.6,40,0,31
2026-06-11T12:36:43,569,25.5,41,0,29
2026-06-11T12:37:43,556,25.6,41,0,29
2026-06-11T12:38:44,556,25.5,41,0,29
2026-06-11T12:39:44,551,25.6,40,0,31
2026-06-11T12:40:44,575,25.6,40,0,29
2026-06-11T12:41:45,569,25.6,42,0,31
2026-06-11T12:42:45,548,25.6,39,0,29
2026-06-11T12:43:46,594,25.5,41,0,31
2026-06-11T12:44:46,545,25.6,42,0,29
2026-06-11T12:45:47,548,25.6,41,0,29
2026-06-11T12:46:47,565,25.6,40,0,31
2026-06-11T12:47:48,575,25.6,42,0,31
2026-06-11T12:48:48,553,25.6,40,0,31
2026-06-11T12:49:49,581,25.6,39,0,29
2026-06-11T12:50:49,594,25.6,39,0,29
2026-06-11T12:51:50,607,25.6,40,0,29
2026-06-11T12:52:50,589,25.6,41,0,29
2026-06-11T12:53:51,556,25.6,42,0,29
2026-06-11T12:54:51,553,25.6,42,0,29
2026-06-11T12:55:52,548,25.6,40,0,29
2026-06-11T12:56:53,589,25.6,39,0,29
2026-06-11T12:57:53,607,25.7,41,0,29
2026-06-11T12:58:53,575,25.6,40,0,29
2026-06-11T12:59:54,589,25.6,40,0,29
2026-06-11T13:00:54,594,25.7,40,0,29
2026-06-11T13:01:55,602,25.7,40,0,29
2026-06-11T13:02:55,589,25.7,39,0,29
2026-06-11T13:03:56,623,25.7,39,0,29
2026-06-11T13:04:56,614,25.7,41,0,29
2026-06-11T13:05:57,575,25.7,40,0,29
2026-06-11T13:06:58,589,25.7,41,0,29
2026-06-11T13:07:58,575,25.7,38,0,29
2026-06-11T13:08:58,627,25.7,40,0,29
2026-06-11T13:09:59,611,25.7,40,0,29
2026-06-11T13:10:59,602,25.6,40,0,29
2026-06-11T13:12:00,602,25.7,40,0,29
2026-06-11T13:13:00,611,25.7,42,0,29
2026-06-11T13:14:01,567,25.7,40,0,29
2026-06-11T13:15:01,594,25.7,42,0,31
2026-06-11T13:16:02,574,25.7,40,0,29
2026-06-11T13:17:03,589,25.7,41,0,29
2026-06-11T13:18:03,584,25.8,40,0,29
2026-06-11T13:19:04,589,25.7,40,0,29
2026-06-11T13:20:04,602,25.8,41,0,29
2026-06-11T13:21:05,584,25.8,40,0,29
2026-06-11T13:22:05,594,25.8,41,0,31
2026-06-11T13:24:06,560,25.8,42,0,29
2026-06-11T13:25:07,574,25.8,38,0,29
2026-06-11T13:26:07,641,26,41,0,29
2026-06-11T13:27:08,531,25.8,40,0,29
2026-06-11T13:28:08,611,25.8,41,0,29
2026-06-11T13:29:09,575,25.8,42,0,29
2026-06-11T13:30:09,574,26,40,0,29
2026-06-11T13:31:10,548,26,41,0,29
2026-06-11T13:32:10,526,26,41,0,29
2026-06-11T13:33:11,531,26,40,0,29
2026-06-11T13:34:11,548,26,42,0,29
2026-06-11T13:35:12,517,26,41,0,29
2026-06-11T13:36:12,531,26,41,0,29
2026-06-11T13:37:13,531,26,40,0,29
2026-06-11T13:38:13,548,25.8,42,0,29
2026-06-11T13:39:14,567,26,42,0,29
2026-06-11T13:40:14,517,26,42,0,29
2026-06-11T13:41:15,501,26,41,0,29
2026-06-11T13:42:15,526,26,39,0,29
2026-06-11T13:43:16,560,26,41,0,29
2026-06-11T13:44:16,526,26,39,0,29
2026-06-11T13:45:17,552,26,39,0,29
2026-06-11T13:46:17,552,26,39,0,29
2026-06-11T13:47:18,560,26,40,0,29
2026-06-11T13:48:18,542,26,40,0,29
2026-06-11T13:49:19,542,26,42,0,29
2026-06-11T13:50:19,517,26,39,0,29
2026-06-11T13:51:20,560,26,40,0,29
2026-06-11T13:52:20,548,26,40,0,29
2026-06-11T13:53:21,548,26,41,0,29
2026-06-11T13:54:21,531,26,40,0,29
2026-06-11T13:55:22,542,25.8,38,0,29
2026-06-11T13:56:22,633,25.8,39,0,29
2026-06-11T13:57:23,623,25.8,39,0,29
2026-06-11T13:58:23,623,26,39,0,29
2026-06-11T13:59:24,566,26,40,0,29
2026-06-11T14:00:24,548,26,40,0,29
2026-06-11T14:01:25,548,26,40,0,29
2026-06-11T14:02:25,548,26,40,0,29
2026-06-11T14:03:26,548,26,39,0,29
2026-06-11T14:04:26,572,26,41,0,29
2026-06-11T14:05:27,531,26,39,0,29
2026-06-11T14:06:27,566,26,41,0,29
2026-06-11T14:07:28,537,26,40,0,29
2026-06-11T14:08:28,542,26,41,0,29
2026-06-11T14:09:29,537,26,41,0,29
2026-06-11T14:10:29,531,26,39,0,29
2026-06-11T14:11:30,560,26.1,40,0,29
2026-06-11T14:12:30,542,26.1,40,0,29
2026-06-11T14:13:31,548,26.1,40,0,29
2026-06-11T14:14:31,542,26.1,39,0,29
2026-06-11T14:15:32,566,26.1,41,0,29
2026-06-11T14:16:32,526,26.1,40,0,29
2026-06-11T14:17:33,548,26.1,40,0,29
2026-06-11T14:18:33,548,26.1,41,0,29
2026-06-11T14:19:34,537,26.1,40,0,29
2026-06-11T14:20:34,548,26.1,40,0,29
2026-06-11T14:21:35,542,26.1,40,0,29
2026-06-11T14:22:35,542,26.1,40,0,29
2026-06-11T14:23:36,641,26.1,40,0,29
2026-06-11T14:24:36,780,26.1,39,0,29
2026-06-11T14:25:37,752,26.1,40,0,29
2026-06-11T14:26:37,749,26.1,40,0,29
2026-06-11T14:27:38,759,26.1,39,0,29
2026-06-11T14:28:38,746,26.1,40,0,29
2026-06-11T14:29:39,719,26.1,40,0,29
2026-06-11T14:30:39,719,26.1,39,0,29
2026-06-11T14:31:40,737,26.1,40,0,29
2026-06-11T14:32:40,704,26.1,41,0,29
2026-06-11T14:33:41,668,26.1,39,0,29
2026-06-11T14:34:41,712,26.1,40,0,29
2026-06-11T14:35:42,690,26.1,40,0,29
2026-06-11T14:36:42,690,26.1,40,0,29
2026-06-11T14:37:43,690,26.2,40,0,29
2026-06-11T14:38:43,682,26.1,40,0,29
2026-06-11T14:40:44,690,26.1,40,0,29
2026-06-11T14:41:45,682,26.1,41,0,29
2026-06-11T14:42:45,652,26.1,40,0,29
2026-06-11T14:43:46,674,26.2,39,0,29
2026-06-11T14:44:46,686,26.1,39,0,29
2026-06-11T14:45:47,678,26.2,39,0,29
2026-06-11T14:46:47,678,26.2,39,0,29
2026-06-11T14:49:49,678,26.2,40,0,29
2026-06-11T14:50:50,641,26.2,41,0,29
2026-06-11T14:51:50,629,26.2,40,0,29
2026-06-11T14:52:51,641,26.1,40,0,29
2026-06-11T14:53:51,649,26.2,40,0,29
2026-06-11T14:54:51,641,26.2,40,0,29
2026-06-11T14:55:52,641,26.2,40,0,29
2026-06-11T14:56:52,633,26.2,40,0,29
2026-06-11T14:57:53,626,26.2,39,0,29
2026-06-11T14:58:53,652,26.2,39,0,29
2026-06-11T14:59:54,627,26.2,39,0,29
2026-06-11T15:00:54,645,26.2,40,0,29
2026-06-11T15:01:55,633,26.3,40,0,29
2026-06-11T15:02:55,600,26.2,39,0,29
2026-06-11T15:03:56,638,26.3,39,0,29
2026-06-11T15:04:56,638,26.3,40,0,29
2026-06-11T15:05:57,626,26.3,40,0,29
2026-06-11T15:06:57,626,26.5,39,0,29
2026-06-11T15:07:58,652,26.5,41,0,29
2026-06-11T15:08:58,581,26.5,39,0,29
2026-06-11T15:09:59,645,26.5,40,0,27
2026-06-11T15:11:00,626,26.6,40,0,27
2026-06-11T15:12:00,641,26.6,40,0,29
2026-06-11T15:14:01,637,26.6,40,0,29
2026-06-11T15:15:02,656,26.7,40,0,27
2026-06-11T15:16:02,665,26.7,41,0,29
2026-06-11T15:17:02,637,26.7,40,0,29
2026-06-11T15:18:03,665,26.7,40,0,29
2026-06-11T15:19:03,665,26.6,40,0,29
2026-06-11T15:20:04,674,26.7,40,0,29
2026-06-11T15:21:04,665,26.7,40,0,29
2026-06-11T15:22:05,674,26.7,40,0,29
2026-06-11T15:23:06,682,26.7,41,0,29
2026-06-11T15:24:06,652,26.8,40,0,29
2026-06-11T15:25:06,682,26.8,41,0,29
2026-06-11T15:26:07,652,26.8,40,0,29
2026-06-11T15:27:07,682,26.8,40,0,29
2026-06-11T15:28:08,682,26.8,40,0,29
2026-06-11T15:29:09,682,26.8,40,0,29
2026-06-11T15:30:09,682,26.7,40,0,29
2026-06-11T15:31:10,682,26.7,40,0,29
2026-06-11T15:32:10,682,26.7,41,0,27
2026-06-11T15:33:11,659,26.7,40,0,29
2026-06-11T15:34:11,682,26.7,41,0,27
2026-06-11T15:36:12,659,26.8,40,0,29
2026-06-11T15:37:13,690,26.8,40,0,29
2026-06-11T15:38:13,674,26.8,40,0,29
2026-06-11T15:39:14,690,26.7,41,0,27
2026-06-11T15:40:14,659,26.8,40,0,29
2026-06-11T15:42:15,682,26.8,40,0,29
2026-06-11T15:43:16,682,26.7,40,0,29
2026-06-11T15:44:16,674,26.8,40,0,27
2026-06-11T15:45:17,682,26.8,40,0,29
2026-06-11T15:46:17,674,26.8,40,0,29
2026-06-11T15:47:18,665,26.7,40,0,29
2026-06-11T15:48:18,665,26.7,40,0,29
2026-06-11T15:49:19,665,26.7,40,0,27
2026-06-11T15:50:19,665,26.7,40,0,29
2026-06-11T15:51:20,656,26.8,40,0,29
2026-06-11T15:52:20,656,26.7,40,0,29
2026-06-11T15:53:21,649,26.7,40,0,29
2026-06-11T15:54:21,656,26.8,40,0,29
2026-06-11T15:55:22,665,26.8,41,0,29
2026-06-11T15:56:22,643,26.8,40,0,27
2026-06-11T15:57:23,665,26.8,40,0,29
2026-06-11T15:58:23,665,26.8,40,0,29
2026-06-11T15:59:24,665,26.8,40,0,29
2026-06-11T16:00:24,674,26.8,40,0,27
2026-06-11T16:01:25,674,26.8,40,0,29
2026-06-11T16:02:25,674,26.8,40,0,29
2026-06-11T16:03:26,674,26.8,40,0,29
2026-06-11T16:04:26,665,26.8,40,0,29
2026-06-11T16:05:27,665,26.8,40,0,29
2026-06-11T16:06:27,674,26.8,40,0,25
2026-06-11T16:07:28,674,26.8,40,0,29
2026-06-11T16:08:28,674,26.8,39,0,27
2026-06-11T16:09:29,697,26.8,40,0,29
2026-06-11T16:10:29,665,26.8,39,0,29
2026-06-11T16:11:30,697,26.8,40,0,29
2026-06-11T16:12:30,674,26.8,39,0,29
2026-06-11T16:13:31,697,26.8,40,0,29
2026-06-11T16:14:31,674,26.8,40,0,29
2026-06-11T16:16:32,665,27,40,0,29
2026-06-11T16:17:33,597,27,40,0,29
2026-06-11T16:18:33,591,26.8,40,0,29
2026-06-11T16:19:34,656,26.8,40,0,29
2026-06-11T16:20:34,665,27,41,0,29
2026-06-11T16:21:35,578,26.8,41,0,29
2026-06-11T16:22:35,643,27,40,0,29
2026-06-11T16:23:36,597,27,41,0,29
2026-06-11T16:24:36,566,27,41,0,29
2026-06-11T16:25:37,566,26.8,41,0,29
2026-06-11T16:26:37,637,27,41,0,29
2026-06-11T16:27:38,578,27,41,0,29
2026-06-11T16:28:38,572,26.8,40,0,29
2026-06-11T16:29:39,649,26.8,41,0,29
1 time co2 temperature humidity windows battery
2 2026-06-11T08:45:47 552 25 40 0 31
3 2026-06-11T08:46:47 485 25 41 0 31
4 2026-06-11T08:47:48 463 25 40 0 31
5 2026-06-11T08:48:48 491 25 41 0 31
6 2026-06-11T08:49:49 463 25 41 0 31
7 2026-06-11T08:50:49 463 25.1 42 0 31
8 2026-06-11T08:51:50 461 25.1 40 0 31
9 2026-06-11T08:52:51 493 25.1 41 0 31
10 2026-06-11T08:53:51 478 25.1 40 0 31
11 2026-06-11T08:54:52 493 25.2 40 0 31
12 2026-06-11T08:55:52 493 25.2 40 0 31
13 2026-06-11T08:56:53 501 25.2 40 0 31
14 2026-06-11T08:57:53 514 25.2 41 0 31
15 2026-06-11T08:59:54 492 25.2 40 0 31
16 2026-06-11T09:00:55 514 25.2 41 0 31
17 2026-06-11T09:01:55 500 25.2 41 0 31
18 2026-06-11T09:02:56 508 25.2 41 0 31
19 2026-06-11T09:03:56 495 25.3 41 0 31
20 2026-06-11T09:04:57 508 25.3 40 0 31
21 2026-06-11T09:05:57 529 25.3 40 0 31
22 2026-06-11T09:06:58 529 25.3 41 0 31
23 2026-06-11T09:07:58 515 25.3 40 0 31
24 2026-06-11T09:08:59 537 25.3 41 0 31
25 2026-06-11T09:09:59 520 25.3 41 0 31
26 2026-06-11T09:11:00 520 25.5 42 0 31
27 2026-06-11T09:12:00 497 25.3 41 0 31
28 2026-06-11T09:13:01 527 25.3 41 0 31
29 2026-06-11T09:14:01 520 25.3 41 0 31
30 2026-06-11T09:15:02 515 25.3 41 0 31
31 2026-06-11T09:16:02 520 25.3 40 0 31
32 2026-06-11T09:17:03 537 25.3 40 0 31
33 2026-06-11T09:18:03 537 25.3 40 0 31
34 2026-06-11T09:20:04 531 25.2 41 0 31
35 2026-06-11T09:22:05 529 25.2 41 0 31
36 2026-06-11T09:23:06 500 25.2 40 0 31
37 2026-06-11T09:24:06 514 25.2 41 0 31
38 2026-06-11T09:25:07 495 25.2 41 0 31
39 2026-06-11T09:26:07 492 25.2 41 0 31
40 2026-06-11T09:27:08 486 25.2 41 0 31
41 2026-06-11T09:28:08 486 25.2 40 0 31
42 2026-06-11T09:29:09 506 25.2 40 0 31
43 2026-06-11T09:30:09 501 25.2 40 0 31
44 2026-06-11T09:31:10 501 25.1 40 0 31
45 2026-06-11T09:32:10 501 25.1 39 0 31
46 2026-06-11T09:33:11 508 25.2 40 0 31
47 2026-06-11T09:34:11 501 25.1 40 0 31
48 2026-06-11T09:35:12 501 25.1 40 0 31
49 2026-06-11T09:36:12 491 25.1 40 0 31
50 2026-06-11T09:37:13 493 25.1 40 0 29
51 2026-06-11T09:38:13 493 25.1 40 0 31
52 2026-06-11T09:40:14 493 25.1 40 0 29
53 2026-06-11T09:41:15 485 25.1 40 0 31
54 2026-06-11T09:42:15 491 25.1 40 0 31
55 2026-06-11T09:43:16 493 25.1 40 0 31
56 2026-06-11T09:44:16 493 25.1 40 0 31
57 2026-06-11T09:45:17 491 25.1 41 0 31
58 2026-06-11T09:46:17 478 25.1 41 0 31
59 2026-06-11T09:47:18 478 25.1 40 0 29
60 2026-06-11T09:48:18 493 25.1 40 0 31
61 2026-06-11T09:49:19 485 25.1 41 0 29
62 2026-06-11T09:50:19 476 25.1 39 0 29
63 2026-06-11T09:51:20 500 25.1 40 0 31
64 2026-06-11T09:52:20 485 25.1 41 0 31
65 2026-06-11T09:53:21 476 25.1 40 0 31
66 2026-06-11T09:54:21 491 25.1 40 0 31
67 2026-06-11T09:55:22 491 25.1 40 0 31
68 2026-06-11T09:56:22 493 25.1 40 0 31
69 2026-06-11T09:57:23 491 25.1 41 0 31
70 2026-06-11T09:58:23 470 25.1 40 0 31
71 2026-06-11T09:59:24 491 25.1 41 0 31
72 2026-06-11T10:00:25 476 25.1 41 0 31
73 2026-06-11T10:01:25 476 25.1 41 0 31
74 2026-06-11T10:03:26 470 25.1 41 0 31
75 2026-06-11T10:04:26 470 25.1 41 0 31
76 2026-06-11T10:05:27 476 25.1 41 0 31
77 2026-06-11T10:06:28 476 25.2 41 0 31
78 2026-06-11T10:07:28 470 25.1 40 0 31
79 2026-06-11T10:08:28 485 25.1 40 0 31
80 2026-06-11T10:09:29 491 25.1 40 0 31
81 2026-06-11T10:10:29 491 25.1 41 0 31
82 2026-06-11T10:11:30 476 25.1 41 0 31
83 2026-06-11T10:12:30 476 25 41 0 31
84 2026-06-11T10:13:31 476 25.1 40 0 33
85 2026-06-11T10:14:31 493 25.1 41 0 31
86 2026-06-11T10:15:32 470 25.1 41 0 31
87 2026-06-11T10:16:32 476 25.1 40 0 31
88 2026-06-11T10:17:33 485 25 41 0 31
89 2026-06-11T10:18:34 478 25 41 0 31
90 2026-06-11T10:19:34 454 25 41 0 31
91 2026-06-11T10:20:34 463 25.1 41 0 31
92 2026-06-11T10:21:35 454 25 40 0 31
93 2026-06-11T10:22:36 466 25.1 41 0 31
94 2026-06-11T10:23:36 470 25.1 41 0 31
95 2026-06-11T10:24:36 463 25.1 41 0 31
96 2026-06-11T10:25:37 478 25.1 41 0 31
97 2026-06-11T10:26:37 478 25.1 41 0 31
98 2026-06-11T10:28:38 492 25.1 41 0 31
99 2026-06-11T10:29:39 486 25.1 41 0 29
100 2026-06-11T10:30:39 486 25.2 41 0 29
101 2026-06-11T10:31:40 492 25.2 40 0 31
102 2026-06-11T10:32:40 506 25.2 41 0 29
103 2026-06-11T10:33:41 495 25.2 41 0 31
104 2026-06-11T10:34:41 495 25.2 42 0 31
105 2026-06-11T10:36:42 519 25.2 41 0 31
106 2026-06-11T10:37:43 508 25.2 41 0 31
107 2026-06-11T10:38:44 508 25.2 41 0 29
108 2026-06-11T10:39:44 515 25.2 41 0 31
109 2026-06-11T10:40:44 500 25.2 41 0 31
110 2026-06-11T10:41:45 508 25.2 41 0 31
111 2026-06-11T10:42:45 500 25.2 41 0 31
112 2026-06-11T10:43:46 508 25.1 41 0 29
113 2026-06-11T10:44:46 508 25.2 41 0 31
114 2026-06-11T10:45:47 500 25.2 41 0 31
115 2026-06-11T10:46:47 508 25.2 42 0 31
116 2026-06-11T10:47:48 486 25.2 42 0 31
117 2026-06-11T10:48:48 486 25.2 40 0 29
118 2026-06-11T10:49:49 519 25.1 40 0 31
119 2026-06-11T10:50:50 519 25.2 42 0 31
120 2026-06-11T10:51:50 486 25.2 42 0 31
121 2026-06-11T10:52:51 486 25.1 40 0 31
122 2026-06-11T10:53:51 514 25.1 40 0 29
123 2026-06-11T10:54:52 514 25.2 41 0 31
124 2026-06-11T10:55:52 508 25.1 42 0 31
125 2026-06-11T10:56:53 495 25.1 41 0 31
126 2026-06-11T10:57:53 508 25.2 41 0 29
127 2026-06-11T10:58:54 500 25.1 41 0 29
128 2026-06-11T10:59:54 500 25.1 41 0 31
129 2026-06-11T11:00:55 500 25.1 41 0 31
130 2026-06-11T11:01:55 508 25.2 42 0 31
131 2026-06-11T11:02:55 486 25.1 41 0 31
132 2026-06-11T11:03:56 500 25.1 42 0 31
133 2026-06-11T11:04:57 495 25.2 42 0 31
134 2026-06-11T11:05:57 495 25.2 41 0 29
135 2026-06-11T11:06:57 508 25.2 41 0 29
136 2026-06-11T11:07:58 508 25.2 42 0 31
137 2026-06-11T11:08:59 486 25.2 41 0 29
138 2026-06-11T11:09:59 508 25.2 42 0 29
139 2026-06-11T11:11:00 486 25.2 42 0 31
140 2026-06-11T11:12:00 495 25.2 41 0 31
141 2026-06-11T11:13:01 500 25.2 40 0 29
142 2026-06-11T11:14:01 531 25.2 40 0 31
143 2026-06-11T11:15:02 519 25.1 41 0 31
144 2026-06-11T11:16:02 508 25.2 41 0 31
145 2026-06-11T11:17:03 508 25.2 41 0 29
146 2026-06-11T11:18:03 500 25.2 41 0 31
147 2026-06-11T11:19:04 500 25.2 42 0 29
148 2026-06-11T11:20:04 497 25.2 41 0 31
149 2026-06-11T11:21:05 508 25.2 41 0 31
150 2026-06-11T11:22:05 500 25.2 41 0 31
151 2026-06-11T11:23:06 515 25.2 41 0 31
152 2026-06-11T11:24:06 508 25.2 41 0 31
153 2026-06-11T11:25:07 500 25.2 40 0 31
154 2026-06-11T11:26:07 531 25.2 41 0 31
155 2026-06-11T11:27:08 508 25.2 40 0 29
156 2026-06-11T11:28:08 529 25.2 41 0 31
157 2026-06-11T11:29:09 500 25.2 41 0 31
158 2026-06-11T11:30:09 500 25.2 40 0 29
159 2026-06-11T11:31:10 531 25.2 41 0 29
160 2026-06-11T11:32:10 508 25.2 41 0 31
161 2026-06-11T11:33:11 508 25.2 41 0 31
162 2026-06-11T11:34:11 500 25.2 41 0 31
163 2026-06-11T11:35:12 508 25.2 40 0 31
164 2026-06-11T11:36:12 514 25.2 41 0 31
165 2026-06-11T11:37:13 486 25.2 40 0 29
166 2026-06-11T11:38:13 501 25.2 41 0 31
167 2026-06-11T11:39:14 495 25.2 42 0 29
168 2026-06-11T11:40:14 495 25.2 39 0 29
169 2026-06-11T11:41:15 545 25.2 40 0 29
170 2026-06-11T11:42:15 531 25.2 41 0 31
171 2026-06-11T11:43:16 508 25.3 41 0 29
172 2026-06-11T11:44:16 520 25.3 42 0 31
173 2026-06-11T11:45:17 502 25.3 41 0 31
174 2026-06-11T11:47:18 520 25.5 41 0 31
175 2026-06-11T11:48:18 536 25.3 40 0 31
176 2026-06-11T11:49:19 543 25.5 40 0 31
177 2026-06-11T11:50:19 555 25.3 41 0 29
178 2026-06-11T11:51:20 536 25.3 42 0 29
179 2026-06-11T11:52:20 508 25.3 40 0 31
180 2026-06-11T11:54:21 520 25.3 40 0 29
181 2026-06-11T11:56:22 555 25.5 41 0 31
182 2026-06-11T11:57:23 539 25.5 40 0 31
183 2026-06-11T11:58:23 564 25.5 41 0 31
184 2026-06-11T11:59:24 551 25.5 41 0 29
185 2026-06-11T12:00:24 556 25.3 40 0 31
186 2026-06-11T12:02:25 556 25.5 42 0 31
187 2026-06-11T12:04:26 551 25.5 41 0 29
188 2026-06-11T12:05:27 551 25.5 41 0 29
189 2026-06-11T12:06:27 551 25.3 41 0 29
190 2026-06-11T12:07:28 556 25.3 41 0 31
191 2026-06-11T12:08:28 551 25.5 40 0 31
192 2026-06-11T12:09:29 575 25.5 41 0 31
193 2026-06-11T12:10:29 551 25.5 41 0 31
194 2026-06-11T12:11:30 551 25.3 41 0 31
195 2026-06-11T12:12:30 545 25.5 41 0 31
196 2026-06-11T12:13:31 551 25.5 42 0 29
197 2026-06-11T12:15:32 530 25.3 42 0 29
198 2026-06-11T12:16:32 536 25.5 42 0 29
199 2026-06-11T12:17:33 536 25.5 42 0 29
200 2026-06-11T12:18:33 530 25.5 41 0 31
201 2026-06-11T12:19:34 551 25.5 40 0 31
202 2026-06-11T12:20:34 564 25.5 41 0 29
203 2026-06-11T12:21:35 539 25.5 41 0 31
204 2026-06-11T12:22:35 545 25.5 40 0 29
205 2026-06-11T12:23:36 569 25.5 40 0 29
206 2026-06-11T12:24:36 575 25.5 42 0 31
207 2026-06-11T12:25:37 530 25.5 40 0 29
208 2026-06-11T12:26:37 569 25.5 41 0 31
209 2026-06-11T12:27:38 551 25.5 40 0 29
210 2026-06-11T12:28:38 564 25.5 40 0 29
211 2026-06-11T12:29:39 564 25.5 41 0 31
212 2026-06-11T12:30:39 545 25.5 40 0 29
213 2026-06-11T12:31:40 569 25.5 39 0 29
214 2026-06-11T12:32:40 588 25.6 42 0 31
215 2026-06-11T12:33:41 536 25.5 39 0 29
216 2026-06-11T12:34:41 588 25.5 40 0 29
217 2026-06-11T12:35:42 575 25.6 40 0 31
218 2026-06-11T12:36:43 569 25.5 41 0 29
219 2026-06-11T12:37:43 556 25.6 41 0 29
220 2026-06-11T12:38:44 556 25.5 41 0 29
221 2026-06-11T12:39:44 551 25.6 40 0 31
222 2026-06-11T12:40:44 575 25.6 40 0 29
223 2026-06-11T12:41:45 569 25.6 42 0 31
224 2026-06-11T12:42:45 548 25.6 39 0 29
225 2026-06-11T12:43:46 594 25.5 41 0 31
226 2026-06-11T12:44:46 545 25.6 42 0 29
227 2026-06-11T12:45:47 548 25.6 41 0 29
228 2026-06-11T12:46:47 565 25.6 40 0 31
229 2026-06-11T12:47:48 575 25.6 42 0 31
230 2026-06-11T12:48:48 553 25.6 40 0 31
231 2026-06-11T12:49:49 581 25.6 39 0 29
232 2026-06-11T12:50:49 594 25.6 39 0 29
233 2026-06-11T12:51:50 607 25.6 40 0 29
234 2026-06-11T12:52:50 589 25.6 41 0 29
235 2026-06-11T12:53:51 556 25.6 42 0 29
236 2026-06-11T12:54:51 553 25.6 42 0 29
237 2026-06-11T12:55:52 548 25.6 40 0 29
238 2026-06-11T12:56:53 589 25.6 39 0 29
239 2026-06-11T12:57:53 607 25.7 41 0 29
240 2026-06-11T12:58:53 575 25.6 40 0 29
241 2026-06-11T12:59:54 589 25.6 40 0 29
242 2026-06-11T13:00:54 594 25.7 40 0 29
243 2026-06-11T13:01:55 602 25.7 40 0 29
244 2026-06-11T13:02:55 589 25.7 39 0 29
245 2026-06-11T13:03:56 623 25.7 39 0 29
246 2026-06-11T13:04:56 614 25.7 41 0 29
247 2026-06-11T13:05:57 575 25.7 40 0 29
248 2026-06-11T13:06:58 589 25.7 41 0 29
249 2026-06-11T13:07:58 575 25.7 38 0 29
250 2026-06-11T13:08:58 627 25.7 40 0 29
251 2026-06-11T13:09:59 611 25.7 40 0 29
252 2026-06-11T13:10:59 602 25.6 40 0 29
253 2026-06-11T13:12:00 602 25.7 40 0 29
254 2026-06-11T13:13:00 611 25.7 42 0 29
255 2026-06-11T13:14:01 567 25.7 40 0 29
256 2026-06-11T13:15:01 594 25.7 42 0 31
257 2026-06-11T13:16:02 574 25.7 40 0 29
258 2026-06-11T13:17:03 589 25.7 41 0 29
259 2026-06-11T13:18:03 584 25.8 40 0 29
260 2026-06-11T13:19:04 589 25.7 40 0 29
261 2026-06-11T13:20:04 602 25.8 41 0 29
262 2026-06-11T13:21:05 584 25.8 40 0 29
263 2026-06-11T13:22:05 594 25.8 41 0 31
264 2026-06-11T13:24:06 560 25.8 42 0 29
265 2026-06-11T13:25:07 574 25.8 38 0 29
266 2026-06-11T13:26:07 641 26 41 0 29
267 2026-06-11T13:27:08 531 25.8 40 0 29
268 2026-06-11T13:28:08 611 25.8 41 0 29
269 2026-06-11T13:29:09 575 25.8 42 0 29
270 2026-06-11T13:30:09 574 26 40 0 29
271 2026-06-11T13:31:10 548 26 41 0 29
272 2026-06-11T13:32:10 526 26 41 0 29
273 2026-06-11T13:33:11 531 26 40 0 29
274 2026-06-11T13:34:11 548 26 42 0 29
275 2026-06-11T13:35:12 517 26 41 0 29
276 2026-06-11T13:36:12 531 26 41 0 29
277 2026-06-11T13:37:13 531 26 40 0 29
278 2026-06-11T13:38:13 548 25.8 42 0 29
279 2026-06-11T13:39:14 567 26 42 0 29
280 2026-06-11T13:40:14 517 26 42 0 29
281 2026-06-11T13:41:15 501 26 41 0 29
282 2026-06-11T13:42:15 526 26 39 0 29
283 2026-06-11T13:43:16 560 26 41 0 29
284 2026-06-11T13:44:16 526 26 39 0 29
285 2026-06-11T13:45:17 552 26 39 0 29
286 2026-06-11T13:46:17 552 26 39 0 29
287 2026-06-11T13:47:18 560 26 40 0 29
288 2026-06-11T13:48:18 542 26 40 0 29
289 2026-06-11T13:49:19 542 26 42 0 29
290 2026-06-11T13:50:19 517 26 39 0 29
291 2026-06-11T13:51:20 560 26 40 0 29
292 2026-06-11T13:52:20 548 26 40 0 29
293 2026-06-11T13:53:21 548 26 41 0 29
294 2026-06-11T13:54:21 531 26 40 0 29
295 2026-06-11T13:55:22 542 25.8 38 0 29
296 2026-06-11T13:56:22 633 25.8 39 0 29
297 2026-06-11T13:57:23 623 25.8 39 0 29
298 2026-06-11T13:58:23 623 26 39 0 29
299 2026-06-11T13:59:24 566 26 40 0 29
300 2026-06-11T14:00:24 548 26 40 0 29
301 2026-06-11T14:01:25 548 26 40 0 29
302 2026-06-11T14:02:25 548 26 40 0 29
303 2026-06-11T14:03:26 548 26 39 0 29
304 2026-06-11T14:04:26 572 26 41 0 29
305 2026-06-11T14:05:27 531 26 39 0 29
306 2026-06-11T14:06:27 566 26 41 0 29
307 2026-06-11T14:07:28 537 26 40 0 29
308 2026-06-11T14:08:28 542 26 41 0 29
309 2026-06-11T14:09:29 537 26 41 0 29
310 2026-06-11T14:10:29 531 26 39 0 29
311 2026-06-11T14:11:30 560 26.1 40 0 29
312 2026-06-11T14:12:30 542 26.1 40 0 29
313 2026-06-11T14:13:31 548 26.1 40 0 29
314 2026-06-11T14:14:31 542 26.1 39 0 29
315 2026-06-11T14:15:32 566 26.1 41 0 29
316 2026-06-11T14:16:32 526 26.1 40 0 29
317 2026-06-11T14:17:33 548 26.1 40 0 29
318 2026-06-11T14:18:33 548 26.1 41 0 29
319 2026-06-11T14:19:34 537 26.1 40 0 29
320 2026-06-11T14:20:34 548 26.1 40 0 29
321 2026-06-11T14:21:35 542 26.1 40 0 29
322 2026-06-11T14:22:35 542 26.1 40 0 29
323 2026-06-11T14:23:36 641 26.1 40 0 29
324 2026-06-11T14:24:36 780 26.1 39 0 29
325 2026-06-11T14:25:37 752 26.1 40 0 29
326 2026-06-11T14:26:37 749 26.1 40 0 29
327 2026-06-11T14:27:38 759 26.1 39 0 29
328 2026-06-11T14:28:38 746 26.1 40 0 29
329 2026-06-11T14:29:39 719 26.1 40 0 29
330 2026-06-11T14:30:39 719 26.1 39 0 29
331 2026-06-11T14:31:40 737 26.1 40 0 29
332 2026-06-11T14:32:40 704 26.1 41 0 29
333 2026-06-11T14:33:41 668 26.1 39 0 29
334 2026-06-11T14:34:41 712 26.1 40 0 29
335 2026-06-11T14:35:42 690 26.1 40 0 29
336 2026-06-11T14:36:42 690 26.1 40 0 29
337 2026-06-11T14:37:43 690 26.2 40 0 29
338 2026-06-11T14:38:43 682 26.1 40 0 29
339 2026-06-11T14:40:44 690 26.1 40 0 29
340 2026-06-11T14:41:45 682 26.1 41 0 29
341 2026-06-11T14:42:45 652 26.1 40 0 29
342 2026-06-11T14:43:46 674 26.2 39 0 29
343 2026-06-11T14:44:46 686 26.1 39 0 29
344 2026-06-11T14:45:47 678 26.2 39 0 29
345 2026-06-11T14:46:47 678 26.2 39 0 29
346 2026-06-11T14:49:49 678 26.2 40 0 29
347 2026-06-11T14:50:50 641 26.2 41 0 29
348 2026-06-11T14:51:50 629 26.2 40 0 29
349 2026-06-11T14:52:51 641 26.1 40 0 29
350 2026-06-11T14:53:51 649 26.2 40 0 29
351 2026-06-11T14:54:51 641 26.2 40 0 29
352 2026-06-11T14:55:52 641 26.2 40 0 29
353 2026-06-11T14:56:52 633 26.2 40 0 29
354 2026-06-11T14:57:53 626 26.2 39 0 29
355 2026-06-11T14:58:53 652 26.2 39 0 29
356 2026-06-11T14:59:54 627 26.2 39 0 29
357 2026-06-11T15:00:54 645 26.2 40 0 29
358 2026-06-11T15:01:55 633 26.3 40 0 29
359 2026-06-11T15:02:55 600 26.2 39 0 29
360 2026-06-11T15:03:56 638 26.3 39 0 29
361 2026-06-11T15:04:56 638 26.3 40 0 29
362 2026-06-11T15:05:57 626 26.3 40 0 29
363 2026-06-11T15:06:57 626 26.5 39 0 29
364 2026-06-11T15:07:58 652 26.5 41 0 29
365 2026-06-11T15:08:58 581 26.5 39 0 29
366 2026-06-11T15:09:59 645 26.5 40 0 27
367 2026-06-11T15:11:00 626 26.6 40 0 27
368 2026-06-11T15:12:00 641 26.6 40 0 29
369 2026-06-11T15:14:01 637 26.6 40 0 29
370 2026-06-11T15:15:02 656 26.7 40 0 27
371 2026-06-11T15:16:02 665 26.7 41 0 29
372 2026-06-11T15:17:02 637 26.7 40 0 29
373 2026-06-11T15:18:03 665 26.7 40 0 29
374 2026-06-11T15:19:03 665 26.6 40 0 29
375 2026-06-11T15:20:04 674 26.7 40 0 29
376 2026-06-11T15:21:04 665 26.7 40 0 29
377 2026-06-11T15:22:05 674 26.7 40 0 29
378 2026-06-11T15:23:06 682 26.7 41 0 29
379 2026-06-11T15:24:06 652 26.8 40 0 29
380 2026-06-11T15:25:06 682 26.8 41 0 29
381 2026-06-11T15:26:07 652 26.8 40 0 29
382 2026-06-11T15:27:07 682 26.8 40 0 29
383 2026-06-11T15:28:08 682 26.8 40 0 29
384 2026-06-11T15:29:09 682 26.8 40 0 29
385 2026-06-11T15:30:09 682 26.7 40 0 29
386 2026-06-11T15:31:10 682 26.7 40 0 29
387 2026-06-11T15:32:10 682 26.7 41 0 27
388 2026-06-11T15:33:11 659 26.7 40 0 29
389 2026-06-11T15:34:11 682 26.7 41 0 27
390 2026-06-11T15:36:12 659 26.8 40 0 29
391 2026-06-11T15:37:13 690 26.8 40 0 29
392 2026-06-11T15:38:13 674 26.8 40 0 29
393 2026-06-11T15:39:14 690 26.7 41 0 27
394 2026-06-11T15:40:14 659 26.8 40 0 29
395 2026-06-11T15:42:15 682 26.8 40 0 29
396 2026-06-11T15:43:16 682 26.7 40 0 29
397 2026-06-11T15:44:16 674 26.8 40 0 27
398 2026-06-11T15:45:17 682 26.8 40 0 29
399 2026-06-11T15:46:17 674 26.8 40 0 29
400 2026-06-11T15:47:18 665 26.7 40 0 29
401 2026-06-11T15:48:18 665 26.7 40 0 29
402 2026-06-11T15:49:19 665 26.7 40 0 27
403 2026-06-11T15:50:19 665 26.7 40 0 29
404 2026-06-11T15:51:20 656 26.8 40 0 29
405 2026-06-11T15:52:20 656 26.7 40 0 29
406 2026-06-11T15:53:21 649 26.7 40 0 29
407 2026-06-11T15:54:21 656 26.8 40 0 29
408 2026-06-11T15:55:22 665 26.8 41 0 29
409 2026-06-11T15:56:22 643 26.8 40 0 27
410 2026-06-11T15:57:23 665 26.8 40 0 29
411 2026-06-11T15:58:23 665 26.8 40 0 29
412 2026-06-11T15:59:24 665 26.8 40 0 29
413 2026-06-11T16:00:24 674 26.8 40 0 27
414 2026-06-11T16:01:25 674 26.8 40 0 29
415 2026-06-11T16:02:25 674 26.8 40 0 29
416 2026-06-11T16:03:26 674 26.8 40 0 29
417 2026-06-11T16:04:26 665 26.8 40 0 29
418 2026-06-11T16:05:27 665 26.8 40 0 29
419 2026-06-11T16:06:27 674 26.8 40 0 25
420 2026-06-11T16:07:28 674 26.8 40 0 29
421 2026-06-11T16:08:28 674 26.8 39 0 27
422 2026-06-11T16:09:29 697 26.8 40 0 29
423 2026-06-11T16:10:29 665 26.8 39 0 29
424 2026-06-11T16:11:30 697 26.8 40 0 29
425 2026-06-11T16:12:30 674 26.8 39 0 29
426 2026-06-11T16:13:31 697 26.8 40 0 29
427 2026-06-11T16:14:31 674 26.8 40 0 29
428 2026-06-11T16:16:32 665 27 40 0 29
429 2026-06-11T16:17:33 597 27 40 0 29
430 2026-06-11T16:18:33 591 26.8 40 0 29
431 2026-06-11T16:19:34 656 26.8 40 0 29
432 2026-06-11T16:20:34 665 27 41 0 29
433 2026-06-11T16:21:35 578 26.8 41 0 29
434 2026-06-11T16:22:35 643 27 40 0 29
435 2026-06-11T16:23:36 597 27 41 0 29
436 2026-06-11T16:24:36 566 27 41 0 29
437 2026-06-11T16:25:37 566 26.8 41 0 29
438 2026-06-11T16:26:37 637 27 41 0 29
439 2026-06-11T16:27:38 578 27 41 0 29
440 2026-06-11T16:28:38 572 26.8 40 0 29
441 2026-06-11T16:29:39 649 26.8 41 0 29

View File

@@ -1,23 +1,40 @@
#import "/resources/measures/plot_measures.typ": *
#let start-date = datetime(year: 2026, month: 5, day: 27, hour: 14, minute: 0, second: 0)
#let stop-date = datetime(year: 2026, month: 5, day: 27, hour: 17, minute: 59, second: 59)
#let start-date = datetime(year: 2026, month: 6, day: 11, hour: 10, minute: 0, second: 0)
#let stop-date = datetime(year: 2026, month: 6, day: 11, hour: 17, minute: 59, second: 59)
// #let node = "A2-C2_64_0F_68_35_3E"
// #let node = "A2-C6_7E_0A_DE_DA_74"
// #let node = "A2-E1_C0_30_15_4E_89"
// #let node = "A2-E8_F3_0A_F7_3B_F3"
// #let node = "A2-C6_95_1B_A6_49_E6" // Windows
#let node = "A2-F5_80_05_76_53_F0" // Windows
#set page(height: auto)
= Indoor Air Quality Analysis
Data range: #start-date.display("[day] [month repr:long] [year] [hour]h[minute]") to #stop-date.display("[day] [month repr:long] [year] [hour]h[minute]")
#let node = "A3-DC_06_D9_40_7A_CB"
== CO2 Level #node
#plot_co2("/resources/measures/" + node + ".csv", start-date, stop-date)
#let node = "A3-E6_8A_79_C8_87_25"
== CO2 Level #node
#plot_co2("/resources/measures/" + node + ".csv", start-date, stop-date)
#let node = "A3-ED_B2_F3_74_3E_C2"
== CO2 Level #node
#plot_co2("/resources/measures/" + node + ".csv", start-date, stop-date)
#pagebreak()
#let node = "A3-DC_06_D9_40_7A_CB"
== Temperature / Humidity #node
#plot_temp_hum("/resources/measures/" + node + ".csv", start-date, stop-date)
#let node = "A3-E6_8A_79_C8_87_25"
== Temperature / Humidity #node
#plot_temp_hum("/resources/measures/" + node + ".csv", start-date, stop-date)
#let node = "A3-ED_B2_F3_74_3E_C2"
== Temperature / Humidity #node
#plot_temp_hum("/resources/measures/" + node + ".csv", start-date, stop-date)

View File

@@ -128,6 +128,11 @@
key: "svg",
short: "SVG",
long: "Scalable Vector Graphics"
),
(
key: "trl",
short: "TRL",
long: "Technical Readiness Level"
)
)

View File