Pyscript - new integration for easy and powerful Python scripting

I script that I’m planning to use to control my fan in my argon case.
The setspeed for the fan has still to be added. For now, I’m only sending a notify message (for testing)…

#Fan Control (Argon case)

temp1=40.0
temp2=45.0
temp3=50.0
sh=60 

@time_trigger
@state_trigger(f"float(sensor.cpu_temp)<{temp1} and (sensor.cpu_temp.old==None or float(sensor.cpu_temp.old)>={temp1} )" ,state_check_now=True,state_hold=sh)
@state_trigger(f"float(sensor.cpu_temp)>={temp1} and (sensor.cpu_temp.old==None or float(sensor.cpu_temp.old)<{temp1})", state_check_now=True, state_hold=sh)
@state_trigger(f"float(sensor.cpu_temp)>={temp2} and (sensor.cpu_temp.old==None or float(sensor.cpu_temp.old)<{temp2})", state_check_now=True, state_hold=sh)
@state_trigger(f"float(sensor.cpu_temp)>={temp3} and (sensor.cpu_temp.old==None or float(sensor.cpu_temp.old)<{temp3})",state_check_now=True, state_hold=sh)
def argon_one_check_fan(trigger_type=None, var_name=None, value=None,old_value=None): 

    if trigger_type=="time":
        #script is saved or HA is (re)started. Get current temperature.
        cputemp=float(sensor.cpu_temp)
        notify.persistent_notification(message=f"trigger is time, cputemp={cputemp}" , title="CPUTEMP")   
    elif trigger_type=="state" and var_name=="sensor.cpu_temp": 
        cputemp=float(value)  #"sensor.cpu_temp" in stead of "value" is also possible. I took value, as this is the "real" value that caused the trigger to go off
        notify.persistent_notification(message=f"kwargs trigger_type={trigger_type}, var_name={var_name},value={value}, old_value={old_value}" , title="CPUTEMP") 
    else:
        notify.persistent_notification(message="SHOULD NEVER HAPPEN" , title="CPUTEMP") 
        return
    
    if cputemp > temp3:  
        notify.persistent_notification(message=f"CPUTEMP {cputemp} > {temp3}. Set fan to speed X for this temp" , title="CPUTEMP")  
    elif cputemp > temp2:
        notify.persistent_notification(message=f"CPUTEMP {cputemp} > {temp2}. Set fan to speed Y for this temp" , title="CPUTEMP")
    elif cputemp > temp1:
        notify.persistent_notification(message=f"CPUTEMP {cputemp} > {temp1}. Set fan to speed Z for this temp" , title="CPUTEMP")
    else:
        notify.persistent_notification(message=f"CPUTEMP {cputemp} <= {temp1}. Set fan to speed 0 for this temp" , title="CPUTEMP")

question:
I want to check the percentage of all the batteries.
so, with state.names() I get all entities. But I would like t get all the entites who have a device_class “battery”. I would rather not test on the names of the entities, but get entities with device_class “battery”.
Any idea?

update: I came up with this: (I still need to add the check for the minumum percentage, but, the main thing here is to search for entities who contain battery data)

@service
def check_batteries(min_perc=None):
    sensors=state.names("sensor")
    for sensor in sensors:
        sensor_attr=state.getattr(sensor)
        if "device_class" in sensor_attr and sensor_attr["device_class"]=="battery":
            battery_level=state.get(sensor)
            notify.persistent_notification(message=f'found battery level {sensor} {battery_level}',title="test notify title")

I was wondering, that, if there’s shorter way.

small question about persistence like :
state.persist(‘pyscript.last_light_on’)
What does it do and when?
in your example:


state.persist('pyscript.last_light_on')

@state_trigger('binary_sensor.motion == "on"')
def turn_on_lights():
  light.turn_on('light.overhead')
  pyscript.last_light_on = "light.overhead"

This I can understand. But, can I use pyscript.last_light_on in another pyscript too?
And do I need to add the same state.persist… statement in that source too?

AFAIK, state.persist() is part of the global context. You need to make sure that you add the state.persist() call somewhere that will be called upon every start-up of HA.

https://hacs-pyscript.readthedocs.io/en/stable/reference.html#persistent-state

I’m just a python beginner, so, I maybe misunderstand some things… :frowning:
But, according to the docs, “Each pyscript file that is loaded, and each Jupyter session, runs inside its own global context, which means its global variables and functions are isolated from each other”
This would mean, if I want to use the same variable in multiple pyscripts, I need to add a state.persist() in those multiple sources… If it’s like that, ok, good to know :slight_smile:
The state.persist() that the content of the variable is “saved” somewhere, so, with a hass restart, it can be retrieved. Ok, so, if I don’t need it anymore? (renaming of the variable, or just don’t need it anymore). How does that work? Is pyscript checking, when a variable isn’t persisted anymore, it deletes the “saved content”?
Update: checked: you can ony state.persist() a variable once…
So,
if script_a.py contains a state.persist(var1) , I can use it in script_b.py too with the last value
But, when restarting hass, how can I be sure, script_a.py is executed before script_b.py
And maybe, if the loading of the scripts is alfabeticly (???) , I can create one (of a few scripts) with only the state.persist() statements.

Correct. However, you can get around that by using modules. If you setup your state.persist() method in a module and then import that module in your other code, you can access that variable pretty much anywhere.

As an example:
config/pyscript/modules/persist.py:

state.persist('pyscript.last_motion_sensor')

config/pyscript/motion.py:

import persist

@state_trigger("binary_sensor.motion_sensor == 'on'")
def on_motion(**kwargs):
    pyscript.last_motion_sensor = 'binary_sensor.motion_sensor':
    # Do other cool things here

Just remove state.persist('pyscript.[var_name']') from your code and pyscript will “forget” that variable.

Hi All,

Appreciate any points on the below post:

Very much appreciated!

marina_volgende_ploeg_start_execute='on'

@state_trigger("int(sensor.marina_volgende_ploeg_start)-75<=0 and int(sensor.marina_volgende_ploeg_end)>=0 and marina_volgende_ploeg_start_execute=='on'")

def calendar_VolgendePloegStart(trigger_type=None, var_name=None, value=None, old_value=None): 

    global marina_volgende_ploeg_start_execute   

    

    notify.persistent_notification(message=f"tijd={sensor.marina_volgende_ploeg_start} minuten tot {calendar.ploeg_marina.start_time}, executed={marina_volgende_ploeg_start_execute}",title="calendar_ploegstart") 

    marina_volgende_ploeg_start_execute='off'

the sensor changes every minute…
The problem is, that I get TWO notification messages: one with “executed off” and one with “executed on”
I guess, turning marina_volgende_ploeg_start_execute to ‘off’, triggers first the state trigger.
Goal of that marina_volgende_ploeg_start_execute field: only trigger once…
How can I solve this?

UPDATE: it’s probably not the global variable.
I use 2 sensors in the state trigger. They are changed around the same time.
I guess now, that they both activate the trigger (executing the trigger twice)
the notigication says that the global variable is once “on” and once “off”, but since the trigger is executed twice around the same time, the global variable tested in the trigger isn’t changed yet.
In the trigger function, it is changed…
Solution is add an extra test in the function… An other solution ?
EDIT:
tried to persist de marina_volgende_ploeg_start_execute field in stead of global var.
result is the same: trigger is executed twice…
EDIT:
what seems to work: state.persist('marina_volgende_ploeg_start_execute ‘)
this way, it can be used in state trigger (seems to work as far as I can see)
AND
use STATE_HOLD =5
Since both sensor fields, used in the state trigger are changed around the same time,
the ‘marina_volgende_ploeg_start_execute’ can’t be set to ‘off’ quickly enough (in the background)
and at the moment the two times the trigger is evaluated because of the change of the sensor fields,
the value of ‘marina_volgende_ploeg_start_execute’ is still ‘on’ (persisted, it’s pyscript.marina_volgende_ploeg_start_execute’)
Using the STATE_HOLD=5 seems to do the trick…
@craigb

On a more general note: I’ve noticed that I’ve move to a more hybride solution and use template sensors for a logic like: int(sensor.marina_volgende_ploeg_start)-75<=0 and then have a more clean trigger for that state in my pyscript. It’s a double edged sword because it also gives you more feedback because you can watch that sensor in the GUI.

Also wondering, ploeg as in plow or team?

ploeg= team (work) :slight_smile:

a state trigger can be triggered by changing the state of an entity.
It is also possible to use a state trigger for a change of the state of an attribute of an entity.
For a change in the state of an entity, there’s also the .old value. So, you can e.g. create a trigger for an entity only if the state changes from 'off" to ‘on’. (domain.entity==‘on’ and domain.entity.old=='off)
Is there something to check on for the ‘old’ value for an attribute? I can’t find it…something like domain.entity.attr.old?
FOUND IT: it’s domain.entity.old.attr :slight_smile:

Firstly, @craigb, thanks so much for the integration. It rocks! Secondly, I’ve made a thing. It’s automated ambient lighting control based on lux sensors and a polynomial calculated with the help of SciPy’s curve_fit. This is just a first draft but it seems to work pretty well. There’s a couple of things I would like, however.

Is it possible to either:
a) make lovelace see the state of the entity as a binary, therefore making icons respect the state_color: true setting, or
b) have pyscript create a switch entity I can use to track the state of the pyscript entity and turn it on/off

Either of these a possibility? Or maybe another workaround?

@craigb This is absolutely incredible. I honestly can’t imagine working with HA without it. It must have increased my productivity for writing automations ten fold. I could (hopefully) see similar syntax being incorporated directly into HA, as even being an experience dev, I find writing automations in yaml tedious/difficult.

I likely have tons of questions, but is there anyway to represent HA’s (From/To) trigger? For instance only fire if media_player goes from stopped to play.

Also, is there anyway to substitute a global variable/constant to represent an entity for an entire file? For instance, set myTV=media_player.tv This would allow for easier swapping of entities without changing logic in the source. I see there are options for taking yaml entity parameters but I have not gotten that far :slight_smile:

Thanks again for such a fantastic addition to Home Assistant.

For the constants i solved it by having a Constants.py in pyscript/modules.
example content of Constants.py:

ENTITY_LAMP_WC = "light.wc_wclamp"

in other files you can just import and use the contants you defined.

import Constants
light.turn_on(entity_id=Constants.ENTITY_LAMP_WC , brightness=50)

The other part of your question i solved by just adding an if statement to the function like this:

@state_trigger("person.<name>")
def LocationChanged(old_value=None, value=None):
    log.info(f"Entry old_value='{old_value}' value='{value}'")
    if old_value == <old_value>:
        <your_logic>
1 Like

Thanks so much! Your snippet let me explore where those vars are coming from in the docs. I settled on grabbing them from the kwargs dict (so I have access to all) So taking a cue from your example:

@state_trigger("person.<name>")
def LocationChanged(**kwargs):
    log.info(f"Here is the whole kwargs dict {kwargs}")
    log.info(f"Entry old_value={kwargs['old_value']} value={kwargs['value']}")

As far as your constant example, I don’t think it is going to work for me as I use the dot syntax. So for your example:

light.wc_wclamp.turn_on(brightness=50)

This below will not work. as it is now a string object AttributeError: ‘str’ object has no attribute ‘turn_on’

ENTITY_LAMP_WC.turn_on(brightness=50)

Thanks so much for getting me on the right track.

I’m having issues getting my script to trigger on state, when changed to @service and firing from developer tools it work fine, any ideas?

@state_trigger("sensor.terrarium_temp")
def turn_on_terrarium_heater():
    temp = float(state.get("sensor.terrarium_temp"))
    if temp <= 72:
        task.unique("turn_on_terrarium_heater")
        log.info(f"triggered; turning on terrarium heater")
        switch.turn_on(entity_id="switch.terrarium_heater")
    else:
        switch.turn_off(entity_id="switch.terrarium_heater")

if __name__ == "__main__":
    turn_on_terrarium_heater()

thanks for explanation. still not clear where to add the module? I need to add “mysql-connector”

I don’t see anything wrong by just looking at the code

try to minimize and find the error

@state_trigger("sensor.terrarium_temp")
def turn_on_terrarium_heater():
    log.info(f"Entry")

wait for the sensorvalue to change and check the log for the entry. then build up to your desired behaviour

Hi!
I’m stuck at using pyscript with command line sensor.
The overall goal is to login at remote resourse with POST, get the cookie, then use the cookie to GET required data, then logout and pass this data to this sensor:

sensor:
  - platform: command_line
    name: My_sensor
    command: "python3 /config/pyscript/test.py"

The script is the following:

log.warning("start script")
import requests
s = requests.Session() 
task.executor(s.post, 'https://<MY_URL>/api/login', data='{"username": "MYUSER", "password": "MYPASS"}', verify=False, timeout=10)
response = task.executor(s.get, 'https://<MY_URL>/stat/sysinfo', timeout=10,  verify=False)
task.executor(s.post, 'https://<MY_URL>/api/logout', timeout=10,  verify=False)
log.warning(response)
print(response.json()["data"][0]["version"])

During initial compilation with debug messages it works and I get the required data, but when this script is called from sensor - the goes an error:

2021-10-22 11:08:15 WARNING (MainThread) [custom_components.pyscript.file.test] start script
2021-10-22 11:08:15 WARNING (MainThread) [custom_components.pyscript.file.test] <Response [200]>
2021-10-22 11:08:15 INFO (MainThread) [custom_components.pyscript.global_ctx] Reloaded /config/pyscript/test.py
2021-10-22 11:08:17 ERROR (SyncWorker_5) [homeassistant.components.command_line] Command failed: python3 /config/pyscript/test.py

After some debugging I found out that log.warning and task.executor both cause “Command failed” error, although log.warning obviously works during reloading.

So, for example, this script works fine and sensor gets “Success” value:

import requests
s = requests.Session() 
print("Success")

So python works, import also works, but pyscript extensions are not compiled when called from command line sensor.
That should be something simple, please help.

Hi,
I’m trying to write a script to call a local API but it wont work via Pythian Script, so am trying Pyscript.

This is my code;

@service
def bond_action(device="5a5af59a", action="Preset"):
    import requests
    headers = {"BOND-token":"xxxxxxxxx"}
    url = "http://xxx.xxx.xxx./v2/devices/{device}/actions/{action}"
    log.error(f"Bond action:{action}, device:{device}")
    response = requests.put(url, headers=headers)

But when I run it, I get exception error in line 15 (the requests line). I’m getting similar with python script too.
I have been assuming I need to import requests too. Not sure about that. (Am a python newbie)
Any thoughts?
Thx
JP

As an update, I discovered that the issue was the headers option in requests.put. For some reason it didn’t like it. The Bond API also supports another way of getting the token through using data payload, so I did that instead and it now works fine.
Here’s the final code.

@service
def bond_action(device="5a5af59a", action="Preset"):
    import requests
    data = {"_token": "xxxxxxxxxxxxxx"}
    url = "http://xxx.xxx.xxx/v2/devices/" + device + "/actions/" + action
    log.error(f"Bond action:{action}, device:{device}, URL: {url}")
    response = task.executor(requests.put, url, json=data)
1 Like