initial version of http/rest API
This commit is contained in:
parent
8f996ffee1
commit
6827006f94
304
api/http/config.go
Normal file
304
api/http/config.go
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
ccc "th7/config"
|
||||||
|
"th7/data/config"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrConfigChannelNotFound = errors.New("a configured channel with this ID was not found")
|
||||||
|
ErrConfigRestrictedDB = errors.New("access to the DB map is restricted")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *GinAdapter) getConfigByID(id int) (config.Channel, error) {
|
||||||
|
for channel := range g.cfg.Channels {
|
||||||
|
if g.cfg.Channels[channel].Id == id {
|
||||||
|
return g.cfg.Channels[channel], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Channel{}, ErrConfigChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a supplied key string is in the device struct (TH7)
|
||||||
|
func configDeviceIsValidKey(key string) bool {
|
||||||
|
deviceKeys := []string{
|
||||||
|
"debug", "nolog", "noweb", "led",
|
||||||
|
}
|
||||||
|
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
|
||||||
|
for k := range deviceKeys {
|
||||||
|
if key == deviceKeys[k] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func configDeviceIsValidValue(key string, value string) bool {
|
||||||
|
// all the values are bools for now
|
||||||
|
value = strings.ToLower(value)
|
||||||
|
return (value == "true" || value == "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this doesn't actually do anything to program state... yet
|
||||||
|
func (g *GinAdapter) updateConfigDevice(key string, value string) error {
|
||||||
|
bValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
|
||||||
|
// might have to sync these writes ?
|
||||||
|
// go doesn't have switch case fall-thru
|
||||||
|
switch key {
|
||||||
|
case "led":
|
||||||
|
g.cfg.Board.Led = bValue
|
||||||
|
|
||||||
|
case "nolog":
|
||||||
|
g.cfg.Board.NoLog = bValue
|
||||||
|
|
||||||
|
case "noweb":
|
||||||
|
g.cfg.Board.NoWeb = bValue
|
||||||
|
|
||||||
|
case "debug":
|
||||||
|
g.cfg.Board.Debug = bValue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) validConfigChannel(id int) bool {
|
||||||
|
|
||||||
|
for c := range g.cfg.Channels {
|
||||||
|
if g.cfg.Channels[c].Id == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func configChannelIsValidKey(key string) bool {
|
||||||
|
// will add more later
|
||||||
|
channelKeys := []string{
|
||||||
|
"thermocouple", "gain", "offset",
|
||||||
|
}
|
||||||
|
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
|
||||||
|
for k := range channelKeys {
|
||||||
|
if key == channelKeys[k] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) configChannelUpdateValue(id int, key string, value string) error {
|
||||||
|
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
value = strings.ToLower(value)
|
||||||
|
var cfgChannelPtr *config.Channel
|
||||||
|
foundChannel := false
|
||||||
|
|
||||||
|
// find the channel, given and checked by id
|
||||||
|
for k := range g.cfg.Channels {
|
||||||
|
if g.cfg.Channels[k].Id == id {
|
||||||
|
cfgChannelPtr = &(g.cfg.Channels[k])
|
||||||
|
foundChannel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundChannel {
|
||||||
|
return ErrConfigChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "thermocouple":
|
||||||
|
tc, err := ccc.GetThermocoupleType(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfgChannelPtr.Thermo = tc
|
||||||
|
case "gain":
|
||||||
|
fValue, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfgChannelPtr.Gain = fValue
|
||||||
|
case "offset":
|
||||||
|
fValue, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfgChannelPtr.Offset = fValue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************/
|
||||||
|
/*************************************************************************/
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetConfigChannelByIdHandler(c *gin.Context) {
|
||||||
|
var timestamp string
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := g.getConfigByID(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = g.getTimestamp()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channel": channel,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetConfigChannelsHandler(c *gin.Context) {
|
||||||
|
timestamp := g.getTimestamp()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channels": g.cfg.Channels,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetConfigDeviceHandler(c *gin.Context) {
|
||||||
|
timestamp := g.getTimestamp()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channels": g.cfg.Board,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetConfigDBHandler(c *gin.Context) {
|
||||||
|
var timestamp string
|
||||||
|
|
||||||
|
if g.cfg.API[0].Restricted {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": ErrConfigRestrictedDB,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = g.getTimestamp()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
// "db": g.getConfigDB(),
|
||||||
|
"db": g.cfg.DB,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) SetConfigDeviceHandler(c *gin.Context) {
|
||||||
|
var req PostRequest
|
||||||
|
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validKey := configDeviceIsValidKey(req.Key)
|
||||||
|
if !validKey {
|
||||||
|
c.String(http.StatusBadRequest, "invalid device config key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validValue := configDeviceIsValidValue(req.Key, req.Value)
|
||||||
|
if !validValue {
|
||||||
|
c.String(http.StatusBadRequest, "invalid device config value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := g.updateConfigDevice(req.Key, req.Value)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function can only modify already set up channels, for now.
|
||||||
|
func (g *GinAdapter) SetConfigChannelByIdHandler(c *gin.Context) {
|
||||||
|
|
||||||
|
var req PostRequest
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validChannel := g.validConfigChannel(id)
|
||||||
|
if !validChannel {
|
||||||
|
c.String(http.StatusBadRequest, "channel not initially configured. TODO fix")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validKey := configChannelIsValidKey(req.Key)
|
||||||
|
if !validKey {
|
||||||
|
c.String(http.StatusBadRequest, "invalid channel config key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.configChannelUpdateValue(id, req.Key, req.Value)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) SetConfigDBHandler(c *gin.Context) {
|
||||||
|
|
||||||
|
var req PostRequest
|
||||||
|
|
||||||
|
if g.cfg.API[0].Restricted {
|
||||||
|
c.String(http.StatusForbidden, ErrConfigRestrictedDB.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// what could go wrong?
|
||||||
|
g.cfg.DB[req.Key] = req.Value
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "OK")
|
||||||
|
}
|
85
api/http/data.go
Normal file
85
api/http/data.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"th7/data/core"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
|
||||||
|
func (g *GinAdapter) getRatio() core.Ratio {
|
||||||
|
return g.corePort.GetRatio()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) getChannels() []core.Channel {
|
||||||
|
return g.corePort.GetChannels()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) getChannelByID(id int) (core.Channel, error) {
|
||||||
|
return g.corePort.GetChannel(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************/
|
||||||
|
/*************************************************************************/
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetChannelByIDHandler(c *gin.Context) {
|
||||||
|
var timestamp string
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := g.getChannelByID(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = g.getTimestamp()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channel": channel,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetChannelsHandler(c *gin.Context) {
|
||||||
|
channels := g.getChannels()
|
||||||
|
timestamp := g.getTimestamp()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channels": channels,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetRatioHandler(c *gin.Context) {
|
||||||
|
ratio := g.getRatio()
|
||||||
|
timestamp := g.getTimestamp()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"ratio": ratio,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) GetDataHandler(c *gin.Context) {
|
||||||
|
timestamp := g.getTimestamp()
|
||||||
|
ratio := g.getRatio()
|
||||||
|
channels := g.getChannels()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"channels": channels,
|
||||||
|
"ratio": ratio,
|
||||||
|
"time": timestamp,
|
||||||
|
})
|
||||||
|
}
|
64
api/http/gin.go
Normal file
64
api/http/gin.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"th7/data/config"
|
||||||
|
"th7/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GinAdapter struct {
|
||||||
|
router *gin.Engine
|
||||||
|
corePort ports.CorePort
|
||||||
|
cfg config.Config
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) IndexHandler(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "page_index", gin.H{
|
||||||
|
"title": "TH7",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGinAdapter(corePort ports.CorePort, cfg config.Config) *GinAdapter {
|
||||||
|
var adapter GinAdapter
|
||||||
|
|
||||||
|
adapter.corePort = corePort
|
||||||
|
adapter.port = cfg.API[0].Port
|
||||||
|
adapter.cfg = cfg
|
||||||
|
|
||||||
|
//gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
adapter.router = gin.New()
|
||||||
|
|
||||||
|
// API
|
||||||
|
adapter.router.GET("/data/channel/:id", adapter.GetChannelByIDHandler)
|
||||||
|
adapter.router.GET("/data/channels", adapter.GetChannelsHandler)
|
||||||
|
adapter.router.GET("/data/ratio", adapter.GetRatioHandler)
|
||||||
|
adapter.router.GET("/data", adapter.GetDataHandler)
|
||||||
|
|
||||||
|
adapter.router.GET("/config/channel/:id", adapter.GetConfigChannelByIdHandler)
|
||||||
|
adapter.router.GET("/config/channels", adapter.GetConfigChannelsHandler)
|
||||||
|
adapter.router.GET("/config/device", adapter.GetConfigDeviceHandler)
|
||||||
|
adapter.router.GET("/config/db", adapter.GetConfigDBHandler)
|
||||||
|
|
||||||
|
adapter.router.POST("/config/device", adapter.SetConfigDeviceHandler)
|
||||||
|
adapter.router.POST("/config/channel/:id", adapter.SetConfigChannelByIdHandler)
|
||||||
|
adapter.router.POST("/config/db", adapter.SetConfigDBHandler)
|
||||||
|
|
||||||
|
//adapter.router.GET("/time", adapter.TimestampHandler)
|
||||||
|
|
||||||
|
adapter.router.LoadHTMLGlob("./templates/*.tmpl")
|
||||||
|
adapter.router.Static("/assets", "./static")
|
||||||
|
adapter.router.GET("/", adapter.IndexHandler)
|
||||||
|
|
||||||
|
return &adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) Run() {
|
||||||
|
portStr := fmt.Sprintf(":%d", g.port)
|
||||||
|
g.router.Run(portStr)
|
||||||
|
}
|
12
api/http/util.go
Normal file
12
api/http/util.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type PostRequest struct {
|
||||||
|
Key string `form:"key" binding:"required"`
|
||||||
|
Value string `form:"value" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GinAdapter) getTimestamp() string {
|
||||||
|
return time.Now().Format(time.RFC1123)
|
||||||
|
}
|
10
config.toml
10
config.toml
@ -1,13 +1,17 @@
|
|||||||
[TH7]
|
[TH7]
|
||||||
port = 8080
|
|
||||||
debug = true
|
debug = true
|
||||||
nolog = true
|
nolog = true
|
||||||
noweb = true
|
noweb = false
|
||||||
|
|
||||||
|
[API]
|
||||||
|
HTTP.enabled = true
|
||||||
|
HTTP.port = 8080
|
||||||
|
HTTP.restricted = false
|
||||||
|
|
||||||
[DB]
|
[DB]
|
||||||
type = "sqlite3"
|
type = "sqlite3"
|
||||||
path = "test.db"
|
path = "test.db"
|
||||||
freq = 60
|
freq = 300
|
||||||
|
|
||||||
[Channel_1]
|
[Channel_1]
|
||||||
type = 'U'
|
type = 'U'
|
||||||
|
@ -28,7 +28,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getThermocoupleType(t string) (thermocouple.Type, error) {
|
func GetThermocoupleType(t string) (thermocouple.Type, error) {
|
||||||
t = strings.ToUpper(t)
|
t = strings.ToUpper(t)
|
||||||
if val, ok := thermocoupleTypes[t]; ok {
|
if val, ok := thermocoupleTypes[t]; ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
@ -53,15 +53,20 @@ func Load() (config.Config, error) {
|
|||||||
|
|
||||||
SetDefaultConfig(v)
|
SetDefaultConfig(v)
|
||||||
|
|
||||||
cfg.Board.Port = v.GetInt("TH7.port")
|
|
||||||
cfg.Board.Cache = v.GetBool("TH7.cache")
|
|
||||||
cfg.Board.Led = v.GetBool("TH7.LED")
|
cfg.Board.Led = v.GetBool("TH7.LED")
|
||||||
cfg.Board.Logfreq = v.GetInt("DB.freq")
|
|
||||||
cfg.Board.Debug = v.GetBool("TH7.debug")
|
cfg.Board.Debug = v.GetBool("TH7.debug")
|
||||||
cfg.Board.NoLogging = v.GetBool("TH7.nolog")
|
cfg.Board.NoLog = v.GetBool("TH7.nolog")
|
||||||
cfg.Board.NoWeb = v.GetBool("TH7.noweb")
|
cfg.Board.NoWeb = v.GetBool("TH7.noweb")
|
||||||
|
|
||||||
cfg.Channels = make([]config.Channel, 0)
|
cfg.Channels = make([]config.Channel, 0)
|
||||||
|
cfg.API = make([]config.API, 0)
|
||||||
|
|
||||||
|
// just REST API for now ..
|
||||||
|
cfg.API = append(cfg.API, config.API{
|
||||||
|
Port: v.GetInt("API.HTTP.port"),
|
||||||
|
Enabled: v.GetBool("API.HTTP.enabled"),
|
||||||
|
Restricted: v.GetBool("API.HTTP.restricted"),
|
||||||
|
})
|
||||||
|
|
||||||
for i := 1; i < 8; i++ {
|
for i := 1; i < 8; i++ {
|
||||||
var c config.Channel
|
var c config.Channel
|
||||||
@ -76,7 +81,7 @@ func Load() (config.Config, error) {
|
|||||||
c.Gain = v.GetFloat64(head + ".gain")
|
c.Gain = v.GetFloat64(head + ".gain")
|
||||||
c.Offset = v.GetFloat64(head + ".offset")
|
c.Offset = v.GetFloat64(head + ".offset")
|
||||||
|
|
||||||
tc, err := getThermocoupleType(v.GetString(head + ".type"))
|
tc, err := GetThermocoupleType(v.GetString(head + ".type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s.type=%s\n", head, v.GetString(head+".type"))
|
fmt.Printf("%s.type=%s\n", head, v.GetString(head+".type"))
|
||||||
return cfg, err
|
return cfg, err
|
||||||
|
@ -7,13 +7,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func SetDefaultConfig(v *viper.Viper) {
|
func SetDefaultConfig(v *viper.Viper) {
|
||||||
v.SetDefault("TH7.port", 8080)
|
|
||||||
v.SetDefault("TH7.cache", true)
|
|
||||||
v.SetDefault("TH7.LED", true)
|
v.SetDefault("TH7.LED", true)
|
||||||
v.SetDefault("TH7.debug", false)
|
v.SetDefault("TH7.debug", false)
|
||||||
v.SetDefault("TH7.nolog", false)
|
v.SetDefault("TH7.nolog", false)
|
||||||
v.SetDefault("TH7.noweb", false)
|
v.SetDefault("TH7.noweb", false)
|
||||||
v.SetDefault("DB.freq", 600)
|
|
||||||
|
// set defaults for REST API
|
||||||
|
v.SetDefault("API.HTTP.enabled", false)
|
||||||
|
v.SetDefault("API.HTTP.restricted", true)
|
||||||
|
v.SetDefault("API.HTTP.port", 8080)
|
||||||
|
|
||||||
// set defaults for channels
|
// set defaults for channels
|
||||||
for i := 1; i < 8; i++ {
|
for i := 1; i < 8; i++ {
|
||||||
|
@ -4,6 +4,12 @@ import (
|
|||||||
"th7/data/thermocouple"
|
"th7/data/thermocouple"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
Port int `json:"port"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Restricted bool `json:"restricted"` // can read/modify DB settings?
|
||||||
|
}
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
SampleSize int `json:"sample_size"`
|
SampleSize int `json:"sample_size"`
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
@ -19,12 +25,9 @@ type Channel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TH7 struct {
|
type TH7 struct {
|
||||||
Port int `json:"port"`
|
|
||||||
Cache bool `json:"cache"`
|
|
||||||
Led bool `json:"LED"`
|
Led bool `json:"LED"`
|
||||||
Logfreq int `json:"logfreq"`
|
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
NoLogging bool `json:"nologging"`
|
NoLog bool `json:"nolog"`
|
||||||
NoWeb bool `json:"noweb"`
|
NoWeb bool `json:"noweb"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,4 +35,5 @@ type Config struct {
|
|||||||
Board TH7 `json:"TH7"`
|
Board TH7 `json:"TH7"`
|
||||||
Channels []Channel `json:"channels"`
|
Channels []Channel `json:"channels"`
|
||||||
DB map[string]interface{} `json:"db"`
|
DB map[string]interface{} `json:"db"`
|
||||||
|
API []API `json:"API"`
|
||||||
}
|
}
|
||||||
|
@ -8,18 +8,25 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DB_LOGGER_DEFAULT_DUR = 300
|
||||||
|
)
|
||||||
|
|
||||||
func NewAdapter(corePort ports.CorePort, cfg config.Config) (ports.DBPort, error) {
|
func NewAdapter(corePort ports.CorePort, cfg config.Config) (ports.DBPort, error) {
|
||||||
|
|
||||||
var duration time.Duration
|
var duration time.Duration
|
||||||
var no_logging bool
|
|
||||||
var adapter ports.DBPort
|
var adapter ports.DBPort
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
no_logging = cfg.Board.NoLogging
|
// if `freq' is not present in the DB map, use default of 5 minutes (300 sec)
|
||||||
duration = time.Duration(cfg.Board.Logfreq) * time.Second
|
if _, ok := cfg.DB["freq"]; !ok {
|
||||||
|
duration = time.Duration(DB_LOGGER_DEFAULT_DUR) * time.Second
|
||||||
|
} else {
|
||||||
|
duration = time.Duration(cfg.DB["freq"].(int64)) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// if no DB is specified, or nolog=true then use a dummy db adapter
|
// if no DB is specified, or nolog=true then use a dummy db adapter
|
||||||
if _, ok := cfg.DB["type"]; !ok || no_logging {
|
if _, ok := cfg.DB["type"]; !ok || cfg.Board.NoLog {
|
||||||
adapter, _ = NewDummyAdapter(cfg)
|
adapter, _ = NewDummyAdapter(cfg)
|
||||||
go startLoggingProcess(adapter, corePort, duration)
|
go startLoggingProcess(adapter, corePort, duration)
|
||||||
return adapter, nil
|
return adapter, nil
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
|
|
||||||
func startLoggingProcess(db ports.DBPort, core ports.CorePort, dur time.Duration) {
|
func startLoggingProcess(db ports.DBPort, core ports.CorePort, dur time.Duration) {
|
||||||
|
|
||||||
|
log.Printf("[DB] logging frequency=%v\n", dur)
|
||||||
|
|
||||||
// wait 15 seconds to not log start up values that can be noisy
|
// wait 15 seconds to not log start up values that can be noisy
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
|
|
||||||
|
16
main.go
16
main.go
@ -6,12 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"th7/api/http"
|
||||||
"th7/config"
|
"th7/config"
|
||||||
"th7/core"
|
"th7/core"
|
||||||
"th7/db"
|
"th7/db"
|
||||||
"th7/pcb"
|
"th7/pcb"
|
||||||
"th7/ports"
|
"th7/ports"
|
||||||
"th7/web"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
@ -22,7 +22,7 @@ func main() {
|
|||||||
var pcbPort ports.PCBPort
|
var pcbPort ports.PCBPort
|
||||||
var corePort ports.CorePort
|
var corePort ports.CorePort
|
||||||
var dbPort ports.DBPort
|
var dbPort ports.DBPort
|
||||||
var webPort ports.WebPort
|
var apiPort ports.APIPort
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
kill := make(chan os.Signal, 1)
|
kill := make(chan os.Signal, 1)
|
||||||
@ -55,21 +55,23 @@ func main() {
|
|||||||
defer dbPort.Close()
|
defer dbPort.Close()
|
||||||
|
|
||||||
// if noweb is false, then start web service
|
// if noweb is false, then start web service
|
||||||
if !cfg.Board.NoWeb {
|
if !cfg.Board.NoWeb && cfg.API[0].Enabled {
|
||||||
webPort = web.NewGinAdapter(corePort, cfg)
|
apiPort = http.NewGinAdapter(corePort, cfg)
|
||||||
go webPort.Run()
|
go apiPort.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
color.Set(color.FgHiRed)
|
color.Set(color.FgHiRed)
|
||||||
fmt.Printf("Started on: %s\n", time.Now().Format(time.DateTime))
|
fmt.Printf("Started on: %s\n", time.Now().Format(time.DateTime))
|
||||||
color.Unset()
|
color.Unset()
|
||||||
|
|
||||||
if !cfg.Board.NoWeb {
|
if !cfg.Board.NoWeb && cfg.API[0].Enabled {
|
||||||
color.Set(color.FgHiGreen)
|
color.Set(color.FgHiGreen)
|
||||||
fmt.Printf("TH7 web view is live on http://localhost:%d/\n", cfg.Board.Port)
|
fmt.Printf("TH7 API is live on http://localhost:%d/\n", cfg.API[0].Port)
|
||||||
color.Unset()
|
color.Unset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(cfg.API[0])
|
||||||
|
|
||||||
sig := <-kill
|
sig := <-kill
|
||||||
log.Printf("Caught signal %v", sig)
|
log.Printf("Caught signal %v", sig)
|
||||||
}
|
}
|
||||||
|
5
ports/api.go
Normal file
5
ports/api.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
type APIPort interface {
|
||||||
|
Run()
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package ports
|
|
||||||
|
|
||||||
type WebPort interface {
|
|
||||||
Run()
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ GET /data/ratio -> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# returns all data fields that a user may want to log/access
|
# returns all data fields that a user may want to log/access
|
||||||
GET /data/all -> {
|
GET /data -> {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/data/channels without timestamp
|
/data/channels without timestamp
|
||||||
@ -97,8 +97,8 @@ GET /config/db -> {
|
|||||||
========== write configuration info ==========
|
========== write configuration info ==========
|
||||||
|
|
||||||
=== The POST request payload should be of the form:
|
=== The POST request payload should be of the form:
|
||||||
=== { key: value } and several options can be specified at once like such:
|
=== { key: value }
|
||||||
=== [{ key1: value }, { key2: value}, {key3: value }]
|
=== $ curl -d "key=testkey&value=testvalue" -X POST "http://localhost:8080/config/device"
|
||||||
|
|
||||||
# write device-specific key:value pair(s) to the device config
|
# write device-specific key:value pair(s) to the device config
|
||||||
# returns success or error
|
# returns success or error
|
||||||
|
129
web/gin.go
129
web/gin.go
@ -1,129 +0,0 @@
|
|||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"th7/data/config"
|
|
||||||
"th7/data/core"
|
|
||||||
"th7/ports"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GinAdapter struct {
|
|
||||||
router *gin.Engine
|
|
||||||
corePort ports.CorePort
|
|
||||||
cfg config.Config
|
|
||||||
port int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) getRatio() core.Ratio {
|
|
||||||
return g.corePort.GetRatio()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) RatioHandler(c *gin.Context) {
|
|
||||||
ratio := g.getRatio()
|
|
||||||
c.JSON(http.StatusOK, ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) getChannels() []core.Channel {
|
|
||||||
return g.corePort.GetChannels()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) ChannelsHandler(c *gin.Context) {
|
|
||||||
channels := g.getChannels()
|
|
||||||
c.JSON(http.StatusOK, channels)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) getChannelByID(id int) (core.Channel, error) {
|
|
||||||
return g.corePort.GetChannel(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) ChannelByIDHandler(c *gin.Context) {
|
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channel, err := g.getChannelByID(id)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) getTimestamp() string {
|
|
||||||
return time.Now().Format(time.RFC1123)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) TimestampHandler(c *gin.Context) {
|
|
||||||
ts := g.getTimestamp()
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"time": ts,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) DataHandler(c *gin.Context) {
|
|
||||||
timestamp := g.getTimestamp()
|
|
||||||
ratio := g.getRatio()
|
|
||||||
channels := g.getChannels()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"time": timestamp,
|
|
||||||
"ratio": ratio,
|
|
||||||
"channels": channels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) IndexHandler(c *gin.Context) {
|
|
||||||
c.HTML(http.StatusOK, "page_index", gin.H{
|
|
||||||
"title": "TH7",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) ConfigHandler(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"config": g.cfg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGinAdapter(corePort ports.CorePort, cfg config.Config) *GinAdapter {
|
|
||||||
var adapter GinAdapter
|
|
||||||
|
|
||||||
adapter.corePort = corePort
|
|
||||||
adapter.port = cfg.Board.Port
|
|
||||||
adapter.cfg = cfg
|
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
|
|
||||||
adapter.router = gin.New()
|
|
||||||
|
|
||||||
// API
|
|
||||||
adapter.router.GET("/ratio", adapter.RatioHandler)
|
|
||||||
adapter.router.GET("/channels", adapter.ChannelsHandler)
|
|
||||||
adapter.router.GET("/channel/:id", adapter.ChannelByIDHandler)
|
|
||||||
adapter.router.GET("/time", adapter.TimestampHandler)
|
|
||||||
adapter.router.GET("/data", adapter.DataHandler)
|
|
||||||
adapter.router.GET("/config", adapter.ConfigHandler)
|
|
||||||
|
|
||||||
adapter.router.LoadHTMLGlob("./templates/*.tmpl")
|
|
||||||
adapter.router.Static("/assets", "./static")
|
|
||||||
adapter.router.GET("/", adapter.IndexHandler)
|
|
||||||
|
|
||||||
return &adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GinAdapter) Run() {
|
|
||||||
portStr := fmt.Sprintf(":%d", g.port)
|
|
||||||
g.router.Run(portStr)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user