From 5f835021d155c5889e844b404dd395ecf97e680b Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 7 Oct 2021 14:33:53 -0600 Subject: [PATCH] added basic network functionality --- API/__init__.py | 9 ++++--- API/bosminer.py | 22 ++++++++++++----- main.py | 12 ++++----- network/__init__.py | 59 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 network/__init__.py diff --git a/API/__init__.py b/API/__init__.py index eabe20be..b85c912e 100644 --- a/API/__init__.py +++ b/API/__init__.py @@ -1,5 +1,6 @@ import asyncio import json +import ipaddress class APIError(Exception): @@ -17,17 +18,17 @@ class APIError(Exception): class BaseMinerAPI: - def __init__(self, ip, port): + def __init__(self, ip: str, port: int): self.port = port - self.ip = ip + self.ip = ipaddress.ip_address(ip) async def multicommand(self, *commands: str) -> dict: command = "+".join(commands) return await self.send_command(command) - async def send_command(self, command, parameters: dict = None) -> dict: + async def send_command(self, command: str, parameters: str = None) -> dict: # get reader and writer streams - reader, writer = await asyncio.open_connection(self.ip, self.port) + reader, writer = await asyncio.open_connection(str(self.ip), self.port) # create the command cmd = {"command": command} diff --git a/API/bosminer.py b/API/bosminer.py index 6a755653..e27a52c6 100644 --- a/API/bosminer.py +++ b/API/bosminer.py @@ -48,19 +48,29 @@ class BOSMinerAPI(BaseMinerAPI): return await self.send_command("lcd") async def switchpool(self, n: int) -> dict: - return await self.send_command("switchpool", parameters=n) + # BOS has not implemented this yet, they will in the future + return NotImplementedError + # return await self.send_command("switchpool", parameters=n) async def enablepool(self, n: int) -> dict: - return await self.send_command("enablepool", parameters=n) + # BOS has not implemented this yet, they will in the future + return NotImplementedError + # return await self.send_command("enablepool", parameters=n) async def disablepool(self, n: int) -> dict: - return await self.send_command("disablepool", parameters=n) + # BOS has not implemented this yet, they will in the future + return NotImplementedError + # return await self.send_command("disablepool", parameters=n) async def addpool(self, url: str, username: str, password: str) -> dict: - return await self.send_command("addpool", parameters=f"{url}, {username}, {password}") + # BOS has not implemented this yet, they will in the future + return NotImplementedError + # return await self.send_command("addpool", parameters=f"{url}, {username}, {password}") async def removepool(self, n: int) -> dict: - return await self.send_command("removepool", parameters=n) + # BOS has not implemented this yet, they will in the future + return NotImplementedError + # return await self.send_command("removepool", parameters=n) async def fans(self) -> dict: return await self.send_command("fans") @@ -78,4 +88,4 @@ class BOSMinerAPI(BaseMinerAPI): return await self.send_command("pause") async def resume(self) -> dict: - return await self.send_command("resume") \ No newline at end of file + return await self.send_command("resume") diff --git a/main.py b/main.py index 25722228..72d32f57 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,11 @@ from API.bosminer import BOSMinerAPI +from network import MinerNetwork import asyncio async def main(): - bosminer = BOSMinerAPI("172.16.1.199") - data_normal = await bosminer.asccount() - data_multi = await bosminer.multicommand("version", "config") - print(data_normal) - print(data_multi) + miner_network = MinerNetwork("192.168.1.1") + await miner_network.scan_network_for_miners() - -asyncio.get_event_loop().run_until_complete(main()) +if __name__ == '__main__': + asyncio.get_event_loop().run_until_complete(main()) diff --git a/network/__init__.py b/network/__init__.py new file mode 100644 index 00000000..73be208d --- /dev/null +++ b/network/__init__.py @@ -0,0 +1,59 @@ +import netifaces +import ipaddress +import asyncio + +PING_RETRIES: int = 3 +PING_TIMEOUT: int = 1 + + +class MinerNetwork: + def __init__(self, ip_addr=None): + self.network = None + self.ip_addr = ip_addr + self.connected_miners = {} + + def get_network(self): + if self.network: + return self.network + gateways = netifaces.gateways() + if not self.ip_addr: + default_gateway = gateways['default'][netifaces.AF_INET][0] + else: + default_gateway = self.ip_addr + subnet_mask = netifaces.ifaddresses(gateways['default'][netifaces.AF_INET][1])[netifaces.AF_INET][0]['netmask'] + return ipaddress.ip_network(f"{default_gateway}/{subnet_mask}", strict=False) + + async def scan_network_for_miners(self): + local_network = self.get_network() + print(f"Scanning {local_network} for miners...") + scan_tasks = [] + for host in local_network.hosts(): + scan_tasks.append(self.ping_miner(host)) + await asyncio.gather(*scan_tasks) + print(f"Found {len(self.connected_miners)} connected miners...") + + async def ping_miner(self, ip: ipaddress.ip_address): + for i in range(PING_RETRIES): + connection_fut = asyncio.open_connection(str(ip), 4028) + try: + # get the read and write streams from the connection + reader, writer = await asyncio.wait_for(connection_fut, timeout=PING_TIMEOUT) + # immediately close connection, we know connection happened + writer.close() + # make sure the writer is closed + await writer.wait_closed() + # add miner to dict + self.connected_miners[ip] = {} + # ping was successful + return True + except asyncio.exceptions.TimeoutError: + # ping failed if we time out + continue + except ConnectionRefusedError: + # handle for other connection errors + print("Unknown error...") + # ping failed, likely with an exception + except Exception as e: + print(e) + continue + return False