Merge "Make camera_viewer autosubscribe."
diff --git a/y2020/www/camera_main.ts b/y2020/www/camera_main.ts
index 46a0e8a..e13452e 100644
--- a/y2020/www/camera_main.ts
+++ b/y2020/www/camera_main.ts
@@ -5,12 +5,6 @@
 
 const conn = new Connection();
 
-const configPrinter = new ConfigHandler(conn);
-
 conn.connect();
 
-const iHandler = new ImageHandler();
-
-conn.addHandler(iHandler.getId(), (data) => iHandler.handleImage(data));
-conn.addHandler(
-    iHandler.getResultId(), (data) => iHandler.handleImageMetadata(data));
+const iHandler = new ImageHandler(conn);
diff --git a/y2020/www/image_handler.ts b/y2020/www/image_handler.ts
index e2ba0b9..a44156a 100644
--- a/y2020/www/image_handler.ts
+++ b/y2020/www/image_handler.ts
@@ -1,5 +1,55 @@
-import {CameraImage} from 'y2020/vision/vision_generated';
+import {Channel} from 'aos/configuration_generated';
+import {Connection} from 'aos/network/www/proxy';
+import {Connect} from 'aos/network/connect_generated';
 import {ImageMatchResult} from 'y2020/vision/sift/sift_generated'
+import {CameraImage} from 'y2020/vision/vision_generated';
+
+/*
+ * All the messages that are required to show an image with metadata.
+ * Messages not readable on the server node are ignored.
+ */
+const REQUIRED_CHANNELS = [
+  {
+    name: '/pi1/camera',
+    type: CameraImage.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi2/camera',
+    type: CameraImage.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi3/camera',
+    type: CameraImage.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi4/camera',
+    type: CameraImage.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi5/camera',
+    type: CameraImage.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi1/camera/detailed',
+    type: ImageMatchResult.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi2/camera/detailed',
+    type: ImageMatchResult.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi3/camera/detailed',
+    type: ImageMatchResult.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi4/camera/detailed',
+    type: ImageMatchResult.getFullyQualifiedName(),
+  },
+  {
+    name: '/pi5/camera/detailed',
+    type: ImageMatchResult.getFullyQualifiedName(),
+  },
+];
 
 export class ImageHandler {
   private canvas = document.createElement('canvas');
@@ -12,8 +62,39 @@
   private height = 0;
   private imageSkipCount = 3;
 
-  constructor() {
+  constructor(private readonly connection: Connection) {
     document.body.appendChild(this.canvas);
+
+    this.connection.addConfigHandler(() => {
+      this.sendConnect();
+    });
+    this.connection.addHandler(ImageMatchResult.getFullyQualifiedName(), (data) => {
+      this.handleImageMetadata(data);
+    });
+    this.connection.addHandler(CameraImage.getFullyQualifiedName(), (data) => {
+      this.handleImage(data);
+    });
+  }
+
+  private sendConnect(): void {
+    const builder = new flatbuffers.Builder(512);
+    const channels: flatbuffers.Offset[] = [];
+    for (const channel of REQUIRED_CHANNELS) {
+      const nameFb = builder.createString(channel.name);
+      const typeFb = builder.createString(channel.type);
+      Channel.startChannel(builder);
+      Channel.addName(builder, nameFb);
+      Channel.addType(builder, typeFb);
+      const channelFb = Channel.endChannel(builder);
+      channels.push(channelFb);
+    }
+
+    const channelsFb = Connect.createChannelsToTransferVector(builder, channels);
+    Connect.startConnect(builder);
+    Connect.addChannelsToTransfer(builder, channelsFb);
+    const connect = Connect.endConnect(builder);
+    builder.finish(connect);
+    this.connection.sendConnectMessage(builder);
   }
 
   handleImage(data: Uint8Array): void {
@@ -108,12 +189,4 @@
       ctx.stroke();
     }
   }
-
-  getId(): string {
-    return CameraImage.getFullyQualifiedName();
-  }
-
-  getResultId(): string {
-    return ImageMatchResult.getFullyQualifiedName();
-  }
 }