Install
$ agentstack add skill-narrowstacks-claude-homeassistant-plugins-esphome-display ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
ESPHome Display Code Generator
Generate ESPHome display lambda code for graphical displays including OLED, E-Paper, and TFT displays.
Core Concepts
Display Lambda Structure
All display code goes in a lambda block that executes on each display update:
display:
- platform: ssd1306_i2c # or other display type
id: my_display
lambda: |-
// Your rendering code here using 'it' object
it.print(0, 0, id(my_font), "Hello");
The it variable represents the display buffer and provides all drawing methods.
Coordinate System
- Origin (0,0) is at top-left corner
- X increases rightward, Y increases downward
- Use
it.get_width()andit.get_height()for display dimensions - Rotation can be set via
rotation: 90(0, 90, 180, or 270 degrees)
Drawing Functions
Basic Shapes
lambda: |-
// Lines
it.line(x1, y1, x2, y2);
it.line(x1, y1, x2, y2, COLOR_ON); // with color
it.horizontal_line(x, y, width);
it.vertical_line(x, y, height);
// Rectangles
it.rectangle(x, y, width, height);
it.filled_rectangle(x, y, width, height);
// Circles
it.circle(center_x, center_y, radius);
it.filled_circle(center_x, center_y, radius);
// Rings and gauges
it.filled_ring(center_x, center_y, outer_radius, inner_radius);
it.filled_gauge(center_x, center_y, outer_radius, inner_radius, percent);
// Triangles
it.triangle(x1, y1, x2, y2, x3, y3);
it.filled_triangle(x1, y1, x2, y2, x3, y3);
// Polygons
it.regular_polygon(x, y, radius, 6); // hexagon
it.filled_regular_polygon(x, y, radius, 8); // octagon
// Single pixel
it.draw_pixel_at(x, y);
it.fill(COLOR_ON); // fill entire display
Colors
Monochrome displays: Use COLOR_ON (default, pixel on) and COLOR_OFF (pixel off).
Color displays: Create Color objects:
lambda: |-
auto red = Color(255, 0, 0);
auto green = Color(0, 255, 0);
auto custom = Color(123, 45, 67);
it.filled_circle(50, 50, 20, red);
Or define colors globally:
color:
- id: my_red
red: 100%
green: 20%
blue: 0%
- id: my_blue
hex: 0000FF
lambda: |-
it.print(0, 0, id(font), id(my_red), "Red text");
Text Rendering
Static Text
font:
- file: "fonts/arial.ttf"
id: my_font
size: 12
- file: "fonts/arial.ttf"
id: large_font
size: 24
lambda: |-
// Basic print
it.print(0, 0, id(my_font), "Hello World");
// With color
it.print(0, 10, id(my_font), COLOR_ON, "Text");
// With alignment
it.print(64, 0, id(my_font), TextAlign::TOP_CENTER, "Centered");
it.print(128, 0, id(my_font), TextAlign::TOP_RIGHT, "Right");
// Multiple alignment options:
// TOP_LEFT, TOP_CENTER, TOP_RIGHT
// CENTER_LEFT, CENTER, CENTER_RIGHT
// BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, BASELINE_LEFT, BASELINE_CENTER, BASELINE_RIGHT
Formatted Text (printf)
Display sensor values and dynamic content:
sensor:
- platform: dht
temperature:
id: temp_sensor
humidity:
id: humidity_sensor
lambda: |-
// Basic sensor display
it.printf(0, 0, id(my_font), "Temp: %.1f°C", id(temp_sensor).state);
// Multiple values
it.printf(0, 10, id(my_font), "T: %.1f°C H: %.0f%%",
id(temp_sensor).state, id(humidity_sensor).state);
// With color and alignment
it.printf(64, 20, id(my_font), COLOR_ON, TextAlign::CENTER,
"Value: %.2f", id(temp_sensor).state);
Common printf specifiers:
%.1f- Float with 1 decimal place%.0f- Float with no decimals (rounds)%d- Integer%s- String (use.c_str()for text sensors)%%- Literal % sign
Text Sensors
text_sensor:
- platform: template
id: status_text
lambda: |-
it.printf(0, 0, id(my_font), "Status: %s", id(status_text).state.c_str());
Binary Sensors
binary_sensor:
- platform: gpio
id: door_sensor
lambda: |-
// Method 1: Conditional
if (id(door_sensor).state) {
it.print(0, 0, id(my_font), "Door: OPEN");
} else {
it.print(0, 0, id(my_font), "Door: CLOSED");
}
// Method 2: Inline ternary
it.printf(0, 0, id(my_font), "Door: %s",
id(door_sensor).state ? "OPEN" : "CLOSED");
Display Pages
Switch between multiple screen layouts:
display:
- platform: ssd1306_i2c
id: my_display
pages:
- id: page1
lambda: |-
it.print(0, 0, id(my_font), "Page 1: Temperature");
it.printf(0, 20, id(my_font), "%.1f°C", id(temp_sensor).state);
- id: page2
lambda: |-
it.print(0, 0, id(my_font), "Page 2: Humidity");
it.printf(0, 20, id(my_font), "%.0f%%", id(humidity_sensor).state);
- id: page3
lambda: |-
it.print(0, 0, id(my_font), "Page 3: Status");
it.printf(0, 20, id(my_font), "%s", id(status_text).state.c_str());
# Auto-cycle through pages
interval:
- interval: 5s
then:
- display.page.show_next: my_display
- component.update: my_display
# Manual page control
button:
- platform: gpio
id: next_button
on_press:
- display.page.show_next: my_display
- component.update: my_display
Common Patterns
Sensor Dashboard
lambda: |-
// Title
it.print(0, 0, id(large_font), "Home Status");
// Temperature with icon
it.print(0, 20, id(my_font), "Temperature:");
it.printf(100, 20, id(my_font), "%.1f°C", id(temp_sensor).state);
// Humidity
it.print(0, 35, id(my_font), "Humidity:");
it.printf(100, 35, id(my_font), "%.0f%%", id(humidity_sensor).state);
// Status indicator
if (id(temp_sensor).state > 25) {
it.filled_circle(120, 50, 5, COLOR_ON); // Warning dot
}
Progress Bar
lambda: |-
float progress = id(sensor).state; // 0-100
int bar_width = 100;
int bar_height = 10;
int x = 10, y = 30;
// Outline
it.rectangle(x, y, bar_width, bar_height);
// Filled portion
int filled = (bar_width - 2) * progress / 100;
it.filled_rectangle(x + 1, y + 1, filled, bar_height - 2);
// Percentage text
it.printf(x + bar_width/2, y - 5, id(my_font),
TextAlign::BOTTOM_CENTER, "%.0f%%", progress);
Gauge Display
lambda: |-
int center_x = 64, center_y = 32;
int outer_radius = 25, inner_radius = 20;
float value = id(sensor).state; // 0-100
// Background ring
it.filled_ring(center_x, center_y, outer_radius, inner_radius, COLOR_OFF);
// Filled gauge (value percentage)
it.filled_gauge(center_x, center_y, outer_radius, inner_radius, value);
// Center text
it.printf(center_x, center_y, id(my_font), TextAlign::CENTER, "%.0f", value);
Multi-line Text
lambda: |-
int line_height = 12;
int y = 0;
it.print(0, y, id(my_font), "Line 1");
y += line_height;
it.print(0, y, id(my_font), "Line 2");
y += line_height;
it.print(0, y, id(my_font), "Line 3");
Time Display
time:
- platform: sntp
id: sntp_time
lambda: |-
it.strftime(64, 0, id(large_font), TextAlign::TOP_CENTER,
"%H:%M", id(sntp_time).now());
it.strftime(64, 30, id(my_font), TextAlign::TOP_CENTER,
"%A, %B %d", id(sntp_time).now());
Common strftime formats:
%H:%M- 24-hour time (14:30)%I:%M %p- 12-hour time (2:30 PM)%A- Full weekday (Monday)%a- Short weekday (Mon)%B %d- Month day (January 15)%Y-%m-%d- ISO date (2025-01-15)
Screen Clipping
Restrict drawing to specific regions:
lambda: |-
// Draw normally across full screen
it.print(0, 0, id(my_font), "Full screen text");
// Start clipping to region
it.start_clipping(20, 10, 100, 50); // left, top, right, bottom
// This text will be clipped to the region
it.print(0, 20, id(my_font), "Clipped text...");
// End clipping
it.end_clipping();
// Back to full screen
it.print(0, 55, id(my_font), "More text");
Advanced clipping:
lambda: |-
it.start_clipping(10, 10, 110, 50);
// Expand clipping region
it.extend_clipping(5, 5, 115, 55);
// Shrink clipping region
it.shrink_clipping(2, 2, 2, 2);
// Check if clipping is active
if (it.is_clipping()) {
auto rect = it.get_clipping();
// rect.x, rect.y, rect.w, rect.h
}
it.end_clipping();
Images
image:
- file: "logo.png"
id: my_logo
resize: 32x32
lambda: |-
// Draw at position
it.image(0, 0, id(my_logo));
// Draw with alignment
it.image(64, 32, id(my_logo), ImageAlign::CENTER);
// For monochrome, specify pixel colors
it.image(0, 0, id(my_logo), COLOR_ON, COLOR_OFF);
Configuration Tips
Display Setup
display:
- platform: ssd1306_i2c
id: my_display
model: "SSD1306 128x64"
address: 0x3C
rotation: 0 # 0, 90, 180, 270
update_interval: 1s
auto_clear_enabled: true # Clear before each lambda
lambda: |-
// Rendering code
Font Optimization
Only include glyphs you need to save memory:
font:
- file: "arial.ttf"
id: my_font
size: 14
glyphs: "0123456789:.°C% " # Only these characters
Anti-aliased Fonts
For color displays with anti-aliasing:
lambda: |-
// Specify foreground and background colors
it.print(0, 0, id(aa_font), COLOR_ON, TextAlign::TOP_LEFT,
"Anti-aliased", COLOR_OFF);
Troubleshooting
Display Test Card
Verify display is working correctly:
display:
- platform: ssd1306_i2c
show_test_card: true # Shows color bars and position markers
Common Issues
Nothing displays:
- Check
update_intervalis not set tonever - Verify display address and wiring
- Ensure fonts are properly defined
- Check sensor IDs are correct
Text cut off:
- Use
it.get_width()andit.get_height()to stay within bounds - Check text alignment settings
- Verify font size fits the display
Slow updates:
- Reduce
update_intervalor usecomponent.updateaction - Simplify lambda code
- Use pages instead of complex conditionals
Colors wrong (color displays):
- Use
show_test_card: trueto verify color output - Check if display is in correct color mode
- Verify Color() RGB values are in 0-255 range
Advanced Examples
See [examples.md](references/examples.md) for:
- Complex multi-page layouts
- Animated graphics
- Graph rendering
- QR code display
- Custom widgets
- Performance optimization
Code Generation Guidelines
When generating display code:
- Always include required components (fonts, sensors, etc.)
- Use appropriate data types (
.statefor sensors,.c_str()for text sensors) - Consider display size (check dimensions fit the content)
- Add helpful comments for complex layouts
- Use constants for repeated values (positions, colors)
- Test with realistic data (consider min/max sensor values)
- Handle edge cases (missing sensors, null states, long text)
Template Structure
# Required components
font:
- file: "arial.ttf"
id: my_font
size: 12
sensor:
- platform: ...
id: my_sensor
# Display configuration
display:
- platform: ssd1306_i2c
id: my_display
lambda: |-
// Clear handled automatically by default
// Title/header
it.print(0, 0, id(my_font), "Title");
// Main content
it.printf(0, 15, id(my_font), "Value: %.1f", id(my_sensor).state);
// Footer/status
it.print(0, 55, id(my_font), "Status: OK");
Quick Reference
Drawing: line, rectangle, circle, triangle, polygon, pixel Text: print, printf, strftime Display info: getwidth(), getheight() Colors: COLORON, COLOROFF, Color(r, g, b) Clipping: startclipping, endclipping, extendclipping, shrinkclipping Pages: pages list, display.page.shownext, display.page.show Alignment: TOPLEFT, CENTER, BOTTOM_RIGHT, etc. Format: %.1f (decimal), %d (int), %s (string), %% (percent)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: narrowstacks
- Source: narrowstacks/claude-homeassistant-plugins
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.