From 4f1b559f1c3a3ec72b659959447144d2fb872aae Mon Sep 17 00:00:00 2001 From: Tom Willemse Date: Sat, 3 Jun 2023 00:29:04 -0700 Subject: [PATCH] Initial commit --- .gitignore | 1 + README.org | 4 + examples/date.scm | 22 ++++ inkplate/lowlevel.scm | 286 ++++++++++++++++++++++++++++++++++++++++++ tests/inkplate.scm | 286 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 599 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 examples/date.scm create mode 100644 inkplate/lowlevel.scm create mode 100644 tests/inkplate.scm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfe3d57 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Inkplate.log diff --git a/README.org b/README.org new file mode 100644 index 0000000..cf51cd8 --- /dev/null +++ b/README.org @@ -0,0 +1,4 @@ +#+title: Guile Inkplate +#+subtitle: A port of my Emacs Lisp Inkplate library to Guile + +This is a simple port of my Emacs Lisp Inkplate library to Guile. diff --git a/examples/date.scm b/examples/date.scm new file mode 100644 index 0000000..0f34145 --- /dev/null +++ b/examples/date.scm @@ -0,0 +1,22 @@ +(use-modules ((inkplate lowlevel) #:prefix inkplate:) + (srfi srfi-19)) + +(let ((my-dev (inkplate:open "/dev/ttyUSB0"))) + (inkplate:clear-screen my-dev) + (inkplate:set-cursor my-dev 280 100) + + (inkplate:set-text-size my-dev 10) + (inkplate:print my-dev (inkplate:convert-string-to-hex (date->string (current-date) "~Y"))) + + (inkplate:set-cursor my-dev 275 180) + (inkplate:set-text-size my-dev 8) + (inkplate:print my-dev (inkplate:convert-string-to-hex (date->string (current-date) "~m-~d"))) + + (inkplate:draw-rectangle my-dev 270 90 250 155 3) + (inkplate:draw-rectangle my-dev 269 89 252 157 3) + (inkplate:draw-rectangle my-dev 268 88 254 159 3) + + (inkplate:update my-dev) + (inkplate:send my-dev) + + (inkplate:close my-dev)) diff --git a/inkplate/lowlevel.scm b/inkplate/lowlevel.scm new file mode 100644 index 0000000..c26daa7 --- /dev/null +++ b/inkplate/lowlevel.scm @@ -0,0 +1,286 @@ +(define-module (inkplate lowlevel) + #:use-module ((ice-9 format) #:select (format)) + #:use-module ((srfi srfi-9) #:select (define-record-type)) + + #:export ( + make-inkplate + inkplate? + inkplate-input-port + inkplate-output-port + + convert-string-to-hex + + open + close + echo + draw-pixel + draw-line + draw-fast-vertical-line + draw-fast-horizontal-line + draw-rectangle + draw-circle + draw-triangle + draw-rounded-rectangle + fill-rectangle + fill-circle + fill-triangle + fill-rounded-rectangle + print + set-text-size + set-cursor + set-text-wrap + set-rotation + draw-bitmap + set-display-mode + get-display-mode + clear-screen + update + partial-update + read-temperature + read-touchpad + read-battery + panel-supply + get-panel-state + draw-image + draw-thick-line + draw-ellipse + fill-ellipse + send)) + +(define-record-type + (make-inkplate input-port output-port) + inkplate? + (input-port inkplate-input-port) + (output-port inkplate-output-port)) + +(define (convert-string-to-hex string) + "Convert STRING to a hexadecimal representation of the text." + (string-join + (map (lambda (x) (format #f "~2,'0x" (char->integer x))) + (string->list string)) + "")) + +(define (open device-path) + "Open a connection to an Inkplate on the named DEVICE-PATH." + (let ((port (open-file device-path "r+"))) + (make-inkplate port port))) + +(define (close device) + "Close a connection to an Inkplate in DEVICE." + (close-port (inkplate-output-port device)) + (close-port (inkplate-input-port device))) + +(define (echo device) + "Send a command to check if the DEVICE is receiving commands." + (display "#?*" (inkplate-output-port device))) + +(define (draw-pixel device x y color) + "Draw a pixel on DEVICE at coordinates X and Y in COLOR." + (format (inkplate-output-port device) "#0(~3,'0d,~3,'0d,~2,'0d)*" x y color)) + +(define (draw-line device x1 y1 x2 y2 color) + "Draw a line on DEVICE from coordinates X1,Y1 to X2,Y2 in COLOR." + (format (inkplate-output-port device) + "#1(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x1 y1 x2 y2 color)) + +(define (draw-fast-vertical-line device x y length color) + "Draw a vertical line on DEVICE from X,Y for LENGTH pixels in COLOR." + (format (inkplate-output-port device) + "#2(~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y length color)) + +(define (draw-fast-horizontal-line device x y length color) + "Draw a horizontal line on DEVICE from X,Y for LENGTH pixels in COLOR." + (format (inkplate-output-port device) + "#3(~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y length color)) + +(define (draw-rectangle device x y width height color) + "Draw a rectangle on DEVICE at X,Y WIDTH wide by HEIGHT high in COLOR. +This function draws an outline of a rectangle whereas ‘fill-rectangle’ fills the +rectangle with the specified color." + (format (inkplate-output-port device) + "#4(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y width height color)) + +(define (draw-circle device x y radius color) + "Draw a circle on DEVICE at X,Y with RADIUS in COLOR. +This function draws an outline of a circle whereas ‘fill-circle’ fills the +circle with the specified color." + (format (inkplate-output-port device) + "#5(~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y radius color)) + +(define (draw-triangle device x1 y1 x2 y2 x3 y3 color) + "Draw a triangle on DEVICE at points X1,Y1, X2,Y2, and X3,Y3 in COLOR. +This function draws an outline of a triangle whereas ‘fill-triangle’ fills the +triangle with the specified color." + (format (inkplate-output-port device) + "#6(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" + x1 y1 x2 y2 x3 y3 color)) + +(define (draw-rounded-rectangle device x y width height radius color) + "Draw a rounded rectangle on DEVICE at X,Y WIDTH wide by HEIGHT high. +RADIUS is the radius of the rounded corners. Draw the rectangle in COLOR." + (format (inkplate-output-port device) + "#7(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" + x y width height radius color)) + +(define (fill-rectangle device x y width height color) + "Draw a rectangle on DEVICE at X,Y WIDTH wide by HEIGHT high in COLOR. +This function fills the entire rectangle with COLOR while ‘draw-rectangle’ only +draws an outline." + (format (inkplate-output-port device) + "#8(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y width height color)) + +(define (fill-circle device x y radius color) + "Draw a circle on DEVICE at X,Y with RADIUS. +This function fills the entire circle with COLOR while ‘draw-circle’ only draws +an outline." + (format (inkplate-output-port device) + "#9(~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y radius color)) + +(define (fill-triangle device x1 y1 x2 y2 x3 y3 color) + "Draw a triangle on DEVICE at points X1,Y1, X2,Y2, and X3,Y3 in COLOR. +This function fills the entire triangle with COLOR while ‘draw-triangle’ only +draws an outline." + (format (inkplate-output-port device) + "#A(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" + x1 y1 x2 y2 x3 y3 color)) + +(define (fill-rounded-rectangle device x y width height radius color) + "Draw a rounded rectangle on DEVICE at X,Y WIDTH wide by HEIGHT high. +CORNER-RADIUS is the radius of the rounded corners. Fill the rectangle with +COLOR." + (format (inkplate-output-port device) + "#B(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" + x y width height radius color)) + +(define (print device text) + "Print the TEXT onto the screen of DEVICE. +TEXT should be a hex string as produced by ‘convert-string-to-hex’." + (format (inkplate-output-port device) "#C(~s)*" text)) + +(define (set-text-size device size) + "Set the text size for the next print command to SIZE for DEVICE." + (format (inkplate-output-port device) "#D(~2,'0d)*" size)) + +(define (set-cursor device x y) + "Move the cursor on DEVICE to the X and Y coordinates." + (format (inkplate-output-port device) "#E(~3,'0d,~3,'0d)*" x y)) + +(define (set-text-wrap device enable) + "Enable or disable text wrapping on DEVICE depending on the value of ENABLE. +If ENABLE is #t, enable text wrapping. Otherwise disable it." + (format (inkplate-output-port device) "#F(~a)*" (if enable "T" "F"))) + +(define (set-rotation device rotation) + "Set the screen rotation on DEVICE. +ROTATION can be one of: +- 0: 0 degrees rotation +- 1: 90 degrees rotation +- 2: 180 degrees rotation +- 3: 270 degrees rotation" + (format (inkplate-output-port device) "#G(~3,'0d)*" rotation)) + +(define (draw-bitmap device x y path) + "Draw a bitmap on DEVICE at coordinates X,Y. +PATH should be a path on the SD card and should be a hex string +as produced by ‘inkplate--convert-string-to-hex’. This command +puts a response in the output buffer. It should be one of: + +- #H(0)*: Image load failed +- #H(1)*: Image loaded successfully +- #H(-1)*: SD Card Init Error" + (format (inkplate-output-port device) "#H(~3,'0d,~3,'0d,~s)*" x y path)) + +(define (set-display-mode device mode) + "Set the display mode for DEVICE to MODE. +Mode can be one of: +- 1: 1-bit mode +- 3: 3-bit mode" + (format (inkplate-output-port device) "#I(~d)*" mode)) + +(define (get-display-mode device) + "Query the DEVICE for which display mode is active. + +This puts one of the following responses into the output buffer: +- #J(0): 1-bit mode +- #J(1): 3-bit mode" + (display "#J(?)*" (inkplate-output-port device))) + +(define (clear-screen device) + "Send the command to clear the screen to DEVICE." + (display "#K(1)*" (inkplate-output-port device))) + +(define (update device) + "Send the command to update the display to DEVICE." + (display "#L(1)*" (inkplate-output-port device))) + +(define (partial-update device yy1 xx2 yy2) + "Send the command to update the display to DEVICE. +Tell it to only do a partial update." + ;; I guess that xx1 is implicitly 0? It's not mentioned in the documentation. + (format (inkplate-output-port device) "#M(~3,'0d,~3,'0d,~3,'0d)*" yy1 xx2 yy2)) + +(define (read-temperature device) + "Query DEVICE for its current temperature. +This will produce a response in the output buffer in the form or #N(22)* which +means its temperature is 22 degrees Celsius." + (display "#N(?)*" (inkplate-output-port device))) + +(define (read-touchpad device touchpad) + "Query DEVICE for the state of TOUCHPAD. +TOUCHPAD can be 1, 2, or 3. This will produce a response in the +output buffer which can be read with ‘inkplate-read-output’. +Possible responses are: +- #O(1)*: The touch pad is in the high state +- #O(0)*: The touch pad is in the low state" + (format (inkplate-output-port device) "#O(~d)*" touchpad)) + +(define (read-battery device) + "Query DEVICE for the current voltage on the battery. +This produces a response in the output buffer which can be read using +‘read-output’. The response is in the form of #P(3.65)* meaning the measured +voltage on the battery is 3.65VDC." + (display "#P(?)*" (inkplate-output-port device))) + +(define (panel-supply device enable) + "Turn DEVICE on or off depending on the value of ENABLE. +If ENABLE is #t turn DEVICE on, otherwise turn it off." + (format (inkplate-output-port device) "#Q(~d)*" (if enable 1 0))) + +(define (get-panel-state device) + "Query DEVICE for the state of the panel. +This command produces a response in the output buffer which can be read using +‘read-output’. The response can be one of: + +- #R(1)*: Panel has power +- #R(0)*: Panel has no power" + (display "#R(?)*" (inkplate-output-port device))) + +(define (draw-image device x y path) + "Draw an image on DEVICE at coordinates X,Y. +PATH should be a path on the SD card and a hex string as produced by +‘convert-string-to-hex’." + (format (inkplate-output-port device) "#S(~3,'0d,~3,'0d,~s)*" x y path)) + +(define (draw-thick-line device x1 y1 x2 y2 thickness color) + "Draw a line on DEVICE from X1,Y1 to X2,Y2 in THICKNESS and COLOR." + (format (inkplate-output-port device) + "#T(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d,~2,'0d)*" + x1 y1 x2 y2 thickness color)) + +(define (draw-ellipse device x y x-radius y-radius color) + "Draw an ellipse on DEVICE at coordinates X,Y. +X-RADIUS and Y-RADIUS specify the x and y radius of the ellipse. Draw it in +COLOR." + (format (inkplate-output-port device) + "#U(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y x-radius y-radius color)) + +(define (fill-ellipse device x y x-radius y-radius color) + "Draw and fill an ellipse on DEVICE at coordinates X,Y. +X-RADIUS and Y-RADIUS specify the x and y radius of the ellipse. Draw it in +COLOR." + (format (inkplate-output-port device) + "#V(~3,'0d,~3,'0d,~3,'0d,~3,'0d,~2,'0d)*" x y x-radius y-radius color)) + +(define (send device) + "Send the accumulated commands to DEVICE." + (force-output (inkplate-output-port device))) diff --git a/tests/inkplate.scm b/tests/inkplate.scm new file mode 100644 index 0000000..fb18199 --- /dev/null +++ b/tests/inkplate.scm @@ -0,0 +1,286 @@ +(use-modules (srfi srfi-64) + (inkplate lowlevel)) + +(define* (call-with-test-device func #:key (input-string "")) + (let ((device (make-inkplate (open-input-string input-string) (open-output-string)))) + (func device) + (close device))) + +(test-begin "Inkplate") +(test-equal "Convert hello" "48656c6c6f" (convert-string-to-hex "Hello")) +(test-equal "Convert zero-pads result" "0a" (convert-string-to-hex "\n")) +(test-equal "Convert a path" "2f696d616765312e626d70" (convert-string-to-hex "/image1.bmp")) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw pixel command zero-padds coordinates and color" + "#0(005,005,01)*" + (begin + (draw-pixel test-device 5 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw line command zero-padds coordinates and color" + "#1(005,010,005,010,01)*" + (begin + (draw-line test-device 5 10 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw fast vertical line command zero-padds coordinates and color" + "#2(005,010,005,01)*" + (begin + (draw-fast-vertical-line test-device 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw fast horizontal line command zero-padds coordinates and color" + "#3(005,010,005,01)*" + (begin + (draw-fast-horizontal-line test-device 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw rectangle command zero-padds coordinates and color" + "#4(005,010,005,010,01)*" + (begin + (draw-rectangle test-device 5 10 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw circle command zero-padds coordinates and color" + "#5(005,010,005,01)*" + (begin + (draw-circle test-device 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw triangle command zero-padds coordinates and color" + "#6(005,010,005,010,005,010,01)*" + (begin + (draw-triangle test-device 5 10 5 10 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw rounded rectangle command zero-padds coordinates and color" + "#7(005,010,005,010,005,01)*" + (begin + (draw-rounded-rectangle test-device 5 10 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Fill rectangle command zero-padds coordinates and color" + "#8(005,010,005,010,01)*" + (begin + (fill-rectangle test-device 5 10 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Fill circle command zero-padds coordinates and color" + "#9(005,010,005,01)*" + (begin + (fill-circle test-device 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Fill triangle command zero-padds coordinates and color" + "#A(005,010,005,010,005,010,01)*" + (begin + (fill-triangle test-device 5 10 5 10 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Fill rounded rectangle command zero-padds coordinates and color" + "#B(005,010,005,010,005,01)*" + (begin + (fill-rounded-rectangle test-device 5 10 5 10 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Print command quotes its argument" + "#C(\"48656c6c6f\")*" + (begin + (print test-device "48656c6c6f") + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set text size command zero-padds size" + "#D(05)*" + (begin + (set-text-size test-device 5) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set cursor command zero-padds coordinates" + "#E(005,010)*" + (begin + (set-cursor test-device 5 10) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set test wrap #t becomes T" + "#F(T)*" + (begin + (set-text-wrap test-device #t) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set test wrap #f becomes F" + "#F(F)*" + (begin + (set-text-wrap test-device #f) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set rotation command zero-padds the rotation" + "#G(000)*" + (begin + (set-rotation test-device 0) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw bitmap command zero-padds the coordinates and quotes the path" + "#H(005,010,\"48656c6c6f\")*" + (begin + (draw-bitmap test-device 5 10 "48656c6c6f") + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Set display mode command inserts a number" + "#I(1)*" + (begin + (set-display-mode test-device 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Get display mode command gets created" + "#J(?)*" + (begin + (get-display-mode test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Clear screen command gets created" + "#K(1)*" + (begin + (clear-screen test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Update display command gets created" + "#L(1)*" + (begin + (update test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Partial update command zero-padds coordinates" + "#M(005,010,100)*" + (begin + (partial-update test-device 5 10 100) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Read temperature command gets created" + "#N(?)*" + (begin + (read-temperature test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Read touchpad command gets created" + "#O(1)*" + (begin + (read-touchpad test-device 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Read battery command gets created" + "#P(?)*" + (begin + (read-battery test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Panel supply command #t becomes 1" + "#Q(1)*" + (begin + (panel-supply test-device #t) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Panel supply command #f becomes 0" + "#Q(0)*" + (begin + (panel-supply test-device #f) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Get panel state command gets created" + "#R(?)*" + (begin + (get-panel-state test-device) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw image zero-padds coordinates and quotes path" + "#S(005,010,\"2f696d616765312e626d70\")*" + (begin + (draw-image test-device 5 10 "2f696d616765312e626d70") + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw thick line zero-padds arguments" + "#T(005,010,100,005,10,01)*" + (begin + (draw-thick-line test-device 5 10 100 5 10 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Draw ellipse zero-padds arguments" + "#U(005,010,100,005,01)*" + (begin + (draw-ellipse test-device 5 10 100 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(call-with-test-device + (lambda (test-device) + (test-equal "Fill ellipse zero-padds arguments" + "#V(005,010,100,005,01)*" + (begin + (fill-ellipse test-device 5 10 100 5 1) + (get-output-string (inkplate-output-port test-device)))))) + +(test-end "Inkplate")