Skip to content

Commit

Permalink
v1.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jayscoder committed Apr 12, 2024
1 parent f5d22ca commit 5ee14a2
Show file tree
Hide file tree
Showing 22 changed files with 564 additions and 87 deletions.
2 changes: 2 additions & 0 deletions demo_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
if __name__ == '__main__':
builder = Builder(folders=['logs', 'demos'])
print(builder.get_relative_filename('demos/demos/demo_1.xml'))


12 changes: 6 additions & 6 deletions pybts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from . import builder
from . import utility
from . import tree
from . import node
from . import board
from .tree import Tree
from .node import *
from .nodes import *
from .composites import *
from .board import Board
from .builder import Builder
from .decorators import *
from py_trees import logging

from . import utility
from . import nodes
from . import composites
from . import decorators

from importlib.metadata import version

try:
Expand Down
15 changes: 11 additions & 4 deletions pybts/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Callable
import xml.etree.ElementTree as ET
import copy
from pybts.node import *
from pybts.nodes import *
from pybts.composites import *
from pybts.decorators import *
import uuid
Expand Down Expand Up @@ -73,12 +73,13 @@ def find_filepath(self, filepath: str):

def read_text_from_file(self, filepath: str) -> str:
# 从folder中找文件
init_filepath = filepath
filepath = self.find_filepath(filepath=filepath)
if filepath != '' and os.path.exists(filepath) and os.path.isfile(filepath):
with open(filepath, 'r', encoding='utf-8') as file:
return file.read()

raise Exception(f'Cannot find file: {filepath}')
raise Exception(f'Cannot find file: {init_filepath}')

def build_from_file(self, filepath: str, attrs: dict = None):
"""
Expand Down Expand Up @@ -161,15 +162,20 @@ def register_default(self):
Template,
PreCondition,
PostCondition,
Print
Switcher,
ReactiveSwitcher,
)

self.register_node(
Failure,
Success,
Running,
IsChanged,
IsMatchRule
IsMatchRule,
IsEqual,
Print,
RandomIntValue,
RandomFloatValue
)

self.register_node(
Expand All @@ -185,6 +191,7 @@ def register_default(self):
SuccessIsRunning,
Timeout,
Throttle,
IsStatusChanged
)

# 且或非
Expand Down
2 changes: 1 addition & 1 deletion pybts/composites/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .condition_branch import ConditionBranch
from .template import Template
from .ppa import PreCondition, PostCondition

from .switcher import Switcher, ReactiveSwitcher
# TODO: RUNNING节点的打断操作应该怎么在行为树上体现出来
# 通过ReactiveSelector/ReactiveSequence来起到打断后续节点的效果
# ReactiveSequence: 前面的节点条件如果满足,则会一直
19 changes: 18 additions & 1 deletion pybts/composites/composite.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import py_trees
from pybts.node import Node
from pybts.nodes import Node
from abc import ABC
import typing
from py_trees.common import Status
Expand Down Expand Up @@ -258,3 +258,20 @@ def SEQ_SEL_tick(

self.status = new_status
yield self

def switch_tick(self, index: int, tick_again_status: list[Status]) -> typing.Iterator[py_trees.behaviour.Behaviour]:
if self.status in tick_again_status:
# 重新执行上次执行的子节点
assert self.current_child is not None
else:
self.current_child = self.children[index] # 执行对应的index

yield from self.current_child.tick()
for child in self.children:
if child != self.current_child:
# 清除子节点的状态(停止正在执行的子节点)
child.stop(Status.INVALID)

self.status = self.current_child.status
yield self

8 changes: 1 addition & 7 deletions pybts/composites/parallel.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
from pybts.composites.composite import Composite
from pybts.composites.sequence import Sequence
from pybts.composites.selector import Selector
import py_trees
from pybts.node import Node
from abc import ABC
import typing
from py_trees.common import Status
from py_trees import behaviour
import itertools
import uuid


class Parallel(Composite):
Expand Down Expand Up @@ -77,6 +70,7 @@ def to_data(self):
'success_threshold': self.success_threshold
}


# class ReactiveParallel(Parallel):
# """
# 反应式并行节点
Expand Down
2 changes: 1 addition & 1 deletion pybts/composites/ppa.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import typing
from py_trees.common import Status
from pybts.composites.parallel import Parallel
from pybts.node import Condition
from pybts.nodes import Condition


class PreCondition(Parallel, Condition):
Expand Down
4 changes: 0 additions & 4 deletions pybts/composites/sequence.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import py_trees
from pybts.node import Node
from abc import ABC
import typing
from py_trees.common import Status
from py_trees import behaviour
import itertools
from pybts.composites.composite import Composite


Expand Down
53 changes: 53 additions & 0 deletions pybts/composites/switcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import py_trees
import typing

from py_trees.behaviour import Behaviour
from py_trees.common import Status
from py_trees import behaviour
from pybts.composites.composite import Composite
import random


class Switcher(Composite):
"""
选择其中一个子节点执行
- 当前执行节点返回 RUNNING,下次执行还是从这个节点开始
返回当前执行节点的状态
index:
- 具体的数字
- jinja2模版: 从context中获取
- random: 随机数
"""

def __init__(self, index: typing.Union[int, str] = 'random', **kwargs):
super().__init__(**kwargs)
self.index = index
self.curr_index = None

def reset(self):
super().reset()
self.curr_index = None

def to_data(self):
return {
**super().to_data(),
'curr_index': self.curr_index
}

def tick(self) -> typing.Iterator[Behaviour]:
if self.index == 'random':
self.curr_index = random.randint(0, len(self.children) - 1)
else:
self.curr_index = self.converter.int(self.index)
return self.switch_tick(index=self.curr_index, tick_again_status=[Status.RUNNING])


class ReactiveSwitcher(Switcher):
"""
相应式选择其中一个子节点执行,每次都会重新选择index
返回当前执行节点的状态
"""

def tick(self) -> typing.Iterator[Behaviour]:
return self.switch_tick(index=self.converter.int(self.index), tick_again_status=[])
28 changes: 28 additions & 0 deletions pybts/converter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import typing
import jinja2
import json
from py_trees.common import Status
from typing import Union

_STATUS_MAP = {
'SUCCESS': Status.SUCCESS,
'FAILURE': Status.FAILURE,
'RUNNING': Status.RUNNING,
'INVALID': Status.INVALID
}


class Converter:
Expand Down Expand Up @@ -60,6 +69,25 @@ def eval(self, value: str, context: dict = None):
ctx[key] = ctx[key]()
return eval(value, ctx)

@classmethod
def status(cls, value: Union[str, Status]) -> Status:
if isinstance(value, Status):
return value
value = value.upper()
if value not in _STATUS_MAP:
raise Exception(f'{value} is not a valid status')
return _STATUS_MAP[value]

@classmethod
def status_list(cls, value: Union[str, Status, list[Status]]) -> list[Status]:
if isinstance(value, Status):
return [value]
elif isinstance(value, list):
return [cls.status(value=item) for item in value]
elif isinstance(value, str):
value_list = value.split(',')
return [cls.status(value=item) for item in value_list if item != '']

def render(self, value: str, context: dict = None) -> str:
ctx = { }
# if self.node.attrs is not None:
Expand Down
65 changes: 64 additions & 1 deletion pybts/decorators/nodes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations
import py_trees
from pybts.node import Node

import pybts
from pybts.nodes import Node
from abc import ABC
from py_trees.common import Status
import typing
Expand Down Expand Up @@ -585,3 +587,64 @@ def tick(self):
else:
yield from Node.tick(self)


class IsStatusChanged(Decorator):
"""
子节点的状态变化后才会认为是成功
如果指定了from_status和to_status,则只有子节点的状态由from_status转变到to_status,才会认为是触发了状态变化
from_status和to_status均可以由多个状态组成,用逗号分隔
immediate: 一开始是否会触发一次IsChanged
"""

def __init__(self, from_status: str | Status = '', to_status: str | Status = '', immediate: bool | str = False,
**kwargs):
super().__init__(**kwargs)

self.from_status: list[Status] = self.converter.status_list(from_status)
self.to_status: list[Status] = self.converter.status_list(to_status)
self.immediate: bool = immediate
self.last_status = None
self.changed_count = 0

def setup(self, **kwargs: typing.Any) -> None:
super().setup(**kwargs)
self.immediate = self.converter.bool(self.immediate)

def reset(self):
super().reset()
self.last_status = None
self.changed_count = 0

def to_data(self):
return {
**super().to_data(),
'from_status' : self.from_status,
'to_status' : self.to_status,
'immediate' : self.immediate,
'changed_count': self.changed_count
}

def check_is_changed(self):
if len(self.from_status) > 0 and self.last_status not in self.from_status:
return False

if len(self.to_status) > 0 and self.decorated.status not in self.to_status:
return False

if self.last_status != self.decorated.status:
return True
else:
return False

def update(self) -> Status:
self.logger.debug("%s.update() %s -> %s" % (self.__class__.__name__, self.last_status, self.decorated.status))
if not self.immediate and self.last_status is None:
self.last_status = self.decorated.status
is_changed = self.check_is_changed()
self.last_status = self.decorated.status
if is_changed:
return Status.SUCCESS
else:
return Status.FAILURE
Loading

0 comments on commit 5ee14a2

Please sign in to comment.