[Documentation] [TitleIndex] [WordIndex

Overwiew

A bridge between ROS and open Home Automation Bus (openHAB), which is an open source home automation software written in Java. More precisely, the Item Events of the Event Bus of openHAB are mirrored via ROS. For this purpose, the REST API of openHAB is accessed. There are two approaches for this. Once via HABApp and once a direct access without HABApp. It offers a more versatile and better alternative to the iot_bridge.

What is openHAB?

A Quick Overview

The open Home Automation Bus (openHAB) is an open source, technology agnostic home automation platform which runs as the center of your smart home!

openHAB communicates electronically with smart and not-so-smart devices, performs user-defined actions and provides web-pages with user-defined information as well as user-defined tools to interact with all devices. To achieve this, openHAB segments and compartmentalizes certain functions and operations. The following table gives a top-level description of the most important concepts as well as a link to more information:

Concepts

Meaning

More informations

Bindings

are the openHAB component that provides the interface to interact electronically with devices

see below

Things

are the first openHAB (software) generated representation of your devices

click for more info on Things

Channels

are the openHAB (software) connection between “Things” and “Items” (see below)

see below

Items

are the openHAB (software) generated representation of information about the devices

click for more info on Items

Rules

that perform automatic actions (in its simplest form: if "this" happens, openHAB will do "that")

click for more info on Rules

Sitemap

is the openHAB (software) generated user interface (web site) that presents information and allows for interactions

click for more info on Sitemaps

While the table above gives an overview, please remember that it is incomplete and a simplification of openHAB for the sake of this overview. More elements will be introduced in the documentation.

Concepts

When first thinking about your home automation system, it may be helpful to bear in mind that there are two ways of thinking about or viewing your system: the physical view and the functional view.

The physical view will be familiar to you. This view focuses on the devices in your system, the connections between these devices (e.g. wires, Z-Wave, WiFi hardware), and other physical aspects of the system.

The functional view might be a new concept for you. The functional view focuses on how information about the devices, connections, and so on, is represented in user interfaces. The functional view includes focusing on how rules affect representations of physical devices in software. Perhaps most important to you, the functional view focuses on how an action in a user interface affects the software associated with the physical device it represents.

It is a bit of an over-simplification, but you can think of the physical view as being a view of the 'real world', and the functional view being a view of the 'software world'.

Things are entities that can be physically added to a system. Things may provide more than one function (for example, a Z-Wave multi-sensor may provide a motion detector and also measure room temperature). Things do not have to be physical devices; they can also represent a web service or any other manageable source of information and functionality.

Things expose their capabilities through Channels. Whether an installation takes advantage of a particular capability reflected by a Channel depends on whether it has been configured to do so. When you configure your system, you do not necessarily have to use every capability offered by a Thing. You can find out what Channels are available for a Thing by looking at the documentation of the Thing's Binding.

Bindings can be thought of as software adapters, making Things available to your home automation system. They are add-ons that provide a way to link Items to physical devices. They also abstract away the specific communications requirements of that device so that it may be treated more generically by the framework.

Items represent capabilities that can be used by applications, either in user interfaces or in automation logic. Items have a State and they may receive Commands.

The glue between Things and Items are Links. A Link is an association between exactly one Channel and one Item. If a Channel is linked to an Item, it is "enabled", which means that the capability the Item represents is accessible through that Channel. Channels may be linked to multiple Items and Items may be linked to multiple Channels.

To illustrate these concepts, consider the example below of a two-channel actuator that controls two lights:

https://www.openhab.org/assets/img/thing-devices-1.bd432b36.png

The actuator is a Thing that might be installed in an electrical cabinet. It has a physical address and it must be configured in order to be used (remember the physical view introduced at the beginning of this article).

In order for the user to control the two lights, he or she accesses the capability of the actuator Thing (turning on and off two separate lights) through two Channels, that are Linked to two switch Items presented to the user through a user interface.

Channels

Channels are the logical link between a Thing and an Item. Channels originate from Things definition and define how your Thing can communicate with Item (and vice versa). You will create channels when defining your Thing.

During the definition of your Thing you will identify the channel to which your Item will be linked. These two steps ensure that openHAB can transmit the information from the Thing to the Item (and vice versa).

Bindings

Bindings are software packages that are installed by the user in openHAB. The main purpose of Bindings is to establish the connection between your device and your Thing. Bindings communicate with your device and translate all commands to and from openHAB between your device and your Thing.

Bindings are provided in the Add-on section (opens new window) of this website. Here you will find a searchable list of several hundred bindings to support as many devices as possible. New bindings are regularly added as developers integrate more devices into openHAB.

For each binding, detailed instructions and examples are provided that include guidance on configuration (if any) of the binding itself, the definition of Things supported by this binding and the Channels these Things provide. In most cases, the description also contains a fully worked out example that includes a definition of Things and its Channels, Items linked to those Channels and the use of these Items in a sitemap.

Event Bus

The openHAB framework provides an event bus for inter-component communication. The communication is based on events which can be sent and received through the event bus in an asynchronous way. Examples of openHAB event types are ItemCommandEvent, ItemStateEvent, ItemAddedEvent, ThingStatusInfoEvent, etc.

For us, only the items and the item events are relevant.

https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/optimized/3X/0/5/05c8f74bea047388d6eb53d1fadb717ae62ffd2e_2_1035x580.jpeg

So if we simplify it a lot, then we have a device and as a Digital Twin for this we have a Thing. This device is connected via various bindings and a Thing is created in openHAB for this device on the basis of the binding, which you can imagine as an instance of the class of the binding for this device. For different manufacturers, communication technologies, etc. there is a corresponding binding. In this binding several classes are created, since e.g. a manufacturer owns several devices. Means for a device there are defined functions in a class and for another device there is accordingly another class with differently defined functions. Thus Things do not only differ on the basis of the Binding but many Binding offer for different devices also again different Things. In the end, an item is created via a Thing. An item refers to a function of the device. If this function is a pure sensor, then this item only receives a state. But if this function is also something that can be switched or a mode, a program or whatever can be changed, a state is assigned and a command can be sent to the device so that something changes.

Every changing state, but also every command runs through the event bus of openHAB.

The Interfaces

The EventPublisher posts Events through the openHAB event bus in an asynchronous way. The EventSubscriber defines the callback interface to receive events of specific types to which the event subscriber is subscribed. The EventPublisher and the EventSubscribers are registered as OSGi services. An event subscriber can provide an EventFilter in order to filter events based on the topic or the content. If there is no filter all subscribed event types are received. The event itself will be subclassed for each event type, which exists in the System (e.g. ItemCommandEvent, ItemUpdatedEvent, ThingStatusInfoEvent).

The Core Events

This section lists the core events provided by openHAB which can be divided into the categories Item Events, Thing Events and Inbox Events.

An event consists of a topic, a type, a payload and a source. The payload can be serialized with any String representation and is determined by its concrete event type implementation (e.g. ItemCommandEvent, ItemUpdatedEvent). The payloads of the openHAB core events are serialized with JSON. Each event implementation provides the payload as high level methods as well, usually presented by a data transfer object (DTO).

A topic clearly defines the target of the event and its structure is similar to a REST URI, except the last part, the action. The topics of openHAB events are divided into the following four parts: {namespace}/{entityType}/{entity}/{action}, e.g. openhab/items/{itemName}/command.

The type of an event is represented by a string, usually the name of the concrete event implementation class, e.g. ItemCommandEvent, ItemUpdatedEvent . This string type presentation is used by event subscribers for event subscription (see chapter "Receive Events") and by the framework for the creation of concrete event instances.

The event source is optional and represents the name of the source identifying the sender.

Item Events

There are the following item events in openHAB:

Event

Description

Topic

ItemAddedEvent

An item has been added to the item registry.

openhab/items/{itemName}/added

ItemRemovedEvent

An item has been removed from the item registry.

openhab/items/{itemName}/removed

ItemUpdatedEvent

An item has been updated in the item registry.

openhab/items/{itemName}/updated

ItemCommandEvent

A command is sent to an item via a channel.

openhab/items/{itemName}/command

ItemStateEvent

The state of an item is updated.

openhab/items/{itemName}/state

ItemStatePredictedEvent

The state of an item predicted to be updated.

openhab/items/{itemName}/statepredicted

ItemStateChangedEvent

The state of an item has changed.

openhab/items/{itemName}/statechanged

GroupItemStateChangedEvent

The state of a group item has changed through a member.

openhab/items/{itemName}/{memberName}/statechanged

The ItemStateEvent is always sent if the state of an item is updated, even if the state did not change. ItemStateChangedEvent is sent only if the state of an item was really changed. It contains the old and the new state of the item.

The GroupItemStateChangedEvent is sent only if the state of a group item was changed by a member. It contains the old and the new state of the group item as well as the member.

As a note to the Topics, one can add that openhab could also be replaced by smarthome.

Interesting for us are the two events ItemStateEvent and ItemCommandEvent.

An ItemCommandEvent is executed when, for example, I switch in the sitemap a switch from OFF to ON or execute a sendCommand in a rule. An ItemStateEvent is triggered when the state of an item changes. This happens immediately, for example, after an ItemCommandEvent or when a binding performs a status update. I can also operate a device without openHAB and the item then recognizes that a state has changed. Or a temperature sensor changes the measured temperature as a state several times by itself.

The bridge from openhab to ROS works bidirectionally. A ROS device, such as a robot, should be able to subscribe to the items from openHAB. For this purpose, the state of an item is published. However, since one is not a binding that changes the information in an item, a sendCommand and no postUpdate must be performed. This means that the bridge must subscribe commands! Then openHAB will execute this command.

Item Types

To subscribe the items from openHAB or to publish an item including a command to openHAB you need the appropriate message types. These are based on the Item Types and Command Types of openHAB.

The Item type defines what kind of state can be stored in that Item and which commands the Item will accept. Item types are comparable to basic variable data types in programming languages. Each Item type has been optimized for a particular kind of component in your smart home. This optimization is reflected in the data and command types.

Available Item types are:

Item Type

Description

Command Types

Color

Color information (HSB)

OnOff, IncreaseDecrease, Percent, HSB

Contact

Status of contacts, e.g. door/window contacts. Does not accept commands, only status updates.

-

DateTime

Stores date and time

-

Dimmer

Percentage value for dimmers

OnOff, IncreaseDecrease, Percent

Group

Item to nest other items / collect them in groups

-

Image

Binary data of an image. This means a base64 encoded string

-

Location

GPS coordinates

Point

Number

Values in number format

Decimal

Player

Allows control of players (e.g. audio players)

PlayPause, NextPrevious, RewindFastforward

Rollershutter

Rollershutter Item, typically used for blinds

UpDown, StopMove, Percent

String

Stores texts

String

Switch

Switch Item, used for anything that needs to be switched ON and OFF

OnOff

Command Types

In the last step we take a closer look at the Command Types and its Range of Values.

Command Type

Range of Values

Decimal

<int> or <float>
So theoretically the max values of an integer or float in Java.

HSB

"<hue>,<saturation>,<brightness>"
<hue>: 0...360
<saturation>: 0...100
<brightness>: 0...100

IncreaseDecrease

"INCREASE" or "DECREASE"

NextPrevious

"NEXT" or "PREVIOUS"

OnOff

"ON" or "OFF"

OpenClosed

"OPEN" or "CLOSED"

!Percent

0...100

PlayPause

"PLAY" or "PAUSE"

Point

"<longitude>,<latitude>,<altitude>"
<longitude>: -180.0...0...180.0 degrees
<latitude>: -90.0...0...90.0 degrees
<altitude>: <float> in meters. There are locations that are below sea level and there are mountains that are quite a bit higher. A limit value is not known.

RewindFastforward

"REWIND" or "FASTFORWARD"

StopMove

"STOP" or "MOVE"

String

<String>
So theoretically the maximum size of an String in Java.

UpDown

"UP" or "DOWN"

A command type like OnOff is ultimately an enum and contains the strings "ON" and "OFF" as constants (this is why they are written in UPPERCASE), so the other enum commands actually look the same. This means that there are only two constants per each enum type.

The Command Types are only used within openHAB. The openhab_msgs also have these enums in their field description, but you may have to work with a string in parts of your program!

Installation

The bridge can be installed with or without HABApp. Since both options access the REST API of openHAB, ROS and openHAB do not need to be installed on the same computer (or virtual machine). However, ROS and openHAB must be able to find each other. Ideally they are in the same network for this.

openHAB

You can skip this part if openHAB is installed. More informations you can find here.

At first you have to install Java:

# install the necessary dependencies
sudo apt-get -q update
sudo apt-get -yq install gnupg curl

# add Azul's public key
sudo apt-key adv \
  --keyserver hkp://keyserver.ubuntu.com:80 \
  --recv-keys 0xB1998361219BD9C9

# download and install the package that adds
# the Azul APT repository to the list of sources
curl -O https://cdn.azul.com/zulu/bin/zulu-repo_1.0.0-3_all.deb

# install the package
sudo apt-get install ./zulu-repo_1.0.0-3_all.deb

# update the package sources
sudo apt-get update

# install Azul Zulu JDK 11
sudo apt-get install zulu11-jdk

After that you have to install openHAB 3 and its addons:

curl -fsSL "https://openhab.jfrog.io/artifactory/api/gpg/key/public" | gpg --dearmor > openhab.gpg
sudo mkdir /usr/share/keyrings
sudo mv openhab.gpg /usr/share/keyrings
sudo chmod u=rw,g=r,o=r /usr/share/keyrings/openhab.gpg

echo 'deb [signed-by=/usr/share/keyrings/openhab.gpg] https://openhab.jfrog.io/artifactory/openhab-linuxpkg stable main' | sudo tee /etc/apt/sources.list.d/openhab.list

sudo apt-get update
sudo apt-get install openhab
sudo apt-get install openhab-addons

To run openHAB automaticall after restart you have to enable it:

sudo systemctl enable openhab.service
sudo systemctl start openhab.service

ROS 1

You can skip this part if ROS 1 is installed.

ROS Noetic for Ubuntu 20.04 ROS Melodic for Ubuntu 18.04 ROS Kinetic for Ubuntu 16.04

After installing ROS you have to create a catkin workspace:

source /opt/ros/<distro>/setup.bash
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
catkin_make
source devel/setup.bash

As example for ROS Kinetic:

source /opt/ros/kinetic/setup.bash
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
catkin_make
source devel/setup.bash

Since you access openHAB via the REST API, it is possible that you install the bridge for multiple ROS distributions at the same time. This allows multiple robots running under different distributions to communicate with openHAB. For this you need several computers or virtual machines, HABApp or the pip packages have to be installed on each of them accordingly.

Setting environment variables

If some packages cannot be found please edit the /etc/environment to something like this:

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:$ROS_ROOT:/home/<user>/.local/bin"
JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
ROS_HOME="/home/<user>/.ros"
ROS_LOG_DIR=$ROS_HOME/log
export PYTHONPATH=$PYTHONPATH:/usr/lib/python3.8/site-packages:/usr/lib/python3/dist-packages:/opt/ros/<ros_distro>/lib/python3/dist-packages:/core/roslib/src:/opt/habapp/bin:/opt/habapp/lib/python3.8/site-packages:/home/<user>/catkin_ws/devel/lib/python3/dist-packages:/home/<user>/.local/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages

Please edit <ros_distro> to your ROS distribution and <user> to the username of your user.

The JAVA_HOME is only needed if you are using openHAB on the same computer or virtual machine than ROS and the openHAB Bridge.

With HABApp

In the variant with HABApp, you must first install HABApp and then adjust it accordingly so that one can use ROS and HABApp together.

Install HABApp

If you are using an older Ubuntu Linux distribution please make sure that you install python3, pip3 and python3-virtualenv. Further informations you can get here.

You have to install HABApp like this:

cd /opt
python3 -m venv habapp
cd habapp
source bin/activate
python3 -m pip install --upgrade pip setuptools
python3 -m pip install habapp

habapp --config /etc/openhab/habapp

To make HABApp run after reboot you have to create a service file with sudo nano /etc/systemd/system/habapp.service:

[Unit]
Description=HABApp
Documentation=https://habapp.readthedocs.io
After=network-online.target

[Service]
Type=simple
User=openhab
Group=openhab
UMask=002
Environment=LD_LIBRARY_PATH=/home/<user>/catkin_ws/devel/lib:/opt/ros/<ros_distro>/lib
ExecStart=/bin/bash -c 'source /etc/environment; /usr/bin/python3 /opt/habapp/bin/habapp -c <PATH_TO_CONFIGURATION_FOLDER>'
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target

If you have openHAB and HABApp installed on the same computer, you can also write the service so that HABApp starts, stops and restarts depending on openHAB:

[Unit]
Description=HABApp
Documentation=https://habapp.readthedocs.io
Requires=openhab.service
After=openhab.service
BindsTo=openhab.service
PartOf=openhab.service

[Service]
Type=simple
User=openhab
Group=openhab
UMask=002
Environment=LD_LIBRARY_PATH=/home/<user>/catkin_ws/devel/lib:/opt/ros/<ros_distro>/lib
ExecStart=/bin/bash -c 'source /etc/environment; /usr/bin/python3 /opt/habapp/bin/habapp -c <PATH_TO_CONFIGURATION_FOLDER>'
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=openhab.service

Both services are already minimally different from the standard installation of HABApp!

Make sure that your replace <user> with the username on which account you have installed your catkin_ws. Also you have to change the <ros_distro> to the actual ros distro you are using.

Now execute the following commands to enable autostart:

sudo systemctl --system daemon-reload
sudo systemctl enable habapp.service

It is now possible to start, stop, restart and check the status of HABApp with:

sudo systemctl start habapp.service
sudo systemctl stop habapp.service
sudo systemctl restart habapp.service
sudo systemctl status habapp.service

How does HABApp work?

HABApp is a Python rule engine for home automation. It has local items, an event bus and can integrate external systems, e.g. openHAB and MQTT. Rules can listen to events from the event bus. These events are generated by HABApp or by the external systems. Additionally there is a scheduler available that makes time based triggering very easy.

https://habapp.readthedocs.io/en/latest/_images/architecture.png

The folder structure looks like:

https://habapp.readthedocs.io/en/latest/_images/folders.png

HABApp connects to the openHAB event stream and automatically updates the local openHAB items when an item in openHAB changes. These item values are cached, so accessing and working with items in rules is very fast. The events from openHAB are also mirrored to the internal event bus which means that triggering on these events is also possible.

When HABApp connects to openHAB for the first time it will load all items/things from the openHAB instance and create local items. The name of the local openHAB items is equal to the name in openHAB.

Posting updates, sending commands or any other openHAB interface call will issue a corresponding REST-API call to change openHAB.

Hack HABApp for using ROS

To run ROS with HABApp at the same time, HABApp must be modified accordingly. This is possible because you can also use rospy outside of the catkin workspace to create a node.

Create a ROS node outside the catkin workspace.

Further informations hacking HABApp you can get in the openHAB community.

Edit /opt/habapp/bin/habapp to:

# -*- coding: utf-8 -*-
import re
import sys
import rospy
from HABApp.__main__ import main
if __name__ == '__main__':
    rospy.init_node("habapp", anonymous=False)
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

So after starting HABApp you should see the node habapp:

$ rosnode list
/habapp
/rosout

You could also change the node name to openhab then you will see of course the node openhab.

# -*- coding: utf-8 -*-
import re
import sys
import rospy
from HABApp.__main__ import main
if __name__ == '__main__':
    rospy.init_node("openhab", anonymous=False)
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

$ rosnode list
/openhab
/rosout

Install the ROS openHAB Bridge HABApp Rule

You have to go to the rules folder of HABApp and download the python file. After that you have to make it executable:

cd <PATH_TO_CONFIGURATION_FOLDER>/rules
wget https://raw.githubusercontent.com/Michdo93/HABApp-ROS-openHAB-Bridge/main/openhab_bridge.py
sudo chown +x openhab_bridge.py

Please make sure that in <PATH_TO_CONFIGURATION_FOLDER>/config.yml the connection is set to your openHAB instance!

Without HABApp

In the variant without HABApp you have to install a catkin package in the source folder of your catkin workspace.

Install dependencies with pip

You have to access the REST API from openHAB to CREATE, READ, UPDATE or DELETE items and you have also to access the Item Events from the Event Bus of openHAB. So you have to install following:

pip install python-openhab-crud
pip install python-openhab-itemevents

Install the openHAB Bridge

In the next step you have to install the openhab_bridge in your catkin workspace:

cd ~/catkin_ws/src
# does not exist currently. Please use the HABApp solution instead
# git clone --branch <branchname> https://github.com/Michdo93/openhab_bridge
cd ~/catkin_ws
catkin_make

Please replace <branchname> with your branch, as example kinetic-devel, melodic-devel or noetic-devel.

Install the openhab_msgs

Message types for the openhab_bridge. It is based on the Item Types and Command Types of openHAB.

For subscribing from openHAB a State Message Type is used and for publishing to openHAB a Command Message Type is used. For an item type (openHAB), this means that two message types (ROS) are used in this event bus.

Type Name

State Message Type

Command Message Type

Color

openhab_msgs/ColorState

openhab_msgs/ColorCommand

Contact

openhab_msgs/ContactState

openhab_msgs/ContactCommand

DateTime

openhab_msgs/DateTimeState

openhab_msgs/DateTimeCommand

Dimmer

openhab_msgs/DimmerState

openhab_msgs/DimmerCommand

Group

openhab_msgs/GroupState

n/a

Image

openhab_msgs/ImageState

openhab_msgs/ImageCommand

Location

openhab_msgs/LocationState

openhab_msgs/LocationCommand

Number

openhab_msgs/NumberState

openhab_msgs/NumberCommand

Player

openhab_msgs/PlayerState

openhab_msgs/PlayerCommand

Rollershutter

openhab_msgs/RollershutterState

openhab_msgs/RollershutterCommand

String

openhab_msgs/StringState

openhab_msgs/StringCommand

Switch

openhab_msgs/SwitchState

openhab_msgs/SwitchCommand

In the next step you have to install the openhab_msgs in your catkin workspace:

cd ~/catkin_ws/src
git clone --branch <branchname> https://github.com/Michdo93/openhab_msgs
cd ~/catkin_ws
catkin_make

Please replace <branchname> with your branch, as example kinetic-devel, melodic-devel or noetic-devel.

Library Overview

ROS package/stack

Description

openhab_bridge

A bridge between ROS and openHAB

openhab_msgs

Message types for the openhab_bridge. It is based on the Item Types and Command Types of openHAB.

openhab_bridge_subscriber

Subscribes States from openhab using a bridge between openHAB and ROS. These are just examples of how you need to proceed for each item type. You can test this by testing the openhab_static_examples.

openhab_bridge_publisher

Publishes Commands to openhab using a bridge between openHAB and ROS. These are just examples of how you need to proceed for each item type. You can test this by testing the openhab_static_examples.

openhab_bridge_image_listener

ROS package which listens and subscribes to a topic which uses sensor_msgs/Image or sensor_msgs/CompressedImage as message type. This image will be published as command to openHAB using a bridge between openHAB and ROS.

openhab_bridge_map_listener

ROS package which listens and subscribes to the map topic. The subscribed map will be converted to a JPEG image and this image will be published as command to openHAB using a bridge between openHAB and ROS.

openhab_bridge_plotter

Subscribes States from openHAB using a bridge between openHAB and ROS. Similar to rqt_plot, matplotlib is then used to create a mathematical representation that shows the changing values of the States over time.

openhab_bridge_plot_publisher

Does the same as openhab_bridge_plotter with the difference that the plot is converted to an image and then published.

In a minimal installation you need at least openhab_bridge and openhab_msgs.

Another word for openhab_bridge_image_listener is openhab_bridge_image_converter. And for openhab_bridge_map_listener you can also use openhab_bridge_map_converter.

Tutorials

To work with openHAB and ROS you need a deeper understanding of both. It is possible that I send data from openHAB to ROS and vice versa. I can control ROS via openHAB or I can also control devices that are integrated in openHAB via ROS. Also interesting are rules that I want to use in my smart home. To explain the variety of possibilities you could probably fill a lot of reference books.

In the tutorials section some basics are explained.


2024-11-09 14:42