diff web_to_mqtt.py @ 5:d97a5930db7e

closer
author drewp@bigasterisk.com
date Wed, 06 Mar 2024 16:38:58 -0800
parents e273cc60b389
children e36abecb48a1
line wrap: on
line diff
--- a/web_to_mqtt.py	Tue Mar 05 18:12:15 2024 -0800
+++ b/web_to_mqtt.py	Wed Mar 06 16:38:58 2024 -0800
@@ -1,6 +1,7 @@
 import asyncio
-import base64
-import io
+import itertools
+import random
+import struct
 import subprocess
 import tempfile
 
@@ -9,69 +10,77 @@
 
 
 class WebRenderer:
+
     def __init__(self):
-        self.chrome_proc = subprocess.Popen(["google-chrome"])
+        self.chrome_proc = subprocess.Popen(["google-chrome", "--headless"])
         print("Chrome subprocess started.")
 
-    def capture_screenshot(self, url, output_path):
-        out = tempfile.NamedTemporaryFile(suffix=".png")
+    async def capture_screenshot(self, url) -> Image.Image:
+        out = tempfile.NamedTemporaryFile(suffix=".png", prefix='webrenderer_')
         screenshot_command = [
             "google-chrome",
             "--headless",
+            "--window-size=320,320",
             f"--screenshot={out.name}",
             url,
         ]
-        subprocess.run(screenshot_command)
-        return Image.open(out.name)
-
+        subprocess.run(screenshot_command,
+                       stdout=subprocess.DEVNULL,
+                       stderr=subprocess.DEVNULL)
+        return Image.open(out.name).convert('RGB')
 
 
-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
+block = 320 // 10
+
+dirtyQueue = {}
 
 
-async def check_for_changes(renderer, last_image):
-
-    renderer.capture_screenshot("https://en.wikipedia.org", "/tmp/output.png")
+async def check_for_changes(renderer, client, last_image):
+    current_image = await renderer.capture_screenshot(
+        "http://localhost:8002/front-door-display/scheduleLcd.html")
+    diff_image = ImageChops.difference(last_image, current_image)
+    for y in range(0, current_image.height, block):
+        for x in range(0, current_image.width, block):
+            box = (x, y, x + block, y + block)
+            region = diff_image.crop(box)
+            if region.getbbox():
+                dirtyQueue[(x, y)] = current_image.crop(box)
+                await asyncio.sleep(0)
 
-    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 sendDirty(client):
+    while True:
+        if dirtyQueue:
+            # pos = random.choice(list(dirtyQueue.keys()))
+            pos = min(list(dirtyQueue.keys()))
+            img = dirtyQueue.pop(pos)
+            await tell_lcd(client, pos[0], pos[1], img)
+        await asyncio.sleep(.15)
+
+framesSent = itertools.count()
+
+
+async def tell_lcd(client: aiomqtt.Client, x: int, y: int,
+                   region: Image.Image):
+    seq = next(framesSent)
+    msg = struct.pack('HHHHH', seq, x, y, region.width, region.height) + region.tobytes()
+    print(f'send {seq=} {x=} {y=} {region.width=} {region.height=}  ', end='\r', flush=True)
+    await client.publish('display/squib/updates', msg, qos=0)
 
 
 async def main():
-    # also listen for dirty msgs from the web page; see ts
+    # also listen for dirty msgs from the web page; see ts.
+    # also get notified of new mqtt listeners who need a full image refresh.
     renderer = WebRenderer()
-    async with aiomqtt.Client("mqtt_client") as client:
-        last_image = None
+    async with aiomqtt.Client("mqtt2") as client:
+        asyncio.create_task(sendDirty(client))
+        last_image = Image.new('RGB', (320, 320))
         while True:
-            last_image = await check_for_changes(renderer,last_image)
-            await asyncio.sleep(2)  # Adjust the interval as needed
+            last_image = await check_for_changes(renderer, client, last_image)
+            # we could get the web page to tell us when any dom changes
+            await asyncio.sleep(5)
 
 
 if __name__ == "__main__":