Brian Silverman | 41cdd3e | 2019-01-19 19:48:58 -0800 | [diff] [blame] | 1 | /*----------------------------------------------------------------------------*/ |
| 2 | /* Copyright (c) 2018 FIRST. All Rights Reserved. */ |
| 3 | /* Open Source Software - may be modified and shared by FRC teams. The code */ |
| 4 | /* must be accompanied by the FIRST BSD license file in the root directory of */ |
| 5 | /* the project. */ |
| 6 | /*----------------------------------------------------------------------------*/ |
| 7 | |
| 8 | import java.io.IOException; |
| 9 | import java.nio.file.Files; |
| 10 | import java.nio.file.Paths; |
| 11 | import java.util.ArrayList; |
| 12 | import java.util.List; |
| 13 | |
| 14 | import com.google.gson.Gson; |
| 15 | import com.google.gson.GsonBuilder; |
| 16 | import com.google.gson.JsonArray; |
| 17 | import com.google.gson.JsonElement; |
| 18 | import com.google.gson.JsonObject; |
| 19 | import com.google.gson.JsonParser; |
| 20 | |
| 21 | import edu.wpi.cscore.VideoSource; |
| 22 | import edu.wpi.first.cameraserver.CameraServer; |
| 23 | import edu.wpi.first.networktables.NetworkTableInstance; |
| 24 | |
| 25 | /* |
| 26 | JSON format: |
| 27 | { |
| 28 | "team": <team number>, |
| 29 | "ntmode": <"client" or "server", "client" if unspecified> |
| 30 | "cameras": [ |
| 31 | { |
| 32 | "name": <camera name> |
| 33 | "path": <path, e.g. "/dev/video0"> |
| 34 | "pixel format": <"MJPEG", "YUYV", etc> // optional |
| 35 | "width": <video mode width> // optional |
| 36 | "height": <video mode height> // optional |
| 37 | "fps": <video mode fps> // optional |
| 38 | "brightness": <percentage brightness> // optional |
| 39 | "white balance": <"auto", "hold", value> // optional |
| 40 | "exposure": <"auto", "hold", value> // optional |
| 41 | "properties": [ // optional |
| 42 | { |
| 43 | "name": <property name> |
| 44 | "value": <property value> |
| 45 | } |
| 46 | ] |
| 47 | } |
| 48 | ] |
| 49 | } |
| 50 | */ |
| 51 | |
| 52 | public final class Main { |
| 53 | private static String configFile = "/boot/frc.json"; |
| 54 | |
| 55 | @SuppressWarnings("MemberName") |
| 56 | public static class CameraConfig { |
| 57 | public String name; |
| 58 | public String path; |
| 59 | public JsonObject config; |
| 60 | } |
| 61 | |
| 62 | public static int team; |
| 63 | public static boolean server; |
| 64 | public static List<CameraConfig> cameras = new ArrayList<>(); |
| 65 | |
| 66 | private Main() { |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Report parse error. |
| 71 | */ |
| 72 | public static void parseError(String str) { |
| 73 | System.err.println("config error in '" + configFile + "': " + str); |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Read single camera configuration. |
| 78 | */ |
| 79 | public static boolean readCameraConfig(JsonObject config) { |
| 80 | CameraConfig cam = new CameraConfig(); |
| 81 | |
| 82 | // name |
| 83 | JsonElement nameElement = config.get("name"); |
| 84 | if (nameElement == null) { |
| 85 | parseError("could not read camera name"); |
| 86 | return false; |
| 87 | } |
| 88 | cam.name = nameElement.getAsString(); |
| 89 | |
| 90 | // path |
| 91 | JsonElement pathElement = config.get("path"); |
| 92 | if (pathElement == null) { |
| 93 | parseError("camera '" + cam.name + "': could not read path"); |
| 94 | return false; |
| 95 | } |
| 96 | cam.path = pathElement.getAsString(); |
| 97 | |
| 98 | cam.config = config; |
| 99 | |
| 100 | cameras.add(cam); |
| 101 | return true; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Read configuration file. |
| 106 | */ |
| 107 | @SuppressWarnings("PMD.CyclomaticComplexity") |
| 108 | public static boolean readConfig() { |
| 109 | // parse file |
| 110 | JsonElement top; |
| 111 | try { |
| 112 | top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile))); |
| 113 | } catch (IOException ex) { |
| 114 | System.err.println("could not open '" + configFile + "': " + ex); |
| 115 | return false; |
| 116 | } |
| 117 | |
| 118 | // top level must be an object |
| 119 | if (!top.isJsonObject()) { |
| 120 | parseError("must be JSON object"); |
| 121 | return false; |
| 122 | } |
| 123 | JsonObject obj = top.getAsJsonObject(); |
| 124 | |
| 125 | // team number |
| 126 | JsonElement teamElement = obj.get("team"); |
| 127 | if (teamElement == null) { |
| 128 | parseError("could not read team number"); |
| 129 | return false; |
| 130 | } |
| 131 | team = teamElement.getAsInt(); |
| 132 | |
| 133 | // ntmode (optional) |
| 134 | if (obj.has("ntmode")) { |
| 135 | String str = obj.get("ntmode").getAsString(); |
| 136 | if ("client".equalsIgnoreCase(str)) { |
| 137 | server = false; |
| 138 | } else if ("server".equalsIgnoreCase(str)) { |
| 139 | server = true; |
| 140 | } else { |
| 141 | parseError("could not understand ntmode value '" + str + "'"); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // cameras |
| 146 | JsonElement camerasElement = obj.get("cameras"); |
| 147 | if (camerasElement == null) { |
| 148 | parseError("could not read cameras"); |
| 149 | return false; |
| 150 | } |
| 151 | JsonArray cameras = camerasElement.getAsJsonArray(); |
| 152 | for (JsonElement camera : cameras) { |
| 153 | if (!readCameraConfig(camera.getAsJsonObject())) { |
| 154 | return false; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | return true; |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Start running the camera. |
| 163 | */ |
| 164 | public static void startCamera(CameraConfig config) { |
| 165 | System.out.println("Starting camera '" + config.name + "' on " + config.path); |
| 166 | VideoSource camera = CameraServer.getInstance().startAutomaticCapture( |
| 167 | config.name, config.path); |
| 168 | |
| 169 | Gson gson = new GsonBuilder().create(); |
| 170 | |
| 171 | camera.setConfigJson(gson.toJson(config.config)); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Main. |
| 176 | */ |
| 177 | public static void main(String... args) { |
| 178 | if (args.length > 0) { |
| 179 | configFile = args[0]; |
| 180 | } |
| 181 | |
| 182 | // read configuration |
| 183 | if (!readConfig()) { |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | // start NetworkTables |
| 188 | NetworkTableInstance ntinst = NetworkTableInstance.getDefault(); |
| 189 | if (server) { |
| 190 | System.out.println("Setting up NetworkTables server"); |
| 191 | ntinst.startServer(); |
| 192 | } else { |
| 193 | System.out.println("Setting up NetworkTables client for team " + team); |
| 194 | ntinst.startClientTeam(team); |
| 195 | } |
| 196 | |
| 197 | // start cameras |
| 198 | for (CameraConfig camera : cameras) { |
| 199 | startCamera(camera); |
| 200 | } |
| 201 | |
| 202 | // loop forever |
| 203 | for (;;) { |
| 204 | try { |
| 205 | Thread.sleep(10000); |
| 206 | } catch (InterruptedException ex) { |
| 207 | return; |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | } |