program udp2ser2;
{$mode objfpc}
{ $RANGECHECKS ON}
{ $OVERFLOWCHECKS ON}
{ $S+ STACK CHECKING ON}
{ $SMARTLINK ON}
{ $TYPEINFO ON}
{$LONGSTRINGS OFF}

{ The command}
{ udp2ser2 /dev/ttyS2 6001 6002 }
{ starts the program listening for }
{ UDP connection on port 6001 }
{ TCP connection on port 6002 }
{ routing data to ttyS? with 19200 }
{ UDP data returning from tty are expected to end with a CR }
{ Possible parameters }
{ 9600 => baud 9600 }
{ 38400 => baud 38400 }
{ DEBUG =ON => activates debug }
{ SILENT => nothing is written to the tty }
{ TERMINATE => Terminates an already running program }

uses
   cthreads,    {System}
   classes,     {System}
   sysutils,    {System}
   errors,      {System}
   sockets,     {Linux system}
   CbDos,       {Beas library}
   inetaux,     {Beas library}
   serial,      {Linux system}
   ipc,         {Linux system}
   BaseUnix;    {Linux system}

const
  ProgramVersion='Version 2.01.0  D. 7/12-06 ';
  baud9600='9600';
  baud38400='38400';
  MaxConn = 1;
  lukEpNed=chr(27)+chr(27)+chr(27)+chr(27)+chr(27)+chr(27)+chr(27);
  udp2ser2_StatusFileName='./udp2ser2';
  MaxUdpPakke=1024;
{$IFDEF ARM9LINUX}
  x16ff=chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255)+chr(255);
{$ENDIF}


Type
  UdpThread_typ=class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create;
    Destructor Destroy;  override;
  end;

  SioListenThread_typ=class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create;
    Destructor Destroy;  override;
  end;

  TcpThread_typ=class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create;
    Destructor Destroy;  override;
  end;

  TimerThread_typ=class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create;
    Destructor Destroy;  override;
    TimerTik1:LongWord;
    TimerTik2:LongWord;
  end;

  DataBuffer_typ=array[0..4095] of char;

Var
  Udp_ListenPort:Word;
  Udp_recvived_bytes:LongInt;
  Udp_rAddr,Udp_sAddr : TInetSockAddr;
  Udp_AddrLen:longint;
  Udp_SenderAcepted:boolean;
  Udp_glt:longWord;
  Udp_rSock:LongInt;
  Udp_sSock:LongInt;
  Udp_buf:DataBuffer_typ;
  Udp_SioBufLen:LongInt;
  Udp_bufSize:LongInt;
  Udp_SendSize:LongInt;
  UdpThread:UdpThread_typ;

  ListenPortStr:ShortString;

  TCP_uSock,Tcp_lSock:LongInt;
  TCP_ListenPort:word;
  TCP_Addr:TInetSockAddr;
  TCP_glt:longWord;
  TCP_AddrLen:longint;
  TCP_sizeOfBuf:longint;
  TCP_SocketError:LongInt;
  TCP_connected:boolean;
  TCP_buf:DataBuffer_typ;
  TCP_recvived_bytes:LongInt;
  TcpThread:TcpThread_typ;

  sio:LongInt;
  Sio_Device:ShortString;
  Sio_BufLen:LongInt;
  Sio_buf:DataBuffer_typ;
  Sio_bufSize:LongInt;
  Sio_baud:longint=19200;
  Sio_Tcp_used:boolean;
  SioListenThread:SioListenThread_typ;

  TimerThread:TimerThread_typ;

  DebugOn:boolean;
  Silent:boolean;
  udp2ser2_terminate:^boolean;

(*      CB d. 27/11-2006
{$IFDEF ARM9LINUX}
Function SerGetCts(s:longint):boolean;
Begin
  SerGetCts:=not serial.SerGetCts(s); {Tmp Kernel device error workaround on ARM9 platform}
end;
{$ENDIF}
  *)

{$IFDEF ARM9LINUX}
Function StrLen16xff(var a:DataBuffer_typ):longInt;
Begin
  strLen16xff:=pred(pos(x16ff,a));
End;
{$ENDIF}

function InitShaerdMemory:boolean;
var
  id,key:cInt;
  pc:array[0..255] of char;
Begin
  InitShaerdMemory:=false;
  fillchar(pc,256,0);
  pc:=udp2ser2_StatusFileName;
  key:=ftok(pc,1);
  if key=-1 then
    exit;
  id:=shmget(key,4,IPC_CREAT);
  if id=-1 then
    exit;
  udp2ser2_terminate:=shmat(id,nil,IPC_CREAT);
  if LongInt(udp2ser2_terminate)=-1 then
    exit;
  InitShaerdMemory:=true;
end;


constructor UdpThread_typ.Create;
  var
    p:LongInt;
begin
  inherited Create(true);
  Udp_recvived_bytes:=0;
  Udp_bufSize:=SizeOf(Udp_buf);
  Udp_SenderAcepted:=false;
  Udp_glt:=0;
  Udp_AddrLen := sizeof(TInetSockAddr);
  Udp_SioBufLen:=0;
  ListenPortStr:=ParamStr(2);
  if ListenPortStr='' then
    ListenPortStr:='5001';
  val(ListenPortStr,Udp_ListenPort,p);
  if p<>0 then
  begin
    if not Silent then
      WriteLn('Error at paramstr 2 (UDP Listen port)');
    halt(1);
  end;
  if not Silent then
    WriteLn('UDP Listen port:',Udp_ListenPort:1);

  Udp_rSock := sockets.Socket(AF_INET,SOCK_DGRAM,PF_UNSPEC);
  if Udp_rSock = -1 then
  begin
    if not Silent then
      WriteLn('UDP Receive socket error:',StrError(sockets.SocketError));
    halt(2);
  end else if not Silent then WriteLn('UDP Receive socket:',Udp_rSock:1);
  Udp_sSock := sockets.Socket(AF_INET,SOCK_DGRAM,PF_UNSPEC);
  if Udp_sSock = -1 then
  begin
    if not Silent then
      WriteLn('UDP Send socket error:',StrError(sockets.SocketError));
    halt(3);
  end else if not Silent then WriteLn('UDP Send socket:',Udp_sSock:1);
  Udp_rAddr.Family:=af_inet;
  Udp_rAddr.Port:=inetaux.htons(Udp_ListenPort);               {Byte order}
  Udp_rAddr.Addr:=0;
  if not sockets.Bind(Udp_rSock,Udp_rAddr,sizeof(TInetSockAddr)) then
  begin
    if not Silent then
      WriteLn('UDP Receive socket bind error: ',StrError(sockets.SocketError));
    halt(4);
  end;
end;

Destructor UdpThread_typ.Destroy;
begin
  if not Silent then
    WriteLn('Destroying UDP-thread ...');
  UdpThread.Terminate;
  sockets.Shutdown(Udp_rSock,SHUT_RD);          {Luk ned for laesning}
  inherited Destroy;
  if not Silent then
    WriteLn('UDP-thread destroyed');
end;

constructor SioListenThread_typ.Create;
begin
  inherited Create(true);
  Sio_Device:=ParamStr(1);
  if Sio_Device='' then
    Sio_Device:='/dev/ttyFS0'
  else
  begin
    if (pos('/dev/ttyS',Sio_Device)=0) and
       (pos('/dev/ttyFS0',Sio_Device)=0) then
    begin
      if not Silent then
        WriteLn('Error at paramstr 1 (Sio device /dev/ttyS[0-3])');
      halt(5);
    end;
  end;
  if not Silent then
    WriteLn('SIO-device:',Sio_Device);
  sio:=serial.SerOpen(Sio_Device);
  if CbDos.ParamCheckStr(baud9600) then
    Sio_baud:=9600;
  if CbDos.ParamCheckStr(baud38400) then
    Sio_baud:=38400;
  serial.SerSetParams(sio,Sio_baud,8,NoneParity,1,[]);
  serial.SerSetDTR(sio,false);
  serial.SerSetRTS(sio,false);
  Sio_Tcp_used:=false;
  Sio_bufSize:=0;
end;

Destructor SioListenThread_typ.Destroy;
begin
  if not Silent then
    WriteLn('Destroying SIO-thread ...');
  SioListenThread.Terminate;
  serial.serClose(sio);
  inherited Destroy;
  if not Silent then
    WriteLn('SIO-thread destroyed');
end;

constructor TcpThread_typ.Create;
  var
    p:LongInt;
begin
  inherited Create(true);
  TCP_AddrLen:=sizeof(TCP_Addr);
  TCP_sizeOfBuf:=SizeOf(TCP_buf);
  ListenPortStr:=ParamStr(3);
  if ListenPortStr='' then
    ListenPortStr:='5002';
  val(ListenPortStr,TCP_ListenPort,p);
  if p<>0 then
  begin
    if not Silent then
      WriteLn('Error at paramstr 3 (TCP port)');
    halt(6);
  end;
  TCP_lSock := sockets.Socket(AF_INET,SOCK_STREAM,PF_UNSPEC);
  if TCP_lSock = -1 then
  begin
    if not Silent then
      WriteLn('TCP socket error:',StrError(sockets.SocketError));
    halt(6);
  end;
  TCP_Addr.Family := AF_INET;
  TCP_Addr.Port := htons(TCP_ListenPort);               {Byte order}
  TCP_Addr.Addr := 0;
  if not sockets.Bind(TCP_lSock, TCP_Addr, sizeof(TCP_Addr)) then
  begin
    if not Silent then
      WriteLn('TCP Socket bind error: ',StrError(sockets.SocketError));
    halt(7);
  end;
  if not sockets.Listen(TCP_lSock, MaxConn) then
  begin
    if not Silent then
      WriteLn('TCP socket listen error: ',StrError(sockets.SocketError));
    halt(8);
  end;
  TCP_glt:=0;
end;

Destructor TcpThread_typ.Destroy;
begin
  if not Silent then
    WriteLn('Destroying TCP-thread ...');
  TcpThread.Terminate;
  sockets.Shutdown(Tcp_uSock,SHUT_RDWR);          {Luk ned for laesning/Skriving}
  sockets.Shutdown(Tcp_lSock,SHUT_RDWR);          {Luk ned for laesning/Skriving}
  inherited Destroy;
  if not Silent then
    WriteLn('TCP-thread destroyed');
end;

constructor TimerThread_typ.Create;
begin
  inherited Create(true);
  TimerTik1:=0;
  TimerTik2:=0;
end;

Destructor TimerThread_typ.Destroy;
begin
  if not Silent then
    WriteLn('Destroying Timer-thread ...');
  TimerThread.Terminate;
  inherited Destroy;
  if not Silent then
    WriteLn('Timer-thread destroyed');
end;

Function SendUdpPacket(var b:DataBuffer_typ; len:longint):boolean;
Begin
  SendUdpPacket:=true;
  Udp_sAddr:=Udp_rAddr;
  Udp_sAddr.Port:=inetaux.htons(Udp_ListenPort);                {Send til samme port som der lyttes p†}
  Udp_SendSize:=sockets.sendto(Udp_sSock,b,len,0,Udp_sAddr,sizeof(TInetSockAddr)); {Send til afsender adresse}
  Udp_glt:=succ(Udp_glt);
  if DebugOn then
  begin
    if not Silent then
      WriteLn('UDP send packet size/no:',Udp_SendSize,'/',Udp_glt);
  end;
End;

procedure UdpThread_typ.Execute;
begin
  if not Silent then
    WriteLn('UDP-thread active');
  repeat
    Udp_recvived_bytes:=sockets.recvfrom(Udp_rSock,Udp_Buf,Udp_bufSize,0,Udp_rAddr,Udp_AddrLen); {Wait for packet}
    if Udp_recvived_bytes = -1 then                                          {Error ?}
    begin
      if not Silent then
        WriteLn('Scocket recieve from error: ',StrError(sockets.SocketError));
    end else
    begin
      if DebugOn then
        WriteLn('Accepted data from ',inetaux.AddrToStr(Udp_rAddr.Addr),':',inetaux.htons(Udp_rAddr.port):1);
      if not Sio_Tcp_used then
      begin
        Udp_SioBufLen:=SerWrite(sio,Udp_Buf,Udp_recvived_bytes);
        if DebugOn then
          WriteLn('UDP-SIO W:',Udp_recvived_bytes,'/',Udp_SioBufLen);
        serial.SerFlush(sio);
      end else
      begin
        Udp_Buf:=chr(1)+'Q'+chr(2)+':"EP ONLINE VIA TCP"'+chr(3)+'56'+chr(13);
        SendUdpPacket(Udp_Buf,strLen(Udp_Buf));
      end;
      Udp_SenderAcepted:=true;
    end;
  until Terminated;
  if not Silent then
    WriteLn('UDP-thread terminated');
end;

procedure SioListenThread_typ.Execute;
begin
  if not Silent then
    WriteLn('SIO-thread active');
  repeat
    if not Sio_Tcp_used then
    begin
      repeat
        Sio_BufLen:=sizeof(Sio_buf);
        Sio_BufLen:=serial.SerRead(sio,Sio_Buf[Sio_bufSize],Sio_BufLen);
        if Sio_BufLen<>0 then
        begin
          TimerThread.timerTik1:=10;             {Vent p† pakke slut i 1 sekund}
          if DebugOn then
          begin
            WriteLn('Sio-read:',Sio_BufLen);
          end;
          Sio_bufSize:=Sio_bufSize+Sio_BufLen;
          if Sio_buf[pred(Sio_bufSize)]<>chr(13) then
          begin
            sleep(5);                   {Det var ikke slut p† pakken s† vent}
          end else
          begin
            SendUdpPacket(Sio_Buf,Sio_bufSize);
            if DebugOn then
            begin
              WriteLn('Packet (end) size:',Sio_bufSize);
            end;
            Sio_bufSize:=0;
          end;
        end;
        if (Sio_bufSize>MaxUdpPakke) or ((Sio_bufSize<>0) and (TimerThread.timerTik1=0)) then
        begin                           {T›m buffer for u-komplet pakke}
          SendUdpPacket(Sio_Buf,Sio_bufSize);
          Sio_bufSize:=0;
          if DebugOn then
          begin
            if Sio_bufSize>MaxUdpPakke then
              WriteLn('Size > MaxUdpPakke')
            else WriteLn('No Packet end');
          end;
        end;
      until Sio_BufLen=0;
      sleep(20);                          {Aflast CPU}
    end else sleep(100);
  until Terminated;
  if not Silent then
    WriteLn('SIO-thread terminated');
end;

procedure TcpThread_typ.Execute;
begin
  if not Silent then
    WriteLn('TCP-thread active');
  repeat
    serial.SerSetDTR(sio,false);
    serial.SerSetRTS(sio,false);
    Sio_Tcp_used:=false;
    if not Silent then
      WriteLn('TCP Waiting for connections...');
    TCP_Addr.Family := af_inet;
    TCP_Addr.Port := htons(TCP_ListenPort);               {Byte order}
    TCP_Addr.Addr := 0;
    TCP_uSock := sockets.Accept(TCP_lSock,TCP_Addr,TCP_AddrLen);
    if TCP_uSock <> -1 then
    begin
      if DebugOn then
        WriteLn('TCP Accepted connection from [',TCP_uSock,']',AddrToStr(TCP_Addr.Addr));
      Sio_Tcp_used:=true;
      serial.SerSetDTR(sio,false);
      serial.SerSetRTS(sio,true);
      TCP_SocketError:=sockets.SocketError;
      TCP_SocketError:=0;
      InitCriticalSection(TcpThread);
      repeat
        {$IFDEF ARM9LINUX}
          fillchar(TCP_buf,TCP_sizeOfBuf,255);
        {$ENDIF}
        TCP_recvived_bytes:=sockets.Recv(TCP_uSock,TCP_buf,TCP_sizeOfBuf,MSG_DONTWAIT);
        {$IFDEF ARM9LINUX}
          TCP_recvived_bytes:=strLen16xff(TCP_buf);
        {$ENDIF}
	if TCP_recvived_bytes>0 then
        begin
          if DebugOn then
            WriteLn('TCP recived:',TCP_recvived_bytes);
          while TCP_recvived_bytes>0 do
          begin
            if DebugOn then
              WriteLn('TCP SIO write:',TCP_recvived_bytes);
            TCP_recvived_bytes:=TCP_recvived_bytes-SerWrite(sio,TCP_buf,TCP_recvived_bytes);
          end;
          serial.SerFlush(sio);
          TimerThread.timerTik2:=10;             {Aflast cpu efter 1 sek.}
        end else
        begin
          TCP_SocketError:=sockets.SocketError;
        end;
        TCP_recvived_bytes:=serial.SerRead(sio,TCP_buf,TCP_sizeOfBuf);
        if TCP_recvived_bytes>0 then
        begin
          if DebugOn then
            WriteLn('TCP SIO send:',TCP_recvived_bytes);
          sockets.Send(TCP_uSock,TCP_buf,TCP_recvived_bytes,MSG_DONTWAIT);
          TimerThread.timerTik2:=10;             {Aflast cpu efter 1 sek.}
        end;
        if TimerThread.timerTik2=0 then
          sleep(50);
      until ((TCP_SocketError<>0) and {OK}
             (TCP_SocketError<>11)) or {Try again}
            not SerGetCTS(sio);         {EP har smidt forbindelse}
      TCP_buf:=lukEpNed;
      SerWrite(sio,TCP_buf,length(lukEpNed));
      sockets.CloseSocket(TCP_uSock);
      if DebugOn then
        WriteLn('TCP Connection closed.  SocketError:',TCP_SocketError,'[',StrError(TCP_SocketError),'] CTS=',SerGetCts(sio));
      serial.SerSetDTR(sio,false);
      serial.SerSetRTS(sio,false);
      sleep(1000);
      TCP_glt:=succ(TCP_glt);
      if DebugOn then
      begin
        WriteLn('RS232 Connection released.',TCP_glt);
        WriteLn('Connection no.',TCP_glt);
      end;
      sockets.Shutdown(TCP_uSock,SHUT_RDWR);
    end{if TCP_uSock <> -1 then} else
    begin
      Write('TCP U-socket accept error: ',StrError(sockets.SocketError));
    end;
  until Terminated;
  if not Silent then
    WriteLn('TCP-thread terminated');
end;

procedure TimerThread_typ.Execute;
begin
  if not Silent then
    WriteLn('Timer-thread active');
  repeat
    Sleep(100);
    if TimerTik1<>0 then
    begin
      TimerTik1:=pred(timertik1);
    end;
  until Terminated;
  if not Silent then
    WriteLn('Timer-thread terminated');
end;

begin
  if not InitShaerdMemory then
  begin
    WriteLn('Error at InitShaerdMemory');
    exit;
  end;
  udp2ser2_terminate^:=false;
  Silent:=CbDos.ParamCheckStr('SILENT');
  if not Silent then
  Begin
    Writeln;
    Writeln(ProgramVersion);
    Writeln('This program was compiled at ',{$I %TIME%},' ',{$I %DATE%});
    Writeln('By ',{$I %USER%});
    Writeln('Compiler version: ',{$I %FPCVERSION%});
    Writeln('Target CPU: ',{$I %FPCTARGET%});
    Writeln('File: ',{$I %FILE%});
    Writeln;
  end;

  if CbDos.ParamCheckStr('TERMINATE') then
  begin
    udp2ser2_terminate^:=true;
    exit;
  end;

  DebugOn:=CbDos.ParamCheckStr('DEBUG=ON');
  if not Silent then
    WriteLn('DEBUG=',DebugOn);

  SioListenThread:=SioListenThread_typ.create;
  TimerThread:=TimerThread_typ.create;
  UdpThread:=UdpThread_typ.create;
  TcpThread:=TcpThread_typ.create;

  UdpThread.Suspended:=false;
  SioListenThread.Suspended:=false;
  TcpThread.Suspended:=false;
  TimerThread.Suspended:=false;

  repeat
    sleep(1000);
  until udp2ser2_terminate^;

  if not Silent then
    WriteLn('Server terminate ....');
  UdpThread.free;
  sleep(200);
  SioListenThread.free;
  sleep(200);
  TcpThread.free;
  sleep(200);
  TimerThread.free;
  if not Silent then
    WriteLn('Server closed.');
end.
