unit u_messages;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, graphics;

var
  ArduinoIP: shortstring;

// for data processing independent of CenterPass_Mag or CenterPass_Cap:
  TPassCenter_ticks: word;
  PeriodTime_ticks: longword;
  SeenCenter: boolean;

// for OutMessage
  OutMessage: ansistring;
  Command: longword;

// for InMessage
  InMessage: shortstring;
  Status: longword;
  FirmwareVersionNumber, FirmwareBuildDate: integer;
  MessageNumber, PrevMessageNumber: byte;
  // Capacitive Center pass detection
  TPassCenter_Cap, WidthCenter_Cap, PrevTPassCenter_Cap,
  Adc_Center_Cap, ABaseCenter_Cap, APeakCenter_Cap, HalfHeightCenter_Cap: word;
  PeriodTime_Cap_ticks: longword;
  // Magnetic Center pass detection
  Adc_Center_Mag, APeakCenter_Mag, AMidCenter_Mag ,
  TPassCenter_Mag, PrevTPassCenter_Mag,
  PeriodTime_Mag_ticks: longword;
  // Capacitive Rim Pass detection
  Adc_Rim_Cap, // Currently not further supported
  // Magnetic Rim Pass detection
  Adc_Rim_Mag, AMidRim_Mag,
  APeakRim1_Mag, TPassRim1_Mag,
  APeakRim2_Mag, TPassRim2_Mag,
  PrevTPassRim1_Mag,
  PrevAPeakRim1_Mag : word;
  TDiffRim1_Mag,
  ADiffRim1_Mag: integer;
  // Resonance Mode
  DDS_FrequencyWord_Changed: longword;
  TResonanceDrive: word;
  // PMS
  Adc_North, Adc_South, Adc_East, Adc_West,
  Center_North, Center_South, Center_East, Center_West,
  PositionCounter: word;
  // Environment
  BME_Temperature, BME_Hygro, BME_Baro: smallint;
  // Booleans from Status Word
  SeenCenter_Cap, Missedcenter_Cap,
  SeenCenter_Mag, Missedcenter_Mag,
  HaveSeenCenter_Cap, HaveSeenCenter_Mag,
  OverRangeCenter_Cap, OverRangeCenter_Mag,
  SeenRim1_Mag, MissedRim1_Mag,
  SeenRim2_Mag, MissedRim2_Mag,
  OverRangeRim1_Mag, OverRangeRim2_Mag,
  HalfSwing, HaveSync, Touch_Charron,
  DidResetMyself, DidReSyncMyself,
  BME280Error, GeneralError,
  DDS_FrequencyChanged: boolean;
  // For data presentation
  LedCounter_SeenCenter_Cap, LedCounter_MissedCenter_Cap,
  LedCounter_SeenCenter_Mag, LedCounter_MissedCenter_Mag,
  LedCounter_SeenRim1_Mag, LedCounter_MissedRim1_Mag,
  LedCounter_SeenRim2_Mag, LedCounter_MissedRim2_Mag,
  LedCounter_DDS_FrequencyChanged,
  LedCounter_OverRangeCenter_Cap,
  LedCounter_OverRangeCenter_Mag,
  LedCounter_OverRangeRim1_Mag,
  LedCounter_OverRangeRim2_Mag:byte;
  TPassCenterDiff_Cap, TPassCenterDiff_Mag: integer;

 // We list all functions and procedures in this unit
 // exept those in the form's class
 // in the order they appear below.
procedure SendAndReceiveMessage;
procedure StartTalking;
procedure StopTalking;
procedure Init_Messages;

implementation

uses forms, u_large_adc, blcksock, u_main, u_calc,
     u_logging, u_numstrings, u_timedisplay, u_timestrings;

const
  ARDUINOPORT = '8888';
  OUTMESSAGESIZE = 40;
  INMESSAGESIZE = 75;
  StatusDisplayTime = 4; // defines time of indication

var
  UDP: tUDPBlockSocket;
  OutCount, InCount: byte;

procedure ProcessInMessage;
begin
  with FrmMain do
  begin
    // Take values from the message
    // Byte numbering is identical to Arduino C++
    move (InMessage[ 1], FirmwareVersionNumber, 2);
    move (InMessage[ 3], Status, 4);
    // Capacitive Center pass detection
    move (InMessage[ 7], Adc_Center_Cap, 2);
    move (InMessage[ 9], APeakCenter_Cap, 2);
    move (InMessage[11], ABaseCenter_Cap, 2);
    move (InMessage[13], TPassCenter_Cap, 2);
    move (InMessage[15], HalfHeightCenter_Cap, 2);
    move (InMessage[17], WidthCenter_Cap, 2);
    // Magnetic Center pass detection
    move (InMessage[19], Adc_Center_Mag, 2);
    move (InMessage[21], APeakCenter_Mag, 2);
    move (InMessage[23], AMidCenter_Mag, 2);
    move (InMessage[25], TPassCenter_Mag, 2);
    // Capacitive Rim Pass detection
    move (InMessage[27], Adc_Rim_Cap, 2); // currently not further implemented
    // Magnetic Rim Pass detection
    move (InMessage[29], Adc_Rim_Mag, 2);
    move (InMessage[31], AMidRim_Mag, 2);
    move (InMessage[33], APeakRim1_Mag, 2);
    move (InMessage[35], TPassRim1_Mag, 2);
    move (InMessage[37], APeakRim2_Mag, 2);
    move (InMessage[39], TPassRim2_Mag, 2);
    // PMS data at sending the message
    move (InMessage[41], Adc_North, 2);
    move (InMessage[43], Adc_South, 2);
    move (InMessage[45], Adc_East, 2);
    move (InMessage[47], Adc_West, 2);
    move (InMessage[49], PositionCounter, 2);
    // PMS data frozen at CenterPass
    move (InMessage[51], Center_North, 2);
    move (InMessage[53], Center_South, 2);
    move (InMessage[55], Center_East, 2);
    move (InMessage[57], Center_West, 2);
    // For Resonance Drive
    move (InMessage[59], DDS_FrequencyWord_Changed, 4);
    move (InMessage[63], TResonanceDrive, 2);
    // Environment
    move (InMessage[65], BME_Temperature, 2);
    move (InMessage[67], BME_Baro, 2);
    BME_Hygro:= byte (InMessage[69]);
    // integrity check
    MessageNumber:= byte (InMessage[70]);

    // Check Message integrety
    if MessageNumber <> PrevMessageNumber then // we missed one or more
    begin
      AddToEventLog (format ('Missed Message nr= %3d prev= %3d', [MessageNumber, PrevMessageNumber]));
      MemoAdd       (format ('Missed Message nr= %3d prev= %3d', [MessageNumber, PrevMessageNumber]));
    end;
    {$R-} PrevMessageNumber:= succ (MessageNumber); {$R+}  // allow byte rollover

    // Process data from message
    FirmwareBuildDate:= FirmwareVersionNumber + 250000;

    // Status word
    // bit 0..3
    if (Status and $00000001) = $00000001 then SeenCenter_Mag:= true;
    if (Status and $00000002) = $00000002 then MissedCenter_Mag:= true;
    if (Status and $00000004) = $00000004 then SeenCenter_Cap:= true;
    if (Status and $00000008) = $00000008 then MissedCenter_Cap:= true;
    // bit 4..7
    if (Status and $00000010) = $00000010 then SeenRim1_Mag:= true;
    if (Status and $00000020) = $00000020 then MissedRim1_Mag:= true;
    if (Status and $00000040) = $00000040 then SeenRim2_Mag:= true;
    if (Status and $00000080) = $00000080 then MissedRim2_Mag:= true;
    // bit 8..11
    HalfSwing:=               (Status and $00000100) = $00000100;
    HaveSync:=                (Status and $00000200) = $00000200;
    //
    Touch_Charron:=           (Status and $00000800) = $00000800;
    // bit 12..15
    UseMaximalDriveCurrent:=  (Status and $00001000) = $00001000;
    UseMinimalDriveCurrent:=  (Status and $00002000) = $00002000;
    //
    if (Status and $00008000) = $00008000 then DDS_FrequencyChanged:= true;
    // bit 16..19
    OverRangeCenter_Cap:=     (Status and $00010000) = $00010000;
    OverRangeCenter_Mag:=     (Status and $00020000) = $00020000;
    OverRangeRim1_Mag:=       (Status and $00040000) = $00040000;
    OverRangeRim2_Mag:=       (Status and $00080000) = $00080000;
    // 2 nibbles not yet used
    // bit 28..31
    DidReSyncMyself:=         (Status and $20000000) = $20000000;
    BME280Error:=             (Status and $40000000) = $40000000;
    GeneralError:=            (Status and $80000000) = $80000000;

    // Display the values
    TbxFirmwareBuildDate.text:=    format ('%6d', [FirmwareBuildDate]);
    TbxAdcCenter_Cap.text:=        format ('%4d', [Adc_Center_Cap]);
    TbxAPeakCenter_Cap.text:=      format ('%4d', [APeakCenter_Cap]);
    TbxABaseCenter_Cap.text:=      format ('%5d', [ABaseCenter_Cap]);
    TbxTPassCenter_Cap.text:=      format ('%5d', [TPassCenter_Cap]);
    TbxHalfHeightCenter_Cap.text:= format ('%5d', [HalfHeightCenter_Cap]);
    TbxWidthCenter_Cap.text:=      format ('%4d', [WidthCenter_Cap]);

    TbxAdcCenter_Mag.text:=        format ('%5d', [Adc_Center_Mag]);
    TbxAPeakCenter_Mag.text:=      format ('%5d', [APeakCenter_Mag]);
    TbxAMidCenter_Mag.text:=       format ('%5d', [AMidCenter_Mag]);
    TbxTPassCenter_Mag.text:=      format ('%4d', [TPassCenter_Mag]);

    TbxAdcRim_Cap.text:=           format ('%4d', [Adc_Rim_Cap]);

    TbxAdcRim_Mag.text:=           format ('%4d', [Adc_Rim_Mag]);
    TbxAMidRim_Mag.text:=          format ('%4d', [AMidRim_Mag]);
    TbxAPeakRim1_Mag.text:=        format ('%4d', [APeakRim1_Mag]);
    TbxAPeakRim2_Mag.text:=        format ('%4d', [APeakRim2_Mag]);
    TbxTPassRim1_Mag.text:=        format ('%4d', [TPassRim1_Mag]);
    TbxTPassRim2_Mag.text:=        format ('%4d', [TPassRim2_Mag]);

    TbxBME_Temperature.text:=      format ('%5.1f', [BME_Temperature / 10]);
    TbxBME_Hygro.text:=            format ('%3d',   [BME_Hygro]);
    TbxBME_Baro.text:=             format ('%6.1f', [BME_Baro/10]);

    // put raw PMS data into the textboxes
    with Frmmain do
    begin
      TbxNorth.text:= format ('%4d', [Adc_North]);
      TbxSouth.text:= format ('%4d', [Adc_South]);
      TbxEast.text:=  format ('%4d', [Adc_East]);
      TbxWest.text:=  format ('%4d', [Adc_West]);
    end;
    if ShowFormLargeADC then with FrmLargeADC do
    begin
      TbxNorth.text:=      format ('%4d', [Adc_North]);
      TbxSouth.text:=      format ('%4d', [Adc_South]);
      TbxEast.text:=       format ('%4d', [Adc_East]);
      TbxWest.text:=       format ('%4d', [Adc_West]);
      TbxCenter_Cap.text:= format ('%4d', [Adc_Center_Cap]);
      TbxCenter_Mag.text:= format ('%4d', [Adc_Center_Mag]);
    end;
    if LogPMS then LogPmsRaw;

    if SeenCenter_Cap then
    begin
      SeenCenter_Cap:= false;
      LedCounter_SeenCenter_Cap:= StatusDisplayTime;
      TPassCenterDiff_Cap:= TPassCenter_Cap - PrevTPassCenter_Cap;
      TimeDisplay_Update_TCenterDiff_Cap;
      PeriodTime_Cap_ticks:= TPassCenter_Cap + PrevTPassCenter_Cap;
      PrevTPassCenter_Cap:= TPassCenter_Cap;
      if MemoShowTDiffCenter_Cap then
        MemoAdd (format ('CC %6d %6d %6d',
          [TPassCenter_Cap, TPassCenterDiff_Cap, PeriodTime_Cap_ticks]));
      if Drive_SyncMode = Drive_SyncByCenter_Cap then
      begin
        PeriodTime_ticks:= PeriodTime_Cap_ticks; // we proceed with this value
        SeenCenter:= true;
        TimeDisplay_Update_Fast;
      end;
    end;

    if SeenCenter_Mag then
    begin
      SeenCenter_Mag:= false;
      LedCounter_SeenCenter_Mag:= StatusDisplayTime;
      HaveSeenCenter_Mag:= true;
      TPassCenterDiff_Mag:= TPassCenter_Mag - PrevTPassCenter_Mag;
      TimeDisplay_Update_TCenterDiff_Mag;
      PeriodTime_Mag_ticks:= TPassCenter_Mag + PrevTPassCenter_Mag;
      PrevTPassCenter_Mag:= TPassCenter_Mag;
      if MemoShowTDiffCenter_Mag then
        MemoAdd (format ('CM %6d %6d %6d',
          [TPassCenter_Mag, TPassCenterDiff_Mag, PeriodTime_Mag_ticks]));
      if Drive_SyncMode = Drive_SyncByCenter_Mag then
      begin
        PeriodTime_ticks:= PeriodTime_Mag_ticks; // we proceed with this value
        SeenCenter:= true;
        TimeDisplay_Update_Fast;
      end;
    end;

    if PositionOnly then exit; // for calibrating the PMS. No other processing done

    TbxPeriodTime_ticks.text:= format ('%5d', [PeriodTime_ticks]);

    ProcessPositionData; // in u_calc

    if MissedCenter_Cap then
    begin
      MissedCenter_Cap:= false;
      LedCounter_MissedCenter_Cap:= StatusDisplayTime;
      AddToLog      ('** Center Pass Cap Missed');
      AddToEventLog ('Center Pass Cap Missed');
      MemoAdd  ('Center Pass_Cap Missed');
    end;

    if SeenCenter_Mag then
    begin
      SeenCenter_Mag:= false;
      LedCounter_SeenCenter_Mag:= StatusDisplayTime;
      HaveSeenCenter_Mag:= true;
      TPassCenterDiff_Mag:= TPassCenter_Mag - PrevTPassCenter_Mag;
      PeriodTime_Mag_ticks:= TPassCenter_Mag + PrevTPassCenter_Mag;
      PrevTPassCenter_Mag:= TPassCenter_Mag;
      if MemoShowTDiffCenter_Mag then
      begin
        TimeDisplay_Update_TCenterDiff_Mag;
        MemoAdd (format ('CM %6d %6d %6d',
          [TPassCenter_Mag, TPassCenterDiff_Mag, PeriodTime_Mag_ticks]));
      end;
      if Drive_SyncMode = Drive_SyncByCenter_Mag then TimeDisplay_Update_Fast;
    end;

    if MissedCenter_Mag then
    begin
      MissedCenter_Mag:= false;
      LedCounter_MissedCenter_Mag:= StatusDisplayTime;
      //AddToLog      ('** Center Pass Mag Missed');
      AddToEventLog ('Center Pass Mag Missed');
      MemoAdd  ('Center Pass_Mag Missed');
    end;

    if SeenRim1_Mag then
    begin
      SeenRim1_Mag:= false;
      LedCounter_SeenRim1_Mag:= StatusDisplayTime;
      TDiffRim1_Mag:= TPassRim1_Mag - PrevTPassRim1_Mag;
      TimeDisplay_Update_TRim1Diff_Mag;
      TbxTDiffRim1_Mag.text:= format ('%5d', [ TDiffRim1_Mag]);
      PrevTPassRim1_Mag:= TPassRim1_Mag;
      ADiffRim1_Mag:= APeakRim1_Mag - PrevAPeakRim1_Mag;
      TimeDisplay_Update_ARim1Diff_Mag;
      PrevAPeakRim1_Mag:= APeakRim1_Mag;
      TbxAPeakRim1Diff_Mag.text:= format ('%5d', [ADiffRim1_Mag]);
      if MemoShowADiffRim1_Mag then
        MemoAdd (format ('APR %5d %5d', [ADiffRim1_Mag, APeakRim1_Mag]));
      if MemoShowTDiffRim1_Mag then
        MemoAdd (format ('TDR %5d %5d', [TPassRim1_Mag, TDiffRim1_Mag]));
    end;

    if MissedRim1_Mag then
    begin
      MissedRim1_Mag:= false;
      LedCounter_MissedRim1_Mag:= StatusDisplayTime;
    end;

    if SeenRim2_Mag then
    begin
      SeenRim2_Mag:= false;
      LedCounter_SeenRim2_Mag:= StatusDisplayTime;
    end;

    if MissedRim2_Mag then
    begin
      MissedRim2_Mag:= false;
      LedCounter_MissedRim2_Mag:= StatusDisplayTime;
    end;

    if DDS_FrequencyChanged then
    begin
      DDS_FrequencyChanged:= false;
      LedCounter_DDS_FrequencyChanged:= StatusDisplayTime;
    end;

    // update Status indicators
    with LblStatusSeenCenter_Mag   do if LedCounter_SeenCenter_Mag   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedCenter_Mag do if LedCounter_MissedCenter_Mag > 0 then color:= clRed else color:= clLime;
    with LblStatusSeenCenter_Cap   do if LedCounter_SeenCenter_Cap   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedCenter_Cap do if LedCounter_MissedCenter_Cap > 0 then color:= clRed else color:= clLime;

    with LblStatusSeenRim1_Mag     do if LedCounter_SeenRim1_Mag   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedRim1_Mag   do if LedCounter_MissedRim1_Mag > 0 then color:= clRed else color:= clLime;
    with LblStatusSeenRim2_Mag     do if LedCounter_SeenRim2_Mag   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedRim2_Mag   do if LedCounter_MissedRim2_Mag > 0 then color:= clRed else color:= clLime;

    with LblStatusMissedRim2_Mag   do if LedCounter_MissedRim2_Mag > 0 then color:= clRed else color:= clLime;

    with LblStatusHalfSwing        do if HalfSwing                     then color:= clRed else color:= clLime;
    with LblStatusHaveSync         do if HaveSync                      then color:= clRed else color:= clLime;
    //
    with LblStatusCharron          do if Touch_Charron                 then color:= clRed else color:= clLime;

    with LblStatusMaxDriveLevel    do if UseMaximalDriveCurrent          then color:= clRed else color:= clLime;
    with LblStatusMinDriveLevel    do if UseMinimalDriveCurrent          then color:= clRed else color:= clLime;
    //
    with LblStatusDDS_FrequencyChanged do if LedCounter_DDS_FrequencyChanged > 0 then color:= clRed else color:= clLime;

    with LblStatusSeenRim1_Mag     do if LedCounter_SeenRim1_Mag   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedRim1_Mag   do if LedCounter_MissedRim1_Mag > 0 then color:= clRed else color:= clLime;
    with LblStatusSeenRim2_Mag     do if LedCounter_SeenRim2_Mag   > 0 then color:= clRed else color:= clLime;
    with LblStatusMissedRim2_Mag   do if LedCounter_MissedRim2_Mag > 0 then color:= clRed else color:= clLime;

    // 2 nibbles of Status not used

    with LblOverRangeCenter_Mag    do if LedCounter_OverRangeCenter_Mag > 0 then color:= clRed else color:= clLime;
    with LblOverRangeCenter_Cap    do if LedCounter_OverRangeCenter_Cap > 0 then color:= clRed else color:= clLime;
    with LblOverRangeRim1_Mag      do if LedCounter_OverRangeRim1_Mag   > 0 then color:= clRed else color:= clLime;
    with LblOverRangeRim2_Mag      do if LedCounter_OverRangeRim2_Mag   > 0 then color:= clRed else color:= clLime;

    //
    with LblStatusDidReSyncMySelf  do if DidReSyncMySelf then color:= clRed else color:= clLime;
    with LblStatusBME280Error      do if BME280Error     then color:= clRed else color:= clLime;
    with LblStatusGeneralError     do if GeneralError    then color:= clRed else color:= clLime;

    // update LedCounters
    if LedCounter_Seencenter_Cap   > 0 then dec (LedCounter_Seencenter_Cap);
    if LedCounter_MissedCenter_Cap > 0 then dec (LedCounter_MissedCenter_Cap);
    if LedCounter_SeenCenter_Mag   > 0 then dec (LedCounter_SeenCenter_Mag);
    if LedCounter_MissedCenter_Mag > 0 then dec (LedCounter_MissedCenter_Mag);

    if LedCounter_SeenRim1_Mag   > 0 then dec (LedCounter_SeenRim1_Mag);
    if LedCounter_MissedRim1_Mag > 0 then dec (LedCounter_MissedRim1_Mag);
    if LedCounter_SeenRim2_Mag   > 0 then dec (LedCounter_SeenRim2_Mag);
    if LedCounter_MissedRim2_Mag > 0 then dec (LedCounter_MissedRim2_Mag);

    if LedCounter_DDS_FrequencyChanged > 0 then dec (LedCounter_DDS_FrequencyChanged);

    if LedCounter_OverRangeCenter_Mag > 0 then dec (LedCounter_OverRangeCenter_Mag);
    if LedCounter_OverRangeCenter_Cap > 0 then dec (LedCounter_OverRangeCenter_Cap);
    if LedCounter_OverRangeRim1_Mag   > 0 then dec (LedCounter_OverRangeRim1_Mag);
    if LedCounter_OverRangeRim2_Mag   > 0 then dec (LedCounter_OverRangeRim2_Mag);
  end;
end;

procedure PrepareOutMessage;
var TDrive_Start, TDrive_Stop: word;
begin
  if Drive_SyncMode <> PrevDrive_SyncMode then
  begin
    AddToEventlog (format ('Drive_SyncMode changed to: %1d', [ord (Drive_SyncMode)]));
    PrevDrive_SyncMode:= Drive_SyncMode;
  end;
  if RimSyncMode <> PrevRimSyncMode then
  begin
    AddToEventlog (format ('RimSyncMode changed to: %1d', [ord (RimSyncMode)]));
    PrevRimSyncMode:= RimSyncMode;
  end;
  if AmplitudeControlMode <> PrevAmplitudeControlMode then
  begin
    AddToEventlog (format ('AmplitudeControlModee changed to: %1d', [ord (AmplitudeControlMode)]));
    PrevAmplitudeControlMode:= AmplitudeControlMode;
  end;

  Command:= 0;
  Command += ord (Drive_SyncMode);  // bits 0 and 1
  if ClearDidReSyncMyself          then Command += $00000004; ClearDidReSyncMyself:= false; // only once
  if ForceReSync                   then Command += $00000008; ForceReSync:= false; // only once

  Command += ord (RimSyncMode) shl 4;  // bits 4 and 5
  // bit 6 Spare
  if SendFrequencyWord             then Command += $00000800; SendFrequencyWord:= false; // only once

  Command += ord (AmplitudeControlMode) shl 8;  // bits 8 and 9
  // bit 10 Spare
  if InvertHalfSwing               then Command += $00000800; InvertHalfSwing:= false; // only once

  if EnableDetectorCenterPass_Mag  then Command += $00001000;
  if EnableDetectorCenterPass_Cap  then Command += $00002000;
  if EnableDetectorRimPass_Mag     then Command += $00004000;
  // bit 15 Spare

  // bits 16..23 Spare

  if ForceMaximalDriveCurrent      then Command += $01000000;
  if ForceMinimalDriveCurrent      then Command += $02000000;
  if EnableDrive                   then Command += $04000000;
  // bit 27 Spare

  if StoreInEEprom                 then Command += $10000000; StoreInEEprom:= false; // only once
  // bit 29 Spare
  // bit 30 Spare
  if ResetArduino                  then Command += $80000000; ResetArduino:= false; // only once

  TDrive_Start:= Drive_Position - (Drive_Width div 2);
  TDrive_Stop:=  Drive_Position + (Drive_Width div 2);

  SetLength (OutMessage, OUTMESSAGESIZE);
  // Put data into message string
  // Byte numbers in Pascal are 1 higher than in Arduino C++
  move (Command,                  OutMessage[ 1], 4);
  move (TStartLookForCenter_Mag,  OutMessage[ 5], 2);
  move (TMissedCenter_Mag,        OutMessage[ 7], 2);
  move (TStartLookForCenter_Cap,  OutMessage[ 9], 2);
  move (TMissedCenter_Cap,        OutMessage[11], 2);
  move (TStartLookForRim1_Mag,    OutMessage[13], 2);
  move (TMissedRim1_Mag,          OutMessage[15], 2);
  move (TStartLookForRim2_Mag,    OutMessage[17], 2);
  move (TMissedRim2_Mag,          OutMessage[19], 2);

  move (SetPoint_Amplitude_Ticks, OutMessage[21], 2);
  move (TDrive_Start,             OutMessage[23], 2);
  move (TDrive_Stop,              OutMessage[25], 2);
  move (Drive_MinimalCurrent,     OutMessage[27], 2);
  move (Drive_MaximalCurrent,     OutMessage[29], 2);
  move (DDS_FrequencyWord,        OutMessage[31], 4);
  move (Divider_T5,               OutMessage[35], 2);
  move (Divider_Final,            OutMessage[37], 2);
end;

procedure SendAndReceiveMessage; // called at 10 Hz by FrmMain.TimerSendMessage
const
  RCVTIMEOUT = 10;  // shorter than send interval
begin
  if DayPassed then NewLogFileNames; // at midnight
  if Talk then with FrmMain do
  begin
    // We start receiving
    InMessage:= '';
    UDP.RecvBufferEx (@InMessage[0], INMESSAGESIZE + 1, RCVTIMEOUT);
    TbxInMessageLength.text:= format ('%3d',[length (InMessage)]);
    TbxInMessage.text:= BytesViewHex (InMessage, length (InMessage));
    if length (InMessage) = INMESSAGESIZE then
    begin
      ProcessInMessage;
      if Incount < 255 then inc (InCount) else InCount:= 0; // prevent overflow
      TbxInMessageCount.text:= format('%3d', [InCount]);
    end;
    // Send message to Arduino
    PrepareOutMessage;
    TbxOutMessage.text:= BytesViewHex (OutMessage, length(OutMessage));
    TbxOutMessageLength.text:= format ('%3d', [length(OutMessage)]);
    UDP.SendString (OutMessage);
    if OutCount < 255 then inc (OutCount) else OutCount:= 0; // prevent overflow
    TbxOutMessageCount.text:= format('%3d', [OutCount]);
  end;
end;

procedure StartTalking;
begin
  // create socket
  UDP:= tUDPBlockSocket.create;
  // Connect to server
  UDP.Connect (ArduinoIP, ArduinoPort);
  AddToLog ('** Talk On');
  AddToEventLog ('Talk On');
  WriteParametersToEventLog;
  FrmMain.ButtClearCompassClick (TObject(0));
  StoreInEEPROM:= true;
  Talk:= true;
end;

procedure StopTalking;
begin
  Talk := false;
  AddToLog('** Talk Off');
  AddToEventLog('Talk Off');
  UDP.CloseSocket;
  UDP.Free;
end;

procedure Init_Messages;
begin
  ArduinoIP:= '192.168.2.51'; // Author's, You should use an IP in your LAN domain
  FrmMain.TbxArduinoIP.text:= ArduinoIP;
  UDP:= tUDPBlockSocket.create;
  UDP.Connect(ArduinoIP, ArduinoPort);
end;

begin
// no initialisation code
end.

