feat(sterni/blipqn): interact with a flipdot display from BQN
The idea of this “library” is to do the least. The most natural way to represent the image to render on a flipdot image is a two dimensional array of booleans. This something BQN is very well equipped for, i.e. it has primitives that are designed to deal with this type of data structure. The only thing we have to do is to take care of sending such arrays to the flipdot display via the μCCC's un(der)documented UDP protocol. Compact implements the conversion from a boolean array to a bitmap that only uses 1 bit per pixel. All socket code is written in C and invoked via •FFI. Currently, every time a bitmap is sent to a display, the target host has to be resolved again. This should be fixed in the future. Change-Id: Idea7c81baac919da93c88a69f98cbbbd026fa328 Reviewed-on: https://cl.tvl.fyi/c/depot/+/13010 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
parent
b51720f844
commit
1027e21eee
6 changed files with 148 additions and 0 deletions
2
users/sterni/blipqn/.gitignore
vendored
Normal file
2
users/sterni/blipqn/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.so
|
||||
*.o
|
||||
24
users/sterni/blipqn/GNUmakefile
Normal file
24
users/sterni/blipqn/GNUmakefile
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
DESTDIR ?=
|
||||
PREFIX ?= /usr
|
||||
LIBDIR ?= $(DESTDIR)$(PREFIX)/lib
|
||||
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
|
||||
|
||||
CFLAGS ?= -Os
|
||||
CFLAGS += -Wall -Wextra
|
||||
LIBNAME = libflipdot.so
|
||||
|
||||
$(LIBNAME): flipdot.o
|
||||
$(CC) -shared -o $@ $^
|
||||
|
||||
.PHONY: clean fmt install
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f $(LIBNAME)
|
||||
|
||||
fmt:
|
||||
clang-format -style=google -i *.c
|
||||
|
||||
install: $(LIBNAME)
|
||||
install -Dm755 $(LIBNAME) -t $(LIBDIR)
|
||||
install -Dm644 blipqn.bqn -t $(LIBDIR)
|
||||
install -Dm755 examples.bqn $(BINDIR)/blipqn-examples
|
||||
20
users/sterni/blipqn/blipqn.bqn
Normal file
20
users/sterni/blipqn/blipqn.bqn
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
MakeFlipdot⇐
|
||||
|
||||
# - host (char*) argument:
|
||||
# - Assume char is i8 (safe even if it's actually u8 if
|
||||
# ASCII). We can't let •FFI check this for us since CBQN doesn't support
|
||||
# specifying c7. We do this in MakeFlipdot
|
||||
# - Needs to be zero-terminated, we also do this in MakeFlipdot.
|
||||
# - bitmap_len (size_t) argument: assume size_t is at least u32.
|
||||
ffi_send_to_flipdot ← "libflipdot.so" •FFI "i8"‿"send_to_flipdot"‿"*i8:c8"‿"u16"‿"*u8"‿"u32"
|
||||
|
||||
Compact ← +´∘(⌽ × 2⊸⋆∘↕∘≠)˘∘(∘‿8⊸⥊)
|
||||
|
||||
MakeFlipdot ← {
|
||||
host‿port 𝕊 w‿h:
|
||||
"Total pixel count must be divisible by 8" ! 0=8|w×h
|
||||
"Hostname must be ASCII" ! ∧´(@+127)≥host
|
||||
shape ⇐ h‿w
|
||||
empty ⇐ shape⥊0
|
||||
Send ⇐ {"sendto(2) error"!FFI_send_to_flipdot (host∾@)‿port∾(⊢⋈≠) Compact 𝕩}
|
||||
}
|
||||
26
users/sterni/blipqn/default.nix
Normal file
26
users/sterni/blipqn/default.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (pkgs) llvmPackages;
|
||||
drv = llvmPackages.stdenv.mkDerivation {
|
||||
name = "blipqn";
|
||||
|
||||
src = lib.cleanSource ./.;
|
||||
|
||||
makeFlags = [ "PREFIX=$(out)" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
llvmPackages.clang-tools
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
pkgs.cbqn
|
||||
];
|
||||
|
||||
passthru.debug = drv.overrideAttrs (old: {
|
||||
CFLAGS = "-g -Werror";
|
||||
});
|
||||
};
|
||||
in
|
||||
|
||||
drv
|
||||
29
users/sterni/blipqn/examples.bqn
Executable file
29
users/sterni/blipqn/examples.bqn
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env BQN
|
||||
|
||||
⟨MakeFlipdot⟩ ⇐ •Import "../lib/"⊸∾⍟("bin" (⊣≡(-∘≠⊸↑)) •path) "blipqn.bqn"
|
||||
|
||||
examples ← ⍉>⟨
|
||||
"random"‿{𝕩.Send 𝕩.shape •rand.Range 2},
|
||||
"235"‿{𝕩.Send 1˙⌾(4‿22⊸⊑) 𝕩.empty},
|
||||
⟩
|
||||
|
||||
usage ← "Usage: "∾•name∾" HOST PORT WIDTH HEIGHT EXAMPLE"∾"
|
||||
|
||||
where EXAMPLE is one of
|
||||
|
||||
"∾∾{(7↑"")∾"- "∾𝕩∾@+10}¨⊏examples
|
||||
|
||||
# TODO(sterni): this is an atrocious interface
|
||||
opts ← {
|
||||
(•Exit 1˙∘•Out)⍟(5⊸≠≠•args) usage
|
||||
host‿port‿width‿height‿example ← •args
|
||||
|
||||
example ⇐
|
||||
addr ⇐ host⋈•ParseFloat port
|
||||
dim ⇐ •ParseFloat¨ width‿height
|
||||
}
|
||||
|
||||
action ← examples (⊏⊸(⊑∘⊐) ⊑ ⊏˜⟜1)⟜< opts.example
|
||||
|
||||
flip ← opts.addr MakeFlipdot opts.dim
|
||||
Action flip
|
||||
47
users/sterni/blipqn/flipdot.c
Normal file
47
users/sterni/blipqn/flipdot.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#define _DEFAULT_SOURCE // see getnameinfo(3), for NI_MAX*
|
||||
#define _POSIX_C_SOURCE 200112L // see getaddrinfo(3)
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int resolve_addr(char *host, char *port, struct addrinfo **addrs) {
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags |= AI_NUMERICSERV;
|
||||
|
||||
return getaddrinfo(host, port, &hints, addrs);
|
||||
}
|
||||
|
||||
// Send given bytes[len] to the host:port via UDP, returns 1 if all bytes
|
||||
// where sent and no locally detectable errors occurred.
|
||||
int8_t send_to_flipdot(char *host, in_port_t port_number, uint8_t *bitmap,
|
||||
size_t bitmap_len) {
|
||||
char port[NI_MAXSERV];
|
||||
int sockfd = -1;
|
||||
ssize_t sent = 0;
|
||||
struct addrinfo *addrs = NULL;
|
||||
|
||||
if (snprintf(port, sizeof port, "%d", port_number) < 0) goto error;
|
||||
|
||||
if (resolve_addr(host, port, &addrs) != 0) goto error;
|
||||
|
||||
sockfd = socket(addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sockfd < 0) goto error;
|
||||
|
||||
sent =
|
||||
sendto(sockfd, bitmap, bitmap_len, 0, addrs->ai_addr, addrs->ai_addrlen);
|
||||
|
||||
if (sent != (ssize_t)bitmap_len) goto error;
|
||||
|
||||
error:
|
||||
if (addrs != NULL) freeaddrinfo(addrs);
|
||||
if (sockfd >= 0) close(sockfd);
|
||||
return (sent == (ssize_t)bitmap_len);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue