Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python asyncio protocol support #2

Open
jameshilliard opened this issue Oct 6, 2022 · 9 comments
Open

Python asyncio protocol support #2

jameshilliard opened this issue Oct 6, 2022 · 9 comments

Comments

@jameshilliard
Copy link
Contributor

jameshilliard commented Oct 6, 2022

I was thinking it might make sense to wire this up somehow to a python asyncio protocol.

I wrote something that acts as a virtual serial port proxy using a pty and asyncio tcp protocol with ser2net but that of course is missing some features this has.

Something like this is handy if one wants to run a serial based application on a development machine when the serial device is attached to a separate embedded system(which may not have the right environment to run said application itself) in addition to allowing for debugging/manipulating of commands bidirectionally between the application and device(think of it like a serial based mitmproxy).

serialproxy.py

I'm wondering, was there a reason for using swig for the python interface of serialsim? Wouldn't it be easier to just use normal python ioctl calls?

@cminyard
Copy link
Owner

cminyard commented Oct 6, 2022 via email

@jameshilliard
Copy link
Contributor Author

I can see that. What's missing for that application is immediate notification that something has changed. At least if you want to propagate baud rate and other settings. That's possible, I just didn't need it.

Yeah, I was configuring stuff manually on the ser2net side.

Well, it was actually easier to use swig if you know it, and from that
python doc: This function is identical to the fcntl() function, except
that the argument handling is even more complicated. :-)

Swig stuff tends to be a bit annoying to build I guess in some cases, and is harder to distribute(ie can't just upload a simple pure python wheel to pypi).

I'm fairly surprised that no one has discovered this until now and
wanted to use it for some different application. I'm certainly open to
extending it to make it more useful. My testing frameworks are tied
around the swig interface, but I'm not against another one. It would be
more convenient to be pure python.

Yeah, I could probably give porting it to pure python a shot, have any good examples of something consuming the swig interface that I could use for checking that there's no regressions when migrating to pure python?

Probably easier to extend once migrated to pure python IMO.

By the way what's the difference between this and your v2 patch? Did that go any further in regards to getting accepted upstream?

@jameshilliard
Copy link
Contributor Author

I am not sure you could get the termios stuff to work properly in a portable manner. I suppose it's possible, but I took the path of least resistance for me.

Yeah, looks like I need to special case types for a few archs for the termios2 structure but this seems to be mostly working:

import ctypes
import fcntl
import termios

tcflag_t = ctypes.c_uint
cc_t = ctypes.c_ubyte
speed_t = ctypes.c_uint
NCCS = 19

class termios2(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("c_iflag", tcflag_t),
                ("c_oflag", tcflag_t),
                ("c_cflag", tcflag_t),
                ("c_lflag", tcflag_t),
                ("c_line", cc_t),
                ("c_cc", cc_t * NCCS),
                ("c_ispeed", speed_t),
                ("c_ospeed", speed_t)]

UNCCS = 32

def _IOR(ty, nr, size):
    return (2 << 30) | (ord(ty) << 8) | (nr << 0) | (size << 16)

TIOCSERGREMTERMIOS = _IOR('T', 0xe7, ctypes.sizeof(termios2))

def getspeed(baudrate):
    return getattr(termios, 'B{}'.format(baudrate))

def get_remote_termios(fd):
    ktermios = termios2()
    rv = fcntl.ioctl(fd, TIOCSERGREMTERMIOS, ktermios);
    user_c_cc = []
    for i in range (0, UNCCS):
        if i == termios.VTIME or i == termios.VMIN:
            user_c_cc.append(ktermios.c_cc[i])
        elif i < NCCS:
            user_c_cc.append(chr(ktermios.c_cc[i]))
        else:
            user_c_cc.append(chr(0))
    return (
        ktermios.c_iflag,
        ktermios.c_oflag,
        ktermios.c_cflag,
        ktermios.c_lflag,
        getspeed(ktermios.c_ispeed),
        getspeed(ktermios.c_ospeed),
        tuple(user_c_cc),
    )

@cminyard
Copy link
Owner

cminyard commented Oct 6, 2022 via email

@cminyard
Copy link
Owner

cminyard commented Oct 6, 2022 via email

@jameshilliard
Copy link
Contributor Author

I was waiting for interest, really. No one else has chimed in.

Having this upstream does sound handy, then anyone wanting to use it would only need to have the pure python userspace serialsim module.

I'm not a python expert, but I think that should work. It's all chars,
so that should be pretty straightforward.

There seemed to be a number of extraneous intermediary operations going on with the swig module that didn't seem to be needed, for example this user_termios structure didn't seem to be doing anything useful.

I just removed some layers of indirection and simplified the logic to return the same value.

The returned structure for get_remote_termios seems rather strange, I mean it was easy enough to replicate the format in pure python but I'm not really sure what the purpose of the weird nested tuple return format is in the first place.

@cminyard
Copy link
Owner

I was waiting for interest, really. No one else has chimed in.

Having this upstream does sound handy, then anyone wanting to use it would only need to have the pure python userspace serialsim module.

I'm not a python expert, but I think that should work. It's all chars,
so that should be pretty straightforward.

There seemed to be a number of extraneous intermediary operations going on with the swig module that didn't seem to be needed, for example this user_termios structure didn't seem to be doing anything useful.

user_termios is there because the representation of termios you get from the kernel is different than the main termios structure, you get from glibc and there is an include nightmare getting both of them. This code was originally written to support C code, not python, too.

I just removed some layers of indirection and simplified the logic to return the same value.

The returned structure for get_remote_termios seems rather strange, I mean it was easy enough to replicate the format in pure python but I'm not really sure what the purpose of the weird nested tuple return format is in the first place.

That is the normal "termios" structure that you get from the python termios calls. I made it match that so it could be compared easily. See https://docs.python.org/3/library/termios.html

Anyway, thanks for your work on this. It's a big improvement.

@jameshilliard
Copy link
Contributor Author

user_termios is there because the representation of termios you get from the kernel is different than the main termios structure, you get from glibc and there is an include nightmare getting both of them. This code was originally written to support C code, not python, too.

Ah, that makes sense, wasn't sure of the history there.

That is the normal "termios" structure that you get from the python termios calls. I made it match that so it could be compared easily. See https://docs.python.org/3/library/termios.html

Oh, hadn't realized that's what termios was returning(I haven't worked with it much directly).

By the way, is there a good way to get notified when a serial application say configures the serialsim serial port parameters without polling the ioctl's?

For asyncio protocol reading/writing I'm thinking I should probably use a fd watcher like I used with my pty based script or maybe a pipe protocol read/writer, what do you think would work best there?

@cminyard
Copy link
Owner

cminyard commented Oct 15, 2022 via email

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

No branches or pull requests

2 participants