unit IOMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons, JvHidControllerClass, StdCtrls;

const
  cCodeMercenariesVID = $07C0;
  cIOWarriorPID       = $1500;
  cLEDByte            = 3;

type
  TIOWarriorIOReport = packed record
    ReportID: Byte;                // all reports have a ReportID
    IOBits: array [0..3] of Byte;  // 32 bits to read and write
  end;

  TMainForm = class(TForm)
    HidCtl: TJvHidDeviceController;
    LED0: TSpeedButton;
    LED1: TSpeedButton;
    LED2: TSpeedButton;
    LED3: TSpeedButton;
    LED4: TSpeedButton;
    LED5: TSpeedButton;
    LED6: TSpeedButton;
    LED7: TSpeedButton;
    IOWarriorDetected: TLabel;
    InputBits: TLabel;
    BlockingRead: TSpeedButton;
    procedure FormCreate(Sender: TObject);
    procedure HidCtlDeviceChange(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure LEDClick(Sender: TObject);
    procedure BlockingReadClick(Sender: TObject);
  public
    LEDs: array [0..7] of TSpeedButton;
    IOWarrior: TJvHidDevice;
    IOWarriorOutputReport: TIOWarriorIOReport;
    procedure UpdateControls;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

// this function has been extracted from the Jedi Code Library
// http://sourceforge.net/projects/jcl

function OrdToBinary(const Value: Byte): string;
const
  BitsPerByte = 8;
var
  I: Integer;
  B: Byte;
  P: PChar;
begin
  SetLength(Result, BitsPerByte);
  P := PChar(Result) + ((BitsPerByte - 1) * SizeOf(Char));
  B := Value;
  for I := 0 to BitsPerByte - 1 do
  begin
    P^ := Chr(48 + (B and $00000001));
    Dec(P);
    B := B shr 1;
  end;
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  // place the SpeedButtons in an array for indexed access
  // mark them with their array index
  for I := Low(LEDs) to High(LEDs) do
  begin
    LEDs[I] := TSpeedButton(FindComponent(Format('LED%d', [I])));
    LEDs[I].Tag := I;
  end;
end;

procedure TMainForm.FormActivate(Sender: TObject);
begin
  // the first OnDeviceChange could not access the controls
  // so it is repeated here 
  UpdateControls;
end;

procedure TMainForm.UpdateControls;
var
  I: Integer;
begin
  // OnDeviceChange happens before OnCreate so the LEDs are not assigned yet
  if Assigned(LEDs[Low(LEDs)]) then
  begin
    // enable/disable the controls
    for I := Low(LEDs) to High(LEDs) do
    begin
      LEDs[I].Enabled := Assigned(IOWarrior);
      if not Assigned(IOWarrior) then
        LEDs[I].Down := False;
    end;
    BlockingRead.Enabled := Assigned(IOWarrior);

    if Assigned(IOWarriorDetected) then
      if Assigned(IOWarrior) then
        IOWarriorDetected.Caption := 'IO-Warrior is plugged in'
      else
        IOWarriorDetected.Caption := 'No IO-Warrior is plugged in';
  end;
end;

function FindIOWarrior(HidDev: TJvHidDevice): Boolean; stdcall;
begin
  // the IO-Warrior shows up as two devices
  // we want access to the IO-Warrior device for the IO pins
  // the other one with a InputReportByteLength of 8 is for access to
  // the optional LCD module
  Result :=
    (HidDev.Attributes.VendorID = cCodeMercenariesVID) and
    (HidDev.Attributes.ProductID = cIOWarriorPID) and
    (HidDev.Caps.InputReportByteLength = 5);
end;

procedure TMainForm.HidCtlDeviceChange(Sender: TObject);
var
  I: Integer;
  BytesWritten: Cardinal;
begin
  // Free the device object if it has been unplugged
  if Assigned(IOWarrior) and not IOWarrior.IsPluggedIn then
    FreeAndNil(IOWarrior);

  // if no IO-Warrior in use yet then search for one
  if not Assigned(IOWarrior) then
    if HidCtl.CheckOutByCallback(IOWarrior, FindIOWarrior) then
    begin
      // initialize the output report
      IOWarriorOutputReport.ReportID := 0;
      // the IO-Warrior LEDs use negative logic
      for I := Low(IOWarriorOutputReport.IOBits) to High(IOWarriorOutputReport.IOBits) do
        IOWarriorOutputReport.IOBits[I] := $FF;
      // write the bits to the IO-Warrior to reset the LEDs
      IOWarrior.WriteFile(IOWarriorOutputReport, SizeOf(IOWarriorOutputReport), BytesWritten);
    end;

  // update the controls on the form
  UpdateControls;
end;

procedure TMainForm.LEDClick(Sender: TObject);
var
  LedIdx: Integer;
  BytesWritten: Cardinal;
begin
  // use the Tag assigned in FormCreate
  LedIdx := (Sender as TSpeedButton).Tag;

  // translate SpeedButton state into correct bit in IOBits[3]
  // IO-Warrior uses negative logic
  if LEDs[LedIdx].Down then
    // set the bit to 0 to switch the LED on
    IOWarriorOutputReport.IOBits[cLEDByte] := IOWarriorOutputReport.IOBits[cLEDByte] and not (1 shl LedIdx)
  else
    // set the bit to 1 to switch the LED off
    IOWarriorOutputReport.IOBits[cLEDByte] := IOWarriorOutputReport.IOBits[cLEDByte] or (1 shl LedIdx);

  // write the bits to the IO-Warrior
  IOWarrior.WriteFile(IOWarriorOutputReport, SizeOf(IOWarriorOutputReport), BytesWritten);
end;

procedure TMainForm.BlockingReadClick(Sender: TObject);
var
  I: Integer;
  BytesRead: Cardinal;
  IOWarriorInputReport: TIOWarriorIOReport;
begin
  InputBits.Caption := '';
  if IOWarrior.ReadFile(IOWarriorInputReport, SizeOf(IOWarriorInputReport), BytesRead) then
    for I := Low(IOWarriorInputReport.IOBits) to High(IOWarriorInputReport.IOBits) do
      InputBits.Caption := InputBits.Caption + OrdToBinary(IOWarriorInputReport.IOBits[I]);
end;

end.
