def do_disconnect(self, arg: str): 'Disconnect from an RTU' if len(self.__rtu_comms) > 0: rtuaddr = runprompt( listq(message='Disconnect from which RTU?', choices=self.__rtu_comms.keys())) self.__keepalive_kill[rtuaddr] = True # Stop keepalive t = self.__keepalive[rtuaddr] t.join() self.__rtu_u_state[rtuaddr] = 0x08 # Expect STOPDT con pkt = APDU() / APCI(ApduLen=4, Type=0x03, UType=0x04) # STOPDT act self.__rtu_comms[rtuaddr].send(pkt.build()) while self.__rtu_u_state[ rtuaddr] is not None and self.__rtu_u_state[rtuaddr] > 0: print(f'\rTerminating connection with {rtuaddr:s} ... ', end='') sleep(0.33) print('') self.__killsignals[rtuaddr] = True t = self.__threads.pop(rtuaddr) t.join() s = self.__rtu_comms.pop(rtuaddr) d = self.__rtu_data.pop(rtuaddr) k = self.__killsignals.pop(rtuaddr) self.__rtu_asdu.pop(rtuaddr) s.close() else: print('Not connected to any RTUs')
def do_connect(self, arg: str): 'Connect to a new RTU' try: arg = arg.split(';') self.__rtu_asdu[arg[0]] = arg[1] arg = arg[0] assert IPv4_REGEX.match(arg) is not None if '/' in arg: prefix = int(arg.split('/')[1]) assert prefix > 0 and prefix <= 32 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) s.settimeout(2) s.connect((arg, IEC104_PORT)) self.__rtu_comms[arg] = s self.__killsignals[arg] = False self.__rtu_u_state[arg] = 0x02 # Expect a STARTDT con U-frame self.__rtu_i_state[arg] = None # Don't expect any I-frames t = Thread(target=self.__handle_rtu, kwargs={ 's': s, 'k': arg }) # Create a receiving thread for this RTU. t.start() self.__threads[arg] = t pkt = APDU() / APCI(ApduLen=4, Type=0x03, UType=0x01) # STARTDT act s.send(pkt.build()) while self.__rtu_u_state[ arg] is not None and self.__rtu_u_state[arg] > 0: print(f'\rInitiating connection with peer {arg:s} ... ', end='') sleep(0.33) print('') if self.__rtu_u_state[arg] is None: print(f'Unable to connect to {arg:s}') self.__killsignals[arg] = True t = self.__threads.pop(arg) t.join() s.close() self.__rtu_asdu.pop(arg) self.__rtu_comms.pop(arg) self.__killsignals.pop(arg) self.__rtu_i_state.pop(arg) self.__rtu_u_state.pop(arg) if arg in self.__rtu_data.keys(): self.__rtu_data.pop(arg) else: self.__keepalive_kill[arg] = False t = Thread(target=self.__keepalive_handler, kwargs={ 's': s, 'k': arg }) # Create a keepalive thread for this RTU. t.start() self.__keepalive[arg] = t except AssertionError: print('Invalid IPv4 address: %s' % arg) except socket.timeout: print('Unable to connect to %s' % arg) return False
def __keepalive_handler(self, s: socket.socket, k:str): while not self.__done and not self.__keepalive_kill[k]: sleep(10) # Send a keepalive every 10 seconds self.__rtu_u_state[k] = 0x20 # Expect a TESTFR con pkt = APDU()/APCI(ApduLen=4, Type=0x03, UType=0x10) # 'TESTFR act' as a keepalive self.__rtu_comms[k].send(pkt.build()) while self.__rtu_u_state[k] is not None and self.__rtu_u_state[k] > 0: sleep(0.33)
def build_104_asdu_packet(typeASDU: int, asdu: int, ioa: int, tx: int, rx: int, causeTx: int = 1, **kwargs) -> bytes: pkt = APDU() pkt /= APCI(ApduLen=APDULEN[typeASDU], Type=0x00, Tx=tx, Rx=rx) if typeASDU == 3: pkt /= ASDU(TypeId=typeASDU, SQ=0, NumIx=1, CauseTx=causeTx, Test=0, OA=0, Addr=asdu, IOA=[IOA3(IOA=ioa, DIQ=DIQ(DPI=kwargs['value'], flags=0))]) elif typeASDU == 36: ct = cp56time() pkt /= ASDU( TypeId=typeASDU, SQ=0, NumIx=1, CauseTx=causeTx, Test=0, OA=0, Addr=asdu, IOA=[IOA36(IOA=ioa, Value=kwargs['value'], QDS=0x00, CP56Time=ct)]) elif typeASDU == 45: pkt /= ASDU(TypeId=typeASDU, SQ=0, NumIx=1, CauseTx=causeTx, Test=0, OA=0, Addr=asdu, IOA=[ IOA45(IOA=ioa, SCO=SCO(SE=kwargs['SE'], QU=kwargs['QU'], SCS=kwargs['SCS'])) ]) elif typeASDU == 50: pkt /= ASDU( TypeId=typeASDU, SQ=0, NumIx=1, CauseTx=causeTx, Test=0, OA=0, Addr=asdu, IOA=[IOA50(IOA=ioa, Value=kwargs['value'], QOS=QOS(QL=0, SE=0))]) else: raise AttributeError if __name__ == '__main__': pkt.show() return pkt.build()
def testfr(actcon: bool = False) -> bytes: pkt = APDU() / APCI(ApduLen=4, Type=0x03, UType=0x10 << int(actcon)) return pkt.build()