Mercurial > code > home > repos > front-door-display
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())