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