google / gousb

gousb provides low-level interface for accessing USB devices
Apache License 2.0
838 stars 124 forks source link

Possible to get IEEE-1284 PNP String? #42

Closed jerbob92 closed 6 years ago

jerbob92 commented 6 years ago

Hi there,

I'm wondering whether it's possible to get the IEEE-1284 PNP String with gousb? This is the code that cups uses to get this string:

/*
 * 'get_device_id()' - Get the IEEE-1284 device ID for the printer.
 */

static int              /* O - 0 on success, -1 on error */
get_device_id(usb_printer_t *printer,   /* I - Printer */
              char          *buffer,    /* I - String buffer */
              size_t        bufsize)    /* I - Number of bytes in buffer */
{
  int   length;             /* Length of device ID */

  if (libusb_control_transfer(printer->handle,
                  LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_ENDPOINT_IN |
                  LIBUSB_RECIPIENT_INTERFACE,
                  0, printer->conf,
                  (printer->iface << 8) | printer->altset,
                  (unsigned char *)buffer, bufsize, 5000) < 0)
  {
    *buffer = '\0';
    return (-1);
  }

 /*
  * Extract the length of the device ID string from the first two
  * bytes.  The 1284 spec says the length is stored MSB first...
  */

  length = (int)((((unsigned)buffer[0] & 255) << 8) | ((unsigned)buffer[1] & 255));

 /*
  * Check to see if the length is larger than our buffer or less than 14 bytes
  * (the minimum valid device ID is "MFG:x;MDL:y;" with 2 bytes for the length).
  *
  * If the length is out-of-range, assume that the vendor incorrectly
  * implemented the 1284 spec and re-read the length as LSB first,..
  */

  if (length > bufsize || length < 14)
    length = (int)((((unsigned)buffer[1] & 255) << 8) | ((unsigned)buffer[0] & 255));

  if (length > bufsize)
    length = bufsize;

  if (length < 14)
  {
   /*
    * Invalid device ID, clear it!
    */

    *buffer = '\0';
    return (-1);
  }

  length -= 2;

 /*
  * Copy the device ID text to the beginning of the buffer and
  * nul-terminate.
  */

  memmove(buffer, buffer + 2, (size_t)length);
  buffer[length] = '\0';

  return (0);
}
jerbob92 commented 6 years ago

This is what I tried:

data := make([]byte, 5000)
n, err := usbdevice.Control(gousb.ControlClass | gousb.ControlIn | gousb.ControlInterface, 0, uint16(usbconfig.Desc.Number), (uint16(usbinterface.Setting.Number)<< 8) | uint16(usbinterface.Setting.Alternate), data)

Sadly, this results in libusb: invalid param [code -2]

jerbob92 commented 6 years ago

I managed to get it to work! The trick is to use a smaller buffer, 1024 bytes worked for me.

zagrodzki commented 6 years ago

That seems correct, the maximum size of a control transfer in libusb is 4KB (also see https://github.com/libusb/libusb/issues/110).

jerbob92 commented 6 years ago

@zagrodzki would you appreciate / accept a pull request that adds functionality to read the PnP identifier?

zagrodzki commented 6 years ago

Hm. Maybe. I'm not sure, we don't have a good place to fit something like this right now. I would imagine it could be useful to have class-specific types that could be used to represent any of the common operations on a given class. So perhaps something like ".../gousb/printer" package with a wrapper that would take a *gousb.Configuration and return a Printer type with methods that make sense for a printer... If you feel like taking on the work, and spending time with me to figure out how that should all be organized, then feel free to start a PR. Fair warning - it's going to take some work, it'll be more than adding a simple function with the code you've listed above. I'm going to ask for tests, and perhaps it will require some additions in the core gousb to work nicely with interfaces you'd need for tests...

jerbob92 commented 6 years ago

Sounds fair. I didn't really dig that deep into USB yet, so I didn't realize those control transfers are different per class. Right now I added it as method on the Interface struct (https://github.com/jerbob92/gousb/tree/feature/1284-pnp-string), but should be added in a class specific package indeed. I'm willing to put some time into this, but only for the printer class. Someplace we could chat about this?

zagrodzki commented 6 years ago

For starters, read through http://www.usb.org/developers/docs/devclass_docs/usbprint11a021811.pdf (this describes all features of the printer class). Specifically section 4.2 lists the control requests. Section 5.3 shows interface descriptor that is expected of a printer class (this maps to gousb.Interface in a particular device configuration).

My first idea was something like that:

type Printer struct { *gousb.Device }

// these will send control interfaces func (Printer) DeviceId() (string, error) ... func (Printer) PortStatus() (PortStatus, error) ... func (Printer) SoftReset() error

func Open(dev gousb.Device) (Printer, error)

Things I don't like about it already: printers in theory may have multiple configurations (gousb.Config) and each configuration might have multiple interfaces. Of these the first interface must be the data interface, other interfaces are unspecified. The first interface may have multiple alternates. Presumably, users should have a way of selecting different configurations, so maybe Printer should be built around gousb.gousb.Interface instead? But Control() works on a gousb.Device, even if the control request is for a particular interface. Maybe Interface should re-export Control() from Config.dev and allow non-device control requests. Also the printer interface should have different modes for different alternates (each alternate will specify a particular type - unidir, bidir, ieee 1284.4 bidir) and that should be presented to the user in some nice way. Depending on the type of the interface, In() and Out() should open the right endpoint, since endpoints are defined by the printer class.

Start with a PR with something and then we can discuss on the review thread.