unit RF10AXLu; { Implementation of Chromulan driver methods for RF10AXL device }

{ see GenDevu.pas for template }

interface
uses
  UlanType, RF10AXLType;

{ The following exported function will be called by Chromulan, either upon
  program startup or during acquistion (for the channel, which have assigned
  this device). The driver must have implemented all the functions, that
  are not marked "Optional" in the comment.

  The fucntions return 0 upon success, negative number upon error. See
  UlanType.pas edrXXXX section for their description.

  If the function returns 1, it means, that the driver wants to send some
  data to the device, so Chromulan will call ExtDevGetStr(epPktReqStr)
  and will send the returned data to the device. }



{ The first method to be called - load, initialize the driver, get handle
  of the driver - AExtDevDrv. AName as a string that can be assigned
  in Chromulan in browser of devices. This device will appear in the list,
  if its DLL is found in dev subdirectory of Chromulan installation directory
  (and if the DLL has all the following methods exported). }
function ExtDevInit(var AExtDevDrv: PExtDevDrv; const AName: shortstring): integer; export;

{ Optional. }
function ExtDevDoAction(AExtDevDrv: PExtDevDrv; ea: TExtDevAction; AInfo: longint): integer;export;

{ Optional. Can be used to set some properties of the driver/device. }
function ExtDevSetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; const S: shortstring):integer; export;

{ Optional. If the device needs some kind of input from device driver, then
  this method must be implemented and should return non empty property
  epPktReqStr (see UlanType.pas epXXXX section). The function is then called
  with epPktReqStr parameter upon acquisition start, after some data received
  or every second. }
function ExtDevGetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; var S: shortstring):integer; export;

{ Called when acquisition started. }
function ExtDevStart(AExtDevDrv: PExtDevDrv): integer; export;

{ Called when acquisition stopped. }
function ExtDevStop(AExtDevDrv: PExtDevDrv): integer; export;

{ Called during acquisition when a character ch comes through the device's port
  (i.e., all characters are read by Chromulan and given (forwarded) to the
  device driver using this method.) }
function ExtDevDoCharIn(AExtDevDrv: PExtDevDrv; ch:char): integer; export;

{ Called by Chromulan to retrieve data point (assembled by the driver from
  characters obtained by ExtDevDoCharIn methods), should return 0 if some point
  is available, assign it to APoint and remove the point from the internal buffer.
  If no point is available return -1. }
function ExtDevReadPoint(AExtDevDrv: PExtDevDrv; var APoint: TExpPoint): integer; export;

{ Deinitialize, unload the driver.}
function ExtDevDone(var AExtDevDrv: PExtDevDrv): integer; export;


procedure test;

implementation
uses GenDevu, binhex;

{Utl}
{ Calculates RF checksum for given txt data (adds EXT) }
function RFCalcChecksum(const txt: TRFTxt): byte;
var i: integer;
begin
  Result := ord(ETX);
  for i := 1 to length(txt) do begin
    inc(Result, ord(txt[i]));
  end;
end;

{ Calculates checksum, fills start and stop chars, get the data ready
  for transmission. }
procedure RFDataFinalize(var RFData: TRFData);
begin
  with RFData do begin
    start := STX;
    stop := ETX;
    sum := RFCalcChecksum(txt);
  end;
end;

{ Returns description of error coder returned by RF10AXL. ErrorCode type used
  intentionally integer, so that range check error won't appear for some nonsense
  Error code. }
function RFGetErrorDesc(ErrorCode: integer): TRFErrorDesc;
var i: integer;
begin
  Result := 'unknown';
  for i := 0 to RFErrorCount - 1 do begin
    if RFErrors[i].code = ErrorCode then begin
      Result := RFErrors[i].desc;
      exit;
    end;
  end;
end;

function RFPktCreate(txt: TRFTxt): string;
var data: TRFData;
begin
  data.txt := txt;
  RFDataFinalize(data);
  Result := data.start + data.txt + data.stop + chr(data.sum);
end;
{/Utl}

{Tests}
procedure testchecksum;
var h, s: string;
c:byte;
begin
//0220202020302E32330346
  h := '20202020302E3233';
  s := HexToString('20202020302E3233');
  c  := RFCalcCheckSum(s);
  writeln('Checksum for text ' + h + ' is ', c, ' ', bytetohex(c));
end;

procedure testerrors;
const
  errors: array[0..RFErrorCount - 1] of TRFErrorCode = (1,2,4,5,9,10,11,132,133,144,254,255);
var
  i: integer;
  code: integer;

  procedure wr(code:integer);
  begin
    writeln('Error Code=', code, ' Desc=', RFGetErrorDesc(code));
  end;

begin
  for i := 0 to RFErrorCount - 1 do begin
    code := errors[i];
    wr(code);
  end;
  wr(500);
end;

procedure test;
var rf: TRFData;
begin
  rf.txt := 'WX400';
  RFDataFinalize(rf);
  writeln(rf.sum);
  writeln('Size of TRFFileHead:', sizeof(TRFFileHead));
  testerrors;
  testchecksum;
  readln;
end;
{/Tests}

{TRF10}
type
  TRF10 = class(TGenDev)
  protected
    { Check if the data in FCurPkt is already completed packet.
      Override to do something.}
    function IsInputPacketFinished: boolean; override;

    procedure DoAfterCreate; override;

    { Extract from completed FCurPkt next data value, assign it to FCurVal,
      return true is there was some value, otherwise false. Called repeatedly,
      i.e. if in one packet contains more values, some returned points counter
      should be set to 0 in IsInputPacketFinished method. }
    function ExtractCurVal: boolean; override;

  end;
{/TRF10}

{TRF10.}
procedure TRF10.DoAfterCreate;
begin
  FPktReqStr := ENQ; { create packet enquiring the device (it must answer ACK if present) }
  FMaxPktLen := 131;
end;

{ Check if the data in FCurPkt is already completed packet.
  Override to do something.}
function TRF10.IsInputPacketFinished: boolean;
var
  pktLen: integer;
  calcedSum: byte;
  inPktSum: byte;
  tx: string;
begin
  Result := false;
  if (FCurPkt <> '') then begin
    if FCurPkt[1] = ENQ then begin
       { Device sending enquire signal, we must answer with acq signal. There
         might be more ENQ chars, just ignore the other ones. }
       Log('ENQ rcvd');
       FPktReqStr := ACK;
       FCurPkt := ''; {no exp.data in the received packet, clear it}
       Result := true;
       exit;
    end else if FCurPkt[1] = EOT then begin
      { End of transmission of data from device. Enquire for next communicaton: }
      Log('EOT rcvd');
      FPktReqStr := ENQ;
      FCurPkt := ''; {no exp.data in the received packet, clear it}
      Result := true;
      exit;
    end else if FCurPkt[1] = STX then begin
      if length(FCurPkt) > 3 then begin
        pktLen := length(FCurPkt);
        if (FCurPkt[pktLen - 1] = ETX) then begin
          { packet is complete, check for checksum }
          tx := copy(FCurPkt, 2, pktLen - 3);
          calcedSum := RFCalcCheckSum(tx);
          inPktSum := ord(FCurPkt[pktLen]);
          if  calcedSum = inPktSum then begin
            { checksum ok, send acknowledged }
            Log('STX pkt ok: ' + PktLogStr(FCurPkt));

            FPktReqStr := ACK;
            Result := true;
            exit;
          end else begin;
            { checksum is wrong, send not-acknowledged }
            Log('STX pkt failed: ' + PktLogStr(FCurPkt));
            FPktReqStr := NAK;
            FCurPkt := ''; {wrong exp.data in the packet, clear them}
            Result := true;
            exit;
          end;
        end else begin
          { no STX char yes - continue receiving chars }
          exit;
        end;
      end else begin
        { not enough chars in packet yet }
        exit;
      end;
    end else if FCurPkt[1] = ACK then begin
      { the device ready for sending it the data/command packet }
      Log('ACK rcvd. LastReqPkt:' + PktLogStr(FLastPktReqStr));
      if FLastPktReqStr = ENQ then begin
        { the device is confirming its presence - request the current emission wavelength)}
        FPktReqStr := RFPktCreate(cmV);
        FCurPkt := '';
        Result := true;
        exit;
      end else begin
         { the device confirmed reception of the data packet, terminate request:}
         FPktReqStr := EOT;
         FCurPkt := '';
         Result := true;
         exit;
      end;
    end else if FCurPkt[1] = NAK then begin
       { the device answered not-acknowledged, get ready to resend the packet again. }
       Log('NAK rcvd');
       FPktReqStr := FLastPktReqStr;
       FCurPkt := '';
       Result := true;
       exit;
    end else begin
      { Wrong packet format. Send NAK to ask device to resend the packet. }
      Log('??? rcvd: ' + PktLogStr(FCurPkt));

      FPktReqStr := NAK;
      Result := true;
      exit;
    end;
  end;
end;

{ Extract from completed FCurPkt next data value, assign it to FCurVal,
  return true is there was some value, otherwise false. Called repeatedly,
  i.e. if in one packet contains more values, some returned points counter
  should be set to 0 in IsInputPacketFinished method. }
function TRF10.ExtractCurVal: boolean;
var
  code: integer;
begin
  Result := false;
  if length(FCurPkt) = 11 then begin
    {STX "  999.99" ETX Sum}
    val(copy(FCurPkt, 2, 8), FCurVal, code);
    Result := code = 0;
    FCurPkt := '';
  end;
end;
{/TRF10.}


{ExtDevXXXX}
function ExtDevInit(var AExtDevDrv: PExtDevDrv; const AName: shortstring): integer;export;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  try
    adrv := TRF10.Create(nil, AName);
    {    Result := edrUnsupportedDeviceType;}
    Result := edrOK;
  except
    adrv := nil;
    Result := edrDevicesInitFailed;
  end;
end;

function ExtDevSetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; const S: shortstring):integer; export;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.SetStr(ep, s);
end;

function ExtDevGetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; var S: shortstring):integer; export;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.GetStr(ep, s);
  {
  else begin
    case ep of
      epDeviceList: begin
        if Devices <> nil then
          s := Devices.DeviceList;
      end;
    else
      Result := edrInvalidGetStrProp;
    end;
  end;
  }
end;

function ExtDevDoAction(AExtDevDrv: PExtDevDrv; ea: TExtDevAction;
  AInfo: longint): integer;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.DoAction(ea, AInfo);
end;


function ExtDevDone(var AExtDevDrv: PExtDevDrv): integer;export;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then begin
    adrv.Free;
    AExtDevDrv := nil;
    Result := 0;
  end;
end;

function ExtDevStart(AExtDevDrv: PExtDevDrv): integer;
var
  adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.Start;
end;

function ExtDevStop(AExtDevDrv: PExtDevDrv): integer;
var adrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.Stop;
end;

function ExtDevDoCharIn(AExtDevDrv: PExtDevDrv; ch:char): integer;
var
  ADrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if ADrv <> nil then
    Result := ADrv.DoCharIn(ch);
end;

function ExtDevReadPoint(AExtDevDrv: PExtDevDrv; var APoint: TExpPoint): integer;
var
  ADrv: TRF10 absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if ADrv <> nil then
    Result := ADrv.ReadPoint(APoint);
end;
{/ExtDevXXXX}

end.
