🐭 GO GO GO!

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
This commit is contained in:
Drasko DRASKOVIC
2016-09-20 19:24:54 +02:00
parent f3dfcd7e7e
commit 469b8f0c79
33 changed files with 1552 additions and 360 deletions
+8
View File
@@ -40,3 +40,11 @@ Session.vim
# auto-generated tag files
tags
# Go sources
./src
# Go tests
*.test
# Binary
mainflux
+30
View File
@@ -0,0 +1,30 @@
###
# Mainflux Dockerfile
###
FROM golang:alpine
MAINTAINER Mainflux
###
# Install
###
RUN apk update && apk add git && rm -rf /var/cache/apk/*
# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/mainflux/mainflux-lite
RUN mkdir -p /config/lite
COPY config/config-docker.yml /config/lite/config.yml
# Get and install the dependencies
RUN go get github.com/mainflux/mainflux-lite
###
# Run main command from entrypoint and parameters in CMD[]
###
CMD ["/config/lite/config.yml"]
# Run mainflux command by default when the container starts.
ENTRYPOINT ["/go/bin/mainflux-lite"]
-35
View File
@@ -1,35 +0,0 @@
# Mainflux follows the timeless, highly efficient and totally unfair system
# known as [Benevolent dictator for
# life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with
# Drasko DRASKOVIC in the role of BDFL.
[bdfl]
[[drasko]]
Name = "Drasko DRASKOVIC"
Email = "drasko.draskovic@mainflux.com"
GitHub = "drasko"
# However, this role serves only in dead-lock events, or in a special and very rare cases
# when BDFL completely disagrees with the decisions made.
# In the normal flow of events, decisions on the project design are made through discussions,
# most often on the Pull Requests.
#
# Maintainers have the special role in the project in managing and accepting PRs,
# overall leading the project and making design decisions on the maintained subsystems.
#
# A reference list of all maintainers of the Mainflux project.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[maintainers]
[[janko]]
Name = "Janko ISIDOROVIC"
Email = "janko.isidorovic@mainflux.com"
GitHub = "janko-isidorovic"
[[nikola]]
Name = "Nikola MARCETIC"
Email = "nikola.marcetic@mainflux.com"
GitHub = "nmarcetic"
+159
View File
@@ -0,0 +1,159 @@
package clients
import (
"fmt"
"strings"
"time"
"log"
"encoding/json"
"github.com/mainflux/mainflux-lite/db"
"github.com/krylovsk/gosenml"
"github.com/kataras/iris"
"gopkg.in/mgo.v2/bson"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
type (
ChannelWriteStatus struct {
Nb int
Str string
}
MqttConn struct {
Opts *mqtt.ClientOptions
Client mqtt.Client
}
)
var (
MqttClient mqtt.Client
WriteStatusChannel chan ChannelWriteStatus
)
//define a function for the default message handler
var msgHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("TOPIC: %s\n", msg.Topic())
fmt.Printf("MSG: %s\n", msg.Payload())
s := strings.Split(msg.Topic(), "/")
chanId := s[len(s)-1]
status := WriteChannel(chanId, msg.Payload())
// Send status to HTTP publisher
WriteStatusChannel <- status
fmt.Println(status)
}
func (mqc *MqttConn) MqttSub() {
// Create a ClientOptions struct setting the broker address, clientid, turn
// off trace output and set the default message handler
mqc.Opts = mqtt.NewClientOptions().AddBroker("tcp://localhost:1883")
mqc.Opts.SetClientID("mainflux")
mqc.Opts.SetDefaultPublishHandler(msgHandler)
//create and start a client using the above ClientOptions
mqc.Client = mqtt.NewClient(mqc.Opts)
if token := mqc.Client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
// Subscribe to all channels of all the devices and request messages to be delivered
// at a maximum qos of zero, wait for the receipt to confirm the subscription
// Topic is in the form:
// mainflux/<channel_id>
if token := mqc.Client.Subscribe("mainflux/+", 0, nil); token.Wait() && token.Error() != nil {
fmt.Println(token.Error())
}
MqttClient = mqc.Client
WriteStatusChannel = make(chan ChannelWriteStatus)
}
/**
* WriteChannel()
* Generic function that updates the channel value.
* Can be called via various protocols
*/
func WriteChannel(id string, bodyBytes []byte) ChannelWriteStatus {
var body map[string]interface{}
if err := json.Unmarshal(bodyBytes, &body); err != nil {
fmt.Println("Error unmarshaling body")
}
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
// Check if someone is trying to change "id" key
// and protect us from this
s := ChannelWriteStatus{}
if _, ok := body["id"]; ok {
s.Nb = iris.StatusBadRequest
s.Str = "Invalid request: 'id' is read-only"
return s
}
if _, ok := body["device"]; ok {
println("Error: can not change device")
s.Nb = iris.StatusBadRequest
s.Str = "Invalid request: 'device' is read-only"
return s
}
if _, ok := body["created"]; ok {
println("Error: can not change device")
s.Nb = iris.StatusBadRequest
s.Str = "Invalid request: 'created' is read-only"
return s
}
senmlDecoder := gosenml.NewJSONDecoder()
m, _ := senmlDecoder.DecodeMessage(bodyBytes)
for _, e := range m.Entries {
// BaseName
e.Name = m.BaseName + e.Name
// BaseTime
e.Time = m.BaseTime + e.Time
if e.Time <= 0 {
e.Time += time.Now().Unix()
}
// BaseUnits
if e.Units == "" {
e.Units = m.BaseUnits
}
/** Insert entry in DB */
colQuerier := bson.M{"id": id}
change := bson.M{"$push": bson.M{"values": e}}
err := Db.C("channels").Update(colQuerier, change)
if err != nil {
log.Print(err)
s.Nb = iris.StatusNotFound
s.Str = "Not inserted"
return s
}
}
// Timestamp
t := time.Now().UTC().Format(time.RFC3339)
body["updated"] = t
/** Then update channel timestamp */
colQuerier := bson.M{"id": id}
change := bson.M{"$set": bson.M{"updated": body["updated"]}}
err := Db.C("channels").Update(colQuerier, change)
if err != nil {
log.Print(err)
s.Nb = iris.StatusNotFound
s.Str = "Not updated"
return s
}
s.Nb = iris.StatusOK
s.Str = "Updated"
return s
}
@@ -6,6 +6,13 @@
# See the included LICENSE file for more details.
###
###
# HTPP Server
###
http:
host: "0.0.0.0"
port: 7070
###
# MongoDB
###
@@ -14,17 +21,3 @@ mongo:
port: 27017
db: "mainflux"
###
# InfluxDB
###
influx:
host: "influx"
port: 8086
db: "mainflux"
###
# NATS
###
nats:
host: "nats"
port: 4222
+68
View File
@@ -0,0 +1,68 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package config
import (
"fmt"
"github.com/spf13/viper"
"os"
)
type Config struct {
// HTTP
HttpHost string
HttpPort int
// Mongo
MongoHost string
MongoPort int
MongoDatabase string
// Influx
InfluxHost string
InfluxPort int
InfluxDatabase string
}
func (this *Config) Parse() {
/**
* Config
*/
/** Viper setup */
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
testEnv := os.Getenv("TEST_ENV")
if testEnv == "" && len(os.Args) > 1 {
// We are not in the TEST_ENV (where different args are provided)
// and provided config file as an argument
viper.SetConfigFile(os.Args[1])
} else {
// default cfg path to source dir, as we keep cfg.yml there
cfgDir := os.Getenv("GOPATH") + "/src/github.com/mainflux/mainflux-lite/config"
viper.SetConfigName("config") // name of config file (without extension)
viper.AddConfigPath(cfgDir) // path to look for the config file in
}
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
this.MongoHost = viper.GetString("mongo.host")
this.MongoPort = viper.GetInt("mongo.port")
this.MongoDatabase = viper.GetString("mongo.db")
this.InfluxHost = viper.GetString("influx.host")
this.InfluxPort = viper.GetInt("influx.port")
this.InfluxDatabase = viper.GetString("influx.db")
this.HttpHost = viper.GetString("http.host")
this.HttpPort = viper.GetInt("http.port")
}
+10 -7
View File
@@ -7,15 +7,18 @@
###
###
# HTTP Server
# HTPP Server
###
server:
host: "0.0.0.0"
http:
host: "0.0.0.0"
port: 7070
###
# NATS
# MongoDB
###
nats:
host: "nats"
port: 4222
mongo:
host: "localhost"
port: 27017
db: "mainflux"
-27
View File
@@ -1,27 +0,0 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0 license.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
var config = {};
/**
* WS Server
*/
config.ws = {
host: '0.0.0.0',
port: 9090,
}
/**
* NATS
*/
config.nats = {
host : 'nats',
port : 4222
}
module.exports = config;
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) Mainflux
*
* FluxMQ is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package main
import (
"time"
)
const (
// VERSION is the current version for the server.
VERSION = "0.0.1"
// DEFAULT_PORT is the default port for client connections.
DEFAULT_PORT = 1833
// DEFAULT_HOST defaults to all interfaces.
DEFAULT_HOST = "0.0.0.0"
// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
// something different if > 1MB payloads are needed.
MAX_PAYLOAD_SIZE = (1024 * 1024)
// MAX_PENDING_SIZE is the maximum outbound size (in bytes) per client.
MAX_PENDING_SIZE = (10 * 1024 * 1024)
// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
DEFAULT_MAX_CONNECTIONS = (64 * 1024)
// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
ACCEPT_MIN_SLEEP = 10 * time.Millisecond
// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
ACCEPT_MAX_SLEEP = 1 * time.Second
// _EMPTY_ is empty string
_EMPTY_ = ""
)
+165
View File
@@ -0,0 +1,165 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package controllers
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/mainflux/mainflux-lite/db"
"github.com/mainflux/mainflux-lite/models"
"github.com/mainflux/mainflux-lite/clients"
"github.com/satori/go.uuid"
"gopkg.in/mgo.v2/bson"
"github.com/kataras/iris"
)
/** == Functions == */
/**
* CreateChannel ()
*/
func CreateChannel(ctx *iris.Context) {
var body map[string]interface{}
ctx.ReadJSON(&body)
/*
if validateJsonSchema("channel", body) != true {
println("Invalid schema")
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid json schema in request"})
return
}
*/
// Init new Mongo session
// and get the "channels" collection
// from this new session
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
c := models.Channel{}
json.Unmarshal(ctx.RequestCtx.Request.Body(), &c)
// Creating UUID Version 4
uuid := uuid.NewV4()
fmt.Println(uuid.String())
c.Id = uuid.String()
// Insert reference to DeviceId
did := ctx.Param("device_id")
c.Device = did
// Timestamp
t := time.Now().UTC().Format(time.RFC3339)
c.Created, c.Updated = t, t
// Insert Channel
err := Db.C("channels").Insert(c)
if err != nil {
ctx.JSON(iris.StatusInternalServerError, iris.Map{"response": "cannot create device"})
return
}
ctx.JSON(iris.StatusCreated, iris.Map{"response": "created", "id": c.Id})
}
/**
* GetChannels()
*/
func GetChannels(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
results := []models.Channel{}
err := Db.C("channels").Find(nil).All(&results)
if err != nil {
log.Print(err)
}
ctx.JSON(iris.StatusOK, &results)
}
/**
* GetChannel()
*/
func GetChannel(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
id := ctx.Param("channel_id")
result := models.Channel{}
err := Db.C("channels").Find(bson.M{"id": id}).One(&result)
if err != nil {
log.Print(err)
ctx.JSON(iris.StatusNotFound, iris.Map{"response": "not found", "id": id})
return
}
ctx.JSON(iris.StatusOK, &result)
}
/**
* UpdateChannel()
*/
func UpdateChannel(ctx *iris.Context) {
var body map[string]interface{}
ctx.ReadJSON(&body)
// Validate JSON schema user provided
/*
if validateJsonSchema("channel", body) != true {
println("Invalid schema")
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid json schema in request"})
return
}
*/
id := ctx.Param("channel_id")
// Publish the channel update.
// This will be catched by the MQTT main client (subscribed to all channel topics)
// and then written in the DB in the MQTT handler
token := clients.MqttClient.Publish("mainflux/" + id, 0, false, string(ctx.RequestCtx.Request.Body()))
token.Wait()
// Wait on status from MQTT handler (which executes DB write)
status := <-clients.WriteStatusChannel
ctx.JSON(status.Nb, iris.Map{"response": status.Str})
}
/**
* DeleteChannel()
*/
func DeleteChannel(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
id := ctx.Param("channel_id")
err := Db.C("channels").Remove(bson.M{"id": id})
if err != nil {
log.Print(err)
ctx.JSON(iris.StatusNotFound, iris.Map{"response": "not deleted", "id": id})
return
}
ctx.JSON(iris.StatusOK, iris.Map{"response": "deleted", "id": id})
}
+174
View File
@@ -0,0 +1,174 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package controllers
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/mainflux/mainflux-lite/db"
"github.com/mainflux/mainflux-lite/models"
"github.com/satori/go.uuid"
"gopkg.in/mgo.v2/bson"
"github.com/kataras/iris"
)
/** == Functions == */
/**
* CreateDevice ()
*/
func CreateDevice(ctx *iris.Context) {
var body map[string]interface{}
ctx.ReadJSON(&body)
if validateJsonSchema("device", body) != true {
println("Invalid schema")
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid json schema in request"})
return
}
// Init new Mongo session
// and get the "devices" collection
// from this new session
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
// Set up defaults and pick up new values from user-provided JSON
d := models.Device{Name: "Some Name"}
json.Unmarshal(ctx.RequestCtx.Request.Body(), &d)
// Creating UUID Version 4
uuid := uuid.NewV4()
fmt.Println(uuid.String())
d.Id = uuid.String()
// Timestamp
t := time.Now().UTC().Format(time.RFC3339)
d.Created, d.Updated = t, t
// Insert Device
erri := Db.C("devices").Insert(d)
if erri != nil {
ctx.JSON(iris.StatusInternalServerError, iris.Map{"response": "cannot create device"})
return
}
ctx.JSON(iris.StatusCreated, iris.Map{"response": "created", "id": d.Id})
}
/**
* GetDevices()
*/
func GetDevices(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
results := []models.Device{}
err := Db.C("devices").Find(nil).All(&results)
if err != nil {
log.Print(err)
}
ctx.JSON(iris.StatusOK, &results)
}
/**
* GetDevice()
*/
func GetDevice(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
id := ctx.Param("device_id")
result := models.Device{}
err := Db.C("devices").Find(bson.M{"id": id}).One(&result)
if err != nil {
log.Print(err)
ctx.JSON(iris.StatusNotFound, iris.Map{"response": "not found", "id": id})
return
}
ctx.JSON(iris.StatusOK, &result)
}
/**
* UpdateDevice()
*/
func UpdateDevice(ctx *iris.Context) {
var body map[string]interface{}
ctx.ReadJSON(&body)
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
id := ctx.Param("device_id")
// Validate JSON schema user provided
if validateJsonSchema("device", body) != true {
println("Invalid schema")
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid json schema in request"})
return
}
// Check if someone is trying to change "id" key
// and protect us from this
if _, ok := body["id"]; ok {
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid request: device id is read-only"})
return
}
if _, ok := body["created"]; ok {
println("Error: can not change device")
ctx.JSON(iris.StatusBadRequest, iris.Map{"response": "invalid request: 'created' is read-only"})
return
}
// Timestamp
t := time.Now().UTC().Format(time.RFC3339)
body["updated"] = t
colQuerier := bson.M{"id": id}
change := bson.M{"$set": body}
err := Db.C("devices").Update(colQuerier, change)
if err != nil {
log.Print(err)
ctx.JSON(iris.StatusNotFound, iris.Map{"response": "not updated", "id": id})
return
}
ctx.JSON(iris.StatusOK, iris.Map{"response": "updated", "id": id})
}
/**
* DeleteDevice()
*/
func DeleteDevice(ctx *iris.Context) {
Db := db.MgoDb{}
Db.Init()
defer Db.Close()
id := ctx.Param("device_id")
err := Db.C("devices").Remove(bson.M{"id": id})
if err != nil {
log.Print(err)
ctx.JSON(iris.StatusNotFound, iris.Map{"response": "not deleted", "id": id})
return
}
ctx.JSON(iris.StatusOK, iris.Map{"response": "deleted", "id": id})
}
@@ -1,20 +1,21 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0 license.
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
var mosca = require('mosca');
package controllers
var config = {};
import (
"github.com/kataras/iris"
)
/** == Functions == */
/**
* Mosca
* getStatus()
*/
config.mosca = {
port: 1883,
func GetStatus(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, iris.Map{"status": "OK"})
}
module.exports = config;
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package controllers
import (
"fmt"
"log"
"os"
"github.com/xeipuuv/gojsonschema"
)
/**
* Function validates JSON schema for `device` od `channel` models
* By convention, Schema files must be kept as:
* - ./models/deviceSchema.json
* - ./models/channelSchema.json
*/
func validateJsonSchema(model string, body map[string]interface{}) bool {
pwd, _ := os.Getwd()
schemaLoader := gojsonschema.NewReferenceLoader("file://" + pwd +
"/models/" + model + "Schema.json")
bodyLoader := gojsonschema.NewGoLoader(body)
result, err := gojsonschema.Validate(schemaLoader, bodyLoader)
if err != nil {
log.Print(err.Error())
}
if result.Valid() {
fmt.Printf("The document is valid\n")
return true
} else {
fmt.Printf("The document is not valid. See errors :\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
return false
}
}
+110
View File
@@ -0,0 +1,110 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package db
import (
"gopkg.in/mgo.v2"
"strconv"
)
var (
mainSession *mgo.Session
mainDb *mgo.Database
DbName string
)
type MgoDb struct {
Session *mgo.Session
Db *mgo.Database
Col *mgo.Collection
}
func InitMongo(host string, port int, db string) error {
var err error
if mainSession == nil {
mainSession, err = mgo.Dial("mongodb://" + host + ":" + strconv.Itoa(port))
if err != nil {
panic(err)
}
mainSession.SetMode(mgo.Monotonic, true)
mainDb = mainSession.DB(db)
DbName = db
}
return err
}
func SetMainSession(s *mgo.Session) {
mainSession = s
mainSession.SetMode(mgo.Monotonic, true)
}
func SetMainDb(db string) {
mainDb = mainSession.DB(db)
DbName = db
}
func (this *MgoDb) Init() *mgo.Session {
this.Session = mainSession.Copy()
this.Db = this.Session.DB(DbName)
return this.Session
}
func (this *MgoDb) C(collection string) *mgo.Collection {
this.Col = this.Session.DB(DbName).C(collection)
return this.Col
}
func (this *MgoDb) Close() bool {
defer this.Session.Close()
return true
}
func (this *MgoDb) DropoDb() {
err := this.Session.DB(DbName).DropDatabase()
if err != nil {
panic(err)
}
}
func (this *MgoDb) RemoveAll(collection string) bool {
this.Session.DB(DbName).C(collection).RemoveAll(nil)
this.Col = this.Session.DB(DbName).C(collection)
return true
}
func (this *MgoDb) Index(collection string, keys []string) bool {
index := mgo.Index{
Key: keys,
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
err := this.Db.C(collection).EnsureIndex(index)
if err != nil {
panic(err)
return false
}
return true
}
func (this *MgoDb) IsDup(err error) bool {
if mgo.IsDup(err) {
return true
}
return false
}
-88
View File
@@ -1,88 +0,0 @@
###
# Copyright (c) Mainflux
#
# Mainflux server is licensed under an Apache license, version 2.0 license.
# All rights not explicitly granted in the Apache license, version 2.0 are reserved.
# See the included LICENSE file for more details.
###
###
# InfluxDB
###
influx:
image: influxdb:latest
container_name: mainflux-influxdb
ports:
- "8083:8083"
- "8086:8086"
###
# MongoDB
###
mongo:
image: mongo:3.2
container_name: mainflux-mongodb
command: --smallfiles --nojournal
###
# NATS
###
nats:
image: apcera/gnatsd:latest
container_name: mainflux-nats
ports:
- "4222:4222"
- "8333:8333"
###
# Mainflux Core Server
###
mainflux-core:
image: mainflux/mainflux-core-server:latest
container_name: mainflux-core
volumes:
- ./config/core/config.yml:/go/src/github.com/mainflux/mainflux-core-server/config.yml
links:
- influx
- mongo
- nats
###
# Mainflux HTTP Server
###
mainflux-http:
image: mainflux/mainflux-http-server:latest
container_name: mainflux-http
volumes:
- ./config/http/config.yml:/go/src/github.com/mainflux/mainflux-http-server/config.yml
links:
- nats
ports:
- "7070:7070"
###
# Mainflux MQTT Server
###
mainflux-mqtt:
image: mainflux/mainflux-mqtt-server:latest
container_name: mainflux-mqtt
volumes:
- ./config/mqtt/config.js:/mainflux-mqtt/config.js
links:
- nats
ports:
- "1883:1883"
###
# Mainflux WS Server
###
mainflux-ws:
image: mainflux/mainflux-ws-server:latest
container_name: mainflux-ws
volumes:
- ./config/ws/config.js:/mainflux-ws/config.js
links:
- nats
ports:
- "9090:9090"
+4 -57
View File
@@ -6,16 +6,6 @@
# See the included LICENSE file for more details.
###
###
# InfluxDB
###
influx:
image: influxdb:latest
container_name: mainflux-influxdb
ports:
- "8083:8083"
- "8086:8086"
###
# MongoDB
###
@@ -25,56 +15,13 @@ mongo:
command: --smallfiles --nojournal
###
# NATS
# Mainflux Lite
###
nats:
image: apcera/gnatsd:latest
container_name: mainflux-nats
ports:
- "4222:4222"
- "8333:8333"
###
# Mainflux Core Server
###
mainflux-core:
image: mainflux/mainflux-core-server:latest
container_name: mainflux-core
mainflux-lite:
image: mainflux/mainflux-lite:latest
container_name: mainflux-lite
links:
- influx
- mongo
- nats
###
# Mainflux HTTP Server
###
mainflux-http:
image: mainflux/mainflux-http-server:latest
container_name: mainflux-http
links:
- nats
ports:
- "7070:7070"
###
# Mainflux MQTT Server
###
mainflux-mqtt:
image: mainflux/mainflux-mqtt-server:latest
container_name: mainflux-mqtt
links:
- nats
ports:
- "1883:1883"
###
# Mainflux WS Server
###
mainflux-ws:
image: mainflux/mainflux-ws-server:latest
container_name: mainflux-ws
links:
- nats
ports:
- "9090:9090"
+155
View File
@@ -0,0 +1,155 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package main
import (
"fmt"
"os"
"strings"
"strconv"
"github.com/mainflux/mainflux-lite/config"
"github.com/mainflux/mainflux-lite/db"
"github.com/mainflux/mainflux-lite/servers"
"github.com/mainflux/mainflux-lite/clients"
"github.com/fatih/color"
"runtime"
"flag"
)
type MainfluxLite struct {
}
var usageStr = `
Usage: mainflux [options]
Server Options:
-a, --addr <host> Bind to host address (default: 0.0.0.0)
-p, --port <port> Use port for clients (default: 4222)
-P, --pid <file> File to store PID
-c, --config <file> Configuration file
Logging Options:
-l, --log <file> File to redirect log output
-T, --logtime Timestamp log entries (default: true)
-D, --debug Enable debugging output
-V, --trace Trace the raw protocol
-DV Debug and trace
Common Options:
-h, --help Show this message
-v, --version Show version
`
// usage will print out the flag options for the server.
func usage() {
fmt.Printf("%s\n", usageStr)
os.Exit(0)
}
// PrintServerAndExit will print our version and exit.
func PrintServerAndExit() {
fmt.Printf("Mainflux version %s\n", VERSION)
os.Exit(0)
}
func main() {
// Server Options
opts := Options{}
var showVersion bool
var debugAndTrace bool
var configFile string
// Parse flags
flag.IntVar(&opts.Port, "port", 0, "Port to listen on.")
flag.IntVar(&opts.Port, "p", 0, "Port to listen on.")
flag.StringVar(&opts.Host, "host", "", "Network host to listen on.")
flag.StringVar(&opts.Host, "h", "", "Network host to listen on.")
flag.StringVar(&opts.Host, "net", "", "Network host to listen on.")
flag.BoolVar(&opts.Debug, "D", false, "Enable Debug logging.")
flag.BoolVar(&opts.Debug, "debug", false, "Enable Debug logging.")
flag.BoolVar(&opts.Trace, "V", false, "Enable Trace logging.")
flag.BoolVar(&opts.Trace, "trace", false, "Enable Trace logging.")
flag.BoolVar(&debugAndTrace, "DV", false, "Enable Debug and Trace logging.")
flag.BoolVar(&opts.Logtime, "T", true, "Timestamp log entries.")
flag.BoolVar(&opts.Logtime, "logtime", true, "Timestamp log entries.")
flag.StringVar(&opts.Username, "user", "", "Username required for connection.")
flag.StringVar(&opts.Password, "pass", "", "Password required for connection.")
flag.StringVar(&opts.Authorization, "auth", "", "Authorization token required for connection.")
flag.StringVar(&configFile, "c", "", "Configuration file.")
flag.StringVar(&configFile, "config", "", "Configuration file.")
flag.StringVar(&opts.PidFile, "P", "", "File to store process pid.")
flag.StringVar(&opts.PidFile, "pid", "", "File to store process pid.")
flag.StringVar(&opts.LogFile, "l", "", "File to store logging output.")
flag.StringVar(&opts.LogFile, "log", "", "File to store logging output.")
flag.BoolVar(&showVersion, "version", false, "Print version information.")
flag.BoolVar(&showVersion, "v", false, "Print version information.")
flag.Usage = usage
flag.Parse()
// Show version and exit
if showVersion {
PrintServerAndExit()
}
// One flag can set multiple options.
if debugAndTrace {
opts.Trace, opts.Debug = true, true
}
// Process args looking for non-flag options,
// 'version' and 'help' only for now
for _, arg := range flag.Args() {
switch strings.ToLower(arg) {
case "version":
PrintServerAndExit()
case "help":
usage()
}
}
// Parse config
var cfg config.Config
cfg.Parse()
// MongoDb
db.InitMongo(cfg.MongoHost, cfg.MongoPort, cfg.MongoDatabase)
// MQTT
mqc := new(clients.MqttConn)
//Sub to everything comming on all channels of all devices
mqc.MqttSub()
// Serve HTTP
go servers.HttpServer(cfg)
// Print banner
color.Cyan(banner)
color.Cyan("Magic happens on port " + strconv.Itoa(cfg.HttpPort))
/** Keep main() runnig */
runtime.Goexit()
}
var banner = `
_| _| _| _|_| _|
_|_| _|_| _|_|_| _|_|_| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _|_|_|_| _| _| _| _|_|
_| _| _| _| _| _| _| _| _| _| _| _| _|
_| _| _|_|_| _| _| _| _| _| _|_|_| _| _|
== Industrial IoT System ==
Made with <3 by Mainflux Team
[w] http://mainflux.io
[t] @mainflux
** LITE **
`
+20
View File
@@ -0,0 +1,20 @@
## JSON Modles
Mainflux uses 2 entities in the system to represent devices and their properties:
- device
- channel
**Device** represents device itself - model, type, serial number, location...
**Channel** represents an observable property of device that we measure - temperature, pressure, light, velocity...
### Creating JSON Schema From the Model Templates
We use `deviceTemplate.json` and `channelTemplate.json` to describe our entities. Then based on these files
we can create more decriptive documents - [JSON Schemas](http://json-schema.org/).
To do this we can use on-line tool [http://jsonschema.net/](http://jsonschema.net/) or `npm` package `json-schema-generator`:
```bash
sudo npm install -g json-schema-generator
json-schema-generator ./deviceTemplate.json -o deviceSchema.json
```
Schemas will be used to perform JSON schema validation during API calls, as described in [this article](http://www.litixsoft.de/english/mms-json-schema/)
+26
View File
@@ -0,0 +1,26 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package models
import (
"github.com/krylovsk/gosenml"
)
type (
Channel struct {
Id string `json:"id"`
Device string `json:"device"`
Created string `json:"created"`
Updated string `json:"updated"`
Values []gosenml.Entry `json:"values"`
Metadata map[string]interface{} `json:"metadata"`
}
)
+89
View File
@@ -0,0 +1,89 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"e": {
"type": "array",
"items": {
"type": "object",
"properties": {
"n": {
"type": "string",
"default": ""
},
"u": {
"type": "string",
"default": ""
},
"v": {
"type": "number",
"default": 0
},
"sv": {
"type": "string",
"default": ""
},
"bv": {
"type": "boolean",
"default": false
},
"s": {
"type": "number",
"default": 0
},
"t": {
"type": "integer",
"default": 0
},
"ut": {
"type": "integer",
"default": 0
}
},
"default": {"n": "", "u": "", "v": 0, "sv": "", "bv": false, "s": 0, "t": 0, "ut": 0},
"required": [
"n"
]
}
},
"bn": {
"type": "string",
"default": ""
},
"bt": {
"type": "integer",
"default": 0
},
"ver": {
"type": "integer",
"default": 0
},
"bu": {
"type": "string",
"default": ""
},
"id": {
"type": "string",
"default": ""
},
"device": {
"type": "string",
"default": ""
},
"created": {
"type": "string",
"default": ""
},
"updated": {
"type": "string",
"default": ""
},
"metadata": {
"type": "object",
"properties": {}
}
},
"additionalProperties": false,
"required": [
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"ts" : {
"e":[
{
"n": "voltage",
"u": "V",
"v": 120.1,
"sv": "one-two",
"bv": true,
"s": 321.12,
"t": -5,
"ut": -1
},
{ "n": "current", "t": -5, "v": 1.2 },
{ "n": "current", "t": -4, "v": 1.30 },
{ "n": "current", "t": -3, "v": 0.14e1 },
{ "n": "current", "t": -2, "v": 1.5 },
{ "n": "current", "t": -1, "v": 1.6 },
{ "n": "current", "t": 0, "v": 1.7 }
],
"bn": "",
"bt": 1276020076,
"ver": 1,
"bu": "A"
},
"id": "88fce370-1c68-11e6-8610-27f9510f1a02",
"device": "9ef8de30-20dd-11e6-9e63-43ae8893b86e",
"created": "2014-09-01T10:00:00.764Z",
"updated": "2014-09-02T10:00:00.245Z",
"metadata": {}
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package models
type (
DeviceLocation struct {
Name string `json:"name"`
Latitude int `json:"latitude"`
Longitude int `json:"longitude"`
Elevation int `json:"elevation"`
}
Device struct {
Id string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Visibility string `json:"visibility"`
Status string `json:"status"`
Tags []string `json:"tags"`
Location DeviceLocation `json:"location"`
Created string `json:"created"`
Updated string `json:"updated"`
Metadata map[string]interface{} `json:"metadata"`
}
)
+72
View File
@@ -0,0 +1,72 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"id": {
"type": "string",
"default": "",
"readOnly": true
},
"name": {
"type": "string",
"default": "Unnamed Device"
},
"description": {
"type": "string",
"default": ""
},
"visibility": {
"type": "string",
"default": "private"
},
"status": {
"type": "string",
"default": "disabled"
},
"tags": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
},
"location": {
"type": "object",
"properties": {
"name": {
"type": "string",
"default": ""
},
"latitude": {
"type": "number",
"default": 0
},
"longitude": {
"type": "number",
"default": 0
},
"elevation": {
"type": "integer",
"default": 0
}
},
"default": {"name": "", "latitude": 0, "longitude": 0, "elevation": 0}
},
"created": {
"type": "string",
"default": "",
"reaOnly": true
},
"updated": {
"type": "string",
"default": "",
"readOnly": true
},
"metadata": {
"type": "object",
"properties": {}
}
},
"additionalProperties": false
}
+18
View File
@@ -0,0 +1,18 @@
{
"id": "88fce370-1c68-11e6-8610-27f9510f1a02",
"name": "Some Device",
"description": "Longer description for Some Device",
"visibility": "private",
"status": "enabled",
"tags": [ "lorem", "ipsum" ],
"location": {
"name": "Storage Room",
"latitude": -37.9788423562422,
"longitude": -57.5478776916862,
"elevation": 5
},
"created": "2014-09-01T10:00:00.764Z",
"updated": "2014-09-02T10:00:00.245Z",
"metadata": {}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) Mainflux
*
* FluxMQ is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package main
// Options block for gnatsd server.
type Options struct {
Host string
Port int
Trace bool
Debug bool
MaxConn int
Logtime bool
Authorization string
Username string
Password string
PidFile string
LogFile string
}
+69
View File
@@ -0,0 +1,69 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package servers
import (
"strconv"
"github.com/mainflux/mainflux-lite/controllers"
"github.com/mainflux/mainflux-lite/config"
"github.com/iris-contrib/middleware/logger"
"github.com/kataras/iris"
)
func HttpServer(cfg config.Config) {
// Iris config
iris.Config.DisableBanner = true
// set the global middlewares
iris.Use(logger.New())
// set the custom errors
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.Render("errors/404.html", iris.Map{"Title": iris.StatusText(iris.StatusNotFound)})
})
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
ctx.Render("errors/500.html", nil, iris.RenderOptions{"layout": iris.NoLayout})
})
// register public API
registerRoutes()
// start the server
iris.Listen(cfg.HttpHost + ":" + strconv.Itoa(cfg.HttpPort))
// Use following to start HTTPS server on the same port
//iris.ListenTLS(cfg.HttpHost + ":" + strconv.Itoa(cfg.HttpPort), "tls/mainflux.crt", "tls/mainflux.key")
}
func registerRoutes() {
// STATUS
iris.Get("/status", controllers.GetStatus)
// DEVICES
iris.Post("/devices", controllers.CreateDevice)
iris.Get("/devices", controllers.GetDevices)
iris.Get("/devices/:device_id", controllers.GetDevice)
iris.Put("/devices/:device_id", controllers.UpdateDevice)
iris.Delete("/devices/:device_id", controllers.DeleteDevice)
// CHANNELS
iris.Post("/devices/:device_id/channels", controllers.CreateChannel)
iris.Get("/devices/:device_id/channels", controllers.GetChannels)
iris.Get("/devices/:device_id/channels/:channel_id", controllers.GetChannel)
iris.Put("/devices/:device_id/channels/:channel_id", controllers.UpdateChannel)
iris.Delete("/devices/:device_id/channels/:channel_id", controllers.DeleteChannel)
}
+90
View File
@@ -0,0 +1,90 @@
/**
* Copyright (c) Mainflux
*
* Mainflux server is licensed under an Apache license, version 2.0.
* All rights not explicitly granted in the Apache license, version 2.0 are reserved.
* See the included LICENSE file for more details.
*/
package servers
import (
"fmt"
"testing"
"time"
"log"
"os"
"github.com/mainflux/mainflux-lite/config"
mfdb "github.com/mainflux/mainflux-lite/db"
"github.com/kataras/iris"
"github.com/ory-am/dockertest"
"gopkg.in/mgo.v2"
)
func TestMain(m *testing.M) {
// We are in testing - notify the program
// so that it is not confused if some other commad line
// arguments come in - for example when test is started with `go test -v ./...`
// which is what Travis does
os.Setenv("TEST_ENV", "1")
var db *mgo.Session
c, err := dockertest.ConnectToMongoDB(15, time.Millisecond*500, func(url string) bool {
// This callback function checks if the image's process is responsive.
// Sometimes, docker images are booted but the process (in this case MongoDB) is still doing maintenance
// before being fully responsive which might cause issues like "TCP Connection reset by peer".
var err error
db, err = mgo.Dial(url)
if err != nil {
return false
}
// Sometimes, dialing the database is not enough because the port is already open but the process is not responsive.
// Most database conenctors implement a ping function which can be used to test if the process is responsive.
// Alternatively, you could execute a query to see if an error occurs or not.
return db.Ping() == nil
})
if err != nil {
log.Fatalf("Could not connect to database: %s", err)
}
// Set-up DB
mfdb.SetMainSession(db)
mfdb.SetMainDb("mainflux_test")
// Run tests
result := m.Run()
// Close database connection.
db.Close()
// Clean up image.
c.KillRemove()
// Exit tests.
os.Exit(result)
}
func TestServer(t *testing.T) {
// Config
var cfg config.Config
cfg.Parse()
go HttpServer(cfg)
// prepare test framework
if ok := <-iris.Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
}
e := iris.Tester(t)
r := e.Request("GET", "/status").Expect().Status(iris.StatusOK).JSON()
fmt.Println("%v", r)
}
-16
View File
@@ -1,16 +0,0 @@
## Locust Performance Testing
Locust website: http://locust.io/
Locust is an easy-to-use, distributed, user load testing tool.
Intended for load testing web sites (or other systems) and figuring out how many
concurrent users a system can handle.
## Usage
```bash
locust -f locustfile.py --host=http://localhost:1026
```
Open the browser at localhost:8089 and start the swarm.
## Documentation
http://docs.locust.io/en/latest/index.html
-19
View File
@@ -1,19 +0,0 @@
from locust import HttpLocust, TaskSet, task, ResponseError
class CheckStatus(TaskSet):
def on_start(self):
""" on_start is called when a Locust start before any task is scheduled """
print("Locus test started")
@task(1)
def getVersion(self):
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
response = self.client.get("/version", headers=headers)
print "Response status code:", response.status_code
print "Response content:", response.content
class HelloLocust(HttpLocust):
task_set = CheckStatus
min_wait=5000
max_wait=9000
+3
View File
@@ -0,0 +1,3 @@
# TLS in Mainflux
Documentation on TLS in Mainflux can be found in official [mainflux-doc](https://github.com/Mainflux/mainflux-doc) repo.
+32
View File
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkzCCA3ugAwIBAgIJAPdKjAQxW89pMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
BAYTAkZSMQwwCgYDVQQIDANJREYxDjAMBgNVBAcMBVBhcmlzMREwDwYDVQQKDAhN
YWluZmx1eDEMMAoGA1UECwwDSW9UMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTYw
OTExMTcxNjA2WhcNMTcwOTExMTcxNjA2WjBgMQswCQYDVQQGEwJGUjEMMAoGA1UE
CAwDSURGMQ4wDAYDVQQHDAVQYXJpczERMA8GA1UECgwITWFpbmZsdXgxDDAKBgNV
BAsMA0lvVDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOC
Ag8AMIICCgKCAgEAw0fcK9O+VhJ6RESuHtI2+ej6fAyDZEXq5hK5vP9VNlU20K5B
/B7cWNlxhFvryt/D4aCnRpES1DZ9Q9SqIQ9+4oig1VFng/latA12NKgZbPYctorc
UlQcukfibVsplKhQd3vW6BnDjRHkIv/Uj+FdMkKpf9Ei4LgdsnXIYv8Fo68fQtZj
MXKHsRl5n7kqNnCihbgbIYFWAPHndOUtWaV+jEyWMj0PYXnhSjipRh7Y10PObKXi
Cx2Erh8Ubp48VgGbRbykRuzhWydIE5nZuZNwZAYqS8CvqIRMnCkMiNJs695z3kGi
9voBof6jCuqG5TpCEMmDbGwTAd2UchScq4ZBj/7AAKS5dEanw1FTlsKArMhbCW11
B9bQ2IsM4TexGixMVaILHM+WRWgtqz01+5Y2OIwNJWWYdo5EFAxQ6b8k4B980tB6
L4lBtH3zcWHjbt2qf59BQmfUxV8zuNjaEZ46IW0+BBBwnYW5UzFdHxl9pnFol2gt
jF3ZGQ0pXS3bZ1rGz+pQlDYsng7DD14L4VN0VGV49jmvcNVlmt5Mb5UdX2qVClYs
WEByqe6oGDjd3WgF2V0L7cWufGbv++nlA1eYqPRl6cRiiSW8b0tkgRkmItBjpKrU
4Xmg/Nag+ePKexTn3mGiJkNjaiNZK4TgEu5uilhf+MS7JGkLuCsZniBsuRcCAwEA
AaNQME4wHQYDVR0OBBYEFPAQrGHSYXiwn/NJSrrwCjRpL58nMB8GA1UdIwQYMBaA
FPAQrGHSYXiwn/NJSrrwCjRpL58nMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggIBAFjOzOPxi/KALpTfromRk7/l2aflGiwsJpQ2kNumQCps51tLIE1sbwIj
SrBkJRFR/G2fPsV15y3SWkrsZK67NvKzQNJ1fWbbZ0O5GZStXGzO8g90hBPVfKnj
AbKnS+EFTF/oMUFinAYW7C8NbwdESudcvsw1XBXineg0uyIC9hBUTSYPhgcI5e35
/L+kUHrcYvTA8SXTLspOH3ERaPrFkz90djqJkK5uLcxEn87oAvr/0KtihyfqWpWB
PlnJXny6KmK5bLYx2Ws2FD+jaL7dtXcbACka0zZ3eQyPGLOQXmuqQCcJgg0A9tLf
VJfe4S399nqWEtfAOYqbG6Lz8w+rEmWeIjMeMNnhXpDuNK+FQswqEZxVE5OxaVss
cLcpgSUY7UwCxBdg8vQmggFm2sEpMMBMHXEC6FMp9/5VLVpqSV1JxGqnP82yFiJh
207B+s/lYiixAsbETGvmdjBrGP0OecY1mt7xE5OjLlD3uLsMew0mqBXM/LTKKAPG
HOtHAFvnCgITi9yKRgUs9880jaJgSaAvC+dbx1OTtckTIjDHf7oant6whwJrfJqe
WWLhHXRprejO+uiOTUaFuG8tbcK++MxyklUtEtCESkYu66fD8jbYe14L/3+KbRDz
3kj4BWUMxrb8pKd/18Sr6PzQnjihEgFTqSd1u0SXhWmCbcoV/ARw
-----END CERTIFICATE-----
+52
View File
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
crIHY4DycNHKqah++V/w4m5ASqFIZT4=
-----END PRIVATE KEY-----
@@ -1,88 +0,0 @@
{
"id": "d9799076-e11c-8926-da5e-9092763cf5bb",
"name": "mainfluxHttp",
"description": "",
"order": [
"b27812bf-0e9b-7a20-102c-b901c535e35f",
"2609fa8b-e39b-5cd9-407e-81ff4d1124b7",
"c28e0d5b-b271-960b-6b41-f3c2b52670b1",
"887ad31c-2e30-f600-1035-68c38c11c3f7"
],
"folders": [],
"timestamp": 1463596373226,
"owner": "",
"remoteLink": "",
"public": false,
"requests": [
{
"id": "2609fa8b-e39b-5cd9-407e-81ff4d1124b7",
"url": "http://localhost:7070/devices/3686f6d0-1c68-11e6-8610-27f9510f1a02",
"method": "DELETE",
"headers": "Content-Type: application/json\n",
"data": null,
"dataMode": "params",
"tests": null,
"preRequestScript": null,
"currentHelper": "normal",
"pathVariables": {},
"version": 2,
"name": "http://localhost:7070/devices/3686f6d0-1c68-11e6-8610-27f9510f1a02",
"description": "",
"descriptionFormat": "html",
"collectionId": "d9799076-e11c-8926-da5e-9092763cf5bb"
},
{
"id": "887ad31c-2e30-f600-1035-68c38c11c3f7",
"url": "http://localhost:7070/devices",
"method": "POST",
"headers": "Content-Type: application/json\n",
"data": [],
"dataMode": "raw",
"tests": null,
"preRequestScript": null,
"currentHelper": "normal",
"pathVariables": {},
"version": 2,
"name": "http://localhost:7070/devices",
"description": "",
"descriptionFormat": "html",
"collectionId": "d9799076-e11c-8926-da5e-9092763cf5bb",
"rawModeData": "{\n \"deviceName\" : \"board01\",\n \"deviceType\" : \"weio\"\n}"
},
{
"id": "b27812bf-0e9b-7a20-102c-b901c535e35f",
"url": "http://localhost:7070/devices",
"method": "GET",
"headers": "Content-Type: application/json\n",
"data": null,
"dataMode": "params",
"tests": null,
"preRequestScript": null,
"currentHelper": "normal",
"pathVariables": {},
"version": 2,
"name": "http://localhost:7070/devices",
"description": "",
"descriptionFormat": "html",
"collectionId": "d9799076-e11c-8926-da5e-9092763cf5bb"
},
{
"id": "c28e0d5b-b271-960b-6b41-f3c2b52670b1",
"url": "http://localhost:7070/devices/3686f6d0-1c68-11e6-8610-27f9510f1a02",
"method": "PUT",
"headers": "Content-Type: application/json\n",
"data": [],
"dataMode": "raw",
"tests": null,
"preRequestScript": null,
"currentHelper": "normal",
"pathVariables": {},
"version": 2,
"name": "http://localhost:7070/devices/3686f6d0-1c68-11e6-8610-27f9510f1a02",
"description": "",
"descriptionFormat": "html",
"collectionId": "d9799076-e11c-8926-da5e-9092763cf5bb",
"rawModeData": "{\n \"deviceName\" : \"board05\",\n \"deviceType\" : \"ESP7766\",\n \"attributes\": [\n {\n \"temperature\": 15\n },\n {\n \"pressure\": 10\n \n }\n ]\n}\n"
}
]
}