me like nix
1{ pkgs, lib, config, ... }:
2
3let
4 cfg = config.pi;
5
6 # Workaround from https://github.com/NixOS/nixos-hardware/blob/master/raspberry-pi/4/apply-overlays-dtmerge.nix
7 deviceTree_overlay = _final: prev: {
8 deviceTree = {
9 applyOverlays = prev.callPackage ./overlays/apply-overlays-dtmerge.nix { };
10 compileDTS = prev.deviceTree.compileDTS;
11 };
12 };
13
14 # Custom libcamera with Raspberry Pi IPA/pipeline support
15 libcamera-rpi = pkgs.libcamera.overrideAttrs (old: {
16 mesonFlags = (old.mesonFlags or [ ]) ++ [
17 "-Dipas=rpi/vc4,rpi/pisp"
18 "-Dpipelines=rpi/vc4,rpi/pisp"
19 ];
20 });
21
22 # rpicam-apps using custom libcamera
23 rpicam-apps = pkgs.stdenv.mkDerivation rec {
24 pname = "rpicam-apps";
25 version = "1.11.1";
26
27 src = pkgs.fetchFromGitHub {
28 owner = "raspberrypi";
29 repo = "rpicam-apps";
30 rev = "v${version}";
31 hash = "sha256-hVoKbvWFeramPkHuibJwUgFOPS9v588+K8828a1fNnA=";
32 };
33
34 nativeBuildInputs = with pkgs; [
35 meson
36 ninja
37 pkg-config
38 ];
39
40 buildInputs = [
41 libcamera-rpi
42 pkgs.libdrm
43 pkgs.libexif
44 pkgs.libjpeg
45 pkgs.libpng
46 pkgs.libtiff
47 pkgs.boost
48 pkgs.ffmpeg
49 ];
50
51 mesonFlags = [
52 "-Denable_libav=enabled"
53 "-Denable_drm=enabled"
54 "-Denable_egl=disabled"
55 "-Denable_qt=disabled"
56 "-Denable_opencv=disabled"
57 "-Denable_tflite=disabled"
58 "-Denable_hailo=disabled"
59 ];
60
61 meta = with lib; {
62 description = "Raspberry Pi camera applications";
63 homepage = "https://github.com/raspberrypi/rpicam-apps";
64 license = licenses.bsd2;
65 platforms = [ "aarch64-linux" ];
66 };
67 };
68in
69{
70 imports = [ ./wifi.nix ];
71
72 options.pi = {
73 streamName = lib.mkOption {
74 type = lib.types.str;
75 description = "Name of the camera stream";
76 };
77
78 resolution = {
79 width = lib.mkOption {
80 type = lib.types.int;
81 default = 1920;
82 description = "Camera resolution width";
83 };
84 height = lib.mkOption {
85 type = lib.types.int;
86 default = 1080;
87 description = "Camera resolution height";
88 };
89 };
90
91 framerate = lib.mkOption {
92 type = lib.types.int;
93 default = 30;
94 description = "Camera framerate";
95 };
96
97 deviceTreeFilter = lib.mkOption {
98 type = lib.types.str;
99 description = "Device tree filter pattern";
100 };
101
102 deviceTreeCompatible = lib.mkOption {
103 type = lib.types.str;
104 description = "Device tree compatible string (e.g., brcm,bcm2711)";
105 };
106
107 gpuMem = lib.mkOption {
108 type = lib.types.int;
109 default = 256;
110 description = "GPU memory allocation in MB";
111 };
112
113 flipCamera = lib.mkOption {
114 type = lib.types.bool;
115 default = false;
116 description = "Flip camera image vertically and horizontally (180 degree rotation)";
117 };
118
119 };
120
121 config = {
122 nix.settings.trusted-users = [ "sean" ];
123
124 # Enable DHCP for ethernet
125 networking.useDHCP = true;
126 # Add device tree overlay for dtmerge support
127 nixpkgs.overlays = [ deviceTree_overlay ];
128
129 # Disable ZFS which isn't supported on Pi
130 boot.supportedFilesystems = lib.mkForce [ "vfat" "ext4" ];
131
132 # Pi kernel lacks device-mapper, so use legacy initrd (not systemd)
133 boot.initrd.systemd.enable = false;
134
135 # Enable SSH for headless setup
136 services.openssh = {
137 enable = true;
138 settings = {
139 PasswordAuthentication = false;
140 PermitRootLogin = "no";
141 };
142 };
143
144 # User config
145 users.users.sean = {
146 isNormalUser = true;
147 extraGroups = [ "wheel" "video" ];
148 openssh.authorizedKeys.keys = [
149 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
150 ];
151 };
152
153 # Allow sudo without password for wheel group
154 security.sudo.wheelNeedsPassword = false;
155
156 # go2rtc for camera streaming to Home Assistant
157 services.go2rtc = {
158 enable = true;
159 settings = {
160 ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg";
161 streams = {
162 "${cfg.streamName}" = "exec:${rpicam-apps}/bin/rpicam-vid -t 0 --width ${toString cfg.resolution.width} --height ${toString cfg.resolution.height} --framerate ${toString cfg.framerate} --codec h264 --inline${lib.optionalString cfg.flipCamera " --vflip --hflip"} -o -";
163 };
164 };
165 };
166
167 # udev rule to give video group access to DMA heap devices (required for libcamera)
168 services.udev.extraRules = ''
169 SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
170 '';
171
172 # Override go2rtc systemd service to run as root for camera access
173 systemd.services.go2rtc.serviceConfig = {
174 User = lib.mkForce "root";
175 };
176
177 # Camera and system tools
178 environment.systemPackages = [
179 pkgs.ffmpeg
180 pkgs.libraspberrypi
181 libcamera-rpi
182 rpicam-apps
183 pkgs.v4l-utils
184 ];
185
186 # Device tree configuration for Pi Camera v3 (IMX708)
187 hardware.deviceTree.filter = cfg.deviceTreeFilter;
188 hardware.deviceTree.overlays = [
189 {
190 name = "imx708-overlay";
191 dtsText = ''
192 // SPDX-License-Identifier: GPL-2.0-only
193 // Definitions for IMX708 camera module on VC I2C bus
194 /dts-v1/;
195 /plugin/;
196
197 /{
198 compatible = "${cfg.deviceTreeCompatible}";
199
200 fragment@0 {
201 target = <&i2c0if>;
202 __overlay__ {
203 status = "okay";
204 };
205 };
206
207 clk_frag: fragment@1 {
208 target = <&cam1_clk>;
209 __overlay__ {
210 status = "okay";
211 clock-frequency = <24000000>;
212 };
213 };
214
215 fragment@2 {
216 target = <&i2c0mux>;
217 __overlay__ {
218 status = "okay";
219 };
220 };
221
222 reg_frag: fragment@3 {
223 target = <&cam1_reg>;
224 cam_reg: __overlay__ {
225 startup-delay-us = <70000>;
226 off-on-delay-us = <30000>;
227 regulator-min-microvolt = <2700000>;
228 regulator-max-microvolt = <2700000>;
229 };
230 };
231
232 i2c_frag: fragment@100 {
233 target = <&i2c_csi_dsi>;
234 __overlay__ {
235 #address-cells = <1>;
236 #size-cells = <0>;
237 status = "okay";
238
239 // IMX708 sensor configuration (from imx708.dtsi)
240 cam_node: imx708@1a {
241 compatible = "sony,imx708";
242 reg = <0x1a>;
243 status = "okay";
244
245 clocks = <&cam1_clk>;
246 clock-names = "inclk";
247
248 vana1-supply = <&cam1_reg>;
249 vana2-supply = <&cam_dummy_reg>;
250 vdig-supply = <&cam_dummy_reg>;
251 vddl-supply = <&cam_dummy_reg>;
252
253 rotation = <180>;
254 orientation = <2>;
255
256 port {
257 cam_endpoint: endpoint {
258 clock-lanes = <0>;
259 data-lanes = <1 2>;
260 clock-noncontinuous;
261 link-frequencies =
262 /bits/ 64 <450000000>;
263 };
264 };
265 };
266
267 // VCM (autofocus motor) configuration
268 vcm_node: dw9817@c {
269 compatible = "dongwoon,dw9817-vcm";
270 reg = <0x0c>;
271 status = "okay";
272 VDD-supply = <&cam1_reg>;
273 };
274 };
275 };
276
277 csi_frag: fragment@101 {
278 target = <&csi1>;
279 csi: __overlay__ {
280 status = "okay";
281 brcm,media-controller;
282
283 port {
284 csi_ep: endpoint {
285 remote-endpoint = <&cam_endpoint>;
286 clock-lanes = <0>;
287 data-lanes = <1 2>;
288 clock-noncontinuous;
289 };
290 };
291 };
292 };
293
294 __overrides__ {
295 rotation = <&cam_node>,"rotation:0";
296 orientation = <&cam_node>,"orientation:0";
297 media-controller = <&csi>,"brcm,media-controller?";
298 cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
299 <&csi_frag>, "target:0=",<&csi0>,
300 <&clk_frag>, "target:0=",<&cam0_clk>,
301 <®_frag>, "target:0=",<&cam0_reg>,
302 <&cam_node>, "clocks:0=",<&cam0_clk>,
303 <&cam_node>, "vana1-supply:0=",<&cam0_reg>,
304 <&vcm_node>, "VDD-supply:0=",<&cam0_reg>;
305 vcm = <&vcm_node>, "status",
306 <0>, "=4";
307 link-frequency = <&cam_endpoint>,"link-frequencies#0";
308 };
309 };
310
311 &cam_endpoint {
312 remote-endpoint = <&csi_ep>;
313 };
314 '';
315 }
316 ];
317
318 # Raspberry Pi firmware for camera
319 hardware.enableRedistributableFirmware = true;
320
321 # Add camera config and overlays to firmware partition
322 sdImage.populateFirmwareCommands = lib.mkAfter ''
323 chmod u+w ./firmware/config.txt
324 cat >> ./firmware/config.txt << EOF
325
326# Camera support - Pi Camera v3 (IMX708)
327gpu_mem=${toString cfg.gpuMem}
328EOF
329
330 # Copy device tree overlays for camera auto-detect
331 if [ -d ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ]; then
332 cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ./firmware/
333 fi
334 '';
335
336 # Firewall
337 networking.firewall.allowedTCPPorts = [
338 22 # SSH
339 1984 # go2rtc API
340 8554 # RTSP
341 ];
342 };
343}