aarch64-linux
hal
and display
protobuf
service Hal {
rpc WatchEvents(google.protobuf.Empty) returns (stream Event) {}
// ...
}
message Event {
EventSource source = 1;
bool state = 2;
}
enum EventSource {
Pir = 0 [(name) = "pir"];
RedButton = 1 [(name) = "red_button"];
YellowButton = 2 [(name) = "yellow_button"];
}
func (h *Hal) Run() {
addr, err := net.ResolveUnixAddr("unix", h.SocketPath)
lis, err := net.ListenUnix("unix", addr)
h.server = grpc.NewServer()
pb.RegisterHalServer(h.server, h)
h.server.Serve(lis)
}
func (h *Hal) WatchEvents(_ *emptypb.Empty, stream pb.Hal_WatchEventsServer) error {
h.addWatcher(stream)
<-stream.Context().Done()
h.removeWatcher(stream)
return nil
}
gpiod.RequestLine(gpioDevice, pirPin,
gpiod.WithBothEdges,
gpiod.WithEventHandler(func(evt gpiod.LineEvent) {
event := pb.Event{
Source: pb.EventSource_Pir,
State: evt.Type == gpiod.LineEventRisingEdge,
}
for _, w := range h.watchers {
w.Send(&event)
}
}))
conn, err := grpc.Dial(
d.config.HalSocketPath,
grpc.WithInsecure(),
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout("unix", addr, timeout)
}))
defer conn.Close()
d.hal = pb.NewHalClient(conn)
// Watch events from HAL and process them.
stream, err := d.hal.WatchEvents(ctx, &emptypb.Empty{})
for {
event, err := stream.Recv()
// Handle event.
}
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hal_proto.proto
package hal_proto
go generate -x -v hal_proto/generate.go
r.GET("/ws", func(c *gin.Context) {
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
defer conn.Close()
d.addClient(conn)
for {
messageType, message, err := conn.ReadMessage()
if websocket.IsCloseError(err, websocket.CloseGoingAway, ...) {
break
}
// Handle message.
}
d.removeClient(conn)
})
r.StaticFS("/video", http.Dir(d.config.VideosPath))
r.GET("/video/:name", func(c *gin.Context) {
name := c.Param("name")
filepath := path.Join(d.config.VideosPath, name)
http.ServeFile(c.Writer, c.Request, filepath)
})
esbuild
before go build
.package frontend
import (
"embed"
)
//go:embed dist/**
var Assets embed.FS
fsys, err := fs.Sub(frontend.Assets, "dist")
r.NoRoute(func(c *gin.Context) {
c.FileFromFS(c.Request.URL.Path, http.FS(fsys))
})
buildGoModule rec {
name = "neon-display";
version = "0.0.1";
src = ../..;
vendorSha256 = "sha256-WqdXu2yO...";
propagatedBuildInputs = [ rpi_ws281x ];
subPackages = [ "cmd/hal" "cmd/display" ];
NIX_CFLAGS_COMPILE = "-I${rpi_ws281x}/include/ws2811";
NIX_LDFLAGS_COMPILE = "-L${rpi_ws281x}/lib";
preBuild = ''
cp -r ${neon-display-frontend} frontend/dist
'';
}
services.neon-display = {
enable = true;
user = "display";
group = "users";
settings = {
web_bind = "0.0.0.0";
photos_path = "/nas/Pics/Photos";
off_timeout = 120; # seconds
sites = [
{
title = "Live";
url = "http://mon.home.lan/...";
}
];
};
};
nix build .#images.neon
nixos-rebuild --flake '.#neon' switch \
--target-host root@neon --build-host localhost \
--use-remote-sudo -L