Add support for multiple zedmons on one system.

 * Add zedmon list command.
 * Add --config=<json config file> to open zedmon by serial number

Change-Id: I7b484b8b6537217defe9177362c61cde7ff2ff46
diff --git a/cmd/zedmon/common.go b/cmd/zedmon/common.go
new file mode 100644
index 0000000..2562aa4
--- /dev/null
+++ b/cmd/zedmon/common.go
@@ -0,0 +1,61 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"fuchsia.googlesource.com/zedmon"
+)
+
+var configFileName string
+
+func setCommonFlags(flags *flag.FlagSet) {
+	flags.StringVar(&configFileName, "config", "", "Config file.")
+}
+
+func getConfig() zedmon.Config {
+	var config zedmon.Config
+	if configFileName != "" {
+		f, err := os.Open(configFileName)
+		if err != nil {
+			panic(fmt.Sprintf("Can't open %s: %v.", configFileName, err))
+		}
+		b, _ := ioutil.ReadAll(f)
+		err = json.Unmarshal(b, &config)
+		if err != nil {
+			panic(fmt.Sprintf("Can't decode config: %v.", err))
+		}
+	}
+	return config
+}
diff --git a/cmd/zedmon/gpio.go b/cmd/zedmon/gpio.go
index 06cfdc8..fc6ac48 100644
--- a/cmd/zedmon/gpio.go
+++ b/cmd/zedmon/gpio.go
@@ -47,11 +47,12 @@
 `
 }
 
-func (*gpioCmd) SetFlags(_ *flag.FlagSet) {
+func (*gpioCmd) SetFlags(flags *flag.FlagSet) {
+	setCommonFlags(flags)
 }
 
 func (*gpioCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
-	zedmon, err := zedmon.OpenZedmon()
+	zedmon, err := zedmon.OpenZedmon(getConfig())
 	if err != nil {
 		fmt.Printf("Can't open zendmon: %v", err)
 		return subcommands.ExitFailure
diff --git a/cmd/zedmon/list.go b/cmd/zedmon/list.go
new file mode 100644
index 0000000..6187837
--- /dev/null
+++ b/cmd/zedmon/list.go
@@ -0,0 +1,64 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"fuchsia.googlesource.com/zedmon"
+	"github.com/google/subcommands"
+)
+
+type listCmd struct{}
+
+func (*listCmd) Name() string     { return "list" }
+func (*listCmd) Synopsis() string { return "List connected zedmons." }
+func (*listCmd) Usage() string {
+	return `list
+`
+}
+
+func (*listCmd) SetFlags(_ *flag.FlagSet) {
+}
+
+func (*listCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
+	devs, err := zedmon.Enumerate()
+	if err != nil {
+		fmt.Printf("Can't enumerate zendmon: %v", err)
+		return subcommands.ExitFailure
+	}
+
+	for _, dev := range devs {
+		fmt.Printf("%s\n", dev.Serial)
+	}
+
+	return subcommands.ExitSuccess
+}
diff --git a/cmd/zedmon/record.go b/cmd/zedmon/record.go
index 1c71cb6..1053656 100644
--- a/cmd/zedmon/record.go
+++ b/cmd/zedmon/record.go
@@ -50,11 +50,12 @@
 `
 }
 
-func (*recordCmd) SetFlags(_ *flag.FlagSet) {
+func (*recordCmd) SetFlags(flags *flag.FlagSet) {
+	setCommonFlags(flags)
 }
 
 func (*recordCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
-	zedmon, err := zedmon.OpenZedmon()
+	zedmon, err := zedmon.OpenZedmon(getConfig())
 	if err != nil {
 		fmt.Printf("Can't open zendmon: %v", err)
 		return subcommands.ExitFailure
diff --git a/cmd/zedmon/relay.go b/cmd/zedmon/relay.go
index c06c0b7..6133813 100644
--- a/cmd/zedmon/relay.go
+++ b/cmd/zedmon/relay.go
@@ -46,11 +46,12 @@
 `
 }
 
-func (*relayCmd) SetFlags(_ *flag.FlagSet) {
+func (*relayCmd) SetFlags(flags *flag.FlagSet) {
+	setCommonFlags(flags)
 }
 
 func (*relayCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
-	zedmon, err := zedmon.OpenZedmon()
+	zedmon, err := zedmon.OpenZedmon(getConfig())
 	if err != nil {
 		fmt.Printf("Can't open zendmon: %v", err)
 		return subcommands.ExitFailure
diff --git a/cmd/zedmon/zedmon.go b/cmd/zedmon/zedmon.go
index a3d92db..52868e8 100644
--- a/cmd/zedmon/zedmon.go
+++ b/cmd/zedmon/zedmon.go
@@ -41,6 +41,7 @@
 	subcommands.Register(subcommands.FlagsCommand(), "")
 	subcommands.Register(subcommands.CommandsCommand(), "")
 	subcommands.Register(&gpioCmd{}, "")
+	subcommands.Register(&listCmd{}, "")
 	subcommands.Register(&recordCmd{}, "")
 	subcommands.Register(&relayCmd{}, "")
 
diff --git a/lib/zedmon.go b/lib/zedmon.go
index 755aaf0..d520289 100644
--- a/lib/zedmon.go
+++ b/lib/zedmon.go
@@ -32,6 +32,7 @@
 	"bytes"
 	"context"
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"log"
 	"math"
@@ -40,6 +41,14 @@
 	"github.com/google/gousb"
 )
 
+type Info struct {
+	Serial string
+}
+
+type Config struct {
+	Serial *string `json:"serial"`
+}
+
 type Report struct {
 	Timestamp uint64
 	Values    []float64
@@ -207,7 +216,7 @@
 	buf := new(bytes.Buffer)
 	err := binary.Write(buf, binary.LittleEndian, &packet)
 	if err != nil {
-		return fmt.Errorf("SetOuput: binary.Write failed:", err)
+		return fmt.Errorf("SetOuput: binary.Write failed: %v", err)
 	}
 
 	_, err = z.outEp.Write(buf.Bytes())
@@ -342,17 +351,70 @@
 	}
 }
 
-func OpenZedmon() (*Zedmon, error) {
+func Enumerate() ([]Info, error) {
+	ctx := gousb.NewContext()
+	devs, err := ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool {
+		return desc.Vendor == 0x18d1 && desc.Product == 0xaf00
+	})
+	if err != nil {
+		ctx.Close()
+		return nil, err
+	}
+
+	if len(devs) == 0 {
+		return nil, errors.New("No zedmon device found")
+	}
+
+	var infos []Info
+	for _, d := range devs {
+		serial, err := d.SerialNumber()
+		if err != nil {
+			continue
+		}
+		infos = append(infos, Info{Serial: serial})
+	}
+
+	return infos, nil
+}
+
+func OpenZedmon(config Config) (*Zedmon, error) {
 	zedmon := &Zedmon{
 		ctx: gousb.NewContext(),
 	}
 
-	dev, err := zedmon.ctx.OpenDeviceWithVIDPID(0x18d1, 0xaf00)
+	devs, err := zedmon.ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool {
+		return desc.Vendor == 0x18d1 && desc.Product == 0xaf00
+	})
 	if err != nil {
 		zedmon.Close()
 		return nil, err
 	}
 
+	if len(devs) == 0 {
+		return nil, errors.New("No zedmon device found")
+	}
+
+	var dev *gousb.Device
+	if config.Serial == nil {
+		dev = devs[0]
+	} else {
+
+		for _, d := range devs {
+			serial, err := d.SerialNumber()
+			if err != nil {
+				continue
+			}
+			if serial == *config.Serial {
+				dev = d
+				break
+			}
+		}
+		if dev == nil {
+			zedmon.Close()
+			return nil, fmt.Errorf("No zedmon device with serial %s found", *config.Serial)
+		}
+	}
+
 	configNum, intfNum, settingNum, err := findZedmonInterface(dev)
 	if err != nil {
 		zedmon.Close()
diff --git a/stub.go b/stub.go
index b5d62a4..f8727e5 100644
--- a/stub.go
+++ b/stub.go
@@ -32,6 +32,13 @@
 	"fuchsia.googlesource.com/zedmon/lib"
 )
 
-func OpenZedmon() (*lib.Zedmon, error) {
-	return lib.OpenZedmon()
+type Config = lib.Config
+type Info = lib.Info
+
+func Enumerate() ([]Info, error) {
+	return lib.Enumerate()
+}
+
+func OpenZedmon(config lib.Config) (*lib.Zedmon, error) {
+	return lib.OpenZedmon(config)
 }