initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.env
|
||||
/.venv/
|
||||
/persist/
|
||||
/.idea/
|
||||
/.vscode/
|
||||
173
api.go
Normal file
173
api.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sensor_dashboard/db"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed public
|
||||
var staticFiles embed.FS // Holds embedded static files
|
||||
|
||||
func Api(queries db.Queries, config Config) (http.Handler, error) {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var publicDir fs.FS
|
||||
if config.ServeFromFS {
|
||||
publicDir = os.DirFS("./public")
|
||||
} else {
|
||||
publicDir, _ = fs.Sub(staticFiles, "public")
|
||||
}
|
||||
|
||||
fileServer := http.FileServer(http.FS(publicDir))
|
||||
|
||||
mux.Handle("/humidity", GetHumidityLogByDate(queries, config))
|
||||
mux.Handle("/power", GetPowerLogByDate(queries, config))
|
||||
mux.Handle("/state", GetSwitchStateLogByDate(queries, config))
|
||||
mux.Handle("/tags", GetTags(queries, config))
|
||||
|
||||
mux.Handle("/", fileServer)
|
||||
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
func tagAndDateFromQuery(r *http.Request) (string, time.Time, error) {
|
||||
q := r.URL.Query()
|
||||
tag := q.Get("tag")
|
||||
startDateStr := q.Get("from")
|
||||
now := time.Now()
|
||||
|
||||
if tag == "" {
|
||||
return "", now, errors.New("no tag specified")
|
||||
}
|
||||
|
||||
var startDate time.Time
|
||||
if startDateStr == "" || startDateStr == "null" {
|
||||
startDate = now.AddDate(0, 0, -7)
|
||||
} else {
|
||||
startDate, _ = time.Parse("2006-01-02T15:04:05Z", startDateStr)
|
||||
}
|
||||
|
||||
return tag, startDate, nil
|
||||
}
|
||||
|
||||
func GetTags(queries db.Queries, config Config) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rows, err := queries.ListDevices(context.Background())
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
log.Printf("Error encoding JSON: %v", err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func GetPowerLogByDate(queries db.Queries, config Config) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tag, startDate, err := tagAndDateFromQuery(r)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "400 Bad Request", http.StatusBadRequest)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := queries.PowerLogForDeviceToDate(context.Background(), db.PowerLogForDeviceToDateParams{
|
||||
Tag: tag,
|
||||
FromTime: startDate,
|
||||
ToTime: time.Now(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
log.Printf("Error encoding JSON: %v", err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func GetSwitchStateLogByDate(queries db.Queries, config Config) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tag, startDate, err := tagAndDateFromQuery(r)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "400 Bad Request", http.StatusBadRequest)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := queries.SwitchStateLogForDeviceToDate(context.Background(), db.SwitchStateLogForDeviceToDateParams{
|
||||
Tag: tag,
|
||||
FromTime: startDate,
|
||||
ToTime: time.Now(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
log.Printf("Error encoding JSON: %v", err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func GetHumidityLogByDate(queries db.Queries, config Config) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tag, startDate, err := tagAndDateFromQuery(r)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "400 Bad Request", http.StatusBadRequest)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := queries.HumidityLogForDeviceToDate(context.Background(), db.HumidityLogForDeviceToDateParams{
|
||||
Tag: tag,
|
||||
FromTime: startDate,
|
||||
ToTime: time.Now(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
log.Printf("Error encoding JSON: %v", err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
20
config.yaml
Normal file
20
config.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
base_url: "localhost:8080"
|
||||
mqtt_url: "tcp://192.168.178.139:1883"
|
||||
mqtt_devices:
|
||||
- name: "plexi01"
|
||||
topic: "tele/plexi01/SENSOR"
|
||||
type: "humidity"
|
||||
tag: "Bad Unten"
|
||||
|
||||
- name: "mere01"
|
||||
topic: "tele/mere01/SENSOR"
|
||||
type: "power"
|
||||
tag: "Bad Unten"
|
||||
|
||||
- name: "mere01result"
|
||||
topic: "stat/mere01/RESULT"
|
||||
type: "switch"
|
||||
tag: "Bad Unten"
|
||||
|
||||
datasource_dir: "./persist"
|
||||
serve_from_fs: true
|
||||
47
database.go
Normal file
47
database.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"sensor_dashboard/db"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
//go:embed schema.sql
|
||||
var ddl string
|
||||
|
||||
func InitDB(datasourceDir string) *db.Queries {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := os.ReadDir(datasourceDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err := os.MkdirAll(datasourceDir, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
sqliteDB, err := sql.Open("sqlite3", path.Join(datasourceDir, "sqlite.db"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = sqliteDB.ExecContext(ctx, ddl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
queries := db.New(sqliteDB)
|
||||
|
||||
return queries
|
||||
}
|
||||
31
db/db.go
Normal file
31
db/db.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
49
db/models.go
Normal file
49
db/models.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceTag struct {
|
||||
ID int64
|
||||
DeviceName string
|
||||
Tag string
|
||||
}
|
||||
|
||||
type HumidityLog struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
Temperature float64
|
||||
Humidity float64
|
||||
DewPoint float64
|
||||
}
|
||||
|
||||
type PowerLog struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
TotalStartTime time.Time
|
||||
Total float64
|
||||
Yesterday float64
|
||||
Today float64
|
||||
Period float64
|
||||
Power float64
|
||||
ApparentPower float64
|
||||
ReactivePower float64
|
||||
Factor float64
|
||||
Voltage float64
|
||||
Current float64
|
||||
SensorTemperature float64
|
||||
}
|
||||
|
||||
type SwitchStateLog struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
SwitchState string
|
||||
}
|
||||
681
db/queries.sql.go
Normal file
681
db/queries.sql.go
Normal file
@@ -0,0 +1,681 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: queries.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const createDeviceTag = `-- name: CreateDeviceTag :one
|
||||
insert
|
||||
into device_tag (device_name, tag)
|
||||
values (?, ?) returning id, device_name, tag
|
||||
`
|
||||
|
||||
type CreateDeviceTagParams struct {
|
||||
DeviceName string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateDeviceTag(ctx context.Context, arg CreateDeviceTagParams) (DeviceTag, error) {
|
||||
row := q.db.QueryRowContext(ctx, createDeviceTag, arg.DeviceName, arg.Tag)
|
||||
var i DeviceTag
|
||||
err := row.Scan(&i.ID, &i.DeviceName, &i.Tag)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createHumidityLog = `-- name: CreateHumidityLog :one
|
||||
insert
|
||||
into humidity_log (time, sensor , temperature, humidity, dew_point)
|
||||
values (?, ?, ?, ?, ?) returning id, time, sensor, temperature, humidity, dew_point
|
||||
`
|
||||
|
||||
type CreateHumidityLogParams struct {
|
||||
Time time.Time
|
||||
Sensor string
|
||||
Temperature float64
|
||||
Humidity float64
|
||||
DewPoint float64
|
||||
}
|
||||
|
||||
func (q *Queries) CreateHumidityLog(ctx context.Context, arg CreateHumidityLogParams) (HumidityLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, createHumidityLog,
|
||||
arg.Time,
|
||||
arg.Sensor,
|
||||
arg.Temperature,
|
||||
arg.Humidity,
|
||||
arg.DewPoint,
|
||||
)
|
||||
var i HumidityLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.Temperature,
|
||||
&i.Humidity,
|
||||
&i.DewPoint,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createPowerLog = `-- name: CreatePowerLog :one
|
||||
insert
|
||||
into power_log (time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power, factor, voltage, current, sensor_temperature)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning id, time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power, factor, voltage, "current", sensor_temperature
|
||||
`
|
||||
|
||||
type CreatePowerLogParams struct {
|
||||
Time time.Time
|
||||
Sensor string
|
||||
TotalStartTime time.Time
|
||||
Total float64
|
||||
Yesterday float64
|
||||
Today float64
|
||||
Period float64
|
||||
Power float64
|
||||
ApparentPower float64
|
||||
ReactivePower float64
|
||||
Factor float64
|
||||
Voltage float64
|
||||
Current float64
|
||||
SensorTemperature float64
|
||||
}
|
||||
|
||||
func (q *Queries) CreatePowerLog(ctx context.Context, arg CreatePowerLogParams) (PowerLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, createPowerLog,
|
||||
arg.Time,
|
||||
arg.Sensor,
|
||||
arg.TotalStartTime,
|
||||
arg.Total,
|
||||
arg.Yesterday,
|
||||
arg.Today,
|
||||
arg.Period,
|
||||
arg.Power,
|
||||
arg.ApparentPower,
|
||||
arg.ReactivePower,
|
||||
arg.Factor,
|
||||
arg.Voltage,
|
||||
arg.Current,
|
||||
arg.SensorTemperature,
|
||||
)
|
||||
var i PowerLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.TotalStartTime,
|
||||
&i.Total,
|
||||
&i.Yesterday,
|
||||
&i.Today,
|
||||
&i.Period,
|
||||
&i.Power,
|
||||
&i.ApparentPower,
|
||||
&i.ReactivePower,
|
||||
&i.Factor,
|
||||
&i.Voltage,
|
||||
&i.Current,
|
||||
&i.SensorTemperature,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createSwitchStateLog = `-- name: CreateSwitchStateLog :one
|
||||
insert
|
||||
into switch_state_log (time, sensor , switch_state)
|
||||
values (?, ?, ?) returning id, time, sensor, switch_state
|
||||
`
|
||||
|
||||
type CreateSwitchStateLogParams struct {
|
||||
Time time.Time
|
||||
Sensor string
|
||||
SwitchState string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSwitchStateLog(ctx context.Context, arg CreateSwitchStateLogParams) (SwitchStateLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, createSwitchStateLog, arg.Time, arg.Sensor, arg.SwitchState)
|
||||
var i SwitchStateLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.SwitchState,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteDeviceTag = `-- name: DeleteDeviceTag :exec
|
||||
delete
|
||||
from device_tag
|
||||
where id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteDeviceTag(ctx context.Context, id int64) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteDeviceTag, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteHumidityLog = `-- name: DeleteHumidityLog :exec
|
||||
delete
|
||||
from humidity_log
|
||||
where id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteHumidityLog(ctx context.Context, id int64) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteHumidityLog, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deletePowerLog = `-- name: DeletePowerLog :exec
|
||||
delete
|
||||
from power_log
|
||||
where id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeletePowerLog(ctx context.Context, id int64) error {
|
||||
_, err := q.db.ExecContext(ctx, deletePowerLog, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSwitchStateLog = `-- name: DeleteSwitchStateLog :exec
|
||||
delete
|
||||
from switch_state_log
|
||||
where id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSwitchStateLog(ctx context.Context, id int64) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteSwitchStateLog, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getDeviceTag = `-- name: GetDeviceTag :one
|
||||
select id, device_name, tag
|
||||
from device_tag
|
||||
where id = ? limit 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetDeviceTag(ctx context.Context, id int64) (DeviceTag, error) {
|
||||
row := q.db.QueryRowContext(ctx, getDeviceTag, id)
|
||||
var i DeviceTag
|
||||
err := row.Scan(&i.ID, &i.DeviceName, &i.Tag)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getHumidityLog = `-- name: GetHumidityLog :one
|
||||
select id, time, sensor, temperature, humidity, dew_point
|
||||
from humidity_log
|
||||
where id = ? limit 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetHumidityLog(ctx context.Context, id int64) (HumidityLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, getHumidityLog, id)
|
||||
var i HumidityLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.Temperature,
|
||||
&i.Humidity,
|
||||
&i.DewPoint,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getPowerLog = `-- name: GetPowerLog :one
|
||||
select id, time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power, factor, voltage, "current", sensor_temperature
|
||||
from power_log
|
||||
where id = ? limit 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetPowerLog(ctx context.Context, id int64) (PowerLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, getPowerLog, id)
|
||||
var i PowerLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.TotalStartTime,
|
||||
&i.Total,
|
||||
&i.Yesterday,
|
||||
&i.Today,
|
||||
&i.Period,
|
||||
&i.Power,
|
||||
&i.ApparentPower,
|
||||
&i.ReactivePower,
|
||||
&i.Factor,
|
||||
&i.Voltage,
|
||||
&i.Current,
|
||||
&i.SensorTemperature,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSwitchStateLog = `-- name: GetSwitchStateLog :one
|
||||
select id, time, sensor, switch_state
|
||||
from switch_state_log
|
||||
where id = ? limit 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSwitchStateLog(ctx context.Context, id int64) (SwitchStateLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, getSwitchStateLog, id)
|
||||
var i SwitchStateLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.SwitchState,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const humidityLogForDeviceToDate = `-- name: HumidityLogForDeviceToDate :many
|
||||
select humidity_log.id, time, sensor, temperature, humidity, dew_point, device_tag.id, device_name, tag
|
||||
from humidity_log
|
||||
join device_tag on device_name = humidity_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc
|
||||
`
|
||||
|
||||
type HumidityLogForDeviceToDateParams struct {
|
||||
Tag string
|
||||
FromTime time.Time
|
||||
ToTime time.Time
|
||||
}
|
||||
|
||||
type HumidityLogForDeviceToDateRow struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
Temperature float64
|
||||
Humidity float64
|
||||
DewPoint float64
|
||||
ID_2 int64
|
||||
DeviceName string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (q *Queries) HumidityLogForDeviceToDate(ctx context.Context, arg HumidityLogForDeviceToDateParams) ([]HumidityLogForDeviceToDateRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, humidityLogForDeviceToDate, arg.Tag, arg.FromTime, arg.ToTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []HumidityLogForDeviceToDateRow
|
||||
for rows.Next() {
|
||||
var i HumidityLogForDeviceToDateRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.Temperature,
|
||||
&i.Humidity,
|
||||
&i.DewPoint,
|
||||
&i.ID_2,
|
||||
&i.DeviceName,
|
||||
&i.Tag,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listDeviceTag = `-- name: ListDeviceTag :many
|
||||
select id, device_name, tag
|
||||
from device_tag
|
||||
`
|
||||
|
||||
func (q *Queries) ListDeviceTag(ctx context.Context) ([]DeviceTag, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listDeviceTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DeviceTag
|
||||
for rows.Next() {
|
||||
var i DeviceTag
|
||||
if err := rows.Scan(&i.ID, &i.DeviceName, &i.Tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listDevices = `-- name: ListDevices :many
|
||||
select tag from device_tag group by tag
|
||||
`
|
||||
|
||||
func (q *Queries) ListDevices(ctx context.Context) ([]string, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listDevices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []string
|
||||
for rows.Next() {
|
||||
var tag string
|
||||
if err := rows.Scan(&tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, tag)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listHumidityLog = `-- name: ListHumidityLog :many
|
||||
select id, time, sensor, temperature, humidity, dew_point
|
||||
from humidity_log
|
||||
order by time desc limit ? offset ?
|
||||
`
|
||||
|
||||
type ListHumidityLogParams struct {
|
||||
Limit int64
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListHumidityLog(ctx context.Context, arg ListHumidityLogParams) ([]HumidityLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listHumidityLog, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []HumidityLog
|
||||
for rows.Next() {
|
||||
var i HumidityLog
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.Temperature,
|
||||
&i.Humidity,
|
||||
&i.DewPoint,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listPowerLog = `-- name: ListPowerLog :many
|
||||
select id, time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power, factor, voltage, "current", sensor_temperature
|
||||
from power_log
|
||||
order by time desc limit ? offset ?
|
||||
`
|
||||
|
||||
type ListPowerLogParams struct {
|
||||
Limit int64
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListPowerLog(ctx context.Context, arg ListPowerLogParams) ([]PowerLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listPowerLog, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []PowerLog
|
||||
for rows.Next() {
|
||||
var i PowerLog
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.TotalStartTime,
|
||||
&i.Total,
|
||||
&i.Yesterday,
|
||||
&i.Today,
|
||||
&i.Period,
|
||||
&i.Power,
|
||||
&i.ApparentPower,
|
||||
&i.ReactivePower,
|
||||
&i.Factor,
|
||||
&i.Voltage,
|
||||
&i.Current,
|
||||
&i.SensorTemperature,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listSwitchStateLog = `-- name: ListSwitchStateLog :many
|
||||
select id, time, sensor, switch_state
|
||||
from switch_state_log
|
||||
order by time desc limit ? offset ?
|
||||
`
|
||||
|
||||
type ListSwitchStateLogParams struct {
|
||||
Limit int64
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListSwitchStateLog(ctx context.Context, arg ListSwitchStateLogParams) ([]SwitchStateLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listSwitchStateLog, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []SwitchStateLog
|
||||
for rows.Next() {
|
||||
var i SwitchStateLog
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.SwitchState,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const powerLogForDeviceToDate = `-- name: PowerLogForDeviceToDate :many
|
||||
select power_log.id, time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power, factor, voltage, "current", sensor_temperature, device_tag.id, device_name, tag
|
||||
from power_log
|
||||
join device_tag on device_name = power_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc
|
||||
`
|
||||
|
||||
type PowerLogForDeviceToDateParams struct {
|
||||
Tag string
|
||||
FromTime time.Time
|
||||
ToTime time.Time
|
||||
}
|
||||
|
||||
type PowerLogForDeviceToDateRow struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
TotalStartTime time.Time
|
||||
Total float64
|
||||
Yesterday float64
|
||||
Today float64
|
||||
Period float64
|
||||
Power float64
|
||||
ApparentPower float64
|
||||
ReactivePower float64
|
||||
Factor float64
|
||||
Voltage float64
|
||||
Current float64
|
||||
SensorTemperature float64
|
||||
ID_2 int64
|
||||
DeviceName string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (q *Queries) PowerLogForDeviceToDate(ctx context.Context, arg PowerLogForDeviceToDateParams) ([]PowerLogForDeviceToDateRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, powerLogForDeviceToDate, arg.Tag, arg.FromTime, arg.ToTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []PowerLogForDeviceToDateRow
|
||||
for rows.Next() {
|
||||
var i PowerLogForDeviceToDateRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.TotalStartTime,
|
||||
&i.Total,
|
||||
&i.Yesterday,
|
||||
&i.Today,
|
||||
&i.Period,
|
||||
&i.Power,
|
||||
&i.ApparentPower,
|
||||
&i.ReactivePower,
|
||||
&i.Factor,
|
||||
&i.Voltage,
|
||||
&i.Current,
|
||||
&i.SensorTemperature,
|
||||
&i.ID_2,
|
||||
&i.DeviceName,
|
||||
&i.Tag,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const switchStateLogForDeviceToDate = `-- name: SwitchStateLogForDeviceToDate :many
|
||||
select switch_state_log.id, time, sensor, switch_state, device_tag.id, device_name, tag
|
||||
from switch_state_log
|
||||
join device_tag on device_name = switch_state_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc
|
||||
`
|
||||
|
||||
type SwitchStateLogForDeviceToDateParams struct {
|
||||
Tag string
|
||||
FromTime time.Time
|
||||
ToTime time.Time
|
||||
}
|
||||
|
||||
type SwitchStateLogForDeviceToDateRow struct {
|
||||
ID int64
|
||||
Time time.Time
|
||||
Sensor string
|
||||
SwitchState string
|
||||
ID_2 int64
|
||||
DeviceName string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (q *Queries) SwitchStateLogForDeviceToDate(ctx context.Context, arg SwitchStateLogForDeviceToDateParams) ([]SwitchStateLogForDeviceToDateRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, switchStateLogForDeviceToDate, arg.Tag, arg.FromTime, arg.ToTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []SwitchStateLogForDeviceToDateRow
|
||||
for rows.Next() {
|
||||
var i SwitchStateLogForDeviceToDateRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Time,
|
||||
&i.Sensor,
|
||||
&i.SwitchState,
|
||||
&i.ID_2,
|
||||
&i.DeviceName,
|
||||
&i.Tag,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const truncateDeviceTag = `-- name: TruncateDeviceTag :exec
|
||||
delete from device_tag
|
||||
`
|
||||
|
||||
func (q *Queries) TruncateDeviceTag(ctx context.Context) error {
|
||||
_, err := q.db.ExecContext(ctx, truncateDeviceTag)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateDeviceTag = `-- name: UpdateDeviceTag :one
|
||||
UPDATE device_tag
|
||||
set device_name = ?,
|
||||
tag = ?
|
||||
WHERE id = ?
|
||||
RETURNING id, device_name, tag
|
||||
`
|
||||
|
||||
type UpdateDeviceTagParams struct {
|
||||
DeviceName string
|
||||
Tag string
|
||||
ID int64
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateDeviceTag(ctx context.Context, arg UpdateDeviceTagParams) (DeviceTag, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateDeviceTag, arg.DeviceName, arg.Tag, arg.ID)
|
||||
var i DeviceTag
|
||||
err := row.Scan(&i.ID, &i.DeviceName, &i.Tag)
|
||||
return i, err
|
||||
}
|
||||
27
go.mod
Normal file
27
go.mod
Normal file
@@ -0,0 +1,27 @@
|
||||
module sensor_dashboard
|
||||
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
modernc.org/sqlite v1.46.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
67
go.sum
Normal file
67
go.sum
Normal file
@@ -0,0 +1,67 @@
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
71
main.go
Normal file
71
main.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseUrl string `yaml:"base_url"`
|
||||
MqttUrl string `yaml:"mqtt_url"`
|
||||
MqttDevices []MqttDevice `yaml:"mqtt_devices"`
|
||||
DatasourceDir string `yaml:"datasource_dir"`
|
||||
ServeFromFS bool `yaml:"serve_from_fs"`
|
||||
}
|
||||
|
||||
type MqttDevice struct {
|
||||
Name string `yaml:"name"`
|
||||
Topic string `yaml:"topic"`
|
||||
Type string `yaml:"type"`
|
||||
Tag string `yaml:"tag"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Println("No .env file found, all config coming from system env")
|
||||
}
|
||||
//
|
||||
//dashboardHost := os.Getenv("SENSOR_DASHBOARD_WEB_BASE_URL")
|
||||
//mqttServer := os.Getenv("SENSOR_DASHBOARD_MQTT_SERVER")
|
||||
//mqttTopicsJson := os.Getenv("SENSOR_DASHBOARD_MQTT_TOPICS")
|
||||
//datasourceDir := os.Getenv("SENSOR_DASHBOARD_DATASOURCE_DIR")
|
||||
|
||||
configPath := os.Getenv("SENSOR_DASHBOARD_CONFIG_PATH")
|
||||
if configPath == "" {
|
||||
configPath = "./config.yaml"
|
||||
}
|
||||
f, err := os.Open(configPath)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var config Config
|
||||
decoder := yaml.NewDecoder(f)
|
||||
err = decoder.Decode(&config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
queries := InitDB(config.DatasourceDir)
|
||||
client, _ := InitMqtt(config.MqttUrl, config.MqttDevices, *queries)
|
||||
|
||||
handler, err := Api(*queries, config)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Starting server on " + config.BaseUrl)
|
||||
log.Fatal(http.ListenAndServe(config.BaseUrl, handler))
|
||||
|
||||
client.Disconnect(250)
|
||||
|
||||
}
|
||||
220
mqtt_client.go
Normal file
220
mqtt_client.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sensor_dashboard/db"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
)
|
||||
|
||||
type MqttHumiditySensorMessage struct {
|
||||
Time IsoTime `json:"Time"`
|
||||
SensorData HumiditySensorData `json:"AM2301"`
|
||||
TempUnit string `json:"TempUnit"`
|
||||
}
|
||||
|
||||
type MqttPowerSensorMessage struct {
|
||||
Time IsoTime `json:"Time"`
|
||||
SensorData PowerSensorData `json:"ENERGY"`
|
||||
SensorTemperature SensorTemperature `json:"ESP32"`
|
||||
TempUnit string `json:"TempUnit"`
|
||||
}
|
||||
|
||||
type MqttSwitchStateSensorMessage struct {
|
||||
PowerState string `json:"POWER"`
|
||||
}
|
||||
|
||||
type HumiditySensorData struct {
|
||||
Temperature float64 `json:"Temperature"`
|
||||
Humidity float64 `json:"Humidity"`
|
||||
DewPoint float64 `json:"DewPoint"`
|
||||
}
|
||||
type PowerSensorData struct {
|
||||
TotalStartTime IsoTime `json:"TotalStartTime"`
|
||||
Total float64 `json:"Total"`
|
||||
Yesterday float64 `json:"Yesterday"`
|
||||
Today float64 `json:"Today"`
|
||||
Period float64 `json:"Period"`
|
||||
Power float64 `json:"Power"`
|
||||
ApparentPower float64 `json:"ApparentPower"`
|
||||
ReactivePower float64 `json:"ReactivePower"`
|
||||
Factor float64 `json:"Factor"`
|
||||
Voltage float64 `json:"Voltage"`
|
||||
Current float64 `json:"Current"`
|
||||
}
|
||||
|
||||
type SensorTemperature struct {
|
||||
Temperature float64 `json:"Temperature"`
|
||||
}
|
||||
|
||||
type IsoTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
const sensorTimeLayout = "2006-01-02T15:04:05"
|
||||
|
||||
func (ct *IsoTime) UnmarshalJSON(b []byte) (err error) {
|
||||
s := strings.Trim(string(b), "\"")
|
||||
if s == "null" {
|
||||
ct.Time = time.Time{}
|
||||
return
|
||||
}
|
||||
ct.Time, err = time.Parse(sensorTimeLayout, s)
|
||||
return
|
||||
}
|
||||
|
||||
var wildcardHandler = func(client mqtt.Client, msg mqtt.Message) {
|
||||
log.Printf("got wildcard message on topic %s with payload: %s\n", msg.Topic(), msg.Payload())
|
||||
}
|
||||
|
||||
func messageHandler(queries db.Queries, sensorName string, sensorType string) func(mqtt.Client, mqtt.Message) {
|
||||
return func(client mqtt.Client, msg mqtt.Message) {
|
||||
log.Printf("handling message for %s! topic: %s, payload: %s\n", sensorName, msg.Topic(), msg.Payload())
|
||||
|
||||
switch strings.ToLower(sensorType) {
|
||||
case "humidity":
|
||||
var message MqttHumiditySensorMessage
|
||||
err := json.Unmarshal(msg.Payload(), &message)
|
||||
if err != nil {
|
||||
log.Printf("error unmarshalling mqtt message: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = queries.CreateHumidityLog(context.Background(), db.CreateHumidityLogParams{
|
||||
Sensor: sensorName,
|
||||
Time: message.Time.Time,
|
||||
Temperature: message.SensorData.Temperature,
|
||||
Humidity: message.SensorData.Humidity,
|
||||
DewPoint: message.SensorData.DewPoint,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("error creating log: %s\n", err)
|
||||
}
|
||||
case "power":
|
||||
var message MqttPowerSensorMessage
|
||||
err := json.Unmarshal(msg.Payload(), &message)
|
||||
if err != nil {
|
||||
log.Printf("error unmarshalling mqtt message: %s\n", err)
|
||||
}
|
||||
|
||||
_, err = queries.CreatePowerLog(context.Background(), db.CreatePowerLogParams{
|
||||
Time: message.Time.Time,
|
||||
Sensor: sensorName,
|
||||
TotalStartTime: message.SensorData.TotalStartTime.Time,
|
||||
Total: message.SensorData.Total,
|
||||
Yesterday: message.SensorData.Yesterday,
|
||||
Today: message.SensorData.Today,
|
||||
Period: message.SensorData.Period,
|
||||
Power: message.SensorData.Power,
|
||||
ApparentPower: message.SensorData.ApparentPower,
|
||||
ReactivePower: message.SensorData.ReactivePower,
|
||||
Factor: message.SensorData.Factor,
|
||||
Voltage: message.SensorData.Voltage,
|
||||
Current: message.SensorData.Current,
|
||||
SensorTemperature: message.SensorTemperature.Temperature,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("error creating log: %s\n", err)
|
||||
}
|
||||
case "switch":
|
||||
var message MqttSwitchStateSensorMessage
|
||||
err := json.Unmarshal(msg.Payload(), &message)
|
||||
if err != nil {
|
||||
log.Printf("error unmarshalling mqtt message: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = queries.CreateSwitchStateLog(context.Background(), db.CreateSwitchStateLogParams{
|
||||
Time: time.Now(),
|
||||
Sensor: sensorName,
|
||||
SwitchState: message.PowerState,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("error creating log: %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
|
||||
log.Printf("Connected to broker!")
|
||||
}
|
||||
|
||||
var connectionLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
|
||||
log.Printf("connection lost: %v", err)
|
||||
}
|
||||
|
||||
func createDeviceTags(queries db.Queries, sensors []MqttDevice) error {
|
||||
|
||||
err := queries.TruncateDeviceTag(context.Background())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sensor := range sensors {
|
||||
_, err = queries.CreateDeviceTag(context.Background(), db.CreateDeviceTagParams{
|
||||
DeviceName: sensor.Name,
|
||||
Tag: sensor.Tag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func InitMqtt(brokerUrl string, sensors []MqttDevice, queries db.Queries) (mqtt.Client, error) {
|
||||
|
||||
err := createDeviceTags(queries, sensors)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Listening for sensors:", sensors)
|
||||
|
||||
opts := mqtt.NewClientOptions().AddBroker(brokerUrl)
|
||||
opts.SetClientID("go-mqtt-dashboard")
|
||||
opts.SetKeepAlive(2 * time.Second)
|
||||
opts.SetPingTimeout(1 * time.Second)
|
||||
opts.SetDefaultPublishHandler(wildcardHandler)
|
||||
opts.OnConnect = connectHandler
|
||||
opts.OnConnectionLost = connectionLostHandler
|
||||
client := mqtt.NewClient(opts)
|
||||
if token := client.Connect(); token.Wait() && token.Error() != nil {
|
||||
panic(token.Error())
|
||||
}
|
||||
|
||||
for _, sensor := range sensors {
|
||||
sub(client, sensor, queries)
|
||||
}
|
||||
//subAll(client)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func subAll(client mqtt.Client) {
|
||||
topic := "#"
|
||||
token := client.Subscribe(topic, 1, wildcardHandler)
|
||||
if token.Wait() && token.Error() != nil {
|
||||
panic(token.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func sub(client mqtt.Client, sensor MqttDevice, queries db.Queries) {
|
||||
topic := sensor.Topic
|
||||
token := client.Subscribe(topic, 1, messageHandler(queries, sensor.Name, sensor.Type))
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
panic(token.Error())
|
||||
}
|
||||
log.Printf("Subscribed to topic: %s\n", topic)
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
45
public/index.css
Normal file
45
public/index.css
Normal file
@@ -0,0 +1,45 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.select-form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border: 1px lightgray solid;
|
||||
gap: 2rem;
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
|
||||
& > .pickers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
& > label {
|
||||
padding: 0.5rem 0rem;
|
||||
& > select {
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
& > input {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
}
|
||||
73
public/index.html
Normal file
73
public/index.html
Normal file
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment"></script>
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/moment-timezone@0.6.0/moment-timezone.min.js"></script>-->
|
||||
<script src="https://momentjs.com/downloads/moment-timezone-with-data.min.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment"></script>
|
||||
|
||||
<script defer src="index.js"></script>
|
||||
<!-- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>-->
|
||||
|
||||
<!-- <script defer src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>-->
|
||||
<!-- <script defer src="https://cdn.jsdelivr.net/npm/chartjs-plugin-colorschemes"></script>-->
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>Luftfeuchtigkeitsdaten</h1>
|
||||
|
||||
|
||||
<form class="select-form">
|
||||
|
||||
<div class="pickers">
|
||||
|
||||
<label for="tag-select">Daten von
|
||||
<select name="tag-select" id="tag-select">
|
||||
<option value="">--Lade--</option>
|
||||
</select>
|
||||
</label>
|
||||
<label for="range-select">Wie lange?
|
||||
|
||||
<select name="range-select" id="range-select">
|
||||
<option value="3" selected>3 Stunden</option>
|
||||
<option value="6">6 Stunden</option>
|
||||
<option value="12">12 Stunden</option>
|
||||
<option value="24">1 Tag</option>
|
||||
<option value="72">3 Tage</option>
|
||||
<option value="72">3 Tage</option>
|
||||
<option value="168">1 Woche</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</label>
|
||||
<input type="submit" value="Anwenden" id="submit-select">
|
||||
</form>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="humChart" class="chart"> Lade...</canvas>
|
||||
<canvas id="pwrChart" class="chart"> Lade...</canvas>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!--<form>-->
|
||||
|
||||
<!-- <label for="range-select">Choose a range:</label>-->
|
||||
|
||||
<!-- <select name="range-select" id="table-select">-->
|
||||
<!-- <option value="">--Please choose an option--</option>-->
|
||||
<!-- <option value="humidity">Luftfeuchtigkeit</option>-->
|
||||
<!-- <option value="power">Stromverbrauch</option>-->
|
||||
<!-- </select>-->
|
||||
|
||||
<!-- <input type="submit" value="Submit" id="submit-table-select">-->
|
||||
<!--</form>-->
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
244
public/index.js
Normal file
244
public/index.js
Normal file
@@ -0,0 +1,244 @@
|
||||
|
||||
moment.tz('Europe/Berlin')
|
||||
Chart.defaults.font.size = 24;
|
||||
|
||||
let humChart, pwrChart
|
||||
|
||||
function toMoment(time) {
|
||||
|
||||
|
||||
let m = moment(time);
|
||||
m = m.subtract({hours: 1});
|
||||
return m
|
||||
}
|
||||
|
||||
async function fetchTags() {
|
||||
let res = await fetch("/tags");
|
||||
let tags = await res.json();
|
||||
return tags;
|
||||
}
|
||||
|
||||
async function fetchChartData(series, tag, from) {
|
||||
|
||||
const queryParams = new URLSearchParams({tag, from});
|
||||
const url = `/${series}?${queryParams.toString()}`
|
||||
let res = await fetch(url);
|
||||
let data = await res.json();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchTagData(tag, from) {
|
||||
|
||||
let humidityDataPromise = fetchChartData("humidity",tag, from);
|
||||
let powerDataPromise = fetchChartData("power",tag, from);
|
||||
let stateDataPromise = fetchChartData("state",tag, from);
|
||||
|
||||
let [humidityData, powerData, stateData] = await Promise.all([humidityDataPromise, powerDataPromise,stateDataPromise])
|
||||
return {
|
||||
humidityData, powerData, stateData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initHumChart({humidityData, powerData, stateData}) {
|
||||
|
||||
// let humidityData = await fetchChartData("humidity",tag, new Date().getTime());
|
||||
// let powerData = await fetchChartData("power",tag, new Date().getTime());
|
||||
// let stateData = await fetchChartData("state",tag, new Date().getTime());
|
||||
|
||||
let humidityDataSet = {
|
||||
label: 'Luftfeuchtigkeit',
|
||||
yAxisID: 'humidity',
|
||||
data: humidityData.map(v => ({x: toMoment(v.Time), y: v.Humidity})),
|
||||
backgroundColor: 'rgba(8,62,236,0.2)',
|
||||
borderColor: 'rgb(101,119,234)',
|
||||
borderWidth: 1
|
||||
}
|
||||
|
||||
|
||||
let powerDataSet = {
|
||||
label: 'Apparent Power',
|
||||
yAxisID: 'power',
|
||||
data: powerData.map(v => ({x: toMoment(v.Time), y: v.ApparentPower})),
|
||||
backgroundColor: 'rgba(193,151,91,0.2)',
|
||||
borderColor: 'rgb(207,179,106)',
|
||||
borderWidth: 1
|
||||
}
|
||||
|
||||
// let stateDataSet = {
|
||||
// label: 'An/Aus',
|
||||
// data: stateData.map(v => ({x: v.Time, y: v.SwitchState})),
|
||||
// backgroundColor: 'rgba(125,158,124,0.2)',
|
||||
// borderColor: 'rgb(8,255,0)',
|
||||
// borderWidth: 1
|
||||
// }
|
||||
|
||||
chart = new Chart(document.getElementById('humChart').getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [humidityDataSet, powerDataSet]
|
||||
},
|
||||
options: {
|
||||
scales:{
|
||||
x:{
|
||||
type: 'time',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20
|
||||
},
|
||||
time: {
|
||||
displayFormats: {minute: 'HH:mm'}
|
||||
}
|
||||
},
|
||||
|
||||
humidity: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
ticks:
|
||||
{
|
||||
beginAtZero: true,
|
||||
},
|
||||
grid: { display: false }
|
||||
},
|
||||
power: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
ticks:
|
||||
{
|
||||
beginAtZero: true,
|
||||
},
|
||||
grid: { display: false }
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
return chart
|
||||
}
|
||||
|
||||
function initPwrChart({humidityData, powerData, stateData}) {
|
||||
|
||||
//
|
||||
// let humidityDataSet = {
|
||||
// label: 'Luftfeuchtigkeit',
|
||||
// yAxisID: 'humidity',
|
||||
// data: humidityData.map(v => ({x: toMoment(v.Time), y: v.Humidity})),
|
||||
// backgroundColor: 'rgba(8,62,236,0.2)',
|
||||
// borderColor: 'rgb(101,119,234)',
|
||||
// borderWidth: 1
|
||||
// }
|
||||
|
||||
|
||||
let totalDataset = {
|
||||
label: 'Total KWh',
|
||||
yAxisID: 'total',
|
||||
data: powerData.map(v => ({x: toMoment(v.Time), y: v.Total})),
|
||||
backgroundColor: 'rgba(193,151,91,0.2)',
|
||||
borderColor: 'rgb(207,179,106)',
|
||||
borderWidth: 1
|
||||
}
|
||||
|
||||
let yesterdayDataset = {
|
||||
label: 'Gestern KWh',
|
||||
yAxisID: 'yesterday',
|
||||
data: powerData.map(v => ({x: toMoment(v.Time), y: v.Yesterday})),
|
||||
backgroundColor: 'rgba(110,70,70,0.2)',
|
||||
borderColor: 'rgb(207,106,114)',
|
||||
borderWidth: 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
chart = new Chart(document.getElementById('pwrChart').getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [totalDataset, yesterdayDataset]
|
||||
},
|
||||
options: {
|
||||
scales:{
|
||||
x:{
|
||||
type: 'time',
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 20
|
||||
},
|
||||
time: {
|
||||
displayFormats: {minute: 'HH:mm'}
|
||||
}
|
||||
},
|
||||
|
||||
total: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
ticks:
|
||||
{
|
||||
beginAtZero: true,
|
||||
},
|
||||
grid: { display: false }
|
||||
},
|
||||
yesterday: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
ticks:
|
||||
{
|
||||
beginAtZero: true,
|
||||
},
|
||||
grid: { display: false }
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
return chart
|
||||
}
|
||||
|
||||
async function loadChartData(tag, date) {
|
||||
console.log(date)
|
||||
let data = await fetchTagData(tag, date);
|
||||
|
||||
humChart = initHumChart(data);
|
||||
pwrChart = initPwrChart(data);
|
||||
}
|
||||
|
||||
|
||||
async function populateOptions() {
|
||||
|
||||
let tagSelectElement = document.getElementById("tag-select")
|
||||
tagSelectElement.active = false
|
||||
let tags = await fetchTags();
|
||||
tagSelectElement.innerHTML = '';
|
||||
for (let tag of tags) {
|
||||
let option = document.createElement("option");
|
||||
option.value = tag;
|
||||
option.text = tag;
|
||||
tagSelectElement.appendChild(option);
|
||||
}
|
||||
tagSelectElement.active = true
|
||||
let rangeSelectElement = document.getElementById("range-select")
|
||||
document.getElementById("submit-select").addEventListener("click", function(event){
|
||||
event.preventDefault();
|
||||
console.log("submit clicked", rangeSelectElement.value, tagSelectElement.value);
|
||||
let m = moment().subtract({hours: rangeSelectElement.value});
|
||||
console.log(m);
|
||||
var fromDate = m.toDate();
|
||||
console.log(fromDate.toISOString());
|
||||
if (humChart !== undefined) {
|
||||
humChart.destroy();
|
||||
}
|
||||
if (pwrChart !== undefined) {
|
||||
pwrChart.destroy();
|
||||
}
|
||||
|
||||
loadChartData(tagSelectElement.value, fromDate.toISOString());
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
populateOptions();
|
||||
|
||||
let m = moment().subtract({hours: 3});
|
||||
var fromDate = m.toDate();
|
||||
|
||||
loadChartData("Bad Unten", fromDate.toISOString());
|
||||
136
queries.sql
Normal file
136
queries.sql
Normal file
@@ -0,0 +1,136 @@
|
||||
-- name: ListHumidityLog :many
|
||||
select *
|
||||
from humidity_log
|
||||
order by time desc
|
||||
limit ? offset ?;
|
||||
|
||||
-- name: HumidityLogForDeviceToDate :many
|
||||
select *
|
||||
from humidity_log
|
||||
join device_tag on device_name = humidity_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc;
|
||||
|
||||
|
||||
-- name: GetHumidityLog :one
|
||||
select *
|
||||
from humidity_log
|
||||
where id = ?
|
||||
limit 1;
|
||||
|
||||
-- name: CreateHumidityLog :one
|
||||
insert
|
||||
into humidity_log (time, sensor, temperature, humidity, dew_point)
|
||||
values (?, ?, ?, ?, ?)
|
||||
returning *;
|
||||
|
||||
-- name: DeleteHumidityLog :exec
|
||||
delete
|
||||
from humidity_log
|
||||
where id = ?;
|
||||
|
||||
|
||||
-- name: ListPowerLog :many
|
||||
select *
|
||||
from power_log
|
||||
order by time desc
|
||||
limit ? offset ?;
|
||||
|
||||
-- name: PowerLogForDeviceToDate :many
|
||||
select *
|
||||
from power_log
|
||||
join device_tag on device_name = power_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc;
|
||||
|
||||
|
||||
-- name: GetPowerLog :one
|
||||
select *
|
||||
from power_log
|
||||
where id = ?
|
||||
limit 1;
|
||||
|
||||
-- name: CreatePowerLog :one
|
||||
insert
|
||||
into power_log (time, sensor, total_start_time, total, yesterday, today, period, power, apparent_power, reactive_power,
|
||||
factor, voltage, current, sensor_temperature)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
returning *;
|
||||
|
||||
-- name: DeletePowerLog :exec
|
||||
delete
|
||||
from power_log
|
||||
where id = ?;
|
||||
|
||||
-- name: ListSwitchStateLog :many
|
||||
select *
|
||||
from switch_state_log
|
||||
order by time desc
|
||||
limit ? offset ?;
|
||||
|
||||
-- name: SwitchStateLogForDeviceToDate :many
|
||||
select *
|
||||
from switch_state_log
|
||||
join device_tag on device_name = switch_state_log.sensor
|
||||
where device_tag.tag = ?
|
||||
and (time between ? and ?)
|
||||
order by time desc;
|
||||
|
||||
-- name: GetSwitchStateLog :one
|
||||
select *
|
||||
from switch_state_log
|
||||
where id = ?
|
||||
limit 1;
|
||||
|
||||
-- name: CreateSwitchStateLog :one
|
||||
insert
|
||||
into switch_state_log (time, sensor, switch_state)
|
||||
values (?, ?, ?)
|
||||
returning *;
|
||||
|
||||
-- name: DeleteSwitchStateLog :exec
|
||||
delete
|
||||
from switch_state_log
|
||||
where id = ?;
|
||||
|
||||
-- name: ListDeviceTag :many
|
||||
select *
|
||||
from device_tag;
|
||||
|
||||
-- name: ListDevices :many
|
||||
select tag
|
||||
from device_tag
|
||||
group by tag;
|
||||
|
||||
-- name: GetDeviceTag :one
|
||||
select *
|
||||
from device_tag
|
||||
where id = ?
|
||||
limit 1;
|
||||
|
||||
-- name: CreateDeviceTag :one
|
||||
insert
|
||||
into device_tag (device_name, tag)
|
||||
values (?, ?)
|
||||
returning *;
|
||||
|
||||
-- name: UpdateDeviceTag :one
|
||||
UPDATE device_tag
|
||||
set device_name = ?,
|
||||
tag = ?
|
||||
WHERE id = ?
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteDeviceTag :exec
|
||||
delete
|
||||
from device_tag
|
||||
where id = ?;
|
||||
|
||||
-- name: TruncateDeviceTag :exec
|
||||
delete
|
||||
from device_tag;
|
||||
|
||||
|
||||
|
||||
43
schema.sql
Normal file
43
schema.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
create table if not exists humidity_log
|
||||
(
|
||||
id integer primary key,
|
||||
time datetime not null,
|
||||
sensor text not null,
|
||||
temperature real not null,
|
||||
humidity real not null,
|
||||
dew_point real not null
|
||||
);
|
||||
|
||||
create table if not exists power_log
|
||||
(
|
||||
id integer primary key,
|
||||
time datetime not null,
|
||||
sensor text not null,
|
||||
total_start_time datetime not null,
|
||||
total real not null,
|
||||
yesterday real not null,
|
||||
today real not null,
|
||||
period real not null,
|
||||
power real not null,
|
||||
apparent_power real not null,
|
||||
reactive_power real not null,
|
||||
factor real not null,
|
||||
voltage real not null,
|
||||
current real not null,
|
||||
sensor_temperature real not null
|
||||
);
|
||||
|
||||
create table if not exists switch_state_log
|
||||
(
|
||||
id integer primary key,
|
||||
time datetime not null,
|
||||
sensor text not null,
|
||||
switch_state text not null
|
||||
);
|
||||
|
||||
create table if not exists device_tag
|
||||
(
|
||||
id integer primary key,
|
||||
device_name text not null,
|
||||
tag text not null
|
||||
)
|
||||
Reference in New Issue
Block a user