unit u_messages;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, graphics;

const
  ARDUINOIP = '192.168.2.50';

var
// for OutMessage
  OutMessage: ansistring;
  Command: word;

// for InMessage
  InMessage: shortstring;
  Status, FirmwareversionNumber: word;
  TPassCenter, TDontLook, TPassRim_1, TPassRim_2, AmplitudeFromRim_mm: word;
  AdcCenter, APeakCenter,
  AdcRim, APeakRim_1, APeakRim_2,
  AdcHallNorth, AdcHallSouth, AdcHallEast, AdcHallWest: byte;
  AdcVBatteryRaw, AdcV24Raw: byte; // currently not used
  SeenCenter, Missedcenter, HalfSwing,
  SeenRim_1, MissedRim_1,
  SeenRim_2, MissedRim_2,
  FirstHalf, HaveSync, GeneralError, DidResetMyself, DidReSyncMyself: boolean;
  BatteryVoltage_mV, BatteryCurrent_mA: integer; // currently not used
  PrevTPassCenter: word;
  TPassCenterDiff: integer;
  TemperatureTop: byte;
  PositionCounter: word;
  TemperatureBME, HygroBME, BaroBME: smallint;

procedure SendAndReceiveMessage;
procedure StartTalking;
procedure StopTalking;
procedure Init_Messages;

implementation

uses blcksock, u_main, u_calc, u_logging, u_numstrings, u_lingraph, u_timestrings;

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

var
  UDP: tUDPBlockSocket;
  OutCount, InCount: byte;
  SeenCenterCnt, MissedCenterCnt, SeenRimCnt_1, MissedRimCnt_1,
  SeenRimCnt_2, MissedRimCnt_2: integer;

procedure ProcessInMessage;
begin
  with FrmMain do
  begin
    // Take values from the message
    move (InMessage[ 1], FirmwareVersionNumber, 2);
    move (InMessage[ 3], Status, 2);
    move (InMessage[ 5], TPassCenter, 2);
    move (InMessage[ 7], TPassRim_1, 2);
    move (InMessage[ 9], TPassRim_2, 2);
    move (InMessage[11], AmplitudeFromRim_mm, 2);
    // Raw ADC values
    AdcCenter:=     byte (InMessage[13]);
    APeakCenter:=   byte (InMessage[14]);
    AdcRim:=        byte (InMessage[15]);
    APeakRim_1:=    byte (InMessage[16]);
    APeakRim_2:=    byte (InMessage[17]);
    // Raw values from Hall sensors
    AdcHallNorth:=  byte (InMessage[18]);
    AdcHallSouth:=  byte (InMessage[19]);
    AdcHallEast:=   byte (InMessage[20]);
    AdcHallWest:=   byte (InMessage[21]);
    // Battery Management   (currently not used)
    AdcV24Raw:=     byte (InMessage[22]);
    move (InMessage[23], BatteryVoltage_mV, 2);
    move (InMessage[25], BatteryCurrent_mA, 2);
    TemperatureTop:= byte (InMessage[27]);
    // Exact timing of Hall data
    move (InMessage[28], PositionCounter, 2);
    move (InMessage[30], TemperatureBME, 2);
    move (InMessage[32], HygroBME, 2);
    move (InMessage[34], BaroBME, 2);

    // Process data from message
    SeenCenter:=           (Status and $0001) = $0001;
    if Seencenter then SeenCenterCnt:= StatusDisplayTime;

    if (Status and $0002) = $0002 then MissedCenterCnt:= StatusDisplayTime;
    HalfSwing:=            (Status and $0004) = $0004;
    HaveSync:=             (Status and $0008) = $0008;
    if (Status and $0010) = $0010 then begin SeenRim_1:= true; SeenRimCnt_1:= StatusDisplayTime; end;
    if (Status and $0020) = $0020 then MissedRimCnt_1:= StatusDisplayTime;
    if (Status and $0040) = $0040 then begin  SeenRim_1:= true; SeenRimCnt_2:= StatusDisplayTime; end;
    if (Status and $0080) = $0080 then MissedRimCnt_2:= StatusDisplayTime;
    UseMinimalDriveWidth:= (Status and $0100) = $0100;
    UseMaximalDriveWidth:= (Status and $0200) = $0200;
    DidResetMyself:=       (Status and $1000) = $1000;
    DidReSyncMyself:=      (Status and $2000) = $2000;
    GeneralError:=         (Status and $8000) = $8000;

    // Display the values
    TbxFirmwareVersion.text:=     format ('%6d', [FirmwareVersionNumber]);
    TbxTPassCenter.text:=         format ('%5d', [TPassCenter]);
    TbxAdcCenter.text:=           format ('%3d', [AdcCenter]);
    TbxPeakCenter.text:=          format ('%3d', [APeakCenter]);
    TbxTPassRim_1.text:=          format ('%5d', [TPassRim_1]);
    TbxTPassRim_2.text:=          format ('%5d', [TPassRim_2]);
    TbxAdcRim.text:=              format ('%3d', [AdcRim]);
    TbxPeakRim_1.text:=           format ('%3d', [APeakRim_1]);
    TbxPeakRim_2.text:=           format ('%3d', [APeakRim_2]);
    TbxAmplitudeFromRim_mm.text:= format ('%3d', [AmplitudefromRim_mm]);
    TbxTemperatureTop.text:=      format ('%5d', [TemperatureTop]);
    TbxTemperatureBME.text:=      format ('%5.1f', [TemperatureBME / 10]);
    TbxHygroBME.text:=            format ('%3d', [HygroBME]);
    TbxBaroBME.text:=             format ('%8.1f', [BaroBME/10]);
    ProcessHallData;

    if HallOnly then exit; // for calibrating the Hall sensor system. No other processing done

    if SeenCenterCnt = StatusDisplayTime then
    begin
      TPassCenterDiff:= TPassCenter - PrevTPassCenter;
      PrevTPassCenter:= TPassCenter;
      LinGraph_Update;
      ListData;
      FirstHalf:= not FirstHalf; // we need two halfswings for a whole swing
    end;

    if MissedCenterCnt = StatusDisplayTime then
    begin
      //AddToLog      ('** Center Pass Missed');
      AddToEventLog ('Center Pass Missed');
      MemoAdd  ('Center Pass Missed');
    end;

    if MissedRimCnt_1 = StatusDisplayTime then
    begin
      //AddToLog      ('** Missed Rim_1');
      AddToEventLog ('Missed Rim_1');
      MemoAdd ('Missed Rim_1');
    end;

    if MissedRimCnt_2 = StatusDisplayTime then
    begin
      //AddToLog      ('** Missed Rim_2');
      AddToEventLog ('Missed Rim_2');
      MemoAdd   ('Missed Rim_2');
    end;

    // update Status indicators and value boxes
    if SeenCenterCnt > 0 then LblStatusCenterSeen.color:= clRed else LblStatusCenterSeen.color:= clLime;
    if MissedCenterCnt > 0 then LblStatusCenterMissed.color:= clRed else LblStatusCenterMissed.color:= clLime;
    if HalfSwing then LblStatusHalfSwing.color:= clRed else LblStatusHalfSwing.color:= clLime;
    if HaveSync then LblStatusHaveSync.color:= clRed else LblStatusHaveSync.color:= clLime;
    if SeenRimCnt_1 > 0 then LblStatusSeenRim_1.color:= clRed else LblStatusSeenRim_1.color:= clLime;
    if MissedRimCnt_1 > 0 then LblStatusMissedRim_1.color:= clRed else LblStatusMissedRim_1.color:= clLime;
    if SeenRimCnt_2 > 0 then LblStatusSeenRim_2.color:= clRed else LblStatusSeenRim_2.color:= clLime;
    if MissedRimCnt_2 > 0 then LblStatusMissedRim_2.color:= clRed else LblStatusMissedRim_2.color:= clLime;
    if UseMinimalDriveWidth then LblStatusMinDriveLevel.color:= clRed else LblStatusMinDriveLevel.color:= clLime;
    if UseMaximalDriveWidth then LblStatusMaxDriveLevel.color:= clRed else LblStatusMaxDriveLevel.color:= clLime;
    if DidResetMyself then LblStatusDidResetMyself.color:= clRed else LblStatusDidResetMyself.color:= clLime;
    if DidReSyncMyself then LblStatusDidReSyncMySelf.color:= clRed else LblStatusDidReSyncMySelf.color:= clLime;
    if GeneralError then LblStatusGeneralError.color:= clRed else LblStatusGeneralError.color:= clLime;
    if SeenCenterCnt > 0 then   dec (SeenCenterCnt);
    if MissedCenterCnt > 0 then dec (MissedCenterCnt);
    if SeenRimCnt_1 > 0 then    dec (SeenRimCnt_1);
    if MissedRimCnt_1 > 0 then  dec (MissedRimCnt_1);
    if SeenRimCnt_2 > 0 then    dec (SeenRimCnt_2);
    if MissedRimCnt_2 > 0 then  dec (MissedRimCnt_2);
  end;
end;

procedure PrepareOutMessage;
begin
  Command:= 0;
  if InvertHalfSwing then Command += $0004;
  InvertHalfSwing:= false; // only once
  if ForceReSync then Command += $0008;
  ForceReSync:= false; // only once
  if HoldPower then Command += $0010;
  if ChargeBattery then Command += $0020;  // Currently not used
  if ForceMinimalDriveWidth then Command += $0100;
  if ForceMaximalDriveWidth then Command += $0200;
  if FrmMain.CbxEnableDrive.checked then Command += $0800;
  if ClearDidResetMyself then Command += $1000;
  ClearDidResetMyself:= false;
  if StoreInEEprom then Command += $4000;
  StoreInEEprom:= false; // only once
  if ResetArduino then Command += $8000;
  ResetArduino:= false; // only once
  SetLength(OutMessage, OUTMESSAGESIZE);
  move (Command,               OutMessage[ 1], 2);
  move (TStartLookForCenter,   OutMessage[ 3], 2);
  move (TMissedCenter,         OutMessage[ 5], 2);
  move (TStartLookForRim_1,    OutMessage[ 7], 2);
  move (TMissedRim_1,          OutMessage[ 9], 2);
  move (TStartLookForRim_2,    OutMessage[11], 2);
  move (TMissedRim_2,          OutMessage[13], 2);
  move (RimCoilRadius_mm,      OutMessage[15], 2);
  move (SetPoint_Amplitude_mm, OutMessage[17], 2);
  move (TMidDrive,             OutMessage[19], 2);
  OutMessage[21]:=             char (TMaxDriveWidth);
  OutMessage[22]:=             char (TMinDriveWidth);
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:= BytesView (InMessage, length (InMessage));
    if length (InMessage) = INMESSAGESIZE then
    begin
      ProcessInMessage; // in u_calc
      if Incount < 255 then inc (InCount) else InCount:= 0; // prevent overflow
      TbxInMessageCount.text:= format('%3d', [InCount]);
    end;
    // Send message to Arduino
    PrepareOutMessage;
    TbxOutMessage.text:= BytesView (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');
  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
  Frmmain.TbxArduinoIP.text:= ArduinoIP;
  UDP:= tUDPBlockSocket.create;
  UDP.Connect(ArduinoIP, ArduinoPort);
end;

begin
// no initialisation code
end.

