Skip to content

feat: add LCD1602 16x2 I2C character display support#239

Open
dbzx6r wants to merge 13 commits intoPierreGode:mainfrom
dbzx6r:pr/lcd1602-upstream
Open

feat: add LCD1602 16x2 I2C character display support#239
dbzx6r wants to merge 13 commits intoPierreGode:mainfrom
dbzx6r:pr/lcd1602-upstream

Conversation

@dbzx6r
Copy link
Contributor

@dbzx6r dbzx6r commented Mar 14, 2026

Summary

Adds full support for the HD44780 LCD1602 16×2 character LCD with I2C PCF8574 backpack as a selectable display type in Ragnar.


New driver — \

esources/waveshare_epd/lcd1602.py\

  • I2C driver using \smbus2\ (bus 1, default address 0x27)
  • EPD-compatible interface: \init(), \Clear(), \write_line(row, text), \sleep()\
  • Auto-detects I2C address (probes 0x27 then 0x3F)
  • Idempotent \init()\ — skips re-init if already running
  • ASCII sanitisation in \write_line()\ (HD44780 ROM is ASCII-only)
  • Wiring: VCC→Pin2 (5V), GND→Pin6, SDA→GPIO2 (Pin3), SCL→GPIO3 (Pin5)

Display loop — \display.py → _run_lcd1602()\

Routed from
un()\ when \�pd_type == 'lcd1602'\

Two independent rotation timers:

  • Top row (15 s each): WiFi SSID → IP address
  • Bottom row (5 s each): \Targets: #\ → \Vuln: #\ → \Credentials: #\

Handles I2C errors gracefully — forces full re-init on next tick, never crashes the service.

Install script — \install_ragnar.sh\

  • LCD1602 added as option 3 in TFT/OLED menu and option 11 in full display menu
  • Installs \smbus2\ via pip when selected
  • Enables I2C interface via
    aspi-config\
  • Verifies driver file post-install

Shared config — \shared.py\

  • \lcd1602\ added to \DISPLAY_PROFILES\ (16×2) and \SIZE_KEY_TO_DEFAULT_DRIVER\
  • \lcd1602_i2c_address\ (default \

dbzx6r and others added 9 commits March 13, 2026 19:14
- Add resources/waveshare_epd/lcd1602.py: full HD44780/PCF8574 I2C driver
  using smbus2 (already a dependency). Auto-detects address 0x27/0x3F.
- Update shared.py: register lcd1602 in DISPLAY_PROFILES,
  SIZE_KEY_TO_DEFAULT_DRIVER, and get_default_config()
- Update display.py: add _run_lcd1602() render loop showing live
  status and H/C/V counters; dispatch from run()
- Update web/index_modern.html: add LCD1602 option to display dropdown
- Update web/scripts/ragnar_modern.js: add lcd1602 to epdTypeToSizeKey(),
  displaySelectOptions, Display config section, alwaysShowKeys,
  fallbackValues, lcd1602_i2c_address input row, and syncDisplayRows()

Wiring (I2C PCF8574 backpack):
  VCC -> Pi Pin 2 (5V), GND -> Pin 6,
  SDA -> Pin 3 (GPIO2), SCL -> Pin 5 (GPIO3)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Complete redesign of _run_lcd1602() for the 16x2 character display:

Page 0 (4s) - Status + Network:
  Line 0: orchestrator status (e.g. 'SCANNING        ')
  Line 1: WiFi SSID via iwgetid (e.g. '>Tango Down     ')

Page 1 (4s) - Counts + IP:
  Line 0: T:<targets> V:<vulns> C:<creds>
  Line 1: IP address (e.g. '192.168.1.100   ')

Improvements:
- Live WiFi SSID using iwgetid (consistent with ssd1306)
- Page rotation every 4s; 0.5s poll tick
- Reset _initialized=False on I2C error for auto-recovery
- Force both lines rewrite on page switch (no stale content)

lcd1602.py QA fixes:
- write_line() sanitizes non-ASCII chars (SSID/unicode safety)
- sleep() now resets _initialized so next init() re-runs HD44780 sequence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
initialize_epd_display() was trying to call getbuffer() on the LCD1602
driver, which has no such method (character displays have no PIL buffer
interface). This caused the fallback auto-detect to run, find the
connected e-paper, and overwrite epd_type back to 'epd2in13_V4' --
so _run_lcd1602() was never dispatched.

Add an early-return path for _CHAR_DISPLAYS (currently {'lcd1602'})
that sets width/height from DISPLAY_PROFILES and skips all
EPDHelper / PIL buffer initialisation entirely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The custom logger in this codebase only accepts 2 positional args;
positional %-format strings raise TypeError. Switch to f-string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- display.py: guard init_partial_update() with 'if epd_helper is not None'
  so character displays (lcd1602) don't crash the display thread
- shared.py: call apply_display_profile() in the char-display early-return
  path so config ref_width/ref_height stay in sync (used for scale_factor)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- display.py: convert all logger.error('%s', exc) calls in _run_lcd1602
  to f-strings so custom Logger doesn't crash the display thread
- lcd1602.py: increase EN pulse timing from 0.1ms to 0.5ms to match
  known-working timing from direct I2C test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Top row rotates every 15s: SSID -> IP address
Bottom row rotates every 5s: Targets:# -> Vulnerabilities:# -> Credentials:#

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 'Vulnerabilities:#' -> 'Vuln: #' (fits 16 chars cleanly)
- IP now fetched via 'hostname -I' subprocess instead of
  non-existent shared_data.ipaddress attribute

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Driver verification with smbus2 install + I2C enable
- Added as option 3 in TFT/OLED menu (short form)
- Added as option 11 in full display menu

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dbzx6r
Copy link
Contributor Author

dbzx6r commented Mar 14, 2026

image

Add a third slot to the LCD1602 top-row rotation cycle:
  Slot 0 (15s) - WiFi SSID
  Slot 1 (15s) - IP address
  Slot 2 (15s) - Ragnar current status (ragnarstatustext)

Status examples: IDLE, NetworkScanner, NmapVulnScanner,
LynisPentest, etc. Falls back to 'IDLE' if unset.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dbzx6r
Copy link
Contributor Author

dbzx6r commented Mar 14, 2026

Update: Ragnar status added to top-row rotation

Just pushed an additional commit to this branch adding the current Ragnar operation status to the LCD1602 top-row display rotation.

New top-row cycle (15 s each):

Slot Content Example
0 WiFi SSID \Tango Down 5G \
1 IP address \192.168.1.100 \
2 Ragnar status \NetworkScanner \

The status slot reads \shared_data.ragnarstatustext\ which is set by the orchestrator and action modules (e.g. \IDLE, \NetworkScanner, \NmapVulnScanner, \LynisPentest). Falls back to \IDLE\ if unset.

This gives users at-a-glance visibility into what Ragnar is actively doing without needing to open the web UI.

Generate a screen.png simulating the physical LCD1602 whenever
the displayed content changes. The web GUI e-paper tab now shows
a green-on-dark monospace image matching the current two lines
on the hardware display instead of a blank/stale e-ink image.

_render_lcd_preview() uses ImageFont (monospace, falls back to
PIL default), sizes the canvas dynamically from glyph metrics,
and writes to shared_data.webdir/screen.png — the same path
the /api/epaper-display route already serves.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dbzx6r
Copy link
Contributor Author

dbzx6r commented Mar 14, 2026

Update: Web GUI display tab now shows live LCD preview

Pushed another commit — the e-paper display tab in the web UI now shows a real-time visual preview of what's on the LCD1602 hardware instead of a blank/stale image.

How it works:

  • _render_lcd_preview(row0, row1) is called inside _run_lcd1602() whenever the displayed content changes
  • Generates a PIL image styled as a classic backlit LCD (green monospace text on dark background)
  • Canvas is sized dynamically from actual glyph metrics so it always fits the font correctly
  • Saved to \webdir/screen.png\ — the exact path the existing /api/epaper-display\ route already serves
  • Zero changes to the route, HTML, or JS — fully backwards-compatible

The preview updates in sync with the physical display rotation (every 5–15s depending on which slot changes).

Split the main loop into two independent try blocks:
1. Content/preview block — computes lines, renders screen.png;
   never blocked by I2C errors
2. Hardware block — writes to the physical LCD; I2C failures
   only reset _initialized, not the preview state

Previously an I2C exception aborted the entire tick before
_render_lcd_preview() was ever called, leaving screen.png stale.

Also adds _hw_line0/_hw_line1 tracking so hardware writes only
happen on actual changes even after a reinit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PierreGode
Copy link
Owner

@dbzx6r please resolve comficts 😊🙌 amazing work man.

Merged upstream/main (MAX7219, display-rename, ap-archive, fix-view-full-report,
pager-optimize) into pr/lcd1602-upstream while preserving all LCD1602 additions.

Conflict resolution strategy — kept BOTH display type additions:

display.py
  - Kept ImageFont import (required by _render_lcd_preview)
  - Added upstream comment about non-EPD displays in __init__
  - Kept _run_lcd1602() + _run_max7219() routing side by side

shared.py
  - Kept lcd1602 in DISPLAY_PROFILES and SIZE_KEY_TO_DEFAULT_DRIVER
  - Kept lcd1602_i2c_address/lcd1602_i2c_bus config defaults
  - Unified _CHAR_DISPLAYS skip block into _BYPASS_DISPLAYS covering
    lcd1602, max7219_4panel, and max7219_8panel
  - Kept max7219_* DISPLAY_PROFILES entries and config defaults

install_ragnar.sh
  - Kept LCD1602 as option 11 in full display menu
  - Added MAX7219 8-panel (12) and 4-panel (13) from upstream
  - Renumbered headless to 14, updated range prompt to 1-14

web/scripts/ragnar_modern.js
  - Kept lcd1602_i2c_address config metadata and form input
  - Kept lcd1602 in epdTypeToSizeKey() and displaySelectOptions
  - Added max7219_* config metadata, form inputs, and syncDisplayRows
    visibility toggling from upstream
  - Merged Display section keys and alwaysShowKeys to include both

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants