Pyscript - new integration for easy and powerful Python scripting

Why do you need IO? It’s hands down the worse way to communicate between 2… anything with computers.

Why do you need basic file I/O?

Forget it…

Uh yeah, you can store everything in memory, there’s no reason to use IO unless you want something to persist through restarts. And at that point you can probably use pickle or another library to handle that instead of open…

The reason the standard open/read/write/print functions are not supported by pyscript is that they can block (eg, on a file system mount that is stale or slow), which stalls the main event loop. You can easily use aiofiles for async file I/O. The second reason is that if people share and run pyscript code without inspecting it, from a security point of view it’s better to have it somewhat sandboxed to avoid reading / writing files anywhere.

I say “somewhat sandboxed” because it’s quite possible that the allowed imports and language features could allow file access (eg, json.dump will write to a file handle if you can create one). Hopefully we don’t have malicious pyscript code being shared, although it would be easy to inspect.

1 Like

I have for many years a stand alone zone-heating system with 8 thermostats a gateway with controls 8 motors to switch on and off the heating to those 8 rooms. This system has the option to read and write setting via a tcp-socket. I have the script itself up and running. But in the next step I want to run the script from HA and send the roomnumber to the script and have the values which are received stored in a device (thermostat) direct or via MQTT ???

Here is where I get stuck. How to send the room number to the script and how to transfer the values back to HA.

This is the pyscript:
(ps. the printing is to see what the values are and as you might see… I’m not a code-er)

@service
def tcp_serivce():
“”“testing using pyscript.”“”
log.info(f"lets test ")

# echo-client.py

mport socket
import binascii
import time
import struct

# vaste variabele

username = “00000000000000000000”
password = “00000000000000000000”
type =“01”
cmd = “00”
ack =“00”
len = “19”
room =“8” <======================== this needs to be a variable
null =“00”
room_tmp = “”
room_set = “”
room_mode = “”
room_state = “”
flags = “”
effective =“”
online = “”
valve = “”
zero =“0”

HOST = “192.168.2.250” # The server’s hostname or IP address
PORT = 9800 # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b"“)
data1 = s.recv(1024) #dummy read to delete welcome message
time.sleep(1)
#s.send(b”\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x19\x01") # all data room 1
#s.send(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x1A\x01\x00") #temperature room 1
data_package = (username + password + type + cmd + ack + len + zero + room + null)
data_package = binascii.unhexlify(data_package)
s.send(data_package) #temperature room 1
data = s.recv(1024)
data = data[20:] # remove username and password
type = data[0:1]
cmd = data[1:2]
ack = data[2:3]
len = data[3:4]
room = int(binascii.hexlify(data[4:5]), base = 16)
room_tmp = (((int((binascii.hexlify(data[5:6])), base=16)) / 2) - 20)
room_set = (((int((binascii.hexlify(data[6:7])), base=16)) / 2) - 20)
room_mode = int(binascii.hexlify(data[7:8]), base = 16)
room_state = int(binascii.hexlify(data[8:9]), base = 16)
ffu = data[9:12]
flags = int(binascii.hexlify(data[12:13]), base = 16)
binary = format(flags, ‘b’)
effective = (flags >> 0 & 1) #bit 0
online = (flags >> 1 & 1) #bit 1
valve = (flags >> 2 & 1) #bit 2
data_hex = binascii.hexlify(data)

print (data_package)
print(f"Received:“, data1)
print(f"type:”, type)
print(f"cmd:“, cmd)
print(f"ack:”, ack)
print(f"len:“, len)
print(f"room:”, room)
print(f"room temperatuur:“, room_tmp)
print(f"room setpoint:”, room_set)
print(f"room_mode:“, room_mode)
print(f"room_state:”, room_state)
print(f"flags:“, flags)
print(f"effective:”, effective)
print(f"online:“, online)
print(f"valve:”, valve)
print(binary)

Anybody who can help me in the right direction…

Please help me how to send roomnumber to script and get room temperature back in a HA thermostat or any other entity…

You can send data to a Pyscript service through the service arguments:

@service
def tcp_serivce(room: int):

then call it from Home Assistant:

service: pyscript.tcp_serivce
data:
  room: 8

You can “send data back to Home Assistant” from Pyscript by just assigning the value to the entity where you want to store the value:

input_number.thermostat_room_8_temperature = room_temp
# or dynamically:
input_number.set_value(entity_id=f"input_number.thermostat_room_{room}_temperature", value = room_temp)

btw: You’ll need an asynchronous sockets handler because synchronous calls like this are not allowed in Pyscript and should end with an error.

@Dingle,

Thanks for your help. I got it to work for one room.

This part did not work for me but I found out that when I use

climate.deel.current_temperature = room_tmp
climate.deel.temperature = room_set

I can update both “current” and “setpoint”.
I don’t see any errors so I’m not sure what you mend with the asynchronous socket.
I will rewrite the script with a loop so it will readout all the 8 thermostats and have and automation so it runs every 5 minutes or so to update the values.

After this I will have to write a second script which will write a new setpoint when I change this in the UI… so enough testing to do…

@dingle I have to come back on my statement that it works…

I use:

 if room == 1: 
   climate.deel.current_temperature = room_tmp_r
   climate.deel.temperature = room_set_r

in my script to write the values in the thermostat (see part of configuration.yaml):

climate:

  • platform: generic_thermostat
    name: deel
    precision: 0.5
    initial_hvac_mode: “off”
    heater: switch.deel_heater
    target_sensor: sensor.deel_temperature

The strange part is when I have the pyscript open in Visual Studio Code and press “save” the values nicely update in the UI. But when I run the script as a service (by hand or as an automation (time plattern trigger)) the values don’t update.

I don’t have a clue anymore…

I’m trying to use pandas in Pyscript. It was all fine until I tried a simple mask and now I’m tearing my hair out!!

In VScode:

    df = pd.DataFrame(np.random.randn(5,3), columns=list('ABC'))

    >>>  df

          A         B         C
0  0.200856 -0.525079 -1.935055
1 -1.577698 -1.805934  0.181529
2  1.547412  0.964483 -0.490851
3  0.106544 -1.113398 -0.535679
4  0.757220 -0.598711 -0.079042

   >>>  df.loc[(df.C > 0.25) | (df.C < -0.25)]

          A         B         C
0  0.200856 -0.525079 -1.935055
2  1.547412  0.964483 -0.490851
3  0.106544 -1.113398 -0.535679

In Pyscript:

    log.info(df)
    log.info(df.loc[(df.C > 0.25) | (df.C < -0.25)])

In the log:

2023-03-01 13:46:50.188 INFO (MainThread) [...]           A         B         C
0  1.764052  0.400157  0.978738
1  2.240893  1.867558 -0.977278
2  0.950088 -0.151357 -0.103219
3  0.410599  0.144044  1.454274
4  0.761038  0.121675  0.443863
2023-03-01 13:46:50.204 ERROR (MainThread) [...] Exception in <apps.test.df_test> line 268:
        log.info(df.loc[(df.C > 0.25) | (df.C < -0.25)])
                                ^
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Sounds like you’re almost there. I can only recommend to use log.info in your pyscript at every step to monitor critical steps. I’ve got a permanent 'tail -f /config/home-assistant.log | grep pyscript’ command running in my terminal when I’m testing pyscripts. Your home assistant system log in the UI will also report errors with details.

@Dingle
I did find the problem, I needed two extra spacings and therefor the script was not in the @service part and did run when I saved but not when I ran the service itself…

there is only one thing I need to tackle now.

Within the script I use the variable “room” to loop through the 8 rooms.
In the configuration.yaml I have 8 generic_thermostats named “therm1” to “therm8”.

If I use “climate.therm1.temperature = room_tmp_r” to update the values it works fine.

to make the script easier I would like to use variable “room” (which is an integer) in this scriptline but I don’t know how.

I tried climate.therm{room}.temperature = roomt_temp_r
but also {{…}} and (…) but I can get it to work.
Can anybod help me here.

You can use the state variable for dynamic entities:

state.setattr(f"climate.therm{room}.temperature", room_tmp_r)

Thanks!!! it works!!!

1 Like

I have an issue where an function doesn’t trigger, and for the life of me I can’t find out why.

The function with triggers:

@state_trigger("binary_sensor.platetopp == 'on' or binary_sensor.stekeovn == 'on'")
@state_trigger("binary_sensor.platetopp == 'off' or binary_sensor.stekeovn == 'off'", state_hold=900)
def manage_kitchen_fan(value=None, var_name=None):
    if value == 'on':
        switch.turn_on(entity_id='switch.kjokken_vifte')

        if var_name == 'binary_sensor.platetopp':
            light.turn_on(entity_id="light.kjokken_viftelys", brightness=255)
    else:
        switch.turn_off(entity_id='switch.kjokken_vifte')

        if not light.taklys_kjokken == 'on':
            light.turn_off(entity_id="light.kjokken_viftelys")

And history (at 09:04 I shut off the switch kjokken_vifte and light kjokken_viftelys manually.

By my account it should have trigger at 08:54 but as you can see that never happend?

You can only have one (1) @state_trigger (that’s why the first has a “or” statement").

I would split them up in 2 functions, one triggers when on and the other when off for 900s. That way you can ditch the if statement.

I’m not sure I fully understand what you are trying to do. However, given your example, the 2nd @state_trigger indeed will not trigger since binary_sensor.stekeovn == 'off' is always True, and therefore the expression binary_sensor.platetopp == 'off' or binary_sensor.stekeovn == 'off' will also be always True. Since that expression never transitions from False to True, the 2nd trigger does not occur.

Do you simply need to use and instead of or? So the second trigger will be:

state_trigger("binary_sensor.platetopp == 'off' and binary_sensor.stekeovn == 'off'", state_hold=900)

The expression will be False when either are on, and will become True when both are off. Then the trigger will happen 900 sec later, provided they both stay off.

No, you can have multiple @state_triggers on a single function.

Also, using or to combine multiple expressions will not behave the same as separate triggers:

@state_trigger("EXPR1 or EXPR2")

is not the same as:

@state_trigger("EXPR1")
@state_trigger("EXPR2")

For example, consider the case where EXPR1 is True. In the first case the trigger will never happen, no matter how EXPR2 changes, since the overall expression is always True. In the 2nd case. EXPR2 will cause a trigger whenever it changes from False to True.

2 Likes

I’m trying integrate my domotic system with HA.
Integration doesn’t exist yet.
I’m new to python and even more with HA coding

But I’ve manage to write in virtual code studio a python that open the connection and can send a command.
i’d like to execute this via ha and tought pyscript could do it.

Am I on the correct path ?
Any suggestion is welcome :slight_smile:

@state_trigger("EXPR1 or EXPR2")

is not the same as:

@state_trigger("EXPR1")
@state_trigger("EXPR2")

I am aware you are the author of Pyscript, but is that correct? Your docs say:

Whenever any of the state variables or attribute values mentioned in the expression change (or specified via the watch argument), the expression is evaluated and the trigger occurs if it evaluates to True (or non-zero).
(Reference — hacs-pyscript 1.5.0 documentation)

I understand that such that actually, @state_trigger("EXPR1 or EXPR2") should trigger whenver EXPR2 changes, while EXPR1 is constantly True.