Quick demo video of communication.
These are really just some notes I've taken while messing around with the CX II's USB. I'm probably inconsistent on my use of quotation marks throughout this post, but nothing needs to be inside of quotes unless a TI-Nspire command (be it BASIC or Python) needs a string as an argument.
Hardware
I'm using an Arduino Nano clone that has a CH340G USB-to-UART adapter in place of something fancy and name-brand. I haven't changed the USB IDs. My TI-Nspire CX II calculators (on 5.3) talk to the CH340G just fine through a USB OTG adapter (a mini-A to mini-B link cable would also work), and communication is bidirectional (this is necessary). I don't know why, but the Nspire does stuff when communicating with the CH340 that the AVR doesn't like, so I wasn't able to directly communicate to my Arduino Nano clone; I was, however, able to use it to essentially break out the TxD and RxD lines from the CH340 and connect it to another Arduino. This made for a misleading breadboard layout in my demo video.
The final setup is my Arduino Nano clone running a blank sketch, and an Arduino Leonardo connected via its RX and TX pins such that the UART is shared. This is exposed on the Leonardo as Serial1, which allows me to use Serial (the USB interface on the Leonardo) to send debugging messages to my computer. The Leonardo runs a basic sketch that parses commands and returns a response code; if I perform the TI-Nspire BASIC commands Send "SET LED 1" \ Get a, a will contain the string '1' as my SET LED command returns the new state of the LED.
The equivalent commands for Send and Get within its Python environment are located in the ti_hub module, as ti_hub.ti_innovator.send(string command) and ti_hub.ti_innovator.get(string variablename). The Python implementation of .get() is a shim over the BASIC Get command, so you can have some unexpected behavior. When you run .get("a"), it runs the BASIC command "Get a", evaluates a (which can throw an error outside Python), then returns a string of what a evaluated to. On my CX II CAS, ti_hub.ti_innovator_get("a") with nothing in the receive buffer returns 'a'.
The Innovator Hub also just works like a USB-to-serial bridge. I presume it expects 115200bps, which is what most TI-Nspire stuff runs at. That makes it fairly straightforward to pretend to be an Innovator Hub.
If you power the Arduinos externally, the calculator will wake itself up for some reason as soon as it turns off; this kills your battery life. If you power the Arduinos via USB OTG power (both run within the power budget, plus my BMP180 as a data source), the calculator turns off power to the devices after inactivity.
Software
The TI-Nspire, when you first run a Send or Get command, the OS sends an ISTI command and expects a response. Over the wire, ISTI is sent as "ISTI\n" (no quotes, and note the lack of \r). The thing on the other end of the UART must respond to this with "TISTEM\r\n", or the calculator will complain about the hub being disconnected (hint: Python error messages are more descriptive than Nspire BASIC's error system). After this initial exchange, there's no more ISTI command unless you manually send one with Send "ISTI" \ Get somevar, or use somevar = ti_hub.ti_innovator.isti(). I think the response to ISTI is a whitelist, since the ti_hub implementation returns what the remote device sent (or raises tihubException) and not a True/False response.
When you're implementing two-way communication with the Arduino, you need to pay attention to what returns a response and what doesn't. Responses are strings sent over the serial port, terminated with CRLF, and you read these with the Get command. The first line you send, and thus what lands on the calculator's UART receive buffer, is what the Get command fetches, even if you send subsequent data before another Get command. If you send two lines from an Arduino, then run Get twice in a row on the calculator, the calculator first parses and stores the first line that was sent, then times out (1 second) receiving a fresh message before returning whatever value was already inside the variable you are Getting. There is no way to tell if you missed a line. You can run Get inside a loop if your Arduino (or other UART device) sends data sufficiently slowly; the timeout should keep the calculator in sync. The better way is to interrogate the Arduino explicitly with a Send command, then immediately fetch the output.
The Lua asi library should, in theory, let you talk directly through the CH340, as well as do things like control the baudrate (which defaults to an unconfigurable 115200 everywhere else). I don't know Lua, so I'm not going to experiment with this.
Quick Summary
The key takeaways here are that the serial port always runs at 115200, 8N1 framing, no flow control as far as I've observed. You need to respond correctly to the ISTI command before you do anything, and you can communicate with the Nspire OS by sending and receiving strings terminated with CRLF. The calculator's buffer only holds the first line that hits it, so it is up to you, the programmer, to properly implement some kind of flow control.
These are really just some notes I've taken while messing around with the CX II's USB. I'm probably inconsistent on my use of quotation marks throughout this post, but nothing needs to be inside of quotes unless a TI-Nspire command (be it BASIC or Python) needs a string as an argument.
Hardware
I'm using an Arduino Nano clone that has a CH340G USB-to-UART adapter in place of something fancy and name-brand. I haven't changed the USB IDs. My TI-Nspire CX II calculators (on 5.3) talk to the CH340G just fine through a USB OTG adapter (a mini-A to mini-B link cable would also work), and communication is bidirectional (this is necessary). I don't know why, but the Nspire does stuff when communicating with the CH340 that the AVR doesn't like, so I wasn't able to directly communicate to my Arduino Nano clone; I was, however, able to use it to essentially break out the TxD and RxD lines from the CH340 and connect it to another Arduino. This made for a misleading breadboard layout in my demo video.
The final setup is my Arduino Nano clone running a blank sketch, and an Arduino Leonardo connected via its RX and TX pins such that the UART is shared. This is exposed on the Leonardo as Serial1, which allows me to use Serial (the USB interface on the Leonardo) to send debugging messages to my computer. The Leonardo runs a basic sketch that parses commands and returns a response code; if I perform the TI-Nspire BASIC commands Send "SET LED 1" \ Get a, a will contain the string '1' as my SET LED command returns the new state of the LED.
The equivalent commands for Send and Get within its Python environment are located in the ti_hub module, as ti_hub.ti_innovator.send(string command) and ti_hub.ti_innovator.get(string variablename). The Python implementation of .get() is a shim over the BASIC Get command, so you can have some unexpected behavior. When you run .get("a"), it runs the BASIC command "Get a", evaluates a (which can throw an error outside Python), then returns a string of what a evaluated to. On my CX II CAS, ti_hub.ti_innovator_get("a") with nothing in the receive buffer returns 'a'.
The Innovator Hub also just works like a USB-to-serial bridge. I presume it expects 115200bps, which is what most TI-Nspire stuff runs at. That makes it fairly straightforward to pretend to be an Innovator Hub.
If you power the Arduinos externally, the calculator will wake itself up for some reason as soon as it turns off; this kills your battery life. If you power the Arduinos via USB OTG power (both run within the power budget, plus my BMP180 as a data source), the calculator turns off power to the devices after inactivity.
Software
The TI-Nspire, when you first run a Send or Get command, the OS sends an ISTI command and expects a response. Over the wire, ISTI is sent as "ISTI\n" (no quotes, and note the lack of \r). The thing on the other end of the UART must respond to this with "TISTEM\r\n", or the calculator will complain about the hub being disconnected (hint: Python error messages are more descriptive than Nspire BASIC's error system). After this initial exchange, there's no more ISTI command unless you manually send one with Send "ISTI" \ Get somevar, or use somevar = ti_hub.ti_innovator.isti(). I think the response to ISTI is a whitelist, since the ti_hub implementation returns what the remote device sent (or raises tihubException) and not a True/False response.
When you're implementing two-way communication with the Arduino, you need to pay attention to what returns a response and what doesn't. Responses are strings sent over the serial port, terminated with CRLF, and you read these with the Get command. The first line you send, and thus what lands on the calculator's UART receive buffer, is what the Get command fetches, even if you send subsequent data before another Get command. If you send two lines from an Arduino, then run Get twice in a row on the calculator, the calculator first parses and stores the first line that was sent, then times out (1 second) receiving a fresh message before returning whatever value was already inside the variable you are Getting. There is no way to tell if you missed a line. You can run Get inside a loop if your Arduino (or other UART device) sends data sufficiently slowly; the timeout should keep the calculator in sync. The better way is to interrogate the Arduino explicitly with a Send command, then immediately fetch the output.
The Lua asi library should, in theory, let you talk directly through the CH340, as well as do things like control the baudrate (which defaults to an unconfigurable 115200 everywhere else). I don't know Lua, so I'm not going to experiment with this.
Quick Summary
The key takeaways here are that the serial port always runs at 115200, 8N1 framing, no flow control as far as I've observed. You need to respond correctly to the ISTI command before you do anything, and you can communicate with the Nspire OS by sending and receiving strings terminated with CRLF. The calculator's buffer only holds the first line that hits it, so it is up to you, the programmer, to properly implement some kind of flow control.