Skip to content

Commit ed07890

Browse files
authored
Merge pull request #94 from davidrapan/dev_discovery
Add Discovery tools implemented using asyncio
2 parents 8b60764 + e15fe2c commit ed07890

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

utils/solarman_discovery.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import socket
2+
import asyncio
3+
4+
from typing import Optional, Union
5+
from argparse import ArgumentParser
6+
7+
DISCOVERY_IP = "255.255.255.255"
8+
DISCOVERY_PORT = 48899
9+
DISCOVERY_MESSAGE = ["WIFIKIT-214028-READ".encode(), "HF-A11ASSISTHREAD".encode()]
10+
DISCOVERY_TIMEOUT = 1
11+
12+
13+
class DiscoveryProtocol:
14+
def __init__(self, addresses: Union[list[str], str]):
15+
self.addresses = addresses
16+
self.responses = asyncio.Queue()
17+
18+
def connection_made(self, transport: asyncio.DatagramTransport):
19+
print(f"DiscoveryProtocol: Send to {self.addresses}")
20+
for address in (
21+
self.addresses if isinstance(self.addresses, list) else [self.addresses]
22+
):
23+
for message in DISCOVERY_MESSAGE:
24+
transport.sendto(message, (address, DISCOVERY_PORT))
25+
26+
def datagram_received(self, data: bytes, addr: tuple[str, int]):
27+
if len(d := data.decode().split(",")) == 3 and (s := int(d[2])):
28+
self.responses.put_nowait((s, {"ip": d[0], "mac": d[1]}))
29+
print(f"DiscoveryProtocol: [{d[0]}, {d[1]}, {s}] from {addr}")
30+
31+
def error_received(self, e: OSError):
32+
print(f"DiscoveryProtocol: {e!r}")
33+
34+
def connection_lost(self, _: Optional[Exception]):
35+
print("DiscoveryProtocol: Connection closed")
36+
37+
38+
async def main():
39+
parser = ArgumentParser(
40+
"solarman-discovery", description="Discovery for Solarman Stick Loggers"
41+
)
42+
parser.add_argument(
43+
"--address",
44+
default=DISCOVERY_IP,
45+
required=False,
46+
type=str,
47+
help="Network IPv4 address",
48+
)
49+
parser.add_argument(
50+
"--timeout",
51+
default=DISCOVERY_TIMEOUT,
52+
required=False,
53+
type=int,
54+
choices=range(10),
55+
help="Timeout in seconds, an integer in the range 0..9",
56+
)
57+
parser.add_argument(
58+
"--wait",
59+
default=True,
60+
required=False,
61+
type=bool,
62+
help="Wait for multiple responses",
63+
)
64+
args = parser.parse_args()
65+
66+
try:
67+
transport, protocol = await asyncio.get_running_loop().create_datagram_endpoint(
68+
lambda: DiscoveryProtocol(args.address),
69+
family=socket.AF_INET,
70+
allow_broadcast=True,
71+
)
72+
while (
73+
await asyncio.wait_for(protocol.responses.get(), args.timeout) is None
74+
or args.wait
75+
):
76+
pass
77+
except TimeoutError:
78+
pass
79+
except Exception as e:
80+
print(repr(e))
81+
finally:
82+
transport.close()
83+
84+
85+
if __name__ == "__main__":
86+
asyncio.run(main())

utils/solarman_discovery_reply.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import socket
2+
import asyncio
3+
import netifaces
4+
5+
from typing import Optional
6+
from argparse import ArgumentParser
7+
8+
DISCOVERY_IP = "0.0.0.0"
9+
DISCOVERY_PORT = 48899
10+
DISCOVERY_MESSAGE = ["WIFIKIT-214028-READ".encode(), "HF-A11ASSISTHREAD".encode()]
11+
DISCOVERY_TIMEOUT = 3600
12+
13+
ifaces = netifaces.ifaddresses(netifaces.gateways()["default"][2][1])
14+
iface_inet = ifaces[netifaces.AF_INET][0]["addr"]
15+
iface_link = ifaces[netifaces.AF_LINK][0]["addr"].replace(":", "").upper()
16+
17+
DISCOVERY_MESSAGE_REPLY = f"{iface_inet},{iface_link},1234567890".encode()
18+
19+
20+
class DiscoveryProtocol:
21+
def connection_made(self, transport: asyncio.DatagramTransport):
22+
self.transport = transport
23+
24+
def datagram_received(self, data: bytes, addr: tuple[str, int]):
25+
if data in DISCOVERY_MESSAGE:
26+
print(f"DiscoveryProtocol: {data} from {addr}")
27+
self.transport.sendto(DISCOVERY_MESSAGE_REPLY, addr)
28+
29+
def error_received(self, e: OSError):
30+
print(f"DiscoveryProtocol: {e!r}")
31+
32+
def connection_lost(self, _: Optional[Exception]):
33+
print("DiscoveryProtocol: Connection closed")
34+
35+
36+
async def main():
37+
parser = ArgumentParser(
38+
"solarman-discovery-reply", description="Discovery for Solarman Stick Loggers"
39+
)
40+
parser.add_argument(
41+
"--timeout",
42+
default=DISCOVERY_TIMEOUT,
43+
required=False,
44+
type=int,
45+
choices=range(3600),
46+
help="Timeout in seconds, an integer in the range 0..3600",
47+
)
48+
49+
try:
50+
transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(
51+
DiscoveryProtocol,
52+
local_addr=(DISCOVERY_IP, DISCOVERY_PORT),
53+
family=socket.AF_INET,
54+
allow_broadcast=True,
55+
)
56+
await asyncio.sleep(parser.parse_args().timeout)
57+
finally:
58+
transport.close()
59+
60+
61+
if __name__ == "__main__":
62+
asyncio.run(main())

0 commit comments

Comments
 (0)