I've been dinking with a CNC lathe at a local maker space, and figured I'd document my efforts...
The lathe in question is a Cincinnati Milacron with an acromatic 850 controller. It came out in 1987, and is an extremely pleasant artifact from the Good Old Days (tm), we have complete schematics! It has 6 128K bubble memory modules, and an RS-232 port for your paper tape reader. So I got to dust off my Christmas tree and pile o' adapters, though I did have to use an RS-232 to USB converter.
So, the first problem... Its not quite RS-232. Its an odd differential driver which is set up to be RS-232, or something else with a different cable. It does actually just work, but the voltages were low and between that and a mysterious protocol which made it completely ignore everything I sent it, I got a false conclusion that the driver was fried. But we had schematics and I was able to put an oscilloscope on the UART itself. It has not only three 8086s (and and 8087), but four 8551 UARTs which are actually big enough to hold a probe on.
Then you're going to need a null modem.
The next problem... that's not ASCII. It looks just like ASCII but the most significant bit is doing stupid things... It is in reality RS-358, and old paper-tape code used for CNC. The most significant bit is even parity. So, tell it lies: set the lathe to 8n1 and set the computer to 7e2. It will then just work if you send it ASCII (mostly). Also, map everything to uppercase, the lathe only believes in 65 characters, and none of them are lowercase.
Then the protocol... The line protocol is RS-491 protocol level 1 or 2 (you can set it). Protocol level 1 means RTS-CTS hardware flow control. Except that the USB serial port thing doesn't seem to be able to assert CTS (the Christmas tree says it is... whatever...). I never got any data through on protocol level 1. But when I set it to level 2, all of a sudden everything works. The lathe has a terminal emulator... which can do lowercase (world's heaviest terminal!).
So, protocol level 2 it is... Which is more or less Xon-Xoff flow control.
When the lathe wants to send something... it will send 0x12 repeatedly, and eventually timeout. If you reply to the 0x12 with a 0x11, it will then happily send all the data with no additional handshaking required. End of file is indicated with 0x14. And 0x10 means abort.
When you want to send the lathe something... It doesn't really care if you send it lots of 0x12's like it sends you. What it really wants to see is 0x25 (start of tape), then a newline (0x0A), your horde of Gcode, and then 0x04 for EOF marker. You also need to honor Xon Xoff flow control or it will declare overflow. I also had to add a sleep per character, since the serial thing would buffer too much data, and I couldn't respond to the Xoff fast enough.
And last but not least, here is a convenient python implementation of upload and download: