Skip to content

Commit 4c36a4e

Browse files
authored
Use TimescaleDB as database for optimized time series queries (#39)
* Use timescaledb to query mystrom results * Fix insert in minutely update request, fix calculation of total kwh by taking the fractions of the hour of first/last date in query * Use timescaledb features in shelly results * Format code with ruff * Mention that project only supports timescaledb as database * Add database in github actions service to test * Fix tests * Revert development changes * Increase max requests * Remove default logging level * Remove unneeded package from builder stage * Fix total power calculation query on partial hours * Use logger instead of print * use django 5.2 composite key * add ruff check workflow * remove mariadb connector since only timescaledb is supported
1 parent 1f72b9f commit 4c36a4e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1230
-603
lines changed

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
db-data/
2+
docker-compose.yml
3+
Dockerfile
4+
.env
5+
.git/
6+
.gitignore

.env

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
ENGINE_TYPE=postgresql
2-
DB_HOST=db
1+
DB_HOST=timescaledb
32
DB_PORT=5432
43
DB_NAME=postgres
54
DB_USER=postgres
65
DB_PASSWORD=postgres
76

87
SECRET_KEY=very-secret-key
9-
ALLOWED_HOSTS=localhost,example.com,interface
8+
ALLOWED_HOSTS=localhost,example.com,interface,192.168.0.19
109
CORS_ORIGIN_ALLOW_ALL=False
1110
CORS_ORIGIN_WHITELIST=http://example.com,http://localhost,http://interface:8000
1211
CHART_TYPE=uplot
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Ruff Format Check
2+
3+
on:
4+
push:
5+
paths:
6+
- '**.py'
7+
pull_request:
8+
paths:
9+
- '**.py'
10+
11+
jobs:
12+
ruff-format:
13+
name: Check code formatting with Ruff
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v3
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v4
22+
with:
23+
python-version: '3.12'
24+
25+
- name: Install Ruff
26+
run: |
27+
pip install ruff
28+
29+
- name: Run ruff format check
30+
run: |
31+
ruff format --check .

.github/workflows/test.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ jobs:
1616
matrix:
1717
python-version: ['3.10', '3.11', '3.12']
1818

19+
services:
20+
timescaledb:
21+
image: timescale/timescaledb:latest-pg16
22+
env:
23+
POSTGRES_PASSWORD: postgres
24+
# Set health checks to wait until postgres has started
25+
options: >-
26+
--health-cmd pg_isready
27+
--health-interval 10s
28+
--health-timeout 5s
29+
--health-retries 5
30+
ports:
31+
# Maps tcp port 5432 on service container to the host
32+
- 5432:5432
33+
1934
steps:
2035
- uses: actions/checkout@v3
2136
- name: Set up Python ${{ matrix.python-version }}
@@ -30,3 +45,9 @@ jobs:
3045
run: |
3146
python manage.py collectstatic --noinput
3247
python manage.py test
48+
env:
49+
DB_NAME: postgres
50+
DB_USER: postgres
51+
DB_PASSWORD: postgres
52+
DB_HOST: localhost
53+
DB_PORT: 5432

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ WORKDIR /app
55

66
COPY requirements.txt .
77

8-
RUN apk add --no-cache --virtual .build-deps gcc musl-dev mariadb-connector-c-dev libpq-dev
8+
RUN apk add --no-cache --virtual .build-deps gcc musl-dev libpq-dev
99

1010
RUN pip install --no-cache-dir --upgrade pip && \
1111
pip install --no-cache-dir gunicorn && \
@@ -20,10 +20,11 @@ COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/pytho
2020
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
2121
COPY --from=builder /app /app
2222

23-
RUN apk add --no-cache mariadb-connector-c libpq
23+
RUN apk add --no-cache libpq
2424

2525
COPY . .
2626

27-
CMD ["sh", "-c", "python manage.py makemigrations && python manage.py migrate && python manage.py collectstatic --noinput && gunicorn --bind=0.0.0.0:8000 --timeout 300 --workers=3 --threads=3 --max-requests 5 --max-requests-jitter 2 pim.wsgi:application"]
27+
28+
CMD ["sh", "-c", "python manage.py migrate && python manage.py collectstatic --noinput && gunicorn --bind=0.0.0.0:8000 --timeout 300 --workers=3 --threads=3 --max-requests 20 --max-requests-jitter 5 pim.wsgi:application"]
2829

2930
EXPOSE 8000/tcp

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
The Django application is designed to allow users to view power usage charts for MyStrom and Shelly3EM devices. The application collects the current power usage data every 60 seconds, which is then used to calculate and present power usage charts to the user.
44

5+
This project uses [TimeScaleDB](https://www.timescale.com/) as database.
6+
The database is optimized for time-series data and this project uses queries which are only compatible with TimeScaleDB.
7+
58
There are two main views: the Device view and the Result view. Here's what each view looks like:
69

710
#### Device View
@@ -37,7 +40,6 @@ pip install -r requirements.txt
3740

3841
Here's a breakdown of all the environment variables that are being used in the Django application:
3942

40-
- `ENGINE_TYPE`: Specifies the type of database engine to use, either `mysql` or `postgresql`.
4143
- `DB_NAME`: Specifies the name of the database to use.
4244
- `DB_USER`: Specifies the username to use when connecting to the database.
4345
- `DB_PASSWORD`: Specifies the password to use when connecting to the database.
@@ -87,7 +89,6 @@ Static files are served with WhiteNoise.
8789
docker run \
8890
--name mystrom-interface \
8991
-p 8000:8000 \
90-
-e ENGINE_TYPE={mysql/postgresql}
9192
-e DB_NAME=db-name \
9293
-e DB_USER=username \
9394
-e DB_PASSWORD=password \
@@ -107,7 +108,3 @@ Make sure to replace the content in `{...}` with your variable of your choice.
107108
```sh
108109
docker compose up
109110
```
110-
111-
## Configurations
112-
### Important things to know
113-
Database configuraton in `pim/settings.py` under `DATABASES`

docker-compose.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,42 @@
1+
12
services:
23
interface:
34
container_name: interface
45
image: ghcr.io/maexled/mystrom-django-interface:master
5-
# build: .
6+
# build: .
67
privileged: true
78
restart: always
89
ports:
910
- "80:8000"
1011
env_file:
1112
- .env
13+
depends_on:
14+
- timescaledb
15+
16+
timescaledb:
17+
image: timescale/timescaledb:latest-pg16
18+
environment:
19+
POSTGRES_DB: postgres
20+
POSTGRES_USER: postgres
21+
POSTGRES_PASSWORD: postgres
22+
volumes:
23+
- ./db-data:/var/lib/postgresql/data
24+
25+
pgadmin:
26+
image: dpage/pgadmin4
27+
environment:
28+
PGADMIN_DEFAULT_EMAIL: admin@admin.com
29+
PGADMIN_DEFAULT_PASSWORD: admin
30+
ports:
31+
- "8080:80"
32+
depends_on:
33+
- timescaledb
1234

1335
requester:
1436
container_name: interface-requester
1537
image: curlimages/curl:7.80.0
16-
command: ["sh", "-c", "while true; do sleep 60; curl -X POST http://interface:8000/shelly-api/devices/request-and-save-results >/dev/null 2>&1 & curl -X POST http://interface:8000/api/devices/request-and-save-results >/dev/null 2>&1; done"]
38+
command: ["sh", "-c", "while true; do sleep 60; curl -fsS -X POST http://interface:8000/shelly-api/devices/request-and-save-results && curl -fsS -X POST http://interface:8000/api/devices/request-and-save-results; done"]
1739
depends_on:
1840
- interface
41+
1942

interface/admin.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
from django.contrib import admin
2-
31
# Register your models here.

interface/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33

44
class MystromConfig(AppConfig):
5-
default_auto_field = 'django.db.models.BigAutoField'
6-
name = 'interface'
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "interface"

interface/forms.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
11
from django import forms
22
from mystrom_rest.models import MystromDevice
33
from shelly3em_rest.models import Shelly3EMDevice
4-
4+
5+
56
# creating a form
67
class MystromDeviceForm(forms.ModelForm):
7-
88
# create meta class
99
class Meta:
1010
# specify model to be used
1111
model = MystromDevice
12-
12+
1313
# specify fields to be used
14-
fields = [
15-
"name",
16-
"ip",
17-
"active"
18-
]
14+
fields = ["name", "ip", "active"]
15+
1916

2017
class Shelly3EMDeviceForm(forms.ModelForm):
21-
2218
# create meta class
2319
class Meta:
2420
# specify model to be used
2521
model = Shelly3EMDevice
26-
22+
2723
# specify fields to be used
28-
fields = [
29-
"name",
30-
"ip",
31-
"active"
32-
]
24+
fields = ["name", "ip", "active"]

0 commit comments

Comments
 (0)