Want to build Django Channels Webserver (SQLite) with real time updates from multiple raspberry pi’s (Message on signal change) over a websocket connection.
Therefore i tried to build a simple python script running on the raspberry pi, opening a websocket connection to the Webserver and sending a value update over websocket immediately when a value changes, and only the value changed.
Each value update ought to be stored into the Django database.
A client should be able to access via simple browser application (JavaScript/HTML/CSS) the database and filter it for criteria (like location, battery state, etc.). The visualised data ought to be in real-time, therefore maybe keeping updated directly via channels from the raspberry pi updates.
I ruled out MQTT, since there seems not to be a propper integration into Django.
I wanted to avoid REST API, since it does not provide real real-time behaviour (allthough i could of course send updates every some seconds).
My Proof-of-Concept attempts have been conducted locally on one machine, but i failed already in connecting to the django channels server via python websockets script.
django project name: solserver
django appname: database
websocket-client:
import asyncio
import websockets
class Base():
"""Basic class, containing common functionality among all versions for Raspberry Pi."""
def __init__(self, serial_number: str, software: str, country: str, postcode: str) -> None:
self.serial_number = serial_number
self.software = software # software version
self.country = country # country
self.postcode = postcode # postcode
self.battery = 0 # % - state of charge of the battery
self.server_url = 'ws://localhost:8000/update/'
self.publish = True
async def publish_state(self, interval: int = 2):
"""Coro: Periodically publishes instance state to websocket URL."""
async with websockets.connect(self.server_url) as ws:
print(f'connected to {self.server_url}')
while self.publish:
await ws.send('hello')
# response = await ws.recv()
# print(f'response: {response}')
await asyncio.sleep(interval)
async def main():
rpi = Base("1234","1.0","at","1040")
await rpi.publish_state()
if __name__ == "__main__":
print(f'running {__file__}')
asyncio.run(main())
consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
class ServerConsumer(AsyncWebsocketConsumer):
groups = ["broadcast"]
async def connect(self):
await self.accept()
routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path("update/", consumers.ServerConsumer.as_asgi()),
]
asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from database.routing import websocket_urlpatterns
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'solserver.settings')
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
),
}
)
settings.py
...
INSTALLED_APPS = [
'daphne',
'database',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
...
Django Channels Server log:
python .\manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 07, 2024 - 16:28:19
Django version 5.0.3, using settings 'solserver.settings'
Starting ASGI/Daphne version 4.1.0 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
WebSocket HANDSHAKING /update/ [127.0.0.1:55755]
WebSocket REJECT /update/ [127.0.0.1:55755]
WebSocket DISCONNECT /update/ [127.0.0.1:55755]
websocket-client log: (ignore the slightly different file naming on my computer)
running v2.py
Traceback (most recent call last):
File "v2.py", line 42, in <module>
asyncio.run(main())
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 649, in run_until_complete
return future.result()
File "v2.py", line 41, in main
await v2.publish_state()
File "base.py", line 39, in publish_state
async with websockets.connect(self.server_url) as ws:
File "client.py", line 629, in __aenter__
return await self
File "client.py", line 647, in __await_impl_timeout__
return await self.__await_impl__()
File "client.py", line 654, in __await_impl__
await protocol.handshake(
File "client.py", line 325, in handshake
raise InvalidStatusCode(status_code, response_headers)
websockets.exceptions.InvalidStatusCode: server rejected WebSocket connection: HTTP 403
Utilizing:
Python 3.10.11
Django 5.0.3
Channels 4.0
Docker version 25.0.3
(for Redis Image)
Daphne 4.1.0
Thanks for your thoughts in advance!