Skip to content

Expose panel resolution, physical size and colour scheme - #86

Open
parkghost wants to merge 1 commit into
OpenDisplay:mainfrom
parkghost:feat/expose-display-geometry
Open

Expose panel resolution, physical size and colour scheme#86
parkghost wants to merge 1 commit into
OpenDisplay:mainfrom
parkghost:feat/expose-display-geometry

Conversation

@parkghost

@parkghost parkghost commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #85

Adds sensor.<device>_resolution — state WIDTHxHEIGHT, with the pixel and physical dimensions and the colour scheme as attributes. Diagnostic and disabled by default, like the other device-fact sensors.

image

@parkghost
parkghost requested a review from g4bri3lDev as a code owner July 28, 2026 18:25
@davelee98

Copy link
Copy Markdown
Contributor

Can you resolve conflicts.

@parkghost
parkghost force-pushed the feat/expose-display-geometry branch from f3ef75a to 1ac6854 Compare August 14, 2026 12:55
@parkghost

Copy link
Copy Markdown
Contributor Author

Rebased onto the current feat/clean-port; conflicts resolved.

The only real conflict was the import block in sensor.py — the SHT40 work landed Sht40Reading/SensorData/SensorType there while this branch adds DisplayConfig/Rotation. They are a plain union, so both sets are kept. Everything else auto-merged.

Verified: full test suite passes locally (134 tests), and validate + HACS Action are green on the rebased head.

@g4bri3lDev
g4bri3lDev changed the base branch from feat/clean-port to main August 21, 2026 20:41
@davelee98

Copy link
Copy Markdown
Contributor

Recommend merge after clearing conflicts.

@parkghost
parkghost force-pushed the feat/expose-display-geometry branch from 1ac6854 to 0aa630b Compare August 23, 2026 03:19
@schlomo

schlomo commented Sep 3, 2026

Copy link
Copy Markdown

Love this! May I suggest calling the entity _info or _hardware or _details or something more descriptive? And then providing the resolution in an attribute and a brief description in the entity state? Or do you need the WxH form specifically as a state?

@parkghost

Copy link
Copy Markdown
Contributor Author

Thanks for taking a look!

For my use case either shape works — I just need some template-readable way to reach a panel's specs, so state vs attributes isn't really the deciding factor for me.

What makes me lean toward keeping a narrow _resolution rather than a generic _info/_details/_device_config is scope. The display config packet alone has ~20 fields, and there are a dozen more config packets behind it. A generic name invites
filling all of that in, and would expose fields nobody has asked for yet — each one then being something we have to keep stable. A narrow name keeps the surface to what's actually needed, and anything further can get its own entity later.

@schlomo

schlomo commented Sep 6, 2026

Copy link
Copy Markdown

@parkghost I guess what stuck me was the attributes, and not the resolution. The attributes carry a lot of extra stuff that currently isn't available at all. And that I think would be useful to expose some way.

Just out of curiosity: Can you illustrate the use case you will enable with this feature?

The panel reports its pixel dimensions, physical size and colour scheme in the
display config packet, but none of it reached Home Assistant: __init__.py reads
the pixel dimensions to build the device model and then discards them whenever
the diagonal is known, and the diagonal it shows is itself derived from the
millimetres. Consumers were left string-parsing a human-facing display name, and
could not get the resolution at all.

That blocks a real use case: an automation that renders an image at runtime and
pushes it with opendisplay.upload_image needs the panel's resolution and colour
scheme to size and quantise the render. A template can only read entity state,
so the values have to live on an entity.

Adds sensor.<device>_resolution, state WIDTHxHEIGHT, with the pixel and physical
dimensions and the colour scheme as attributes. Diagnostic and disabled by
default, like the other device-fact sensors.

- One sensor per device, reporting displays[0]. The config packet is repeatable
  (max 4), but upload_image and drawcustom take no display argument and both use
  displays[0] - unlike activate_led/activate_buzzer/play_melody, which do take
  an instance. A sensor for display 1..3 would describe a panel nothing can draw
  on, and a consumer sizing a render from it would silently produce an image for
  the wrong panel. If display addressing is added to those services, this grows
  to match, keyed the same way.
- The state is the panel's NATIVE resolution, never transposed. What a consumer
  should compose is a question about how the panel is mounted and what the
  caller wants on it, not a property of the packet.
- Rotation is deliberately left out, although the packet carries it. It is a
  stored firmware offset, not an orientation: a frame configured at one base is
  still something a person can stand on its side, so the value answers neither
  "is this panel portrait or landscape" nor "what should I compose". It is only
  one term in the device's (base + rotate) % 360, and a deployment that needs
  that arithmetic settles it once per panel by experiment. Exposing it would
  widen the entity for a fact no consumer can act on by itself.
- The config is re-read on every access. delivery.py replaces
  runtime_data.device_config wholesale on a wake-time resync without reloading
  the entry, so a snapshot taken at construction would serve stale geometry.
- Stays available while the panel sleeps, since the value comes from config
  rather than from advertisements.

value_fn becomes optional on the description: last_seen already passed a dead
no-op to satisfy it, and this sensor would have been a second one.
@parkghost
parkghost force-pushed the feat/expose-display-geometry branch from 0aa630b to 8884212 Compare September 7, 2026 04:49
@parkghost

parkghost commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@schlomo I run about seven OpenDisplay screens across four sizes (7.5", 7.3", 4.2", 2.9") in three colour schemes (BWGBRY, BWR, MONO). What goes on them comes from all over: HA integrations (weather, calendar, OpenAI image generator), internal MCP servers (timers, scheduled tasks), an external MCP server (Notion), and live voice-assistant turns. The images are composed at runtime by an AI agent — Claude Code or the voice assistant (based on Pi) — which picks the layout from both the content AND the target panel's characteristics, then hands the PNG to opendisplay.upload_image. The ones that prove useful get frozen into HA automations, triggered by time, by a button, by a voice command, or by a Claude Code schedule.

So on every single render the composer needs the target panel's geometry, and it has to come from HA — an MCP tool call or a Jinja template — because that is all these callers can reach. That is what this PR is for.

That is also why the attributes are the part I actually use. My renderer takes five arguments for a target, and three of them are read straight off the panel: the canvas (pixel_width/pixel_height), the quantisation target (color_scheme), and the physical type floor (active_*_mm → true dpi).

You mentioned some of it would be useful to expose "some way" — which parts? Happy to fold them in here if they fit, or leave them for a follow-up.

@g4bri3lDev — a maintainer call would help here, and I would rather build the shape you want than leave this sitting.

@schlomo

schlomo commented Sep 7, 2026

Copy link
Copy Markdown

@parkghost nice use case, thanks a lot for sharing!

I think that we simply should make all information that OD has on a display also available via states and attributes. For example, for #127 I need to be able to read the last content sent to a display via drawcustom to enable a "load from display" feature.

That is why I was wondering about the extra attributes attached to the resolution vs. having a state that simply carries that and more information.

@parkghost

Copy link
Copy Markdown
Contributor Author

for #127 I need to be able to read the last content sent to a display via drawcustom to enable a "load from display" feature.

Would a helpers.storage.Store be a better fit for that than attributes? On an entity the payload writes a recorder row per draw and still needs RestoreEntity to survive a restart, which expires after 7 days — whereas a Store just holds it, and energy and lovelace both pair one with a websocket command for the panel to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose panel geometry (resolution, physical size, color_scheme)

3 participants