Pyscript - new integration for easy and powerful Python scripting

Don’t move it, leave it in custom_components, because this is the intergration and will be overwritten on updates.
Just create a pyscript folder in your config directory, like CM000n said.
For you, it should be /etc/homeassistant/config/pyscript

1 Like

Here is my pyscript to switch of a light when 2 PIR sensors are both off for a certain time. I did this in yaml before, but it did not work well. Now I have a working and compact solution

sensor_hallway = "binary_sensor.pir_1"
sensor_basement = "binary_sensor.pir_2"

light_basement = "light.bulb_1"

hold_time = 5*60

@state_trigger(f"{sensor_hallway} == 'off' and {sensor_basement} == 'off'", state_hold=hold_time)
def motion_light_basement_off():
    """ turn off basement after hold_time is true for both sensors"""
    log.info(f"triggered; turning off basement light(s)")
    light.turn_off(entity_id=light_basement)

Interested in this to augment the Unifi Networks integration.

I added their python SDK “unificontrol” to the requirements.txt, and set up a simple service example that attaches to my Unifi Cloud Key to block a MAC address from the network.

When that specific call was made, my system logs coughed an async issue and asked that I use await async…

Are there examples of how that process works? (my script)

import unificontrol

@service
def block_mac(action=None, mac=None):
    client = unificontrol.UnifiClient(host="10.0.0.5",username='<snip>', password='<snip>')
    log.info(f"block_mac: got action {action} mac {mac}")
    if action == "block" and mac is not None:
        result = await hass.async_add_executor_job(client.block_client(mac))
    elif action == "unblock" and mac is not None:
        result = await hass.async_add_executor_job(client.unblock_client(mac))
    client.logout()

The error in the log. Granted I tried first with just the strait call not wrapped in the await command my previous error suggested.


This error originated from a custom integration.

Logger: custom_components.pyscript.file.unifi.block_mac
Source: custom_components/pyscript/eval.py:493
Integration: Pyscript Python scripting (documentation, issues)
First occurred: 11:43:26 AM (1 occurrences)
Last logged: 11:43:26 AM

Exception in <file.unifi.block_mac> line 8: result = await hass.async_add_executor_job(client.block_client(mac)) ^ 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 1941: return func(*args, **kwargs)

Hi,

Is it possible with pyscript to react to a long press on a light switch (e.g. Shelly)?
If yes, how can it be done?
@state_trigger only triggers the normal press.
@event_trigger doesn’t seem to be the right thing either.
With HA autmatization it works. Would like to implement it with pyscript though.

Thanks a lot!

Hello all, my HACS pyscripts to communicate with RS485 / modulus stopped working after 1.5 yrs of serving me very well. This happened after 2023.9 home assistant upgrade, also upgraded HACS to 1.33.0

Before I bore y’all with the details, Where would be the right community / forum to try and find some guidance?

@state_trigger(buttons)
def state_triggered(value=None):
    if value == "single":
        log.info("Single button press")

Should work with double and long also…

Unfortunatly not.
I tried this:

@state_trigger("switch.my_switch")
def state_triggered(value=None):
    log.info("value: " + value)

I only get trigger for short press:

2023-10-15 13:02:38.437 INFO (MainThread) [custom_components.pyscript.file.cover.state_triggered] value: on
2023-10-15 13:02:39.365 INFO (MainThread) [custom_components.pyscript.file.cover.state_triggered] value: off

As far as I know switches only have the states on and off. the button I use are zigbee push buttons and they expose as sensor.

would it be possible to use python-synology · PyPI in pyscript

I tried it with following code

import synology_dsm

from synology_dsm import SynologyDSM

@service
def shutdown():
    api = SynologyDSM(dsm_ip="xxx.xxx.xxx.22", dsm_port="5000", username="user", password="password", session="false")
    system = api.system
    system.shutdown()

but I get following error

Exception in <file.ds114_shutdown.shutdown> line 12: system.shutdown() ^ AttributeError: ‘str’ object has no attribute ‘get’

Please have a look at Importing

Hi guys, my a little heads-up about using pyscript in the docker version of HA.
I use my simple pyscript with selenium dependency defined in the requirements.txt and I observe strange behaviour about importing some classes and functions from the whole selenium library.
Sometimes I get object has no attribute ... for some specific import but the other stuff from the same library works fine. I figured out it’s needed to stop and rerun the whole docker, not just restart the HA instance in the docker.
Now I moved the piece of code with selenium import to pyscript_modules and the behaviour seems to be better.

Does anyone have a working example using !secret in the config? The help documentation refers to it working, but doesn’t show how the actual script would use it. I have the following in configuration.yaml:

pyscript:
  allow_all_imports: true
  apps:
    get_linked_image:
      - service_name: get_linked_image
        username: !secret link_user
        password: !secret link_pass

Those secrets exist and I’m using them in another section of my config. Then in the pyscript directory, I have a file named get_linked_image.py, which I dumbed down to see why it wasn’t working:

@service
def get_linked_image(url=None):
    log.error("in a script! (" + username + ")")

When I run this from the services tab, I see this in my log:

This error originated from a custom integration.

Logger: custom_components.pyscript.file.get_linked_image.get_linked_image
Source: custom_components/pyscript/eval.py:493
Integration: Pyscript Python scripting (documentation, issues)
First occurred: 3:31:44 PM (1 occurrences)
Last logged: 3:31:44 PM

Exception in <file.get_linked_image.get_linked_image > line 3: log.error("in a script! (" + username + ")") ^ NameError: name 'username' is not defined

How do I refer to these variables within my script, if not like this? I rebooted HA after adding them to the configuration.yaml.

Thanks!

1 Like

Hi

Why does this not work? The x != 5 or x !=6 check?

x = 6

if x == 0 or x == 1 or x == 3 or x == 4 :
    print("Should not print, and does not")

if x != 5 or x != 6:
   print("Should not print, but does")

x != 5 is True so the expression x != 5 or x != 6 evaluates to True. Because of short circuiting, x != 6 is not even evaluated, since its value doesn’t matter when the first expression is already True.

Ah, ok, so this fails just because today is day 6, and that is never checked for?

ChatGPT tried to tell me the same thing, i understood you better :stuck_out_tongue:

Thank you

Hi all, trying to make working a pyscript, but cannot understand why is not working. When i try to run service, i get following error:

Blockquote
Exception in <file.telegram_mark_msg_read.telegram_mark_msg_read> line 23: dialogs = await client.get_dialogs() ^ ConnectionError: Cannot send requests while disconnected

from telethon.sync import TelegramClient
from dotenv import dotenv_values
from telethon.sessions import StringSession


# Variabili
api_id="xxxxxxxxxx"
api_hash="xxxxxxxxxxxx
session_token="xxxxxxxxxxx"

def create_telegram_client(session_token, api_id, api_hash):
    client = TelegramClient(StringSession(session_token), api_id, api_hash)
    client.flood_sleep_threshold = 0
    client.start()
    return client



async def mark_all_as_read():
    dialogs = await client.get_dialogs()
    
    for dialog in dialogs:
        try:
            #print(f"NAME:{dialog.name}, USERNAME: {dialog.entity.id}")
            await client.send_read_acknowledge(dialog.entity.id)
            print(f"Marked as read: {dialog.name}")
        except Exception as e:
            print(f"Error marking as read in {dialog.name}: {e}")

    await client.disconnect()
    print("Client disconnected")

@service
def telegram_mark_msg_read():
    create_telegram_client(session_token, api_id, api_hash)
    client.loop.run_until_complete(mark_all_as_read())


Any idea?
regards

Does anyone know if it is possible to evaluate a template is pyscript? Either with pyscript, a service call or an another custom integration.

I have a configuration for an app written in pyscript like shown below. Instead of defining state and sensor, I would rather use a template so that I can use ‘{{ states(‘sensor.effekt’) > ‘300’ }}’ in the configuration of the app and evaluate this template in code. It will be more flexible that way and I don’t have to define several helper binary sensors.

      scenes:
          scene1:
            name: scene.stue_stovsuge
            sensor: binary_sensor.stovsuge
            state: "on"
            TransitionTo: 10
            TransitionFrom: 10
            TransitionMotion: 10
            delay: 30
            lightlevel: 5000
          scene2:
            name: scene.stue_middag
            sensor: input_boolean.middagslys
            state: "on"
            TransitionTo: 2
            TransitionFrom: 10

Can you share the pyscript code as well? It isn’t clear to me where you’d want to have the template evaluated right now.
At first glance, you can always just get the state from sensor.effekt in pyscript and check if it’s larger than 300 (you may need to convert the state from a string to a float/integer though).

No, I don’ want to use sensor.effekt in code. Then it is hardcoded and doesn’t work well in an app where it should be configurable.

As the code works now, I can only test for identities that has two states, normally on and off. I wan’t to extend that with templates, but doubt it is possible. But wanted to hear if someone know of a solution.

The trigger for the sensor is set up in this code section:

        if "scenes" in self.cfg:
            for tr in self.cfg['scenes']:
                if "sensor" in self.cfg['scenes'][tr]:
                    @state_trigger(f"{self.cfg['scenes'][tr]['sensor']} != 'unavailable'", state_hold = 1)
                    def statechanged_trig(var_name=None):
                        task.unique(f"scenestyringstatechg_{self.cfg['id']}_{var_name}")
                        self.set_sceneparams()
                        self.statechanged()
                    registered_triggers.append(statechanged_trig)

and state is checked in this section:

                if ("sensor" in self.cfg['scenes'][tr]
                        and "state" in self.cfg['scenes'][tr] 
                        and state.get(f"{self.cfg['scenes'][tr]['sensor']}") == self.cfg['scenes'][tr]['state']):
                    
                    if "name" in self.cfg['scenes'][tr]:
                        s = self.cfg['scenes'][tr]['name']

My best guess in that case would be using an eval() python function, and I guess putting in the template in a python format instead of jinja2? I.e. sensor.effekt > 300 in the configuration. However I’m not sure still if that is fully the goal you want to reach.

I also don’t have experience using eval(), so I can’t promise it well work. Got the answer from here: