feat(gateway): added first implement of the Gateway (MQTT, Influx, REST)

Signed-off-by: Klagarge <remi@heredero.ch>
This commit is contained in:
2025-04-14 22:39:47 +02:00
parent d33a6a1d02
commit e6ef490314
14 changed files with 928 additions and 0 deletions

239
gateway/src/Connection.go Normal file
View File

@@ -0,0 +1,239 @@
package main
import (
_ "crypto/md5"
_ "gateway-softweng/docs"
paho "github.com/eclipse/paho.mqtt.golang"
"github.com/gin-gonic/gin"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
"github.com/labstack/gommon/log"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
"os"
)
type Gateway struct {
username string
password string
mqtt paho.Client
rest *gin.Engine
restGroup *gin.RouterGroup
accounts gin.Accounts
influx influxdb2.Client
influxApi api.WriteAPIBlocking
}
// @Summary Ping test endpoint
// @Description get ping response
// @Tags ping
// @Accept json
// @Produce json
// @Success 200 {string} string "pong"
// @Router /ping [get]
func pingHandler(c *gin.Context) {
c.String(http.StatusOK, "pong")
}
// getUsers initializes user account.
//
// It performs the following actions:
// - Creates user accounts with usernames and password provided by environnement variable
// - Stores the credentials for basic authentication.
func (gh *Gateway) getUser() {
// Create username
ok := false
gh.username, ok = os.LookupEnv("REST_USERNAME")
if !ok {
log.Fatal("REST_USERNAME not set")
}
gh.password, ok = os.LookupEnv("REST_PASSWORD")
if !ok {
log.Fatal("REST_PASSWORD not set")
}
gh.accounts = gin.Accounts{}
gh.accounts[gh.username] = gh.password
}
// createRestGateway sets up the REST API using the Gin framework.
// It defines the following routes:
// - A public ping test endpoint for health checks.
// - A Swagger documentation endpoint for API documentation.
// - A secured group of endpoints for commands and data requests, protected by basic authentication.
//
// Behavior:
// - The `/ping` endpoint responds with "pong" to test server availability.
// - The `/swagger/*any` endpoint serves the Swagger UI for API documentation.
// - The `/raclette` group includes:
// - A POST route for publishing commands to MQTT.
// - A GET route for requesting data from InfluxDB.
//
// Security:
// - The `/raclette` group requires basic authentication using credentials generated for each user.
func (gh *Gateway) createRestGateway() {
// Create a new Gin router
gh.rest = gin.Default()
gh.rest.GET("/ping", pingHandler)
// Swagger documentation route
gh.rest.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// Create a new router group with basic authentication for /raclette
gh.restGroup = gh.rest.Group("/raclette", gin.BasicAuth(gh.accounts))
// Define the route for the publish command
gh.restGroup.POST("", func(c *gin.Context) {
ret := gh.publishCommand(c)
if ret == nil {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()})
}
})
// Define the route for the publish command (GET)
gh.restGroup.GET("", func(c *gin.Context) {
ret := gh.requestInflux(c)
if ret != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()})
}
})
}
// createMQTTGateway initializes the MQTT client and sets up subscriptions.
//
// It subscribes to the following topics:
// - "+/+/+/update": For device state updates.
//
// The OnConnect callback ensures that subscriptions are re-established after reconnecting.
//
// Returns:
// - error: An error if the MQTT client fails to connect or subscribe to topics.
func (gh *Gateway) createMQTTGateway() error {
// Get env variables and set default values if not set
MQTT_URL, ok := os.LookupEnv("MQTT_URL")
if !ok {
log.Error("MQTT_URL not set, using default value: tcp://mqtt.mse.kb28.ch:1883")
MQTT_URL = "tcp://mqtt.mse.kb28.ch:1883"
}
CLIENT_ID, ok := os.LookupEnv("CLIENT_ID")
if !ok {
log.Error("CLIENT_ID not set, using default value: Gateway-SoftwEng")
CLIENT_ID = "Gateway-SoftwEng"
}
// Get env variables for MQTT credentials, panic if not set
MQTT_USERNAME, ok := os.LookupEnv("MQTT_USERNAME")
if !ok {
log.Fatal("MQTT_USERNAME not set")
}
MQTT_PASSWORD, ok := os.LookupEnv("MQTT_PASSWORD")
if !ok {
log.Fatal("MQTT_PASSWORD not set")
}
// Create MQTT client
option := paho.NewClientOptions()
option.AddBroker(MQTT_URL)
option.SetClientID(CLIENT_ID)
option.SetUsername(MQTT_USERNAME)
option.SetPassword(MQTT_PASSWORD)
option.SetAutoReconnect(true)
option.OnConnect = func(client paho.Client) {
// Subscribe to update
token := gh.mqtt.Subscribe("+/+/+/update", 0, gh.processUpdate)
if token.Wait() && token.Error() != nil {
log.Error(token.Error())
client.Disconnect(1000) // Disconnect from the broker after 1000ms
}
}
gh.mqtt = paho.NewClient(option)
token := gh.mqtt.Connect()
if token.Wait() && token.Error() != nil {
return token.Error()
}
return nil
}
// createInfluxGateway initializes the InfluxDB client.
func (gh *Gateway) createInfluxGateway() {
// Get env variables and set default values if not set
INFLUXDB_ORG, ok := os.LookupEnv("INFLUXDB_ORG")
if !ok {
log.Error("INFLUXDB_ORG not set, using default value: raclette")
INFLUXDB_ORG = "raclette"
}
INFLUXDB_BUCKET, ok := os.LookupEnv("INFLUXDB_BUCKET")
if !ok {
log.Error("INFLUXDB_BUCKET not set, using default value: raclette")
INFLUXDB_BUCKET = "raclette"
}
// Get env variables and set default values if not set
INFLUXDB_URL, ok := os.LookupEnv("INFLUXDB_URL")
if !ok {
log.Error("INFLUXDB_URL not set, using default value: http://influx.mse.kb28.ch")
INFLUXDB_URL = "http://influx.mse.kb28.ch"
}
// Get env variables for InfluxDB credentials, panic if not set
INFLUXDB_TOKEN, ok := os.LookupEnv("INFLUXDB_TOKEN")
if !ok {
log.Fatal("INFLUXDB_TOKEN not set")
}
// Initialize the InfluxDB client
gh.influx = influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
gh.influxApi = gh.influx.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
gh.influxApi.EnableBatching()
}
// NewGateway initializes the Gateway instance by setting up the necessary components.
//
// It performs the following actions:
// - Initializes the InfluxDB client using the provided token.
// - Creates user account with unique credentials and InfluxDB write APIs.
// - Sets up the REST API server with routes for health checks, Swagger documentation, and secured endpoints.
// - Connects to the MQTT broker and subscribes to relevant topics.
//
// Returns:
// - *Gateway: A pointer to the initialized Gateway instance.
// - error: An error if any of the initialization steps fail.
func NewGateway() (*Gateway, error) {
var gh = Gateway{}
var err error
// Initialize the InfluxDB client
gh.createInfluxGateway()
// Generate user accounts and credentials
gh.getUser()
// Set up the REST API server
gh.createRestGateway()
// Initialize the MQTT client and subscriptions
err = gh.createMQTTGateway()
if err != nil {
return nil, err
}
// Start the HTTP server
err = gh.rest.Run(":8080")
if err != nil {
return nil, err
}
return &gh, nil
}