Skip to content

Commit feecd06

Browse files
committed
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.<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.
1 parent 0dc912a commit feecd06

1 file changed

Lines changed: 383 additions & 0 deletions

File tree

src/hal/components/pushmsg.comp

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
component pushmsg "Push configurable RT messages to non-RT logging on rising edges";
2+
3+
description """
4+
Each entry in the `msgs` modparam describes a level, pin name, and message
5+
text using `level|pinname|message` syntax. Level is one letter:
6+
`e` (error), `w` (warning), `i` (info), `d` (debug). On the rising edge of
7+
`pushmsg.<pinname>` the corresponding text is emitted via `rtapi_print_msg()`
8+
at the chosen level.
9+
10+
Pin names use HAL-allowed characters (alphanumeric, dot, dash, underscore).
11+
Maximum 64 entries per loadrt, 2 substitutions per message, 128 chars per
12+
message.
13+
14+
Message text may include `{t:refname}` placeholders. Each one creates a
15+
typed input pin `pushmsg.<refname>` that the user wires to the source
16+
signal. Type letter selects the pin type:
17+
`b` => bit, `s` => s32, `u` => u32, `l` => s64, `k` => u64, `f` => float.
18+
Use `\\{` to embed a literal curly brace.
19+
20+
Example:
21+
[source,hal]
22+
----
23+
loadrt pushmsg msgs="e|nooil|No oil pressure","w|low|Oil {f:level} L low"
24+
addf pushmsg servo-thread
25+
net no-oil classicladder.0.out-21 pushmsg.nooil
26+
net oil-level oil.level pushmsg.level
27+
----
28+
29+
Example INI expansion:
30+
[source,ini]
31+
----
32+
[PUSHMSG]
33+
# Note: no surrounding whitespace
34+
MSGS=\\
35+
"e|nooil|No oil pressure",\\
36+
"w|low|Oil {f:level} L low"
37+
----
38+
[source,hal]
39+
----
40+
loadrt pushmsg msgs=[PUSHMSG]MSGS
41+
----
42+
""";
43+
44+
pin in bit enable = TRUE "Enable message generation";
45+
46+
modparam dummy msgs "Comma separated list of custom messages in format t|pin|message";
47+
48+
option period no;
49+
option extra_setup yes;
50+
option extra_cleanup yes;
51+
option singleton yes;
52+
53+
function _;
54+
license "GPL"; // v2+
55+
author "LinuxCNC";
56+
57+
include <rtapi_ctype.h>;
58+
include <rtapi_gfp.h>;
59+
include <rtapi_slab.h>;
60+
61+
;;
62+
63+
#define MSG_LEN_MAX 128 // Max size of a message at input and expanded
64+
#define MSG_N_MAX 64 // Max number of messages supported
65+
#define MSG_SUBST_MAX 2 // Max number of substitutions within one message
66+
67+
// Command-line config parameter
68+
static char *msgs[MSG_N_MAX] = {};
69+
RTAPI_MP_ARRAY_STRING(msgs, MSG_N_MAX, "Message slots in 't|pin|message' format");
70+
71+
// The HAL structure lives in HAL memory space
72+
typedef struct {
73+
hal_bit_t *trigger;
74+
hal_data_u *substs[MSG_SUBST_MAX];
75+
} msg_hal_t;
76+
77+
// The admin structure lives in normal memory space
78+
typedef struct {
79+
bool prev; // Previous pin state (to detect rising edge)
80+
int msgtype; // Rtapi message type (err, warn, info, debug)
81+
char msgbuf[MSG_LEN_MAX]; // Complete message with embedded NULs for substitution
82+
int msglen; // Length so we may find the end easily (stpcpy is not available)
83+
hal_type_t substtype[MSG_SUBST_MAX]; // Substitution types (hal type enum)
84+
const char *parts[MSG_SUBST_MAX]; // Text parts index msgbuf for after substitution
85+
} msg_slot_t;
86+
87+
//
88+
// The message structure is decomposed from the source:
89+
// msgs[n] = "e|trigpin|Message {s:pin1} othertext {u:pin2} trailing"
90+
//
91+
// The source is copied into the slots->msgbuf and all pin refs are isolated:
92+
// msgbuf = "Message \0s:pin1\0 othertext \0u:pin2\0 trailing"
93+
// ^ ^
94+
// parts[0] parts[1]
95+
// The slots->parts[] array is always the trailing part after the substitution.
96+
// The pins for the substitution variable are referenced in the pins->substs[]
97+
// array.
98+
//
99+
100+
static msg_hal_t *pins; // Trigger and substitution pins
101+
static msg_slot_t *slots; // Admin data for each message
102+
static int nslots; // Number of active messages
103+
104+
static inline char printable(char c)
105+
{
106+
return isprint(c & 0xff) ? c : '?';
107+
}
108+
109+
static int message_type(char c)
110+
{
111+
switch (c) {
112+
case 'e': case 'E': return RTAPI_MSG_ERR;
113+
case 'w': case 'W': return RTAPI_MSG_WARN;
114+
case 'i': case 'I': return RTAPI_MSG_INFO;
115+
case 'd': case 'D': return RTAPI_MSG_DBG;
116+
}
117+
return -1;
118+
}
119+
120+
static int check_pinname(const char *name)
121+
{
122+
for (; *name; name++) {
123+
if (!isascii(*name & 0xff) || (!isalnum(*name & 0xff) && NULL == strchr("._-", *name & 0xff)))
124+
return -EINVAL;
125+
}
126+
return 0;
127+
}
128+
129+
static int setup_message(const char *pfx, int idx, const char *msg, msg_slot_t *slot, msg_hal_t *pin)
130+
{
131+
int pfxlen = strlen(pfx);
132+
int l = strlen(msg);
133+
if (l >= MSG_LEN_MAX) {
134+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d too long, more than %d characters\n", pfx, idx, MSG_LEN_MAX-1);
135+
return -EMSGSIZE;
136+
}
137+
// Absolute minimum is "t|p|"
138+
if (l < 4) {
139+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d too short, less than 4 characters\n", pfx, idx);
140+
return -EMSGSIZE;
141+
}
142+
// Get the message type
143+
if ((slot->msgtype = message_type(msg[0])) < 0) {
144+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d has invalid type '%c', expected one of {e,w,i,d}\n",
145+
pfx, idx, printable(msg[0]));
146+
return -EBADMSG;
147+
}
148+
// Message type must be followed by a '|'
149+
if ('|' != msg[1]) {
150+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing first '|' separator\n", pfx, idx);
151+
return -EBADMSG;
152+
}
153+
// Find the second '|' in "t|name|message"
154+
const char *pinend = strchr(msg+2, '|');
155+
if (!pinend) {
156+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing pin name, no second '|' separator\n", pfx, idx);
157+
return -EBADMSG;
158+
}
159+
// Make sure we can encompass the full name
160+
int pinnamelen = pinend - msg - 2;
161+
if (pinnamelen + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character
162+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name too long\n", pfx, idx);
163+
return -EBADMSG;
164+
}
165+
166+
int rv;
167+
// Copy the pinname for later pin creation use
168+
char pinname[HAL_NAME_LEN+1] = {}; // The init ensures termination
169+
memcpy(pinname, &msg[2], pinnamelen);
170+
if ((rv = check_pinname(pinname)) < 0) {
171+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d pin name contains bad characters\n", pfx, idx);
172+
return rv;
173+
}
174+
// Create the trigger input pin
175+
if ((rv = hal_pin_bit_newf(HAL_IN, &pin->trigger, comp_id, "%s.%s", pfx, pinname)) < 0) {
176+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d cannot create pin (duplicate?)\n", pfx, idx);
177+
return rv;
178+
}
179+
180+
strcpy(slot->msgbuf, msg + pinnamelen + 3); // Copy message so we may take it apart
181+
182+
// Now see if there are any expansions in the string.
183+
int subst = 0; // Count number of substitutions
184+
for (char *cptr = slot->msgbuf; *cptr; cptr++) {
185+
// Check "\{..." escaped curly open brace
186+
if ('\\' == *cptr) {
187+
// The next char is escaped and cannot start a substitution
188+
if (cptr[1]) // Or this is a backslash at the end
189+
cptr++;
190+
continue;
191+
}
192+
if ('{' == *cptr) {
193+
// Need "{t:pinname}"
194+
*cptr = 0; // Terminate the previous string "foo {t:pin} bar" --> "foo "
195+
cptr++; // Move to subst internals
196+
char *end = strchr(cptr, '}');
197+
if (!end) {
198+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d missing '}' in substitution\n", pfx, idx);
199+
return -EBADMSG;
200+
}
201+
slot->parts[subst] = end + 1; // This is the continuation position "t:pin} bar" --> " bar"
202+
*end = 0; // Terminate the pinname "t:pin} bar" --> "t:pin"
203+
204+
// Min content is 3 chars: type + ':' + 1-char-name. end-cptr counts those bytes.
205+
if (end - cptr < 3) {
206+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution too small, need type and pinname\n", pfx, idx);
207+
return -EBADMSG;
208+
}
209+
if (':' != cptr[1]) {
210+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution missing ':' after type character\n", pfx, idx);
211+
return -EBADMSG;
212+
}
213+
214+
char tchar = *cptr;
215+
cptr += 2; // Move to pinname
216+
if ((end - cptr) + pfxlen + 1 >= HAL_NAME_LEN) { // +1 for the extra '.' character
217+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution pin name too long\n", pfx, idx);
218+
return -EBADMSG;
219+
}
220+
if ((rv = check_pinname(cptr)) < 0) {
221+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d substitution pin name contains bad characters\n", pfx, idx);
222+
return rv;
223+
}
224+
225+
// Create the correct pin based on the type letter
226+
// Unfortunately, we cannot determine whether a pin has a duplicate
227+
// name when pin creation fails. The return value is not specific
228+
// enough. We'd have to track all pin names ourselves if we wanted
229+
// to merge pin substitution references into one pool.
230+
switch (tchar) {
231+
case 'b': case 'B':
232+
slot->substtype[subst] = HAL_BIT;
233+
if ((rv = hal_pin_bit_newf(HAL_IN, (hal_bit_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
234+
return rv;
235+
break;
236+
case 's': case 'S':
237+
slot->substtype[subst] = HAL_S32;
238+
if ((rv = hal_pin_s32_newf(HAL_IN, (hal_s32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
239+
return rv;
240+
break;
241+
case 'u': case 'U':
242+
slot->substtype[subst] = HAL_U32;
243+
if ((rv = hal_pin_u32_newf(HAL_IN, (hal_u32_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
244+
return rv;
245+
break;
246+
case 'l': case 'L':
247+
slot->substtype[subst] = HAL_S64;
248+
if ((rv = hal_pin_s64_newf(HAL_IN, (hal_s64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
249+
return rv;
250+
break;
251+
case 'k': case 'K':
252+
slot->substtype[subst] = HAL_U64;
253+
if ((rv = hal_pin_u64_newf(HAL_IN, (hal_u64_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
254+
return rv;
255+
break;
256+
case 'f': case 'F':
257+
slot->substtype[subst] = HAL_FLOAT;
258+
if ((rv = hal_pin_float_newf(HAL_IN, (hal_float_t **)&pin->substs[subst], comp_id, "%s.%s", pfx, cptr)) < 0)
259+
return rv;
260+
break;
261+
default:
262+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: Message %d bad substitution type '%c', expected one of {b,f,s,u,k,l}\n",
263+
pfx, idx, printable(tchar));
264+
return -EBADMSG;
265+
}
266+
267+
// Do not perform more substitutions than allowed
268+
subst++;
269+
if (subst >= MSG_SUBST_MAX)
270+
break;
271+
272+
cptr = end; // Move to end of substitution (loop will increment to next char)
273+
}
274+
}
275+
276+
// Finally set the first part's length for quick concat in print_slot()
277+
slot->msglen = strlen(slot->msgbuf);
278+
return 0;
279+
}
280+
281+
EXTRA_SETUP()
282+
{
283+
(void)__comp_inst;
284+
(void)extra_arg;
285+
286+
// See how many messages are set
287+
for (nslots = 0; nslots < MSG_N_MAX && msgs[nslots]; nslots++) {
288+
// Just counting. Drops out at the first NULL (unset msgs)
289+
rtapi_print_msg(RTAPI_MSG_DBG, "%s: msgs[%d]=%s\n", prefix, nslots, msgs[nslots]);
290+
}
291+
292+
if (nslots < 1) {
293+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: No messages to handle\n", prefix);
294+
return -EINVAL;
295+
}
296+
297+
// Get memory for the HAL interface
298+
pins = (msg_hal_t *)hal_malloc(nslots * sizeof(pins[0]));
299+
if (!pins) {
300+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: No HAL memory for pins\n", prefix);
301+
return -ENOMEM;
302+
}
303+
memset(pins, 0, nslots * sizeof(pins[0])); // Ensure zeros because we check NULLs
304+
305+
// Get memory for the message slots
306+
slots = (msg_slot_t *)rtapi_kzalloc(nslots * sizeof(slots[0]), RTAPI_GFP_KERNEL);
307+
if (!slots) {
308+
rtapi_print_msg(RTAPI_MSG_ERR, "%s: No kernel memory for slots\n", prefix);
309+
return -ENOMEM;
310+
}
311+
312+
// Interpret every message format and allocate pin resources
313+
for (int i = 0; i < nslots; i++) {
314+
int rv = setup_message(prefix, i, msgs[i], &slots[i], &pins[i]);
315+
if (rv < 0) {
316+
rtapi_kfree(slots);
317+
slots = NULL;
318+
return rv;
319+
}
320+
}
321+
return 0;
322+
}
323+
324+
EXTRA_CLEANUP()
325+
{
326+
if (slots)
327+
rtapi_kfree(slots);
328+
}
329+
330+
static void print_slot(int s)
331+
{
332+
char buf[MSG_LEN_MAX];
333+
char *end = strcpy(buf, slots[s].msgbuf) + slots[s].msglen;
334+
for (int i = 0; i < MSG_SUBST_MAX && pins[s].substs[i]; i++) {
335+
int left = sizeof(buf) - (end - buf);
336+
int n;
337+
if (left <= 1) // Need room for terminator
338+
break;
339+
switch (slots[s].substtype[i]) {
340+
case HAL_BIT:
341+
n = rtapi_snprintf(end, left, "%d%s", (int)!!pins[s].substs[i]->b, slots[s].parts[i]);
342+
break;
343+
case HAL_S32:
344+
n = rtapi_snprintf(end, left, "%d%s", (int)pins[s].substs[i]->s, slots[s].parts[i]);
345+
break;
346+
case HAL_U32:
347+
n = rtapi_snprintf(end, left, "%u%s", (unsigned)pins[s].substs[i]->u, slots[s].parts[i]);
348+
break;
349+
case HAL_S64:
350+
n = rtapi_snprintf(end, left, "%ld%s", (long)pins[s].substs[i]->ls, slots[s].parts[i]);
351+
break;
352+
case HAL_U64:
353+
n = rtapi_snprintf(end, left, "%lu%s", (unsigned long)pins[s].substs[i]->lu, slots[s].parts[i]);
354+
break;
355+
case HAL_FLOAT:
356+
n = rtapi_snprintf(end, left, "%lf%s", (double)pins[s].substs[i]->f, slots[s].parts[i]);
357+
break;
358+
default:
359+
n = 0;
360+
break;
361+
}
362+
if (n < 0) {
363+
rtapi_print_msg(RTAPI_MSG_ERR, "pushmsg: Printing slot %d failed rtapi_snprintf() call\n", s);
364+
return;
365+
}
366+
end += n;
367+
}
368+
rtapi_print_msg(slots[s].msgtype, "%s\n", buf);
369+
}
370+
371+
// Servo-thread cycle function
372+
FUNCTION(_)
373+
{
374+
bool en = enable; // Cache enable pin
375+
// For each slot, test the trigger and save the trigger state
376+
for (int i = 0; i < nslots; i++) {
377+
bool trig = *(pins[i].trigger);
378+
if (en && trig && !slots[i].prev) {
379+
print_slot(i); // Rising edge -> print message
380+
}
381+
slots[i].prev = trig;
382+
}
383+
}

0 commit comments

Comments
 (0)