Pyscript - new integration for easy and powerful Python scripting

@craigb Sorry for missing your reply. Didn’t have alerts on…

Anyhow, apologies for omitting important information. The package is called pyfunctional even though it’s imported as functional.

I’ve installed it to the host machine using requirements.txt file in pyscript folder as follows, but since Jupyter is running on my dev laptop, it’s probably not the host machine library that is used in Jupyter.

requirements.txt

pyfunctional

So, I’m running Jupyter on different host than Home Assistant where pyscript runs. Jupyter host’s manually installed version of pyfunctional is in version 1.4.3. I’ve installed it using pip3. The python interpreter I’m using is Python 3.9.1.

Ok thanks. I can replicate the error and it’s fixed with this commit:

What is the correct way of initializing pyscript.total_pris_for_strom?

Something like
if pyscript.total_pris_for_strom not in pyscript:
pyscript.total_pris_for_strom = 0

??

@time_trigger("startup")
def init_stromkostnad():
    state.persist('pyscript.total_pris_for_strom')

@time_trigger("cron(59 * * * *)")
def akkumulere_stromkostnad():
    p = round(float(sensor.total_electricity_price) * float(sensor.estimated_hourly_consumption) + float(pyscript.total_pris_for_strom), 2)
    pyscript.total_pris_for_strom = p
    sensor.total_pris_for_strom = p
    #log.info(f"Satte pris til {p}")

@time_trigger("cron(0 0 * * *)")
def nullstille_stromkostnad():
    sensor.total_pris_for_strom = 0
    pyscript.total_pris_for_strom = 0

state.persist() takes optional arguments to set a default value and/or default attributes:

state.persist(entity_id, default_value=None, default_attributes=None)

Also, it’s simpler to just call state.persist() as the script is loaded, rather than a bit later on startup:

state.persist('pyscript.total_pris_for_strom', default_value =0)

@time_trigger("cron(59 * * * *)")
def akkumulere_stromkostnad():
    ...

Edit per @stigvi: fixed keyword argument default_value

1 Like
Logger: custom_components.pyscript.file.strompris
Source: custom_components/pyscript/global_ctx.py:337
Integration: Pyscript Python scripting (documentation, issues)
First occurred: 8:55:20 (1 occurrences)
Last logged: 8:55:20

Exception in </config/pyscript/strompris.py> line 1: state.persist('pyscript.total_pris_for_strom', default=0) ^ TypeError: persist() got an unexpected keyword argument 'default'

Edit: Ups, you wrote default_value at the top and default in the example …

I see that pyscript.some_name is easily accessible in lovelace so no need to set a sensor.some_name in addition to pyscript.some_name.

The code I ended up with is shown below and is a bit shorter than the appdaemon code. Mainly because of the timer handling in appdaemon, but also because it is easier to maintain state in pyscript, it seems.

state.persist('pyscript.total_pris_for_strom', default_value=0, default_attributes={"unit_of_measurement":"NOK"})

@time_trigger("cron(59 * * * *)")
def akkumulere_stromkostnad():
    p = round(float(sensor.total_electricity_price) * float(sensor.estimated_hourly_consumption) + float(pyscript.total_pris_for_strom), 2)
    pyscript.total_pris_for_strom = p

@time_trigger("cron(0 0 * * *)")
def nullstille_stromkostnad():
    pyscript.total_pris_for_strom = 0

I am new to pyscript and currently testing it out. I can’t get the script below to trig, but the appdaemon code at the bottom works. Does someone see what is wrong with the pyscript code?

@state_trigger("sensor.passat_status.old == 'charging'")
def statusendring_ferdig():
    easee.set_charger_max_current(charger_id = "EH4", current = "6")
import appdaemon.plugins.hass.hassapi as hass

class Lading(hass.Hass):
    def initialize(self):
        self.listen_state(self.statusendring_ferdig, "sensor.passat_status", old = "charging")

    def statusendring_ferdig(self, entity, attribute, old, new, kwargs):
        self.call_service("easee/set_charger_max_current", charger_id = "EH4", current = "6")

Edit:

This works

@state_trigger("sensor.passat_status")
@state_active("sensor.passat_status.old == 'charging'")

but not this

@state_trigger("sensor.passat_status.old == 'charging'")

This is bug. Good catch! In addition to your workaround, you could mention sensor.passat_status in the trigger expression too, although it’s not intuitive:

@state_trigger("sensor.passat_status.old == 'charging' or sensor.passat_status == 'no such value'")

I just committed a fix, which you can try by using the master version:

Thanks :slight_smile:

Would this also be a valid syntax? Sensor.passat_status alone?

@state_trigger("sensor.passat_status.old == 'charging' and sensor.passat_status)

Not quite, since sensor.passat_status could be an empty string, or “0”, which will evaluate to False. It would work if you know for sure that sensor.passat_status can’t have those values. To avoid the bug you need to mention sensor.passat_status in the expression, which is why I proposed adding an or clause which would always be False.

OK. Nice to know. But I suppose @state_trigger(“sensor.passat_status”) will trig either the sensor is evaluates to true or false?

Just using a state variable (entity) name on its own has a special meaning - trigger on any change. In that case there is no expression to evaluate, so no True/False test.

One more question: Do the app configuration have to be in configuration.yaml or can I, like its done in appdaemon, have several yaml files in the pyscript directory, one yaml for each app?

I’d imagine that you could use one of the !include_dir_merge_list or !include_dir_merged_named construct in the YAML configuration to implement that. Though I don’t know if the automagic reload when changed thing would still work?

I think something like

pyscript:
 hass_is_global: true
 allow_all_imports: true
 apps: !include_dir_merge_named your_app_dir_here/

But if it’s in the configuration.yaml, I have to restart HA when making changes to an app configuration. Or have I misunderstood something?

I’d expect that you could use the “RELOAD PYSCRIPT PYTHON SCRIPTING” thing under “Configuration / Server Controls” to re-parse the YAML.

1 Like

Thanks !! Haven’t noticed that pysrcipt was there :slight_smile:
That is good enough for me, indeed.

Auto-reload detects changes to any yaml files below the pyscript directory (in addition to .py files). You could put all the configuration in pyscript/config.yaml by doing this in the main config file:

pyscript: !include pyscript/config.yaml

Within that file you could also have additional includes for each app, if you prefer to break them out.

When you change any yaml file below the pyscript directory, the entire yaml configuration will be automatically reloaded, and any config changes to each app’s config will then cause that app to be reloaded. Apps whose config is unchanged will not be reloaded.

Hi Craig, after installing the last version of pyscript, and moving all my scripts to the apps folder, reload doesn’t work anymore.

The situation is like this: I have all the pyscript apps running perfectly, then I make a reload (from the integration page or from calling the service with global_ctx: ‘*’ is the same) and every trigger stops working.
Note that I see the reload messages in the log:
2021-02-21 02:05:32 INFO (MainThread) [custom_components.pyscript.global_ctx] Reloaded /config/pyscript/apps/motion_lights.py
but no trigger is working after that, and I have to restart Home Assistant.

This was working perfectly before.
Thanks for your help!

Fabio

Yes, it’s a bug - thanks for reporting it!

I just fixed it with this commit, so you can test the fix by installing the master git version. The fix will be in the next version, which should get released in the next week.