Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Aiky30/shed-pi into feature/paginat…
Browse files Browse the repository at this point in the history
…ed-endpoints
  • Loading branch information
Aiky30 committed Oct 25, 2024
2 parents c16c789 + e9f1aeb commit ddc4d58
Show file tree
Hide file tree
Showing 43 changed files with 842 additions and 89 deletions.
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,30 @@
A package to run a Raspberry Pi in an outbuilding such as a garden shed, for the internet of sheds.

Automated use cases:

- Weather station
- Garden lighting
- Security

## Design Principles

The Hub contains a Protocol, and can utilise modules
A device contains a protocol, and can utilise modules

## TODO:

1. Fix: Bug: The script startup gets an incorrect time (Hasn't yet got the internet time)
2. Instructions on pinout
3. Tests for the utils and base protocol
4. Extend Baseprotocol with a reusable run method
5. General Integration test for the logger using a fake module
6. Test default endpoint address settings work in theory, because the test above overrides them
7. Device startup and shutdown needs a unit test
8. Temp probe should be a fixture
9. CPU temp probe should be a fixture

### Wish list:

- Poetry (Not started)
- ASGI backend server (Daphne)
- Native webcomponent FE with Bootstrap
Expand All @@ -40,6 +55,7 @@ sudo raspi-config
3. Agree to installing the 1-Wire script

4. Reboot

```shell
sudo reboot
```
Expand All @@ -51,6 +67,7 @@ ls /sys/bus/w1/devices/
```

Check the value read, be sure to change "28-000003ebbf13" to the values listed above:

```shell
cat /sys/bus/w1/devices/28-000003ebbf13/w1_slave
```
Expand All @@ -67,20 +84,21 @@ With the contents

```ini
[Unit]
Description=Shed-Pi
After=multi-user.target
StartLimitIntervalSec=0
Description = Shed-Pi
After = multi-user.target
StartLimitIntervalSec = 0
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/shed-pi/temp_logger.py
StandardInput=tty-force
Type = simple
ExecStart = /usr/bin/python3 /home/shed-pi/temp_logger.py
StandardInput = tty-force
[Install]
WantedBy=multi-user.target
WantedBy = multi-user.target
```

Enable the service, (CAVEAT: didn't work when manually starting, works on reboot)
```shell
sudo systemctl daemon-reload
sudo systemctl enable shed-pi.service
Expand All @@ -89,6 +107,7 @@ sudo systemctl status shed-pi.service
```
Read the logs
```shell
tail -f /var/log/shed-pi.log
```
Expand All @@ -100,18 +119,19 @@ tail -f /var/log/shed-pi.log
Install pre-commit: https://pre-commit.com/
Configure precommit on your local git for the project by running the following at the root of the project:
```shell
pre-commit install
```
Pre-commit will then automatically run for your changes at commit time.
Pre-commit will then automatically run for your changes at commit time.
To run the pre-commit config manually, run:
```shell
pre-commit run --all-files
```
## Release
Generate the release
Expand Down
9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
asgiref==3.7.2
attrs==23.2.0
black==24.2.0
build==0.10.0
certifi==2024.2.2
cfgv==3.4.0
charset-normalizer==3.3.2
click==8.1.7
distlib==0.3.8
Django==5.0.1
djangorestframework==3.14.0
Expand All @@ -10,11 +14,14 @@ factory-boy==3.3.0
Faker==22.5.0
filelock==3.13.1
identify==2.5.33
idna==3.6
iniconfig==2.0.0
jsonschema==4.21.1
jsonschema-specifications==2023.12.1
mypy-extensions==1.0.0
nodeenv==1.8.0
packaging==23.1
pathspec==0.12.1
platformdirs==4.1.0
pluggy==1.4.0
pre-commit==3.6.0
Expand All @@ -25,10 +32,12 @@ python-dateutil==2.8.2
pytz==2023.3.post1
PyYAML==6.0.1
referencing==0.32.1
requests==2.31.0
rpds-py==0.17.1
setuptools-scm==7.1.0
six==1.16.0
sqlparse==0.4.4
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.2.1
virtualenv==20.25.0
22 changes: 21 additions & 1 deletion shedpi_hub_dashboard/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import ClassVar

from django.contrib import admin
from django.contrib.admin.options import InlineModelAdmin

from .models import Device, DeviceModule, DeviceModuleReading

Expand All @@ -8,11 +11,28 @@ class DeviceAdmin(admin.ModelAdmin):
list_display = ("id", "name")


class DeviceModuleReadingInlineAdmin(admin.TabularInline):
model = DeviceModuleReading
extra = 0
can_delete = False

# TODO: trying to limit the inline with pagination
# list_per_page = 5 # No of records per page
#
# def get_queryset(self, request):
# # TODO: return type -> QuerySet
#
# queryset = super().get_queryset(request)
#
# return queryset[:5]


@admin.register(DeviceModule)
class DeviceModuleAdmin(admin.ModelAdmin):
pass
inlines: ClassVar[list[InlineModelAdmin]] = [DeviceModuleReadingInlineAdmin]


@admin.register(DeviceModuleReading)
class DeviceModuleReadingAdmin(admin.ModelAdmin):
list_display = ("id", "device_module_id", "created_at")
list_filter = ("device_module_id",)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.1 on 2024-04-19 18:32

import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("shedpi_hub_dashboard", "0001_initial"),
]

operations = [
migrations.AlterField(
model_name="devicemodulereading",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
3 changes: 2 additions & 1 deletion shedpi_hub_dashboard/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import uuid

from django.db import models
from django.utils import timezone
from jsonschema import validate

from shedpi_hub_dashboard.forms.fields import PrettyJsonField
Expand Down Expand Up @@ -37,7 +38,7 @@ class DeviceModuleReading(models.Model):
help_text="A device whose readings were collected.",
)
data = PrettyJsonField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_at = models.DateTimeField(default=timezone.now)

def validate_data(self) -> None:
"""
Expand Down
3 changes: 2 additions & 1 deletion shedpi_hub_dashboard/static/shedpi_hub_dashboard/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ section.append(tableContainer);
let loadTableData = function (deviceModuleId) {

// const url = section.getAttribute("data-json-feed")
const url = "http://localhost:8000//api/v1/device-module-readings/"
const url = window.location.origin + "/api/v1/device-module-readings/"
const endpoint = new URL(url);
endpoint.searchParams.append("device_module", deviceModuleId);
endpoint.searchParams.append("format", "json");

// FIXME: Need data output and need headings from Schema

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import json
from unittest.mock import Mock, patch

import pytest
from rest_framework import status

from shedpi_hub_dashboard.models import DeviceModuleReading
from shedpi_hub_dashboard.tests.utils.factories import (
DeviceModuleFactory,
)
from standalone_modules.shed_pi_module_utils.data_submission import (
ReadingSubmissionService,
)
from standalone_modules.temperature_module.temperature_probe import (
TempProbe,
)


@patch("standalone_modules.temperature_module.temperature_probe.Path")
@pytest.mark.django_db
def test_temperature_module_reading_submission(mocked_path, live_server):
schema = {
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Reading",
"type": "object",
"properties": {
"temperature": {"type": "string", "description": "The Temperature"},
},
}
device_module = DeviceModuleFactory(schema=schema)
submission_service = ReadingSubmissionService()
# Override the serviuce url
submission_service.base_url = live_server.url

probe = TempProbe(submission_service=submission_service)
# Override the module id
probe.device_id = device_module.id

probe.read_temp_raw = Mock(
return_value=[
"YES",
"t=12345",
]
)

response = probe.submit_reading()

assert response.status_code == status.HTTP_201_CREATED

response_data = json.loads(response.text)

assert "created_at" in response_data
assert DeviceModuleReading.objects.filter(device_module=device_module).count() == 1
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rest_framework.exceptions import ValidationError
from rest_framework.reverse import reverse

from shedpi_hub_dashboard.models import DeviceModuleReading
from shedpi_hub_dashboard.tests.utils.factories import (
DeviceModuleFactory,
DeviceModuleReadingFactory,
Expand Down Expand Up @@ -149,3 +150,4 @@ def test_device_module_reading_submission(client):

assert response.status_code == status.HTTP_201_CREATED
assert response.data["data"] == data
assert DeviceModuleReading.objects.filter(device_module=device_module).count() == 1
Empty file added standalone_modules/__init__.py
Empty file.
Empty file.
64 changes: 64 additions & 0 deletions standalone_modules/rpi/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging
import os

import requests

from standalone_modules.shed_pi_module_utils.data_submission import (
ReadingSubmissionService,
)
from standalone_modules.shed_pi_module_utils.utils import get_time

logger = logging.getLogger(__name__)

MODULE_VERSION = "0.0.1"


class RPIDevice:
def __init__(
self,
submission_service: ReadingSubmissionService,
device_module_id: int,
cpu_module_id: int,
) -> None:
self.device_module_id = device_module_id
self.cpu_module_id = cpu_module_id
self.submission_service = submission_service

def get_cpu_temp(self):
cpu_temp = os.popen("vcgencmd measure_temp").readline()

# Convert the temp read from the OS to a clean float
return float(cpu_temp.replace("temp=", "").replace("'C\n", ""))

def submit_reading(self) -> requests.Response:
"""
Submits a reading to an external endpoint
:return:
"""
cpu_temp = self.get_cpu_temp()

# FIXME: Should this be a float or a string? Broke the test
data = {"temperature": str(cpu_temp)}

response = self.submission_service.submit(
device_module_id=self.device_module_id, data=data
)

return response

def submit_device_startup(self):
logger.info(f"Shed pi started: {get_time()}, using version: {MODULE_VERSION}")

data = {"power": True}
response = self.submission_service.submit(
device_module_id=self.device_module_id, data=data
)
return response

def submit_device_shutdown(self):
data = {"power": False}
response = self.submission_service.submit(
device_module_id=self.device_module_id, data=data
)
return response
Loading

0 comments on commit ddc4d58

Please sign in to comment.