func Connect() {
	syncRequired := true
	for {
		if err := ReportAPInfo(syncRequired); err != nil {
			log.Errorf("Cannot connect to the controller, retry in %s. Error: %v.", heartbeatInterval, err)
			syncRequired = true
		} else if syncRequired {
			log.Info("Controller connected and received the sync request.")
			syncRequired = false
		}
		time.Sleep(heartbeatInterval)
	}
}
Connect sends heartbeat messages to the assigned controller periodically.
The first (received) message contains a sync request to fetch the latest AP configuration.
This function never returns, should be run in the background.
package controller
import (
	"errors"
	"time"
	devctx "github.com/google/link022/agent/context"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	log "github.com/golang/glog"
	pb "github.com/openconfig/gnmi/proto/gnmi"
)
const (
	hostnameHeader       = "hostname"
	gnmiAddrHeader       = "gnmi_address"
	resyncRequiredHeader = "resync_required"
	heartbeatInterval = 15 * time.Second
)
func ReportAPInfo(syncRequried bool) error {
	deviceConfig := devctx.GetDeviceConfig()
	if deviceConfig.ControllerAddr == "" {
		return errors.New("no controller assigned, unable to report AP information")
	}
	conn, err := grpc.Dial(deviceConfig.ControllerAddr, grpc.WithInsecure())
	if err != nil {
		return err
	}
	defer conn.Close()
	getRequest := &pb.GetRequest{
		Path: []*pb.Path{},
	}
	headerMap := map[string]string{
		hostnameHeader: deviceConfig.Hostname,
		gnmiAddrHeader: deviceConfig.GNMIServerAddr,
	}
	if syncRequried {
		headerMap[resyncRequiredHeader] = "True"
	}
	ctx := metadata.NewOutgoingContext(context.Background(), metadata.New(headerMap))
	cli := pb.NewGNMIClient(conn)
	if _, err := cli.Get(ctx, getRequest); err != nil {
		return err
	}
	return nil
}