// Process PollNotification task func (service *Service) processPollNotification(task Task) (notifications []core.Notification, err error) { // check task error first if task.err != nil { err = task.err return } // check status code if task.response.StatusCode != http.StatusOK { log.Warnf("REST: unexpected /notification/poll status %s", task.response.Status) err = fmt.Errorf("unexpected status: %s", task.response.Status) return } // unmarshal err = json.Unmarshal(task.body, ¬ifications) if err != nil { log.Warnf("REST: failed to parse /notification/poll body (error: %s)", err) return } return }
// CommandUpdate() function updates the command. func (service *Service) UpdateCommand(device *core.Device, command *core.Command, timeout time.Duration) (err error) { task, err := service.prepareUpdateCommand(device, command) if err != nil { log.Warnf("WS: failed to prepare /command/update task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /command/update task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processUpdateCommand(task) if err != nil { log.Warnf("WS: failed to process /command/update task (error: %s)", err) return } } return }
// InsertNotification() function inserts the notification. func (service *Service) InsertNotification(device *core.Device, notification *core.Notification, timeout time.Duration) (err error) { task, err := service.prepareInsertNotification(device, notification) if err != nil { log.Warnf("WS: failed to prepare /notification/insert task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /notification/insert task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processInsertNotification(task, notification) if err != nil { log.Warnf("WS: failed to process /notification/insert task (error: %s)", err) return } } return }
// Prepare InsertNotification task func (service *Service) prepareInsertNotification(device *core.Device, notification *core.Notification) (task Task, err error) { // create request url := fmt.Sprintf("%s/device/%s/notification", service.baseUrl, device.Id) // do not put some fields to the request body notification = &core.Notification{Name: notification.Name, Parameters: notification.Parameters} body, err := json.Marshal(notification) if err != nil { log.Warnf("REST: failed to format /notification/insert request (error: %s)", err) return } task.request, err = http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { log.Warnf("REST: failed to create /notification/insert request (error: %s)", err) return } task.request.Header.Add("Content-Type", "application/json") // authorization service.prepareAuthorization(task.request, device) return }
// GetNetwork() function get the network data. func (service *Service) GetNetwork(networkId uint64, timeout time.Duration) (network *core.Network, err error) { log.Tracef("REST: getting network %d...", networkId) task, err := service.prepareGetNetwork(networkId) if err != nil { log.Warnf("REST: failed to prepare /network/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /network/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): network = &core.Network{Id: networkId} err = service.processGetNetwork(task, network) if err != nil { log.Warnf("REST: failed to process /network/get task (error: %s)", err) return } } return }
// GetNotification() function get the notification data. func (service *Service) GetNotification(device *core.Device, notificationId uint64, timeout time.Duration) (notification *core.Notification, err error) { log.Tracef("REST: getting notification %q/%d...", device.Id, notificationId) task, err := service.prepareGetNotification(device, notificationId) if err != nil { log.Warnf("REST: failed to prepare /notification/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /notification/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): notification = &core.Notification{Id: notificationId} err = service.processGetNotification(task, notification) if err != nil { log.Warnf("REST: failed to process /notification/get task (error: %s)", err) return } } return }
// GetServerInfo() function gets the main server's information. func (service *Service) GetServerInfo(timeout time.Duration) (info *core.ServerInfo, err error) { task, err := service.prepareGetServerInfo() if err != nil { log.Warnf("WS: failed to prepare /info task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /info task", timeout) err = fmt.Errorf("timed out") case <-task.done: info = &core.ServerInfo{} err = service.processGetServerInfo(task, info) if err != nil { log.Warnf("WS: failed to process /info task (error: %s)", err) return } } return }
// UnsubscribeCommand() function updates the command. func (service *Service) UnsubscribeCommands(device *core.Device, timeout time.Duration) (err error) { task, err := service.prepareUnsubscribeCommand(device) if err != nil { log.Warnf("WS: failed to prepare /command/unsubscribe task (error: %s)", err) return } service.removeCommandListener(device.Id) // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /command/unsubscribe task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processUnsubscribeCommand(task) if err != nil { log.Warnf("WS: failed to process /command/unsubscribe task (error: %s)", err) return } } return }
// Process InsertNetwork task func (service *Service) processInsertNetwork(task Task, network *core.Network) (err error) { // check task error first if task.err != nil { err = task.err return } // check status code if task.response.StatusCode < http.StatusOK || task.response.StatusCode > http.StatusPartialContent { log.Warnf("REST: unexpected /network/insert status %s", task.response.Status) err = fmt.Errorf("unexpected status: %s", task.response.Status) return } // unmarshal err = json.Unmarshal(task.body, network) if err != nil { log.Warnf("REST: failed to parse /network/insert body (error: %s)", err) return } return }
// GetDevice() function get the device data. func (service *Service) GetDevice(deviceId, deviceKey string, timeout time.Duration) (device *core.Device, err error) { log.Tracef("REST: getting device %q...", deviceId) task, err := service.prepareGetDevice(deviceId, deviceKey) if err != nil { log.Warnf("REST: failed to prepare /device/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /device/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): device = &core.Device{Id: deviceId, Key: deviceKey} err = service.processGetDevice(task, device) if err != nil { log.Warnf("REST: failed to process /device/get task (error: %s)", err) return } } return }
// RegisterDevice() function registers the device. func (service *Service) RegisterDevice(device *core.Device, timeout time.Duration) (err error) { task, err := service.prepareRegisterDevice(device) if err != nil { log.Warnf("WS: failed to prepare /device/register task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /device/register task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processRegisterDevice(task) if err != nil { log.Warnf("WS: failed to process /device/register task (error: %s)", err) return } } return }
// Process GetCommand task func (service *Service) processGetCommand(task Task, command *core.Command) (err error) { // check task error first if task.err != nil { err = task.err return } // check status code if task.response.StatusCode != http.StatusOK { log.Warnf("REST: unexpected /command/get status %s", task.response.Status) err = fmt.Errorf("unexpected status: %s", task.response.Status) return } // unmarshal err = json.Unmarshal(task.body, command) if err != nil { log.Warnf("REST: failed to parse /command/get body (error: %s)", err) return } return }
// Process GetDeviceList task func (service *Service) processGetDeviceList(task Task) (devices []core.Device, err error) { // check task error first if task.err != nil { err = task.err return } // check status code if task.response.StatusCode != http.StatusOK { log.Warnf("REST: unexpected /device/list status %s", task.response.Status) err = fmt.Errorf("unexpected status: %s", task.response.Status) return } // unmarshal err = json.Unmarshal(task.body, &devices) if err != nil { log.Warnf("REST: failed to parse /device/list body (error: %s)", err) return } return }
// Prepare InsertCommand task func (service *Service) prepareInsertCommand(device *core.Device, command *core.Command) (task Task, err error) { // create request url := fmt.Sprintf("%s/device/%s/command", service.baseUrl, device.Id) // do not put some fields to the request body command = &core.Command{Name: command.Name, Parameters: command.Parameters, Lifetime: command.Lifetime} body, err := json.Marshal(command) if err != nil { log.Warnf("REST: failed to format /command/insert request (error: %s)", err) return } task.request, err = http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { log.Warnf("REST: failed to create /command/insert request (error: %s)", err) return } task.request.Header.Add("Content-Type", "application/json") // authorization service.prepareAuthorization(task.request, device) return }
// GetCommand() function get the command data. func (service *Service) GetCommand(device *core.Device, commandId uint64, timeout time.Duration) (command *core.Command, err error) { log.Debugf("REST: getting command %q/%d...", device.Id, commandId) task, err := service.prepareGetCommand(device, commandId) if err != nil { log.Warnf("REST: failed to prepare /command/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /command/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): command = &core.Command{Id: commandId} err = service.processGetCommand(task, command) if err != nil { log.Warnf("REST: failed to process /command/get task (error: %s)", err) return } } return }
// Do a request/task asynchronously func (service *Service) doAsync(task Task) <-chan Task { ch := make(chan Task, 1) go func() { defer func() { ch <- task }() log.Tracef("REST: sending: %+v", task.request) task.response, task.err = service.client.Do(task.request) if task.err != nil { log.Warnf("REST: failed to do %s %s request (error: %s)", task.request.Method, task.request.URL, task.err) return } log.Tracef("REST: got %s %s response: %+v", task.request.Method, task.request.URL, task.response) // read body defer task.response.Body.Close() task.body, task.err = ioutil.ReadAll(task.response.Body) if task.err != nil { log.Warnf("REST: failed to read %s %s response body (error: %s)", task.request.Method, task.request.URL, task.err) return } log.Debugf("REST: got %s %s body: %s", task.request.Method, task.request.URL, string(task.body)) }() return ch }
// TX thread func (service *Service) doTX() { for { select { case task, ok := <-service.tx: if !ok || task == nil { log.Infof("WS: TX thread stopped") service.conn.Close() // TODO: send Close frame? return } body, err := task.Format() if err != nil { log.Warnf("WS: failed to format message (error: %s)", err) continue // TODO: return? } log.Tracef("WS: sending message: %s", string(body)) err = service.conn.WriteMessage(websocket.TextMessage, body) if err != nil { log.Warnf("WS: failed to send message (error: %s)", err) continue // TODO: return? } // case <-service.pingTimer.C: // if err := c.write(websocket.PingMessage, []byte{}); err != nil { // log.Warnf("WS: could not write ping message (error: %s)", err) // return // } } } }
// GetDevice() function gets the device information. func (service *Service) GetDevice(deviceId, deviceKey string, timeout time.Duration) (device *core.Device, err error) { device = &core.Device{Id: deviceId, Key: deviceKey} task, err := service.prepareGetDevice(device) if err != nil { log.Warnf("WS: failed to prepare /device/get task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /device/get task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processGetDevice(task, device) if err != nil { log.Warnf("WS: failed to process /device/get task (error: %s)", err) return } } return }
// GetServerInfo() function gets the main server's information. func (service *Service) GetServerInfo(timeout time.Duration) (info *core.ServerInfo, err error) { log.Tracef("REST: getting server info...") task, err := service.prepareGetServerInfo() if err != nil { log.Warnf("REST: failed to prepare /info task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /info task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): info = &core.ServerInfo{} err = service.processGetServerInfo(task, info) if err != nil { log.Warnf("REST: failed to process /info task (error: %s)", err) return } } return }
// SubscribeCommand() function updates the command. func (service *Service) SubscribeCommands(device *core.Device, timestamp string, timeout time.Duration) (listener *core.CommandListener, err error) { task, err := service.prepareSubscribeCommand(device, timestamp) if err != nil { log.Warnf("WS: failed to prepare /command/subscribe task (error: %s)", err) return } // add to the TX pipeline service.tx <- task select { case <-time.After(timeout): log.Warnf("WS: failed to wait %s for /command/subscribe task", timeout) err = fmt.Errorf("timed out") case <-task.done: err = service.processSubscribeCommand(task) if err != nil { log.Warnf("WS: failed to process /command/subscribe task (error: %s)", err) return } // done, create listener listener = core.NewCommandListener() service.insertCommandListener(device.Id, listener) } return }
// main loop func mainLoop(bus *dbus.Conn, service devicehive.Service, config conf.Conf) { // getting server info info, err := service.GetServerInfo(waitTimeout) if err != nil { log.Warnf("Cannot get service info (error: %s)", err) return } // registering device device := devicehive.NewDevice(config.DeviceID, config.DeviceName, devicehive.NewDeviceClass("go-gateway-class", "0.1")) device.Key = config.DeviceKey if len(config.NetworkName) != 0 || len(config.NetworkKey) != 0 { device.Network = devicehive.NewNetwork(config.NetworkName, config.NetworkKey) device.Network.Description = config.NetworkDesc } err = service.RegisterDevice(device, waitTimeout) if err != nil { log.Warnf("Cannot register device (error: %s)", err) return } // start polling commands listener, err := service.SubscribeCommands(device, info.Timestamp, waitTimeout) if err != nil { log.Warnf("Cannot subscribe commands (error: %s)") return } wrapper := DBusWrapper{service: service, device: device} exportDBusObject(bus, &wrapper) for { select { case cmd := <-listener.C: params := "" if cmd.Parameters != nil { buf, err := json.Marshal(cmd.Parameters) if err != nil { log.Warnf("Cannot generate JSON from parameters of command %+v (error: %s)", cmd, err) continue } params = string(buf) } log.Infof("COMMAND %s -> %s(%v)", config.URL, cmd.Name, params) bus.Emit(ComDevicehiveCloudPath, ComDevicehiveCloudIface+".CommandReceived", cmd.Id, cmd.Name, params) } //time.Sleep(5 * time.Second) } }
// Prepare GetDeviceList task func (service *Service) prepareGetDeviceList(take, skip int) (task Task, err error) { // create request query := url.Values{} if take > 0 { query.Set("take", fmt.Sprintf("%d", take)) } if skip > 0 { query.Set("skip", fmt.Sprintf("%d", skip)) } url := fmt.Sprintf("%s/device", service.baseUrl) if len(query) != 0 { url += "?" + query.Encode() } task.request, err = http.NewRequest("GET", url, nil) if err != nil { log.Warnf("REST: failed to create /device/list request (error: %s)", err) return } // authorization service.prepareAuthorization(task.request, nil) return }
// NewService creates new service. func NewService(baseUrl, accessKey string) (service *Service, err error) { log.Tracef("REST: creating service (url:%q)", baseUrl) service = &Service{accessKey: accessKey} // remove trailing slashes from URL for len(baseUrl) > 1 && strings.HasSuffix(baseUrl, "/") { baseUrl = baseUrl[0 : len(baseUrl)-1] } // parse URL service.baseUrl, err = url.Parse(baseUrl) if err != nil { log.Warnf("REST: failed to parse URL (error: %s)", err) return } // initialize HTTP client service.client = &http.Client{} // TODO: client.Transport // TODO: client.CookieJar // TODO: client.Timeout service.commandListeners = make(map[string]*core.CommandListener) service.notificationListeners = make(map[string]*core.NotificationListener) return }
// subscribe for notifications func (service *Service) SubscribeNotifications(device *core.Device, timestamp string, timeout time.Duration) (listener *core.NotificationListener, err error) { if listener, ok := service.notificationListeners[device.Id]; ok { return listener, nil } // install new listener = core.NewNotificationListener() service.notificationListeners[device.Id] = listener go func(deviceId string) { log.Debugf("REST: start notification polling %q", deviceId) for { names := "" wait := "30" ntfs, err := service.PollNotifications(device, timestamp, names, wait, 60*time.Second) if err != nil { log.Warnf("REST: failed to poll notifications (error: %s)", err) // TODO: break? wait and try again? } if listener, ok := service.notificationListeners[deviceId]; ok { for _, ntf := range ntfs { log.Debugf("REST: got notification %s received", ntf) timestamp = ntf.Timestamp listener.C <- &ntf } } else { log.Debugf("REST: stop notification polling %q", deviceId) return // stop } } }(device.Id) return }
// Process UpdateCommand task func (service *Service) processUpdateCommand(task Task, command *core.Command) (err error) { // check task error first if task.err != nil { err = task.err return } // check status code if task.response.StatusCode < http.StatusOK || task.response.StatusCode > http.StatusPartialContent { log.Warnf("REST: unexpected /command/update status %s", task.response.Status) err = fmt.Errorf("unexpected status: %s", task.response.Status) return } _ = command // unused // unmarshal // err = json.Unmarshal(task.body, command) // if err != nil { // log.Warnf("REST: failed to parse /command/update body (error: %s)", err) // return // } return }
// subscribe for commands func (service *Service) SubscribeCommands(device *core.Device, timestamp string, timeout time.Duration) (listener *core.CommandListener, err error) { if listener, ok := service.commandListeners[device.Id]; ok { return listener, nil } // install new listener = core.NewCommandListener() service.commandListeners[device.Id] = listener go func(deviceId string) { log.Debugf("REST: start command polling %q", deviceId) for { names := "" wait := "30" cmds, err := service.PollCommands(device, timestamp, names, wait, 60*time.Second) if err != nil { log.Warnf("REST: failed to poll commands (error: %s)", err) // TODO: break? wait and try again? } if listener, ok := service.commandListeners[deviceId]; ok { for _, cmd := range cmds { log.Debugf("REST: got command %s received", cmd) timestamp = cmd.Timestamp listener.C <- &cmd } } else { log.Debugf("REST: stop command polling %q", deviceId) return // stop } } }(device.Id) return }
// Prepare PollCommand task func (service *Service) preparePollCommand(device *core.Device, timestamp, names, waitTimeout string) (task Task, err error) { // create request query := url.Values{} if len(timestamp) != 0 { query.Set("timestamp", timestamp) } if len(names) != 0 { query.Set("names", names) } if len(waitTimeout) != 0 { query.Set("waitTimeout", waitTimeout) } url := fmt.Sprintf("%s/device/%s/command/poll", service.baseUrl, device.Id) if len(query) != 0 { url += "?" + query.Encode() } task.request, err = http.NewRequest("GET", url, nil) if err != nil { log.Warnf("REST: failed to create /command/poll request (error: %s)", err) return } // authorization service.prepareAuthorization(task.request, device) return }
// send notification // priority is ignored func (w *DBusWrapper) SendNotification(name, parameters string, priority uint64) *dbus.Error { log.Infof("sending notification(name=%q, params=%q, priority=%d)", name, parameters, priority) dat, err := parseJSON(parameters) if err != nil { log.Warnf("failed to convert notification parameters to JSON (error: %s)", err) return newDHError(err.Error()) } notification := devicehive.NewNotification(name, dat) err = w.service.InsertNotification(w.device, notification, waitTimeout) if err != nil { log.Warnf("failed to send notification (error: %s)", err) return newDHError(err.Error()) } return nil // OK }
// NewService creates new Websocket /device service. func NewService(baseUrl, accessKey string) (service *Service, err error) { log.Tracef("WS: creating service (url:%q)", baseUrl) service = &Service{accessKey: accessKey} // remove trailing slashes from URL for len(baseUrl) > 1 && strings.HasSuffix(baseUrl, "/") { baseUrl = baseUrl[0 : len(baseUrl)-1] } // parse URL service.baseUrl, err = url.Parse(baseUrl) if err != nil { log.Warnf("WS: failed to parse URL (error: %s)", err) service = nil return } // connect to /device endpoint ws_url := fmt.Sprintf("%s/device", service.baseUrl) headers := http.Header{} headers.Add("Origin", "http://localhost/") if len(service.accessKey) != 0 { headers.Add("Authorization", "Bearer "+service.accessKey) } service.conn, _, err = websocket.DefaultDialer.Dial(ws_url, headers) if err != nil { log.Warnf("WS: failed to dial (error: %s)", err) service = nil return } // set of active tasks service.tasks = make(map[uint64]*Task) // command listeners service.commandListeners = make(map[string]*core.CommandListener) // create TX channel service.tx = make(chan *Task) // and start RX/TX threads go service.doRX() go service.doTX() return }
// update command result func (w *DBusWrapper) UpdateCommand(id uint64, status, result string) *dbus.Error { log.Infof("updating command(id:%d, status=%q, result:%q", id, status, result) dat, err := parseJSON(result) if err != nil { log.Warnf("failed to convert command result to JSON (error: %s)", err) return newDHError(err.Error()) } command := devicehive.NewCommandResult(id, status, dat) err = w.service.UpdateCommand(w.device, command, waitTimeout) if err != nil { log.Warnf("failed to update command (error: %s)", err) return newDHError(err.Error()) } return nil // OK }