getPrinterStatus method

Future<Map<String, dynamic>> getPrinterStatus(
  1. int vendorId,
  2. int productId,
  3. List<int> command, {
  4. int interfaceNumber = 0,
  5. int timeout = 10000,
  6. bool expectResponse = false,
  7. int maxResponseLength = 542,
})

Implementation

Future<Map<String, dynamic>> getPrinterStatus(
  int vendorId,
  int productId,
  List<int> command, {
  int interfaceNumber = 0,
  int timeout = 10000,
  bool expectResponse = false,
  int maxResponseLength = 542,
}) async {
  // 🔹 1️⃣ Open the device
  final Pointer<libusb_device_handle>? handleNullable =
      openDevice(vendorId, productId);
  if (handleNullable == nullptr || handleNullable == null) {
    return {
      'success': false,
      'error': 'Could not open device',
      'isConnected': false,
      'statusType': command.length >= 3 ? command[2] : 0
    };
  }

  // 🔹 2️⃣ Obtener los endpoints correctos
  final device = getDevice(vendorId, productId);
  if (device == null) {
    //log("❌ The USB device could not be found");
    return {'success': false, 'error': 'The device could not be found.'};
  }

  final endpoints = getDeviceEndpoints(device);
  if (endpoints.isEmpty) {
    return {'success': false, 'error': 'No valid endpoints found'};
  }

  // Detect OUT (write) eN (read)
  int endpointOut =
      endpoints.firstWhere((e) => e & 0x80 == 0, orElse: () => 0x01);
  int endpointIn =
      endpoints.firstWhere((e) => e & 0x80 != 0, orElse: () => 0x82);

  log("✔️ Endpoint OUT: ${endpointOut.toRadixString(16)} - Endpoint IN: ${endpointIn.toRadixString(16)}");

  final handle = handleNullable;

  Map<String, dynamic> statusInfo = {
    'success': false,
    'isConnected': false,
    'rawData': null,
    'statusType': command.length >= 3 ? command[2] : 0
  };
  try {
    // Check if there is an active kernel driver and detach if necessary
    int hasKernelDriver = 0;
    if (Platform.isLinux || Platform.isMacOS) {
      try {
        hasKernelDriver =
            _bindings.libusb_kernel_driver_active(handle, interfaceNumber);
        if (hasKernelDriver == 1) {
          //log("Detaching kernel driver...");
          final detachResult =
              _bindings.libusb_detach_kernel_driver(handle, interfaceNumber);
          if (detachResult < 0) {
            //log("Could not detach kernel driver: $detachResult");
          } else {
            //log("Kernel driver detached successfully");
          }
        }
      } catch (e) {
        //log("Error checking/detaching kernel driver: $e");
      }
    }

    // Configure the device if necessary
    final configResult = _bindings.libusb_set_configuration(handle, 1);
    if (configResult < 0) {
      //log("Warning: Could not set configuration: $configResult");
    }

    // 🔹 3️⃣ Reclamar la interfaz antes de transferir datos
    // Claim the interface with multiple attempts
    int claimResult = -1;
    int attempts = 0;
    const maxAttempts = 3;

    while (attempts < maxAttempts) {
      claimResult = _bindings.libusb_claim_interface(handle, interfaceNumber);
      if (claimResult == 0) break;

      //log("Attempt ${attempts + 1} failed with error $claimResult. Retrying...");
      await Future.delayed(Duration(milliseconds: 500));
      attempts++;
    }

    if (claimResult < 0) {
      return {
        'success': false,
        'error': 'Could not claim interface',
        'isConnected': false,
        'statusType': command.length >= 3 ? command[2] : 0
      };
    }

    // 🔹 4️⃣ Enviar el comando a la impresora
    final buffer = calloc<Uint8>(command.length);
    final bufferList = buffer.asTypedList(command.length);
    bufferList.setAll(0, command);

    final transferredPtr = calloc<Int>();

    log("📤 Sending command $command to endpoint ${endpointOut.toRadixString(16)}...");
    int transferResult = _bindings.libusb_bulk_transfer(handle, endpointOut,
        buffer.cast<UnsignedChar>(), command.length, transferredPtr, timeout);

    await Future.delayed(Duration(milliseconds: 100));

    calloc.free(buffer);
    calloc.free(transferredPtr);

    if (transferResult < 0) {
      String errorDescription = _getUsbErrorDescription(transferResult);
      log("❌ Error sending command: $errorDescription");
      return {
        'success': false,
        'error': 'Error sending command: $command, detail: $errorDescription',
        'isConnected': false,
        'statusType': command.length >= 3 ? command[2] : 0
      };
    }

    log("✔️ Command sent successfully");

    // 🔹 5️⃣ Leer la respuesta si aplica
    Uint8List buffer2 = Uint8List(512);
    final Pointer<UnsignedChar> dataPointer =
        malloc.allocate<UnsignedChar>(buffer2.length);
    for (var i = 0; i < buffer2.length; i++) {
      dataPointer[i] = buffer[i];
      //dataPointer[i] = 0; // Inicializar con ceros
    }

    final Pointer<Int> transferredPointer = malloc.allocate<Int>(1);
    transferredPointer.value = 0;

    log("📥 Waiting for response on endpointIn ${endpointIn.toRadixString(16)}...");

    // Call the function correctly
    int readResult = _bindings.libusb_bulk_transfer(
      handle, // libusb_device_handle*
      endpointIn, // unsigned char endpoint
      dataPointer, // unsigned char* data
      buffer2.length, // int length
      transferredPointer, // int* transferred
      timeout, // unsigned int timeout
    );

    // Read how many bytes were transferred
    int bytesReceived = transferredPointer.value;

    if (readResult == 0 && bytesReceived > 0) {
      // Copy the received data back to a Uint8List
      Uint8List receivedData = Uint8List(bytesReceived);
      for (var i = 0; i < bytesReceived; i++) {
        receivedData[i] = dataPointer[i];
      }

      // Determine what type of command it is based on the third byte
      int statusType = command.length >= 3 ? command[2] : 0;

      statusInfo['success'] = true;
      statusInfo['isConnected'] = true;
      statusInfo['rawData'] = receivedData;
      statusInfo['binaryResponse'] =
          receivedData[0].toRadixString(2).padLeft(8, '0');
      statusInfo['statusType'] = statusType;

      // Interpret the data based on the status type
      if (bytesReceived > 0) {
        switch (statusType) {
          case 1: // Printer status [0x10, 0x04, 0x01]
            statusInfo['isOnline'] = (receivedData[0] & (1 << 3)) == 0;
            statusInfo['cashDrawerOpen'] = (receivedData[0] & (1 << 2)) != 0;

            statusInfo['waitingForRecovery'] =
                (receivedData[0] & (1 << 5)) != 0;
            statusInfo['paperFeedButtonPressed'] =
                (receivedData[0] & (1 << 6)) != 0;

            // Si está offline y esperando recuperación, podemos inferir posibles problemas
            if (!statusInfo['isOnline'] && statusInfo['waitingForRecovery']) {
              statusInfo['possibleErrorConditions'] = [
                'cover_open',
                'paper_end',
                'error_state'
              ];
              // No es necesario enviar el segundo comando si ya sabemos que hay un error
            }
            break;

          case 2: // Offline status [0x10, 0x04, 0x02]
            statusInfo['isCoverOpen'] = (receivedData[0] & (1 << 2)) != 0;
            statusInfo['isPaperFeedByButton'] =
                (receivedData[0] & (1 << 3)) != 0;
            break;

          case 4: // Paper sensor status [0x10, 0x04, 0x04]
            bool bit2 = (receivedData[0] & (1 << 2)) != 0;
            bool bit3 = (receivedData[0] & (1 << 3)) != 0;
            bool bit5 = (receivedData[0] & (1 << 5)) != 0;
            bool bit6 = (receivedData[0] & (1 << 6)) != 0;

            statusInfo['paperStatus'] = {
              'paperNearEnd': bit2 || bit3,
              'paperEnd': bit5 || bit6,
              'paperPresent': !(bit5 || bit6),
              'paperAdequate': !(bit2 || bit3),
            };
            break;

          default:
            statusInfo['error'] = 'Unrecognized command type';
        }
      }
    } else {
      log("Error: ${_bindings.libusb_error_name(readResult)}");
      log("Description: ${_getUsbErrorDescription(readResult)}");
      statusInfo['error'] =
          'Error reading response: ${_bindings.libusb_error_name(readResult)}';
    }

    // Free memory
    malloc.free(dataPointer);
    malloc.free(transferredPointer);

    // Release the interface
    _bindings.libusb_release_interface(handle, interfaceNumber);

    // Reattach the kernel driver if it was detached
    if (hasKernelDriver == 1 && (Platform.isLinux || Platform.isMacOS)) {
      _bindings.libusb_attach_kernel_driver(handle, interfaceNumber);
    }
  } catch (e) {
    log('Exception: $e');
    return {
      'success': false,
      'error': 'Error communicating with the printer: ${e.toString()}',
      'isConnected': false,
      'statusType': command.length >= 3 ? command[2] : 0
    };
  } finally {
    closeDevice(handle);
  }
  return statusInfo;
}