Pyscript - new integration for easy and powerful Python scripting

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

Hi,

has anyone else struggled with getting time_triggers to behave as expected according to your timezone?

I’ve noticed that have I to account for my timezone e.g. when trying to have a nightly trigger at midnight:

@time_trigger("period(midnight, 1 days)")

seems to trigger at midnight UTC time, so I have to specify

@time_trigger("period(midnight + 2 hours, 1 days)")

instead to get midnight in UTC+2.

No big deal, but it means updating the automations every DST change. Any easy fix I can do on my end? I’ve set the timezone correctly in my Home Assistant install.

I need to convert a text string containing json into a json object. The string comes in as an mqtt message and I would call a pyscript in the action section of a mqtt platform trigger.

I cannot find a way to do this. Can it be done? Could you provide a simple example of a @service pyscript which does this?

Alternatively, maybe I can pass a json object (not string) in to pyscript from a yaml template filter “tojson”. If this can be done, can anyone provide a simple example of this?

Thanks for any help or suggestions!

P.S. I completely green to pyscript, python_script and/or AppDaemon. I am trying to find a way to to get data: into a generalized service: call from a yaml automation.

I.E., this doesn’t seem to be possible:

- alias: 'MqttAPI'
  initial_state: true
  mode: parallel
  trigger:
    platform: mqtt
    topic: 'HomeAssistant/API/Service/#'
  action:
    - service: system_log.write
      data:
        level: info
        message: "MqttAPI: service: {{ trigger.topic | replace('HomeAssistant/API/Service/', '') | replace('/', '.') }} message: ({{ trigger.payload }})"
    - service: "{{ trigger.topic | replace('HomeAssistant/API/Service/','') | replace('/', '.') }}"
      data: |
        {{ '{"entity_id": "media_player.d_office_mini", "media_content_id": "/local/audio/hello.mp3", "media_content_type": "music"}' | tojson }}

So I need a way to do this using a pyscript…

The code above is just for testing. The actual service call data would be:

- service: "{{ trigger.topic | replace('HomeAssistant/API/Service/','') | replace('/', '.') }}"
      data: "{{  trigger.payload | tojson }}"

I figured it out:
YAML:

- alias: 'MqttAPI'
  initial_state: true
  mode: parallel
  trigger:
    platform: mqtt
    topic: 'HomeAssistant/API/Service/#'
  action:
    - service: pyscript.mqttapi
      data:
        service: "{{ trigger.topic | replace('HomeAssistant/API/Service/','') | replace('/', '.') }}"
        params: "{{ trigger.payload }}"

pyscript.mqttapi:

@service
def mqttapi(service=None, params={}):
    """Call HA service."""
    log.info(f"Service: {service} Data: {params}")
    if service is not None:
        eval(service+'(**params)')

MQTT publish:

mosquitto_pub -t HomeAssistant/API/Service/media_player/play_media -m '{"entity_id":"media_player.d_office_mini", "media_content_type":"music", "media_content_id":"/local/audio/hello.mp3"}'

Experts, please check and let me know if you see any problem/issues/improvements.

Thanks!

Can anyone help me getting this to work within PyScript? w1ll1am23/pydukeenergy: Access to the undocumented Duke Energy usage API (github.com)

Honestly, I’m not even sure how I would go about getting it to work. What I am wanting to do is pull data from my Duke Energy account and display it on the energy dashboard.

@craigb Regarding your TCP connection above,
How would this be modified to use UDP instead of TCP? I’m looking at online docs now, but am a bit unsure. I have an AV processor that I need to send an XML string to for command control. For example:

<?xml version="1.0" encoding="utf-8"?<emotivaControl><power_on value="0" ack="no" /></emotivaControl>

I’m unsure if I want to pass that entire string as a payload to my “UDP Sender” or if I want to pass the two variables for “command” and “value”; letting this script plug in the values. I’m looking for something simple really. Click a button that sends either variables to the script, or the entire XML string, which gets sent to the AVP.
Thank you in advance!

First of all, pyscript is amazing! Incredibly powerful to be able to express scripts in Python, be able to import modules, and it is very nicely integrated.

Is there, or are there any plans for a gallery of contributed scripts or libraries? Or a list of repos where people have done useful pyscripts?

I’m working on some image processing and if nothing else would be interested in contributing it back.

2 Likes

pyscript 1.3.2 installed yesterday on HA Core (Raspbery PI flash install) 2022.2.9

I have set up requirements.txt is failing on module called json. I have tried with and without a version number.

2022-03-02 08:59:02 INFO (MainThread) [custom_components.pyscript] Installing the following packages: {'json': {'version': '_unpinned_version', 'sources': ['/config/pyscript/requirements.txt'], 'installed_version': None}}

2022-03-02 08:59:03 INFO (SyncWorker_3) [homeassistant.util.package] Attempting install of json

2022-03-02 08:59:20 ERROR (SyncWorker_3) [homeassistant.util.package] Unable to install package json: ERROR: Could not find a version that satisfies the requirement json (from versions: none)

ERROR: No matching distribution found for json

WARNING: You are using pip version 20.2.4; however, version 22.0.3 is available.

@Pacers31Colts18 Did you get anywhere with this?

I want to do the same with my energy provider - I have found working python on GIT but getting it working here is not as easy as I expected.

I’m not sure that pyscript is the best platform to host arbitrary external python packages. It runs inside of Home Assistant and isn’t exactly a the standard python interpreter environment that you’d run at the command line.

For something like this Duke Energy polling application, I’d just build an external script using that library to poll the external provider. Then I’d gather the response and push that data into Home Assistant using MQTT to MQTT sensors. I do this with a number of one-off sort of integration problems (like my Tempest Weatherflow weather station.)

I have done some other work with pyscript, and it’s a really great solution for a bunch of different problems. I think that complicated external libraries might cause you issues if they can’t operate in the async environment that pyscript provides. I could be off-base here; I’ve only done one non-trivial pyscript bit of development which solves a different flavor problem.

Hi,

I’ve been struggling to create a selenium script using pyscript. I have read the docs and tried to implement my understanding of them but haven’t been able to get this working. Is this something someone can please help with?

Main issue:

  • Script not running with below error:
RuntimeError: Blocking calls must be done in the executor or a separate thread; Use `await hass.async_add_executor_job()` at custom_components/pyscript/eval.py, line 1899: return func(*args, **kwargs)

I’ve tried different approaches from the documentation I’ve read and added the following line to my script:

if "/config/pyscript_modules" not in sys.path:
    sys.path.append("/config/pyscript_modules")

I’ve created the pyscript_modules file under my home assistant config folder. This folder contains scraper.py which has the get_info function.

I have a file called web_scraper.py under the pyscripts directory and added this line of code to call my script:

task.executor(scraper.get_info, location)

This then gives me a new error saying:

ModuleNotFoundError: No module named 'scraper'

Although this imports the module fine when I have it under pyscript/modules/

Other issues I’ve faced but managed to progress:

  • Not installing modules listed in requirements.txt file. I’m on docker and in the end I just installed it on the container manually using pip although it would have been nice to get the auto install working from this file. I have allow all imports enabled.
  • ‘AttributeError: module ‘selenium’ has no attribute ‘webdriver’’ when importing selenium module using the config below. I managed to sort this by changing how I import this after reading a post on here. But the below config wasn’t working for me.
	from selenium import webdriver
	from selenium.webdriver.chrome.options import Options

Many thanks in advance


Edit:

Never mind got this working now. Sometimes all it needs is a restart :slight_smile:

Good point. Is there a lightweight API for creating a bridge between pyscript in HA and a service (also probably python) in a container add-on?

Hello there.

I’ve just discovered this cool integration as I want use the plexapi module for some automations.
I’m trying to get the number of unwatched episodes from af specific tv show, but I don’t understand why I get this error:

Exception in <file.main.fetch_unwatched_episode_count> line 8: shows = task.executor(plex.library.section, ‘Tv-serier’) ^ RuntimeError: Blocking calls must be done in the executor or a separate thread

The code so far is:

from plexapi.server import PlexServer

@service
def fetch_unwatched_episode_count():
    baseurl = 'http://192.168.0.12:32400'
    token = 'myToken'
    plex = task.executor(PlexServer, baseurl, token)
    shows = task.executor(plex.library.section, 'Tv-serier')

Can anyone explain me how this is?`

Thanks.

Well I moved all the plexapi calls to a function in another .py-file and imported it as a module, then running the new function with task.executor. Then it works.