changeset 4:e273cc60b389

draft of web-to-lcd and simulator
author drewp@bigasterisk.com
date Tue, 05 Mar 2024 18:12:15 -0800
parents 045013c772ed
children d97a5930db7e
files lcd_simulator.py pdm.lock pyproject.toml src/scheduleLcd.ts web_to_mqtt.py
diffstat 5 files changed, 231 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lcd_simulator.py	Tue Mar 05 18:12:15 2024 -0800
@@ -0,0 +1,40 @@
+import asyncio
+import pygame
+import aiomqtt
+import struct
+
+WIDTH = 320
+HEIGHT = 320
+
+pygame.init()
+screen = pygame.display.set_mode((WIDTH, HEIGHT))
+clock = pygame.time.Clock()
+
+
+async def on_message(client, userdata, message):
+    payload = bytes(message.payload)
+    x, y, w, h = struct.unpack("HHHH", payload[:8])
+    buf = payload[8:]
+    for dy in range(h):
+        for dx in range(w):
+            off = w * dy + dx
+            r, g, b = buf[off * 3 : off * 3 + 3]
+            screen.set_at((x + dx, y + dy), (r, g, b))
+
+
+async def main():
+    async with aiomqtt.Client("mqtt2.bigasterisk.com") as client:
+        client.on_message = on_message
+        await client.subscribe('display/squib/updates')
+
+        while True:
+            for event in pygame.event.get():
+                if event.type == pygame.QUIT:
+                    raise SystemExit
+
+            await client.loop_read()
+            pygame.display.flip()
+            clock.tick(60)
+
+
+asyncio.run(main())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pdm.lock	Tue Mar 05 18:12:15 2024 -0800
@@ -0,0 +1,57 @@
+# This file is @generated by PDM.
+# It is not intended for manual editing.
+
+[metadata]
+groups = ["default"]
+strategy = ["cross_platform", "inherit_metadata"]
+lock_version = "4.4.1"
+content_hash = "sha256:6afa60fb1d3171c06f7d569932d978d79af5c9e326af7ce4c7e5a60a971c6a4b"
+
+[[package]]
+name = "aiomqtt"
+version = "2.0.0"
+requires_python = ">=3.8,<4.0"
+summary = "The idiomatic asyncio MQTT client, wrapped around paho-mqtt"
+groups = ["default"]
+dependencies = [
+    "paho-mqtt<2.0.0,>=1.6.0",
+]
+files = [
+    {file = "aiomqtt-2.0.0-py3-none-any.whl", hash = "sha256:f3b97eca4a5a2c40769ed14f660520f733be1d2ec383a9976153fe49141e2fa2"},
+    {file = "aiomqtt-2.0.0.tar.gz", hash = "sha256:3d480429334bdba4e4b9936c6cc198ea4f76a94d36cf294e0f713ec59f6a2120"},
+]
+
+[[package]]
+name = "paho-mqtt"
+version = "1.6.1"
+summary = "MQTT version 5.0/3.1.1 client class"
+groups = ["default"]
+files = [
+    {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"},
+]
+
+[[package]]
+name = "pygame"
+version = "2.5.2"
+requires_python = ">=3.6"
+summary = "Python Game Development"
+groups = ["default"]
+files = [
+    {file = "pygame-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34646ca20e163dc6f6cf8170f1e12a2e41726780112594ac061fa448cf7ccd75"},
+    {file = "pygame-2.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b8a6e351665ed26ea791f0e1fd649d3f483e8681892caef9d471f488f9ea5ee"},
+    {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc346965847aef00013fa2364f41a64f068cd096dcc7778fc306ca3735f0eedf"},
+    {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35632035fd81261f2d797fa810ea8c46111bd78ceb6089d52b61ed7dc3c5d05f"},
+    {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"},
+    {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"},
+    {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"},
+    {file = "pygame-2.5.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:e708fc8f709a0fe1d1876489345f2e443d47f3976d33455e2e1e937f972f8677"},
+    {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c13edebc43c240fb0532969e914f0ccefff5ae7e50b0b788d08ad2c15ef793e4"},
+    {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b4a7cbfc9fe2055abc21b0251cc17dea6dff750f0e1c598919ff350cdbffe"},
+    {file = "pygame-2.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e58e2b0c791041e4bccafa5bd7650623ba1592b8fe62ae0a276b7d0ecb314b6c"},
+    {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0bd67426c02ffe6c9827fc4bcbda9442fbc451d29b17c83a3c088c56fef2c90"},
+    {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dcff6cbba1584cf7732ce1dbdd044406cd4f6e296d13bcb7fba963fb4aeefc9"},
+    {file = "pygame-2.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce4b6c0bfe44d00bb0998a6517bd0cf9455f642f30f91bc671ad41c05bf6f6ae"},
+    {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68c4e8e60b725ffc7a6c6ecd9bb5fcc5ed2d6e0e2a2c4a29a8454856ef16ad63"},
+    {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f3849f97372a3381c66955f99a0d58485ccd513c3d00c030b869094ce6997a6"},
+    {file = "pygame-2.5.2.tar.gz", hash = "sha256:c1b89eb5d539e7ac5cf75513125fb5f2f0a2d918b1fd6e981f23bf0ac1b1c24a"},
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyproject.toml	Tue Mar 05 18:12:15 2024 -0800
@@ -0,0 +1,18 @@
+[project]
+name = "front-door-display"
+version = "0.1.0"
+description = "Default template for PDM package"
+authors = [
+    {name = "", email = ""},
+]
+dependencies = [
+    "pygame>=2.5.2",
+    "aiomqtt>=2.0.0",
+]
+requires-python = "==3.11.*"
+readme = "README.md"
+license = {text = "MIT"}
+
+
+[tool.pdm]
+distribution = false
--- a/src/scheduleLcd.ts	Tue Mar 05 17:24:18 2024 -0800
+++ b/src/scheduleLcd.ts	Tue Mar 05 18:12:15 2024 -0800
@@ -10,6 +10,44 @@
 const EV = "http://bigasterisk.com/event#";
 const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
 
+
+// Function to send a POST request
+function sendPostRequest(data) {
+  fetch('https://example.com/api', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify(data),
+  })
+  .then(response => {
+    if (!response.ok) {
+      throw new Error('Network response was not ok');
+    }
+    return response.json();
+  })
+  .then(data => {
+    console.log('POST request successful');
+    // Handle response data if needed
+  })
+  .catch(error => {
+    console.error('There was a problem with the POST request:', error);
+  });
+}
+
+// Callback function to handle DOM changes
+function handleDomChanges(mutationsList, observer) {
+  // Send a POST request whenever the DOM changes
+  sendPostRequest({ domChanges: mutationsList });
+}
+
+// Create a MutationObserver instance
+const observer = new MutationObserver(handleDomChanges);
+
+// Start observing the DOM for changes
+observer.observe(document.body, { subtree: true, childList: true, attributes: true });
+
+
 function getLiteral(store: Store, graph: Term, subj: Quad_Subject, pred: Quad_Predicate, missing: string | null): string {
   let out = null;
   store.getObjects(subj, pred, graph).forEach((attr) => {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web_to_mqtt.py	Tue Mar 05 18:12:15 2024 -0800
@@ -0,0 +1,78 @@
+import asyncio
+import base64
+import io
+import subprocess
+import tempfile
+
+import aiomqtt
+from PIL import Image, ImageChops
+
+
+class WebRenderer:
+    def __init__(self):
+        self.chrome_proc = subprocess.Popen(["google-chrome"])
+        print("Chrome subprocess started.")
+
+    def capture_screenshot(self, url, output_path):
+        out = tempfile.NamedTemporaryFile(suffix=".png")
+        screenshot_command = [
+            "google-chrome",
+            "--headless",
+            f"--screenshot={out.name}",
+            url,
+        ]
+        subprocess.run(screenshot_command)
+        return Image.open(out.name)
+
+
+
+async def render_webpage_to_png():
+    # Code to render the webpage to a PNG
+    # Replace this with your actual code to render the webpage to a PNG
+    # For example, you can use libraries like Selenium or requests-html to render the webpage and capture a screenshot
+    pass
+
+
+async def check_for_changes(renderer, last_image):
+
+    renderer.capture_screenshot("https://en.wikipedia.org", "/tmp/output.png")
+
+    current_image = await render_webpage_to_png()
+    if last_image is not None:
+        diff_image = ImageChops.difference(last_image, current_image)
+        # Iterate over 64x64 pixel squares and check for changes
+        for y in range(0, diff_image.height, 64):
+            for x in range(0, diff_image.width, 64):
+                box = (x, y, x + 64, y + 64)
+                region = diff_image.crop(box)
+                if (region.getbbox()
+                    ):  # Check if region is not empty (i.e., contains changes)
+                    # Send changed square as MQTT message
+queue these
+                    await send_mqtt_message(region)
+    return current_image
+
+
+async def send_mqtt_message(region):
+    # Convert changed region to base64 encoded string
+    buffer = io.BytesIO()
+    region.save(buffer, format="PNG")
+    base64_image = base64.b64encode(buffer.getvalue()).decode("utf-8")
+    mqtt_client = aiomqtt.Client("mqtt_client")
+    await mqtt_client.connect("mqtt://broker.example.com")
+    await mqtt_client.publish("changed_squares", base64_image, qos=1)
+    await mqtt_client.disconnect()
+
+
+async def main():
+    # also listen for dirty msgs from the web page; see ts
+    renderer = WebRenderer()
+    async with aiomqtt.Client("mqtt_client") as client:
+        last_image = None
+        while True:
+            last_image = await check_for_changes(renderer,last_image)
+            await asyncio.sleep(2)  # Adjust the interval as needed
+
+
+if __name__ == "__main__":
+    asyncio.run(main())