///////////////////////////////////////////////////////////////////////////
// Project:   ICOM
// Program:   Icoms.pas
// Language:  Object Pascal - Delphi Pro ver 5.0
// Support:
// Date:      21February06
// Purpose:   This unit implements an object that manages the current
//            state of many ICOM transceivers and for decoding messages
//            that are received from the radio over the serial interface.
// History:   07Nov97  Initial coding  by David Fahey for a receiver project
//            coded with Delphi 3
//            13Feb06 Modified to work with ICOM Amateur Radio transceivers
///////////////////////////////////////////////////////////////////////////

unit Icomstuff;

interface

uses
  Windows, SysUtils  ;

type
  TRMode = (trmLSB, trmUSB, trmAM, trmAMNarrow,
            trmAMWide, trmCW, trmCWNarrow,
            trmFM, trmFMNarrow, trmWFM) ;

  TRNB =(trmON, trmOFF);

  TPreAmble = array[1..2] of Byte ;

const
  DefFreq:    integer = 1000000 ;
  DefMode:    TRMode  = trmUSB ;
  DefNB:       TRNB = trmOFF;

  DefStep:    integer = 10000 ;

  RadioAddr: Byte =($58) ;               { Default 58 - Imodel}
  PCAddr: Byte = ($00);

  StdStartMsg: TPreAmble = ($FE, $FE) ;
  StdEndMsg:   byte = $FD ;

type

  Icom = class(TObject)
    private
    { Private declarations }
    InputStream: string ;  { tail of the current input msg stream }
    RadioAddr: byte ;      { radios address byte }
    PCAddr: byte ;         { computers address byte }
    Freq: integer ;        { operating frequency }
    Mode: TRMode ;         { reception mode }
    Step: integer ;        { tuning frequency increment }
    NB: TRNB    ;          { noise blanker ON or OFF }

    public
    { Public declarations }
    constructor Create ; virtual ;
    { These functions return a string that contains }
    { messages formatted for serial transmission to the radio }
    function SetFreq(f: integer): string ;
    function IncFreq: string ;
    function DecFreq: string ;

    function SetStep(st: integer): string ;
    function ToggleNB: string ;
    function SetNB(m: TRNB): string ;
    function SyncRadio: string ;
    procedure ResetToDefaults ;
    procedure DecodeInputStream(instream: string) ;
    function GetStepValue: integer ;

end ; { of Icom }

implementation

type
  IcomSetFreq = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { 05 - Set Freq }
    Freq: array[1..5] of byte ;   { frequency }
    EndMsg: Byte ;                { FD }
  end ;

  IcomSetMode = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { 06 - Set Mode }
    Mode: Byte ;                  { Mode }
    SubMode: Byte ;               { Sub mode}
    EndMsg: Byte ;                { FD }
  end ;

    IcomSetNB = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { 16 - Set Mode }
    NB: Byte ;                  { 22 Mode }
    SubCmd: Byte ;               { Sub mode 00 off   01 on}
    EndMsg: Byte ;                { FD }
  end ;

  IcomSetStep = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { 10 - Set Step }
    SubCmd: Byte ;                { 13 - programmable step }
    Step: array[1..2] of byte ;   { step frequency }
    EndMsg: Byte ;                { FD }
  end ;

  IcomSetSimple = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { Command }
    SubCmd: Byte ;                { Sub command }
    EndMsg: Byte ;                { FD }
  end ;

  IcomSetLevel = record
    StartMsg: TPreAmble ;         { FE,FE }
    R85Addr: Byte ;               { Default 58 - Imodel}
    PCAddr: Byte ;                { E0 }
    Cmd: Byte ;                   { Command }
    SubCmd: Byte ;                { Sub command } 
    Level: array[1..2] of byte ;  { level }
    EndMsg: Byte ;                { FD }
  end ;


function MemCopyToStr(p: pointer; len:integer):string ;
var
  i: integer ;
  t: string ;
  pc: PChar ;
begin
  pc := PChar(p) ;
  SetLength(t,len) ;
  for i := 0 to len-1 do
    t[i+1] := pc[i] ;
  Result := t ;
end ;

constructor Icom.Create ;
begin
  inherited Create ;
  ResetToDefaults ;
end ;

function Icom.SetFreq(f: integer):string ;
var
  r: IcomSetFreq ;
  v: integer ;
  t,u: byte ;
  fok: boolean ;
begin
  fok := True ;

  if f < 300000 then    {30 khz start of IC706MKII coverage }
    fok := False ;
  if f > 199999999 then  { 199.9 Mhz end of IC706MKII coverage }
    fok := False ;
  if fok then
    begin
      Freq := f ;   { remember new freq in this object }
      { create message prolog }
      r.StartMsg := StdStartMsg ;
      r.R85Addr := RadioAddr ;
      r.PCAddr := PCAddr ;
      r.Cmd := $05 ; { Set Freq command }

      { build reversed BCD frequency from integer }
      v := f ;
      t := v mod 10 ;           { Hz part }
      u := (v div 10) mod 10 ;  { 10 Hz part }
      r.Freq[1] := (u shl 4) or t ;

      v := f div 100 ;
      t := v mod 10 ;           { 100 Hz part }
      u := (v div 10) mod 10 ;  { 1,000 Hz part }
      r.Freq[2] := (u shl 4) or t ;

      v := f div 10000 ;
      t := v mod 10 ;           { 10,000 Hz part }
      u := (v div 10) mod 10 ;  { 100,000 Hz part }
      r.Freq[3] := (u shl 4) or t ;

      v := f div 1000000 ;
      t := v mod 10 ;           { 1,000,000 Hz part }
      u := (v div 10) mod 10 ;  { 10,000,000 Hz part }
      r.Freq[4] := (u shl 4) or t ;

      v := f div 100000000 ;
      t := v mod 10 ;           { 100,000,000 Hz part }
      u := (v div 10) mod 10 ;  { 1,000,000,000 Hz part }
      r.Freq[5] := (u shl 4) or t ;

      { message tail }
      r.EndMsg := StdEndMsg ;
      result := MemCopyToStr(addr(r),SizeOf(r)) ;
    end
  else
    result := '' ;  { error }
end ;



function Icom.IncFreq:string ;
begin
  result := SetFreq(Freq+Step) ;
end ;

function Icom.DecFreq:string ;
begin
  result := SetFreq(Freq-Step) ;
end ;

function SetMode(m: TRMode):string ;
var
  r: IcomSetMode ;
  mok: boolean ;
begin
  mok := True ;
  { create message preample }
  r.StartMsg := StdStartMsg ;
  r.R85Addr := RadioAddr ;
  r.PCAddr := PCAddr ;
  r.Cmd := $06 ; //$06 Set Freq command
  { message tail }
  r.EndMsg := StdEndMsg ;
  case m of
    trmLSB:
      begin
        r.Mode := $00 ;     //works
        r.SubMode := $01 ;
      end ;
    trmUSB:
      begin
        r.Mode := $01 ;     //works
        r.SubMode := $01 ;
      end ;
    trmAM:
      begin
        r.Mode := $02 ;  //works
        r.SubMode := $01 ;
      end ;
    trmAMNarrow:
      begin
        r.Mode := $02 ;
        r.SubMode := $00 ;
      end ;
    trmAMWide:
      begin
        r.Mode := $02 ;
        r.SubMode := $03 ;
      end ;
    trmCW:
      begin
        r.Mode := $03 ;
        r.SubMode := $01 ;
      end ;
    trmCWNarrow:
      begin
        r.Mode := $03 ;
        r.SubMode := $01 ;
      end ;
    trmFM:
      begin
        r.Mode := $05 ;
        r.SubMode := $01 ;
      end ;
    trmFMNarrow:
      begin
        r.Mode := $05 ;
        r.SubMode := $06 ;
      end ;
    trmWFM:
      begin
        r.Mode := $06 ;
        r.SubMode := $01 ;
      end ;
  else mok := False ;
  end ;  { case }

  if mok then
    begin
      result := MemCopyToStr(addr(r),SizeOf(r)) ;
      Mode := m ;
    end
  else
    result := '' ;   // error
end ;









function Icom.SetStep(st: integer):string ;
var
  r: IcomSetStep ;
  e: IcomSetSimple ;
  v: integer ;
  t,u: byte ;
begin

  if (st > 10) and (st <= 100) then  { programmable range }
    begin
     ///???  r.SubCmd := $13 ;
      { build reversed BCD frequency from integer }
      v := st div 100 ;
      t := v mod 10 ;           { 100 Hz part }
      u := (v div 10) mod 10 ;  { 1,000 Hz part }
      r.Step[1] := (u shl 4) or t ;

      v := st div 10000 ;
      t := v mod 10 ;           { 10,000 Hz part }
      u := (v div 10) mod 10 ;  { 100,000 Hz part }
      r.Step[2] := (u shl 4) or t ;

      Step := st ;   { remember new step in this object }
      { create message prolog }
      r.StartMsg := StdStartMsg ;
      r.R85Addr := RadioAddr ;
      r.PCAddr := PCAddr ;
      r.Cmd := $10 ; { Set Step command }

      { message tail }
      r.EndMsg := StdEndMsg ;
      result := MemCopyToStr(addr(r),SizeOf(r)) ;
    end
  else if (st = 10) or (st = 50)
       or (st = 100) or (st = 1000000) then  { enum range }
    begin
      if st = 10 then    { step = 10 Hz }
        e.SubCmd := $00
      else if st = 100 then    { step = 100 Hz }
        e.SubCmd := $01       
      else if st = 1000 then    { step = 1k Hz }
        e.SubCmd := $02
      else if st = 5000 then    { step = 5k Hz }
        e.SubCmd := $03
      else if st = 9000 then    { step = 9k Hz }
        e.SubCmd := $04
      else if st = 10000 then    { step = 10k Hz }
        e.SubCmd := $05
      else if st = 12500 then    { step = 12.5k Hz }
        e.SubCmd := $06
      else if st = 20000 then    { step = 20k Hz }
        e.SubCmd := $07
      else if st = 25000 then    { step = 25k Hz }
        e.SubCmd := $08
      else if st = 100000 then    { step = 100k Hz }
        e.SubCmd := $09 ;
  
      Step := st ;   { remember new step in this object }
      { create message prolog }
      e.StartMsg := StdStartMsg ;
      e.R85Addr := RadioAddr ;
      e.PCAddr := PCAddr ;
      e.Cmd := $10 ; { Set Step command }

      { message tail }
      e.EndMsg := StdEndMsg ;
      result := MemCopyToStr(addr(e),SizeOf(e)) ;
    end
  else
    result := '' ;  { error }
end ;

function Icom.ToggleNB: string ;
begin
//  if NB ='ON' then   { currently on - turn it off }
 //   NB := False
 // else
 //   NB ='OFF' := True ;
 // result := SetNB(NB) ;
end ;

function Icom.SetNB(m: TRNB):string ;
var
  r: IcomSetNB ;
  mok: boolean ;
begin
  mok := True ;
  { create message prolog }
  r.StartMsg := StdStartMsg ;
  r.R85Addr := RadioAddr ;
  r.PCAddr := PCAddr ;
  r.Cmd := $16 ; { switch command }
  { message tail }
  r.EndMsg := StdEndMsg ;
 case m of
    trmON:
    begin
      r.NB := $22;
      r.SubCmd := $01 ;   { on }
    end;
   trmOFF:
     begin
      r.NB:= $22;
      r.SubCmd := $00 ;   { off }
    end ;
    else mok := False ;
    end;//case

  if mok then
    begin
      result := MemCopyToStr(addr(r),SizeOf(r)) ;
      NB := m ;
    end
  else
    result := '' ;   { error }
end ;


function Icom.SyncRadio: string ;
var
  t: string ;
begin
  t := SetFreq(Freq) ;
///  t := t + SetMode(Mode) ;
  t := t + SetStep(Step) ;
  t := t + SetNB(NB) ;
  Result := t ;
end ;

procedure Icom.ResetToDefaults ;
var
Imodel2:variant;
begin
 //Imodel2:=CommandandControlU.Label6.caption;

  InputStream := '' ;     { tail of the current input msg stream }
 RadioAddr := $58 ;      { radios address byte (default x'4A')}
  PCAddr := $E0 ;         { computers address byte (default x'E0')}
  Freq := 10000000 ;       { operating frequency  1 MHz }
  Mode := trmUSB ;         { reception mode USB (upper sideband) }
  Step := 1000 ;          { tuning frequency increment 10 KHz }
  NB := trmOFF ;           { noise blanker ON or OFF }
end ;

procedure Icom.DecodeInputStream(instream: string) ;
var
  p: integer ;
  a,b,c: byte ;
  len: integer ;
  sig: byte ;
begin
  { add any new data to tail of buffer }
 ///// InputStream := InputStream + instream ;

  { process messages }
  len := length(InputStream) ;
  while len > 0 do
    begin
      { remove any invalid data from head of buffer }
      while ((Byte(InputStream[1])<>$FE)
        and (Byte(InputStream[2])<>$FE)) do
        InputStream := copy(InputStream,2,length(InputStream)-1) ;

      p := pos(Chr($FD),InputStream) ;
      if p > 0 then     { complete msg exists ? }
        begin
          if Byte(InputStream[4]) = RadioAddr then  { message from radio to PC }
            begin
              if (Byte(InputStream[5]) = $04)then // and   //read display mode{ read signal level msg ? }
               /// (Byte(InputStream[6]) = $00) then     // $02
                begin
                  { convert BCD signal level to integer }
                  a := Byte(InputStream[7]) ;
                  b := Byte(InputStream[8]) ;
                  { NB: The following statements should remain single }
                  {     statements. Do not combine. Apparent compiler }
                  {     error causes incorrect results }
                  a := a shl 4 ;  { get hundreds part only }
                  a := a shr 4 ;
                  c := b shl 4 ;  { get units part }
                  c := c shr 4 ;
                  b := b shr 4 ;  { get tens part }
                  sig := a*100 + b*10 + c ;  { join the parts }

            end ; { of message from radio to PC }
            end;
          { remove processed msg from front of input stream }
          InputStream := copy(InputStream,p+1,length(InputStream)-p) ;
          len := length(InputStream) ;
        end   { of complete msg exists }
      else
        exit ;
    end ; // of input stream has data    
end ;



function Icom.GetStepValue: integer ;
begin
  Result := Step ;
end ;


end.
