From 7751186d9afaecb3c0af0c311c6355dd1e022841 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:02:16 +1100 Subject: [PATCH 1/4] rtapi: end every message that lacks the newline Every rtapi_print_msg() message is a line of its own: the uspace default handlers, in the main thread of rtapi_app, in the queue that prints the messages of its realtime threads and in user processes, add the newline when the text lacks one, at every level. rtapi_print() still prints like printf. A message that already ends in a newline does not change, so the existing formats and their translations stay as they are, and new code may leave the newline out. tests/rtapi-msg-newline prints errors, a warning and an info message without a newline from the main thread of rtapi_app and from a realtime thread, an error with its newline, and an rtapi_print() line in two parts. Fixes #4689. --- src/rtapi/uspace_common.h | 9 ++++++++ src/rtapi/uspace_rtapi_main.cc | 16 +++++++++++++- src/rtapi/uspace_ulapi.c | 16 +++++++------- tests/rtapi-msg-newline/.gitignore | 1 + tests/rtapi-msg-newline/README | 10 +++++++++ tests/rtapi-msg-newline/expected | 7 +++++++ tests/rtapi-msg-newline/halrun.err | 14 +++++++++++++ tests/rtapi-msg-newline/msgline.comp | 31 ++++++++++++++++++++++++++++ tests/rtapi-msg-newline/msgline.hal | 6 ++++++ tests/rtapi-msg-newline/print.py | 15 ++++++++++++++ tests/rtapi-msg-newline/skip | 4 ++++ tests/rtapi-msg-newline/test.sh | 7 +++++++ 12 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 tests/rtapi-msg-newline/.gitignore create mode 100644 tests/rtapi-msg-newline/README create mode 100644 tests/rtapi-msg-newline/expected create mode 100644 tests/rtapi-msg-newline/halrun.err create mode 100644 tests/rtapi-msg-newline/msgline.comp create mode 100644 tests/rtapi-msg-newline/msgline.hal create mode 100755 tests/rtapi-msg-newline/print.py create mode 100755 tests/rtapi-msg-newline/skip create mode 100755 tests/rtapi-msg-newline/test.sh diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 4032e7eb2fa..c3f22399b00 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -230,6 +230,15 @@ int rtapi_shmem_delete(int handle, int module_id) void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap); +/* Every rtapi_print_msg() message is one line, and the default handlers + end one that lacks the newline. rtapi_print() (RTAPI_MSG_ALL) prints + like printf. */ +static inline int rtapi_msg_needs_newline(msg_level_t level, const char *msg) +{ + size_t n = strlen(msg); + return level != RTAPI_MSG_ALL && n > 0 && msg[n - 1] != '\n'; +} + static rtapi_msg_handler_t rtapi_msg_handler = default_rtapi_msg_handler; rtapi_msg_handler_t rtapi_get_msg_handler(void) { diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 9946f3d16d1..337bce75d90 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -1708,9 +1708,23 @@ void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { message_t m; m.level = level; vsnprintf(m.msg, sizeof(m.msg), fmt, ap); + if (rtapi_msg_needs_newline(level, m.msg)) { + size_t n = strlen(m.msg); + if (n == sizeof(m.msg) - 1) + n--; /* truncated: the newline replaces the last character */ + m.msg[n] = '\n'; + m.msg[n + 1] = '\0'; + } rtapi_msg_queue.push(m); } else { - vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + FILE *f = level == RTAPI_MSG_ALL ? stdout : stderr; + char *msg; + if (vasprintf(&msg, fmt, ap) < 0) + return; + fputs(msg, f); + if (rtapi_msg_needs_newline(level, msg)) + fputc('\n', f); + free(msg); } } diff --git a/src/rtapi/uspace_ulapi.c b/src/rtapi/uspace_ulapi.c index 0bec4b70257..65509783d3b 100644 --- a/src/rtapi/uspace_ulapi.c +++ b/src/rtapi/uspace_ulapi.c @@ -75,11 +75,13 @@ void rtapi_delay(long ns) { } void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { - if(level == RTAPI_MSG_ALL) { - vfprintf(stdout, fmt, ap); - fflush(stdout); - } else { - vfprintf(stderr, fmt, ap); - fflush(stderr); - } + FILE *f = level == RTAPI_MSG_ALL ? stdout : stderr; + char *msg; + if (vasprintf(&msg, fmt, ap) < 0) + return; + fputs(msg, f); + if (rtapi_msg_needs_newline(level, msg)) + fputc('\n', f); + fflush(f); + free(msg); } diff --git a/tests/rtapi-msg-newline/.gitignore b/tests/rtapi-msg-newline/.gitignore new file mode 100644 index 00000000000..284bd9713dd --- /dev/null +++ b/tests/rtapi-msg-newline/.gitignore @@ -0,0 +1 @@ +halrun.out diff --git a/tests/rtapi-msg-newline/README b/tests/rtapi-msg-newline/README new file mode 100644 index 00000000000..d79375e4caf --- /dev/null +++ b/tests/rtapi-msg-newline/README @@ -0,0 +1,10 @@ +Every rtapi_print_msg() message is a line of its own, also when its +text lacks the newline (#4689). + +msgline prints two errors without a newline from its setup, which runs +in the main thread of rtapi_app, and an error, a warning and an info +message without a newline from its function, whose messages rtapi_app +queues and prints from another thread. Each must be a line of its own +in the output, and an error that ends in a newline must not get a +second one. rtapi_print() prints like printf: a line printed with it +in two parts must stay one line. diff --git a/tests/rtapi-msg-newline/expected b/tests/rtapi-msg-newline/expected new file mode 100644 index 00000000000..fd4e5f72093 --- /dev/null +++ b/tests/rtapi-msg-newline/expected @@ -0,0 +1,7 @@ +msgline: error at load +msgline: error at load +msgline: error in the thread +msgline: warning in the thread +msgline: info in the thread +msgline: error with its newline +msgline: print in parts diff --git a/tests/rtapi-msg-newline/halrun.err b/tests/rtapi-msg-newline/halrun.err new file mode 100644 index 00000000000..ac064432a8d --- /dev/null +++ b/tests/rtapi-msg-newline/halrun.err @@ -0,0 +1,14 @@ +Note: realtime scheduling unavailable (sched_setscheduler SCHED_FIFO: Operation not permitted). + Process capabilities: cap_sys_nice=no cap_ipc_lock=no. + Falling back to POSIX non-realtime. + Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' on rtapi_app. + Override (testing only): set LINUXCNC_FORCE_REALTIME=1. +Note: Using POSIX non-realtime +No isolated CPU's found, expect some latency or set RTAPI_CPU_NUMBER to select CPU +msgline: error at load +msgline: error at load +msgline: error in the thread +msgline: warning in the thread +msgline: info in the thread +msgline: error with its newline +THREADS: hal_exit 7 0 0 diff --git a/tests/rtapi-msg-newline/msgline.comp b/tests/rtapi-msg-newline/msgline.comp new file mode 100644 index 00000000000..976bf82dd60 --- /dev/null +++ b/tests/rtapi-msg-newline/msgline.comp @@ -0,0 +1,31 @@ +component msgline "Test component for tests/rtapi-msg-newline: prints messages that lack a newline"; + +pin io bool print "set to print the messages from the realtime thread once; cleared by the function"; + +function _; +option extra_setup; +option period no; +license "GPL"; +author "Luca Toniolo"; +;; +EXTRA_SETUP() { + (void)__comp_inst; + (void)prefix; + (void)extra_arg; + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error at load"); + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error at load"); + return 0; +} + +FUNCTION(_) { + if (!print) + return; + print_set(0); + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error in the thread"); + rtapi_print_msg(RTAPI_MSG_WARN, "msgline: warning in the thread"); + rtapi_print_msg(RTAPI_MSG_INFO, "msgline: info in the thread"); + rtapi_print_msg(RTAPI_MSG_ERR, "msgline: error with its newline\n"); + /* rtapi_print() prints like printf */ + rtapi_print("msgline: print "); + rtapi_print("in parts\n"); +} diff --git a/tests/rtapi-msg-newline/msgline.hal b/tests/rtapi-msg-newline/msgline.hal new file mode 100644 index 00000000000..b985977d858 --- /dev/null +++ b/tests/rtapi-msg-newline/msgline.hal @@ -0,0 +1,6 @@ +loadrt threads name1=t period1=1000000 +loadrt msgline +addf msgline.0 t +debug 3 +start +loadusr -w ./print.py diff --git a/tests/rtapi-msg-newline/print.py b/tests/rtapi-msg-newline/print.py new file mode 100755 index 00000000000..3cd8457a458 --- /dev/null +++ b/tests/rtapi-msg-newline/print.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Make msgline print from the realtime thread and wait until it has. +# A wait that takes more than 10 s of wall time means the machine is +# dying; the test then aborts. + +import hal +import sys +import time + +hal.set_p("msgline.0.print", "1") +deadline = time.monotonic() + 10 +while hal.get_p("msgline.0.print"): + if time.monotonic() > deadline: + sys.exit("FAIL: the thread did not run msgline.0 in 10 s") + time.sleep(0.01) diff --git a/tests/rtapi-msg-newline/skip b/tests/rtapi-msg-newline/skip new file mode 100755 index 00000000000..14f65ebbd51 --- /dev/null +++ b/tests/rtapi-msg-newline/skip @@ -0,0 +1,4 @@ +#!/bin/sh +# Builds a realtime component with halcompile, which needs the build +# tree. Skip when testing installed packages. +[ -z "$SYSTEM_BUILD" ] diff --git a/tests/rtapi-msg-newline/test.sh b/tests/rtapi-msg-newline/test.sh new file mode 100755 index 00000000000..ba8d80bd391 --- /dev/null +++ b/tests/rtapi-msg-newline/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +halcompile --install msgline.comp >/dev/null +timeout -k 5 60 halrun -f msgline.hal > halrun.out 2> halrun.err +# the messages of msgline, each on a line of its own, then rtapi_print() +grep '^msgline:' halrun.err +grep '^msgline:' halrun.out From d10eea027296b9888708bd3f63c05f4b616d20c9 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:35:08 +1100 Subject: [PATCH 2/4] print each line with one rtapi_print_msg() call Now that every message is a line, the places that built one line from several calls print it with one: motion's command debug output, rtapi_app's isolated cpuset report, the PPMC slot scan, halcmd's command echo and the hm2_modbus frame dumps. Hannes Diethelm listed most of them on #4697. --- src/emc/motion/command.c | 76 +++++++--------------- src/hal/drivers/hal_ppmc.c | 9 ++- src/hal/drivers/mesa-hostmot2/hm2_modbus.c | 25 +++++-- src/hal/utils/halcmd.c | 7 +- src/rtapi/uspace_rtapi_app.cc | 7 +- 5 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/emc/motion/command.c b/src/emc/motion/command.c index b8094e03e9b..0ba29b8c4fd 100644 --- a/src/emc/motion/command.c +++ b/src/emc/motion/command.c @@ -664,8 +664,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) Called by task using [KINS]JOINTS= which is typically the same value as the motmod num_joints= parameter */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_NUM_JOINTS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->joint); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_NUM_JOINTS %d", emcmotCommand->joint); if (( emcmotCommand->joint <= 0 ) || ( emcmotCommand->joint > EMCMOT_MAX_JOINTS )) { break; @@ -679,9 +678,8 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) the number of spindles configured for the motion module (motion_num_spindles) */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_NUM_SPINDLES"); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_NUM_SPINDLES %d", emcmotCommand->spindle); emcmot_config_change(); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->spindle); if ( emcmotCommand->spindle > motion_num_spindles || emcmotCommand->spindle <= 0 || emcmotCommand->spindle > EMCMOT_MAX_SPINDLES @@ -706,8 +704,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) break; case EMCMOT_SET_JOINT_HOMING_PARAMS: - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_HOMING_PARAMS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_HOMING_PARAMS %d", joint_num); emcmot_config_change(); if (joint == 0) { break; @@ -727,8 +724,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) break; case EMCMOT_UPDATE_JOINT_HOMING_PARAMS: - rtapi_print_msg(RTAPI_MSG_DBG, "UPDATE_JOINT_HOMING_PARAMS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "UPDATE_JOINT_HOMING_PARAMS %d", joint_num); emcmot_config_change(); if (joint == 0) { break; @@ -744,8 +740,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) /* this command can be issued with joint < 0 to re-enable limits, but they are automatically re-enabled at the end of the next jog */ - rtapi_print_msg(RTAPI_MSG_DBG, "OVERRIDE_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "OVERRIDE_LIMITS %d", joint_num); if (joint_num < 0) { /* don't override limits */ rtapi_print_msg(RTAPI_MSG_DBG, "override off"); @@ -775,8 +770,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) break; case EMCMOT_SET_JOINT_MOTOR_OFFSET: - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MOTOR_OFFSET"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MOTOR_OFFSET %d", joint_num); if(joint == 0) { break; } @@ -786,8 +780,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_JOINT_POSITION_LIMITS: /* set the position limits for the joint */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_POSITION_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_POSITION_LIMITS %d", joint_num); emcmot_config_change(); if (joint == 0) { break; @@ -799,8 +792,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_JOINT_BACKLASH: /* set the backlash for the joint */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_BACKLASH"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_BACKLASH %d", joint_num); emcmot_config_change(); if (joint == 0) { break; @@ -815,8 +807,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) minFerror then OK else if ferror < limiting ferror then OK else ERROR */ case EMCMOT_SET_JOINT_MAX_FERROR: - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MAX_FERROR"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MAX_FERROR %d", joint_num); emcmot_config_change(); if (joint == 0 || emcmotCommand->maxFerror < 0.0) { break; @@ -825,8 +816,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) break; case EMCMOT_SET_JOINT_MIN_FERROR: - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MIN_FERROR"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_MIN_FERROR %d", joint_num); emcmot_config_change(); if (joint == 0 || emcmotCommand->minFerror < 0.0) { break; @@ -838,8 +828,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) /* do a continuous jog, implemented as an incremental jog to the limit. When the user lets go of the button an abort will stop the jog. */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOG_CONT"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOG_CONT %d", joint_num); if (!GET_MOTION_ENABLE_FLAG()) { reportError(_("Can't jog joint when not enabled.")); SET_JOINT_ERROR_FLAG(joint, 1); @@ -907,8 +896,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_JOG_INCR: /* do an incremental jog */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOG_INCR"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOG_INCR %d", joint_num); if (!GET_MOTION_ENABLE_FLAG()) { reportError(_("Can't jog joint when not enabled.")); SET_JOINT_ERROR_FLAG(joint, 1); @@ -984,8 +972,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_JOG_ABS: /* do an absolute jog */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOG_ABS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOG_ABS %d", joint_num); if (joint == 0) { break; } @@ -1208,8 +1195,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_JOINT_VEL_LIMIT: /* set joint max velocity */ /* can do it at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_VEL_LIMIT"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_VEL_LIMIT %d", joint_num); emcmot_config_change(); /* check joint range */ if (joint == 0) { @@ -1221,8 +1207,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_JOINT_ACC_LIMIT: /* set joint max acceleration */ /* can do it at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_ACC_LIMIT"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_ACC_LIMIT %d", joint_num); emcmot_config_change(); /* check joint range */ if (joint == 0) { @@ -1234,8 +1219,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_JOINT_JERK_LIMIT: /* set joint max jerk */ /* can do it at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_JERK_LIMIT"); - rtapi_print_msg(RTAPI_MSG_DBG, " j(%d) jerk(%f)", joint_num, emcmotCommand->jerk); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_JOINT_JERK_LIMIT j(%d) jerk(%f)", joint_num, emcmotCommand->jerk); emcmot_config_change(); if (joint == 0) { break; @@ -1483,8 +1467,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) /* make joint active, so that amps will be enabled when system is enabled or disabled */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_ACTIVATE"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_ACTIVATE %d", joint_num); if (joint == 0) { break; } @@ -1495,8 +1478,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) /* make joint inactive, so that amps won't be affected when system is enabled or disabled */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_DEACTIVATE"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_DEACTIVATE %d", joint_num); if (joint == 0) { break; } @@ -1508,8 +1490,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) /* need to be in free mode, enable on */ /* this just sets the initial state, then the state machine in homing.c does the rest */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_HOME"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_HOME %d", joint_num); /* Normally homing requires free (joint) mode. The queued G28.2 path * (emctaskmain EMC_TASK_EXEC::WAITING_FOR_HOMING) dips motion into @@ -1550,8 +1531,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_JOINT_UNHOME: /* unhome the specified joint, or all joints if -1, or volatile joints if -2 */ - rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_UNHOME"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num); + rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_UNHOME %d", joint_num); if ( (emcmotStatus->motion_state != EMCMOT_MOTION_FREE) && (emcmotStatus->motion_state != EMCMOT_MOTION_DISABLED)) { @@ -2043,8 +2023,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_AXIS_POSITION_LIMITS: /* set the position limits for axis */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_POSITION_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->axis); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_POSITION_LIMITS %d", emcmotCommand->axis); emcmot_config_change(); if ((emcmotCommand->axis < 0) || (emcmotCommand->axis >= EMCMOT_MAX_AXIS)) { break; @@ -2056,8 +2035,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_AXIS_VEL_LIMIT: /* set the max axis vel */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_VEL_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->axis); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_VEL_LIMITS %d", emcmotCommand->axis); emcmot_config_change(); if ((emcmotCommand->axis < 0) || (emcmotCommand->axis >= EMCMOT_MAX_AXIS)) { break; @@ -2069,8 +2047,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_AXIS_ACC_LIMIT: /* set the max axis acc */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_ACC_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->axis); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_ACC_LIMITS %d", emcmotCommand->axis); emcmot_config_change(); if ((emcmotCommand->axis < 0) || (emcmotCommand->axis >= EMCMOT_MAX_AXIS)) { break; @@ -2082,8 +2059,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) case EMCMOT_SET_AXIS_JERK_LIMIT: /* set the max axis jerk */ /* can be done at any time */ - rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_JERK_LIMITS"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d jerk(%f)", emcmotCommand->axis, emcmotCommand->jerk); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_JERK_LIMITS %d jerk(%f)", emcmotCommand->axis, emcmotCommand->jerk); emcmot_config_change(); if ((emcmotCommand->axis < 0) || (emcmotCommand->axis >= EMCMOT_MAX_AXIS)) { break; @@ -2092,8 +2068,7 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) break; case EMCMOT_SET_AXIS_LOCKING_JOINT: - rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_ACC_LOCKING_JOINT"); - rtapi_print_msg(RTAPI_MSG_DBG, " %d", emcmotCommand->axis); + rtapi_print_msg(RTAPI_MSG_DBG, "SET_AXIS_ACC_LOCKING_JOINT %d", emcmotCommand->axis); emcmot_config_change(); if ((emcmotCommand->axis < 0) || (emcmotCommand->axis >= EMCMOT_MAX_AXIS)) { break; @@ -2139,7 +2114,6 @@ void emcmotCommandHandler_locked(void *arg, long servo_period) rtapi_print_msg(RTAPI_MSG_DBG, "ERROR: %d", emcmotStatus->commandStatus); } - rtapi_print_msg(RTAPI_MSG_DBG, "\n"); /* synch tail count */ rtapi_seqlock_write_end(&emcmotStatus->tail, &emcmotStatus->head); rtapi_seqlock_write_end(&emcmotConfig->tail, &emcmotConfig->head); diff --git a/src/hal/drivers/hal_ppmc.c b/src/hal/drivers/hal_ppmc.c index 83e65900f2e..9e8a553198b 100644 --- a/src/hal/drivers/hal_ppmc.c +++ b/src/hal/drivers/hal_ppmc.c @@ -566,8 +566,7 @@ int rtapi_app_main(void) slot = &(bus->slot_data[slotnum]); /* rv1 is used to flag errors that fail one bus */ rv1 = 0; - rtapi_print_msg(RTAPI_MSG_INFO, "PPMC: slot %d: ", slotnum); - + /* check slot */ idcode = SelRead(slot->slot_base+SLOT_ID_OFFSET, slot->port_addr); if ((idcode == 0)||(idcode == 0xFF)||((idcode&0x0f) == 0x0f)) { @@ -575,13 +574,13 @@ int rtapi_app_main(void) // being left on the bus by a non-implemented device slot slot->id = 0; slot->ver = 0; - rtapi_print_msg(RTAPI_MSG_INFO, "nothing detected at addr %x reads %x\n", - slotnum,idcode); + rtapi_print_msg(RTAPI_MSG_INFO, "PPMC: slot %d: nothing detected at addr %x reads %x\n", + slotnum,slotnum,idcode); ClrTimeout(slot->port_addr); /* skip to next slot */ continue; } - rtapi_print_msg(RTAPI_MSG_INFO, "ID code: %02X ", idcode); + rtapi_print_msg(RTAPI_MSG_INFO, "PPMC: slot %d: ID code: %02X\n", slotnum, idcode); slot->id = id = idcode & 0xF0; slot->ver = ver = idcode & 0x0F; slot->use_timestamp = 0; /* default is no timestamp */ diff --git a/src/hal/drivers/mesa-hostmot2/hm2_modbus.c b/src/hal/drivers/mesa-hostmot2/hm2_modbus.c index 01b982cc5e9..a562a3818a2 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_modbus.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_modbus.c @@ -151,6 +151,20 @@ static inline unsigned mtypesize(unsigned mtype) { #define MSG_WARN(fmt...) do { rtapi_print_msg(RTAPI_MSG_WARN, fmt); } while(0) #define MSG_PRINT(fmt...) do { rtapi_print(fmt); } while(0) +#ifdef DEBUG +// Debug dump of a frame, one message for every 16 bytes +static void dump_frame(const char *what, unsigned cmdidx, const rtapi_u8 *data, unsigned len) +{ + for(unsigned i = 0; i < len; i += 16) { + char hex[16 * 3 + 1] = ""; + unsigned n = 0; + for(unsigned j = i; j < len && j < i + 16; j++) + n += rtapi_snprintf(hex + n, sizeof(hex) - n, " %02x", data[j]); + MSG_DBG("%s cmd=%u [%u]:%s\n", what, cmdidx, i, hex); + } +} +#endif + // State-machine states enum { STATE_START = 0, @@ -495,9 +509,8 @@ static int send_modbus_pkt(hm2_modbus_inst_t *inst) cc->data[cc->datalen++] = (checksum >> 8) & 0xff; #ifdef DEBUG - MSG_DBG("Sending cmd=%u to '%s' %i bytes", inst->cmdidx, inst->uart, cc->datalen); - for(int i = 0; i < cc->datalen; i++) MSG_DBG(" 0x%02x", cc->data[i]); - MSG_DBG("\n"); + MSG_DBG("Sending cmd=%u to '%s' %i bytes\n", inst->cmdidx, inst->uart, cc->datalen); + dump_frame("Sending", inst->cmdidx, cc->data, cc->datalen); #endif rtapi_u8 frames = 1; @@ -1719,13 +1732,13 @@ static int parse_data_frame(hm2_modbus_inst_t *inst) return -1; } - MSG_DBG("Return PDU cmd=%u is", inst->cmdidx); for(unsigned i = 0; i < rxcount; i++) { bytes[i] = (data[w] >> b) & 0xFF; - MSG_DBG(" %02x", bytes[i]); if((b += 8) >= 32) { b = 0; w++; } } - MSG_DBG("\n"); +#ifdef DEBUG + dump_frame("Return PDU", inst->cmdidx, bytes, rxcount); +#endif if(bytes[0] != cc->cmd.mbid) { MSG_ERR("%s: error: Modbus device address mismatch: got 0x%02x, expected 0x%02x\n", inst->name, bytes[0], cc->cmd.mbid); diff --git a/src/hal/utils/halcmd.c b/src/hal/utils/halcmd.c index 542cb19b127..694801c8d0a 100644 --- a/src/hal/utils/halcmd.c +++ b/src/hal/utils/halcmd.c @@ -212,14 +212,17 @@ pid_t hal_systemv_nowait(const char *const argv[]) { if ( pid == 0 ) { /* child process */ /* print debugging info if "very verbose" (-V) */ + char cmdline[1024] = ""; for(n=0; argv[n] != NULL; n++) { - rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[n] ); + if (n) + strncat(cmdline, " ", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, argv[n], sizeof(cmdline) - strlen(cmdline) - 1); } if (n == 0) { halcmd_error("hal_systemv_nowait: empty argv array passed in\n"); exit(1); } - rtapi_print_msg(RTAPI_MSG_DBG, "\n" ); + rtapi_print_msg(RTAPI_MSG_DBG, "%s\n", cmdline); /* call execv() to invoke command */ execvp(argv[0], (char * const *)argv); /* should never get here */ diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 2b4bcb0f9a6..93e3e2014b8 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -22,6 +22,7 @@ #include #include #include +#include std::atomic_int WithRoot::level; uid_t WithRoot::ruid, WithRoot::euid; @@ -243,13 +244,13 @@ int RtapiApp::find_rt_cpu_number() { parse_cpu_list(isolated_file, &cpuset); //Print list - rtapi_print_msg(RTAPI_MSG_INFO, "cpuset isolated "); + std::string list; for (int i = 0; i < CPU_SETSIZE; i++) { if (CPU_ISSET(i, &cpuset)) { - rtapi_print_msg(RTAPI_MSG_INFO, "%i ", i); + list += " " + std::to_string(i); } } - rtapi_print_msg(RTAPI_MSG_INFO, "\n"); + rtapi_print_msg(RTAPI_MSG_INFO, "cpuset isolated%s", list.c_str()); int top = -1; for (int i = 0; i < CPU_SETSIZE; i++) { From 0440f1c380da449d3d879f8ec41dc4f9ea3b8a7f Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:35:08 +1100 Subject: [PATCH 3/4] task, motion: one newline at the end of an error An operator error is one line, and the GUIs and task's echo end it, so task drops the newline at the end of the text; motion's errors that end in one showed a blank line in every GUI. reportError() likewise adds its newline for the console only when the text lacks one. --- src/emc/motion/motion.c | 3 +++ src/emc/task/emctaskmain.cc | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/emc/motion/motion.c b/src/emc/motion/motion.c index 9370b8803be..4a096507d64 100644 --- a/src/emc/motion/motion.c +++ b/src/emc/motion/motion.c @@ -224,6 +224,9 @@ void reportError(const char *fmt, ...) char fmt_tmp[EMCMOT_ERROR_LEN]; size_t fmt_len = strlen(fmt); //Manually check string length and truncate if it is to long + if(fmt_len > 0 && fmt[fmt_len-1] == '\n'){ + fmt_len--; + } if(fmt_len < EMCMOT_ERROR_LEN-1){ memcpy(fmt_tmp, fmt, fmt_len); fmt_tmp[fmt_len+0] = '\n'; diff --git a/src/emc/task/emctaskmain.cc b/src/emc/task/emctaskmain.cc index 72afc74b7bb..049c640f1af 100644 --- a/src/emc/task/emctaskmain.cc +++ b/src/emc/task/emctaskmain.cc @@ -222,6 +222,11 @@ static int emcOperatorErrorV(bool echo, const char *fmt, va_list ap) // force a NULL at the end for safety error_msg.error[LINELEN - 1] = 0; + // an error is one line: the GUI and the echo below end it + size_t n = strlen(error_msg.error); + while (n > 0 && error_msg.error[n - 1] == '\n') + error_msg.error[--n] = 0; + // write it if (echo) { log_info("{}\n", error_msg.error); From de21b0b0f01c6863f6c136d06f905700e22ae64a Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:26:40 +1100 Subject: [PATCH 4/4] rtapi: RTAI ends every message that lacks the newline too The RTAI kernel handler and rtapi_print_msg() in RTAI user space follow the same rule as uspace: every rtapi_print_msg() message is a line, and rtapi_print() prints like printf. printk() shows a message without the newline only when the next one comes. The kernel handler now gives rt_printk() the text as an argument instead of as the format. Hannes Diethelm pointed out both on #4697. --- src/rtapi/rtai_rtapi.c | 13 +++++++++++-- src/rtapi/rtai_ulapi.c | 11 ++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/rtapi/rtai_rtapi.c b/src/rtapi/rtai_rtapi.c index 3eb1bae6101..b019fda81f0 100644 --- a/src/rtapi/rtai_rtapi.c +++ b/src/rtapi/rtai_rtapi.c @@ -433,10 +433,19 @@ int rtapi_snprintf(char *buf, unsigned long int size, const char *fmt, ...) #define BUFFERLEN 1024 +/* Every rtapi_print_msg() message is one line: end one that lacks the + newline. rtapi_print() (RTAPI_MSG_ALL) prints like printf. */ void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { char buf[BUFFERLEN]; - rtapi_vsnprintf(buf, BUFFERLEN, fmt, ap); - rt_printk(buf); + size_t n; + /* leave room for the newline */ + rtapi_vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + n = strlen(buf); + if (level != RTAPI_MSG_ALL && n > 0 && buf[n - 1] != '\n') { + buf[n] = '\n'; + buf[n + 1] = '\0'; + } + rt_printk("%s", buf); } static rtapi_msg_handler_t rtapi_msg_handler = default_rtapi_msg_handler; diff --git a/src/rtapi/rtai_ulapi.c b/src/rtapi/rtai_ulapi.c index cc73ead56b2..47f2eccbc5f 100644 --- a/src/rtapi/rtai_ulapi.c +++ b/src/rtapi/rtai_ulapi.c @@ -269,9 +269,18 @@ void rtapi_print_msg(msg_level_t level, const char *fmt, ...) va_list args; if ((level <= msg_level) && (msg_level != RTAPI_MSG_NONE)) { + /* one message is one line: end one that lacks the newline */ + char *msg; + int n; va_start(args, fmt); - vfprintf(stderr, fmt, args); + n = vasprintf(&msg, fmt, args); va_end(args); + if (n < 0) + return; + fputs(msg, stderr); + if (level != RTAPI_MSG_ALL && n > 0 && msg[n - 1] != '\n') + fputc('\n', stderr); + free(msg); } }