Usage¶
Building your first HERO is very simple. Just write your custom class and make it inherit from heros.LocalHero.
A simple example can be seen
import time
from heros import LocalHERO
class TestObject(LocalHERO):
testme: int = 0
def __init__(self, name: str, start: int, *args, **kwargs):
super().__init__(name, *args, **kwargs)
self.testme = start
def read_temp(self, min: int, max: int) -> float:
return (max + min) / 2
def hello(self) -> str:
self.testme += 1
return "world"
with TestObject("my_hero", 5) as obj:
# keep running with infinite loop
while True:
time.sleep(1)
Due to the infinite loop, the script will not terminate and keep the object obj alive. Since it inherits from heros.LocalHERO, the object was analyzed upon instantiation and provided as a HERO to the network.
This means, we can now simply access the method attributes of the object from a remote site.
To get remote access we can run the following in a different process or on a different machine in the same network (UDP broadcast needs to reach the other machine for discovery):
from heros import RemoteHERO
with RemoteHERO("my_hero") as obj:
# call remote functions
print(obj.read_temp(0, 10))
print(obj.hello())
# access remote attribute
obj.testme = 10
Nested HEROs¶
HEROS is able to serialize HEROs as references to a HERO.
This allows to pass a HERO between the local and the remote site.
When either of the sites receives such a reference, it creates a RemoteHERO to access the referenced HERO.
This allows things like
Deep access to HEROs nested inside of HEROs.
Passing HEROs as arguments into methods exposed by a HERO.
Retrieving HEROs returned by a HERO.
Unserializable Objects¶
A HERO attribute or method might return an object that is not a HERO and can not be serialized.
In that case, the returned object is cached on the side of the LocalHERO and an identifier is sent to the remote side. The remote side can store the reference locally.
If the reference is sent back to the LocalHERO side, the corresponding object is taken from the cache and inserted into the request instead of the reference.
This allows to instruct the LocalHERO to do something with an object that cannot be transferred.
This allows, for example, to hand over an unserializable object retrieved earlier as argument to a function.
Note
The cache that keeps the object references uses only weak references to to avoid memory leaks. That means that an object can be garbage collected if not any other instance keeps a reference on it.
Getting a list of all running HEROs in a network¶
Either use a HERO monitor or open a python console with HEROS installed and run
from heros.heros import HEROObserver
ob = HEROObserver()
ob.known_objects.keys()
Configuring HEROS¶
Besides the config mechanisms through the BOSS starter and event decorators, HEROS can also be configured via the following environment variables:
- HEROS_LOG
Set the log level of all HEROS related systems.
- HEROS_SEP_MULTICAST >0.10.0 unstable
If true, different realms use different multicast addresses for discovery. This means the HEROS of different realms will not discover each other and can not communicate. Defaults to False.
Note
You can get the multicast group for your realm by running
uv run python -c "import heros; print(heros.helper.generate_multicast_address('my_realm'))"Note
If you want to run a zenohd router, you need to run it on the correct multicast group for your realm for example by
zenohd --cfg='scouting/multicast/address:"<multicast-group-ip>:7447"'- HEROS_ZENOH_CONFIG >0.12.0
Can be used to alter the configuration of the Zenoh process. Must be supplied in the form of a valid json configuration string as described in the example. For example
HEROS_ZENOH_CONFIG='{"connect": {"endpoints":["tcp/172.16.50.2:7447"]}}'. The configuration that is supplied directly to theheros.zenoh.ZenohSessionManager.update_config()method overwrites this configuration.Warning
This can have negative side effects if configurations are incompatible with the default HEROS settings and should be considered a last resort option.