Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 62 additions & 14 deletions docs/src/config/python-hal-interface.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,61 @@ Message level constants:
* `hal.MSG_DBG` - Additionally include debugging information
* `hal.MSG_ALL` - Print all messages encountered, disregarding level

Realtime type constants:

* `hal.REALTIME_TYPE_UNINITIALIZED` - Real time module not running
* `hal.REALTIME_TYPE_NONE` - No realtime available
* `hal.REALTIME_TYPE_UNKNOWN` - Only used when LINUXCNC_FORCE_REALTIME=1 is set. Unknown, no PREEMPT_DYNAMIC but SCHED_FIFO is available. Not recommended.
* `hal.REALTIME_TYPE_PREEMPT_DYNAMIC` - Preempt dynamic: available in vanilla kernel. Only used when LINUXCNC_FORCE_REALTIME=1 is set. Not recommended.
* `hal.REALTIME_TYPE_PREEMPT_RT` - Preempt RT
* `hal.REALTIME_TYPE_RTAI` - RTAI kernel mode
* `hal.REALTIME_TYPE_LXRT` - LXRT, userspace implementation for RTAI
* `hal.REALTIME_TYPE_XENOMAI` - Xenomai 3
* `hal.REALTIME_TYPE_XENOMAI_EVL` - Xenomai 4 aka Xenomai EVL

System information:

* `hal.is_kernelspace` - One (1) if RTAPI runs in the kernel, otherwise zero (0)
* `hal.is_userspace` - Inverted `hal.is_kernelspace`
* `hal.kernel_version` - A string specifying the real-time kernel version if `hal.is_kernelspace` is one.
Otherwise it specifies `"Not Available"`.
* `hal.is_rt` - One (1) if the system runs in real-time, otherwise zero (0)
* `hal.is_sim` - Inverted `hal.is_rt`

=== hal methods

hal.is_initialized()::
Returns a boolean to indicate whether hal is initialized. The hal is initialized when there is
at least one component. If this is not the case, many of the following functions will throw
a `RuntimeError` exception: `Cannot call before creating component`

.Example:
[source,python]
----
#The hal must be initialized to use many of the hal functions
#If it is not initialized, create a component which will initialize it
#The component will be cleaned up after comp goes out of scope
#Use pid as component identifier, so it is unlikely to collide
#with existing components
comp_name = f"halpy{os.getpid()}"
if not hal.is_initialized():
comp = hal.component(comp_name)

type = hal.get_realtime_type()
----

hal.get_realtime_type()::
Returns the type of the running realtime system.
Might return `hal.REALTIME_TYPE_UNINITIALIZED` if `rtapi_app` is not running.
See xref:_hal_constants[realtime type constants]. +
Throws a `RuntimeError` exception if HAL is not initialized.

hal.component_exists(_name_:string)::
Returns a boolean to indicate whether or not the specified component exist at this time.
Returns a boolean to indicate whether or not the specified component exist at this time. +
Throws a `RuntimeError` exception if HAL is not initialized.

hal.component_is_ready(_name_:string)::
Returns a boolean to indicate whether or not the specified component is in the ready state.
Also returns False if the component does not exist.
Also returns False if the component does not exist. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -86,7 +124,8 @@ See 'hal.set_msg_level()' and xref:_hal_constants[message constants] for list of
hal.new_sig(_name_:string, _type_:enum)::
Create a new signal (net) called _name_.
The signal can carry information of _type_ content.
Returns `True` on success.
Returns `True` on success. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -98,7 +137,8 @@ if not hal.new_sig("signalname", hal.HAL_BIT):
hal.connect(_pinname_:string, _signame_:string)::
Connect the pin _pinname_ to signal _signame_.
Both signal and pin must exist and both pin and signal must be of the same type.
Returns `True` on success.
Returns `True` on success. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -110,7 +150,8 @@ if not hal.connect("mycomp.pinname", "signalname"):
hal.disconnect(_pinname_:string, _signame_:string)::
Disconnect the pin _pinname_ from signal _signame_.
Both signal and pin must exist.
Returns `True` on success.
Returns `True` on success. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -121,7 +162,8 @@ if not hal.disconnect("mycomp.pinname"):

hal.pin_has_writer(_pinname_:string)::
Returns `True` if pin with name _pinname_ is attached to a signal and there is at least one writer.
Otherwise, `False` is returned.
Otherwise, `False` is returned. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -135,8 +177,9 @@ else:
hal.set_p(_name_:string, _value_:mixed)::
Sets the pin or param called _name_ to _value_.
The _name_ is the full name of the pin or param.
The search order is pin names first, then parameter names.
The search order is pin names first, then parameter names. +
Throws a `RuntimeError` exception if the _name_ is not found. +
Throws a `RuntimeError` exception if HAL is not initialized. +
The type of _value_ depends on the type of the pin or param.
Integer scalar types may use integers or a textual representation of an integer to set the value.
Floating point type may use both integer, floating point and textual representation thereof to set the value.
Expand All @@ -158,14 +201,16 @@ The same rules for _value_ apply to 'set_s()' as to 'set_p()'. +
+
The 'set_s()' method has one special case when the signal is of type `hal.HAL_PORT` and it is fully connected.
In that case, the call uses the _value_ to set the port's queue size and it must be a positive integer.
See below xref:_hal_port_pipes[on configuring a port].
See below xref:_hal_port_pipes[on configuring a port]. +
Throws a `RuntimeError` exception if HAL is not initialized.

hal.get_value(_name_:string)::
Returns the value of the pin, param or signal with _name_, searched in that order.
Boolean types return `True` or `False`.
Integer scalar types return an integer.
Floating point types return a float.
A `RuntimeError` exception is thrown if no pin, param or signal is found by that name.
A `RuntimeError` exception is thrown if no pin, param or signal is found by that name. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand All @@ -174,13 +219,16 @@ value = hal.get_value("iocontrol.0.emc-enable-in")
----

hal.get_info_pins()::
Returns a list of dictionary tuples as in `{"NAME":"pinname", "VALUE":<bool|int|float>, "TYPE":<int>, "DIRECTION":<int>}`.
Returns a list of dictionary tuples as in `{"NAME":"pinname", "VALUE":<bool|int|float>, "TYPE":<int>, "DIRECTION":<int>}`. +
Throws a `RuntimeError` exception if HAL is not initialized.

hal.get_info_params()::
Returns a list of dictionary tuples as in `{"NAME":"paramname", "VALUE":<bool|int|float>, "TYPE":<int>, "DIRECTION":<int>}`.
Returns a list of dictionary tuples as in `{"NAME":"paramname", "VALUE":<bool|int|float>, "TYPE":<int>, "DIRECTION":<int>}`. +
Throws a `RuntimeError` exception if HAL is not initialized.

hal.get_info_signals()::
Returns a list of dictionary tuples as in `{"NAME":"signame", "VALUE":<bool|int|float>, "TYPE":<int>, "DRIVER":"name"|None}`.
Returns a list of dictionary tuples as in `{"NAME":"signame", "VALUE":<bool|int|float>, "TYPE":<int>, "DRIVER":"name"|None}`. +
Throws a `RuntimeError` exception if HAL is not initialized.

.Example:
[source,python]
Expand Down
3 changes: 1 addition & 2 deletions docs/src/hal/halmodule.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,7 @@ Use these to specify details rather then the value they hold.
Read these to acquire information about the realtime system.

* `is_kernelspace`
* `is_rt`
* `is_sim`
* `is_userspace`
* `get_realtime_type()`

// vim: set syntax=asciidoc:
21 changes: 21 additions & 0 deletions docs/src/man/man3/hal_get_realtime_type.3.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
:manvolnum: 3

= hal_get_realtime_type(3)

== NAME

hal_get_realtime_type - Get the type of the running realtime

== SYNTAX

hal_realtime_type_t hal_get_realtime_type()

== RETURN VALUE

*hal_get_realtime_type* returns the type of the running realtime system or a HAL status code on failure.

For uspace, this returns *REALTIME_TYPE_UNINITIALIZED* if *rtapi_app* is not running. It is safe to assume
this never happens in realtime components. But userspace components can be loaded without *rtapi_app* being
started.

For rtai, this always returns *REALTIME_TYPE_RTAI*.
13 changes: 8 additions & 5 deletions docs/src/man/man3/rtapi_is.3.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ int rtapi_is_realtime();
and zero when they run in userspace (e.g., under uspace).

*rtapi_is_realtime()* returns nonzero when capable of running with realtime guarantees.

Always use *hal_get_realtime_type()* instead. *rtapi_is_realtime()* is only available in realtime context.

For rtai, this always returns nonzero (but actually loading realtime modules will fail if not running under the appropriate kernel).
For uspace, this returns nonzero when the running kernel indicates it is capable of realtime performance.
If *rtapi_app* is not setuid root,
this returns nonzero even though *rtapi_app* will not be able to obtain realtime scheduling or hardware access,
so e.g., attempting to *loadrt* a hardware driver will fail.
For uspace, this returns nonzero when the running kernel indicates it is capable of realtime performance and *rtapi_app* has the
required capabilities or is setuid root.
If *rtapi_app* is not setuid root or setcap with the proper capabilities,
this returns zero.

== REALTIME CONSIDERATIONS

May be called from non-realtime or from realtime setup code.
May only be called from realtime setup code.
*rtapi_is_realtime()* may perform filesystem I/O.

== RETURN VALUE
Expand Down
1 change: 1 addition & 0 deletions lib/python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
nf.py
realtime.py
gscreen
gmoccapy/
stepconf/
Expand Down
39 changes: 39 additions & 0 deletions lib/python/realtime.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Python wrapper of realtime script
# Copyright 2026 Hannes Diethelm <hannes.diethelm@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import os

REALTIME="@REALTIME@"

def verify():
#Checks if system is realtime compatible
cmd = REALTIME + " verify > /dev/null"
status = os.system(cmd)
exit_code = os.WEXITSTATUS(status)
if exit_code != 0 and exit_code != 1:
raise RuntimeError("cmd \"" + cmd + "\" failed with status " + str(status) + " exit_code " + str(exit_code))
return exit_code==0;

def status():
#Status: realtime core running (rtapi_app running or kernel modules active)
#Note: rtapi_app running does not mean realtime compatibility!
cmd = REALTIME + " status > /dev/null"
status = os.system(cmd)
exit_code = os.WEXITSTATUS(status)
if exit_code != 0 and exit_code != 1:
raise RuntimeError("cmd \"" + cmd + "\" failed with status " + str(status) + " exit_code " + str(exit_code))
return exit_code==0;
37 changes: 31 additions & 6 deletions scripts/realtime.in
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,11 @@ CheckStatus(){
case $RTPREFIX in
uspace)
if [ -z "$($PS -o stat= -o comm= -C rtapi_app | $GREP -v '^Z')" ]; then
exit 1
echo rtapi_app is not running
return 1
else
exit 0
echo rtapi_app is running
return 0
fi ;;
*)
# check loaded/unloaded status of modules
Expand All @@ -138,9 +140,9 @@ CheckStatus(){
fi
done
if [ -z "$NOTLOADED" ]; then
exit 0
return 0
else
exit 1
return 1
fi
esac
}
Expand Down Expand Up @@ -222,6 +224,25 @@ Unload(){
done
}

Verify(){
HAS_REALTIME=false
case $RTPREFIX in
uspace)
rtapi_app check_rt && HAS_REALTIME=true
;;
(*rtai*)
HAS_REALTIME=true
esac

if [ $HAS_REALTIME = true ]; then
echo "realtime detected"
return 0
else
echo "WARNING: no realtime detected"
return 1
fi
}

CheckUnloaded(){
# checks to see if all modules were unloaded
STATUS=
Expand Down Expand Up @@ -260,10 +281,14 @@ case "$CMD" in
;;
status)
CheckConfig
CheckStatus
CheckStatus || exit $?
;;
verify)
CheckConfig
Verify || exit $?
;;
*)
echo "Usage: $0 {start|load|stop|unload|restart|force-reload|status}" >&2
echo "Usage: $0 {start|load|stop|unload|restart|force-reload|status|verify}" >&2
exit 1
;;
esac
Expand Down
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ INFILES = \
../tcl/linuxcnc.tcl ../scripts/halrun ../scripts/rip-environment \
../scripts/linuxcncmkdesktop \
../lib/python/nf.py \
../lib/python/realtime.py \
../share/desktop-directories/linuxcnc-doc.directory \
../share/desktop-directories/linuxcnc-ref.directory \
../share/desktop-directories/linuxcnc-cnc.directory \
Expand Down
3 changes: 2 additions & 1 deletion src/emc/usr_intf/pncconf/pncconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from pncconf import private_data
import cairo
import hal
import realtime
#import mesatest
try:
LINUXCNCVERSION = os.environ['LINUXCNCVERSION']
Expand Down Expand Up @@ -652,7 +653,7 @@ def print_image(self,image_name):
# check for realtime kernel
def check_for_rt(self):
actual_kernel = os.uname()[2]
if hal.is_sim :
if not realtime.verify():
self.warning_dialog(self._p.MESS_NO_REALTIME,True)
if self.debugstate:
return True
Expand Down
6 changes: 3 additions & 3 deletions src/emc/usr_intf/stepconf/stepconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
import os
from optparse import Option, OptionParser
import hal
import realtime
import xml.dom.minidom
import hashlib
import math
import errno
import textwrap
import hal
import shutil
import time
from multifilebuilder import MultiFileBuilder
Expand Down Expand Up @@ -893,9 +893,9 @@ def dbg(self,str):
def check_for_rt(self):
is_realtime_capable = False
try:
if hal.is_sim:
if not realtime.verify():
self.warning_dialog(self._p.MESS_NO_REALTIME,True)
elif hal.is_rt:
else:
if hal.is_kernelspace:
actual_kernel = os.uname()[2]
if hal.kernel_version == actual_kernel:
Expand Down
5 changes: 5 additions & 0 deletions src/hal/hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ extern int hal_unready(int comp_id);
*/
extern char* hal_comp_name(int comp_id);

/** hal_get_realtime_type() returns the type of the running real time
*/
typedef rtapi_realtime_type_t hal_realtime_type_t;
extern hal_realtime_type_t hal_get_realtime_type(void);

/** The HAL maintains lists of variables, functions, and so on in
a central database, located in shared memory so all components
can access it. To prevent contention, functions that may
Expand Down
Loading
Loading