Example #1
0
// Connect Builds a new ninja connection to the MQTT broker, using the given client ID
func Connect(clientID string) (*Connection, error) {

	log := logger.GetLogger(fmt.Sprintf("%s.connection", clientID))

	conn := Connection{
		log:      log,
		services: []model.ServiceAnnouncement{},
	}

	mqttURL := fmt.Sprintf("%s:%d", config.MustString("mqtt", "host"), config.MustInt("mqtt", "port"))

	log.Infof("Connecting to %s using cid:%s", mqttURL, clientID)

	conn.mqtt = bus.MustConnect(mqttURL, clientID)

	log.Infof("Connected")

	conn.rpc = rpc.NewClient(conn.mqtt, json2.NewClientCodec())
	conn.rpcServer = rpc.NewServer(conn.mqtt, json2.NewCodec())

	// Add service discovery service. Responds to queries about services exposed in this process.
	discoveryService := &discoverService{&conn}
	_, err := conn.exportService(discoveryService, "$discover", &simpleService{*discoveryService.GetServiceAnnouncement()})
	if err != nil {
		log.Fatalf("Could not expose discovery service: %s", err)
	}

	return &conn, nil
}
Example #2
0
func (c *client) exportNodeDevice() {

	if c.nodeDevice != nil {
		return
	}

	c.nodeDevice = &NodeDevice{info: ninja.LoadModuleInfo("./package.json")}

	// TODO: Make some generic way to see if homecloud is running.
	// XXX: Fix this. It's ugly.
	for {
		siteModel := c.conn.GetServiceClient("$home/services/SiteModel")
		err := siteModel.Call("fetch", config.MustString("siteId"), nil, time.Second*5)

		if err == nil {
			break
		}

		log.Infof("Failed to fetch siteid from sitemodel: %s", err)
		time.Sleep(time.Second * 5)
	}

	for {
		err := c.conn.ExportDevice(c.nodeDevice)
		if err == nil {
			break
		}

		log.Warningf("Failed to export node device. Retrying in 5 sec: %s", err)
		time.Sleep(time.Second * 5)
	}

}
Example #3
0
func (c *client) ensureTimezoneIsSet() error {

	siteModel := c.conn.GetServiceClient("$home/services/SiteModel")
	var site model.Site

	for {
		err := siteModel.Call("fetch", config.MustString("siteId"), &site, time.Second*5)

		if err == nil && site.TimeZoneID != nil {

			log.Infof("Saving timezone: %s", *site.TimeZoneID)

			cmd := exec.Command("with-rw", "ln", "-s", "-f", "/usr/share/zoneinfo/"+*site.TimeZoneID, "/etc/localtime")
			_, err := cmd.Output()

			if err != nil {
				return err
			}

			break
		}
		time.Sleep(time.Second * 2)
	}

	return nil

}
Example #4
0
func (c *client) pair() error {

	var boardType string
	if config.HasString("boardType") {
		boardType = config.MustString("boardType")
	} else {
		boardType = fmt.Sprintf("custom-%s-%s", runtime.GOOS, runtime.GOARCH)
	}

	log.Debugf("Board type: %s", boardType)

	client := &http.Client{
		Timeout: time.Second * 60, // It's 20sec on the server so this *should* be ok
	}

	if config.Bool(false, "cloud", "allowSelfSigned") {
		log.Warningf("Allowing self-signed certificate (should only be used to connect to development cloud)")
		client.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}

	var creds *credentials

	for {
		url := fmt.Sprintf(config.MustString("cloud", "activation"), config.Serial(), getLocalIP(), boardType)

		log.Debugf("Activating at URL: %s", url)

		var err error
		creds, err = activate(client, url)

		if err != nil {
			log.Warningf("Activation error : %s", err)
			log.Warningf("Sleeping for 3sec")
			time.Sleep(time.Second * 3)
		} else if creds != nil {
			break
		}
	}

	log.Infof("Got credentials. User: %s", creds.UserID)

	return saveCreds(creds)
}
func (m *SiteModel) Delete(id string, conn redis.Conn) error {
	m.syncing.Wait()
	//defer m.sync()

	if id == "here" {
		id = config.MustString("siteId")
	}

	return m.delete(id, conn)
}
func (p *WeatherPane) GetWeather() {

	enableWeatherPane = false

	for {
		site := &model.Site{}
		err := p.siteModel.Call("fetch", config.MustString("siteId"), site, time.Second*5)

		if err == nil && (site.Longitude != nil || site.Latitude != nil) {
			p.site = site
			globalSite = site

			if site.TimeZoneID != nil {
				if timezone, err = time.LoadLocation(*site.TimeZoneID); err != nil {
					log.Warningf("error while setting timezone (%s): %s", *site.TimeZoneID, err)
					timezone, _ = time.LoadLocation("Local")
				}
			}
			break
		}

		log.Infof("Failed to get site, or site has no location.")

		time.Sleep(time.Second * 2)
	}

	for {

		p.weather.DailyByCoordinates(
			&owm.Coordinates{
				Longitude: *p.site.Longitude,
				Latitude:  *p.site.Latitude,
			},
			1,
		)

		if len(p.weather.List) > 0 {

			filename := util.ResolveImagePath("weather/" + p.weather.List[0].Weather[0].Icon + ".png")

			if _, err := os.Stat(filename); os.IsNotExist(err) {
				enableWeatherPane = false
				fmt.Printf("Couldn't load image for weather: %s", filename)
				bugsnag.Notify(fmt.Errorf("Unknown weather icon: %s", filename), p.weather)
			} else {
				p.image = util.LoadImage(filename)
				enableWeatherPane = true
			}
		}

		time.Sleep(weatherUpdateInterval)

	}

}
func NewLedController(conn *ninja.Connection) (*LedController, error) {

	s, err := util.GetLEDConnection()

	if err != nil {
		log.Fatalf("Failed to get connection to LED matrix: %s", err)
	}

	// Send a blank image to the led matrix
	util.WriteLEDMatrix(image.NewRGBA(image.Rect(0, 0, 16, 16)), s)

	controller := &LedController{
		conn:          conn,
		pairingLayout: ui.NewPairingLayout(),
		serial:        s,
		waiting:       make(chan bool),
	}

	conn.MustExportService(controller, "$node/"+config.Serial()+"/led-controller", &model.ServiceAnnouncement{
		Schema: "/service/led-controller",
	})

	conn.MustExportService(controller, "$home/led-controller", &model.ServiceAnnouncement{
		Schema: "/service/led-controller",
	})

	if config.HasString("siteId") {
		log.Infof("Have a siteId, checking if homecloud is running")
		// If we have just started, and we have a site, and homecloud is running... enable control!
		go func() {
			siteModel := conn.GetServiceClient("$home/services/SiteModel")
			for {

				if controller.commandReceived {
					log.Infof("Command has been received, stopping search for homecloud.")
					break
				}

				err := siteModel.Call("fetch", config.MustString("siteId"), nil, time.Second*5)

				if err != nil {
					log.Infof("Fetched site to enableControl. Got err: %s", err)
				} else if err == nil && !controller.commandReceived {
					controller.EnableControl()
					break
				}
				time.Sleep(time.Second * 5)
			}
		}()
	}

	return controller, nil
}
Example #8
0
func getNodes() (map[string]Node, error) {
	var data []Node
	err := req(config.MustString("cloud", "nodes"), &data)
	log.Debugf("Fetched nodes: %+v", data)

	m := make(map[string]Node)
	for _, n := range data {
		m[n.ID] = n
	}

	return m, err
}
Example #9
0
func getSites() (map[string]Site, error) {
	var data []Site
	err := req(config.MustString("cloud", "sites"), &data)
	log.Debugf("Fetched sites: %+v", data)

	m := make(map[string]Site)
	for _, s := range data {
		m[s.ID] = s
	}

	return m, err
}
Example #10
0
// bridgeMqtt connects one mqtt broker to another. Shouldn't probably be doing this. But whatever.
func (c *client) bridgeMqtt(from, to bus.Bus, masterToSlave bool, topics []string) {

	onMessage := func(topic string, payload []byte) {

		if masterToSlave {
			// This is a message from master, clear the timeout
			c.masterReceiveTimeout.Reset(orphanTimeout)
		}

		if payload[0] != '{' {
			log.Warningf("Invalid payload (should be a json-rpc object): %s", payload)
			return
		}

		var msg meshMessage
		json.Unmarshal(payload, &msg)

		interesting := false

		if masterToSlave {
			// Interesting if it's from the master or one of the other slaves
			interesting = msg.Source == nil || (*msg.Source != config.Serial())
		} else {
			// Interesting if it's from me
			interesting = msg.Source == nil
		}

		log.Infof("Mesh master2slave:%t topic:%s interesting:%t", masterToSlave, topic, interesting)

		if interesting {

			if msg.Source == nil {
				if masterToSlave {
					payload = addMeshSource(config.MustString("masterNodeId"), payload)
				} else {
					payload = addMeshSource(config.Serial(), payload)
				}
			}

			to.Publish(topic, payload)
		}

	}

	for _, topic := range topics {
		_, err := from.Subscribe(topic, onMessage)
		if err != nil {
			log.Fatalf("Failed to subscribe to topic %s when bridging to master: %s", topic, err)
		}
	}

}
Example #11
0
func NewConfigService(scheduler *service.SchedulerService, conn *ninja.Connection) *ConfigService {
	siteID := config.MustString("siteId")
	service := &ConfigService{
		scheduler:  scheduler,
		thingModel: conn.GetServiceClient("$home/services/ThingModel"),
		roomModel:  conn.GetServiceClient("$home/services/RoomModel"),
		siteModel:  conn.GetServiceClient("$home/services/SiteModel"),
		presets:    conn.GetServiceClient(fmt.Sprintf("$site/%s/service/presets", siteID)),
		rooms:      make(map[string]*nmodel.Room),
		sites:      make(map[string]*nmodel.Site),
	}
	return service
}
Example #12
0
func (a *presetsAction) actuate(ctx *actuationContext) error {
	siteID := config.MustString("siteId")
	topic := fmt.Sprintf("$site/%s/service/%s", siteID, "presets")
	client := ctx.conn.GetServiceClient(topic)
	id := a.getModel().GetSceneID()
	if id != "" {
		params := []string{id}
		reply := &struct{}{}
		return client.Call(a.model.Action, params, reply, ctx.timeout)
	} else {
		return fmt.Errorf("The scene id for presets action was empty. The actuation did nothing.")
	}
}
Example #13
0
func (c *client) onBridgeStatus(status *bridgeStatus) bool {
	log.Debugf("Got bridge status. connected:%t configured:%t", status.Connected, status.Configured)

	if status.Connected {
		c.updatePairingLight("green", false)
	} else {
		if c.master {
			c.updatePairingLight("red", true)
		} else {
			c.updatePairingLight("blue", false)
		}
	}

	if !status.Configured && c.master {
		log.Infof("Configuring bridge")

		c.conn.PublishRawSingleValue("$sphere/bridge/connect", map[string]string{
			"url":   config.MustString("cloud", "url"),
			"token": config.MustString("token"),
		})
	}

	return true
}
Example #14
0
func (m *SiteModel) Fetch(id string, conn redis.Conn) (*model.Site, error) {
	m.syncing.Wait()

	if id == "here" {
		id = config.MustString("siteId")
	}

	site := &model.Site{}

	if err := m.fetch(id, site, false, conn); err != nil {
		return nil, err
	}

	return site, nil
}
Example #15
0
func (c *HomeCloud) ensureSiteExists() {
	conn := c.Pool.Get()
	defer conn.Close()

	site, err := c.SiteModel.Fetch(config.MustString("siteId"), conn)
	if err != nil && err != models.RecordNotFound {
		log.Fatalf("Failed to get site: %s", err)
	}

	if err == models.RecordNotFound {
		siteType := "home"
		name := "Home"
		site = &model.Site{
			Name: &name,
			ID:   config.MustString("siteId"),
			Type: &siteType,
		}
		err = c.SiteModel.Create(site, conn)
		if err != nil && err != models.RecordNotFound {
			log.Fatalf("Failed to create site: %s", err)
		}
	}

}
Example #16
0
func (c *client) bridgeToMaster(host net.IP, port int) {

	log.Debugf("Bridging to the master: %s:%d", host, port)

	mqttURL := fmt.Sprintf("%s:%d", host, port)

	clientID := "slave-" + config.Serial()

	log.Infof("Connecting to master %s using cid:%s", mqttURL, clientID)

	c.masterBus = bus.MustConnect(mqttURL, clientID)
	c.localBus = bus.MustConnect(fmt.Sprintf("%s:%d", config.MustString("mqtt.host"), config.MustInt("mqtt.port")), "meshing")

	log.Infof("Connected to master? %t", c.masterBus.Connected())

	if c.masterBus.Connected() {
		c.setUnorphaned()
	} else {
		c.setOrphaned()
	}

	c.masterBus.OnDisconnect(func() {
		log.Infof("Disconnected from master")
		go func() {
			time.Sleep(time.Second * 5)
			if !c.masterBus.Connected() {
				log.Infof("Still disconnected from master, setting orphaned.")
				c.setOrphaned()
			}
		}()
	})

	c.masterBus.OnConnect(func() {
		log.Infof("Connected to master")
		go func() {
			time.Sleep(time.Second * 2)
			if !c.masterBus.Connected() {
				log.Infof("Still connected to master, setting unorphaned")
				c.setUnorphaned()
			}
		}()
	})

	bridgeTopics := []string{"$discover", "$site/#", "$home/#" /*deprecated*/, "$node/#", "$thing/#", "$device/#"}

	c.bridgeMqtt(c.masterBus, c.localBus, true, bridgeTopics)
	c.bridgeMqtt(c.localBus, c.masterBus, false, bridgeTopics)
}
Example #17
0
func (m *SiteModel) Create(site *model.Site, conn redis.Conn) error {
	m.syncing.Wait()
	//defer m.sync()

	if site.ID == "here" {
		site.ID = config.MustString("siteId")
	}

	m.log.Debugf("Saving site %s", site.ID)

	updated, err := m.save(site.ID, site, conn)

	m.log.Debugf("Site was updated? %t", updated)

	return err
}
Example #18
0
func req(url string, data interface{}) error {

	client := &http.Client{
		Timeout: time.Second * 30,
	}

	if config.Bool(false, "cloud", "allowSelfSigned") {
		log.Warningf("Allowing self-signed cerificate (should only be used to connect to development cloud)")
		client.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}

	resp, err := client.Get(fmt.Sprintf(url, config.MustString("token")))
	if err != nil {
		return err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var response restResponse

	err = json.Unmarshal(body, &response)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK || response.Type == "error" {
		var data restError
		err = json.Unmarshal(response.Data, &data)

		if data.Type == "authentication_invalid_token" {
			return errorUnauthorised
		}

		if err != nil {
			return err
		}

		return fmt.Errorf("Error from cloud: %s (%s)", data.Message, data.Type)
	}

	return json.Unmarshal(response.Data, data)
}
Example #19
0
// update the site-preferences.json file with a copy read from the site model
func listenToSiteUpdates(conn *ninja.Connection) {
	configSiteId := config.MustString("siteId")
	siteModel := conn.GetServiceClient("$home/services/SiteModel")
	siteModel.OnEvent("updated", func(siteId *string, values map[string]string) bool {
		if siteId != nil && configSiteId == *siteId {
			err := updateSitePreferences(siteModel, *siteId)
			if err != nil {
				log.Debugf("error ignored while updating site preferences: %v", err)
			}
		}
		return true
	})
	err := updateSitePreferences(siteModel, configSiteId)
	if err != nil {
		log.Debugf("error ignored while updating site preferences: %v", err)
	}
}
func (m *SiteModel) Update(id string, site *model.Site, conn redis.Conn) error {
	m.syncing.Wait()
	//defer m.sync()

	if id == "here" {
		id = config.MustString("siteId")
	}

	oldSite := &model.Site{}

	if err := m.fetch(id, oldSite, false, conn); err != nil {
		return fmt.Errorf("Failed to fetch site (id:%s): %s", id, err)
	}

	oldSite.Name = site.Name
	oldSite.Type = site.Type
	oldSite.SitePreferences = site.SitePreferences
	oldSite.DefaultRoomID = site.DefaultRoomID

	if site.Latitude != nil &&
		site.Longitude != nil &&
		((oldSite.Latitude == nil || oldSite.Longitude == nil) ||
			(*oldSite.Latitude != *site.Latitude || *oldSite.Longitude != *site.Longitude)) {
		oldSite.Latitude = site.Latitude
		oldSite.Longitude = site.Longitude

		tz, err := getTimezone(*site.Latitude, *site.Longitude)
		if err != nil {
			return fmt.Errorf("Failed to get timezone: %s", err)
		} else {
			m.log.Debugf("Timezone (%0.4f, %0.4f) -> %v", *site.Latitude, *site.Longitude, tz)
		}

		oldSite.TimeZoneID = tz.TimeZoneID
		oldSite.TimeZoneName = tz.TimeZoneName
		oldSite.TimeZoneOffset = tz.RawOffset // TODO: Not handling DST. Worth even having?
	} else {
		m.log.Debugf("no change to latitude or longitude")
	}

	if _, err := m.save(id, oldSite, conn); err != nil {
		return fmt.Errorf("Failed to update site (id:%s): %s", id, err)
	}

	return nil
}
func getSiteLocation() {
	siteModel := conn.GetServiceClient("$home/services/SiteModel")
	for {

		var site model.Site

		err := siteModel.Call("fetch", config.MustString("siteId"), &site, time.Second*5)

		if err == nil && site.Latitude != nil {
			latitude, longitude = *site.Latitude, *site.Longitude
			break
		}

		log.Infof("Failed to fetch site latitude/longitude: err:%s", err)
		time.Sleep(time.Second * 5)
	}
}
Example #22
0
func (s *SchedulerService) Init(moduleID string) error {
	siteID := config.MustString("siteId")

	s.Scheduler = &controller.Scheduler{}
	s.Scheduler.SetLogger(s.Log)
	s.Scheduler.SetSiteID(siteID)
	s.Scheduler.SetConfigStore(s.ConfigStore)
	s.Scheduler.SetConnection(s.Conn, time.Millisecond*time.Duration(config.Int(10000, "scheduler", "timeout")))

	var err error
	topic := fmt.Sprintf("$site/%s/service/%s", siteID, "scheduler")
	announcement := &nmodel.ServiceAnnouncement{
		Schema: "http://schema.ninjablocks.com/service/scheduler",
	}
	if s.Service, err = s.Conn.ExportService(s, topic, announcement); err != nil {
		return err
	}
	if err := s.Scheduler.Start(s.Model); err != nil {
		return err
	}
	return nil
}
Example #23
0
	"reflect"
	"strings"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/gojsonschema"
	"github.com/xeipuuv/gojsonreference"
)

var log = logger.GetLogger("schemas")

var root = "http://schema.ninjablocks.com/"
var rootURL, _ = url.Parse(root)
var filePrefix = config.MustString("installDirectory") + "/sphere-schemas/"
var fileSuffix = ".json"

var schemaPool = gojsonschema.NewSchemaPool()
var validationEnabled = config.Bool(false, "validate")

func init() {
	schemaPool.FilePrefix = &filePrefix
	schemaPool.FileSuffix = &fileSuffix

	if validationEnabled {
		log.Infof("-------- VALIDATION ENABLED --------")
	}
}

func Validate(schema string, obj interface{}) (*string, error) {
	volumeDevices  []*ninja.ServiceClient
}

type MediaPaneImages struct {
	Volume     string
	VolumeUp   string
	VolumeDown string
	Mute       string
	Play       string
	Pause      string
	Stop       string
	Next       string
}

var mediaImages = MediaPaneImages{
	Volume:     util.ResolveImagePath(config.MustString("led.media.images.volume")),
	VolumeUp:   util.ResolveImagePath(config.MustString("led.media.images.volumeUp")),
	VolumeDown: util.ResolveImagePath(config.MustString("led.media.images.volumeDown")),
	Mute:       util.ResolveImagePath(config.MustString("led.media.images.mute")),
	Play:       util.ResolveImagePath(config.MustString("led.media.images.play")),
	Pause:      util.ResolveImagePath(config.MustString("led.media.images.pause")),
	Stop:       util.ResolveImagePath(config.MustString("led.media.images.stop")),
	Next:       util.ResolveImagePath(config.MustString("led.media.images.next")),
}

func NewMediaPane(conn *ninja.Connection) *MediaPane {
	log := logger.GetLogger("MediaPane")

	pane := &MediaPane{
		log:         log,
		conn:        conn,
func (m *TimeSeriesManager) Start() error {

	m.log.Infof("Starting")

	m.Conn.GetMqttClient().Subscribe("$device/+/channel/+/event/state", func(topic string, message []byte) {

		x, _ := ninja.MatchTopicPattern("$device/:device/channel/:channel/event/state", topic)
		values := *x

		thing, inCache := thingsByDeviceId[values["device"]]

		if !inCache {

			conn := m.Pool.Get()
			defer conn.Close()

			var err error
			thing, err = m.ThingModel.FetchByDeviceId(values["device"], conn)
			if err != nil {
				log.Errorf("Got a state event, but failed to fetch thing for device: %s error: %s", values["device"], err)
				return
			}

			if thing == nil {
				return
			}

			thingsByDeviceId[values["device"]] = thing
		}

		channel, inCache := channels[values["device"]+values["channel"]]

		if !inCache {
			conn := m.Pool.Get()
			defer conn.Close()

			var err error
			channel, err = m.ChannelModel.Fetch(values["device"], values["channel"], conn)

			if err != nil {
				log.Errorf("Got a state event, but failed to fetch channel: %s on device: %s error: %s", values["channel"], values["device"], err)
				return
			}

			channels[values["device"]+values["channel"]] = channel
		}

		var data map[string]interface{}

		err := json.Unmarshal(message, &data)

		params := data["params"]
		if paramsArray, ok := data["params"].([]interface{}); ok {
			params = paramsArray[0]
		}

		if err != nil {
			log.Errorf("Got a state event, but failed to unmarshal it. channel: %s on device: %s error: %s", values["channel"], values["device"], err)
			return
		}

		log.Debugf("Got state event from device:%s channel:%s payload:%v", values["device"], values["channel"], params)

		points, err := schemas.GetEventTimeSeriesData(params, channel.Schema, "state")
		if err != nil {
			log.Errorf("Got a state event, but failed to create time series points. channel: %s on device: %s error: %s", values["channel"], values["device"], err)
			return
		}

		if len(points) > 0 {

			payload := &model.TimeSeriesPayload{
				Thing:     thing.ID,
				ThingType: thing.Type,
				Promoted:  thing.Promoted,
				Device:    values["device"],
				Channel:   values["channel"],
				Schema:    channel.Schema,
				Event:     "state",
				Points:    points,
				Site:      config.MustString("siteId"),
				Time:      time.Now().Format(time.RFC3339Nano),
			}

			/*if user, ok := data["_userOverride"].(string); ok {
				payload.UserOverride = user
			}

			if node, ok := data["_nodeOverride"].(string); ok {
				payload.NodeOverride = node
			}

			if site, ok := data["_siteOverride"].(string); ok {
				payload.SiteOverride = site
			}*/

			payload.TimeZone, payload.TimeOffset = time.Now().Zone()

			err = m.Conn.SendNotification("$ninja/services/timeseries", payload)
			if err != nil {
				log.Fatalf("Got a state event, but failed to send time series points. channel: %s on device: %s error: %s", values["channel"], values["device"], err)
			}

		}

	})

	return nil
}
Example #26
0
func (c *client) start() {

	if !config.IsPaired() {
		log.Infof("Client is unpaired. Attempting to pair.")
		if err := c.pair(); err != nil {
			log.Fatalf("An error occurred while pairing. Restarting. error: %s", err)
		}

		log.Infof("Pairing was successful.")
		// We reload the config so the creds can be picked up
		config.MustRefresh()

		if !config.IsPaired() {
			log.Fatalf("Pairing appeared successful, but I did not get the credentials. Restarting.")
		}

	}

	log.Infof("Client is paired. User: %s", config.MustString("userId"))

	if !config.NoCloud() {

		mesh, err := refreshMeshInfo()

		if err == errorUnauthorised {
			log.Warningf("UNAUTHORISED! Unpairing.")
			c.unpair()
			return
		}

		if err != nil {
			log.Warningf("Failed to refresh mesh info: %s", err)
		} else {
			log.Debugf("Got mesh info: %+v", mesh)
		}

		config.MustRefresh()

		if !config.HasString("masterNodeId") {
			log.Warningf("We don't have any mesh information. Which is unlikely. But we can't do anything without it, so restarting client.")
			time.Sleep(time.Second * 10)
			os.Exit(0)
		}

	}

	if config.MustString("masterNodeId") == config.Serial() {
		log.Infof("I am the master, starting HomeCloud.")

		cmd := exec.Command("start", "sphere-homecloud")
		cmd.Output()
		go c.exportNodeDevice()

		c.master = true
	} else {
		log.Infof("I am a slave. The master is %s", config.MustString("masterNodeId"))

		// TODO: Remove this when we are running drivers on slaves
		cmd := exec.Command("stop", "sphere-director")
		cmd.Output()

		c.masterReceiveTimeout = time.AfterFunc(orphanTimeout, func() {
			c.setOrphaned()
		})

	}

	go func() {

		log.Infof("Starting search for peers")

		for {
			c.findPeers()
			time.Sleep(time.Second * 30)
		}
	}()
}
Example #27
0
func (c *client) findPeers() {

	query := "_ninja-homecloud-mqtt._tcp"

	// Make a channel for results and start listening
	entriesCh := make(chan *mdns.ServiceEntry, 4)
	go func() {
		for entry := range entriesCh {

			if !strings.Contains(entry.Name, query) {
				continue
			}
			nodeInfo := parseMdnsInfo(entry.Info)

			id, ok := nodeInfo["ninja.sphere.node_id"]

			if !ok {
				log.Warningf("Found a node, but couldn't get it's node id. %v", entry)
				continue
			}

			if id == config.Serial() {
				// It's me.
				continue
			}

			user, ok := nodeInfo["ninja.sphere.user_id"]
			if !ok {
				log.Warningf("Found a node, but couldn't get it's user id. %v", entry)
				continue
			}

			site, ok := nodeInfo["ninja.sphere.site_id"]
			siteUpdated, ok := nodeInfo["ninja.sphere.site_updated"]
			masterNodeID, ok := nodeInfo["ninja.sphere.master_node_id"]

			if user == config.MustString("userId") {

				if site == config.MustString("siteId") {
					log.Infof("Found a sibling node (%s) - %s", id, entry.Addr)

					siteUpdatedInt, err := strconv.ParseInt(siteUpdated, 10, 64)

					if err != nil {
						log.Warningf("Failed to read the site_updated field (%s) on node %s - %s", siteUpdated, id, entry.Addr)
					} else {
						if int(siteUpdatedInt) > config.MustInt("siteUpdated") {

							log.Infof("Found node (%s - %s) with a newer site update time (%s).", id, entry.Addr, siteUpdated)

							info := &meshInfo{
								MasterNodeID: masterNodeID,
								SiteID:       config.MustString("siteId"),
								SiteUpdated:  int(siteUpdatedInt),
							}

							err := saveMeshInfo(info)
							if err != nil {
								log.Warningf("Failed to save updated mesh info from node: %s - %+v", err, info)
							}

							if masterNodeID == config.MustString("masterNodeId") {
								log.Infof("Updated master id is the same (%s). Moving on with our lives.", masterNodeID)
							} else {
								log.Infof("Master id has changed (was %s now %s). Rebooting", config.MustString("masterNodeId"), masterNodeID)

								reboot()
								return
							}
						}
					}

				} else {
					log.Warningf("Found a node owned by the same user (%s) but from a different site (%s) - ID:%s - %s", user, site, id, entry.Addr)
				}

			} else {
				log.Infof("Found a node owned by another user (%s) (%s) - %s", user, id, entry.Addr)
			}

			if id == config.MustString("masterNodeId") {
				log.Infof("Found the master node (%s) - %s", id, entry.Addr)

				select {
				case c.foundMaster <- true:
				default:
				}

				if !c.bridged {
					c.bridgeToMaster(entry.Addr, entry.Port)
					c.bridged = true
					c.exportNodeDevice()
				}
			}

		}
	}()

	// Start the lookup
	mdns.Lookup(query, entriesCh)
	close(entriesCh)
}