Skip to content

hal: add pushmsg component for RT message generation#4003

Draft
grandixximo wants to merge 1 commit intoLinuxCNC:masterfrom
grandixximo:feature/pushmsg-comp
Draft

hal: add pushmsg component for RT message generation#4003
grandixximo wants to merge 1 commit intoLinuxCNC:masterfrom
grandixximo:feature/pushmsg-comp

Conversation

@grandixximo
Copy link
Copy Markdown
Contributor

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}-N emits the corresponding string from the load-time {e,w,i,d}msg arrays through rtapi_print_msg(). 16 slots per level. Slots without a configured message are inert. Singleton instance.

Example:

loadrt pushmsg emsg="boom","other boom" wmsg="careful"
addf pushmsg servo-thread
setp pushmsg.error-00 1

cc @BsAtHome who proposed this in #3993.

Draft for discussion. Open to flexibility tweaks (personality bits for per-level counts, multi-instance, etc.).

Comment thread src/hal/components/pushmsg.comp Outdated
Comment thread src/hal/components/pushmsg.comp Outdated
@andypugh
Copy link
Copy Markdown
Collaborator

andypugh commented May 4, 2026

How does this compare with the existing "message" component?

https://linuxcnc.org/docs/stable/html/man/man9/message.9.html

@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 4, 2026

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.

@grandixximo
Copy link
Copy Markdown
Contributor Author

@ BsAtHome

While we are at it, what about, .msg or similar file, INI style

[ERROR]
error-0 = the inverter had error <halpin.error.val>
[WARNING]
warning-0 = the oil pump is empty

and lift the 16 max limit?

Or is this too complex?

@grandixximo grandixximo force-pushed the feature/pushmsg-comp branch from 6553f80 to a87a084 Compare May 4, 2026 11:45
@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 4, 2026

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.

@grandixximo
Copy link
Copy Markdown
Contributor Author

I don't think no loop is structurally possible if input is N HAL bit signals...

@grandixximo
Copy link
Copy Markdown
Contributor Author

It should suffice to enter them as arguments in the loadrt.

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.

@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 5, 2026

That is a valid argument. However, you then need to parse a text file in the kernel...

@grandixximo
Copy link
Copy Markdown
Contributor Author

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?

@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 5, 2026

I was thinking of rescoping to a userspace daemon (loadusr): ...

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 message is not used in any example. Just adding a new component 'because' is not very convincing.

@grandixximo
Copy link
Copy Markdown
Contributor Author

grandixximo commented May 5, 2026

Fair, uspace message is another PR, not rescoping this one, pushmsg stays RT, do you want me to redraft it? make it similar to message component?

@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 5, 2026

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.

@grandixximo
Copy link
Copy Markdown
Contributor Author

Use-case: integrator commissioning + field diagnostics. Real examples I hit:

  • inverter fault bit => ERROR (operator stops, sees in GUI)
  • coolant low / lube level => WARNING (pre-failure heads-up)
  • spindle warmup incomplete / tool-change in progress => INFO (status, not error)
  • state-machine transitions for ladder/classicladder debug => DEBUG (only when DEBUG set)

The message component covers only ERROR, so today integrators either:

  • abuse error for warnings (operator fatigue, real errors lost in noise), or
  • write a custom .comp per machine.

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, messages (strings) and levels (ints 0..3), single pin array in-NN, each pin emits its assigned message at its assigned level. One loop over N pins, edge-detect, fire. No 4x scan. Pins stay 1:1 with messages so wiring is unambiguous.

Bonus: HAL pin value substitution via <pin.name> or <pin.name:%fmt> syntax in the message string. Parsed once at loadrt (no kernel runtime string parsing): halpr_find_pin_by_name resolves to ptr + type, cached per slot. Fire time is just rtapi_snprintf with cached pointers. Constraint: referenced source comp must be loaded before pushmsg, standard HAL ordering.

Example:
loadrt pushmsg messages="inverter fault <vfd.fault-code>","coolant low" levels=0,1
net inverter-fault vfd.fault => pushmsg.0.in-00
net coolant-low coolant.low => pushmsg.0.in-01

Want me to redraft pushmsg.comp on those lines?

@andypugh
Copy link
Copy Markdown
Collaborator

andypugh commented May 5, 2026

Maybe it would be better to have a string of key-value pairs in the loadrt line?
loadrt message w|oil|low_oil, e|nooil|no_oil at_all ...
ie, a key that determines warning/error etc and also provides the pin name (needs uniqueness check at load time).
Then there is no need to count commas and new messages can be insterted anywhere in the string.
(In the above example I have also decided that we might need to replace spaces with underscores to keep the modparam system happy, but the system would replace with spaces for display)

@andypugh
Copy link
Copy Markdown
Collaborator

andypugh commented May 5, 2026

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.

@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 5, 2026

And we can make it easier for people by using an INI expansion like:

loadrt pushmsg msgs=[PUSHMSG]MESSAGES

And then:

[PUSHMSG]
# Note: no surrounding whitespace
MESSAGES=\
"w|oil|Low oil",\
"e|nooil|No oil",\
"i|ping|Tool said hi"

@grandixximo grandixximo force-pushed the feature/pushmsg-comp branch from a87a084 to 294fb90 Compare May 5, 2026 12:08
Comment thread src/hal/components/pushmsg.comp Outdated
Comment thread src/hal/components/pushmsg.comp Outdated
Comment thread src/hal/components/pushmsg.comp Outdated
Comment on lines +67 to +75
typedef struct {
hal_bit_t **trigger;
hal_bit_t prev;
int level;
char literal[MAX_LIT];
int litlen;
ref_t refs[MAX_REFS];
int nrefs;
} slot_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two types of structures required. One where the hal reference lives (in hal memory) and one for internal admin can be simply allocated.

The hall pin in not a double pointer. It is a single pointer.
You do not use hal-types to store the previous pin status. This is abuse of hal types and we've been battling this for a long time. You need the underlying type.

With these very basic mistakes, it looks like this code is LLM generated??

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked, apologies for the rough first pass, not sure I fully understand why it is wrong :-(
I did have LLM help, but I don't understand why its wrong, I'll look at it again tomorrow after some sleep...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spent the morning studying it, I think I get it now....

HAL pin storage and admin state in separate structs (paired by index). Mixing forces per-pin malloc instead of one alloc sized after pre-count, which should be preferred.
Internal state uses underlying C types (bool, int), not hal_bit_t.

I had the wrong mental model on the double pointer; thought it was a clever address cache. The legitimate ** pattern (matrix_kb, classicladder, mux_generic) is pointer-to-array with one alloc and indexed access. Per-pin ** is just single-pointer with extra steps.

Noticed several legacy components mix the two struct types (encoder, pid, pwmgen, stepgen, parport, debounce, watchdog) and mb2hal does the per-pin malloc, so I see the pattern you've been pushing back on. Not asking to clean those up, just confirming I get it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hal pins in a component are a pointer to a in-HAL-memory data unit. When you create a pin, then you must store the reference to that internal data unit. The reference is a pointer. To store a reference, you need the address of the reference, hence the double pointer.

Yes, there is a lot of confusion about types and memory usage. It needs to be cleaned up, but that is another issue.

Comment thread src/hal/hal_lib.c Outdated
Comment thread src/hal/components/pushmsg.comp Outdated
Comment thread src/hal/components/pushmsg.comp Outdated
Comment thread src/hal/components/pushmsg.comp
Comment thread src/hal/components/pushmsg.comp Outdated
@grandixximo grandixximo force-pushed the feature/pushmsg-comp branch 3 times, most recently from 2036f36 to ff3e7fb Compare May 6, 2026 01:36
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.
@grandixximo grandixximo force-pushed the feature/pushmsg-comp branch from ff3e7fb to dcf0c95 Compare May 6, 2026 01:37
@BsAtHome
Copy link
Copy Markdown
Contributor

BsAtHome commented May 6, 2026

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants