Mercurial > code > home > repos > front-door-display
comparison web_to_mqtt.py @ 5:d97a5930db7e
closer
author | drewp@bigasterisk.com |
---|---|
date | Wed, 06 Mar 2024 16:38:58 -0800 |
parents | e273cc60b389 |
children | e36abecb48a1 |
comparison
equal
deleted
inserted
replaced
4:e273cc60b389 | 5:d97a5930db7e |
---|---|
1 import asyncio | 1 import asyncio |
2 import base64 | 2 import itertools |
3 import io | 3 import random |
4 import struct | |
4 import subprocess | 5 import subprocess |
5 import tempfile | 6 import tempfile |
6 | 7 |
7 import aiomqtt | 8 import aiomqtt |
8 from PIL import Image, ImageChops | 9 from PIL import Image, ImageChops |
9 | 10 |
10 | 11 |
11 class WebRenderer: | 12 class WebRenderer: |
13 | |
12 def __init__(self): | 14 def __init__(self): |
13 self.chrome_proc = subprocess.Popen(["google-chrome"]) | 15 self.chrome_proc = subprocess.Popen(["google-chrome", "--headless"]) |
14 print("Chrome subprocess started.") | 16 print("Chrome subprocess started.") |
15 | 17 |
16 def capture_screenshot(self, url, output_path): | 18 async def capture_screenshot(self, url) -> Image.Image: |
17 out = tempfile.NamedTemporaryFile(suffix=".png") | 19 out = tempfile.NamedTemporaryFile(suffix=".png", prefix='webrenderer_') |
18 screenshot_command = [ | 20 screenshot_command = [ |
19 "google-chrome", | 21 "google-chrome", |
20 "--headless", | 22 "--headless", |
23 "--window-size=320,320", | |
21 f"--screenshot={out.name}", | 24 f"--screenshot={out.name}", |
22 url, | 25 url, |
23 ] | 26 ] |
24 subprocess.run(screenshot_command) | 27 subprocess.run(screenshot_command, |
25 return Image.open(out.name) | 28 stdout=subprocess.DEVNULL, |
29 stderr=subprocess.DEVNULL) | |
30 return Image.open(out.name).convert('RGB') | |
26 | 31 |
27 | 32 |
33 block = 320 // 10 | |
28 | 34 |
29 async def render_webpage_to_png(): | 35 dirtyQueue = {} |
30 # Code to render the webpage to a PNG | |
31 # Replace this with your actual code to render the webpage to a PNG | |
32 # For example, you can use libraries like Selenium or requests-html to render the webpage and capture a screenshot | |
33 pass | |
34 | 36 |
35 | 37 |
36 async def check_for_changes(renderer, last_image): | 38 async def check_for_changes(renderer, client, last_image): |
39 current_image = await renderer.capture_screenshot( | |
40 "http://localhost:8002/front-door-display/scheduleLcd.html") | |
41 diff_image = ImageChops.difference(last_image, current_image) | |
42 for y in range(0, current_image.height, block): | |
43 for x in range(0, current_image.width, block): | |
44 box = (x, y, x + block, y + block) | |
45 region = diff_image.crop(box) | |
46 if region.getbbox(): | |
47 dirtyQueue[(x, y)] = current_image.crop(box) | |
48 await asyncio.sleep(0) | |
37 | 49 |
38 renderer.capture_screenshot("https://en.wikipedia.org", "/tmp/output.png") | |
39 | |
40 current_image = await render_webpage_to_png() | |
41 if last_image is not None: | |
42 diff_image = ImageChops.difference(last_image, current_image) | |
43 # Iterate over 64x64 pixel squares and check for changes | |
44 for y in range(0, diff_image.height, 64): | |
45 for x in range(0, diff_image.width, 64): | |
46 box = (x, y, x + 64, y + 64) | |
47 region = diff_image.crop(box) | |
48 if (region.getbbox() | |
49 ): # Check if region is not empty (i.e., contains changes) | |
50 # Send changed square as MQTT message | |
51 queue these | |
52 await send_mqtt_message(region) | |
53 return current_image | 50 return current_image |
54 | 51 |
55 | 52 |
56 async def send_mqtt_message(region): | 53 async def sendDirty(client): |
57 # Convert changed region to base64 encoded string | 54 while True: |
58 buffer = io.BytesIO() | 55 if dirtyQueue: |
59 region.save(buffer, format="PNG") | 56 # pos = random.choice(list(dirtyQueue.keys())) |
60 base64_image = base64.b64encode(buffer.getvalue()).decode("utf-8") | 57 pos = min(list(dirtyQueue.keys())) |
61 mqtt_client = aiomqtt.Client("mqtt_client") | 58 img = dirtyQueue.pop(pos) |
62 await mqtt_client.connect("mqtt://broker.example.com") | 59 await tell_lcd(client, pos[0], pos[1], img) |
63 await mqtt_client.publish("changed_squares", base64_image, qos=1) | 60 await asyncio.sleep(.15) |
64 await mqtt_client.disconnect() | 61 |
62 framesSent = itertools.count() | |
63 | |
64 | |
65 async def tell_lcd(client: aiomqtt.Client, x: int, y: int, | |
66 region: Image.Image): | |
67 seq = next(framesSent) | |
68 msg = struct.pack('HHHHH', seq, x, y, region.width, region.height) + region.tobytes() | |
69 print(f'send {seq=} {x=} {y=} {region.width=} {region.height=} ', end='\r', flush=True) | |
70 await client.publish('display/squib/updates', msg, qos=0) | |
65 | 71 |
66 | 72 |
67 async def main(): | 73 async def main(): |
68 # also listen for dirty msgs from the web page; see ts | 74 # also listen for dirty msgs from the web page; see ts. |
75 # also get notified of new mqtt listeners who need a full image refresh. | |
69 renderer = WebRenderer() | 76 renderer = WebRenderer() |
70 async with aiomqtt.Client("mqtt_client") as client: | 77 async with aiomqtt.Client("mqtt2") as client: |
71 last_image = None | 78 asyncio.create_task(sendDirty(client)) |
79 last_image = Image.new('RGB', (320, 320)) | |
72 while True: | 80 while True: |
73 last_image = await check_for_changes(renderer,last_image) | 81 last_image = await check_for_changes(renderer, client, last_image) |
74 await asyncio.sleep(2) # Adjust the interval as needed | 82 # we could get the web page to tell us when any dom changes |
83 await asyncio.sleep(5) | |
75 | 84 |
76 | 85 |
77 if __name__ == "__main__": | 86 if __name__ == "__main__": |
78 asyncio.run(main()) | 87 asyncio.run(main()) |