Timendus / ticalc-usb

A library to communicate with TI graphing calculators through WebUSB
https://ticalc.link
GNU General Public License v3.0
29 stars 4 forks source link
calculator es6 javascript ti-84 ti-84-plus usb webusb

Build Status Version on NPM Downloads on NPM Maintainability Test Coverage License

ko-fi

ticalc-usb

ticalc-usb is a Javascript library to communicate with Texas Instruments graphing calculators over WebUSB. It also has a module that can parse TI files (*.8?p / *.8?g files). As I only own a TI-84 Plus, development is highly skewed towards that device, but other devices should be fairly simple to add.

Check out ticalc.link to see the library in action.

Status of this library

This library is currently very experimental. I have successfully communicated with my TI-84 Plus with it, and sent files to it. However, it comes with absolutely no guarantees that it can handle other models or even other versions of the TI-84 Plus, nor that it can properly parse and send any file you throw at it. So be warned.

If you run into issues, please report them or submit a PR.

Usage

ticalc-usb is available through NPM and can be installed as a dependency using NPM or Yarn:

npm install --save ticalc-usb

You can then import both modules of the library in your project like this:

const { ticalc, tifiles } = require('ticalc-usb');

ticalc

The ticalc module exposes these regular functions:

And these async functions:

Please note that the Promises that both init and choose return can be rejected if the user selects a device that is not supported. Unfortunately Texas Instruments reused their USB product IDs, so we can't be more specific up front. You'll probably want to catch this error and show the user an appropriate message. Also, choose will reject if the user selects no device at all.

A somewhat complete example:

ticalc.addEventListener('connect', async calculator => {
  if ( await calculator.isReady() ) {
    // Type "HELLO", key values for TI-83/84 Plus taken from
    // https://github.com/debrouxl/tilibs/blob/master/libticalcs/trunk/src/keys83p.h
    await calculator.pressKey(0xA1);
    await calculator.pressKey(0x9E);
    await calculator.pressKey(0xA5);
    await calculator.pressKey(0xA5);
    await calculator.pressKey(0xA8);

    // Get available memory
    console.log(await calculator.getFreeMem());
  }
});

try {
  // Initialise the library
  await ticalc.init();

  // Ask user to pick a device. Don't do this on page load, browsers don't like
  // that, plus `init` may already have fired a `connect` event. Call this
  // function when the user clicks on a button.
  await ticalc.choose();
} catch(e) {
  // Handle unsupported or no device selected
  console.error(e);
}

On calculator objects you can call five async methods, three of which are shown in the example above:

And one regular method:

tifiles

The tifiles module exposes these functions:

Combined with ticalc, we can use these functions to send a TI file to a connected calculator:

// Load file (make sure readFile returns a Uint8Array)
const file = tifiles.parseFile(readFile(filename));

if ( !tifiles.isValid(file) )
  return console.error('The file you have selected does not seem to be a valid calculator file');

// Assuming we received a calculator object from the `connect` event
if ( !calculator.canReceive(file) )
  return console.error(`The file you have selected does not appear to be a valid file for your ${calculator.name}`);

await calculator.sendFile(file);

Special features

The init and choose functions can take an options object. This exposes some special features that most people will not need, but do come in handy sometimes.

Choosing the right support level

By default, ticalc-usb will only successfully resolve the choose promise if the connected calculator has the status supported. You can be more adventurous and also allow calculators that have partial-support, beta support or that are experimental.

If you want your user to be able to select any possible Texas Instruments device, not just the ones that have any support at all, use none.

You have to pass this alternative supportLevel as an option to the init function:

await ticalc.init({ supportLevel: 'beta' });

I use this feature in ticalc.link to allow users to experiment with newly added devices, and to submit support requests for unsupported devices.

Injecting a different WebUSB object

By default, ticalc-usb will check to see if your web browser supports WebUSB and then take navigator.usb and wrap it in an object that adds logging and recording for debugging purposes. You can override this behaviour by supplying another object that implements the WebUSB API.

For example, you can use thegecko's Node.js WebUSB implementation by handing it to init and choose:

const usb = require('webusb').usb;
await ticalc.init({ usb });
await ticalc.choose({ usb });

Getting the debug recording

The default WebUSB wrapper records all interactions with the WebUSB API. You can get the recording by asking ticalc for it. ticalc.getRecording() returns an instance of Recorder.

const recording = ticalc.getRecording().getSteps();

Developing

The easiest way to work on this library is to work on a project that makes use of it. I use ticalc.link for this, and if you don't have a project of your own in mind, you can use it too.

First, check out both repositories:

git clone git@github.com:Timendus/ticalc.link.git
git clone git@github.com:Timendus/ticalc-usb.git

Then, make sure your changes to ticalc-usb are watched and get transpiled when you make a change:

cd ticalc-usb
npm install
npm start

In another terminal, make sure you also have the tests running, so you don't break things by accident:

cd ticalc-usb
npm run watch-tests

And finally, in yet another terminal, use your local ticalc-usb as a dependency for ticalc.link and serve the website:

cd ticalc.link
sed -i.bak 's/"ticalc-usb": ".*"/"ticalc-usb": "file:\/\/..\/ticalc-usb"/g' package.json && rm package.json.bak
npm install
npm start

(Note: If you don't have sed on your machine, manually replace the version matcher for ticalc-usb in ticalc.link's package.json file with file://../ticalc-usb.)

ticalc.link should now be running on localhost:8080 with the local version of ticalc-usb. Any changes to ticalc-usb should trigger a reload of the website automatically.