Here is my situation, I’m creating a RaspPi4 controlled solar heating system that is quite complicated in options / scope. I’m essentially having to rewrite their entire app in Python. Not only that, there are two apps, a MainApp which is just a usual Python script, and a DjangoApp which is also run on the same Pi. Things don’t have to work remotely. All that I need is the ability for the user to log into the site from their phone and make setting changes in the DjangoApp’s pages. I figured even though it’s a slow-running heat system (heat transfer is naturally a slow process), I wanted all comm channels to be fast. The previous version of the app was cludgy because it relied on the Database to communicate between MainApp and their web app (PHP! ). I tried to get the PHP going, but had many issues getting the database migrating from one of several remote live systems. The two coders that worked on it have vanished so can’t really help get it setup for development again. I suggested Django since I wrote a BSS (Bootstrap Studio) to Django template export script, Django is modern, Python based, so everything is under one systems language, and I know some Django. Not to mention database model management is also done in Python and is very nice. Anyhow, long story short here is the communication topology I came up with:
I’ve spent a week getting both MainApp ← {WebSocket} → DjangoApp and DjangoApp ← {WebSocket} → Browser JS both working error-free and coded in a robust way with classes and even and ErrorReporting class. Learned a lot of new JS features that were previously black magic.
One thing to note is that I have a full-stack Debug environment setup: I am able to debug in WingWare (Python) and VSCode (JS) on the same running instance. Even if the project is on the RaspPi4. However, currently because I don’t have time to write a Copy-over-to-Pi build step over SSH (though I know how from previous work), I am simply working on my Windows 10 workstation for now. So there is no MainApp yet, just a simple loop that keeps sending a text string or a number over the WS to Django.
Here are the relevant sections of code:
# sensor_test/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json
from channels.layers import get_channel_layer
from channels.generic.websocket import AsyncWebsocketConsumer
class SensorTestConsumer(AsyncWebsocketConsumer):
groups = ["sensor_test_group"]
async def connect(self):
await self.accept();
await get_channel_layer().group_add(
'sensor_update_group',
self.channel_name
)
async def disconnect(self, close_code):
pass
async def receive(self, text_data=None, bytes_data=None):
print(f'{self.__class__.__name__} received: {text_data}')
# asynchronously wait for string to send
await self.send("Browser JS -> MainApp 😎😎😎😎😎😎")
async def forward_to_client_browser(self, event):
print(event['message'])
#main_app_comms/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from channels.layers import get_channel_layer
from channels.generic.websocket import AsyncWebsocketConsumer
class ServerConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
self.send("Django->MainApp")
async def disconnect(self, close_code):
pass
async def receive(self, text_data=None, bytes_data=None):
print(f'{self.__class__.__name__} received: {text_data}. Forwarding to the SensorTestConsumer')
await get_channel_layer().group_send(
"sensor_update_group",
{
"type": "forward_to_client_browser",
"message" : json.dumps({ "type": "SensorTestUpdate", "sensor_vals" : {"HT3": text_data}})
}
)
### y wait for string to send
await self.send("Django -> MainApp 😎😎😎😎😎😎")
async def forward_to_client_browser(self, event):
print("TEST TEST TEST") # This is the only place the event gets forwarded to
#website/asgi.py
# This file completely rewritten per Django Channels install instructions.
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import main_app_comms.routing
import sensor_test.routing
from channels.auth import AuthMiddlewareStack
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'website.settings')
django_asgi_app = get_asgi_application()
websocket_urlpatterns = sensor_test.routing.websocket_urlpatterns + main_app_comms.routing.websocket_urlpatterns
application = ProtocolTypeRouter({
"http" : django_asgi_app,
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})
#website/settings.py
# .....
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)],
},
},
}
Expected Behavior: For a message received by MainAppCommsConsumer to be forwarded correctly to the “sensor_test_group” of connections of which the Consumer called SensorTestConsumer is subscribed.
Actual Behavior: The best actual behavior I’ve seen is that the group_send() message is being sent but only to the MainAppCommsConsumer which is a useless feature, because MainAppCommsConsumer was the one who received the original message.
What I need to do is in general “forward the MainApp data” to the client browser WS connection. Any other way of occomplishing this will be fine, but remember in Django there could be 1-N users connected and viewing the page, so Django Channels group sending is exactly appropriate.
I have considered but do not want to code the entire MainApp as Django worker/background processes.
I would be willing to pay you even if you have a freelancer.com account and/or Zelle on your banking app. This is critical for me. They want it done ASAP and this is probably typical of any project, however this is my second embedded project and they’re giving me the bum’s rush a second time.
Would also be willing to post my code on a public GitHub repository for others to try to debug.
Currently what I’m doing is debugging the Channels library code starting from group_send() with my WingWare debugger. Sometimes this helps, but it’s up-in-the-air when I’ll discover the bug or my coding error. Hence this post.