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 flipCamera = lib.mkOption {
104 type = lib.types.bool;
105 default = false;
106 description = "Flip camera image 180 degrees";
107 };
108 };
109
110 config = {
111 nix.settings.trusted-users = [ "sean" ];
112
113 networking.useDHCP = true;
114 nixpkgs.overlays = [ deviceTree_overlay ];
115
116 boot.supportedFilesystems = lib.mkForce [
117 "vfat"
118 "ext4"
119 ];
120 boot.initrd.systemd.enable = false;
121
122 services.openssh = {
123 enable = true;
124 settings = {
125 PasswordAuthentication = false;
126 PermitRootLogin = "no";
127 };
128 };
129
130 users.users.sean = {
131 isNormalUser = true;
132 extraGroups = [
133 "wheel"
134 "video"
135 ];
136 openssh.authorizedKeys.keys = [
137 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
138 "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
139 ];
140 };
141
142 security.sudo.wheelNeedsPassword = false;
143
144 services.go2rtc = {
145 enable = true;
146 settings = {
147 ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg";
148 streams = {
149 "${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 -";
150 };
151 };
152 };
153
154 services.udev.extraRules = ''
155 SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
156 '';
157
158 systemd.services.go2rtc.serviceConfig = {
159 User = lib.mkForce "root";
160 };
161
162 environment.systemPackages = [
163 pkgs.ffmpeg
164 pkgs.libraspberrypi
165 libcamera-rpi
166 rpicam-apps
167 pkgs.v4l-utils
168 ];
169
170 hardware.deviceTree.filter = cfg.deviceTreeFilter;
171 hardware.deviceTree.overlays = [
172 {
173 name = "imx708-overlay";
174 dtsText = ''
175 // SPDX-License-Identifier: GPL-2.0-only
176 // Definitions for IMX708 camera module on VC I2C bus
177 /dts-v1/;
178 /plugin/;
179
180 /{
181 compatible = "${cfg.deviceTreeCompatible}";
182
183 fragment@0 {
184 target = <&i2c0if>;
185 __overlay__ {
186 status = "okay";
187 };
188 };
189
190 clk_frag: fragment@1 {
191 target = <&cam1_clk>;
192 __overlay__ {
193 status = "okay";
194 clock-frequency = <24000000>;
195 };
196 };
197
198 fragment@2 {
199 target = <&i2c0mux>;
200 __overlay__ {
201 status = "okay";
202 };
203 };
204
205 reg_frag: fragment@3 {
206 target = <&cam1_reg>;
207 cam_reg: __overlay__ {
208 startup-delay-us = <70000>;
209 off-on-delay-us = <30000>;
210 regulator-min-microvolt = <2700000>;
211 regulator-max-microvolt = <2700000>;
212 };
213 };
214
215 i2c_frag: fragment@100 {
216 target = <&i2c_csi_dsi>;
217 __overlay__ {
218 #address-cells = <1>;
219 #size-cells = <0>;
220 status = "okay";
221
222 cam_node: imx708@1a {
223 compatible = "sony,imx708";
224 reg = <0x1a>;
225 status = "okay";
226
227 clocks = <&cam1_clk>;
228 clock-names = "inclk";
229
230 vana1-supply = <&cam1_reg>;
231 vana2-supply = <&cam_dummy_reg>;
232 vdig-supply = <&cam_dummy_reg>;
233 vddl-supply = <&cam_dummy_reg>;
234
235 rotation = <180>;
236 orientation = <2>;
237
238 port {
239 cam_endpoint: endpoint {
240 clock-lanes = <0>;
241 data-lanes = <1 2>;
242 clock-noncontinuous;
243 link-frequencies =
244 /bits/ 64 <450000000>;
245 };
246 };
247 };
248
249 vcm_node: dw9817@c {
250 compatible = "dongwoon,dw9817-vcm";
251 reg = <0x0c>;
252 status = "okay";
253 VDD-supply = <&cam1_reg>;
254 };
255 };
256 };
257
258 csi_frag: fragment@101 {
259 target = <&csi1>;
260 csi: __overlay__ {
261 status = "okay";
262 brcm,media-controller;
263
264 port {
265 csi_ep: endpoint {
266 remote-endpoint = <&cam_endpoint>;
267 clock-lanes = <0>;
268 data-lanes = <1 2>;
269 clock-noncontinuous;
270 };
271 };
272 };
273 };
274
275 __overrides__ {
276 rotation = <&cam_node>,"rotation:0";
277 orientation = <&cam_node>,"orientation:0";
278 media-controller = <&csi>,"brcm,media-controller?";
279 cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
280 <&csi_frag>, "target:0=",<&csi0>,
281 <&clk_frag>, "target:0=",<&cam0_clk>,
282 <®_frag>, "target:0=",<&cam0_reg>,
283 <&cam_node>, "clocks:0=",<&cam0_clk>,
284 <&cam_node>, "vana1-supply:0=",<&cam0_reg>,
285 <&vcm_node>, "VDD-supply:0=",<&cam0_reg>;
286 vcm = <&vcm_node>, "status",
287 <0>, "=4";
288 link-frequency = <&cam_endpoint>,"link-frequencies#0";
289 };
290 };
291
292 &cam_endpoint {
293 remote-endpoint = <&csi_ep>;
294 };
295 '';
296 }
297 ];
298
299 hardware.enableRedistributableFirmware = true;
300
301 sdImage.populateFirmwareCommands = lib.mkAfter ''
302 chmod u+w ./firmware/config.txt
303 cat >> ./firmware/config.txt << EOF
304
305 # Camera support - Pi Camera v3 (IMX708)
306 gpu_mem=${toString cfg.gpuMem}
307 EOF
308
309 if [ -d ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ]; then
310 cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ./firmware/
311 fi
312 '';
313
314 networking.firewall.allowedTCPPorts = [
315 22
316 1984
317 8554
318 ];
319 };
320 };
321}