hal: add pushmsg component for RT message generation#4003
hal: add pushmsg component for RT message generation#4003grandixximo wants to merge 3 commits intoLinuxCNC:masterfrom
Conversation
|
How does this compare with the existing "message" component? https://linuxcnc.org/docs/stable/html/man/man9/message.9.html |
The message component only allows you to send error messages. You cannot use it for debug, warning or informational messages. |
|
@ BsAtHome While we are at it, what about, .msg or similar file, INI style and lift the 16 max limit? Or is this too complex? |
6553f80 to
a87a084
Compare
|
Reading files in components is not necessary here. It should suffice to enter them as arguments in the loadrt. The problem I have is the loop as such (I wrote exactly the same thing before deciding it was wrong). I think it is wasteful to loop over all possible triggers that are all ever so unlikely until you actually trigger. Similarly, having a single trigger and a setup pin to select the message makes it more difficult to select which message you want to emit. Neither solution are enough KISS in my eyes. At least, I'm not convinced either solution is the right one. |
|
I don't think no loop is structurally possible if input is N HAL bit signals... |
I find that keeping track of which message is which on a single long string of loadrt is quite a bother, that is why I was proposing a file, where everything is forcibly formatted to be readable and properly indexed, knowing what message 7 of 15 is at a glance is convenient when you are debugging, rather than counting the commas in a long string. |
|
That is a valid argument. However, you then need to parse a text file in the kernel... |
|
Fair, no kernel parsing needed, if what we want is just a better messaging tool for integrators. I was thinking of rescoping to a userspace daemon (loadusr): reads config file, creates HAL pins, polls at 20 Hz, emits via rtapi_print_msg (all 4 levels) plus NML for GUI ERR. RT untouched, file remains readable, <halpin.x> substitution available. What do you think? |
Well, that would make this whole discussion redundant. The point of the original hack was to test rtapi_print_msg() propagation from within a component in RT space. Making a userspace daemon is kinda like a "problem solved, patient dead" solution. I am looking for the actual use-case where you want several messages at several levels. The "old" component |
|
Fair, uspace message is another PR, not rescoping this one, |
|
Well, answer my question: What is the use-case? There must be a clear reason why this is more than just a debug tool. And then, why use a loop over all message types? Wouldn't it be better to use two args, one with strings and the other with message level? This complicates pins, but reduces cpu cycles. I have this cost/effort/benefit picture in my mind that does not want to fit. |
|
Use-case: integrator commissioning + field diagnostics. Real examples I hit:
The
A 4-level RT push closes that gap. NML is GUI-only; rtapi_print_msg reaches dmesg/log too, which matters for headless/embedded retrofits. On design: agreed, drop the per-level loop. Two parallel modparams, Bonus: HAL pin value substitution via Example: Want me to redraft |
|
Maybe it would be better to have a string of key-value pairs in the loadrt line? |
|
I think that the "message" component is used quite often on real-world configs. Whether is should be used in a sample config is an interesting question. |
|
And we can make it easier for people by using an INI expansion like: And then: [PUSHMSG]
# Note: no surrounding whitespace
MESSAGES=\
"w|oil|Low oil",\
"e|nooil|No oil",\
"i|ping|Tool said hi" |
a87a084 to
294fb90
Compare
ff3e7fb to
dcf0c95
Compare
|
I've been hacking a bit too (with my LBM† running on sugar and oxygen) and came up with this: † Large Brain Model ;-) component pushmsg "Push a message to non-RT";
pin in bit enable "Enable message generation";
description """
bla bla bla
"""
;
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 <rtapi_ctype.h>;
include <rtapi_gfp.h>;
include <rtapi_slab.h>;
;;
#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
const 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 pin name too long\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"
if(end - cptr - 1 < 3) { // Note: end points one char beyond (at a NUL)
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;
}
cptr += 2; // Move to pinname
// Note: end points to the trailing char
if((end - cptr - 1) + 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+2)) < 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(*cptr) {
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, expected one of {b,f,s,u,k,l}\n", pfx, idx);
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) - (buf - end);
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;
}
}
// vim: ts=4 sw=4 syn=c |
f15505e to
66da6a3
Compare
|
Your LBM beats my LLM 😅 |
|
BTW, the mighty off-by-one gift keeps on giving. |
pushmsg accepts a list of `level|pinname|message` entries via the `msgs` modparam and creates one `pushmsg.<pinname>` 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 `<refname:%fmt>` placeholders. Each placeholder exposes a typed input pin `pushmsg.<pinname>.<refname>` 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.
66da6a3 to
feecd06
Compare
|
Should we add a deprecation message to the documentation for the "message" component? |
That may be appropriate. I think we need to have a look at some of the extra pins and trigger modes provided in the "message" component. I don't think they need to be forward ported, but it doesn't hurt to look another time. |
|
Just had a quick look at message. If we want to add trigger-edge selection and force-trigger, then we need to change the pin names to something like: If we decide that it would be appropriate to add this, then it may also be appropriate to add the substitution pins in the sub-tree like: |
|
This diff (wrt current PR) should enable, for each pin, edge selection, force generation and isolate all pins into a pin name hierarchy. Please test it. --- hal/components/pushmsg.comp.orig 2026-05-06 15:16:05.145630367 +0200
+++ hal/components/pushmsg.comp 2026-05-06 15:15:07.194801100 +0200
@@ -71,12 +71,15 @@
// 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 +161,35 @@
}
// 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 +229,7 @@
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 +246,32 @@
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 +390,12 @@
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;
}
} |
Apply BsAtHome's diff: each entry now exposes a sub-tree
`pushmsg.<pinname>.{trigger,force,edge}` and substitution pins live
under the same sub-tree as `pushmsg.<pinname>.<refname>`.
`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.
|
Logic test passes: Test 1 (rising edge=0, 0→1): fires val=1 ✓ |
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.
Deprecation message pointing at pushmsg added |
KISS RT-to-non-RT message generator, replaces the dirty test-rig hack used in the #3993 latency comparison.
Each rising edge on
pushmsg.{error,warning,info,debug}-Nemits the corresponding string from the load-time{e,w,i,d}msgarrays throughrtapi_print_msg(). 16 slots per level. Slots without a configured message are inert. Singleton instance.Example:
cc @BsAtHome who proposed this in #3993.
Draft for discussion. Open to flexibility tweaks (personality bits for per-level counts, multi-instance, etc.).