Generate y2020 pi configs from a template

This removes a bunch of obnoxious redundancy.

Change-Id: I85741b7dfa482a6e95190628eb23d07394a25ede
diff --git a/y2020/BUILD b/y2020/BUILD
index 45d958c..cd977f9 100644
--- a/y2020/BUILD
+++ b/y2020/BUILD
@@ -2,6 +2,7 @@
 load("//aos:config.bzl", "aos_config")
 load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library")
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("//y2020:config_gen.bzl", "generate_pi_config")
 
 robot_downloader(
     binaries = [
@@ -245,3 +246,11 @@
         "//aos/events:shm_event_loop",
     ],
 )
+
+py_binary(
+    name = "generate_pi_config",
+    srcs = ["generate_pi_config.py"],
+    deps = ["@python_jinja2"],
+)
+
+[generate_pi_config(num) for num in range(1, 5)]
diff --git a/y2020/config_gen.bzl b/y2020/config_gen.bzl
new file mode 100644
index 0000000..aed1302
--- /dev/null
+++ b/y2020/config_gen.bzl
@@ -0,0 +1,13 @@
+def generate_pi_config(number):
+  native.genrule(
+      name = "generate_pi_config_%d" % (number),
+      srcs = ["y2020_pi_template.json"],
+      outs = ["y2020_pi%d.json" % (number)],
+      cmd = " ".join([
+          "$(location //y2020:generate_pi_config)",
+          "$(location y2020_pi_template.json)",
+          "'{\"NUM\": %d}'" % (number),
+          "$(OUTS)",
+      ]),
+      tools = ["//y2020:generate_pi_config"],
+  )
diff --git a/y2020/generate_pi_config.py b/y2020/generate_pi_config.py
new file mode 100644
index 0000000..afd1fca
--- /dev/null
+++ b/y2020/generate_pi_config.py
@@ -0,0 +1,30 @@
+#!/usr/bin/python3
+
+import argparse
+from pathlib import Path
+import json
+import sys
+
+import jinja2
+
+
+def main():
+  # Note: this is a pretty transparent interface to jinja2--there's no reason
+  # this script couldn't be renamed and then used to generate any config from
+  # a template.
+  parser = argparse.ArgumentParser(
+      description="Generates the raspberry pi configs from a template.")
+  parser.add_argument("template", type=str, help="File to use for template.")
+  parser.add_argument("replacements", type=json.loads, help="Dictionary of parameters to replace in the template.")
+  parser.add_argument("output", type=str, help="Output file to create.")
+  args = parser.parse_args(sys.argv[1:])
+
+  with open(args.template, 'r') as input_file:
+    template = jinja2.Template(input_file.read())
+
+  output = template.render(args.replacements)
+  with open(args.output, 'w') as config_file:
+    config_file.write(output)
+
+if __name__ == '__main__':
+  main()
diff --git a/y2020/y2020_pi1.json b/y2020/y2020_pi1.json
deleted file mode 100644
index 6974796..0000000
--- a/y2020/y2020_pi1.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
-  "channels":
-  [
-    {
-      "name": "/pi1/aos",
-      "type": "aos.timing.Report",
-      "source_node": "pi1",
-      "frequency": 50,
-      "num_senders": 20,
-      "max_size": 2048
-    },
-    {
-      "name": "/pi1/aos",
-      "type": "aos.logging.LogMessageFbs",
-      "source_node": "pi1",
-      "frequency": 200,
-      "num_senders": 20
-    },
-    {
-      "name": "/pi1/aos",
-      "type": "aos.message_bridge.ServerStatistics",
-      "source_node": "pi1",
-      "frequency": 2,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi1/aos",
-      "type": "aos.message_bridge.ClientStatistics",
-      "source_node": "pi1",
-      "frequency": 10,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi1/aos",
-      "type": "aos.message_bridge.Timestamp",
-      "source_node": "pi1",
-      "frequency": 10,
-      "num_senders": 2,
-      "max_size": 200,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi1/camera",
-      "type": "frc971.vision.CameraImage",
-      "source_node": "pi1",
-      "frequency": 25,
-      "max_size": 620000,
-      "num_senders": 18
-    },
-    {
-      "name": "/pi1/camera",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi1",
-      "logger": "LOCAL_AND_REMOTE_LOGGER",
-      "logger_nodes": ["roborio"],
-      "frequency": 25,
-      "max_size": 10000,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi1/camera/detailed",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi1",
-      "frequency": 25,
-      "max_size": 1000000
-    },
-    {
-      "name": "/pi1/camera",
-      "type": "frc971.vision.sift.TrainingData",
-      "source_node": "pi1",
-      "frequency": 2,
-      "max_size": 2000000
-    }
-  ],
-  "maps": [
-    {
-      "match": {
-        "name": "/aos*",
-        "source_node": "pi1"
-      },
-      "rename": {
-        "name": "/pi1/aos"
-      }
-    },
-    {
-      "match": {
-        "name": "/camera*",
-        "source_node": "pi1"
-      },
-      "rename": {
-        "name": "/pi1/camera"
-      }
-    }
-  ],
-  "nodes": [
-    {
-      "name": "pi1",
-      "hostname": "pi1",
-      "hostnames": [
-        "pi-971-1",
-        "pi-7971-1",
-        "pi-8971-1",
-        "pi-9971-1"
-      ],
-      "port": 9971
-    },
-    {
-      "name": "roborio"
-    }
-  ]
-}
diff --git a/y2020/y2020_pi3.json b/y2020/y2020_pi3.json
deleted file mode 100644
index 9d9c839..0000000
--- a/y2020/y2020_pi3.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
-  "channels":
-  [
-    {
-      "name": "/pi3/aos",
-      "type": "aos.timing.Report",
-      "source_node": "pi3",
-      "frequency": 50,
-      "num_senders": 20,
-      "max_size": 2048
-    },
-    {
-      "name": "/pi3/aos",
-      "type": "aos.logging.LogMessageFbs",
-      "source_node": "pi3",
-      "frequency": 200,
-      "num_senders": 20
-    },
-    {
-      "name": "/pi3/aos",
-      "type": "aos.message_bridge.ServerStatistics",
-      "source_node": "pi3",
-      "frequency": 2,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi3/aos",
-      "type": "aos.message_bridge.ClientStatistics",
-      "source_node": "pi3",
-      "frequency": 10,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi3/aos",
-      "type": "aos.message_bridge.Timestamp",
-      "source_node": "pi3",
-      "frequency": 10,
-      "num_senders": 2,
-      "max_size": 200,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi3/camera",
-      "type": "frc971.vision.CameraImage",
-      "source_node": "pi3",
-      "frequency": 25,
-      "max_size": 620000,
-      "num_senders": 18
-    },
-    {
-      "name": "/pi3/camera",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi3",
-      "logger": "LOCAL_AND_REMOTE_LOGGER",
-      "logger_nodes": ["roborio"],
-      "frequency": 25,
-      "max_size": 10000,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi3/camera/detailed",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi3",
-      "frequency": 25,
-      "max_size": 1000000
-    },
-    {
-      "name": "/pi3/camera",
-      "type": "frc971.vision.sift.TrainingData",
-      "source_node": "pi3",
-      "frequency": 2,
-      "max_size": 2000000
-    }
-  ],
-  "maps": [
-    {
-      "match": {
-        "name": "/aos*",
-        "source_node": "pi3"
-      },
-      "rename": {
-        "name": "/pi3/aos"
-      }
-    },
-    {
-      "match": {
-        "name": "/camera*",
-        "source_node": "pi3"
-      },
-      "rename": {
-        "name": "/pi3/camera"
-      }
-    }
-  ],
-  "nodes": [
-    {
-      "name": "pi3",
-      "hostname": "pi3",
-      "hostnames": [
-        "pi-971-3",
-        "pi-7971-3",
-        "pi-8971-3",
-        "pi-9971-3"
-      ],
-      "port": 9971
-    },
-    {
-      "name": "roborio"
-    }
-  ]
-}
diff --git a/y2020/y2020_pi4.json b/y2020/y2020_pi4.json
deleted file mode 100644
index ce11ded..0000000
--- a/y2020/y2020_pi4.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
-  "channels":
-  [
-    {
-      "name": "/pi4/aos",
-      "type": "aos.timing.Report",
-      "source_node": "pi4",
-      "frequency": 50,
-      "num_senders": 20,
-      "max_size": 2048
-    },
-    {
-      "name": "/pi4/aos",
-      "type": "aos.logging.LogMessageFbs",
-      "source_node": "pi4",
-      "frequency": 200,
-      "num_senders": 20
-    },
-    {
-      "name": "/pi4/aos",
-      "type": "aos.message_bridge.ServerStatistics",
-      "source_node": "pi4",
-      "frequency": 2,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi4/aos",
-      "type": "aos.message_bridge.ClientStatistics",
-      "source_node": "pi4",
-      "frequency": 10,
-      "num_senders": 2
-    },
-    {
-      "name": "/pi4/aos",
-      "type": "aos.message_bridge.Timestamp",
-      "source_node": "pi4",
-      "frequency": 10,
-      "num_senders": 2,
-      "max_size": 200,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi4/camera",
-      "type": "frc971.vision.CameraImage",
-      "source_node": "pi4",
-      "frequency": 25,
-      "max_size": 620000,
-      "num_senders": 18
-    },
-    {
-      "name": "/pi4/camera",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi4",
-      "logger": "LOCAL_AND_REMOTE_LOGGER",
-      "logger_nodes": ["roborio"],
-      "frequency": 25,
-      "max_size": 10000,
-      "destination_nodes": [
-        {
-          "name": "roborio",
-          "priority": 1,
-          "time_to_live": 5000000
-        }
-      ]
-    },
-    {
-      "name": "/pi4/camera/detailed",
-      "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi4",
-      "frequency": 25,
-      "max_size": 1000000
-    },
-    {
-      "name": "/pi4/camera",
-      "type": "frc971.vision.sift.TrainingData",
-      "source_node": "pi4",
-      "frequency": 2,
-      "max_size": 2000000
-    }
-  ],
-  "maps": [
-    {
-      "match": {
-        "name": "/aos*",
-        "source_node": "pi4"
-      },
-      "rename": {
-        "name": "/pi4/aos"
-      }
-    },
-    {
-      "match": {
-        "name": "/camera*",
-        "source_node": "pi4"
-      },
-      "rename": {
-        "name": "/pi4/camera"
-      }
-    }
-  ],
-  "nodes": [
-    {
-      "name": "pi4",
-      "hostname": "pi4",
-      "hostnames": [
-        "pi-971-4",
-        "pi-7971-4",
-        "pi-8971-4",
-        "pi-9971-4"
-      ],
-      "port": 9971
-    },
-    {
-      "name": "roborio"
-    }
-  ]
-}
diff --git a/y2020/y2020_pi2.json b/y2020/y2020_pi_template.json
similarity index 64%
rename from y2020/y2020_pi2.json
rename to y2020/y2020_pi_template.json
index facc010..4b04c6c 100644
--- a/y2020/y2020_pi2.json
+++ b/y2020/y2020_pi_template.json
@@ -2,38 +2,38 @@
   "channels":
   [
     {
-      "name": "/pi2/aos",
+      "name": "/pi{{ NUM }}/aos",
       "type": "aos.timing.Report",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 50,
       "num_senders": 20,
       "max_size": 2048
     },
     {
-      "name": "/pi2/aos",
+      "name": "/pi{{ NUM }}/aos",
       "type": "aos.logging.LogMessageFbs",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 200,
       "num_senders": 20
     },
     {
-      "name": "/pi2/aos",
+      "name": "/pi{{ NUM }}/aos",
       "type": "aos.message_bridge.ServerStatistics",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 2,
       "num_senders": 2
     },
     {
-      "name": "/pi2/aos",
+      "name": "/pi{{ NUM }}/aos",
       "type": "aos.message_bridge.ClientStatistics",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 10,
       "num_senders": 2
     },
     {
-      "name": "/pi2/aos",
+      "name": "/pi{{ NUM }}/aos",
       "type": "aos.message_bridge.Timestamp",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 10,
       "num_senders": 2,
       "max_size": 200,
@@ -46,17 +46,17 @@
       ]
     },
     {
-      "name": "/pi2/camera",
+      "name": "/pi{{ NUM }}/camera",
       "type": "frc971.vision.CameraImage",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 25,
       "max_size": 620000,
       "num_senders": 18
     },
     {
-      "name": "/pi2/camera",
+      "name": "/pi{{ NUM }}/camera",
       "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "logger": "LOCAL_AND_REMOTE_LOGGER",
       "logger_nodes": ["roborio"],
       "frequency": 25,
@@ -70,16 +70,16 @@
       ]
     },
     {
-      "name": "/pi2/camera/detailed",
+      "name": "/pi{{ NUM }}/camera/detailed",
       "type": "frc971.vision.sift.ImageMatchResult",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 25,
       "max_size": 1000000
     },
     {
-      "name": "/pi2/camera",
+      "name": "/pi{{ NUM }}/camera",
       "type": "frc971.vision.sift.TrainingData",
-      "source_node": "pi2",
+      "source_node": "pi{{ NUM }}",
       "frequency": 2,
       "max_size": 2000000
     }
@@ -88,31 +88,31 @@
     {
       "match": {
         "name": "/aos*",
-        "source_node": "pi2"
+        "source_node": "pi{{ NUM }}"
       },
       "rename": {
-        "name": "/pi2/aos"
+        "name": "/pi{{ NUM }}/aos"
       }
     },
     {
       "match": {
         "name": "/camera*",
-        "source_node": "pi2"
+        "source_node": "pi{{ NUM }}"
       },
       "rename": {
-        "name": "/pi2/camera"
+        "name": "/pi{{ NUM }}/camera"
       }
     }
   ],
   "nodes": [
     {
-      "name": "pi2",
-      "hostname": "pi2",
+      "name": "pi{{ NUM }}",
+      "hostname": "pi{{ NUM }}",
       "hostnames": [
-        "pi-971-2",
-        "pi-7971-2",
-        "pi-8971-2",
-        "pi-9971-2"
+        "pi-971-{{ NUM }}",
+        "pi-7971-{{ NUM }}",
+        "pi-8971-{{ NUM }}",
+        "pi-9971-{{ NUM }}"
       ],
       "port": 9971
     },