001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.vision.camera;
019
020import com.fasterxml.jackson.annotation.JsonCreator;
021import com.fasterxml.jackson.annotation.JsonProperty;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Objects;
027
028public class QuirkyCamera {
029    private static final List<QuirkyCamera> quirkyCameras =
030            List.of(
031                    // SeeCam, which has an odd exposure range
032                    new QuirkyCamera(
033                            0x2560, 0xc128, "See3Cam_24CUG", CameraQuirk.Gain, CameraQuirk.See3Cam_24CUG),
034                    // Chris's older generic "Logitech HD Webcam"
035                    new QuirkyCamera(0x9331, 0x5A3, CameraQuirk.CompletelyBroken),
036                    // Logitech C270
037                    new QuirkyCamera(0x825, 0x46D, CameraQuirk.CompletelyBroken),
038                    // A laptop internal camera someone found broken
039                    new QuirkyCamera(0x0bda, 0x5510, CameraQuirk.CompletelyBroken),
040                    // SnapCamera on Windows
041                    new QuirkyCamera(-1, -1, "Snap Camera", CameraQuirk.CompletelyBroken),
042                    // Mac Facetime Camera shared into Windows in Bootcamp
043                    new QuirkyCamera(-1, -1, "FaceTime HD Camera", CameraQuirk.CompletelyBroken),
044                    // Microsoft Lifecam
045                    new QuirkyCamera(-1, -1, "LifeCam HD-3000", CameraQuirk.LifeCamControls),
046                    // Microsoft Lifecam
047                    new QuirkyCamera(-1, -1, "LifeCam Cinema (TM)", CameraQuirk.LifeCamControls),
048                    // PS3Eye
049                    new QuirkyCamera(
050                            0x1415, 0x2000, CameraQuirk.Gain, CameraQuirk.FPSCap100, CameraQuirk.PsEyeControls),
051                    // Logitech C925-e
052                    new QuirkyCamera(0x85B, 0x46D, CameraQuirk.AdjustableFocus),
053                    // Generic arducam. Since OV2311 can't be differentiated
054                    // at first boot, apply stickyFPS to the generic case, too
055                    new QuirkyCamera(
056                            0x0c45,
057                            0x6366,
058                            "",
059                            "Arducam Generic",
060                            CameraQuirk.ArduCamCamera,
061                            CameraQuirk.Gain,
062                            CameraQuirk.StickyFPS),
063                    // Arducam OV2311
064                    new QuirkyCamera(
065                            0x0c45,
066                            0x6366,
067                            "OV2311",
068                            "OV2311",
069                            CameraQuirk.ArduCamCamera,
070                            CameraQuirk.ArduOV2311Controls,
071                            CameraQuirk.StickyFPS),
072                    // Arducam OV9281
073                    new QuirkyCamera(
074                            0x0c45,
075                            0x6366,
076                            "OV9281",
077                            "OV9281",
078                            CameraQuirk.ArduCamCamera,
079                            CameraQuirk.ArduOV9281Controls),
080                    // Arducam OV9782
081                    new QuirkyCamera(
082                            0x0c45,
083                            0x6366,
084                            "OV9782",
085                            "OV9782",
086                            CameraQuirk.ArduCamCamera,
087                            CameraQuirk.Gain,
088                            CameraQuirk.ArduOV9782Controls),
089                    // Innomaker OV9281
090                    new QuirkyCamera(
091                            0x0c45, 0x636d, "USB Camera", "Innomaker OV9281", CameraQuirk.InnoOV9281Controls));
092
093    public static final QuirkyCamera DefaultCamera = new QuirkyCamera(0, 0, "");
094    public static final QuirkyCamera ZeroCopyPiCamera =
095            new QuirkyCamera(
096                    -1,
097                    -1,
098                    "unicam",
099                    CameraQuirk.Gain,
100                    CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
101
102    @JsonProperty("baseName")
103    public final String baseName;
104
105    @JsonProperty("usbVid")
106    public final int usbVid;
107
108    @JsonProperty("usbPid")
109    public final int usbPid;
110
111    @JsonProperty("displayName")
112    public final String displayName;
113
114    @JsonProperty("quirks")
115    public final HashMap<CameraQuirk, Boolean> quirks;
116
117    /**
118     * Creates a QuirkyCamera that matches by USB VID/PID
119     *
120     * @param usbVid USB VID of camera
121     * @param usbPid USB PID of camera
122     * @param quirks Camera quirks
123     */
124    private QuirkyCamera(int usbVid, int usbPid, CameraQuirk... quirks) {
125        this(usbVid, usbPid, "", quirks);
126    }
127
128    /**
129     * Creates a QuirkyCamera that matches by USB VID/PID and name
130     *
131     * @param usbVid USB VID of camera
132     * @param usbPid USB PID of camera
133     * @param baseName CSCore name of camera
134     * @param quirks Camera quirks
135     */
136    private QuirkyCamera(int usbVid, int usbPid, String baseName, CameraQuirk... quirks) {
137        this(usbVid, usbPid, baseName, "", quirks);
138    }
139
140    /**
141     * Creates a QuirkyCamera that matches by USB VID/PID and name
142     *
143     * @param usbVid USB VID of camera
144     * @param usbPid USB PID of camera
145     * @param baseName CSCore name of camera
146     * @param displayName Human-friendly quirky camera name
147     * @param quirks Camera quirks
148     */
149    private QuirkyCamera(
150            int usbVid, int usbPid, String baseName, String displayName, CameraQuirk... quirks) {
151        this.usbVid = usbVid;
152        this.usbPid = usbPid;
153        this.baseName = baseName;
154        this.displayName = displayName;
155
156        this.quirks = new HashMap<>();
157
158        // (1) Fill quirk map with the supplied Quirk list
159        for (var q : quirks) {
160            this.quirks.put(q, true);
161        }
162
163        // (2) for all other quirks in CameraQuirks (in this version of Photon), default to false
164        for (var q : CameraQuirk.values()) {
165            this.quirks.putIfAbsent(q, false);
166        }
167    }
168
169    @JsonCreator
170    public QuirkyCamera(
171            @JsonProperty("baseName") String baseName,
172            @JsonProperty("usbVid") int usbVid,
173            @JsonProperty("usbPid") int usbPid,
174            @JsonProperty("displayName") String displayName,
175            @JsonProperty("quirks") HashMap<CameraQuirk, Boolean> quirks) {
176        this.baseName = baseName;
177        this.usbPid = usbPid;
178        this.usbVid = usbVid;
179        this.quirks = quirks;
180        this.displayName = displayName;
181    }
182
183    /**
184     * Check if this camera
185     *
186     * @param quirk
187     * @return
188     */
189    public boolean hasQuirk(CameraQuirk quirk) {
190        return quirks.getOrDefault(quirk, false);
191    }
192
193    public static QuirkyCamera getQuirkyCamera(int usbVid, int usbPid) {
194        return getQuirkyCamera(usbVid, usbPid, "");
195    }
196
197    public static QuirkyCamera getQuirkyCamera(int usbVid, int usbPid, String baseName) {
198        for (var qc : quirkyCameras) {
199            boolean useBaseNameMatch = !qc.baseName.isEmpty();
200            boolean matchesBaseName = true; // default to matching
201            if (useBaseNameMatch) {
202                matchesBaseName = baseName.endsWith(qc.baseName);
203            }
204
205            boolean usePidVidMatch = (qc.usbVid != -1) && (qc.usbPid != -1);
206            boolean matchesPidVid = true; // default to matching
207            if (usePidVidMatch) {
208                matchesPidVid = (qc.usbVid == usbVid && qc.usbPid == usbPid);
209            }
210
211            if (matchesPidVid && matchesBaseName) {
212                // We have a quirky camera!
213                // Copy the quirks from our predefined object and create
214                // a QuirkyCamera object with the complete properties
215                List<CameraQuirk> quirks = new ArrayList<CameraQuirk>();
216                for (var q : CameraQuirk.values()) {
217                    if (qc.hasQuirk(q)) quirks.add(q);
218                }
219                QuirkyCamera c =
220                        new QuirkyCamera(
221                                usbVid,
222                                usbPid,
223                                baseName,
224                                Arrays.copyOf(quirks.toArray(), quirks.size(), CameraQuirk[].class));
225                return c;
226            }
227        }
228        return new QuirkyCamera(usbVid, usbPid, baseName);
229    }
230
231    public boolean hasQuirks() {
232        return quirks.containsValue(true);
233    }
234
235    @Override
236    public boolean equals(Object o) {
237        if (this == o) return true;
238        if (o == null || getClass() != o.getClass()) return false;
239        QuirkyCamera that = (QuirkyCamera) o;
240        return usbVid == that.usbVid
241                && usbPid == that.usbPid
242                && Objects.equals(baseName, that.baseName)
243                && Objects.equals(quirks, that.quirks);
244    }
245
246    @Override
247    public String toString() {
248        String ret =
249                "QuirkyCamera [baseName="
250                        + baseName
251                        + ", displayName="
252                        + displayName
253                        + ", usbVid="
254                        + usbVid
255                        + ", usbPid="
256                        + usbPid
257                        + ", quirks="
258                        + quirks.toString()
259                        + "]";
260        return ret;
261    }
262
263    @Override
264    public int hashCode() {
265        return Objects.hash(usbVid, usbPid, baseName, quirks);
266    }
267
268    /**
269     * Add/remove quirks from the camera we're controlling
270     *
271     * @param quirksToChange map of true/false for quirks we should change
272     */
273    public void updateQuirks(HashMap<CameraQuirk, Boolean> quirksToChange) {
274        for (var q : quirksToChange.entrySet()) {
275            var quirk = q.getKey();
276            var hasQuirk = q.getValue();
277
278            this.quirks.put(quirk, hasQuirk);
279        }
280    }
281}