# coding: Shift_JIS
# coding: UTF-8

#
# This program for ruby version 2.7
#

#
# Trust Design LLC : SECS/HSMS Communication library
#
# (c) Copyright Trust Design LLC.  2010-2023.  All rights reserved.
#


# ==============================================================================
#
# SimpleIo : Test and sample program
#
#  The simplest use case of constructing a message in AP and simply repeating
#  sending and receiving of SECS message.
#
#  Execute the following flow.
# 
#  (Note) In the case of HSMS communication, this AP assumes that the equipment
#  side is Active connection and the host side is Passive connection.
#
#   Host side (Simple h)   |          | Equipment side (Simple e)
#   ---------------------------------------------------------------------------
#   Waiting for connection |          |
#                          |          |
#        Accept connection |<---------| Connect
#            Accept Select |<---------| Select request
#                          |          |
#                          |          |
#        +--> S1F1 Receive | <------- | S1F1 Send <--+
#        |    S1F2 Send    | -------> | S1F2 Receive | Repeat
#        |    Wait 2 sec.  |          |              | Exit with CTRL-C
#        |    S1F1 Send    | -------> | S1F1 Receive | Transmission interval
#        +--- S1F2 Receive | <------- | S1F2 Send    | is determined by WAIT1
#                          |          | Wait 2 sec. -+ macro constant
#                          |          |
#          Accept Deselect |<---------| Deselect request  <Omission>
#          Accept Separate |<---------| Separate request
#                          |          |
#                 Shutdown |          | Shutdown                                 
#                          |          |
#
#
# Starting method
#
#   ruby SimpleIo.rb {h|e}
#   ~~~~~~~~~~~~~~~~~~~~~~
#   h    : Refer to [HOST]  section of SampleE.ini to determine operation
#   e    : Refer to [EQUIP] section of SampleE.ini to determine operation
#
#
#   Normally, "SimpleIo h" and "SimpleIo e" both operate on the same
#   machine or different machines to communicate with each other.
#   Entering CTRL-C terminates the next action.
#
#   This sample omits some of the abnormal processing.
#
#
# ==============================================================================
#
# SimpleIo : テスト 及び サンプル・プログラム
#
#  ＡＰ内でメッセージを構築し、単純に SECS メッセージの送受信を繰り返す、最も
#  単純な使用例。
#
#  以下の流れを実行する。
#
#  (注) 本ＡＰは HSMS 通信においては、設備側を Active 接続、ホスト側を Passive
#  接続と仮定している。
#
#   ホスト側（Simple h） |          | 設備側（Simple e）
#   --------------------------------------------------------
#            接続 を待つ |          |
#                        |          |
#       接続を受け付ける |<---------| Connect
#    Select を受け付ける |<---------| Select 要求
#                        |          |
#                        |          |
#         +--> S1F1 受信 | <------- | S1F1 送信 <--+
#         |    S1F2 送信 | -------> | S1F2 受信    | 繰り返す
#         |    ２秒待つ  |          |              | CTRL-C で終了
#         |    S1F1 送信 | -------> | S1F1 受信    | 送信間隔は WAIT1 定数で
#         +--- S1F2 受信 | <------- | S1F2 送信    | 決める
#                        |          | ２秒待つ  ---+
#                        |          |
#   Deselectを受け付ける |<---------| Deselect 要求  <省略>
#   Separateを受け付ける |<---------| Separate 要求
#                        |          |
#                   切断 |          | 切断                                 
#                        |          |
#
#
#
# 起動方法
#
#   ruby SimpleIo.rb {h|e}
#   ~~~~~~~~~~~~~~~~~~~~~~
#   h    : Sample.ini の [HOST]  セクションを参照し動作を決定する
#   e    : Sample.ini の [EQUIP]      :              :
#
#
#   通常、Simple h 及び Simple e の両方を、同一マシン、もしくは異なる
#   マシン上で動作させて、相互通信を行う。
#   CTRL-C の入力で終了する。
#
#   本サンプルは、異常時処理を省略している。
#
# ==============================================================================

require	"fiddle/import"

module TDS
  extend Fiddle::Importer
  dlload File.expand_path("TDS.dll")
  extern "int	__TDSCommOpen	(int,char*,char*,int,int,int,int,int,int)"
  extern "int	__TDSCommOpen0	(int,char*,char*)"
  extern "int	__TDSCommClose	(int,int)"
  extern "int	__TDSCommRecv	(int,int,int*,int*,unsigned int*,void*,int,TDSECSHead*)"
  extern "int	__TDSCommSend	(int,int,int ,int ,unsigned int ,void*,int,TDSECSHead*)"
  extern "int	__TDSCommStatus	(int,int)"

  extern "int	__TDSMssgInit	(int,void*,int,int)"
  extern "int	__TDSMssgEnd	(int,int,void*)"
  extern "int	__TDSMssgBuild	(int,int,void*,int,int,void*)"
  extern "int	__TDSMssgBuildL	(int,int,void*,char*)"
  extern "int	__TDSMssgFind	(int,void*,int,int,TDSECSHead*,char*)"
  extern "int	__TDSMssgExit	(int,int,void*)"
  extern "int	__TDSMssgNext	(int,int,void*,int*,int*,int*,void*,int)"
  extern "int	__TDSMssgNextL	(int,int,void*,int*,int*,char*,int)"

  VSI4 =struct(	["int			si",])
  VUI4 =struct(	["unsigned int		ui",])
  SHead=struct(	["unsigned short	did"	\
		,"unsigned char		scd"	\
		,"unsigned char		fcd"	\
		,"unsigned char		ptp"	\
		,"unsigned char		stp"	\
		,"unsigned short	sid"	\
		,"unsigned short	xid",])
end



# ------------------------------------------------------------------------------

E_BADF		=9
E_BUSY		=16
E_NOMEM		=12
E_NODEV		=19
E_2BIG		=7

E_NOTCONNECT	=999
E_DESELECT	=998
E_REJECT	=997
E_SEPARATE	=996
E_SELECT	=992
E_CONNECT	=991
E_RETRYOVER	=989
E_T8TIMEDOUT	=988
E_T7TIMEDOUT	=987
E_T6TIMEDOUT	=986
E_T5TIMEDOUT	=985
E_T4TIMEDOUT	=984
E_T3TIMEDOUT	=983
E_T2TIMEDOUT	=982
E_T1TIMEDOUT	=981
E_ILLBLOCK	=980
E_NODATA	=951



# ------------------------------------------------------------------------------

PARAMFILE	="Sample.ini"	# Configureation file

WAIT0		=0.1		# Monitoring interval   (s)
WAIT1		=0.3		# Transmission interval (s)

$Fd		=0		# Communication identifier



# ==============================================================================
# Display SECS Message =========================================================

def DispData(
		tp,		# i  : Message type
				#	=1 : Received messag
				#	 2 : Send message
		hd,		# i  : SECS Message Header
		msg,		# i  : SECS Message Body
		len,		# i  : Byte size of 'msg'
		rtn)		# i  : I/O retrun value

  ctp	=["SRES","RECV","SEND"]

  if rtn<0 then
    case -rtn
    when E_NODEV	then	printf("No such device ID\n")
    when E_2BIG		then	printf("Data size to large\n")
    when E_NODATA
    when E_ILLBLOCK	then	printf("Illegal block#\n")
    when E_T1TIMEDOUT	then	printf("T1 Timeout occur\n")
    when E_T2TIMEDOUT	then	printf("T2 Timeout occur\n")
    when E_T3TIMEDOUT	then	printf("T3 Timeout occur\n")
    when E_T4TIMEDOUT	then	printf("T4 Timeout occur\n")
    when E_T5TIMEDOUT	then	printf("T5 Timeout occur\n")
    when E_T6TIMEDOUT	then	printf("T6 Timeout occur\n")
    when E_T7TIMEDOUT	then	printf("T7 Timeout occur\n")
    when E_T8TIMEDOUT	then	printf("T8 Timeout occur\n")
    when E_RETRYOVER	then	printf("Retry over\n")
    when E_CONNECT	then	printf("Connected\n")
    when E_SELECT	then	printf("Selected   (0x%04x)\n"	,hd.did)
    when E_REJECT	then
			printf("Rejected XId=0x%04x.%04x\n"	,hd.sid,hd.xid)
    when E_DESELECT	then	printf("Deselected (0x%04x)\n"	,hd.did)
    when E_SEPARATE	then	printf("Separated  (0x%04x)\n"	,hd.did)
    when E_NOTCONNECT	then	printf("Not connected\n")
    else			printf("Error [%d]\n"		,rtn)
    end

  else
    rbit=" ";	if (hd.did&0x8000)!=0 then	rbit="R"	end
    wbit=" ";	if (hd.scd&  0x80)!=0 then	wbit="W"	end
    sfcode=sprintf("S%dF%d",hd.scd&0x7f,hd.fcd)
    printf("[%s]  Dev=0x%04x  %-8s  %s%s  XId=0x%04x.0x%04x  Len=%4d\n"	\
 			,ctp[tp],hd.did,sfcode,rbit,wbit,hd.sid,hd.xid,len)
  end
end



# ==============================================================================
# Message construction =========================================================

# ------------------------------------------------------------------------------
# S1F1 message construction and sending ----------------------------------------

def SendS1F1()
  hd =TDS::SHead.malloc
  msg="\0"*16
  sf =0x8101
  len=0

  rtn=TDS.__TDSCommSend($Fd,0x0000,0,sf,0,msg,len,hd)
  DispData(2,hd,msg,len,rtn)

  return rtn
end


# ------------------------------------------------------------------------------
# S1F2 message (Host) construction and sending ---------------------------------

def SendS1F2H(
		did,
		xid)
  hd =TDS::SHead.malloc
  msg="\0"*256
  sf =0x0102
  len=0

  md =TDS.__TDSMssgInit(    0,msg,  256,$Fd)			# S1F2
      TDS.__TDSMssgBuild(md,0,msg,0o000,  0,  0)		# L0
  len=TDS.__TDSMssgEnd(  md,0,msg)

  if len<0 then	rtn=len
  else		rtn=TDS.__TDSCommSend($Fd,0x0000,did,sf,xid,msg,len,hd)
  end
  DispData(2,hd,msg,len,rtn)

  return rtn
end


# ------------------------------------------------------------------------------
# S1F2 message (Equipment) construction and sending ----------------------------

def SendS1F2E(
		did,
		xid)
  hd =TDS::SHead.malloc
  msg="\0"*256
  sf =0x0102
  len=0
  
  md =TDS.__TDSMssgInit(    0,msg,  256,$Fd)			# S1F2
      TDS.__TDSMssgBuild(md,0,msg,0o000,  2,  0)		# L2
      TDS.__TDSMssgBuild(md,0,msg,0o020,  6,"EQUIP1")		#  MDLN
      TDS.__TDSMssgBuild(md,0,msg,0o020,  6,"01.000")		#  SOFTREV
  len=TDS.__TDSMssgEnd(  md,0,msg)

  if len<0 then rtn=len
  else		rtn=TDS.__TDSCommSend($Fd,0x0000,did,sf,xid,msg,len,hd)
  end
  DispData(2,hd,msg,len,rtn)

  return rtn
end



# ==============================================================================
# Host side process ------------------------------------------------------------

def Host()
  hd =TDS::SHead.malloc
  did=TDS::VUI4.malloc
  xid=TDS::VUI4.malloc
  msg="\0"*1024
  msz=1024

  catch :Exit do
  $Fd=TDS.__TDSCommOpen(0x1002,PARAMFILE,"HOST",0,0,0,0,0,0)
  if $Fd					< 0 then	throw :Exit end
  print "(H) Opened (#{$Fd})\n"

  while true do
    rtn=TDS.__TDSCommRecv($Fd,0,did,0,xid,msg,msz,hd)
    DispData(1,hd,msg,rtn,rtn)
    if rtn	< 0 then
      sleep(WAIT0)	# Wait if there is no data received including NODATA
                        # NODATA を含めデータ受信でなかったらならチョット待つ
    else
      if hd.scd==0x81 && hd.fcd==0x01 then	# S1F1 received ...
	SendS1F2H(did.ui,xid.ui)		# S1F1 受信したら・・
	sleep(WAIT1)
	SendS1F1()
      end
    end
  end

  end						# :Exit
  if $Fd>0 then	TDS.__TDSCommClose($Fd,0)
  else		print "(H) Error (#{$Fd})\n"
  end
end



# ==============================================================================
# Equipment side process -------------------------------------------------------

def Equip()
  hd =TDS::SHead.malloc
  did=TDS::VUI4.malloc
  xid=TDS::VUI4.malloc
  msg="\0"*1024
  msz=1024

  catch :Exit do
  $Fd=TDS.__TDSCommOpen(0x1002,PARAMFILE,"EQUIP",0,0,0,0,0,0)
  if $Fd					< 0 then	throw :Exit end
  print "(E) Opened (#{$Fd})\n"

  # For HSMS-SS, do the following. Not required for SECS-1
  # HSMS-SS の場合は、以下を実行。SECS-1 の場合は必要なし。
  if TDS.__TDSCommSend($Fd,0x0100,0,0,0,0,0,0)	< 0 then	throw :Exit end
  print "(E) Connected\n"
  if TDS.__TDSCommSend($Fd,0x0200,0,0,0,0,0,0)	< 0 then	throw :Exit end
  print "(E) Select request\n"

  # Wait for Select state. Not required for SECS-1, but fine to do.
  # This AP issues S1F1 from Equip side when it becomes "Selected" state, so
  # check Selected state at this position.  (It's usually OK to simply wait for
  # a few seconds)
  # Select 状態になるのを待つ。SECS-1 の場合は必要ないが、行っても問題ない。
  # 本 AP は、Select 状態になったら Equip 側から S1F1 を発行するので、この位置で
  # Select 状態を確認する (簡易的に数秒間の「待ち」でも通常はＯＫ) 。
  while true do
    rtn=TDS.__TDSCommStatus($Fd,0)
    if rtn					< 0 then	throw :Exit end
    if rtn					==3 then	break	    end
    sleep(0.1)
  end
  print "(E) Selected\n"

  SendS1F1()
  while true do
    rtn=TDS.__TDSCommRecv($Fd,0,did,0,xid,msg,msz,hd)
    DispData(1,hd,msg,rtn,rtn)
    if rtn	< 0 then
      sleep(WAIT0)	# Wait if there is no data received including NODATA
			# NODATA を含めデータ受信でなかったらならチョット待つ
    else
      if hd.scd==0x81 && hd.fcd==0x01 then	# S1F1 received ...
	SendS1F2E(did.ui,xid.ui)		# S1F1 受信したら・・
	sleep(WAIT1)
	SendS1F1()
      end
    end
  end

  end						# :Exit

  if $Fd>0 then
    # If you want to terminate (call _TDSCommClose() or _TDSUDrvClose()) like
    # this AP, you don't need the following "Separate request" processing.
    # この AP の様に、このまま終了する (_TDSCommClose() もしくは _TDSUDrvClose()
    # を Call する) のであれば、以下の "Separate 要求" 処理は必要ない。
    if TDS.__TDSCommStatus($Fd,0)==3 then
      TDS.__TDSCommSend($Fd,0x0900,0,0,0,0,0,0)
      print "(E) Separated\n"
    end
    TDS.__TDSCommClose($Fd,0)
    print "(E) Closed\n"
  else
    print "(E) Error (#{$Fd})\n"
  end
end



# ==============================================================================
# Main process -----------------------------------------------------------------

if ARGV.size<1 then
  print "Usage: ruby #{$0} {h|e}\n"
else
  if ARGV[0]=="h" then	Host( )
  else			Equip()
  end
end
