104_parse.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import socket
  2. import struct
  3. import time
  4. import traceback
  5. from enum import Enum
  6. from tmp.iec104.ASDU import ASDU
  7. class IEC104Type(Enum):
  8. M_SP_NA_1 = 1 # 单点遥信
  9. M_DP_NA_1 = 3 # 双点遥信
  10. M_ME_NA_1 = 9 # 测量值,规一化值
  11. M_ME_NB_1 = 11 # 测量值,标度化值
  12. M_ME_NC_1 = 13 # 测量值,短浮点数
  13. M_SP_TB_1 = 30 # 带时标单点遥信
  14. M_DP_TB_1 = 31 # 带时标双点遥信
  15. CUSTOM_151 = 151 # 自定义遥信类型
  16. @classmethod
  17. def get_description(cls, type_id):
  18. descriptions = {
  19. 1: "单点遥信",
  20. 3: "双点遥信",
  21. 9: "测量值(规一化)",
  22. 11: "测量值(标度化)",
  23. 13: "测量值(短浮点)",
  24. 30: "带时标单点遥信",
  25. 31: "带时标双点遥信",
  26. 151: "自定义遥信"
  27. }
  28. return descriptions.get(type_id, f"未知类型({type_id})")
  29. class IEC104Client:
  30. def __init__(self, host, port):
  31. self.host = host
  32. self.port = port
  33. self.socket = None
  34. self.send_seq = 0
  35. self.recv_seq = 0
  36. self.max_retries = 3
  37. self.retry_interval = 5 # 重试间隔(秒)
  38. def connect(self):
  39. retry_count = 0
  40. while retry_count < self.max_retries:
  41. try:
  42. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  43. self.socket.settimeout(10) # 设置超时时间
  44. self.socket.connect((self.host, self.port))
  45. self.send_seq = 0
  46. self.recv_seq = 0
  47. print(f"已连接到 {self.host}:{self.port}")
  48. return True
  49. except Exception as e:
  50. retry_count += 1
  51. print(f"连接失败 (尝试 {retry_count}/{self.max_retries}): {e}")
  52. if retry_count < self.max_retries:
  53. time.sleep(self.retry_interval)
  54. return False
  55. def disconnect(self):
  56. if self.socket:
  57. try:
  58. self.socket.close()
  59. except:
  60. pass
  61. self.socket = None
  62. print("连接已断开")
  63. def send_startdt(self):
  64. try:
  65. # 发送STARTDT ACT
  66. apci = APCI(4, self.send_seq, self.recv_seq)
  67. startdt_act = apci.pack() + b'\x07\x00\x00\x00' # U格式帧,STARTDT ACT
  68. self.socket.send(startdt_act)
  69. self.send_seq += 1
  70. print("已发送STARTDT激活命令")
  71. # 设置超时等待STARTDT CON
  72. self.socket.settimeout(5) # 5秒超时
  73. try:
  74. data = self.socket.recv(1024)
  75. if len(data) >= 6 and data[6:10] == b'\x0B\x00\x00\x00': # STARTDT CON
  76. print("收到STARTDT确认,连接已激活")
  77. return True
  78. # 如果收到其他响应,可能是服务器拒绝了连接
  79. if len(data) > 0:
  80. print(f"收到非预期响应: {data.hex()}")
  81. else:
  82. print("未收到STARTDT确认响应")
  83. return False
  84. except socket.timeout:
  85. print("等待STARTDT确认超时")
  86. return False
  87. except ConnectionResetError:
  88. print("发送STARTDT时连接被重置,服务器可能拒绝了连接")
  89. print(traceback.format_exc())
  90. return False
  91. except Exception as e:
  92. print(f"发送STARTDT时发生错误: {e}")
  93. print(traceback.format_exc())
  94. return False
  95. def send_general_interrogation(self):
  96. try:
  97. apci = APCI(14, self.send_seq, self.recv_seq)
  98. gi_asdu = struct.pack('!BBHHHBBH',
  99. 100, # 类型ID 100 = 总召
  100. 0x01, # VSQ (1个元素)
  101. 0x06, # COT (激活)
  102. 0x00, # 公共地址
  103. 0x00, # 信息对象地址
  104. 0x00, # 限定词 (QOI)
  105. 0x14, # 总召限定词 (20)
  106. 0x00) # 无时标
  107. gi_frame = apci.pack() + gi_asdu
  108. self.socket.send(gi_frame)
  109. self.send_seq += 1
  110. print("发送总召命令")
  111. return True
  112. except Exception as e:
  113. print(f"发送总召命令错误: {e}")
  114. return False
  115. def receive_data(self):
  116. while True:
  117. try:
  118. data = self.socket.recv(1024)
  119. if not data:
  120. print("连接被远程主机关闭")
  121. break
  122. apci = APCI.unpack(data[:6])
  123. if not apci:
  124. continue
  125. self.recv_seq = apci.send_seq
  126. if apci.length == 4:
  127. if data[6:10] == b'\x0B\x00\x00\x00':
  128. print("收到STARTDT CON")
  129. elif data[6:10] == b'\x13\x00\x00\x00':
  130. print("收到STOPDT CON")
  131. continue
  132. asdu = ASDU.unpack(data[6:6 + apci.length - 4])
  133. if not asdu:
  134. continue
  135. self._process_asdu(asdu)
  136. if asdu.cot in [1, 3, 5, 7, 9, 11, 20]:
  137. self._send_ack()
  138. except socket.timeout:
  139. print("接收数据超时,发送测试帧保持连接...")
  140. self._send_test_frame()
  141. continue
  142. except ConnectionResetError:
  143. print("连接被远程主机重置")
  144. break
  145. except Exception as e:
  146. print(f"接收数据错误: {e}")
  147. break
  148. def _process_asdu(self, asdu):
  149. type_desc = IEC104Type.get_description(asdu.type_id)
  150. cot_desc = self._get_cot_description(asdu.cot)
  151. print(f"\n收到ASDU: 类型={type_desc}, 原因={cot_desc}({asdu.cot}), 公共地址={asdu.common_addr}")
  152. for io in asdu.io_elements:
  153. print(f" 点地址: {io['address']}, 数据: {self._format_io_data(asdu.type_id, io['data'])}")
  154. if 'time' in io['data']:
  155. time_data = io['data']['time']
  156. print(f" 时间: {time_data['year']}-{time_data['month']:02d}-{time_data['day']:02d} "
  157. f"{time_data['hour']:02d}:{time_data['minute']:02d}:{time_data['milliseconds'] / 1000:.3f}")
  158. def _format_io_data(self, type_id, data):
  159. if type_id in [1, 30, 151]:
  160. return f"值={data['value']}, 质量={data['quality']}"
  161. elif type_id in [3, 31]:
  162. states = ["中间状态", "分", "合", "不确定"]
  163. state = states[data['value']] if data['value'] < len(states) else "未知"
  164. return f"状态={state}({data['value']}), 质量={data['quality']}"
  165. elif type_id in [9, 11, 13]:
  166. return f"值={data['value']}, 质量={data['quality']}"
  167. else:
  168. return str(data)
  169. def _get_cot_description(self, cot):
  170. descriptions = {
  171. 1: "周期/循环",
  172. 2: "背景扫描",
  173. 3: "突发",
  174. 5: "请求或被请求",
  175. 6: "激活",
  176. 7: "激活确认",
  177. 8: "停止激活",
  178. 9: "停止激活确认",
  179. 20: "响应总召"
  180. }
  181. return descriptions.get(cot, f"未知原因({cot})")
  182. def _send_ack(self):
  183. try:
  184. apci = APCI(4, self.send_seq, self.recv_seq)
  185. ack_frame = apci.pack() + b'\x00\x00\x00\x00'
  186. self.socket.send(ack_frame)
  187. self.send_seq += 1
  188. except Exception as e:
  189. print(f"发送确认帧错误: {e}")
  190. def _send_test_frame(self):
  191. try:
  192. apci = APCI(4, self.send_seq, self.recv_seq)
  193. test_frame = apci.pack() + b'\x43\x00\x00\x00' # 测试帧
  194. self.socket.send(test_frame)
  195. self.send_seq += 1
  196. except Exception as e:
  197. print(f"发送测试帧错误: {e}")
  198. def main():
  199. client = IEC104Client("192.168.50.242", 2404)
  200. try:
  201. while True:
  202. if client.connect():
  203. if client.send_startdt():
  204. if client.send_general_interrogation():
  205. client.receive_data()
  206. print(f"将在 {client.retry_interval} 秒后尝试重新连接...")
  207. time.sleep(client.retry_interval)
  208. except KeyboardInterrupt:
  209. print("\n用户中断")
  210. except Exception as e:
  211. print(f"发生错误: {e}")
  212. print(traceback.format_exc())
  213. finally:
  214. client.disconnect()
  215. if __name__ == "__main__":
  216. main()