Initial commit
This commit is contained in:
commit
f6de3fdfbf
|
|
@ -0,0 +1,6 @@
|
|||
GIN_MODE=debug
|
||||
DATABASE_URL=./sensors.sqlite3
|
||||
PORT=8083
|
||||
BIND_IP=localhost
|
||||
AUTH_USERNAME=admin
|
||||
AUTH_PASSWORD=password
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.env
|
||||
basic-sensor-receiver.exe
|
||||
basic-sensor-receiver
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package app
|
||||
|
||||
import "database/sql"
|
||||
|
||||
func initializeDb(databaseUrl string) *sql.DB {
|
||||
db, err := sql.Open("sqlite3", databaseUrl)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_values (
|
||||
timestamp INTEGER NOT NULL,
|
||||
sensor TEXT NOT NULL,
|
||||
value REAL NOT NULL
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_config (
|
||||
sensor TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
PRIMARY KEY (sensor, key)
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type sensorConfigValue struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (s *Server) HandlePutSensorConfig() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var configValue sensorConfigValue
|
||||
sensor := c.Param("sensor")
|
||||
key := c.Param("key")
|
||||
|
||||
if err := c.BindJSON(&configValue); err != nil {
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.Services.SensorConfig.SetValue(sensor, key, configValue.Value); err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetSensorConfig() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
sensor := c.Param("sensor")
|
||||
|
||||
config, err := s.Services.SensorConfig.GetValues(sensor)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, config)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type postSensorValueBody struct {
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type getSensorQuery struct {
|
||||
From int64 `form:"from"`
|
||||
To int64 `form:"to"`
|
||||
}
|
||||
|
||||
func (s *Server) HandlePostSensorValues() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var newValue postSensorValueBody
|
||||
sensor := c.Param("sensor")
|
||||
|
||||
if err := c.BindJSON(&newValue); err != nil {
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := s.Services.SensorValues.Push(sensor, newValue.Value); err != nil {
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) HandleGetSensorValues() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var query getSensorQuery
|
||||
|
||||
sensor := c.Param("sensor")
|
||||
|
||||
if err := c.BindQuery(&query); err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
values, err := s.Services.SensorValues.GetList(sensor, query.From, query.To)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, values)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Server) GetSensors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
sensors, err := s.Services.Sensors.GetList()
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, sensors)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/config"
|
||||
"basic-sensor-receiver/services"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
DB *sql.DB
|
||||
Config *config.Config
|
||||
Services *services.Services
|
||||
}
|
||||
|
||||
func InitializeServer() *Server {
|
||||
server := Server{}
|
||||
server.Config = config.LoadConfig()
|
||||
server.DB = initializeDb(server.Config.DatabaseUrl)
|
||||
|
||||
ctx := services.Context{DB: server.DB, Config: server.Config}
|
||||
|
||||
server.Services = services.InitializeServices(&ctx)
|
||||
|
||||
return &server
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
body {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#sensors-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.sensor {
|
||||
background: #fff;
|
||||
border-radius: 0.5rem;
|
||||
margin: 0.5rem;
|
||||
box-shadow: 0px 10px 15px -3px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sensor .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.sensor .header .actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sensors</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<script src="https://cdn.plot.ly/plotly-2.14.0.min.js"></script>
|
||||
<script src="js/lighterhtml@4.2.0.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sensors-container"></div>
|
||||
<script src="js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
const { html, render } = lighterhtml;
|
||||
const $container = document.getElementById("sensors-container");
|
||||
|
||||
function load() {
|
||||
fetch("/api/sensors")
|
||||
.then((r) => r.json())
|
||||
.then((sensors) => {
|
||||
const components = sensors.map((sensor) => createSensor(sensor));
|
||||
|
||||
components.forEach((component) =>
|
||||
$container.appendChild(component.container)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function createSensor(sensor) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "sensor";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "header";
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "body";
|
||||
|
||||
const renderHeader = () => {
|
||||
render(
|
||||
header,
|
||||
html`
|
||||
<div class="name">${sensor.config?.name ?? sensor.sensor}</div>
|
||||
<div class="actions">
|
||||
<button class="config">Config</button>
|
||||
<button class="refresh" onClick=${refreshValues}>Refresh</button>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
const renderBody = (range, values) => {
|
||||
const { from, to } = range;
|
||||
|
||||
Plotly.newPlot(
|
||||
body,
|
||||
[
|
||||
{
|
||||
type: "lines",
|
||||
x: values.map((v) => new Date(v.timestamp * 1000)),
|
||||
y: values.map((v) => v.value),
|
||||
},
|
||||
],
|
||||
{
|
||||
xaxis: { range: [from, to], type: "date" },
|
||||
margin: {
|
||||
l: 50,
|
||||
r: 20,
|
||||
b: 60,
|
||||
t: 20,
|
||||
pad: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
responsive: true,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const refreshValues = () => {
|
||||
const from = new Date(Date.now() - 5 * 24 * 3600 * 1000);
|
||||
const to = new Date();
|
||||
|
||||
fetch(
|
||||
`/api/sensors/${sensor.sensor}/values?from=${Math.round(
|
||||
from.getTime() / 1000
|
||||
)}&to=${Math.round(to.getTime() / 1000)}`
|
||||
)
|
||||
.then((r) => r.json())
|
||||
.then((values) => renderBody({ from, to }, values));
|
||||
};
|
||||
|
||||
renderHeader();
|
||||
refreshValues();
|
||||
|
||||
container.appendChild(header);
|
||||
container.appendChild(body);
|
||||
|
||||
return {
|
||||
container,
|
||||
};
|
||||
}
|
||||
|
||||
load();
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,34 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/golobby/dotenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Mode string `env:"GIN_MODE"`
|
||||
DatabaseUrl string `env:"DATABASE_URL"`
|
||||
Port int `env:"PORT"`
|
||||
Ip string `env:"BIND_IP"`
|
||||
AuthUsername string `env:"AUTH_USERNAME"`
|
||||
AuthPassword string `env:"AUTH_PASSWORD"`
|
||||
}
|
||||
|
||||
func LoadConfig() *Config {
|
||||
config := Config{}
|
||||
|
||||
file, err := os.Open(".env")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = dotenv.NewDecoder(file).Decode(&config)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &config
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
module basic-sensor-receiver
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/golobby/dotenv v1.3.1
|
||||
github.com/mattn/go-sqlite3 v1.14.14
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/goccy/go-json v0.9.10 // indirect
|
||||
github.com/golobby/cast v1.3.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
|
||||
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golobby/cast v1.3.0 h1:8nM9nYU5Pzi1LWXwISx0xhW/7oWXPt9r0hdTC1nnPSI=
|
||||
github.com/golobby/cast v1.3.0/go.mod h1:WCusT3z1fzp4XVBUGbWy61insoQS8CPJHNTQwlW8qnM=
|
||||
github.com/golobby/dotenv v1.3.1 h1:BvQyNuOQITmIXNHpQ/FUG2gZcUGmcGMyODMeUfiKkeU=
|
||||
github.com/golobby/dotenv v1.3.1/go.mod h1:EWUdOzuDlA1g4hdjo++WD37DhNZw33Oce8ryH3liZTQ=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/app"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server := app.InitializeServer()
|
||||
|
||||
gin.SetMode(server.Config.Mode)
|
||||
|
||||
router := gin.Default()
|
||||
|
||||
protected := router.Group("/", gin.BasicAuth(gin.Accounts{
|
||||
server.Config.AuthUsername: server.Config.AuthPassword,
|
||||
}))
|
||||
|
||||
protected.StaticFile("/", "client/index.html")
|
||||
protected.Static("/js", "client/js")
|
||||
protected.Static("/css", "client/css")
|
||||
|
||||
protected.GET("/api/sensors", server.GetSensors())
|
||||
protected.GET("/api/sensors/:sensor/values", server.HandleGetSensorValues())
|
||||
protected.POST("/api/sensors/:sensor/values", server.HandlePostSensorValues())
|
||||
protected.GET("/api/sensors/:sensor/config", server.GetSensorConfig())
|
||||
protected.PUT("/api/sensors/:sensor/config/:key", server.HandlePutSensorConfig())
|
||||
|
||||
router.Run(fmt.Sprintf("%s:%d", server.Config.Ip, server.Config.Port))
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,41 @@
|
|||
package services
|
||||
|
||||
type SensorConfigService struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func (s *SensorConfigService) SetValue(sensor string, key string, value string) error {
|
||||
_, err := s.ctx.DB.Exec("INSERT OR REPLACE INTO sensor_config (sensor, key, value) VALUES (?, ?, ?)", sensor, key, value)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SensorConfigService) GetValues(sensor string) (map[string]string, error) {
|
||||
var key string
|
||||
var value string
|
||||
|
||||
config := make(map[string]string)
|
||||
|
||||
rows, err := s.ctx.DB.Query("SELECT key, value FROM sensor_config WHERE sensor = ?", sensor)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&key, &value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package services
|
||||
|
||||
import "time"
|
||||
|
||||
type SensorValuesService struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
type sensorValue struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
func (s *SensorValuesService) Push(sensor string, value float64) (int64, error) {
|
||||
res, err := s.ctx.DB.Exec("INSERT INTO sensor_values (timestamp, sensor, value) VALUES (?, ?, ?)", time.Now().Unix(), sensor, value)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (s *SensorValuesService) GetList(sensor string, from int64, to int64) ([]sensorValue, error) {
|
||||
var value float64
|
||||
var timestamp int64
|
||||
var values []sensorValue
|
||||
|
||||
rows, err := s.ctx.DB.Query("SELECT timestamp, value FROM sensor_values WHERE sensor = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp ASC", sensor, from, to)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(×tamp, &value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values = append(values, sensorValue{Timestamp: timestamp, Value: value})
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package services
|
||||
|
||||
type SensorsService struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
type sensorItem struct {
|
||||
Sensor string `json:"sensor"`
|
||||
LastUpdate string `json:"lastUpdate"`
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
func (s *SensorsService) GetList() ([]sensorItem, error) {
|
||||
var sensors []sensorItem
|
||||
var sensor string
|
||||
var lastUpdate string
|
||||
|
||||
rows, err := s.ctx.DB.Query("SELECT sensor, MAX(timestamp) last_update FROM sensor_values GROUP BY sensor")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&sensor, &lastUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := s.ctx.Services.SensorConfig.GetValues(sensor)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sensors = append(sensors, sensorItem{Sensor: sensor, LastUpdate: lastUpdate, Config: config})
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sensors, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/config"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Services struct {
|
||||
SensorConfig *SensorConfigService
|
||||
SensorValues *SensorValuesService
|
||||
Sensors *SensorsService
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
DB *sql.DB
|
||||
Config *config.Config
|
||||
Services *Services
|
||||
}
|
||||
|
||||
func InitializeServices(ctx *Context) *Services {
|
||||
services := Services{}
|
||||
|
||||
ctx.Services = &services
|
||||
|
||||
services.SensorConfig = &SensorConfigService{ctx: ctx}
|
||||
services.SensorValues = &SensorValuesService{ctx: ctx}
|
||||
services.Sensors = &SensorsService{ctx: ctx}
|
||||
|
||||
return &services
|
||||
}
|
||||
Loading…
Reference in New Issue