Skip to content

Commit 420fa88

Browse files
committed
merge develop into master
2 parents 4aa7b97 + 554e942 commit 420fa88

12 files changed

Lines changed: 167 additions & 314 deletions

.github/workflows/unittests.yml

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
name: "Unittests Plugins"
2-
#on: [workflow_dispatch, push]
32
on:
43
workflow_dispatch:
54
push:
@@ -11,11 +10,11 @@ on:
1110
- 'develop'
1211
jobs:
1312
build:
14-
runs-on: ubuntu-24.04 #latest
13+
runs-on: ubuntu-24.04
1514
strategy:
1615
fail-fast: false
1716
matrix:
18-
python-version: [ '3.10', '3.11', '3.12', '3.13' ]
17+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
1918
name: Python ${{ matrix.python-version }}
2019
steps:
2120
- name: Setup OS (Ubuntu)
@@ -25,62 +24,82 @@ jobs:
2524
sudo apt-get install librrd-dev libpython3-dev
2625
sudo apt-get install gcc --only-upgrade
2726
sudo apt-get install libglib2.0
27+
sudo apt-get install libxml2-dev libxslt1-dev
2828
29+
# For push events github.ref_name is the branch name.
30+
# For PR events github.head_ref is the source branch name.
31+
# The previous GITHUB_REF strip produced 'refs/pull/N/merge' for PRs.
2932
- name: Get branch name
30-
run: echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >>$GITHUB_OUTPUT
3133
id: extract_branch
34+
run: |
35+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
36+
echo "branch=${{ github.head_ref }}" >>$GITHUB_OUTPUT
37+
else
38+
echo "branch=${{ github.ref_name }}" >>$GITHUB_OUTPUT
39+
fi
3240
3341
- name: Workflow Information
3442
run: |
35-
echo github.event_name '${{ github.event_name }}'
36-
echo github.workflow '${{ github.workflow }}'
37-
echo github.action_repository '${{ github.action_repository }}'
38-
echo github.actor '${{ github.actor }}'
39-
echo github.ref_name '${{ github.ref_name }}'
40-
echo github.ref '${{ github.ref }}'
41-
echo github.base_ref '${{ github.base_ref }}'
42-
echo github.head_ref '${{ github.head_ref }}'
43-
echo github.pull_request.base.ref '${{ github.pull_request.base.ref }}'
44-
echo steps.extract_branch.outputs.branch '${{ steps.extract_branch.outputs.branch }}'
43+
echo "event: ${{ github.event_name }}"
44+
echo "actor: ${{ github.actor }}"
45+
echo "ref: ${{ github.ref_name }}"
46+
echo "branch: ${{ steps.extract_branch.outputs.branch }}"
47+
echo "base: ${{ github.base_ref }}"
48+
echo "head: ${{ github.head_ref }}"
4549
4650
- name: Check if branch '${{ steps.extract_branch.outputs.branch }}' exists in smarthomeNG/smarthome
47-
run: echo "code=$(git ls-remote --exit-code --heads https://github.com/smarthomeNG/smarthome ${{ steps.extract_branch.outputs.branch }} > /dev/null; echo $? )" >>$GITHUB_OUTPUT
4851
id: shng_branch_check
52+
run: echo "code=$(git ls-remote --exit-code --heads https://github.com/smarthomeNG/smarthome ${{ steps.extract_branch.outputs.branch }} > /dev/null; echo $? )" >>$GITHUB_OUTPUT
4953

50-
- name: Checkout core from branch '${{ steps.extract_branch.outputs.branch }}' (for push on known smarthomeNG/smarthome branch)
54+
- name: Checkout core from branch '${{ steps.extract_branch.outputs.branch }}' (push on known smarthomeNG/smarthome branch)
5155
if: github.event_name != 'pull_request' && steps.shng_branch_check.outputs.code == '0'
52-
uses: actions/checkout@v3
56+
uses: actions/checkout@v4
5357
with:
5458
repository: smarthomeNG/smarthome
5559
ref: ${{ steps.extract_branch.outputs.branch }}
5660

57-
- name: Checkout core from branch 'develop' (for pull request or push on unknown smarthomeNG/smarthome branch)
61+
- name: Checkout core from branch 'develop' (pull request or push on unknown smarthomeNG/smarthome branch)
5862
if: github.event_name == 'pull_request' || steps.shng_branch_check.outputs.code == '2'
59-
uses: actions/checkout@v3
63+
uses: actions/checkout@v4
6064
with:
6165
repository: smarthomeNG/smarthome
6266
ref: develop
6367

64-
- name: Checkout plugins from branch '${{steps.extract_branch.outputs.branch}}'
65-
uses: actions/checkout@v3
68+
# For push: check out the pushed branch of the plugins repo.
69+
# For PR: no ref specified — actions/checkout defaults to refs/pull/N/merge
70+
# (the merged result of the PR), which is what we want to test.
71+
- name: Checkout plugins (push)
72+
if: github.event_name != 'pull_request'
73+
uses: actions/checkout@v4
6674
with:
6775
repository: ${{ github.repository_owner }}/plugins
68-
ref: ${{steps.extract_branch.outputs.branch}}
76+
ref: ${{ steps.extract_branch.outputs.branch }}
6977
path: plugins
7078

79+
- name: Checkout plugins (pull request — tests the merge result)
80+
if: github.event_name == 'pull_request'
81+
uses: actions/checkout@v4
82+
with:
83+
path: plugins
84+
# No ref: defaults to refs/pull/N/merge (PR code merged into develop)
85+
7186
- name: Set up Python
72-
uses: actions/setup-python@v3
87+
uses: actions/setup-python@v5
7388
with:
7489
python-version: ${{ matrix.python-version }}
7590
architecture: x64
91+
7692
- run: python3 -m pip install --upgrade pip
7793

78-
- name: Install setuptools (needed for Python 3.12)
94+
- name: Install setuptools
7995
run: pip install setuptools
96+
8097
- name: Install requirements for unit testing
8198
run: pip install -r tests/requirements.txt
99+
82100
- name: Build Requirements for SmartHomeNG
83101
run: python3 tools/build_requirements.py
102+
84103
- name: Install SmartHomeNG base requirements
85104
# base requirements are needed for pytest to run
86105
run: pip install -r requirements/base.txt

__init__.py

100755100644
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
def plugin_release():
2-
return '1.12.0.0'
2+
return '1.12.0.1'
3+
34

45
def plugin_branch():
56
return 'master'
6-

stateengine/StateEngineAction.py

Lines changed: 43 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __init__(self, abitem, name: str):
7272
self._caller = StateEngineDefaults.plugin_identification
7373
self.shtime = Shtime.get_instance()
7474
self._name = name
75-
self.__delay = StateEngineValue.SeValue(self._abitem, "delay")
75+
self._delay = StateEngineValue.SeValue(self._abitem, "delay")
7676
self.__repeat = None
7777
self.__instanteval = None
7878
self.__overwrite = None
@@ -121,7 +121,7 @@ def _update_value(self, value_type, value, attribute, cast=None):
121121
return _issue_list
122122

123123
def update_delay(self, value):
124-
_issue = self._update_value(self.__delay, value, 'delay', 'seconds')
124+
_issue = self._update_value(self._delay, value, 'delay', 'seconds')
125125
return _issue
126126

127127
def update_instanteval(self, value):
@@ -206,7 +206,7 @@ def update_webif_actionstatus(self, state, name, success, issue=None, reason=Non
206206
# Write action to logger
207207
def write_to_logger(self):
208208
self._log_info("function: {}", self._function)
209-
delay = self.__delay.write_to_logger() or 0
209+
delay = self._delay.write_to_logger() or 0
210210
if self.__repeat is not None:
211211
repeat = self.__repeat.write_to_logger()
212212
else:
@@ -551,17 +551,20 @@ def _update_repeat_webif(value: bool):
551551
repeat_text = ""
552552
self._log_increase_indent()
553553
if _validitem:
554-
delay = 0 if self.__delay.is_empty() else self.__delay.get()
554+
delay = 0 if self._delay.is_empty() else self._delay.get()
555+
overwrite = None if self.__overwrite is None else self.__overwrite.get()
556+
cond_remove = delay == -1 and self._scheduler_name
555557
plan_next = None
556558
if self._scheduler_name:
557559
plan_next = self._se_plugin.scheduler_return_next(self._scheduler_name)
558-
if plan_next is not None and plan_next > self.shtime.now() or delay == -1:
559-
self._log_info("Action '{0}: Removing previous delay timer '{1}'.", self._name, self._scheduler_name)
560+
cond_plan = plan_next is not None and plan_next > self.shtime.now()
561+
cond_remove = delay == -1 and self._scheduler_name
562+
if cond_plan and overwrite is False:
563+
self._log_develop("Not removing previous delay timer '{0}' because overwrite is false. Action will run at {1}", self._scheduler_name, plan_next)
564+
return
565+
elif cond_plan or cond_remove:
566+
self._log_info("Removing previous delay timer '{0}'.", self._scheduler_name)
560567
self._se_plugin.scheduler_remove(self._scheduler_name)
561-
try:
562-
self._abitem.remove_scheduler_entry(self._scheduler_name)
563-
except Exception:
564-
pass
565568

566569
actionname = "Action '{0}'".format(self._name) if delay == 0 else "Delayed Action ({0} seconds) '{1}'.".format(
567570
delay, self._scheduler_name)
@@ -613,82 +616,47 @@ def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text
613616

614617
self._log_decrease_indent(50)
615618
self._log_increase_indent()
616-
vals = {
617-
'state': state,
618-
'actionname': actionname,
619-
'namevar': self._name,
620-
'repeat_text': repeat_text,
621-
'value': None,
622-
'current_condition': current_condition,
623-
'previous_condition': previous_condition,
624-
'previousstate_condition': previousstate_condition,
625-
'next_condition': next_condition
626-
}
627619
if delay == 0:
628-
self._abitem.scheduler_remove(self._scheduler_name)
620+
if self._scheduler_name:
621+
self._se_plugin.scheduler_remove(self._scheduler_name)
622+
self._abitem.remove_scheduler_entry(self._scheduler_name)
629623
self._log_info("Action '{}': Running.", namevar)
630-
self.real_execute(
631-
state, actionname, namevar, repeat_text,
632-
None, False, # False = kein Instant Eval
633-
current_condition, previous_condition,
634-
previousstate_condition, next_condition
635-
)
624+
self.real_execute(state, actionname, namevar, repeat_text, None, False, current_condition, previous_condition, previousstate_condition, next_condition)
636625
else:
637626
instanteval = None if self.__instanteval is None else self.__instanteval.get()
638627
overwrite = None if self.__overwrite is None else self.__overwrite.get()
628+
next_run = (self.shtime.now() + datetime.timedelta(seconds=delay)).replace(microsecond=0)
639629
self._log_info(
640-
"Action '{0}': Add {1} second timer '{2}' for delayed execution.{3} Instant Eval: {4}. Overwrite: {5}",
641-
self._name, delay, self._scheduler_name, repeat_text, instanteval, overwrite
630+
"Action '{0}': Add {1} second timer '{2}' for delayed execution.{3} Instant Eval: {4}. Overwrite: {5}. Action will run at ca. {6}",
631+
self._name, delay, self._scheduler_name, repeat_text, instanteval, overwrite, next_run
642632
)
643-
644633
next_run = self.shtime.now() + datetime.timedelta(seconds=delay)
645-
646634
if instanteval is True:
647635
self._log_increase_indent()
648636
self._log_debug("Evaluating value for delayed action '{}'.", namevar)
649-
650-
vals['value'] = self.real_execute(
651-
state, actionname, self._name, repeat_text,
652-
None, True,
653-
current_condition, previous_condition,
654-
previousstate_condition, next_condition
655-
)
656-
657-
self._log_debug("Value for delayed action is going to be '{}'.", vals['value'])
637+
value = self.real_execute(state, actionname, namevar, repeat_text, None, True, current_condition, previous_condition, previousstate_condition, next_condition)
638+
self._log_debug("Value for delayed action is going to be '{}'.", value)
658639
self._log_decrease_indent()
659-
640+
else:
641+
value = None
642+
self._abitem.add_scheduler_entry(self._scheduler_name)
660643
self.update_webif_actionstatus(state, self._name, 'Scheduled')
661-
662-
self._abitem.scheduler_add(
663-
self._scheduler_name,
664-
self,
665-
value=vals,
666-
next=next_run,
667-
overwrite=overwrite
668-
)
669-
670-
def delayed_execute(self, actionname: str, namevar: str = "", repeat_text: str = "", value=None, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None, state=None, caller=None):
671-
self._log_debug(
672-
"Putting action '{}'{} into queue. Caller: {}", namevar,
673-
f" from state '{state}'" if state else "", caller
674-
)
675-
676-
self.__queue.put([
677-
"delayedaction",
678-
self,
679-
actionname,
680-
namevar,
681-
repeat_text,
682-
value,
683-
current_condition,
684-
previous_condition,
685-
previousstate_condition,
686-
next_condition,
687-
state
688-
])
644+
self._se_plugin.scheduler_add(self._scheduler_name, self._delayed_execute,
645+
value={'actionname': actionname, 'namevar': self._name,
646+
'repeat_text': repeat_text, 'value': value,
647+
'current_condition': current_condition,
648+
'previous_condition': previous_condition,
649+
'previousstate_condition': previousstate_condition,
650+
'next_condition': next_condition, 'state': state}, next=next_run)
651+
652+
def _delayed_execute(self, actionname: str, namevar: str = "", repeat_text: str = "", value=None, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None, state=None, caller=None):
653+
if state:
654+
self._log_debug("Putting delayed action '{}' from state '{}' into queue. Caller: {}", namevar, state, caller)
655+
self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition, next_condition, state])
656+
else:
657+
self._log_debug("Putting delayed action '{}' into queue. Caller: {}", namevar, caller)
658+
self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition, next_condition])
689659
if not self._abitem.update_lock.locked():
690-
self._log_debug("Running queue")
691-
self._log_increase_indent()
692660
self._abitem.run_queue()
693661

694662
# Really execute the action (needs to be implemented in derived classes)
@@ -1055,6 +1023,7 @@ def complete(self, evals_items=None, use=None):
10551023
self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state)
10561024
self._abitem.set_variable('current.action_name', self._name)
10571025
self._abitem.set_variable('current.state_name', self._state.name)
1026+
delay = 0 if self._delay.is_empty() else self._delay.get()
10581027
self._scheduler_name = "{}-SeLogicDelayTimer".format(self.__logic)
10591028
_issue = {self._name: {'issue': None, 'logic': self.__logic,
10601029
'issueorigin': [{'state': self._state.id, 'action': self._function}]}}
@@ -1381,7 +1350,8 @@ def suspend_execute(self, state=None, current_condition=None, previous_condition
13811350
# determine remaining suspend time and write to variable item.suspend_remaining
13821351
suspend_time = self._abitem.get_variable("item.suspend_time")
13831352
suspend_over = suspend_item.property.last_change_age
1384-
suspend_remaining = int(suspend_time - suspend_over + 0.5) # adding 0.5 causes round up ...
1353+
suspend_remaining = max(-1, int(suspend_time - suspend_over + 0.5)) # adding 0.5 causes round up ...
1354+
13851355
self._abitem.set_variable("item.suspend_remaining", suspend_remaining)
13861356
self._log_debug("Updated variable 'item.suspend_remaining' to {0}", suspend_remaining)
13871357
self._action_status = _issue

0 commit comments

Comments
 (0)