# coding: Shift_JIS
# coding: UTF-8

#
# This program for python version 3.8
#

#
# 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
#
#   python SimpleIo.py {h|e}
#   ~~~~~~~~~~~~~~~~~~~~~~~~
#   h    : Refer to [HOST]  section of Sample.ini to determine operation
#   e    : Refer to [EQUIP] section of Sample.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 要求
#                        |          |
#                   切断 |          | 切断
#                        |          |
#
#
#
# 起動方法
#
#   python SimpleIo.py {h|e}
#   ~~~~~~~~~~~~~~~~~~~~~~~~
#   h    : Sample.ini の [HOST]  セクションを参照し動作を決定する
#   e    : Sample.ini の [EQUIP]      :              :
#
#
#   通常、Simple h 及び Simple e の両方を、同一マシン、もしくは異なる
#   マシン上で動作させて、相互通信を行う。
#   CTRL-C の入力で終了する。
#
#   本サンプルは、異常時処理を省略している。
#
# ==============================================================================

import sys
import ctypes

from time   import *
from ctypes import *


class SHead(Structure):
  _fields_=     [("did"     ,c_ushort    )
                ,("scd"     ,c_ubyte     )
                ,("fcd"     ,c_ubyte     )
                ,("ptp"     ,c_ubyte     )
                ,("stp"     ,c_ubyte     )
                ,("sid"     ,c_ushort    )
                ,("xid"     ,c_ushort    )]


#tds=windll.LoadLibrary('TDS.dll')
tds=windll.LoadLibrary('./TDS.dll')
tds._TDSCommOpen    .restype    =  c_int
tds._TDSCommOpen0   .restype    =  c_int
tds._TDSCommClose   .restype    =  c_int
tds._TDSCommRecv    .restype    =  c_int
tds._TDSCommSend    .restype    =  c_int
tds._TDSCommStatus  .restype    =  c_int

tds._TDSMssgInit    .restype    =  c_int
tds._TDSMssgEnd     .restype    =  c_int
tds._TDSMssgBuild   .restype    =  c_int
tds._TDSMssgBuildL  .restype    =  c_int
tds._TDSMssgFind    .restype    =  c_int
tds._TDSMssgExit    .restype    =  c_int
tds._TDSMssgNext    .restype    =  c_int
tds._TDSMssgNextL   .restype    =  c_int

tds._TDSCommOpen    .argtypes   = [c_int,POINTER(c_char),POINTER(c_char),c_int,c_int,c_int,c_int,c_int,c_int]
tds._TDSCommOpen0   .argtypes   = [c_int,POINTER(c_char),POINTER(c_char)]
tds._TDSCommClose   .argtypes   = [c_int,c_int]
tds._TDSCommRecv    .argtypes   = [c_int,c_int,POINTER(c_int),POINTER(c_int),POINTER(c_int),POINTER(c_ubyte),c_int,POINTER(SHead)]
tds._TDSCommSend    .argtypes   = [c_int,c_int,c_int,c_int,c_int,POINTER(c_ubyte),c_int,POINTER(SHead)]
tds._TDSCommStatus  .argtypes   = [c_int,c_int]

tds._TDSMssgInit    .argtypes   = [c_int,POINTER(c_ubyte),c_int,c_int]
tds._TDSMssgEnd     .argtypes   = [c_int,c_int,POINTER(c_ubyte)]
tds._TDSMssgBuild   .argtypes   = [c_int,c_int,POINTER(c_ubyte),c_int,c_int,c_void_p]
tds._TDSMssgBuildL  .argtypes   = [c_int,c_int,POINTER(c_ubyte),POINTER(c_char)]
tds._TDSMssgFind    .argtypes   = [c_int,POINTER(c_ubyte),c_int,c_int,POINTER(SHead),POINTER(c_char)]
tds._TDSMssgExit    .argtypes   = [c_int,c_int,POINTER(c_ubyte)]
tds._TDSMssgNext    .argtypes   = [c_int,c_int,POINTER(c_ubyte),POINTER(c_int),POINTER(c_int),POINTER(c_int),c_void_p,c_int]
tds._TDSMssgNextL   .argtypes   = [c_int,c_int,POINTER(c_ubyte),POINTER(c_int),POINTER(c_int),POINTER(c_char),c_int]



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

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       =b"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,hd,msg,ln,rtn):
#   int         tp              # i  : Message type
#                               #       =1 : Received message
#                               #        2 : Send message
#   SHead       hd              # i  : SECS Message Header
#   c_ubyte*    msg             # i  : SECS Message Body
#   int         ln              # i  : Byte size of 'msg'
#   int         rtn             # i  : I/O return value

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

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

  else:
    rbit=" ";   wbit=" ";
    if (hd.did&0x8000)!=0:      rbit="R"
    if (hd.scd&  0x80)!=0:      wbit="W"
    sfcode="S{0:d}F{1:d}".format(hd.scd&0x7f,hd.fcd)
    print("[{0:s}]  Dev=0x{1:04x}  {2:<8s}  {3:s}{4:s}" \
          " XId=0x{5:04x}.0x{6:04x}  Len={7:3d}" \
                .format(ctp[tp],hd.did,sfcode,rbit,wbit,hd.sid,hd.xid,ln))



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

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

def SendS1F1():
  hd =SHead()
  msg=(c_ubyte*16)();   p_msg=cast(msg,POINTER(c_ubyte))
  sf =0x8101
  ln =0

  rtn=tds._TDSCommSend(Fd,0x0000,0,sf,0,p_msg,ln,byref(hd))
  DispData(2,hd,msg,ln,rtn)

  return rtn


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

def SendS1F2H(did,xid):
  hd =SHead()
  msg=(c_ubyte*256)();  p_msg=cast(msg,POINTER(c_ubyte))
  sf =0x0102
  ln =0

  md =tds._TDSMssgInit(    0,p_msg,  256,Fd)                        # S1F2
  dmy=tds._TDSMssgBuild(md,0,p_msg,0o000,  0,  0)                   # L0
  ln =tds._TDSMssgEnd(  md,0,p_msg);

  if ln<0:  rtn=ln
  else:     rtn=tds._TDSCommSend(Fd,0x0000,did,sf,xid,p_msg,ln,byref(hd))
  DispData(2,hd,msg,ln,rtn)

  return rtn


# ------------------------------------------------------------------------------
# S1F2 message (Equipment) construction and sending ----------------------------
# S1F2 メッセージ (Equip) 構築及び送信 -----------------------------------------

def SendS1F2E(did,xid):
  hd =SHead()
  msg=(c_ubyte*256)();  p_msg=cast(msg,POINTER(c_ubyte))
  sf =0x0102
  ln =0;

  md =tds._TDSMssgInit(    0,p_msg,  256,Fd)                        # S1F2
  dmy=tds._TDSMssgBuild(md,0,p_msg,0o000,  2,  0)                   # L2
  dmy=tds._TDSMssgBuild(md,0,p_msg,0o020,  6,b"EQUIP1")             #  MDLN
  dmy=tds._TDSMssgBuild(md,0,p_msg,0o020,  6,b"01.000")             #  SOFTREV
  ln =tds._TDSMssgEnd(  md,0,p_msg);

  if ln<0:  rtn=ln
  else:     rtn=tds._TDSCommSend(Fd,0x0000,did,sf,xid,p_msg,ln,byref(hd))
  DispData(2,hd,msg,ln,rtn)

  return rtn



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

def Host():
  global Fd

  hd =SHead()
  sf =c_int(0)
  did=c_int(0)
  xid=c_int(0)
  msg=(c_ubyte*1024)(); p_msg=cast(msg,POINTER(c_ubyte))
  msz=1024

  p_para=create_string_buffer(PARAMFILE)
  p_sect=create_string_buffer(b"HOST")

  try:
    Fd=tds._TDSCommOpen(0x1002,p_para,p_sect,0,0,0,0,0,0)
    if Fd                                                   < 0:    raise ValueError
    print("(H) Opened ({0:d})".format(Fd))

    while True:
      rtn=tds._TDSCommRecv(Fd,0,byref(did),byref(sf),byref(xid),p_msg,msz,byref(hd))
      DispData(1,hd,msg,rtn,rtn)
      if rtn    < 0:
        sleep(WAIT0)    # Wait if there is no data received including NODATA
                        # NODATA を含めデータ受信でなかったらならチョット待つ
      else:
        if hd.scd==0x81 and hd.fcd==0x01:       # S1F1 received ...
          SendS1F2H(did.value,xid.value)        # S1F1 受信したら・・
          sleep(WAIT1)
          SendS1F1()

  except ValueError:  pass                      # Exit:
  if Fd>0:  tds._TDSCommClose(Fd,0)
  else:     print("(H) Error ({0:d})".format(Fd))

  return 0


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

def Equip():
  global Fd

  hd =SHead()
  sf =c_int(0)
  did=c_int(0)
  xid=c_int(0)
  msg=(c_ubyte*1024)(); p_msg=cast(msg,POINTER(c_ubyte))
  msz=1024

  p_para=create_string_buffer(PARAMFILE)
  p_sect=create_string_buffer(b"EQUIP")

  try:
    Fd=tds._TDSCommOpen(0x1002,p_para,p_sect,0,0,0,0,0,0)
    if Fd                                                   < 0:    raise ValueError
    print("(E) Opened ({0:d})".format(Fd))

    # For HSMS-SS, do the following. Not required for SECS-1.
    # HSMS-SS の場合は、以下を実行。SECS-1 の場合は必要なし。
    if tds._TDSCommSend(Fd,0x0100,0,0,0,p_msg,0,byref(hd))  < 0:    raise ValueError
    print("(E) Connected")
    if tds._TDSCommSend(Fd,0x0200,0,0,0,p_msg,0,byref(hd))  < 0:    raise ValueError
    print("(E) Select request")

    # 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:
      rtn=tds._TDSCommStatus(Fd,0)
      if rtn                                                < 0:    raise ValueError
      if rtn                                                ==3:    break
      sleep(0.1)
    print("(E) Selected")

    SendS1F1()
    while True:
      rtn=tds._TDSCommRecv(Fd,0,byref(did),byref(sf),byref(xid),p_msg,msz,byref(hd))
      DispData(1,hd,msg,rtn,rtn)
      if rtn    < 0:
        sleep(WAIT0)    # Wait if there is no data received including NODATA
                        # NODATA を含めデータ受信でなかったらならチョット待つ
      else:
        if hd.scd==0x81 and hd.fcd==0x01:       # S1F1 received ...
          SendS1F2E(did.value,xid.value)        # S1F1 受信したら・・
          sleep(WAIT1)
          SendS1F1()

  except ValueError:  pass                      # Exit:

  if Fd>0: 
    # 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:
      tds._TDSCommSend(Fd,0x0900,0,0,0,p_msg,0,byref(hd))
      print("(E) Separated")
    tds._TDSCommClose(Fd,0)
    print("(E) Closed")
  else:
    print("(E) Error ({0:d})".format(Fd))

  return 0


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

if len(sys.argv)<2:
  print("Usage: python {0:s} {{h|e}}".format(sys.argv[0]))
else:
  if sys.argv[1]=="h":  Host( )
  else:                 Equip()
