goscript

package module
v0.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2023 License: MIT Imports: 25 Imported by: 3

README

GoDoc

GoScript

Something like PyScript for Home Assistant but in Go. Functionality is being added as needed for my automations but once I have finished what I need I will go through PyScript and backfill any missing functionality. There will be additions to what PyScript can like the ability to add new devices to Home Assistant through MQTT.

Configuration

Configuration is stored in a Yaml file. Only websocket is required. For home assistant setup a long lived token specific to your scripts.

websocket:
  host: <server host or ip>
  port: 8123
  token: <super secret token>

To allow GoScript to create Home Assistant devices MQTT is required. Node ID is presented to the MQTT server. If it is not unique within your MQTT server messages can get lost.

mqtt:
  node_id: goscript
  mqtt:
    host: <mqtt host or ip>
    port: 1883
    ssl: false # SSL Not Supported yet

Use goscript.ParseConfig(path, modules) to parse configuration. The second parameter, modules, is a map[string]interface{} used to assign other configuration entries to custom structs. For example if I have a struct Lights

type Lights struct {
    Name      string
    Entities  []string
}

And a configuration entry like

lights:
  name: test
  entities:
    - light.door
    - light.door2

To fill in my Lights struct from the config file

modules := map[string]interface{
	"lights": &Lights{}
}

Then ParseConfig will fill in the struct properly and can get my config back from the GoScript.GetModule(key) method. Note that GetModule will return a interface, you will need to cast that back to your type.

inter, err := gs.GetModule(key)
if err != nil {
    return nil
}
lights := inter.(*Lights)

Trigger

A trigger is what starts an automation. It contains the trigger reason and function to be run.

Unique

Unique is an object that limits how the TriggerFunc runs. If just the blank object is provided any currently running functions will be killed and the current function will run after completion of currently running one. KillMe flag will kill to be run function and let the currently running one continue. UUID allows you to link multiple Uniques together.

Domain Entity Triggers

Trigger.Triggers are an array of strings. Format for each string is "domain.entity", there is no validation that the domain entity combination exists in your Home Assistant instance.

Domain Triggers

Trigger.DomainTriggers is an array of strings containing just the domain. All entities within that domain will cause the trigger to fire.

Periodic Triggers

Trigger.PeriodicTriggers is an array of strings containing the cron expression matching for when the trigger should run. A blank cron expression, "", will launch the trigger at program start. All cron jobs are evaluated every minute so no periodic job can be set to run quicker than 1 minute. Cron expression parsing and matching is provided by gronx.

States

Trigger.States is an array of other entities that you would like the States to be available within the function that is run. All triggers are automatically added to States.

Evaluation

Trigger.Evaluation is a list of strings that are run through expr to evaluate the output. Like with PyScript type is important in the evaluation scripts. Check out expr for more details on casting and converting. You cannot mix types in a single evaluation so state == "on" || state > 10 will always return false due to failure parsing the evaluation. Attributes are available inside the evaluations,color_temp > 100 will work as long as color_temp exists in the attributes of the entity and the data type is a float

TriggerFunc

Trigger.Func is the function to run when the criteria are met. Within the trigger function a *Task is available to give information on the trigger. Killing the TriggerFunc panics to exit. The runner recovers this panic, this also means that if your code panics the whole program will not crash but will continue. Panic will be written to the logs.

Example

This trigger will fire at program startup, every minute and every time input.button.test_button is pressed. It will flip input_boolean.test_toggle, wait 10 seconds and flip it back

&goscript.Trigger{
    Unique:        &goscript.Unique{KillMe: true},
    Triggers:      []string{"input_button.test_button"},
    Periodic:      goscript.Periodics("* * * * *", ""),
    States:        goscript.Entities("input_button.test_button", "input_boolean.test_toggle", "input_number.test_number"),
    Eval:          nil,
    Func: func(tr *goscript.Task) {
        gs.ServiceChan <- services.NewInputBooleanToggle(services.Targets("input_boolean.test_toggle"))
        tr.Sleep(10 * time.Second)
        gs.ServiceChan <- services.NewInputBooleanToggle(services.Targets("input_boolean.test_toggle"))
    },
}

Task

Within each TriggerFunc a task object is available to get information from.

task.Message contains the message that caused the trigger to fire.

task.States contains all the States that were requested to be available by the trigger. Use task.States.Get(string) to retrieve objects. The states held within are pointers to the actual states in GoScript and are updated in real time.

task.Sleep(timeout) will sleep for the specified duration.

task.WaitUntil(entityId, eval, timeout) Waits until the eval is true for the entity or the timeout is reached. Timeout of 0 means no timeout.

task.While(entityId, eval, whileFunc) Runs the whileFunc until the eval is false. task.Sleep should be used within your whileFunc to delay otherwise whileFunc will be run very quickly.

Services

GoScript has a channel to put service calls onto. A set of default services to call is available in the hass-ws package however this is most likely not a complete list of services available in your Home Assistant installation since the service list is dynamic based on integrations installed. Generating your own service definitions is needed to interact properly with all your specific integrations.

From your personal GoScript project directory run these commands to install the service generator and run it. You must have a config.yml with the websocket credentials defined. HassWSService will generate a folder called services and the files within, make sure you do not already have a folder named services in the root of your project.

go install github.com/kjbreil/hass-ws/helpers/HassWSService@latest
go install github.com/campoy/jsonenums@latest
go install golang.org/x/tools/cmd/stringer@latest
HassWSService
go generate ./...
Calling a Service

To call a service you create a service and then add options to the service. Check the source files for available options. They are not yet commented but will have comments in the future. I recommend using Home Assistant Developer Tools -> Services page to get a better understanding of what is needed for each call and to test. There is no reporting of requirements in the service definitions so be warned, some parameters are required and others are not, it is also conditional at times. For example ClimateSetTemperature{} needs TargetTempHigh(float64) and TargetTempLow(float64) when the mode is Heat/Cool however if the mode is Heat or the mode is Cool then Temperature(float64) is needed and both TargetTempHigh(float64) and TargetTempLow(float64) are ignored.

service := services.NewClimateSetTemperature(services.Targets("climate.kitchen")).
		HvacMode(services.HvacModeheat_cool).
		TargetTempHigh(75).
		TargetTempLow(65)
gs.ServiceChan <- service

Logging

goscript.New is passed the config and a logger if you want to use one using the logr interface. There is a default goscript.DefaultLogger() available which will just print the logs to the terminal.

Example

package main

import (
	"fmt"
	"github.com/go-logr/zapr"
	"github.com/kjbreil/goscript"
	"github.com/kjbreil/hass-ws/services"
	"go.uber.org/zap"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	config, err := goscript.ParseConfig("config.yml", nil)
	if err != nil {
		panic(err)
	}
	
	gs, err := goscript.New(config, goscript.DefaultLogger())
	if err != nil {
		panic(err)
	}

	gs.AddTrigger(&goscript.Trigger{
		Unique:        &goscript.Unique{KillMe: true},
		Triggers:      []string{"input_button.test_button"},
		States:        goscript.Entities("input_button.test_button", "input_boolean.test_toggle", "input_number.test_number"),
		Func: func(tr *goscript.Task) {
			gs.ServiceChan <- services.NewInputBooleanToggle(services.Targets("input_boolean.test_toggle"))
			tr.Sleep(10 * time.Second)
			gs.ServiceChan <- services.NewInputBooleanToggle(services.Targets("input_boolean.test_toggle"))
		},
	})
	
	if err != nil {
		panic(err)
	}

	err = gs.Connect()

	if err != nil {
		panic(err)
	}
	done := make(chan os.Signal, 1)
	signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

	gs.GetLogger().Info("Everything is set up")
	<-done

	gs.Close()
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrModuleNotFound = errors.New("module not found")
)

Functions

func DefaultLogger added in v0.2.0

func DefaultLogger() logr.Logger

func Entities

func Entities(entities ...string) []string

Entities is a simple helper function to create a []string. Will most likely be removed in the future.

func Eval

func Eval(exp ...string) []string

func Evaluate

func Evaluate(states States, eval string) bool

func Evaluates

func Evaluates(states States, eval []string) bool

func Periodics

func Periodics(times ...string) []string

Periodics is a helper function to add multiple strings without needing a []string{}

Types

type Command added in v0.2.2

type Command struct {
	// contains filtered or unexported fields
}

func GetStatesCommand added in v0.2.2

func GetStatesCommand(entities ...string) *Command

func ServiceCommand added in v0.2.2

func ServiceCommand(service services.Service) *Command

type CommandType added in v0.2.2

type CommandType int
const (
	CommandTypeService   CommandType = iota
	CommandTypeGetStates CommandType = iota
)

type Config

type Config struct {
	Websocket *ws.Config
	MQTT      *mqtt.Config
	Modules   Modules
}

func ParseConfig

func ParseConfig(filename string, modules Modules) (*Config, error)

func (*Config) GetModule

func (c *Config) GetModule(key string) (interface{}, error)

type Device

type Device struct {
	// contains filtered or unexported fields
}

func (*Device) AddEntities

func (d *Device) AddEntities(ets []entities.Entity) error

func (*Device) GetEntities added in v0.2.1

func (d *Device) GetEntities() []entities.Entity

func (*Device) GetEntity added in v0.2.4

func (d *Device) GetEntity(domainEntity string) entities.Entity

func (*Device) GetUniqueID

func (d *Device) GetUniqueID() string

func (*Device) Update

func (d *Device) Update()

type GoScript

type GoScript struct {
	ServiceChan ServiceChan
	// contains filtered or unexported fields
}

GoScript is the base type for GoScript holding all the state and functionality for interacting with Home Assistant

func New

func New(c *Config, logger logr.Logger) (*GoScript, error)

New creates a new GoScript instance

func (*GoScript) AddDevice

func (gs *GoScript) AddDevice(dev *device.Device) (*Device, error)

func (*GoScript) AddTrigger

func (gs *GoScript) AddTrigger(tr *Trigger)

AddTrigger adds a trigger to the trigger map. There is no validation of a trigger.

func (*GoScript) AddTriggers

func (gs *GoScript) AddTriggers(triggers ...*Trigger)

AddTriggers helper function to add multiple triggers

func (*GoScript) CallService

func (gs *GoScript) CallService(service services.Service)

func (*GoScript) Close

func (gs *GoScript) Close()

Close the connections to WebSocket and MQTT

func (*GoScript) Connect

func (gs *GoScript) Connect() error

Connect connects to the WebSocket server and MQTT server as setup all options need to be passed before firing connect, anything added after will have odd effects

func (*GoScript) GetAreaDomain added in v0.1.2

func (gs *GoScript) GetAreaDomain(area, domain string) []string

func (*GoScript) GetDevice added in v0.2.4

func (gs *GoScript) GetDevice(entity string) (*Device, error)

func (*GoScript) GetDomainStates

func (gs *GoScript) GetDomainStates(domainentity []string) *States

func (*GoScript) GetModule

func (gs *GoScript) GetModule(key string) (interface{}, error)

GetModule returns the config module in interface{} form, must be cast to module type

func (*GoScript) GetState

func (gs *GoScript) GetState(domain, entityid string) *State

func (*GoScript) GetStates

func (gs *GoScript) GetStates(domainentity []string) *States

func (*GoScript) Logger added in v0.2.0

func (gs *GoScript) Logger() logr.Logger

Logger returns the logr to create your own logs

func (*GoScript) RemoveTrigger

func (gs *GoScript) RemoveTrigger(t *Trigger)

RemoveTrigger can be used to remove a trigger while program is running.

func (*GoScript) TaskMQTT added in v0.2.4

func (gs *GoScript) TaskMQTT(tr *Trigger) func(message mqtt.Message, client mqtt.Client)

TaskMQTT wraps a trigger and TaskFunc setting up and passing the task through

type Modules

type Modules map[string]interface{}

type Periodic

type Periodic []string

Periodic is the list of cron expressions to run periodically

type ServiceChan

type ServiceChan chan services.Service

ServiceChan is a channel to send services to be run to

type State

type State struct {
	DomainEntity string
	Domain       string
	Entity       string
	State        string
	Attributes   map[string]interface{}
}

func MessageState

func MessageState(message *model.Message) *State

type States

type States struct {
	// contains filtered or unexported fields
}

func (*States) Combine added in v0.2.2

func (s *States) Combine(cs *States)

Combine takes two States objects and merges them, passed object will overwrite a state in current object

func (*States) Entities

func (s *States) Entities() []string

Entities returns a string of the entities contained in the States object

func (*States) Find added in v0.2.2

func (s *States) Find(entities []string) map[string]*State

Find returns a new map of states of the passed entities

func (*States) FindDomainMap added in v0.2.4

func (s *States) FindDomainMap(keys []string) map[string]*State

FindDomainMap returns a map of the states for the passed domain

func (*States) Get added in v0.2.2

func (s *States) Get(key string) (*State, bool)

Get returns a single state record and a bool if found

func (*States) Insert added in v0.2.3

func (s *States) Insert(ps *State) *State

Insert only adds to the map if something does not exist already. Returns what is in the map whether added or not

func (*States) Map added in v0.2.2

func (s *States) Map() map[string]*State

Map returns a map of all the states

func (*States) Slice added in v0.2.2

func (s *States) Slice() []*State

Slice returns a slice of the states in no particular order

func (*States) SubSet added in v0.2.2

func (s *States) SubSet(entities []string) States

SubSet returns a new States which contains a subset of the current states based on entities passed

func (*States) Upsert added in v0.2.3

func (s *States) Upsert(ps *State) *State

Upsert inserts a new record if one does not exist otherwise updates the data at the pointer so the update propagates

func (*States) Where added in v0.2.4

func (s *States) Where(state string) *States

Where returns a new States object containing all the states that match the passed state. strings.Equalfold is used for the comparison.

type Task

type Task struct {
	Message     *model.Message
	MqttMessage mqtt.Message
	States      States
	ServiceChan ServiceChan
	// contains filtered or unexported fields
}

Task is used within a TriggerFunc to give information about the task. Message is the message that triggered the task. States is all the States defined when the trigger was created. States gets updated each time the methods are run. Task contains 3 methods: Sleep, WaitUntil and While to help processing within a function and handle being able to properly kill the task externally.

func (*Task) Cancelled added in v0.2.3

func (t *Task) Cancelled() bool

func (*Task) Context added in v0.2.3

func (t *Task) Context() context.Context

Context return the current tasks context

func (*Task) Sleep

func (t *Task) Sleep(timeout time.Duration)

Sleep waits for the timeout to occur and panics if the context is cancelled The panic is caught by a recover

func (*Task) UUID added in v0.2.3

func (t *Task) UUID() uuid.UUID

UUID return the current tasks uuid

func (*Task) WaitUntil

func (t *Task) WaitUntil(entityID string, eval []string, timeout time.Duration) bool

WaitUntil waits until the eval equals true. Timeout of 0 means no timeout panics if the context is cancelled

func (*Task) While

func (t *Task) While(entityID string, eval []string, whileFunc WhileFunc)

While runs a function until the eval does not evaluate true panics if the context is cancelled take care to use a sleep within the whileFunc best to keep the function inline so task.Sleep can be used

type TaskFunc added in v0.2.3

type TaskFunc func(t *Task)

TaskFunc is used to include a task object in MQTT command functions.

type Trigger

type Trigger struct {
	Unique        *Unique
	Triggers      []string
	DomainTrigger []string // DomainTrigger, triggers of everything in the domain, also attaches all States for the domain
	Periodic
	States       []string
	DomainStates []string
	Eval         []string

	Func TriggerFunc
	// contains filtered or unexported fields
}

Trigger takes in trigger items, domains or a schedule and runs a function based on any variation of the inputs.

If Unique is not nil then the trigger function will either kill off currently running trigger functions of the same type or kill itself.

Triggers can be on Entity's (full domain.entity format), Domains or on a Periodic schedule. Periodics do not get run through Eval's but it is best to handle all evaluation within the function for Periodics mixed with other trigger types to ensure consistent results.

States is a list of entities to which the state will be available within the task function. All triggers are automatically included in the list. DomainStates allows you to specify a whole domain to be included in the States.

Evaluation is done through a list of strings that are run through github.com/antonmedv/expr to evaluate the output. Like with PyScript type is important in the evaluation scripts. Check out github.com/antonmedv/expr for more details on casting and converting. You cannot mix types in a single evaluation so `state == "on" || state > 10` will always return false due to failure parsing the evaluation. Attributes are available inside the evaluations so `color_temp > 100` will work as long as color_temp exists in the attributes of the entity and the data type is a float

Func is the function to run when the criteria are met. Within the trigger function a *Task is available to give information on the trigger. Killing the triggerfunc panics to exit. The runner recovers this panic, this also means that if your code panics the whole program will not crash but will continue. Panic will be written to the logs.

func (*Trigger) NextTime added in v0.2.0

func (t *Trigger) NextTime(tt time.Time) (*time.Time, error)

type TriggerFunc

type TriggerFunc func(t *Task)

TriggerFunc is the function to run when the criteria are met. Within the trigger function a *Task is available. See Task for more information on what is available in Task.

type Unique

type Unique struct {
	KillMe bool
	Wait   bool
	UUID   *uuid.UUID
	// contains filtered or unexported fields
}

The Unique task will wait until the currently running task finishes to start. To quickly kill tasks that are running it is important to only use the task methods within a trigger function instead of time.Sleep. If Unique.KillMe is set to true the task will not be setup and will not run if another task is running of the same type. Unique.UUID is used to link multiple triggers together. For example two triggers that control the same light and you only want one of the trigger functions to run at a time. Unique.Wait waits until the current task is finished before running, will build up multiple tasks. The queue is based on the UUID so linking the UUID's will make one bit queue.

type WhileFunc

type WhileFunc func()

WhileFunc is the function that runs inside of a task.While on a continuous loop until the while evals false

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL