gobot.io/x/gobot@v1.16.0/platforms/particle/adaptor.go (about) 1 package particle 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "strconv" 11 12 "github.com/donovanhide/eventsource" 13 "gobot.io/x/gobot" 14 ) 15 16 // Adaptor is the Gobot Adaptor for Particle 17 type Adaptor struct { 18 name string 19 DeviceID string 20 AccessToken string 21 APIServer string 22 servoPins map[string]bool 23 gobot.Eventer 24 } 25 26 // Event is an event emitted by the Particle cloud 27 type Event struct { 28 Name string 29 Data string 30 Error error 31 } 32 33 var eventSource = func(url string) (chan eventsource.Event, chan error, error) { 34 stream, err := eventsource.Subscribe(url, "") 35 if err != nil { 36 return nil, nil, err 37 } 38 return stream.Events, stream.Errors, nil 39 } 40 41 // NewAdaptor creates new Photon adaptor with deviceId and accessToken 42 // using api.particle.io server as default 43 func NewAdaptor(deviceID string, accessToken string) *Adaptor { 44 return &Adaptor{ 45 name: gobot.DefaultName("Particle"), 46 DeviceID: deviceID, 47 AccessToken: accessToken, 48 servoPins: make(map[string]bool), 49 APIServer: "https://api.particle.io", 50 Eventer: gobot.NewEventer(), 51 } 52 } 53 54 // Name returns the Adaptor name 55 func (s *Adaptor) Name() string { return s.name } 56 57 // SetName sets the Adaptor name 58 func (s *Adaptor) SetName(n string) { s.name = n } 59 60 // Connect returns true if connection to Particle Photon or Electron is successful 61 func (s *Adaptor) Connect() (err error) { 62 return 63 } 64 65 // Finalize returns true if connection to Particle Photon or Electron is finalized successfully 66 func (s *Adaptor) Finalize() (err error) { 67 return 68 } 69 70 // AnalogRead reads analog ping value using Particle cloud api 71 func (s *Adaptor) AnalogRead(pin string) (val int, err error) { 72 params := url.Values{ 73 "params": {pin}, 74 "access_token": {s.AccessToken}, 75 } 76 77 url := fmt.Sprintf("%v/analogread", s.deviceURL()) 78 79 resp, err := s.request("POST", url, params) 80 if err == nil { 81 val = int(resp["return_value"].(float64)) 82 return 83 } 84 85 return 0, err 86 } 87 88 // PwmWrite writes in pin using analog write api 89 func (s *Adaptor) PwmWrite(pin string, level byte) (err error) { 90 return s.AnalogWrite(pin, level) 91 } 92 93 // AnalogWrite writes analog pin with specified level using Particle cloud api 94 func (s *Adaptor) AnalogWrite(pin string, level byte) (err error) { 95 params := url.Values{ 96 "params": {fmt.Sprintf("%v,%v", pin, level)}, 97 "access_token": {s.AccessToken}, 98 } 99 url := fmt.Sprintf("%v/analogwrite", s.deviceURL()) 100 _, err = s.request("POST", url, params) 101 return 102 } 103 104 // DigitalWrite writes to a digital pin using Particle cloud api 105 func (s *Adaptor) DigitalWrite(pin string, level byte) (err error) { 106 params := url.Values{ 107 "params": {fmt.Sprintf("%v,%v", pin, s.pinLevel(level))}, 108 "access_token": {s.AccessToken}, 109 } 110 url := fmt.Sprintf("%v/digitalwrite", s.deviceURL()) 111 _, err = s.request("POST", url, params) 112 return err 113 } 114 115 // DigitalRead reads from digital pin using Particle cloud api 116 func (s *Adaptor) DigitalRead(pin string) (val int, err error) { 117 params := url.Values{ 118 "params": {pin}, 119 "access_token": {s.AccessToken}, 120 } 121 url := fmt.Sprintf("%v/digitalread", s.deviceURL()) 122 resp, err := s.request("POST", url, params) 123 if err == nil { 124 val = int(resp["return_value"].(float64)) 125 return 126 } 127 return -1, err 128 } 129 130 // ServoWrite writes the 0-180 degree angle to the specified pin. 131 // To use it requires installing the "tinker-servo" sketch on your 132 // Particle device. not just the default "tinker". 133 func (s *Adaptor) ServoWrite(pin string, angle byte) (err error) { 134 if _, present := s.servoPins[pin]; !present { 135 err = s.servoPinOpen(pin) 136 if err != nil { 137 return 138 } 139 } 140 141 params := url.Values{ 142 "params": {fmt.Sprintf("%v,%v", pin, angle)}, 143 "access_token": {s.AccessToken}, 144 } 145 url := fmt.Sprintf("%v/servoSet", s.deviceURL()) 146 _, err = s.request("POST", url, params) 147 return err 148 } 149 150 // EventStream returns a gobot.Event based on the following params: 151 // 152 // * source - "all"/"devices"/"device" (More info at: http://docs.particle.io/api/#reading-data-from-a-core-events) 153 // * name - Event name to subscribe for, leave blank to subscribe to all events. 154 // 155 // A new event is emitted as a particle.Event struct 156 func (s *Adaptor) EventStream(source string, name string) (event *gobot.Event, err error) { 157 var url string 158 159 switch source { 160 case "all": 161 url = fmt.Sprintf("%s/v1/events/%s?access_token=%s", s.APIServer, name, s.AccessToken) 162 case "devices": 163 url = fmt.Sprintf("%s/v1/devices/events/%s?access_token=%s", s.APIServer, name, s.AccessToken) 164 case "device": 165 url = fmt.Sprintf("%s/events/%s?access_token=%s", s.deviceURL(), name, s.AccessToken) 166 default: 167 err = errors.New("source param should be: all, devices or device") 168 return 169 } 170 171 events, _, err := eventSource(url) 172 if err != nil { 173 return 174 } 175 176 go func() { 177 for { 178 select { 179 case ev := <-events: 180 if ev.Event() != "" && ev.Data() != "" { 181 s.Publish(ev.Event(), ev.Data()) 182 } 183 } 184 } 185 }() 186 return 187 } 188 189 // Variable returns a core variable value as a string 190 func (s *Adaptor) Variable(name string) (result string, err error) { 191 url := fmt.Sprintf("%v/%s?access_token=%s", s.deviceURL(), name, s.AccessToken) 192 resp, err := s.request("GET", url, nil) 193 194 if err != nil { 195 return 196 } 197 198 val := resp["result"] 199 switch val.(type) { 200 case bool: 201 result = strconv.FormatBool(val.(bool)) 202 case float64: 203 result = strconv.FormatFloat(val.(float64), 'f', -1, 64) 204 case string: 205 result = val.(string) 206 } 207 208 return 209 } 210 211 // Function executes a core function and 212 // returns value from request. 213 // Takes a String as the only argument and returns an Int. 214 // If function is not defined in core, it will time out 215 func (s *Adaptor) Function(name string, args string) (val int, err error) { 216 params := url.Values{ 217 "args": {args}, 218 "access_token": {s.AccessToken}, 219 } 220 221 url := fmt.Sprintf("%s/%s", s.deviceURL(), name) 222 resp, err := s.request("POST", url, params) 223 224 if err != nil { 225 return -1, err 226 } 227 228 val = int(resp["return_value"].(float64)) 229 return 230 } 231 232 // setAPIServer sets Particle cloud api server, this can be used to change from default api.spark.io 233 func (s *Adaptor) setAPIServer(server string) { 234 s.APIServer = server 235 } 236 237 // deviceURL constructs device url to make requests from Particle cloud api 238 func (s *Adaptor) deviceURL() string { 239 if len(s.APIServer) <= 0 { 240 s.setAPIServer("https://api.particle.io") 241 } 242 return fmt.Sprintf("%v/v1/devices/%v", s.APIServer, s.DeviceID) 243 } 244 245 // pinLevel converts byte level to string expected in api 246 func (s *Adaptor) pinLevel(level byte) string { 247 if level == 1 { 248 return "HIGH" 249 } 250 return "LOW" 251 } 252 253 // request makes request to Particle cloud server, return err != nil if there is 254 // any issue with the request. 255 func (s *Adaptor) request(method string, url string, params url.Values) (m map[string]interface{}, err error) { 256 var resp *http.Response 257 258 if method == "POST" { 259 resp, err = http.PostForm(url, params) 260 } else if method == "GET" { 261 resp, err = http.Get(url) 262 } 263 264 if err != nil { 265 return 266 } 267 268 buf, err := ioutil.ReadAll(resp.Body) 269 270 if err != nil { 271 return 272 } 273 274 json.Unmarshal(buf, &m) 275 276 if resp.Status != "200 OK" { 277 err = fmt.Errorf("%v: error communicating to the Particle cloud", resp.Status) 278 } else if _, ok := m["error"]; ok { 279 err = errors.New(m["error"].(string)) 280 } 281 282 return 283 } 284 285 func (s *Adaptor) servoPinOpen(pin string) error { 286 params := url.Values{ 287 "params": {fmt.Sprintf("%v", pin)}, 288 "access_token": {s.AccessToken}, 289 } 290 url := fmt.Sprintf("%v/servoOpen", s.deviceURL()) 291 _, err := s.request("POST", url, params) 292 if err != nil { 293 return err 294 } 295 s.servoPins[pin] = true 296 return nil 297 }