From 1baaa6ebd33299d8a5759cdf391213dccf0c19ea Mon Sep 17 00:00:00 2001 From: Jake Wright Date: Fri, 31 Aug 2018 10:41:36 +0100 Subject: [PATCH 1/2] Add config service --- home-automation/03-config/config.yaml | 9 ++ .../03-config/controller/controller.go | 42 +++++++++ home-automation/03-config/domain/config.go | 94 +++++++++++++++++++ home-automation/03-config/main.go | 31 ++++++ home-automation/03-config/service/service.go | 41 ++++++++ 5 files changed, 217 insertions(+) create mode 100644 home-automation/03-config/config.yaml create mode 100644 home-automation/03-config/controller/controller.go create mode 100644 home-automation/03-config/domain/config.go create mode 100644 home-automation/03-config/main.go create mode 100644 home-automation/03-config/service/service.go diff --git a/home-automation/03-config/config.yaml b/home-automation/03-config/config.yaml new file mode 100644 index 0000000..ab06e27 --- /dev/null +++ b/home-automation/03-config/config.yaml @@ -0,0 +1,9 @@ +base: + apiGateway: http://service.api-gateway + redis: + host: redis + port: 6379 + +service.registry.device: + database: /data/devices.db + apiGateway: somethingNew diff --git a/home-automation/03-config/controller/controller.go b/home-automation/03-config/controller/controller.go new file mode 100644 index 0000000..7d60c85 --- /dev/null +++ b/home-automation/03-config/controller/controller.go @@ -0,0 +1,42 @@ +package controller + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/jakewright/tutorials/home-automation/03-config/domain" +) + +// Controller exports the handlers for the endpoints +type Controller struct { + Config *domain.Config +} + +// ReadConfig writes the config for the given service to the ResponseWriter +func (c *Controller) ReadConfig(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + vars := mux.Vars(r) + serviceName, ok := vars["serviceName"] + if !ok { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "error") + } + + config, err := c.Config.Get(serviceName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "error") + } + + rsp, err := json.Marshal(&config) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "error") + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, string(rsp)) +} diff --git a/home-automation/03-config/domain/config.go b/home-automation/03-config/domain/config.go new file mode 100644 index 0000000..1479c71 --- /dev/null +++ b/home-automation/03-config/domain/config.go @@ -0,0 +1,94 @@ +package domain + +import ( + "fmt" + "sync" + + "gopkg.in/yaml.v2" +) + +// Config is an abstraction around the map that holds the config values +type Config struct { + config map[string]interface{} + lock sync.RWMutex +} + +// SetFromBytes sets the internal config based on a byte array of YAML +func (c *Config) SetFromBytes(data []byte) error { + var rawConfig interface{} + if err := yaml.Unmarshal(data, &rawConfig); err != nil { + return err + } + + untypedConfig, ok := rawConfig.(map[interface{}]interface{}) + if !ok { + return fmt.Errorf("config is not a map") + } + + config, err := convertKeysToStrings(untypedConfig) + if err != nil { + return err + } + + c.lock.Lock() + defer c.lock.Unlock() + + c.config = config + return nil +} + +// Get returns the config for a particular service +func (c *Config) Get(serviceName string) (map[string]interface{}, error) { + c.lock.RLock() + defer c.lock.RUnlock() + + a, ok := c.config["base"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("base config is not a map") + } + + // If no config is defined for the service + if _, ok = c.config[serviceName]; !ok { + // Return the base config + return a, nil + } + + b, ok := c.config[serviceName].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("service %q config is not a map", serviceName) + } + + // Merge the maps with the service config taking precedence + config := make(map[string]interface{}) + for k, v := range a { + config[k] = v + } + for k, v := range b { + config[k] = v + } + + return config, nil +} + +func convertKeysToStrings(m map[interface{}]interface{}) (map[string]interface{}, error) { + n := make(map[string]interface{}) + + for k, v := range m { + str, ok := k.(string) + if !ok { + return nil, fmt.Errorf("config key is not a string") + } + + if vMap, ok := v.(map[interface{}]interface{}); ok { + var err error + v, err = convertKeysToStrings(vMap) + if err != nil { + return nil, err + } + } + + n[str] = v + } + + return n, nil +} diff --git a/home-automation/03-config/main.go b/home-automation/03-config/main.go new file mode 100644 index 0000000..ff68b4f --- /dev/null +++ b/home-automation/03-config/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "time" + + "github.com/jakewright/muxinator" + + "github.com/jakewright/tutorials/home-automation/03-config/controller" + "github.com/jakewright/tutorials/home-automation/03-config/domain" + "github.com/jakewright/tutorials/home-automation/03-config/service" +) + +func main() { + config := domain.Config{} + + configService := service.ConfigService{ + Config: &config, + Location: "config.yaml", + } + + go configService.Watch(time.Second * 30) + + c := controller.Controller{ + Config: &config, + } + + router := muxinator.NewRouter() + router.Get("/read/{serviceName}", c.ReadConfig) + log.Fatal(router.ListenAndServe(":8080")) +} diff --git a/home-automation/03-config/service/service.go b/home-automation/03-config/service/service.go new file mode 100644 index 0000000..31ef1ef --- /dev/null +++ b/home-automation/03-config/service/service.go @@ -0,0 +1,41 @@ +package service + +import ( + "io/ioutil" + "log" + "time" + + "github.com/jakewright/tutorials/home-automation/03-config/domain" +) + +type ConfigService struct { + Config *domain.Config + Location string +} + +// Watch reloads the config every d duration +func (s *ConfigService) Watch(d time.Duration) { + for { + err := s.Reload() + if err != nil { + log.Print(err) + } + + time.Sleep(d) + } +} + +// Reload reads the config and applies changes +func (s *ConfigService) Reload() error { + data, err := ioutil.ReadFile(s.Location) + if err != nil { + return err + } + + err = s.Config.SetFromBytes(data) + if err != nil { + return err + } + + return nil +} From 73e2e76460fb300d9b20178826200e57b5af3e11 Mon Sep 17 00:00:00 2001 From: Jake Wright Date: Wed, 20 Mar 2019 22:27:48 +0000 Subject: [PATCH 2/2] Add Hue service --- .../04-hue-lights/docker-compose.yml | 25 +++++ .../04-hue-lights/service.config/Dockerfile | 11 ++ .../04-hue-lights/service.config/config.yaml | 4 + .../service.config/controller/controller.go | 45 ++++++++ .../service.config/domain/config.go | 97 +++++++++++++++++ .../04-hue-lights/service.config/main.go | 31 ++++++ .../service.config/service/service.go | 41 +++++++ .../service.controller.hue/Dockerfile | 14 +++ .../service.controller.hue/api/hueClient.js | 44 ++++++++ .../service.controller.hue/dao/index.js | 33 ++++++ .../service.controller.hue/index.js | 32 ++++++ .../service.controller.hue/package.json | 17 +++ .../service.controller.hue/routes/index.js | 36 +++++++ .../service.registry.device/Dockerfile | 10 ++ .../service.registry.device/README.md | 98 +++++++++++++++++ .../device_registry/__init__.py | 100 ++++++++++++++++++ .../service.registry.device/devices.db | Bin 0 -> 12986 bytes .../service.registry.device/requirements.txt | 3 + .../service.registry.device/run.py | 3 + 19 files changed, 644 insertions(+) create mode 100644 home-automation/04-hue-lights/docker-compose.yml create mode 100644 home-automation/04-hue-lights/service.config/Dockerfile create mode 100644 home-automation/04-hue-lights/service.config/config.yaml create mode 100644 home-automation/04-hue-lights/service.config/controller/controller.go create mode 100644 home-automation/04-hue-lights/service.config/domain/config.go create mode 100644 home-automation/04-hue-lights/service.config/main.go create mode 100644 home-automation/04-hue-lights/service.config/service/service.go create mode 100644 home-automation/04-hue-lights/service.controller.hue/Dockerfile create mode 100644 home-automation/04-hue-lights/service.controller.hue/api/hueClient.js create mode 100644 home-automation/04-hue-lights/service.controller.hue/dao/index.js create mode 100644 home-automation/04-hue-lights/service.controller.hue/index.js create mode 100644 home-automation/04-hue-lights/service.controller.hue/package.json create mode 100644 home-automation/04-hue-lights/service.controller.hue/routes/index.js create mode 100644 home-automation/04-hue-lights/service.registry.device/Dockerfile create mode 100644 home-automation/04-hue-lights/service.registry.device/README.md create mode 100644 home-automation/04-hue-lights/service.registry.device/device_registry/__init__.py create mode 100644 home-automation/04-hue-lights/service.registry.device/devices.db create mode 100644 home-automation/04-hue-lights/service.registry.device/requirements.txt create mode 100644 home-automation/04-hue-lights/service.registry.device/run.py diff --git a/home-automation/04-hue-lights/docker-compose.yml b/home-automation/04-hue-lights/docker-compose.yml new file mode 100644 index 0000000..4f2d6eb --- /dev/null +++ b/home-automation/04-hue-lights/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3' + +services: + service.config: + build: ./service.config + volumes: + - ./service.config:/go/src/github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config + - ./service.config/config.yaml:/data/config.yaml + ports: + - 7000:80 + + service.registry.device: + build: ./service.registry.device + volumes: + - ./service.registry.device:/usr/src/app + ports: + - 7001:80 + + service.controller.hue: + build: ./service.controller.hue + volumes: + - ./service.controller.hue:/usr/src/app + - /usr/src/app/node_modules + ports: + - 7003:80 diff --git a/home-automation/04-hue-lights/service.config/Dockerfile b/home-automation/04-hue-lights/service.config/Dockerfile new file mode 100644 index 0000000..5dd366e --- /dev/null +++ b/home-automation/04-hue-lights/service.config/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:latest +RUN go get -u golang.org/x/lint/golint +RUN go get github.com/githubnemo/CompileDaemon + +WORKDIR /go/src/github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config +COPY . . + +RUN go get -d -v ./... +RUN go install -v ./... + +CMD CompileDaemon -build="go install ." -command="/go/bin/service.config" \ No newline at end of file diff --git a/home-automation/04-hue-lights/service.config/config.yaml b/home-automation/04-hue-lights/service.config/config.yaml new file mode 100644 index 0000000..2688b46 --- /dev/null +++ b/home-automation/04-hue-lights/service.config/config.yaml @@ -0,0 +1,4 @@ +service.controller.hue: + hueBridge: + host: http://192.168.1.110 + username: g3avkmfKYFZtxwb6Ny7DNQgQXJtn7Sl3VzEcbJDi \ No newline at end of file diff --git a/home-automation/04-hue-lights/service.config/controller/controller.go b/home-automation/04-hue-lights/service.config/controller/controller.go new file mode 100644 index 0000000..bd0f2de --- /dev/null +++ b/home-automation/04-hue-lights/service.config/controller/controller.go @@ -0,0 +1,45 @@ +package controller + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config/domain" +) + +// Controller exports the handlers for the endpoints +type Controller struct { + Config *domain.Config +} + +// ReadConfig writes the config for the given service to the ResponseWriter +func (c *Controller) ReadConfig(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + vars := mux.Vars(r) + serviceName, ok := vars["serviceName"] + if !ok { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "serviceName not set") + return + } + + config, err := c.Config.Get(serviceName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, err.Error()) + return + } + + rsp, err := json.Marshal(&config) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, err.Error()) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, string(rsp)) +} diff --git a/home-automation/04-hue-lights/service.config/domain/config.go b/home-automation/04-hue-lights/service.config/domain/config.go new file mode 100644 index 0000000..d103c47 --- /dev/null +++ b/home-automation/04-hue-lights/service.config/domain/config.go @@ -0,0 +1,97 @@ +package domain + +import ( + "fmt" + "sync" + + "gopkg.in/yaml.v2" +) + +// Config is an abstraction around the map that holds the config values +type Config struct { + config map[string]interface{} + lock sync.RWMutex +} + +// SetFromBytes sets the internal config based on a byte array of YAML +func (c *Config) SetFromBytes(data []byte) error { + var rawConfig interface{} + if err := yaml.Unmarshal(data, &rawConfig); err != nil { + return err + } + + untypedConfig, ok := rawConfig.(map[interface{}]interface{}) + if !ok { + return fmt.Errorf("config is not a map") + } + + config, err := convertKeysToStrings(untypedConfig) + if err != nil { + return err + } + + c.lock.Lock() + defer c.lock.Unlock() + + c.config = config + return nil +} + +// Get returns the config for a particular service +func (c *Config) Get(serviceName string) (map[string]interface{}, error) { + c.lock.RLock() + defer c.lock.RUnlock() + + var a map[string]interface{} + if _, ok := c.config["base"]; ok { + a, ok = c.config["base"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("base config is not a map") + } + } + + // If no config is defined for the service + if _, ok := c.config[serviceName]; !ok { + // Return the base config + return a, nil + } + + b, ok := c.config[serviceName].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("service %q config is not a map", serviceName) + } + + // Merge the maps with the service config taking precedence + config := make(map[string]interface{}) + for k, v := range a { + config[k] = v + } + for k, v := range b { + config[k] = v + } + + return config, nil +} + +func convertKeysToStrings(m map[interface{}]interface{}) (map[string]interface{}, error) { + n := make(map[string]interface{}) + + for k, v := range m { + str, ok := k.(string) + if !ok { + return nil, fmt.Errorf("config key is not a string") + } + + if vMap, ok := v.(map[interface{}]interface{}); ok { + var err error + v, err = convertKeysToStrings(vMap) + if err != nil { + return nil, err + } + } + + n[str] = v + } + + return n, nil +} diff --git a/home-automation/04-hue-lights/service.config/main.go b/home-automation/04-hue-lights/service.config/main.go new file mode 100644 index 0000000..14182c0 --- /dev/null +++ b/home-automation/04-hue-lights/service.config/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "time" + + "github.com/jakewright/muxinator" + + "github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config/controller" + "github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config/domain" + "github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config/service" +) + +func main() { + config := domain.Config{} + + configService := service.ConfigService{ + Config: &config, + Location: "/data/config.yaml", + } + + go configService.Watch(time.Second * 30) + + c := controller.Controller{ + Config: &config, + } + + router := muxinator.NewRouter() + router.Get("/read/{serviceName}", c.ReadConfig) + log.Fatal(router.ListenAndServe(":80")) +} diff --git a/home-automation/04-hue-lights/service.config/service/service.go b/home-automation/04-hue-lights/service.config/service/service.go new file mode 100644 index 0000000..4915eed --- /dev/null +++ b/home-automation/04-hue-lights/service.config/service/service.go @@ -0,0 +1,41 @@ +package service + +import ( + "io/ioutil" + "log" + "time" + + "github.com/jakewright/tutorials/home-automation/04-hue-lights/service.config/domain" +) + +type ConfigService struct { + Config *domain.Config + Location string +} + +// Watch reloads the config every d duration +func (s *ConfigService) Watch(d time.Duration) { + for { + err := s.Reload() + if err != nil { + log.Print(err) + } + + time.Sleep(d) + } +} + +// Reload reads the config and applies changes +func (s *ConfigService) Reload() error { + data, err := ioutil.ReadFile(s.Location) + if err != nil { + return err + } + + err = s.Config.SetFromBytes(data) + if err != nil { + return err + } + + return nil +} diff --git a/home-automation/04-hue-lights/service.controller.hue/Dockerfile b/home-automation/04-hue-lights/service.controller.hue/Dockerfile new file mode 100644 index 0000000..8431766 --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/Dockerfile @@ -0,0 +1,14 @@ +FROM node:8.11 + +# Install nodemon +RUN npm install -g nodemon + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install app dependencies +COPY package.json . +RUN npm install + +CMD [ "npm", "start" ] diff --git a/home-automation/04-hue-lights/service.controller.hue/api/hueClient.js b/home-automation/04-hue-lights/service.controller.hue/api/hueClient.js new file mode 100644 index 0000000..edc52e4 --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/api/hueClient.js @@ -0,0 +1,44 @@ +const axios = require("axios"); + +class HueClient { + get hueUrl() { + return `${this.host}/api/${this.username}`; + } + + async fetchAllState() { + const rsp = await axios.get(`${this.hueUrl}/lights`); + + const lights = {}; + + for (const hueId in rsp.data) { + lights[hueId] = { + power: rsp.data[hueId].state.on, + brightness: rsp.data[hueId].state.bri + }; + } + + return lights; + } + + async fetchState(hueId) { + const rsp = await axios.get(`${this.hueUrl}/lights/${hueId}`); + + return { + power: rsp.data.state.on, + brightness: rsp.data.state.bri + }; + } + + async applyState(hueId, state) { + const hueState = { + on: state.power, + bri: state.brightness, + }; + + await axios.put(`${this.hueUrl}/lights/${hueId}/state`, hueState); + return this.fetchState(hueId); + } +} + +const hueClient = new HueClient(); +exports = module.exports = hueClient; \ No newline at end of file diff --git a/home-automation/04-hue-lights/service.controller.hue/dao/index.js b/home-automation/04-hue-lights/service.controller.hue/dao/index.js new file mode 100644 index 0000000..60a8271 --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/dao/index.js @@ -0,0 +1,33 @@ +const axios = require("axios"); +const hueClient = require("../api/hueClient"); + +let devices = []; + +const findByHueId = (hueId) => { + return devices.find(device => device.attributes["hue_id"] === hueId); +}; + +const findByIdentifier = (identifier) => { + return devices.find(device => device.identifier === identifier); +}; + +const fetchAllState = async () => { + const rsp = await axios.get("http://service.registry.device/devices"); + devices = rsp.data.data; + + const hueIdToState = await hueClient.fetchAllState(); + + for (const hueId in hueIdToState) { + const device = findByHueId(hueId); + if (device === undefined) continue; + + Object.assign(device, hueIdToState[hueId]); + } +}; + +const applyState = async (device, state) => { + const newState = await hueClient.applyState(device.attributes["hue_id"], state); + Object.assign(device, newState); +}; + +exports = module.exports = { findByIdentifier, fetchAllState, applyState }; diff --git a/home-automation/04-hue-lights/service.controller.hue/index.js b/home-automation/04-hue-lights/service.controller.hue/index.js new file mode 100644 index 0000000..2f47cb4 --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/index.js @@ -0,0 +1,32 @@ +const axios = require("axios"); +const express = require("express"); +const dao = require("./dao"); +const hueClient = require("./api/hueClient"); +const routes = require("./routes"); + +const port = 80; + +axios.get("http://service.config/read/service.controller.hue") + .then(rsp => { + hueClient.host = rsp.data.hueBridge.host; + hueClient.username = rsp.data.hueBridge.username; + + return dao.fetchAllState(); + }) + .then(() => { + const app = express(); + routes.register(app); + app.listen(port, () => console.log(`Listening on port ${port}`)); + }) + .catch(err => { + console.error("Error initialising service", err); + }); + + + + + + + + + diff --git a/home-automation/04-hue-lights/service.controller.hue/package.json b/home-automation/04-hue-lights/service.controller.hue/package.json new file mode 100644 index 0000000..326347e --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/package.json @@ -0,0 +1,17 @@ +{ + "name": "service.controller.hue", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "nodemon index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^0.18.0", + "express": "^4.16.4", + "npm": "^6.9.0" + } +} diff --git a/home-automation/04-hue-lights/service.controller.hue/routes/index.js b/home-automation/04-hue-lights/service.controller.hue/routes/index.js new file mode 100644 index 0000000..1d981fd --- /dev/null +++ b/home-automation/04-hue-lights/service.controller.hue/routes/index.js @@ -0,0 +1,36 @@ +const express = require("express"); +const dao = require("../dao"); + +const register = (app) => { + app.use(express.json()); + + // Request logger + app.use((req, res, next) => { + console.log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body)}`); + next(); + }); + + app.get("/device/:deviceId", (req, res) => { + const device = dao.findByIdentifier(req.params.deviceId); + res.json({data: device}); + }); + + app.patch("/device/:deviceId", (req, res, next) => { + const device = dao.findByIdentifier(req.params.deviceId); + const state = req.body; + + dao.applyState(device, state) + .then(() => { + res.json({data: device}); + }) + .catch(next); + }); + + app.use(function (err, req, res, next) { + console.error(err); + res.status(500); + res.json({message: err.message}); + }); +}; + +exports = module.exports = { register }; \ No newline at end of file diff --git a/home-automation/04-hue-lights/service.registry.device/Dockerfile b/home-automation/04-hue-lights/service.registry.device/Dockerfile new file mode 100644 index 0000000..c8a0628 --- /dev/null +++ b/home-automation/04-hue-lights/service.registry.device/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3 + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD [ "python", "./run.py" ] diff --git a/home-automation/04-hue-lights/service.registry.device/README.md b/home-automation/04-hue-lights/service.registry.device/README.md new file mode 100644 index 0000000..169be9c --- /dev/null +++ b/home-automation/04-hue-lights/service.registry.device/README.md @@ -0,0 +1,98 @@ +# Device Registry Service + +## Usage + +All responses will have the form + +```json +{ + "data": "Mixed type holding the content of the response", + "message": "Description of what happened" +} +``` + +Subsequent response definitions will only detail the expected value of the `data field` + +### List all devices + +**Definition** + +`GET /devices` + +**Response** + +- `200 OK` on success + +```json +[ + { + "identifier": "floor-lamp", + "name": "Floor Lamp", + "device_type": "switch", + "controller_gateway": "192.1.68.0.2" + }, + { + "identifier": "samsung-tv", + "name": "Living Room TV", + "device_type": "tv", + "controller_gateway": "192.168.0.9" + } +] +``` + +### Registering a new device + +**Definition** + +`POST /devices` + +**Arguments** + +- `"identifier":string` a globally unique identifier for this device +- `"name":string` a friendly name for this device +- `"device_type":string` the type of the device as understood by the client +- `"controller_gateway":string` the IP address of the device's controller + +If a device with the given identifier already exists, the existing device will be overwritten. + +**Response** + +- `201 Created` on success + +```json +{ + "identifier": "floor-lamp", + "name": "Floor Lamp", + "device_type": "switch", + "controller_gateway": "192.1.68.0.2" +} +``` + +## Lookup device details + +`GET /device/` + +**Response** + +- `404 Not Found` if the device does not exist +- `200 OK` on success + +```json +{ + "identifier": "floor-lamp", + "name": "Floor Lamp", + "device_type": "switch", + "controller_gateway": "192.1.68.0.2" +} +``` + +## Delete a device + +**Definition** + +`DELETE /devices/` + +**Response** + +- `404 Not Found` if the device does not exist +- `204 No Content` on success diff --git a/home-automation/04-hue-lights/service.registry.device/device_registry/__init__.py b/home-automation/04-hue-lights/service.registry.device/device_registry/__init__.py new file mode 100644 index 0000000..23703a2 --- /dev/null +++ b/home-automation/04-hue-lights/service.registry.device/device_registry/__init__.py @@ -0,0 +1,100 @@ +import markdown +import os +import shelve + +# Import the framework +from flask import Flask, g +from flask_restful import Resource, Api, reqparse + +# Create an instance of Flask +app = Flask(__name__) + +# Create the API +api = Api(app) + + +def get_db(): + db = getattr(g, '_database', None) + if db is None: + db = g._database = shelve.open("devices.db") + return db + + +@app.teardown_appcontext +def teardown_db(exception): + db = getattr(g, '_database', None) + if db is not None: + db.close() + +@app.route("/") +def index(): + """Present some documentation""" + + # Open the README file + with open(os.path.dirname(app.root_path) + '/README.md', 'r') as markdown_file: + + # Read the content of the file + content = markdown_file.read() + + # Convert to HTML + return markdown.markdown(content) + + +class DeviceList(Resource): + def get(self): + shelf = get_db() + keys = list(shelf.keys()) + + devices = [] + + for key in keys: + devices.append(shelf[key]) + + return {'message': 'Success', 'data': devices}, 200 + + def post(self): + parser = reqparse.RequestParser() + + parser.add_argument('identifier', required=True) + parser.add_argument('name', required=True) + parser.add_argument('device_type', required=True) + parser.add_argument('controller_name', required=True) + parser.add_argument( + 'attributes', type=dict, required=False, location='json') + + # Parse the arguments into an object + args = parser.parse_args() + + shelf = get_db() + shelf[args['identifier']] = args + + return {'message': 'Device registered', 'data': args}, 201 + + +class Device(Resource): + def get(self, identifier): + shelf = get_db() + + # If the key does not exist in the data store, return a 404 error. + if not (identifier in shelf): + return {'message': 'Device not found', 'data': {}}, 404 + + return {'message': 'Device found', 'data': shelf[identifier]}, 200 + + def delete(self, identifier): + shelf = get_db() + + # If the key does not exist in the data store, return a 404 error. + if not (identifier in shelf): + return {'message': 'Device not found', 'data': {}}, 404 + + del shelf[identifier] + return '', 204 + + +api.add_resource(DeviceList, '/devices') +api.add_resource(Device, '/device/') + + + + diff --git a/home-automation/04-hue-lights/service.registry.device/devices.db b/home-automation/04-hue-lights/service.registry.device/devices.db new file mode 100644 index 0000000000000000000000000000000000000000..3853b76662f1f421441fa2a89bfbdbf8271a507c GIT binary patch literal 12986 zcmeI2O-sWt9LBR3=hitD1kXF_Va0jy;K_psf_M{iE49sBUH6ig3LXUg8h!vjgeT7) zbl<{*egr|2rD_LW#4%8xLi#ib`KSHmpQjXhe|5M-76};@Ek-M;UK`yqCqiDV;0yvF z00JNY0w4eaAOHd&00JNY0wC}k3Dnb@hFTEd01k!#@&I{&JitDH55NcD1MmU(0DJ&G z03Y~cK9IM&nhaAVYxZKNX7y(Mz2oO<=6cn~SCPo^E$f{0S$U~$a*Sj zMNIHKH3-MU@d7RC#-1L_i`2YIxAjc*Jv|ejp;cWE*=ZCoPn``IFVV7YX(ud>j@ydQ z(gj@&`aLE3am<7_3R9-bx>Yh^#<`!pt{%rLi93D+zx{bekbR{@bd)G2xpU5|v}7rH cQOM_LPFI?|CKGoui)a?o=?2F+=;H2?qr literal 0 HcmV?d00001 diff --git a/home-automation/04-hue-lights/service.registry.device/requirements.txt b/home-automation/04-hue-lights/service.registry.device/requirements.txt new file mode 100644 index 0000000..358cd7a --- /dev/null +++ b/home-automation/04-hue-lights/service.registry.device/requirements.txt @@ -0,0 +1,3 @@ +Flask==0.12.2 +flask-restful==0.3.6 +markdown==2.6.11 diff --git a/home-automation/04-hue-lights/service.registry.device/run.py b/home-automation/04-hue-lights/service.registry.device/run.py new file mode 100644 index 0000000..f49d3ea --- /dev/null +++ b/home-automation/04-hue-lights/service.registry.device/run.py @@ -0,0 +1,3 @@ +from device_registry import app + +app.run(host='0.0.0.0', port=80, debug=True)