From feecd06fab9129c339903b6c826a091feb2f13df Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Tue, 5 May 2026 19:17:20 +0800 Subject: [PATCH 1/3] hal: add pushmsg component for RT messages with HAL value substitution pushmsg accepts a list of `level|pinname|message` entries via the `msgs` modparam and creates one `pushmsg.` HAL trigger pin per entry. On the rising edge the corresponding text is emitted via rtapi_print_msg() at the configured level (e/w/i/d). Message text may include `` placeholders. Each placeholder exposes a typed input pin `pushmsg..` that the user nets to the source signal. Type is inferred from the format spec (`%d`/`%i` => s32, `%u`/`%x` => u32, `%f`/`%g`/`%e` => float, `%b` => bit). The cycle function only dereferences the component's own pins; no HAL search, no locking, no kernel string parsing in RT. HAL state and admin state live in separate structures: pin pointer storage is one hal_malloc block sized to the parsed entry count, edge state and parsed literals stay in module memory. A master `pushmsg.enable` pin gates emission for runtime suppression. Maximum 64 entries per loadrt, 8 references per message. --- src/hal/components/pushmsg.comp | 383 ++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 src/hal/components/pushmsg.comp diff --git a/src/hal/components/pushmsg.comp b/src/hal/components/pushmsg.comp new file mode 100644 index 00000000000..9379addd6b6 --- /dev/null +++ b/src/hal/components/pushmsg.comp @@ -0,0 +1,383 @@ +component pushmsg "Push configurable RT messages to non-RT logging on rising edges"; + +description """ +Each entry in the `msgs` modparam describes a level, pin name, and message +text using `level|pinname|message` syntax. Level is one letter: +`e` (error), `w` (warning), `i` (info), `d` (debug). On the rising edge of +`pushmsg.` the corresponding text is emitted via `rtapi_print_msg()` +at the chosen level. + +Pin names use HAL-allowed characters (alphanumeric, dot, dash, underscore). +Maximum 64 entries per loadrt, 2 substitutions per message, 128 chars per +message. + +Message text may include `{t:refname}` placeholders. Each one creates a +typed input pin `pushmsg.` that the user wires to the source +signal. Type letter selects the pin type: +`b` => bit, `s` => s32, `u` => u32, `l` => s64, `k` => u64, `f` => float. +Use `\\{` to embed a literal curly brace. + +Example: +[source,hal] +---- +loadrt pushmsg msgs="e|nooil|No oil pressure","w|low|Oil {f:level} L low" +addf pushmsg servo-thread +net no-oil classicladder.0.out-21 pushmsg.nooil +net oil-level oil.level pushmsg.level +---- + +Example INI expansion: +[source,ini] +---- +[PUSHMSG] +# Note: no surrounding whitespace +MSGS=\\ +"e|nooil|No oil pressure",\\ +"w|low|Oil {f:level} L low" +---- +[source,hal] +---- +loadrt pushmsg msgs=[PUSHMSG]MSGS +---- +"""; + +pin in bit enable = TRUE "Enable message generation"; + +modparam dummy msgs "Comma separated list of custom messages in format t|pin|message"; + +option period no; +option extra_setup yes; +option extra_cleanup yes; +option singleton yes; + +function _; +license "GPL"; // v2+ +author "LinuxCNC"; + +include ; +include ; +include ; + +;; + +#define MSG_LEN_MAX 128 // Max size of a message at input and expanded +#define MSG_N_MAX 64 // Max number of messages supported +#define MSG_SUBST_MAX 2 // Max number of substitutions within one message + +// Command-line config parameter +static char *msgs[MSG_N_MAX] = {}; +RTAPI_MP_ARRAY_STRING(msgs, MSG_N_MAX, "Message slots in 't|pin|message' format"); + +// The HAL structure lives in HAL memory space +typedef struct { + hal_bit_t *trigger; + hal_data_u *substs[MSG_SUBST_MAX]; +} msg_hal_t; + +// The admin structure lives in normal memory space +typedef struct { + bool prev; // Previous pin state (to detect rising edge) + int msgtype; // Rtapi message type (err, warn, info, debug) + char msgbuf[MSG_LEN_MAX]; // Complete message with embedded NULs for substitution + int msglen; // Length so we may find the end easily (stpcpy is not available) + hal_type_t substtype[MSG_SUBST_MAX]; // Substitution types (hal type enum) + const char *parts[MSG_SUBST_MAX]; // Text parts index msgbuf for after substitution +} msg_slot_t; + +// +// The message structure is decomposed from the source: +// msgs[n] = "e|trigpin|Message {s:pin1} othertext {u:pin2} trailing" +// +// The source is copied into the slots->msgbuf and all pin refs are isolated: +// msgbuf = "Message \0s:pin1\0 othertext \0u:pin2\0 trailing" +// ^ ^ +// parts[0] parts[1] +// The slots->parts[] array is always the trailing part after the substitution. +// The pins for the substitution variable are referenced in the pins->substs[] +// array. +// + +static msg_hal_t *pins; // Trigger and substitution pins +static msg_slot_t *slots; // Admin data for each message +static int nslots; // Number of active messages + +static inline char printable(char c) +{ + return isprint(c & 0xff) ? c : '?'; +} + +static int message_type(char c) +{ + switch (c) { + case 'e': case 'E': return RTAPI_MSG_ERR; + case 'w': case 'W': return RTAPI_MSG_WARN; + case 'i': case 'I': return RTAPI_MSG_INFO; + case 'd': case 'D': return RTAPI_MSG_DBG; + } + return -1; +} + +static int check_pinname(const char *name) +{ + for (; *name; name++) { + if (!isascii(*name & 0xff) || (!isalnum(*name & 0xff) && NULL == strchr("._-", *name & 0xff))) + return -EINVAL; + } + return 0; +} + +static int setup_message(const char *pfx, int idx, const char *msg, msg_slot_t *slot, msg_hal_t *pin) +{ + int pfxlen = strlen(pfx); + int l = strlen(msg); + if (l >= MSG_LEN_MAX) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d too long, more than %d characters\n", pfx, idx, MSG_LEN_MAX-1); + return -EMSGSIZE; + } + // Absolute minimum is "t|p|" + if (l < 4) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d too short, less than 4 characters\n", pfx, idx); + return -EMSGSIZE; + } + // Get the message type + if ((slot->msgtype = message_type(msg[0])) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d has invalid type '%c', expected one of {e,w,i,d}\n", + pfx, idx, printable(msg[0])); + return -EBADMSG; + } + // Message type must be followed by a '|' + if ('|' != msg[1]) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing first '|' separator\n", pfx, idx); + return -EBADMSG; + } + // Find the second '|' in "t|name|message" + const char *pinend = strchr(msg+2, '|'); + if (!pinend) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing pin name, no second '|' separator\n", pfx, idx); + return -EBADMSG; + } + // Make sure we can encompass the full name + int pinnamelen = pinend - msg - 2; + if (pinnamelen + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name too long\n", pfx, idx); + return -EBADMSG; + } + + int rv; + // Copy the pinname for later pin creation use + char pinname[HAL_NAME_LEN+1] = {}; // The init ensures termination + memcpy(pinname, &msg[2], pinnamelen); + if ((rv = check_pinname(pinname)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name contains bad characters\n", pfx, idx); + return rv; + } + // Create the trigger input pin + if ((rv = hal_pin_bit_newf(HAL_IN, &pin->trigger, comp_id, "%s.%s", pfx, pinname)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create pin (duplicate?)\n", pfx, idx); + return rv; + } + + strcpy(slot->msgbuf, msg + pinnamelen + 3); // Copy message so we may take it apart + + // Now see if there are any expansions in the string. + int subst = 0; // Count number of substitutions + for (char *cptr = slot->msgbuf; *cptr; cptr++) { + // Check "\{..." escaped curly open brace + if ('\\' == *cptr) { + // The next char is escaped and cannot start a substitution + if (cptr[1]) // Or this is a backslash at the end + cptr++; + continue; + } + if ('{' == *cptr) { + // Need "{t:pinname}" + *cptr = 0; // Terminate the previous string "foo {t:pin} bar" --> "foo " + cptr++; // Move to subst internals + char *end = strchr(cptr, '}'); + if (!end) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing '}' in substitution\n", pfx, idx); + return -EBADMSG; + } + slot->parts[subst] = end + 1; // This is the continuation position "t:pin} bar" --> " bar" + *end = 0; // Terminate the pinname "t:pin} bar" --> "t:pin" + + // Min content is 3 chars: type + ':' + 1-char-name. end-cptr counts those bytes. + if (end - cptr < 3) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution too small, need type and pinname\n", pfx, idx); + return -EBADMSG; + } + if (':' != cptr[1]) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution missing ':' after type character\n", pfx, idx); + return -EBADMSG; + } + + char tchar = *cptr; + cptr += 2; // Move to pinname + if ((end - cptr) + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution pin name too long\n", pfx, idx); + return -EBADMSG; + } + if ((rv = check_pinname(cptr)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution pin name contains bad characters\n", pfx, idx); + return rv; + } + + // Create the correct pin based on the type letter + // Unfortunately, we cannot determine whether a pin has a duplicate + // name when pin creation fails. The return value is not specific + // enough. We'd have to track all pin names ourselves if we wanted + // to merge pin substitution references into one pool. + switch (tchar) { + case 'b': case 'B': + slot->substtype[subst] = HAL_BIT; + if ((rv = hal_pin_bit_newf(HAL_IN, (hal_bit_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + case 's': case 'S': + slot->substtype[subst] = HAL_S32; + if ((rv = hal_pin_s32_newf(HAL_IN, (hal_s32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + case 'u': case 'U': + slot->substtype[subst] = HAL_U32; + if ((rv = hal_pin_u32_newf(HAL_IN, (hal_u32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + case 'l': case 'L': + slot->substtype[subst] = HAL_S64; + if ((rv = hal_pin_s64_newf(HAL_IN, (hal_s64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + case 'k': case 'K': + slot->substtype[subst] = HAL_U64; + if ((rv = hal_pin_u64_newf(HAL_IN, (hal_u64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + case 'f': case 'F': + slot->substtype[subst] = HAL_FLOAT; + if ((rv = hal_pin_float_newf(HAL_IN, (hal_float_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + return rv; + break; + default: + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d bad substitution type '%c', expected one of {b,f,s,u,k,l}\n", + pfx, idx, printable(tchar)); + return -EBADMSG; + } + + // Do not perform more substitutions than allowed + subst++; + if (subst >= MSG_SUBST_MAX) + break; + + cptr = end; // Move to end of substitution (loop will increment to next char) + } + } + + // Finally set the first part's length for quick concat in print_slot() + slot->msglen = strlen(slot->msgbuf); + return 0; +} + +EXTRA_SETUP() +{ + (void)__comp_inst; + (void)extra_arg; + + // See how many messages are set + for (nslots = 0; nslots < MSG_N_MAX && msgs[nslots]; nslots++) { + // Just counting. Drops out at the first NULL (unset msgs) + rtapi_print_msg(RTAPI_MSG_DBG, "%s: msgs[%d]=%s\n", prefix, nslots, msgs[nslots]); + } + + if (nslots < 1) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: No messages to handle\n", prefix); + return -EINVAL; + } + + // Get memory for the HAL interface + pins = (msg_hal_t *)hal_malloc(nslots * sizeof(pins[0])); + if (!pins) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: No HAL memory for pins\n", prefix); + return -ENOMEM; + } + memset(pins, 0, nslots * sizeof(pins[0])); // Ensure zeros because we check NULLs + + // Get memory for the message slots + slots = (msg_slot_t *)rtapi_kzalloc(nslots * sizeof(slots[0]), RTAPI_GFP_KERNEL); + if (!slots) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: No kernel memory for slots\n", prefix); + return -ENOMEM; + } + + // Interpret every message format and allocate pin resources + for (int i = 0; i < nslots; i++) { + int rv = setup_message(prefix, i, msgs[i], &slots[i], &pins[i]); + if (rv < 0) { + rtapi_kfree(slots); + slots = NULL; + return rv; + } + } + return 0; +} + +EXTRA_CLEANUP() +{ + if (slots) + rtapi_kfree(slots); +} + +static void print_slot(int s) +{ + char buf[MSG_LEN_MAX]; + char *end = strcpy(buf, slots[s].msgbuf) + slots[s].msglen; + for (int i = 0; i < MSG_SUBST_MAX && pins[s].substs[i]; i++) { + int left = sizeof(buf) - (end - buf); + int n; + if (left <= 1) // Need room for terminator + break; + switch (slots[s].substtype[i]) { + case HAL_BIT: + n = rtapi_snprintf(end, left, "%d%s", (int)!!pins[s].substs[i]->b, slots[s].parts[i]); + break; + case HAL_S32: + n = rtapi_snprintf(end, left, "%d%s", (int)pins[s].substs[i]->s, slots[s].parts[i]); + break; + case HAL_U32: + n = rtapi_snprintf(end, left, "%u%s", (unsigned)pins[s].substs[i]->u, slots[s].parts[i]); + break; + case HAL_S64: + n = rtapi_snprintf(end, left, "%ld%s", (long)pins[s].substs[i]->ls, slots[s].parts[i]); + break; + case HAL_U64: + n = rtapi_snprintf(end, left, "%lu%s", (unsigned long)pins[s].substs[i]->lu, slots[s].parts[i]); + break; + case HAL_FLOAT: + n = rtapi_snprintf(end, left, "%lf%s", (double)pins[s].substs[i]->f, slots[s].parts[i]); + break; + default: + n = 0; + break; + } + if (n < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "pushmsg: Printing slot %d failed rtapi_snprintf() call\n", s); + return; + } + end += n; + } + rtapi_print_msg(slots[s].msgtype, "%s\n", buf); +} + +// Servo-thread cycle function +FUNCTION(_) +{ + bool en = enable; // Cache enable pin + // For each slot, test the trigger and save the trigger state + for (int i = 0; i < nslots; i++) { + bool trig = *(pins[i].trigger); + if (en && trig && !slots[i].prev) { + print_slot(i); // Rising edge -> print message + } + slots[i].prev = trig; + } +} From 2cb816abc8009966073292565927090361932aa7 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 6 May 2026 21:29:56 +0800 Subject: [PATCH 2/3] hal: pushmsg add force, edge and pin-name hierarchy Apply BsAtHome's diff: each entry now exposes a sub-tree `pushmsg..{trigger,force,edge}` and substitution pins live under the same sub-tree as `pushmsg..`. `force` mirrors message.comp: a FALSE->TRUE transition re-emits while trigger is at the active level. `edge` selects polarity (FALSE default = rising-edge of trigger). Type-letter list reformatted as a bulleted asciidoc list per BsAtHome. --- src/hal/components/pushmsg.comp | 83 ++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/hal/components/pushmsg.comp b/src/hal/components/pushmsg.comp index 9379addd6b6..1cff1c94945 100644 --- a/src/hal/components/pushmsg.comp +++ b/src/hal/components/pushmsg.comp @@ -3,18 +3,31 @@ component pushmsg "Push configurable RT messages to non-RT logging on rising edg description """ Each entry in the `msgs` modparam describes a level, pin name, and message text using `level|pinname|message` syntax. Level is one letter: -`e` (error), `w` (warning), `i` (info), `d` (debug). On the rising edge of -`pushmsg.` the corresponding text is emitted via `rtapi_print_msg()` -at the chosen level. +`e` (error), `w` (warning), `i` (info), `d` (debug). Each entry creates a +HAL pin sub-tree `pushmsg..{trigger,force,edge}` and emits the +corresponding text via `rtapi_print_msg()` at the chosen level. + +`pushmsg..trigger` (in bit): emits on transition to `edge` value. +`pushmsg..force` (in bit): a FALSE->TRUE transition re-emits while +trigger is at the active level. +`pushmsg..edge` (in bit): selects edge polarity. The default value +of FALSE makes a FALSE->TRUE trigger transition emit (rising-edge mode). Pin names use HAL-allowed characters (alphanumeric, dot, dash, underscore). Maximum 64 entries per loadrt, 2 substitutions per message, 128 chars per message. Message text may include `{t:refname}` placeholders. Each one creates a -typed input pin `pushmsg.` that the user wires to the source -signal. Type letter selects the pin type: -`b` => bit, `s` => s32, `u` => u32, `l` => s64, `k` => u64, `f` => float. +typed input pin `pushmsg..` that the user wires to the +source signal. The type letter selects the pin type: + +* `b` - bit +* `s` - s32 +* `u` - u32 +* `l` - s64 +* `k` - u64 +* `f` - float + Use `\\{` to embed a literal curly brace. Example: @@ -22,8 +35,8 @@ Example: ---- loadrt pushmsg msgs="e|nooil|No oil pressure","w|low|Oil {f:level} L low" addf pushmsg servo-thread -net no-oil classicladder.0.out-21 pushmsg.nooil -net oil-level oil.level pushmsg.level +net no-oil classicladder.0.out-21 pushmsg.nooil.trigger +net oil-level oil.level pushmsg.low.level ---- Example INI expansion: @@ -71,12 +84,15 @@ RTAPI_MP_ARRAY_STRING(msgs, MSG_N_MAX, "Message slots in 't|pin|message' format" // The HAL structure lives in HAL memory space typedef struct { hal_bit_t *trigger; + hal_bit_t *force; + hal_bit_t *edge; hal_data_u *substs[MSG_SUBST_MAX]; } msg_hal_t; // The admin structure lives in normal memory space typedef struct { - bool prev; // Previous pin state (to detect rising edge) + bool prevtrig; // Previous trigger pin state (to detect rising edge) + bool prevforce; // Previous force pin state (to detect rising edge) int msgtype; // Rtapi message type (err, warn, info, debug) char msgbuf[MSG_LEN_MAX]; // Complete message with embedded NULs for substitution int msglen; // Length so we may find the end easily (stpcpy is not available) @@ -158,22 +174,35 @@ static int setup_message(const char *pfx, int idx, const char *msg, msg_slot_t * } // Make sure we can encompass the full name int pinnamelen = pinend - msg - 2; - if (pinnamelen + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character + if (pinnamelen + pfxlen + 9 >= HAL_NAME_LEN) { // +9 for the two extra '.' characters and 'trigger' word (longest) rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name too long\n", pfx, idx); return -EBADMSG; } int rv; - // Copy the pinname for later pin creation use + // Copy the pin name for later pin creation use. + // It already includes the prefix for our convenience. char pinname[HAL_NAME_LEN+1] = {}; // The init ensures termination - memcpy(pinname, &msg[2], pinnamelen); - if ((rv = check_pinname(pinname)) < 0) { + strcpy(pinname, pfx); // "pushmsg" + pinname[pfxlen] = '.'; // "pushmsg." + memcpy(&pinname[pfxlen+1], &msg[2], pinnamelen); + // Only checks the actual pin name, not the prefix + if ((rv = check_pinname(&pinname[pfxlen+2])) < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name contains bad characters\n", pfx, idx); return rv; } - // Create the trigger input pin - if ((rv = hal_pin_bit_newf(HAL_IN, &pin->trigger, comp_id, "%s.%s", pfx, pinname)) < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create pin (duplicate?)\n", pfx, idx); + + // Create the trigger, force and edge input pins + if ((rv = hal_pin_bit_newf(HAL_IN, &pin->trigger, comp_id, "%s.trigger", pinname)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create trigger pin (duplicate?)\n", pfx, idx); + return rv; + } + if ((rv = hal_pin_bit_newf(HAL_IN, &pin->force, comp_id, "%s.force", pinname)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create force pin (duplicate?)\n", pfx, idx); + return rv; + } + if ((rv = hal_pin_bit_newf(HAL_IN, &pin->edge, comp_id, "%s.edge", pinname)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create edge pin (duplicate?)\n", pfx, idx); return rv; } @@ -213,7 +242,7 @@ static int setup_message(const char *pfx, int idx, const char *msg, msg_slot_t * char tchar = *cptr; cptr += 2; // Move to pinname - if ((end - cptr) + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character + if ((end - cptr) + pfxlen + pinnamelen + 2 >= HAL_NAME_LEN) { // +2 for the two extra '.' characters rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution pin name too long\n", pfx, idx); return -EBADMSG; } @@ -230,32 +259,32 @@ static int setup_message(const char *pfx, int idx, const char *msg, msg_slot_t * switch (tchar) { case 'b': case 'B': slot->substtype[subst] = HAL_BIT; - if ((rv = hal_pin_bit_newf(HAL_IN, (hal_bit_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_bit_newf(HAL_IN, (hal_bit_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; case 's': case 'S': slot->substtype[subst] = HAL_S32; - if ((rv = hal_pin_s32_newf(HAL_IN, (hal_s32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_s32_newf(HAL_IN, (hal_s32_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; case 'u': case 'U': slot->substtype[subst] = HAL_U32; - if ((rv = hal_pin_u32_newf(HAL_IN, (hal_u32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_u32_newf(HAL_IN, (hal_u32_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; case 'l': case 'L': slot->substtype[subst] = HAL_S64; - if ((rv = hal_pin_s64_newf(HAL_IN, (hal_s64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_s64_newf(HAL_IN, (hal_s64_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; case 'k': case 'K': slot->substtype[subst] = HAL_U64; - if ((rv = hal_pin_u64_newf(HAL_IN, (hal_u64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_u64_newf(HAL_IN, (hal_u64_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; case 'f': case 'F': slot->substtype[subst] = HAL_FLOAT; - if ((rv = hal_pin_float_newf(HAL_IN, (hal_float_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0) + if ((rv = hal_pin_float_newf(HAL_IN, (hal_float_t **)&pin->substs[subst], comp_id, "%s.%s", pinname, cptr)) < 0) return rv; break; default: @@ -374,10 +403,12 @@ FUNCTION(_) bool en = enable; // Cache enable pin // For each slot, test the trigger and save the trigger state for (int i = 0; i < nslots; i++) { - bool trig = *(pins[i].trigger); - if (en && trig && !slots[i].prev) { + bool trig = (int)!!*(pins[i].trigger) ^ (int)!!*(pins[i].edge); + bool force = *(pins[i].force); + if (en && ((trig && !slots[i].prevtrig) || (force && !slots[i].prevforce))) { print_slot(i); // Rising edge -> print message } - slots[i].prev = trig; + slots[i].prevtrig = trig; + slots[i].prevforce = force; } } From cbbaf3aea4ba6d635fd2fb690ae3776d9c0a3dd3 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 6 May 2026 21:35:16 +0800 Subject: [PATCH 3/3] hal: deprecate message.comp in favour of pushmsg Add a deprecation notice to the message.comp NAME and description. pushmsg covers the same trigger/force/edge surface, plus all four rtapi_print_msg() levels and HAL value substitution. --- src/hal/components/message.comp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hal/components/message.comp b/src/hal/components/message.comp index 1301a6014de..54e46736be5 100644 --- a/src/hal/components/message.comp +++ b/src/hal/components/message.comp @@ -35,9 +35,15 @@ * *************************************************************************/ -component message "Display a message"; - -description """Allows HAL pins to trigger a message. Example hal commands: +component message "Display a message (deprecated, see pushmsg)"; + +description """*DEPRECATED*: this component only emits at the ERROR level. +For new configurations, use *pushmsg*(9) which supports all four +`rtapi_print_msg()` levels (error, warning, info, debug), HAL value +substitution into the message text, plus the same trigger/force/edge +behaviour from a single `loadrt` line. + +Allows HAL pins to trigger a message. Example hal commands: [source,hal] ----