Skip to content

Instantly share code, notes, and snippets.

@Largo
Created March 27, 2026 22:17
Show Gist options
  • Select an option

  • Save Largo/3b1ab48c1661065590dd7f36622e81ed to your computer and use it in GitHub Desktop.

Select an option

Save Largo/3b1ab48c1661065590dd7f36622e81ed to your computer and use it in GitHub Desktop.
PicoRuby Quotes Downloader (with waveshare 2.9 inch e-paper display)
# configure wifi first using ncli
require 'spi'
require 'gpio'
require 'net/http'
class Epaper
BUSY = 1
WS_20_30 = [
0x80,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,
0x10,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
0x80,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,
0x10,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x14,0x08,0x00,0x00,0x00,0x00,0x02,
0x0A,0x0A,0x00,0x0A,0x0A,0x00,0x01,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x14,0x08,0x00,0x01,0x00,0x00,0x01,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x44,0x44,0x44,0x44,0x44,0x44,0x00,0x00,0x00,
0x22,0x17,0x41,0x00,0x32,0x36
]
FONT = [
0x18,0x24,0x42,0x7E,0x42,0x42,0x42,
0x7C,0x42,0x7C,0x42,0x42,0x42,0x7C,
0x3C,0x42,0x40,0x40,0x40,0x42,0x3C,
0x78,0x44,0x42,0x42,0x42,0x44,0x78,
0x7E,0x40,0x7E,0x40,0x40,0x40,0x7E,
0x7E,0x40,0x7E,0x40,0x40,0x40,0x40,
0x3C,0x42,0x40,0x4E,0x42,0x42,0x3C,
0x42,0x42,0x7E,0x42,0x42,0x42,0x42,
0x7C,0x10,0x10,0x10,0x10,0x10,0x7C,
0x3E,0x04,0x04,0x04,0x04,0x44,0x38,
0x44,0x48,0x70,0x48,0x44,0x42,0x42,
0x40,0x40,0x40,0x40,0x40,0x40,0x7E,
0x42,0x66,0x5A,0x42,0x42,0x42,0x42,
0x42,0x62,0x52,0x4A,0x46,0x42,0x42,
0x3C,0x42,0x42,0x42,0x42,0x42,0x3C,
0x7C,0x42,0x42,0x7C,0x40,0x40,0x40,
0x3C,0x42,0x42,0x42,0x4A,0x44,0x3A,
0x7C,0x42,0x42,0x7C,0x44,0x42,0x42,
0x3C,0x42,0x40,0x3C,0x02,0x42,0x3C,
0x7E,0x10,0x10,0x10,0x10,0x10,0x10,
0x42,0x42,0x42,0x42,0x42,0x42,0x3C,
0x42,0x42,0x42,0x24,0x24,0x18,0x18,
0x42,0x42,0x42,0x5A,0x5A,0x66,0x42,
0x42,0x24,0x18,0x18,0x24,0x42,0x42,
0x42,0x24,0x18,0x10,0x10,0x10,0x10,
0x7E,0x04,0x08,0x10,0x20,0x40,0x7E,
0x3C,0x42,0x46,0x5A,0x62,0x42,0x3C,
0x10,0x30,0x10,0x10,0x10,0x10,0x7C,
0x3C,0x42,0x02,0x0C,0x10,0x20,0x7E,
0x3C,0x42,0x02,0x1C,0x02,0x42,0x3C,
0x04,0x0C,0x14,0x24,0x7E,0x04,0x04,
0x7E,0x40,0x7C,0x02,0x02,0x42,0x3C,
0x3C,0x42,0x40,0x7C,0x42,0x42,0x3C,
0x7E,0x02,0x04,0x08,0x10,0x20,0x40,
0x3C,0x42,0x42,0x3C,0x42,0x42,0x3C,
0x3C,0x42,0x42,0x3E,0x02,0x42,0x3C,
0x00,0x00,0x00,0x00,0x00,0x00,0x18,
0x00,0x00,0x00,0x00,0x00,0x18,0x10,
0x00,0x18,0x00,0x00,0x00,0x18,0x00,
0x10,0x10,0x10,0x10,0x10,0x00,0x10,
0x3C,0x42,0x02,0x0C,0x10,0x00,0x10,
0x00,0x00,0x00,0x7E,0x00,0x00,0x00,
0x10,0x10,0x20,0x00,0x00,0x00,0x00,
0x02,0x04,0x08,0x10,0x20,0x40,0x40
]
def initialize
@rst = GPIO.new(12, GPIO::OUT)
@busy = GPIO.new(13, GPIO::IN)
@cs = GPIO.new(9, GPIO::OUT)
@dc = GPIO.new(8, GPIO::OUT)
@busy.set_pull(GPIO::PULL_UP)
@cs.write(1)
@dc.write(1)
@rst.write(1)
@spi = SPI.new(unit: :RP2040_SPI1, frequency: 4000_000, mode: 0, cs_pin: -1, sck_pin: 10, copi_pin: 11)
@white = Array.new(128, 0xFF)
@chunk = Array.new(128, 0xFF)
end
def cmd(c) @dc.write(0); @cs.write(0); @spi.write([c]); @cs.write(1) end
def dat(d) @dc.write(1); @cs.write(0); @spi.write(d.is_a?(Array) ? d : [d]); @cs.write(1) end
def busy() while @busy.read == 1 do sleep_ms 10 end end
def init
@rst.write(1); sleep_ms(50); @rst.write(0); sleep_ms(10); @rst.write(1); sleep_ms(50)
busy; cmd(0x12); busy
cmd(0x01); dat([0x27,0x01,0x00])
cmd(0x11); dat([0x03])
cmd(0x44); dat([0x00,0x0F])
cmd(0x45); dat([0x00,0x00,0x27,0x01])
cmd(0x21); dat([0x00,0x80])
cmd(0x4E); dat([0x00]); cmd(0x4F); dat([0x00,0x00]); busy
cmd(0x32)
i = 0
while i < 153 do dat([WS_20_30[i]]); i += 1 end
busy
cmd(0x3F); dat([WS_20_30[153]])
cmd(0x03); dat([WS_20_30[154]])
cmd(0x04); dat([WS_20_30[155],WS_20_30[156],WS_20_30[157]])
cmd(0x2C); dat([WS_20_30[158]])
end
def clear
init
cmd(0x24); i=0; while i<37 do white_chunk; i+=1 end
cmd(0x26); i=0; while i<37 do white_chunk; i+=1 end
cmd(0x22); dat([0xC7]); cmd(0x20); busy
end
def glyph(c, r)
o = c.ord
return 0x00 if o == 32
if o >= 65 && o <= 90 then o = o - 65
elsif o >= 97 && o <= 122 then o = o - 97
else
case c
when '0' then o=26; when '1' then o=27; when '2' then o=28
when '3' then o=29; when '4' then o=30; when '5' then o=31
when '6' then o=32; when '7' then o=33; when '8' then o=34
when '9' then o=35; when '.' then o=36; when ',' then o=37
when ':' then o=38; when '!' then o=39; when '?' then o=40
when '-' then o=41; when '\'' then o=42; when '/' then o=43
else return 0x00
end
end
FONT[o * 7 + r]
end
def white_chunk
@dc.write(1); @cs.write(0); @spi.write(@white); @cs.write(1)
end
# Landscape display 2x scaled: 296 wide x 128 tall
# 18 chars/line, 8 lines (16px each: 14 glyph + 2 space)
# Rotated 90 CCW: hold bottom of display to the left
def display_landscape(lines)
cmd(0x4E); dat([0x00]); cmd(0x4F); dat([0x00,0x00]); busy
cmd(0x24)
dy = 0
while dy < 296
ci = 0
ri = 0
while ri < 8 && dy + ri < 296
lx = 295 - (dy + ri)
char_col = lx / 16
char_px = (lx % 16) / 2
char_px = 7 - char_px
bx = 0
while bx < 16
byte_val = 0xFF
bp = 0
while bp < 8
dx = bx * 8 + bp
text_line = dx / 16
char_py = (dx % 16) / 2
if char_py < 7 && text_line < lines.length
line = lines[text_line]
if char_col < line.length
g = glyph(line[char_col], char_py)
if (g >> char_px) & 1 == 1
byte_val = byte_val & (0xFF ^ (0x80 >> bp))
end
end
end
bp += 1
end
@chunk[ci] = byte_val
ci += 1
bx += 1
end
ri += 1
end
dat(@chunk)
dy += 8
end
cmd(0x22); dat([0xC7]); cmd(0x20); busy
end
end
def wrap(text, w)
lines = []
line = ""
words = text.split(" ")
wi = 0
while wi < words.length
word = words[wi]
if line.length == 0
line = word
elsif line.length + 1 + word.length <= w
line = line + " " + word
else
lines << line
line = word
end
wi += 1
end
lines << line if line.length > 0
lines
end
epd = Epaper.new
epd.init
epd.clear
loop do
quote = "No quote"
author = ""
begin
puts "Fetching quote..."
http = Net::HTTP.new("zenquotes.io", 443)
http.use_ssl = true
response = http.get("/api/random")
body = response.body
response = nil
http = nil
if body != nil && body.length > 0
qi = body.index("\"q\":\"")
if qi != nil
qi += 5
qe = body.index("\"", qi)
quote = body[qi, qe - qi] if qe != nil
end
ai = body.index("\"a\":\"")
if ai != nil
ai += 5
ae = body.index("\"", ai)
author = body[ai, ae - ai] if ae != nil
end
end
body = nil
rescue => e
puts "HTTP error: #{e}"
Machine.reboot
end
puts "Quote: #{quote}"
lines = wrap(quote, 18)
lines << ""
lines << "- #{author}" if author.length > 0
epd.clear
epd.display_landscape(lines)
lines = nil
GC.start
sleep 10
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment