Created
March 27, 2026 22:17
-
-
Save Largo/3b1ab48c1661065590dd7f36622e81ed to your computer and use it in GitHub Desktop.
PicoRuby Quotes Downloader (with waveshare 2.9 inch e-paper display)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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