add script triggers.
This commit is contained in:
@@ -42,6 +42,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bus.JoinPrivileged(resolver)
|
bus.JoinPrivileged(resolver)
|
||||||
|
bus.Join(services.NewTicker())
|
||||||
bus.Join(effectenforcer.NewService(resolver))
|
bus.Join(effectenforcer.NewService(resolver))
|
||||||
bus.Join(nanoleaf.NewService())
|
bus.Join(nanoleaf.NewService())
|
||||||
bus.Join(hue.NewService())
|
bus.Join(hue.NewService())
|
||||||
|
|||||||
+1
-1
@@ -10,5 +10,5 @@ type Log struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l Log) EventDescription() string {
|
func (l Log) EventDescription() string {
|
||||||
return fmt.Sprintf("LOG(id:%s level:%s code:%s :: %s)", l.ID, l.Level, l.Code, l.Message)
|
return fmt.Sprintf("Log(id:%s level:%s code:%s :: %s)", l.ID, l.Level, l.Code, l.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Time struct {
|
||||||
|
Hour int
|
||||||
|
Minute int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Time) EventDescription() string {
|
||||||
|
return fmt.Sprintf("Time(%02d:%02d)", t.Hour, t.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Time) VerboseKey() string {
|
||||||
|
return "time"
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"git.aiterp.net/lucifer3/server/events"
|
"git.aiterp.net/lucifer3/server/events"
|
||||||
"git.aiterp.net/lucifer3/server/services/script"
|
"git.aiterp.net/lucifer3/server/services/script"
|
||||||
"git.aiterp.net/lucifer3/server/services/uistate"
|
"git.aiterp.net/lucifer3/server/services/uistate"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
"log"
|
"log"
|
||||||
@@ -14,6 +15,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var zeroUUID = uuid.UUID{
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
func New(addr string) (lucifer3.Service, error) {
|
func New(addr string) (lucifer3.Service, error) {
|
||||||
svc := &service{}
|
svc := &service{}
|
||||||
|
|
||||||
@@ -67,6 +75,19 @@ func New(addr string) (lucifer3.Service, error) {
|
|||||||
bus.RunCommand(*input.UpdateScript)
|
bus.RunCommand(*input.UpdateScript)
|
||||||
case input.ExecuteScript != nil:
|
case input.ExecuteScript != nil:
|
||||||
bus.RunCommand(*input.ExecuteScript)
|
bus.RunCommand(*input.ExecuteScript)
|
||||||
|
case input.UpdateTrigger != nil:
|
||||||
|
if input.UpdateTrigger.ID == zeroUUID {
|
||||||
|
input.UpdateTrigger.ID = uuid.New()
|
||||||
|
}
|
||||||
|
if input.UpdateTrigger.ScriptPre == nil {
|
||||||
|
input.UpdateTrigger.ScriptPre = make([]script.Line, 0)
|
||||||
|
}
|
||||||
|
if input.UpdateTrigger.ScriptPost == nil {
|
||||||
|
input.UpdateTrigger.ScriptPost = make([]script.Line, 0)
|
||||||
|
}
|
||||||
|
bus.RunCommand(*input.UpdateTrigger)
|
||||||
|
case input.DeleteTrigger != nil:
|
||||||
|
bus.RunCommand(*input.DeleteTrigger)
|
||||||
default:
|
default:
|
||||||
return c.String(400, "No supported command found in input")
|
return c.String(400, "No supported command found in input")
|
||||||
}
|
}
|
||||||
@@ -123,8 +144,10 @@ type commandInput struct {
|
|||||||
ConnectDevice *commands.ConnectDevice `json:"connectDevice,omitempty"`
|
ConnectDevice *commands.ConnectDevice `json:"connectDevice,omitempty"`
|
||||||
SearchDevices *commands.SearchDevices `json:"searchDevices,omitempty"`
|
SearchDevices *commands.SearchDevices `json:"searchDevices,omitempty"`
|
||||||
ForgetDevice *commands.ForgetDevice `json:"forgetDevice,omitempty"`
|
ForgetDevice *commands.ForgetDevice `json:"forgetDevice,omitempty"`
|
||||||
UpdateScript *script.UpdateScript `json:"updateScript,omitempty"`
|
UpdateScript *script.Update `json:"updateScript,omitempty"`
|
||||||
ExecuteScript *script.ExecuteScript `json:"executeScript,omitempty"`
|
ExecuteScript *script.Execute `json:"executeScript,omitempty"`
|
||||||
|
UpdateTrigger *script.UpdateTrigger `json:"updateTrigger,omitempty"`
|
||||||
|
DeleteTrigger *script.DeleteTrigger `json:"deleteTrigger,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type assignInput struct {
|
type assignInput struct {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE script_trigger (
|
||||||
|
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||||
|
event VARCHAR(255) NOT NULL,
|
||||||
|
device_match TEXT NOT NULL,
|
||||||
|
parameter TEXT NOT NULL,
|
||||||
|
script_target TEXT NOT NULL,
|
||||||
|
script_name VARCHAR(255) NOT NULL,
|
||||||
|
script_pre JSON NOT NULL,
|
||||||
|
script_post JSON NOT NULL
|
||||||
|
)
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE IF EXISTS script_trigger;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -48,6 +48,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
|
|||||||
if q.deleteDeviceInfoLikeStmt, err = db.PrepareContext(ctx, deleteDeviceInfoLike); err != nil {
|
if q.deleteDeviceInfoLikeStmt, err = db.PrepareContext(ctx, deleteDeviceInfoLike); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query DeleteDeviceInfoLike: %w", err)
|
return nil, fmt.Errorf("error preparing query DeleteDeviceInfoLike: %w", err)
|
||||||
}
|
}
|
||||||
|
if q.deleteScriptTriggerStmt, err = db.PrepareContext(ctx, deleteScriptTrigger); err != nil {
|
||||||
|
return nil, fmt.Errorf("error preparing query DeleteScriptTrigger: %w", err)
|
||||||
|
}
|
||||||
if q.insertDeviceAliasStmt, err = db.PrepareContext(ctx, insertDeviceAlias); err != nil {
|
if q.insertDeviceAliasStmt, err = db.PrepareContext(ctx, insertDeviceAlias); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query InsertDeviceAlias: %w", err)
|
return nil, fmt.Errorf("error preparing query InsertDeviceAlias: %w", err)
|
||||||
}
|
}
|
||||||
@@ -63,6 +66,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
|
|||||||
if q.listDeviceInfosStmt, err = db.PrepareContext(ctx, listDeviceInfos); err != nil {
|
if q.listDeviceInfosStmt, err = db.PrepareContext(ctx, listDeviceInfos); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query ListDeviceInfos: %w", err)
|
return nil, fmt.Errorf("error preparing query ListDeviceInfos: %w", err)
|
||||||
}
|
}
|
||||||
|
if q.listScriptTriggersStmt, err = db.PrepareContext(ctx, listScriptTriggers); err != nil {
|
||||||
|
return nil, fmt.Errorf("error preparing query ListScriptTriggers: %w", err)
|
||||||
|
}
|
||||||
if q.listScriptVariablesStmt, err = db.PrepareContext(ctx, listScriptVariables); err != nil {
|
if q.listScriptVariablesStmt, err = db.PrepareContext(ctx, listScriptVariables); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query ListScriptVariables: %w", err)
|
return nil, fmt.Errorf("error preparing query ListScriptVariables: %w", err)
|
||||||
}
|
}
|
||||||
@@ -78,6 +84,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
|
|||||||
if q.replaceDeviceInfoStmt, err = db.PrepareContext(ctx, replaceDeviceInfo); err != nil {
|
if q.replaceDeviceInfoStmt, err = db.PrepareContext(ctx, replaceDeviceInfo); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query ReplaceDeviceInfo: %w", err)
|
return nil, fmt.Errorf("error preparing query ReplaceDeviceInfo: %w", err)
|
||||||
}
|
}
|
||||||
|
if q.replaceScriptTriggerStmt, err = db.PrepareContext(ctx, replaceScriptTrigger); err != nil {
|
||||||
|
return nil, fmt.Errorf("error preparing query ReplaceScriptTrigger: %w", err)
|
||||||
|
}
|
||||||
if q.saveScriptStmt, err = db.PrepareContext(ctx, saveScript); err != nil {
|
if q.saveScriptStmt, err = db.PrepareContext(ctx, saveScript); err != nil {
|
||||||
return nil, fmt.Errorf("error preparing query SaveScript: %w", err)
|
return nil, fmt.Errorf("error preparing query SaveScript: %w", err)
|
||||||
}
|
}
|
||||||
@@ -129,6 +138,11 @@ func (q *Queries) Close() error {
|
|||||||
err = fmt.Errorf("error closing deleteDeviceInfoLikeStmt: %w", cerr)
|
err = fmt.Errorf("error closing deleteDeviceInfoLikeStmt: %w", cerr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if q.deleteScriptTriggerStmt != nil {
|
||||||
|
if cerr := q.deleteScriptTriggerStmt.Close(); cerr != nil {
|
||||||
|
err = fmt.Errorf("error closing deleteScriptTriggerStmt: %w", cerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
if q.insertDeviceAliasStmt != nil {
|
if q.insertDeviceAliasStmt != nil {
|
||||||
if cerr := q.insertDeviceAliasStmt.Close(); cerr != nil {
|
if cerr := q.insertDeviceAliasStmt.Close(); cerr != nil {
|
||||||
err = fmt.Errorf("error closing insertDeviceAliasStmt: %w", cerr)
|
err = fmt.Errorf("error closing insertDeviceAliasStmt: %w", cerr)
|
||||||
@@ -154,6 +168,11 @@ func (q *Queries) Close() error {
|
|||||||
err = fmt.Errorf("error closing listDeviceInfosStmt: %w", cerr)
|
err = fmt.Errorf("error closing listDeviceInfosStmt: %w", cerr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if q.listScriptTriggersStmt != nil {
|
||||||
|
if cerr := q.listScriptTriggersStmt.Close(); cerr != nil {
|
||||||
|
err = fmt.Errorf("error closing listScriptTriggersStmt: %w", cerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
if q.listScriptVariablesStmt != nil {
|
if q.listScriptVariablesStmt != nil {
|
||||||
if cerr := q.listScriptVariablesStmt.Close(); cerr != nil {
|
if cerr := q.listScriptVariablesStmt.Close(); cerr != nil {
|
||||||
err = fmt.Errorf("error closing listScriptVariablesStmt: %w", cerr)
|
err = fmt.Errorf("error closing listScriptVariablesStmt: %w", cerr)
|
||||||
@@ -179,6 +198,11 @@ func (q *Queries) Close() error {
|
|||||||
err = fmt.Errorf("error closing replaceDeviceInfoStmt: %w", cerr)
|
err = fmt.Errorf("error closing replaceDeviceInfoStmt: %w", cerr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if q.replaceScriptTriggerStmt != nil {
|
||||||
|
if cerr := q.replaceScriptTriggerStmt.Close(); cerr != nil {
|
||||||
|
err = fmt.Errorf("error closing replaceScriptTriggerStmt: %w", cerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
if q.saveScriptStmt != nil {
|
if q.saveScriptStmt != nil {
|
||||||
if cerr := q.saveScriptStmt.Close(); cerr != nil {
|
if cerr := q.saveScriptStmt.Close(); cerr != nil {
|
||||||
err = fmt.Errorf("error closing saveScriptStmt: %w", cerr)
|
err = fmt.Errorf("error closing saveScriptStmt: %w", cerr)
|
||||||
@@ -236,16 +260,19 @@ type Queries struct {
|
|||||||
deleteDeviceInfoStmt *sql.Stmt
|
deleteDeviceInfoStmt *sql.Stmt
|
||||||
deleteDeviceInfoByIDStmt *sql.Stmt
|
deleteDeviceInfoByIDStmt *sql.Stmt
|
||||||
deleteDeviceInfoLikeStmt *sql.Stmt
|
deleteDeviceInfoLikeStmt *sql.Stmt
|
||||||
|
deleteScriptTriggerStmt *sql.Stmt
|
||||||
insertDeviceAliasStmt *sql.Stmt
|
insertDeviceAliasStmt *sql.Stmt
|
||||||
listDeviceAliasesStmt *sql.Stmt
|
listDeviceAliasesStmt *sql.Stmt
|
||||||
listDeviceAssignmentsStmt *sql.Stmt
|
listDeviceAssignmentsStmt *sql.Stmt
|
||||||
listDeviceAuthStmt *sql.Stmt
|
listDeviceAuthStmt *sql.Stmt
|
||||||
listDeviceInfosStmt *sql.Stmt
|
listDeviceInfosStmt *sql.Stmt
|
||||||
|
listScriptTriggersStmt *sql.Stmt
|
||||||
listScriptVariablesStmt *sql.Stmt
|
listScriptVariablesStmt *sql.Stmt
|
||||||
listScriptsStmt *sql.Stmt
|
listScriptsStmt *sql.Stmt
|
||||||
replaceDeviceAssignmentStmt *sql.Stmt
|
replaceDeviceAssignmentStmt *sql.Stmt
|
||||||
replaceDeviceAuthStmt *sql.Stmt
|
replaceDeviceAuthStmt *sql.Stmt
|
||||||
replaceDeviceInfoStmt *sql.Stmt
|
replaceDeviceInfoStmt *sql.Stmt
|
||||||
|
replaceScriptTriggerStmt *sql.Stmt
|
||||||
saveScriptStmt *sql.Stmt
|
saveScriptStmt *sql.Stmt
|
||||||
updateScriptVariablesStmt *sql.Stmt
|
updateScriptVariablesStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
@@ -262,16 +289,19 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
|||||||
deleteDeviceInfoStmt: q.deleteDeviceInfoStmt,
|
deleteDeviceInfoStmt: q.deleteDeviceInfoStmt,
|
||||||
deleteDeviceInfoByIDStmt: q.deleteDeviceInfoByIDStmt,
|
deleteDeviceInfoByIDStmt: q.deleteDeviceInfoByIDStmt,
|
||||||
deleteDeviceInfoLikeStmt: q.deleteDeviceInfoLikeStmt,
|
deleteDeviceInfoLikeStmt: q.deleteDeviceInfoLikeStmt,
|
||||||
|
deleteScriptTriggerStmt: q.deleteScriptTriggerStmt,
|
||||||
insertDeviceAliasStmt: q.insertDeviceAliasStmt,
|
insertDeviceAliasStmt: q.insertDeviceAliasStmt,
|
||||||
listDeviceAliasesStmt: q.listDeviceAliasesStmt,
|
listDeviceAliasesStmt: q.listDeviceAliasesStmt,
|
||||||
listDeviceAssignmentsStmt: q.listDeviceAssignmentsStmt,
|
listDeviceAssignmentsStmt: q.listDeviceAssignmentsStmt,
|
||||||
listDeviceAuthStmt: q.listDeviceAuthStmt,
|
listDeviceAuthStmt: q.listDeviceAuthStmt,
|
||||||
listDeviceInfosStmt: q.listDeviceInfosStmt,
|
listDeviceInfosStmt: q.listDeviceInfosStmt,
|
||||||
|
listScriptTriggersStmt: q.listScriptTriggersStmt,
|
||||||
listScriptVariablesStmt: q.listScriptVariablesStmt,
|
listScriptVariablesStmt: q.listScriptVariablesStmt,
|
||||||
listScriptsStmt: q.listScriptsStmt,
|
listScriptsStmt: q.listScriptsStmt,
|
||||||
replaceDeviceAssignmentStmt: q.replaceDeviceAssignmentStmt,
|
replaceDeviceAssignmentStmt: q.replaceDeviceAssignmentStmt,
|
||||||
replaceDeviceAuthStmt: q.replaceDeviceAuthStmt,
|
replaceDeviceAuthStmt: q.replaceDeviceAuthStmt,
|
||||||
replaceDeviceInfoStmt: q.replaceDeviceInfoStmt,
|
replaceDeviceInfoStmt: q.replaceDeviceInfoStmt,
|
||||||
|
replaceScriptTriggerStmt: q.replaceScriptTriggerStmt,
|
||||||
saveScriptStmt: q.saveScriptStmt,
|
saveScriptStmt: q.saveScriptStmt,
|
||||||
updateScriptVariablesStmt: q.updateScriptVariablesStmt,
|
updateScriptVariablesStmt: q.updateScriptVariablesStmt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ type Script struct {
|
|||||||
Data json.RawMessage
|
Data json.RawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScriptTrigger struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Event string
|
||||||
|
DeviceMatch string
|
||||||
|
Parameter string
|
||||||
|
ScriptTarget string
|
||||||
|
ScriptName string
|
||||||
|
ScriptPre json.RawMessage
|
||||||
|
ScriptPost json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
type ScriptVariable struct {
|
type ScriptVariable struct {
|
||||||
Scope string
|
Scope string
|
||||||
Name string
|
Name string
|
||||||
|
|||||||
@@ -8,8 +8,55 @@ package mysqlgen
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deleteScriptTrigger = `-- name: DeleteScriptTrigger :exec
|
||||||
|
DELETE FROM script_trigger WHERE id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteScriptTrigger(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := q.exec(ctx, q.deleteScriptTriggerStmt, deleteScriptTrigger, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listScriptTriggers = `-- name: ListScriptTriggers :many
|
||||||
|
SELECT id, event, device_match, parameter, script_target, script_name, script_pre, script_post FROM script_trigger
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListScriptTriggers(ctx context.Context) ([]ScriptTrigger, error) {
|
||||||
|
rows, err := q.query(ctx, q.listScriptTriggersStmt, listScriptTriggers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []ScriptTrigger{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i ScriptTrigger
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Event,
|
||||||
|
&i.DeviceMatch,
|
||||||
|
&i.Parameter,
|
||||||
|
&i.ScriptTarget,
|
||||||
|
&i.ScriptName,
|
||||||
|
&i.ScriptPre,
|
||||||
|
&i.ScriptPost,
|
||||||
|
); 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 listScriptVariables = `-- name: ListScriptVariables :many
|
const listScriptVariables = `-- name: ListScriptVariables :many
|
||||||
SELECT scope, name, value FROM script_variable
|
SELECT scope, name, value FROM script_variable
|
||||||
`
|
`
|
||||||
@@ -64,6 +111,36 @@ func (q *Queries) ListScripts(ctx context.Context) ([]Script, error) {
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const replaceScriptTrigger = `-- name: ReplaceScriptTrigger :exec
|
||||||
|
REPLACE INTO script_trigger (id, event, device_match, parameter, script_target, script_name, script_pre, script_post)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
type ReplaceScriptTriggerParams struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Event string
|
||||||
|
DeviceMatch string
|
||||||
|
Parameter string
|
||||||
|
ScriptTarget string
|
||||||
|
ScriptName string
|
||||||
|
ScriptPre json.RawMessage
|
||||||
|
ScriptPost json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ReplaceScriptTrigger(ctx context.Context, arg ReplaceScriptTriggerParams) error {
|
||||||
|
_, err := q.exec(ctx, q.replaceScriptTriggerStmt, replaceScriptTrigger,
|
||||||
|
arg.ID,
|
||||||
|
arg.Event,
|
||||||
|
arg.DeviceMatch,
|
||||||
|
arg.Parameter,
|
||||||
|
arg.ScriptTarget,
|
||||||
|
arg.ScriptName,
|
||||||
|
arg.ScriptPre,
|
||||||
|
arg.ScriptPost,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const saveScript = `-- name: SaveScript :exec
|
const saveScript = `-- name: SaveScript :exec
|
||||||
REPLACE INTO script (name, data) VALUES (?, ?)
|
REPLACE INTO script (name, data) VALUES (?, ?)
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -8,4 +8,14 @@ REPLACE INTO script (name, data) VALUES (?, ?);
|
|||||||
SELECT * FROM script_variable;
|
SELECT * FROM script_variable;
|
||||||
|
|
||||||
-- name: UpdateScriptVariables :exec
|
-- name: UpdateScriptVariables :exec
|
||||||
REPLACE INTO script_variable (scope, name, value) VALUES (?, ?, ?);
|
REPLACE INTO script_variable (scope, name, value) VALUES (?, ?, ?);
|
||||||
|
|
||||||
|
-- name: ListScriptTriggers :many
|
||||||
|
SELECT * FROM script_trigger;
|
||||||
|
|
||||||
|
-- name: ReplaceScriptTrigger :exec
|
||||||
|
REPLACE INTO script_trigger (id, event, device_match, parameter, script_target, script_name, script_pre, script_post)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
|
-- name: DeleteScriptTrigger :exec
|
||||||
|
DELETE FROM script_trigger WHERE id = ?;
|
||||||
@@ -141,7 +141,7 @@ func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bus.RunCommand(script.UpdateScript{
|
bus.RunCommand(script.Update{
|
||||||
Name: scr.Name,
|
Name: scr.Name,
|
||||||
Lines: lines,
|
Lines: lines,
|
||||||
})
|
})
|
||||||
@@ -174,6 +174,26 @@ func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
|
|||||||
bus.RunCommand(cmd)
|
bus.RunCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scriptTriggers, err := q.ListScriptTriggers(timeout)
|
||||||
|
if err != nil {
|
||||||
|
bus.RunEvent(events.Log{
|
||||||
|
Level: "error",
|
||||||
|
Code: "database_could_not_list_script_triggers",
|
||||||
|
Message: "Database could not list script triggers: " + err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, trig := range scriptTriggers {
|
||||||
|
bus.RunCommand(script.UpdateTrigger{
|
||||||
|
ID: trig.ID,
|
||||||
|
Event: script.TriggerEvent(trig.Event),
|
||||||
|
DeviceMatch: trig.DeviceMatch,
|
||||||
|
Parameter: trig.Parameter,
|
||||||
|
ScriptTarget: trig.ScriptTarget,
|
||||||
|
ScriptName: trig.ScriptName,
|
||||||
|
ScriptPre: fromJSON[[]script.Line](trig.ScriptPre),
|
||||||
|
ScriptPost: fromJSON[[]script.Line](trig.ScriptPost),
|
||||||
|
})
|
||||||
|
}
|
||||||
case events.DeviceAccepted:
|
case events.DeviceAccepted:
|
||||||
if event.Extras == nil {
|
if event.Extras == nil {
|
||||||
event.Extras = map[string]string{}
|
event.Extras = map[string]string{}
|
||||||
@@ -359,6 +379,33 @@ func (d *database) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Comman
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
switch command := command.(type) {
|
switch command := command.(type) {
|
||||||
|
case script.UpdateTrigger:
|
||||||
|
err := q.ReplaceScriptTrigger(timeout, mysqlgen.ReplaceScriptTriggerParams{
|
||||||
|
ID: command.ID,
|
||||||
|
Event: string(command.Event),
|
||||||
|
DeviceMatch: command.DeviceMatch,
|
||||||
|
Parameter: command.Parameter,
|
||||||
|
ScriptTarget: command.ScriptTarget,
|
||||||
|
ScriptName: command.ScriptName,
|
||||||
|
ScriptPre: toJSON(command.ScriptPre),
|
||||||
|
ScriptPost: toJSON(command.ScriptPost),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
bus.RunEvent(events.Log{
|
||||||
|
Level: "error",
|
||||||
|
Code: "database_could_not_save_trigger",
|
||||||
|
Message: "Failed to save trigger: " + err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case script.DeleteTrigger:
|
||||||
|
err := q.DeleteScriptTrigger(timeout, command.ID)
|
||||||
|
if err != nil {
|
||||||
|
bus.RunEvent(events.Log{
|
||||||
|
Level: "error",
|
||||||
|
Code: "database_could_not_delete_trigger",
|
||||||
|
Message: "Failed to delete trigger: " + err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
case script.SetVariable:
|
case script.SetVariable:
|
||||||
scopeName := "global"
|
scopeName := "global"
|
||||||
if command.Match != nil {
|
if command.Match != nil {
|
||||||
@@ -368,13 +415,21 @@ func (d *database) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Comman
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.scriptVariables[scopeName+"::"+command.Key] != command.Value {
|
if d.scriptVariables[scopeName+"::"+command.Key] != command.Value {
|
||||||
_ = q.UpdateScriptVariables(timeout, mysqlgen.UpdateScriptVariablesParams{
|
d.scriptVariables[scopeName+"::"+command.Key] = command.Value
|
||||||
|
err := q.UpdateScriptVariables(timeout, mysqlgen.UpdateScriptVariablesParams{
|
||||||
Scope: scopeName,
|
Scope: scopeName,
|
||||||
Name: command.Key,
|
Name: command.Key,
|
||||||
Value: command.Value,
|
Value: command.Value,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
bus.RunEvent(events.Log{
|
||||||
|
Level: "error",
|
||||||
|
Code: "database_could_not_save_variables",
|
||||||
|
Message: "Failed to save variable: " + err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case script.UpdateScript:
|
case script.Update:
|
||||||
j, _ := json.Marshal(command.Lines)
|
j, _ := json.Marshal(command.Lines)
|
||||||
|
|
||||||
err := q.SaveScript(timeout, mysqlgen.SaveScriptParams{
|
err := q.SaveScript(timeout, mysqlgen.SaveScriptParams{
|
||||||
@@ -391,6 +446,17 @@ func (d *database) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Comman
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fromJSON[T any](j []byte) T {
|
||||||
|
var t T
|
||||||
|
_ = json.Unmarshal(j, &t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJSON[T any](v T) json.RawMessage {
|
||||||
|
j, _ := json.Marshal(v)
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func Connect(host string, port int, username, password, dbname string) (lucifer3.ActiveService, error) {
|
func Connect(host string, port int, username, password, dbname string) (lucifer3.ActiveService, error) {
|
||||||
db, err := sql.Open("mysql", fmt.Sprintf(
|
db, err := sql.Open("mysql", fmt.Sprintf(
|
||||||
"%s:%s@(%s:%d)/%s?parseTime=true", username, password, host, port, dbname,
|
"%s:%s@(%s:%d)/%s?parseTime=true", username, password, host, port, dbname,
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ func (r *Resolver) GetByID(id string) *device.Pointer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) Resolve(pattern string) []device.Pointer {
|
func (r *Resolver) Resolve(pattern string) []device.Pointer {
|
||||||
|
if pattern == "" {
|
||||||
|
return []device.Pointer{}
|
||||||
|
}
|
||||||
|
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,26 @@ package script
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UpdateTrigger Trigger
|
||||||
|
|
||||||
|
func (c UpdateTrigger) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("script.UpdateTrigger(%s, %s(%#+v, %#+v))",
|
||||||
|
c.ID, c.Event, c.DeviceMatch, c.Parameter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTrigger struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DeleteTrigger) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("script.DeleteTrigger(%s)", c.ID)
|
||||||
|
}
|
||||||
|
|
||||||
type SetVariable struct {
|
type SetVariable struct {
|
||||||
Match *string
|
Match *string
|
||||||
Devices []string
|
Devices []string
|
||||||
@@ -15,28 +32,28 @@ type SetVariable struct {
|
|||||||
func (c SetVariable) CommandDescription() string {
|
func (c SetVariable) CommandDescription() string {
|
||||||
switch {
|
switch {
|
||||||
case c.Match != nil:
|
case c.Match != nil:
|
||||||
return fmt.Sprintf("SetVariable(tag(%s), %s, %s)", *c.Match, c.Key, c.Value)
|
return fmt.Sprintf("script.SetVariable(tag(%s), %s, %s)", *c.Match, c.Key, c.Value)
|
||||||
case c.Devices != nil:
|
case c.Devices != nil:
|
||||||
return fmt.Sprintf("SetVariable(devices(%s), %s, %s)", strings.Join(c.Devices, ", "), c.Key, c.Value)
|
return fmt.Sprintf("script.SetVariable(devices(%s), %s, %s)", strings.Join(c.Devices, ", "), c.Key, c.Value)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("SetVariable(global, %s, %s)", c.Key, c.Value)
|
return fmt.Sprintf("script.SetVariable(global, %s, %s)", c.Key, c.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateScript struct {
|
type Update struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Lines []Line `json:"lines"`
|
Lines []Line `json:"lines"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c UpdateScript) CommandDescription() string {
|
func (c Update) CommandDescription() string {
|
||||||
return fmt.Sprintf("UpdateScript(%s, [%d lines...])", c.Name, len(c.Lines))
|
return fmt.Sprintf("script.Update(%s, [%d lines...])", c.Name, len(c.Lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecuteScript struct {
|
type Execute struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Match string `json:"match"`
|
Match string `json:"match"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ExecuteScript) CommandDescription() string {
|
func (c Execute) CommandDescription() string {
|
||||||
return fmt.Sprintf("ExecuteScript(%s, %s)", c.Name, c.Match)
|
return fmt.Sprintf("script.Execute(%s, %s)", c.Name, c.Match)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-10
@@ -6,36 +6,51 @@ import (
|
|||||||
"git.aiterp.net/lucifer3/server/device"
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
"git.aiterp.net/lucifer3/server/internal/formattools"
|
"git.aiterp.net/lucifer3/server/internal/formattools"
|
||||||
"git.aiterp.net/lucifer3/server/internal/gentools"
|
"git.aiterp.net/lucifer3/server/internal/gentools"
|
||||||
|
"github.com/google/uuid"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
mu sync.Mutex
|
|
||||||
resolver device.Resolver
|
resolver device.Resolver
|
||||||
variables *Variables
|
variables *Variables
|
||||||
scripts map[string]*Script
|
scripts map[string]*Script
|
||||||
|
triggers map[uuid.UUID]*Trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Active() bool {
|
func (s *service) Active() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) HandleEvent(_ *lucifer3.EventBus, _ lucifer3.Event) {}
|
func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
|
||||||
|
for _, trig := range s.triggers {
|
||||||
|
if trig.Check(event, s.resolver) {
|
||||||
|
lines := append(make([]Line, 0, 64), trig.ScriptPre...)
|
||||||
|
script := s.scripts[trig.ScriptName]
|
||||||
|
if script != nil {
|
||||||
|
lines = append(lines, script.Lines...)
|
||||||
|
}
|
||||||
|
lines = append(lines, trig.ScriptPost...)
|
||||||
|
|
||||||
|
variables := s.variables.Get()
|
||||||
|
devices := s.resolver.Resolve(trig.ScriptTarget)
|
||||||
|
sort.Slice(devices, func(i, j int) bool {
|
||||||
|
return devices[i].ID < devices[j].ID
|
||||||
|
})
|
||||||
|
|
||||||
|
s.runScript(bus, script.Lines, trig.ScriptTarget, map[string]bool{}, devices, variables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
|
func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
|
||||||
switch command := command.(type) {
|
switch command := command.(type) {
|
||||||
case UpdateScript:
|
case Update:
|
||||||
s.mu.Lock()
|
|
||||||
s.scripts[command.Name] = &Script{
|
s.scripts[command.Name] = &Script{
|
||||||
Name: command.Name,
|
Name: command.Name,
|
||||||
Lines: command.Lines,
|
Lines: command.Lines,
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
case Execute:
|
||||||
case ExecuteScript:
|
|
||||||
s.mu.Lock()
|
|
||||||
script := s.scripts[command.Name]
|
script := s.scripts[command.Name]
|
||||||
s.mu.Unlock()
|
|
||||||
if script == nil {
|
if script == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -48,6 +63,10 @@ func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command
|
|||||||
})
|
})
|
||||||
|
|
||||||
s.runScript(bus, script.Lines, command.Match, map[string]bool{}, devices, variables)
|
s.runScript(bus, script.Lines, command.Match, map[string]bool{}, devices, variables)
|
||||||
|
case UpdateTrigger:
|
||||||
|
s.triggers[command.ID] = gentools.Ptr(Trigger(command))
|
||||||
|
case DeleteTrigger:
|
||||||
|
delete(s.triggers, command.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +74,8 @@ func NewService(resolver device.Resolver, variables *Variables) lucifer3.Service
|
|||||||
return &service{
|
return &service{
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
variables: variables,
|
variables: variables,
|
||||||
scripts: map[string]*Script{},
|
triggers: make(map[uuid.UUID]*Trigger, 8),
|
||||||
|
scripts: make(map[string]*Script, 8),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package script
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"git.aiterp.net/lucifer3/server/events"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TriggerEvent string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TEButton TriggerEvent = "Button"
|
||||||
|
TETime TriggerEvent = "Time"
|
||||||
|
TETemperatureAbove TriggerEvent = "TemperatureAbove"
|
||||||
|
TETemperatureBelow TriggerEvent = "TemperatureBelow"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trigger struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Event TriggerEvent `json:"event"`
|
||||||
|
DeviceMatch string `json:"deviceMatch"`
|
||||||
|
Parameter string `json:"parameter"`
|
||||||
|
ScriptTarget string `json:"scriptTarget"`
|
||||||
|
ScriptName string `json:"scriptName"`
|
||||||
|
ScriptPre []Line `json:"scriptPre"`
|
||||||
|
ScriptPost []Line `json:"scriptPost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) Check(event lucifer3.Event, res device.Resolver) bool {
|
||||||
|
devices := res.Resolve(t.DeviceMatch)
|
||||||
|
|
||||||
|
switch event := event.(type) {
|
||||||
|
case events.ButtonPressed:
|
||||||
|
if t.Event != TEButton {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range devices {
|
||||||
|
if dev.ID == event.ID {
|
||||||
|
return t.Parameter == event.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case events.Time:
|
||||||
|
if t.Event != TETime {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%02d:%02d", event.Hour, event.Minute) == t.Parameter
|
||||||
|
case events.TemperatureChanged:
|
||||||
|
if t.Event != TETemperatureAbove && t.Event != TETemperatureBelow {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseFloat(t.Parameter, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range devices {
|
||||||
|
if dev.ID == event.ID {
|
||||||
|
return (event.Temperature >= num) == (t.Event == TETemperatureAbove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||||
|
"git.aiterp.net/lucifer3/server/events"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTicker() lucifer3.Service {
|
||||||
|
return &tickerService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tickerService struct{}
|
||||||
|
|
||||||
|
func (s *tickerService) Active() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tickerService) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
|
||||||
|
if _, ok := event.(events.Started); ok {
|
||||||
|
go func() {
|
||||||
|
now := time.Now()
|
||||||
|
bus.RunEvent(events.Time{
|
||||||
|
Hour: now.Hour(),
|
||||||
|
Minute: now.Minute(),
|
||||||
|
})
|
||||||
|
|
||||||
|
time.Sleep(time.Now().Truncate(time.Minute).Add(time.Minute).Sub(time.Now()))
|
||||||
|
|
||||||
|
now = time.Now()
|
||||||
|
bus.RunEvent(events.Time{
|
||||||
|
Hour: now.Hour(),
|
||||||
|
Minute: now.Minute(),
|
||||||
|
})
|
||||||
|
|
||||||
|
for t := range time.NewTicker(time.Minute).C {
|
||||||
|
bus.RunEvent(events.Time{
|
||||||
|
Hour: t.Hour(),
|
||||||
|
Minute: t.Minute(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command
|
|||||||
Device: &DevicePatch{ID: id, DesiredState: gentools.ShallowCopy(&state)},
|
Device: &DevicePatch{ID: id, DesiredState: gentools.ShallowCopy(&state)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case script.UpdateScript:
|
case script.Update:
|
||||||
patches = append(patches, Patch{
|
patches = append(patches, Patch{
|
||||||
Script: &ScriptPatch{Name: command.Name, Lines: command.Lines},
|
Script: &ScriptPatch{Name: command.Name, Lines: command.Lines},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user