setserial alternative

Hello!
Perhaps a bit offtopic, but could Ioninja perform some configurations similars to what "setserial" does?
The reason it that I have working with a Moxa RS232/485/422 device (UPORT 1250), and the mode (232/485/422) is configured using setserial (eg. setserial /dev/ttyUSB0 port 1). It would be useful to me to be able to configure it from inside ioninja (and to be franc, it would solve a problem I currently have, as I am not able to make setserial work well in my ARM device... any idea would be welcome :-)).
Regards!
Thank you

Hello Jose!

IO Ninja supports Python scripting now, so the easiest road to victory would be to find (or ask GPT/Claude to write) a snippet of Python equivalent of setserial.

A quick search reveals that setserial calls TIOCSSERIAL with struct serial_struct -- we just need to convert it to Python.

Claude wrote this:

import fcntl, struct, os

DEVICE = "/dev/ttyUSB0"

# ioctl request numbers (from <asm-generic/ioctls.h>)
TIOCGSERIAL = 0x541E   # get struct serial_struct
TIOCSSERIAL = 0x541F   # set struct serial_struct

# struct serial_struct — from <linux/serial.h>. Each format char below maps
# 1:1 to a field, in order. Native alignment (default '@', no </>/! prefix)
# reproduces the compiler's padding, so this is correct on both 64- and 32-bit.
#
#  idx  fmt  C type            field            off64  note
#  ---  ---  ----------------  ---------------  -----  -------------------------
#   0   i    int               type                 0
#   1   i    int               line                 4  ttyS index
#   2   I    unsigned int      port                 8  <-- set by `setserial port N`
#   3   i    int               irq                 12
#   4   i    int               flags               16
#   5   i    int               xmit_fifo_size      20
#   6   i    int               custom_divisor      24
#   7   i    int               baud_base           28
#   8   H    unsigned short    close_delay         32
#   9   c    char              io_type             34
#  10   c    char              reserved_char[1]    35
#            (1 byte pad)                          36  align int hub6 to 4
#  11   i    int               hub6                36
#  12   H    unsigned short    closing_wait        40
#  13   H    unsigned short    closing_wait2       42
#            (4 bytes pad, 64-bit)                 44  align pointer to 8
#  14   P    void *            iomem_base          48
#  15   H    unsigned short    iomem_reg_shift     56
#  16   I    unsigned int      port_high           60
#  17   L    unsigned long     iomap_base          64
#            total size: 72 bytes (64-bit); 60 bytes (32-bit)
FMT = "iiIiiiiiHcciHHPHIL"

PORT = 2  # field index of `port` in the tuple above

fd = os.open(DEVICE, os.O_RDWR | os.O_NOCTTY)
try:
    # Read-modify-write: pull the real struct, change only `port`, write it back.
    # This preserves every other field (and any padding) exactly as the kernel
    # returned it, so we never depend on constructing the struct from scratch.
    size = struct.calcsize(FMT)
    buf = fcntl.ioctl(fd, TIOCGSERIAL, bytes(size))
    fields = list(struct.unpack(FMT, buf))

    fields[PORT] = 1          # == `setserial /dev/ttyUSB0 port 1`

    fcntl.ioctl(fd, TIOCSSERIAL, struct.pack(FMT, *fields))
finally:
    os.close(fd)

Please note -- I didn't actually run it. But the code looks about right. Let me know if it works with your Moxa!

Thank you Vladimir! I think you point me in the right direction. Also, I see that the code runs in the Python script pane (next week I will try to do it with the real device and I let you know)

...anyway, I think you should teach jancy to claude, because I feel that Python is not the best way to deal with structs...

In fact, I tried to do the same using jancy, but I cannot identify an "ioclt" equivalent in the standard library (I used the search option for "ioctl" and also check the serial coms and generic IO classes), is there some way? (easy way... I'm always asking about some thing that could be done in "in-app" scripts)

ops... I just see that problably could be done with the io.DeviceMonitor class...

Hello. I tried the python snippet with the real moxa device, and it works well. Thank you, regards!

Haha, I'm very pleased to hear that you actually prefer Jancy to Python for in-app scripting! 🙂 Thank you!

Indeed, Jancy maps much nicer to the underlying C APIs, and porting C structs (such as serial_struct) to Jancy is pretty much copy-pasting. BTW, claude/codex already do very well with Jancy -- LLMs don't care much about syntax itself, so given enough samples (such as in ioninja scripts/ folder or the Jancy github repo), they will deduce everything they need to know about a programming language.

As for ioctl, Jancy doesn't offer it because it's essentially impossible to come up with a sane cross-platform API for ioctls -- it's highly platform specific. The C++ STL, QT, boost, and other well established libs don't attempt to abstract ioctls either -- instead, their file classes expose the underlying OS-level handle (Jancy does the same via m_osHandle) so that you can call ioctl/DeviceIoControl yourself.

Jancy provides dylib for very natural-looking bindings to any dynamic libraries. You can use it to link against glibc and call any system function, including ioctl:

enum {
	// from: bits/fcntl-linux.h

	O_RDONLY = 00,
	O_WRONLY = 01,
	O_RDWR   = 02,
	O_CREAT  = 0100,
	O_NOCTTY = 0400,

	// from: asm-generic/ioctls.h

	TIOCGSERIAL	= 0x541E,
	TIOCSSERIAL	= 0x541F,
}

pragma(ThinPointers, true) // all C pointers are `thin`

// from: linux/serial.h

struct serial_struct {
	int	type;
	int	line;
	unsigned int	port;
	int	irq;
	int	flags;
	int	xmit_fifo_size;
	int	custom_divisor;
	int	baud_base;
	unsigned short	close_delay;
	char	io_type;
	char	reserved_char[1];
	int	hub6;
	unsigned short	closing_wait; /* time to wait before closing */
	unsigned short	closing_wait2; /* no longer used... */
	unsigned char	*iomem_base;
	unsigned short	iomem_reg_shift;
	unsigned int	port_high;
	unsigned long	iomap_base;	/* cookie passed into ioremap */
};

// dylib is jancy's FFI gate to anything not covered in stdlib

dylib Libc {
	int cdecl open(
		char const* name,
		int flags,
		... // int mode (only for O_CREAT)
	);

	int close(int fd);

	int cdecl ioctl(
		int fd,
		long code,
		...
	);

	int write(
		int fd,
		void const* p,
		size_t size
	);

	// add as needed
}

pragma(ThinPointers, default)

void main() {
	Libc libc;
	libc.open("libc.so.6"); // link to glibc

	int fd = libc.lib.open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
	if (fd == -1) {
		std.setError("can't open port");
		throw;
	}

	serial_struct ss;
	libc.lib.ioctl(fd, TIOCGSERIAL, &ss); // read struct_serial
	ss.port = ...;                        // modify port as needed
	libc.lib.ioctl(fd, TIOCSSERIAL, &ss); // write it back
	libc.lib.close(fd);
}

Hello Vladimir!
Thank you. I have tried the code with the Moxa and it works perfectly.
And also, it is very interesting to know about the dylib facility, it could be very useful!!
Python is a great language, and it is great to be able to use it in ioninja as it is very popular and many people will appreciate it but I think that it is not designed for for working with structs -neither packet headers !!-.
Jancy is very nice and very capable -and great for communications stuff!- and being able to use it as "in-app scripts" is great. The only problem is that sometimes it is not easy know how to use it (and know what it can do). But I think this is understandable given the context... and we can always ask you 😉
Thank you and regards!
Josep