├── .gitignore ├── dcp.py ├── main.py ├── protocol.py ├── rpc.py ├── server.py ├── templates ├── device.html ├── discover.html ├── inm0.html └── inm1.html └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /dcp.py: -------------------------------------------------------------------------------- 1 | import argparse, time 2 | 3 | 4 | from util import * 5 | from protocol import * 6 | 7 | 8 | params = { 9 | "name": PNDCPBlock.NAME_OF_STATION, 10 | "ip": PNDCPBlock.IP_ADDRESS 11 | } 12 | 13 | 14 | class DCPDeviceDescription: 15 | def __init__(self, mac, blocks): 16 | self.mac = mac2s(mac) 17 | self.name = blocks[PNDCPBlock.NAME_OF_STATION].decode() 18 | self.ip = s2ip(blocks[PNDCPBlock.IP_ADDRESS][0:4]) 19 | self.netmask = s2ip(blocks[PNDCPBlock.IP_ADDRESS][4:8]) 20 | self.gateway = s2ip(blocks[PNDCPBlock.IP_ADDRESS][8:12]) 21 | self.vendorHigh, self.vendorLow, self.devHigh, self.devLow = unpack(">BBBB", blocks[PNDCPBlock.DEVICE_ID][0:4]) 22 | 23 | 24 | def get_param(s, src, target, param): 25 | dst = s2mac(target) 26 | 27 | if param not in params.keys(): 28 | return 29 | 30 | param = params[param] 31 | 32 | block = PNDCPBlockRequest(param[0], param[1], 0, bytes()) 33 | dcp = PNDCPHeader(0xfefd, PNDCPHeader.GET, PNDCPHeader.REQUEST, 0x012345, 0, 2, block) 34 | eth = EthernetVLANHeader(dst, src, 0x8100, 0, PNDCPHeader.ETHER_TYPE, dcp) 35 | 36 | s.send(bytes(eth)) 37 | 38 | return list(read_response(s, src, once=True).values())[0][param] 39 | 40 | def set_param(s, src, target, param, value): 41 | dst = s2mac(target) 42 | 43 | if param not in params.keys(): 44 | return 45 | 46 | param = params[param] 47 | 48 | block = PNDCPBlockRequest(param[0], param[1], len(value) + 2, bytes([0x00, 0x00]) + bytes(value, encoding='ascii')) 49 | dcp = PNDCPHeader(0xfefd, PNDCPHeader.SET, PNDCPHeader.REQUEST, 0x012345, 0, len(value) + 6 + (1 if len(value) % 2 == 1 else 0), block) 50 | eth = EthernetVLANHeader(dst, src, 0x8100, 0, PNDCPHeader.ETHER_TYPE, dcp) 51 | 52 | s.send(bytes(eth)) 53 | 54 | # ignore response 55 | s.recv(1522) 56 | 57 | time.sleep(2) 58 | 59 | 60 | def send_discover(s, src): 61 | 62 | block = PNDCPBlockRequest(0xFF, 0xFF, 0, bytes()) 63 | dcp = PNDCPHeader(0xfefe, PNDCPHeader.IDENTIFY, PNDCPHeader.REQUEST, 0x012345, 0, len(block), payload=block) 64 | eth = EthernetVLANHeader(s2mac("01:0e:cf:00:00:00"), src, 0x8100, 0, PNDCPHeader.ETHER_TYPE, payload=dcp) 65 | 66 | s.send(bytes(eth)) 67 | 68 | 69 | def send_request(s, src, t, value): 70 | 71 | block = PNDCPBlockRequest(t[0], t[1], len(value), bytes(value)) 72 | dcp = PNDCPHeader(0xfefe, PNDCPHeader.IDENTIFY, PNDCPHeader.REQUEST, 0x012345, 0, len(block), block) 73 | eth = EthernetVLANHeader(s2mac("01:0e:cf:00:00:00"), src, 0x8100, 0, PNDCPHeader.ETHER_TYPE, dcp) 74 | 75 | s.send(bytes(eth)) 76 | 77 | 78 | def read_response(s, my_mac, to=20, once=False, debug=False): 79 | ret = {} 80 | found = [] 81 | s.settimeout(2) 82 | try: 83 | with max_timeout(to) as t: 84 | while True: 85 | if t.timed_out: 86 | break 87 | try: 88 | data = s.recv(1522) 89 | except timeout: 90 | continue 91 | 92 | # nur Ethernet Pakete an uns und vom Ethertype Profinet 93 | eth = EthernetHeader(data) 94 | if eth.dst != my_mac or eth.type != PNDCPHeader.ETHER_TYPE: 95 | continue 96 | debug and print("MAC address:", mac2s(eth.src)) 97 | 98 | # nur DCP Identify Responses 99 | pro = PNDCPHeader(eth.payload) 100 | if not (pro.service_type == PNDCPHeader.RESPONSE): 101 | continue 102 | 103 | # Blöcke der Response parsen 104 | blocks = pro.payload 105 | length = pro.length 106 | parsed = {} 107 | 108 | while length > 6: 109 | block = PNDCPBlock(blocks) 110 | blockoption = (block.option, block.suboption) 111 | parsed[blockoption] = block.payload 112 | 113 | block_len = block.length 114 | if blockoption == PNDCPBlock.NAME_OF_STATION: 115 | debug and print("Name of Station: %s" % block.payload) 116 | parsed["name"] = block.payload 117 | elif blockoption == PNDCPBlock.IP_ADDRESS: 118 | debug and print(str(block.parse_ip())) 119 | parsed["ip"] = s2ip(block.payload[0:4]) 120 | elif blockoption == PNDCPBlock.DEVICE_ID: 121 | parsed["devId"] = block.payload 122 | 123 | # Padding: 124 | if block_len % 2 == 1: 125 | block_len += 1 126 | 127 | # geparsten Block entfernen 128 | blocks = blocks[block_len+4:] 129 | length -= 4 + block_len 130 | 131 | ret[eth.src] = parsed 132 | 133 | if once: 134 | break 135 | 136 | except TimeoutError: 137 | pass 138 | 139 | return ret 140 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | import dcp 5 | import rpc 6 | 7 | from util import * 8 | from protocol import * 9 | 10 | 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument("-i", required=True, 13 | help="use INTERFACE", metavar="INTERFACE") 14 | parser.add_argument("action", choices=("discover", "get-param", "set-param", "read", "read-inm0-filter", "read-inm0", "read-inm1", "write-inm1")) 15 | parser.add_argument("target", nargs='?', help="MAC address of the device") 16 | parser.add_argument("param", nargs='?', help="parameter to read/write") 17 | parser.add_argument("value", nargs='?', help="value to write") 18 | parser.add_argument("additional1", nargs='?', help="additional parameters") 19 | parser.add_argument("additional2", nargs='?', help="additional parameters") 20 | 21 | args = parser.parse_args() 22 | 23 | 24 | s = ethernet_socket(args.i, 3) 25 | src = get_mac(args.i) 26 | 27 | if args.action == "discover": 28 | dcp.send_discover(s, src) 29 | dcp.read_response(s, src, debug=True) 30 | elif args.action == "get-param": 31 | dcp.get_param(s, src, args.target, args.param) 32 | elif args.action == "set-param": 33 | dcp.set_param(s, src, args.target, args.param, args.value) 34 | elif args.action.startswith("read") or args.action.startswith("write"): 35 | print("Getting station info ...") 36 | info = rpc.get_station_info(s, src, args.target) 37 | con = rpc.RPCCon(info) 38 | 39 | print("Connecting to device ...") 40 | con.connect(src) 41 | 42 | if args.action == "read": 43 | print(con.read(api=int(args.param), slot=int(args.value), subslot=int(args.additional1), idx=int(args.additional2, 16)).payload) 44 | 45 | if args.action[5:] == "inm0-filter": 46 | 47 | data = con.read_inm0filter() 48 | for api in data.keys(): 49 | for slot_number, (module_ident_number, subslots) in data[api].items(): 50 | print("Slot %d has module 0x%04X" % (slot_number, module_ident_number)) 51 | for subslot_number, submodule_ident_number in subslots.items(): 52 | print(" Subslot %d has submodule 0x%04X" % (subslot_number, submodule_ident_number)) 53 | 54 | elif args.action[5:] == "inm0": 55 | inm0 = PNInM0(con.read(api=int(args.param), slot=int(args.value), subslot=int(args.additional1), idx=PNInM0.IDX).payload) 56 | print(inm0) 57 | 58 | elif args.action[5:] == "inm1": 59 | inm1 = PNInM1(con.read(api=int(args.param), slot=int(args.value), subslot=int(args.additional1), idx=PNInM1.IDX).payload) 60 | print(inm1) 61 | 62 | elif args.action[6:] == "inm1": 63 | api = int(args.param) 64 | slot = int(args.value) 65 | subslot = int(args.additional1) 66 | inm1 = PNInM1(con.read(api, slot, subslot, PNInM1.IDX).payload) 67 | inm1 = PNInM1(inm1.block_header, bytes(args.additional2, "utf-8"), inm1.im_tag_location) 68 | con.write(api, slot, subslot, PNInM1.IDX, inm1) 69 | 70 | 71 | -------------------------------------------------------------------------------- /protocol.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | from util import * 5 | 6 | 7 | # ---------------------------------------------------------------------------------------------- 8 | # 9 | # DCP 10 | # 11 | 12 | EthernetHeader = make_packet("EthernetHeader", ( 13 | ("dst", ("6s", mac2s)), 14 | ("src", ("6s", mac2s)), 15 | ("type", ("H", "0x%04X")) 16 | )) 17 | 18 | EthernetVLANHeader = make_packet("EthernetHeader", ( 19 | ("dst", ("6s", mac2s)), 20 | ("src", ("6s", mac2s)), 21 | ("tpid", ("H", "0x%04X")), 22 | ("tci", ("H", "0x%04X")), 23 | ("type", ("H", "0x%04X")) 24 | )) 25 | 26 | PNDCPHeader = make_packet("PNDCPHeader", ( 27 | ("frame_id", ("H", "0x%04X")), 28 | ("service_id", "B"), 29 | ("service_type", "B"), 30 | ("xid", ("I", "0x%08X")), 31 | ("resp", "H"), 32 | ("length", "H") 33 | ), statics={ 34 | "ETHER_TYPE": 0x8892, 35 | "GET": 3, 36 | "SET": 4, 37 | "IDENTIFY": 5, 38 | "REQUEST": 0, 39 | "RESPONSE": 1 40 | }) 41 | 42 | class IPConfiguration(namedtuple("IPConfiguration", ["address", "netmask", "gateway"])): 43 | def __str__(self): 44 | return ("IP configuration\n" 45 | "Address: %s\n" 46 | "Netmask: %s\n" 47 | "Gateway: %s\n") % (self.address, self.netmask, self.gateway) 48 | 49 | class PNDCPBlockRequest(make_packet("PNDCPBlockRequest", ( 50 | ("option", "B"), 51 | ("suboption", "B"), 52 | ("length", "H") 53 | ), payload_size_field="length")): 54 | def parse_ip(self): 55 | return IPConfiguration(s2ip(self.payload[0:4]), s2ip(self.payload[4:8]), s2ip(self.payload[8:12])) 56 | 57 | class PNDCPBlock(make_packet("PNDCPBlockRequest", ( 58 | ("option", "B"), 59 | ("suboption", "B"), 60 | ("length", "H"), 61 | ("status", "H"), 62 | ), payload_size_field="length", payload_offset=-2)): 63 | 64 | IP_ADDRESS = (1, 2) 65 | NAME_OF_STATION = (2, 2) 66 | DEVICE_ID = (2, 3) 67 | ALL = (0xFF, 0xFF) 68 | 69 | def parse_ip(self): 70 | return IPConfiguration(s2ip(self.payload[0:4]), s2ip(self.payload[4:8]), s2ip(self.payload[8:12])) 71 | 72 | 73 | 74 | 75 | # ---------------------------------------------------------------------------------------------- 76 | # 77 | # RPC 78 | # 79 | 80 | _UUID = [0x6c, 0x97, 0x11, 0xd1, 0x82, 0x71, 0x00, 0xA0, 0x24, 0x42, 0xDF, 0x7D] 81 | 82 | PNRPCHeader = make_packet("PNRPCHeader", ( 83 | ("version", "B"), 84 | ("packet_type", "B"), 85 | ("flags1", "B"), 86 | ("flags2", "B"), 87 | ("drep", "3s"), 88 | ("serial_high", "B"), 89 | ("object_uuid", "16s"), 90 | ("interface_uuid", "16s"), 91 | ("activity_uuid", "16s"), 92 | ("server_boot_time", "I"), 93 | ("interface_version", "I"), 94 | ("sequence_number", "I"), 95 | ("operation_number", "H"), 96 | ("interface_hint", "H"), 97 | ("activity_hint", "H"), 98 | ("length_of_body", "H"), 99 | ("fragment_number", "H"), 100 | ("authentication_protocol", "B"), 101 | ("serial_low", "B") 102 | ), statics={ 103 | "REQUEST": 0x00, 104 | "PING": 0x01, 105 | "RESPONSE": 0x02, 106 | "FAULT": 0x03, 107 | "WORKING": 0x04, 108 | "PONG": 0x05, 109 | "REJECT": 0x06, 110 | "ACK": 0x07, 111 | "CANCEL": 0x08, 112 | "FRAG_ACK": 0x09, 113 | "CANCEL_ACK": 0xA, 114 | 115 | "CONNECT": 0x00, 116 | "RELEASE": 0x01, 117 | "READ": 0x02, 118 | "WRITE": 0x03, 119 | "CONTROL": 0x04, 120 | "IMPLICIT_READ": 0x05, 121 | 122 | "IFACE_UUID_DEVICE": bytes([0xDE, 0xA0, 0x00, 0x01] + _UUID), 123 | "IFACE_UUID_CONTROLLER": bytes([0xDE, 0xA0, 0x00, 0x02] + _UUID), 124 | "IFACE_UUID_SUPERVISOR": bytes([0xDE, 0xA0, 0x00, 0x03] + _UUID), 125 | "IFACE_UUID_PARAMSERVER": bytes([0xDE, 0xA0, 0x00, 0x04] + _UUID), 126 | 127 | "OBJECT_UUID_PREFIX": bytes([0xDE, 0xA0, 0x00, 0x00, 0x6C, 0x97, 0x11, 0xD1, 0x82, 0x71]) 128 | }) 129 | 130 | 131 | PNNRDData = make_packet("PNNRDData", ( 132 | ("args_maximum_status", "I"), 133 | ("args_length", "I"), 134 | ("maximum_count", "I"), 135 | ("offset", "I"), 136 | ("actual_count", "I") 137 | )) 138 | 139 | 140 | PNIODHeader = make_packet("PNIODHeader", ( 141 | ("block_header", "6s"), 142 | ("sequence_number", "H"), 143 | ("ar_uuid", "16s"), 144 | ("api", "I"), 145 | ("slot", "H"), 146 | ("subslot", "H"), 147 | ("padding1", "H"), 148 | ("index", "H"), 149 | ("length", "I"), 150 | ("target_ar_uuid", "16s"), 151 | ("padding2", "8s") 152 | )) 153 | 154 | 155 | PNBlockHeader = make_packet("PNBlockHeader", ( 156 | ("block_type", "H"), 157 | ("block_length", "H"), 158 | ("block_version_high", "B"), 159 | ("block_version_low", "B") 160 | ), payload=False, statics={ 161 | "IDOReadRequestHeader": 0x0009, 162 | "IODReadResponseHeader": 0x8009, 163 | "InM0": 0x0020, 164 | "InM0FilterDataSubModul": 0x0030, 165 | "InM0FilterDataModul": 0x0031, 166 | "InM0FilterDataDevice": 0x0032 167 | }) 168 | 169 | PNARBlockRequest = make_packet("PNARBlockRequest", ( 170 | ("block_header", "6s"), 171 | ("ar_type", "H"), 172 | ("ar_uuid", "16s"), 173 | ("session_key", "H"), 174 | ("cm_initiator_mac_address", "6s"), 175 | ("cm_initiator_object_uuid", "16s"), 176 | ("ar_properties", "I"), 177 | ("cm_initiator_activity_timeout_factor", "H"), 178 | ("initiator_udp_rtport", "H"), 179 | ("station_name_length", "H") 180 | ), vlf="cm_initiator_station_name", vlf_size_field="station_name_length") 181 | 182 | 183 | PNIODReleaseBlock = make_packet("IODReleaseBlock", ( 184 | ("block_header", "6s"), 185 | ("padding1", "H"), 186 | ("ar_uuid", "16s"), 187 | ("session_key", "H"), 188 | ("padding2", "H"), 189 | ("control_command", "H"), 190 | ("control_block_properties", "H") 191 | )) 192 | 193 | 194 | PNInM0 = make_packet("InM0", ( 195 | ("block_header", "6s"), 196 | ("vendor_id_high", "B"), 197 | ("vendor_id_low", "B"), 198 | ("order_id", ("20s", decode_bytes)), 199 | ("im_serial_number", ("16s", decode_bytes)), 200 | ("im_hardware_revision", "H"), 201 | ("sw_revision_prefix", "B"), 202 | ("im_sw_revision_functional_enhancement", "B"), 203 | ("im_sw_revision_bug_fix", "B"), 204 | ("im_sw_revision_internal_change", "B"), 205 | ("im_revision_counter", "H"), 206 | ("im_profile_id", "H"), 207 | ("im_profile_specific_type", "H"), 208 | ("im_version", "H"), 209 | ("im_supported", "H") 210 | ), payload=False, statics={ "IDX": 0xAFF0 }) 211 | 212 | PNInM1 = make_packet("InM1", ( 213 | ("block_header", "6s"), 214 | ("im_tag_function", ("32s", decode_bytes)), 215 | ("im_tag_location", ("22s", decode_bytes)), 216 | ), payload=False, statics={ "IDX": 0xAFF1 }) 217 | 218 | 219 | -------------------------------------------------------------------------------- /rpc.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from socket import MSG_WAITALL 3 | from datetime import datetime 4 | 5 | from util import * 6 | from protocol import * 7 | 8 | import dcp 9 | 10 | 11 | def get_station_info(s, src, name): 12 | dcp.send_request(s, src, PNDCPBlock.NAME_OF_STATION, bytes(name, 'utf-8')) 13 | resp = list(dcp.read_response(s, src, once=True).items())[0] 14 | return dcp.DCPDeviceDescription(*resp) 15 | 16 | 17 | class RPCCon: 18 | def __init__(self, info): 19 | self.info = info 20 | self.peer = (info.ip, 0x8894) 21 | 22 | self.ar_uuid = bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]) 23 | self.activity_uuid = self.ar_uuid 24 | 25 | self.local_object_uuid = PNRPCHeader.OBJECT_UUID_PREFIX + bytes([0x00, 0x01, 0x76, 0x54, 0x32, 0x10]) 26 | self.remote_object_uuid = PNRPCHeader.OBJECT_UUID_PREFIX + bytes([0x00, 0x01, self.info.devHigh, self.info.devLow, self.info.vendorHigh, self.info.vendorLow]) 27 | 28 | self.live = None 29 | 30 | self.u = socket(AF_INET, SOCK_DGRAM) 31 | 32 | 33 | def _create_rpc(self, operation, nrd): 34 | return PNRPCHeader(0x04, PNRPCHeader.REQUEST, 35 | 0x20, # Flags1 36 | 0x00, # Flags2 37 | bytes([0x00, 0x00, 0x00]), # DRep 38 | 0x00, # Serial High 39 | self.remote_object_uuid, 40 | PNRPCHeader.IFACE_UUID_DEVICE, 41 | self.activity_uuid, 42 | 0, # ServerBootTime 43 | 1, # InterfaceVersion 44 | 0, # SequenceNumber 45 | operation, 46 | 0xFFFF, # InterfaceHint 47 | 0xFFFF, # ActivityHint 48 | len(nrd), 49 | 0, # FragmentNumber 50 | 0, # AuthenticationProtocol 51 | 0, # SerialLow 52 | payload=nrd 53 | ) 54 | 55 | def _create_nrd(self, payload): 56 | return PNNRDData(1500, len(payload), 1500, 0, len(payload), payload=payload) 57 | 58 | def _check_timeout(self): 59 | if self.live is not None and (datetime.now() - self.live).seconds >= 10: 60 | self.connect() 61 | 62 | def connect(self, src_mac=None): 63 | if self.live is None: 64 | self.src_mac = src_mac 65 | 66 | block = PNBlockHeader(0x0101, PNARBlockRequest.fmt_size - 2, 0x01, 0x00) 67 | ar = PNARBlockRequest(bytes(block), 68 | 0x0006, # AR Type 69 | self.ar_uuid, # AR UUID 70 | 0x1234, # Session key 71 | self.src_mac, 72 | self.local_object_uuid, 73 | 0x131, # AR Properties 74 | 100, # Timeout factor 75 | 0x8892, # udp port? 76 | 2, 77 | bytes("tp", encoding="utf-8"), payload=bytes() 78 | ) 79 | nrd = self._create_nrd(ar) 80 | rpc = self._create_rpc(PNRPCHeader.CONNECT, nrd) 81 | self.u.sendto(bytes(rpc), self.peer) 82 | 83 | data = self.u.recvfrom(4096)[0] 84 | # ignore response 85 | #rpc = PNRPCHeader(data) 86 | #nrd = PNNRDData(rpc.payload) 87 | #ar = PNARBlockRequest(nrd.payload) 88 | #block = PNBlockHeader(iod.block_header) 89 | 90 | self.live = datetime.now() 91 | 92 | def read(self, api, slot, subslot, idx): 93 | self._check_timeout() 94 | 95 | block = PNBlockHeader(PNBlockHeader.IDOReadRequestHeader, 60, 0x01, 0x00) 96 | iod = PNIODHeader(bytes(block), 0, self.ar_uuid, api, slot, subslot, 0, idx, 4096, bytes(16), bytes(8), payload=bytes()) 97 | nrd = self._create_nrd(iod) 98 | rpc = self._create_rpc(PNRPCHeader.READ, nrd) 99 | self.u.sendto(bytes(rpc), self.peer) 100 | 101 | data = self.u.recvfrom(4096)[0] 102 | rpc = PNRPCHeader(data) 103 | nrd = PNNRDData(rpc.payload) 104 | iod = PNIODHeader(nrd.payload) 105 | block = PNBlockHeader(iod.block_header) 106 | 107 | self.live = datetime.now() 108 | 109 | return iod 110 | 111 | def read_implicit(self, api, slot, subslot, idx): 112 | block = PNBlockHeader(PNBlockHeader.IDOReadRequestHeader, 60, 0x01, 0x00) 113 | iod = PNIODHeader(bytes(block), 0, bytes(16), api, slot, subslot, 0, idx, 4096, bytes(16), bytes(8), payload=bytes()) 114 | nrd = self._create_nrd(iod) 115 | rpc = self._create_rpc(PNRPCHeader.IMPLICIT_READ, nrd) 116 | self.u.sendto(bytes(rpc), self.peer) 117 | 118 | data = self.u.recvfrom(4096)[0] 119 | rpc = PNRPCHeader(data) 120 | nrd = PNNRDData(rpc.payload) 121 | iod = PNIODHeader(nrd.payload) 122 | block = PNBlockHeader(iod.block_header) 123 | 124 | return iod 125 | 126 | def write(self, api, slot, subslot, idx, data): 127 | self._check_timeout() 128 | block = PNBlockHeader(0x8, 60, 0x01, 0x00) 129 | iod = PNIODHeader(bytes(block), 0, self.ar_uuid, api, slot, subslot, 0, idx, len(data), bytes(16), bytes(8), payload=bytes(data)) 130 | nrd = self._create_nrd(iod) 131 | rpc = self._create_rpc(PNRPCHeader.WRITE, nrd) 132 | self.u.sendto(bytes(rpc), self.peer) 133 | 134 | data = self.u.recvfrom(4096)[0] 135 | # ignore response 136 | 137 | self.live = datetime.now() 138 | 139 | 140 | def read_inm0filter(self): 141 | data = self.read(api=0, slot=0, subslot=0, idx=0xF840).payload 142 | block = PNBlockHeader(data) 143 | data = data[6:] 144 | 145 | ret = {} 146 | 147 | num_api, = unpack(">H", data[:2]) 148 | data = data[2:] 149 | for i_api in range(0, num_api): 150 | api, num_modules = unpack(">IH", data[:6]) 151 | data = data[6:] 152 | ret[api] = {} 153 | for i_module in range(0, num_modules): 154 | slot_number, module_ident_num, num_subslots = unpack(">HIH", data[:8]) 155 | data = data[8:] 156 | ret[api][slot_number] = (module_ident_num, {}) 157 | for i_subslot in range(0, num_subslots): 158 | subslot_number, submodule_ident_number = unpack(">HI", data[:6]) 159 | data = data[6:] 160 | ret[api][slot_number][1][subslot_number] = submodule_ident_number 161 | 162 | return ret 163 | 164 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | import argparse 3 | import binascii 4 | 5 | from flask import Flask, request, render_template, redirect, url_for 6 | 7 | 8 | from protocol import * 9 | import dcp, rpc 10 | 11 | 12 | app = Flask(__name__) 13 | conns = {} 14 | 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument("-i", required=True, 17 | help="use INTERFACE", metavar="INTERFACE") 18 | args = parser.parse_args() 19 | 20 | s = ethernet_socket(args.i, 3) 21 | src = get_mac(args.i) 22 | 23 | def get_connection(name): 24 | if name not in conns.keys(): 25 | info = rpc.get_station_info(s, src, name) 26 | con = rpc.RPCCon(info) 27 | con.connect(src) 28 | conns[name] = (info, con) 29 | return conns[name] 30 | 31 | 32 | @app.route("/") 33 | def index(): 34 | dcp.send_discover(s, src) 35 | resp = [dcp.DCPDeviceDescription(mac, blocks) for mac, blocks in dcp.read_response(s, src).items()] 36 | return render_template("discover.html", resp=resp) 37 | 38 | @app.route("/device") 39 | def device(): 40 | name = request.values.get('name') 41 | info, con = get_connection(name) 42 | data = con.read_inm0filter() 43 | return render_template("device.html", info=info, data=data) 44 | 45 | @app.route("/inm0", methods=["GET", "POST"]) 46 | def inm0(): 47 | name = request.values.get('name') 48 | 49 | api = int(request.values.get('api')) 50 | slot = int(request.values.get('slot')) 51 | subslot = int(request.values.get('subslot')) 52 | 53 | info, con = get_connection(name) 54 | payload = con.read(api, slot, subslot, idx=PNInM0.IDX).payload 55 | if len(payload) != 0: 56 | data = PNInM0(payload) 57 | else: 58 | data = None 59 | 60 | idx = request.values.get('idx') 61 | paramdata = None 62 | if idx is not None: 63 | _idx = int(idx, 16) 64 | if request.values.get('action') == "write": 65 | paramdata = request.values.get('data') 66 | con.write(api, slot, subslot, _idx, binascii.unhexlify(paramdata.replace(":", ""))) 67 | else: 68 | paramdata = to_hex(con.read(api, slot, subslot, idx=_idx).payload) 69 | else: 70 | idx = "" 71 | 72 | return render_template("inm0.html", name=name, api=api, slot=slot, subslot=subslot, idx=idx, data=data, paramdata=paramdata, inm1_supported=(data.im_supported&1<<1 if data is not None else False)) 73 | 74 | @app.route("/inm1", methods=["GET", "POST"]) 75 | def inm1(): 76 | name = request.values.get('name') 77 | 78 | api = int(request.values.get('api')) 79 | slot = int(request.values.get('slot')) 80 | subslot = int(request.values.get('subslot')) 81 | 82 | info, con = get_connection(name) 83 | 84 | data = PNInM1(con.read(api, slot, subslot, idx=PNInM1.IDX).payload) 85 | 86 | if request.method == 'POST': 87 | function = request.values.get('function') 88 | location = request.values.get('location') 89 | inm1 = PNInM1(data.block_header, bytes(function, "utf-8"), bytes(location, "utf-8")) 90 | con.write(api, slot, subslot, PNInM1.IDX, inm1) 91 | return redirect(url_for("inm1", name=name, api=api, slot=slot, subslot=subslot)) 92 | else: 93 | return render_template("inm1.html", name=name, api=api, slot=slot, subslot=subslot, data=data) 94 | 95 | @app.route("/rename") 96 | def rename(): 97 | mac = request.values.get('mac') 98 | name = request.values.get('name') 99 | 100 | old_name = dcp.get_param(s, src, mac, "name").decode() 101 | 102 | # delete connection 103 | if old_name in conns.keys(): 104 | del conns[old_name] 105 | 106 | dcp.set_param(s, src, mac, "name", name) 107 | 108 | return redirect(url_for("device", name=name)) 109 | 110 | app.run(debug=True) 111 | -------------------------------------------------------------------------------- /templates/device.html: -------------------------------------------------------------------------------- 1 |