Python script: shade position automation

Hi everybody, this is my first post so, please, understand my poor knowledge.

I wrote a python script. My automation passes parameters to it. What I wonder is if this script creates several instances upon concurrent calls or if the calls have to be sequential.

Thanks for your help

Going to need a little more detail. Would be best to post your script & how you plan on calling it.

My target is to set and get the position of some shades via a slider input_number + STOP button.
Iā€™d like not to replicate the script for each shade so I tried the following.

Trigger sequence is the following:
input_number + button state --> timers start/stop --> switches on/off

Iā€™m having troubles in getting the timer.remaining attribute because I have to wait for it to appear after timer is paused; Iā€™m waiting for it comparing remaining with duration inside a while cycle but I donā€™t think itā€™s the best solution (suggestions are appreciated :thinking:)

Thanks in advance for your help
Ciao

This is the automation calling the script

- id: '0101'
  alias: Gestione variazione slider tenda grande
  trigger:
  - entity_id: input_number.slider_tenda_grande
    platform: state
  condition: []
  action:
  - data_template:
      timer_salita: timer.salita_tenda_grande
      timer_discesa: timer.discesa_tenda_grande
      da_stato: >
        {{trigger.from_state.state | float}}
      a_stato: >
        {{trigger.to_state.state | float}}
    service: python_script.time_to_integer

and this is the Python script (I cut some calculations and parts not needed for comprehension)

TIM_UP = data.get('timer_salita')
TIM_DN = data.get('timer_discesa')
FROM_STATO = data.get('da_stato')
TO_STATO = data.get('a_stato')

sal_att = hass.states.get(TIM_UP).state
dis_att = hass.states.get(TIM_DN).state

if (sal_att=='active' or dis_att=='active'):
    if sal_att=='active':
        timer_to_stop = TIM_UP
    else:
        timer_to_stop = TIM_DN

    service_data = {'entity_id': timer_to_stop}
    hass.services.call('timer', 'pause', service_data, False)
# here is the while cycle waiting for 'remaining' to be set
    remain_time = hass.states.get(timer_to_stop).attributes['remaining']
    hass.services.call('timer', 'cancel', service_data, False)
else:
    timer_to_stop=''
    rem_time='0:00:00'

actual_position = CALCULATION OF THE POSITION DEPENDING ON FROM/TO/REMAINING

next_duration = CALCULATION OF THE NEW TIMER DURATION UPON FROM/TO/REMAINING

if float(TO_STATO) < posizione:
    timer_to_start = TIM_DN
else:
    timer_to_start = TIM_UP

service_data = {'entity_id': timer_to_start, 'duration': next_duration }
hass.services.call('timer', 'start', service_data, False)

I think you should move this to appdeamon. You know python and this is complicated enough to warrant the move. Your whole automation will be in appdeamon, including your timer and you wonā€™t rely on home assistantā€™s timer entities. Youā€™ll be able to use the time class or the threading.Timer class.

Ok, letā€™s go for another programming tool.
I started from COBOL and LISP a few days ago (decades, not days :scream:) and after passing through FORTRAN, ASSEMBLER, C, Pascal, BASIC, Java, Python and YAML I think I can try this one too,
Thanks again for your help.
Ciao

Itā€™s python. Nothing to learn other than the API and setup object structure, which is super easy.

1 Like

Hi Petro, everything is working perfectly. I only had a few troubles in using run_in with extra args.
I tried the following

        self.shades_dict[self.key][self.callback_idx] = \
            self.run_in(self.switch_off, self.delta_time, chiave=self.key, interruttore=self.switch_to_turn_on)

    def switch_off(self, chiave, interruttore):
        self.turn_off (interruttore)
        self.shades_dict[chiave][self.state_idx] = self.stop_state

but the runtime debug is

File ā€œ/usr/lib/python3.7/site-packages/appdaemon/appdaemon.pyā€, line 586, in worker
funcref(self.sanitize_timer_kwargs(app, args[ā€œkwargsā€]))
TypeError: switch_off() missing 1 required positional argument: ā€˜interruttoreā€™

so I tried the following and this works.
Iā€™m an engineer so I want to understand what Iā€™m doing wrong. Could you help me?

        self.shades_dict[self.key][self.callback_idx] = \
            self.run_in(self.switch_off, self.delta_time, a=[self.key,self.switch_to_turn_on])

    def switch_off(self, *args):
        chiave = args[0]['a'][0]
        interruttore = args[0]['a'][1]

        self.turn_off (interruttore)
        self.shades_dict[chiave][self.state_idx] = self.stop_state

Ciao

so if i remember correctly, run_in only passes all keywargs. So in your case, a dictionary of keword arguments is getting passed to switch_off as the variable name chiave.

chiave will look like this when passed to switch_off

{'chiave':self.key, 'interruttore':self.switch_to_turn_on}

So, keep this line:

self.run_in(self.switch_off, self.delta_time, chiave=self.key, interruttore=self.switch_to_turn_on)

and in switch off, change it to this:

    def switch_off(self, kwargs):
        chiave = kwargs.get('chiave')
        interruttore = kwargs.get('interruttore')
        self.turn_off (interruttore)
        self.shades_dict[chiave][self.state_idx] = self.stop_state

And just to clarify the distinction between args and kwargs:

if I make a function func(*args), anything I pass into said function will be added to a list named args. Each item passed is placed in the order that they are passed (ignore typo in middle):

image

kwargs are the same way, but it makes a dictionary and you HAVE to specify a kwarg.

image

So, the run_in function takes 2 attributes a method to run and the duration to run it in, all other attributes get plopped into kwargs. This is pretty standard practice in python apiā€™s btw.

Ok, Iā€™m so stupid :roll_eyes: Thatā€™s obvious, you have a dictionary so you need to get the item through the key. Thank you very much, i was using Pascal notation for value parameters instead of Python one.

Thanks again for your precious help.
Ciao
Roberto