Compare commits

..

28 Commits

Author SHA1 Message Date
UpstreamData
5261b00aad fixed logfile in settings to allow for adding or removing a logfile 2022-06-22 13:28:37 -06:00
UpstreamData
f18d37a19e add gitignore and fix a small bug with settings if the file doesn't exist 2022-06-14 09:42:20 -06:00
UpstreamData
7c3af3da41 fixed a bug with old bosminers not updating properly 2022-06-10 13:21:31 -06:00
UpstreamData
8948af55f2 fixed a small bug with bosminer MAC 2022-06-10 11:30:24 -06:00
UpstreamData
dd8fe41ad1 added estimate env temp for X19 and change format of X19 and X17 files 2022-06-10 11:22:41 -06:00
UpstreamData
198eedcd43 added env_temp for whatsminers 2022-06-10 11:03:09 -06:00
UpstreamData
f7309decdb finish adding support for a bunch of new avalonminers 2022-06-09 14:38:51 -06:00
UpstreamData
078579d8e1 add a ton of new avalonminers to be added to miner factory later. 2022-06-09 14:10:12 -06:00
UpstreamData
39eeb13409 improved the implementation of fault lights on avalonminers by fixing a bad implementation of ascset. 2022-06-09 13:49:15 -06:00
UpstreamData
dfccd67ccb added fault lights to 1066 miners, and framework for configuring (although it may not work, the documentation implementation is broken) 2022-06-08 15:43:34 -06:00
UpstreamData
10949225c0 fix generate report pie chart to fix overlapping labels when all boards are working 2022-06-08 10:59:52 -06:00
UpstreamData
3a60a3584a added support for avalon 1066 miners 2022-06-08 10:42:19 -06:00
UpstreamData
480aab550c added advanced config file generator 2022-06-07 15:55:43 -06:00
UpstreamData
fa83e61249 fix a bug with config tool generating configs 2022-06-07 14:47:21 -06:00
UpstreamData
2f3411e12d add documentation for MinerConfig 2022-06-07 13:17:44 -06:00
UpstreamData
3e7311687e Update README.md 2022-06-07 12:01:31 -06:00
UpstreamData
bc2d549ce5 moved MinerConfig to config.__init__.py and removed old config methods 2022-06-07 11:50:36 -06:00
UpstreamData
3d31d89c9e update dev-requirements.txt 2022-06-07 11:43:00 -06:00
UpstreamData
15fc27e6fa added configuration for X19 miners 2022-06-07 11:12:26 -06:00
UpstreamData
943ebc77a1 switch braiins miners over to using new config dataclass 2022-06-07 10:49:41 -06:00
UpstreamData
733437ef03 create basic config dataclass to be used to configure miners 2022-06-06 16:05:09 -06:00
UpstreamData
b444245e98 added new whatsminer types to miner factory 2022-06-06 10:09:11 -06:00
UpstreamData
481d31a0f1 added more new whatsminer types 2022-06-06 10:06:17 -06:00
UpstreamData
264db3bdd6 fix a bug with whatsminer M21S missing import 2022-06-06 09:41:10 -06:00
UpstreamData
d292b9c195 improved whatsminer handling, and added VF20 to miner dict 2022-06-06 09:26:38 -06:00
UpstreamData
dce25a679f added new miner type M30S+VF20 2022-06-06 09:17:42 -06:00
UpstreamData
c903631742 improved build process 2022-06-06 09:17:22 -06:00
Colin Crossman
e70bfdc886 Fix indent issue that caused missing MAC addresses (#10) 2022-06-05 15:50:07 -06:00
82 changed files with 3609 additions and 1426 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
venv/
build/
__pycache__/
pyvenv.cfg
.env/
bin/
lib/

View File

@@ -171,7 +171,10 @@ If you are sure you want to use this command please use API.send_command("{item}
return False, data["Msg"] return False, data["Msg"]
else: else:
# make sure the command succeeded # make sure the command succeeded
if data["STATUS"][0]["STATUS"] not in ("S", "I"): if type(data["STATUS"]) == str:
if data["STATUS"] in ["RESTART"]:
return True, None
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
# this is an error # this is an error
if data["STATUS"][0]["STATUS"] not in ("S", "I"): if data["STATUS"][0]["STATUS"] not in ("S", "I"):
return False, data["STATUS"][0]["Msg"] return False, data["STATUS"][0]["Msg"]
@@ -197,8 +200,12 @@ If you are sure you want to use this command please use API.send_command("{item}
str_data = str_data.replace("}{", "},{") str_data = str_data.replace("}{", "},{")
# fix an error with a bmminer return having a specific comma that breaks json.loads() # fix an error with a bmminer return having a specific comma that breaks json.loads()
str_data = str_data.replace("[,{", "[{") str_data = str_data.replace("[,{", "[{")
# fix an error with a btminer return having a specific comma that breaks json.loads() # fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("inf", "0") str_data = str_data.replace("inf", "0")
str_data = str_data.replace("nan", "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):
str_data = f"{{{str_data[1:]}"
# parse the json # parse the json
parsed_data = json.loads(str_data) parsed_data = json.loads(str_data)
# handle bad json # handle bad json

View File

@@ -126,7 +126,7 @@ class BMMinerAPI(BaseMinerAPI):
:return: A confirmation of adding the pool. :return: A confirmation of adding the pool.
""" """
return await self.send_command( return await self.send_command(
"addpool", parameters=f"{url}, " f"{username}, " f"{password}" "addpool", parameters=f"{url},{username},{password}"
) )
async def poolpriority(self, *n: int) -> dict: async def poolpriority(self, *n: int) -> dict:
@@ -147,7 +147,7 @@ class BMMinerAPI(BaseMinerAPI):
:return: A confirmation of setting pool quota. :return: A confirmation of setting pool quota.
""" """
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}") return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict: async def disablepool(self, n: int) -> dict:
"""Disable a pool. """Disable a pool.
@@ -326,7 +326,7 @@ class BMMinerAPI(BaseMinerAPI):
:return: The results of setting config of name to n. :return: The results of setting config of name to n.
""" """
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}") return await self.send_command("setconfig", parameters=f"{name},{n}")
async def usbstats(self) -> dict: async def usbstats(self) -> dict:
"""Get stats of all USB devices except ztex. """Get stats of all USB devices except ztex.
@@ -354,11 +354,9 @@ class BMMinerAPI(BaseMinerAPI):
:return: Confirmation of setting PGA n with opt[,val]. :return: Confirmation of setting PGA n with opt[,val].
""" """
if val: if val:
return await self.send_command( return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
)
else: else:
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}") return await self.send_command("pgaset", parameters=f"{n},{opt}")
async def zero(self, which: str, summary: bool) -> dict: async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device. """Zero a device.
@@ -373,7 +371,7 @@ class BMMinerAPI(BaseMinerAPI):
:return: the STATUS section with info on the zero and optional :return: the STATUS section with info on the zero and optional
summary. summary.
""" """
return await self.send_command("zero", parameters=f"{which}, {summary}") return await self.send_command("zero", parameters=f"{which},{summary}")
async def hotplug(self, n: int) -> dict: async def hotplug(self, n: int) -> dict:
"""Enable hotplug. """Enable hotplug.
@@ -474,9 +472,9 @@ class BMMinerAPI(BaseMinerAPI):
:return: Confirmation of setting option opt to value val. :return: Confirmation of setting option opt to value val.
""" """
if val: if val:
return await self.send_command("ascset", parameters=f"{n}, {opt}, {val}") return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
else: else:
return await self.send_command("ascset", parameters=f"{n}, {opt}") return await self.send_command("ascset", parameters=f"{n},{opt}")
async def lcd(self) -> dict: async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner. """Get a general all-in-one status summary of the miner.

View File

@@ -158,7 +158,7 @@ class BOSMinerAPI(BaseMinerAPI):
async def addpool(self, url: str, username: str, password: str) -> dict: async def addpool(self, url: str, username: str, password: str) -> dict:
# BOS has not implemented this yet, they will in the future # BOS has not implemented this yet, they will in the future
raise NotImplementedError raise NotImplementedError
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}") # return await self.send_command("addpool", parameters=f"{url},{username},{password}")
async def removepool(self, n: int) -> dict: async def removepool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future # BOS has not implemented this yet, they will in the future

View File

@@ -122,7 +122,7 @@ class CGMinerAPI(BaseMinerAPI):
:return: A confirmation of adding the pool. :return: A confirmation of adding the pool.
""" """
return await self.send_command( return await self.send_command(
"addpool", parameters=f"{url}, " f"{username}, " f"{password}" "addpool", parameters=f"{url},{username},{password}"
) )
async def poolpriority(self, *n: int) -> dict: async def poolpriority(self, *n: int) -> dict:
@@ -143,7 +143,7 @@ class CGMinerAPI(BaseMinerAPI):
:return: A confirmation of setting pool quota. :return: A confirmation of setting pool quota.
""" """
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}") return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict: async def disablepool(self, n: int) -> dict:
"""Disable a pool. """Disable a pool.
@@ -322,7 +322,7 @@ class CGMinerAPI(BaseMinerAPI):
:return: The results of setting config of name to n. :return: The results of setting config of name to n.
""" """
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}") return await self.send_command("setconfig", parameters=f"{name},{n}")
async def usbstats(self) -> dict: async def usbstats(self) -> dict:
"""Get stats of all USB devices except ztex. """Get stats of all USB devices except ztex.
@@ -350,11 +350,9 @@ class CGMinerAPI(BaseMinerAPI):
:return: Confirmation of setting PGA n with opt[,val]. :return: Confirmation of setting PGA n with opt[,val].
""" """
if val: if val:
return await self.send_command( return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
)
else: else:
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}") return await self.send_command("pgaset", parameters=f"{n},{opt}")
async def zero(self, which: str, summary: bool) -> dict: async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device. """Zero a device.
@@ -369,7 +367,7 @@ class CGMinerAPI(BaseMinerAPI):
:return: the STATUS section with info on the zero and optional :return: the STATUS section with info on the zero and optional
summary. summary.
""" """
return await self.send_command("zero", parameters=f"{which}, " f"{summary}") return await self.send_command("zero", parameters=f"{which},{summary}")
async def hotplug(self, n: int) -> dict: async def hotplug(self, n: int) -> dict:
"""Enable hotplug. """Enable hotplug.
@@ -470,11 +468,9 @@ class CGMinerAPI(BaseMinerAPI):
:return: Confirmation of setting option opt to value val. :return: Confirmation of setting option opt to value val.
""" """
if val: if val:
return await self.send_command( return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
"ascset", parameters=f"{n}, " f"{opt}, " f"{val}"
)
else: else:
return await self.send_command("ascset", parameters=f"{n}, " f"{opt}") return await self.send_command("ascset", parameters=f"{n},{opt}")
async def lcd(self) -> dict: async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner. """Get a general all-in-one status summary of the miner.

View File

@@ -72,7 +72,7 @@ class UnknownAPI(BaseMinerAPI):
async def addpool(self, url: str, username: str, password: str) -> dict: async def addpool(self, url: str, username: str, password: str) -> dict:
# BOS has not implemented this yet, they will in the future # BOS has not implemented this yet, they will in the future
raise NotImplementedError raise NotImplementedError
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}") # return await self.send_command("addpool", parameters=f"{url},{username},{password}")
async def removepool(self, n: int) -> dict: async def removepool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future # BOS has not implemented this yet, they will in the future

View File

@@ -9,7 +9,7 @@ For those of you who aren't comfortable with code and developer tools, there are
*CFG Util is a GUI for interfacing with the miners easily, it is mostly self-explanatory.* *CFG Util is a GUI for interfacing with the miners easily, it is mostly self-explanatory.*
To use CFG Util you have 2 options - To use CFG Util you have 2 options -
1. Run it directly with the file ```config_tool.py``` or import it with ```from cfg_util import main```, then run the ```main()``` function in an asyncio event loop like - 1. Run it directly with the file ```config_tool.py``` or import it with ```from cfg_util import main```, then run the ```main()``` function like -
```python ```python
from tools.cfg_util import main from tools.cfg_util import main
@@ -20,7 +20,7 @@ if __name__ == '__main__':
2. Make a build of the CFG Util for your system using cx_freeze and ```make_cfg_tool_exe.py``` 2. Make a build of the CFG Util for your system using cx_freeze and ```make_cfg_tool_exe.py```
(Alternatively, you can get a build made by me here -> https://drive.google.com/drive/folders/147vBXbuaX85inataXeSAiKk8IKf-7xtR) (Alternatively, you can get a build made by me here -> https://drive.google.com/drive/folders/147vBXbuaX85inataXeSAiKk8IKf-7xtR)
1. Open either Command Prompt on Windows or Terminal on Mac or UNIX. 1. Open either Command Prompt on Windows or Terminal on Mac or UNIX.
2. Navigate to this directory, and run ```make_cfg_tool_exe.py build``` on Windows or ```python3 make_cfg_tool_exe.py``` on Mac or UNIX. 2. Navigate to this directory, and run ```make_cfg_tool_exe.py build``` on Windows or ```python3 make_cfg_tool_exe.py build``` on Mac or UNIX.
### Interfacing with miners programmatically ### Interfacing with miners programmatically
<br> <br>
@@ -48,7 +48,6 @@ A basic script to find all miners on the network and get the hashrate from them
```python ```python
import asyncio import asyncio
from network import MinerNetwork from network import MinerNetwork
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_hashrate(): async def get_hashrate():
@@ -60,18 +59,11 @@ async def get_hashrate():
# Miner Network scan function returns Miner classes for all miners found # Miner Network scan function returns Miner classes for all miners found
miners = await miner_network.scan_network_for_miners() miners = await miner_network.scan_network_for_miners()
# Each miner will return with its own set of functions, and an API class instance # Each miner will return with its own set of functions, and an API class instance
tasks = [miner.api.summary() for miner in miners] tasks = [miner.get_data() for miner in miners]
# Gather all tasks asynchronously and run them # Gather all tasks asynchronously and run them
data = await asyncio.gather(*tasks) data = await asyncio.gather(*tasks)
parse_tasks = [] # now we have a list of MinerData, and can get .hashrate
for item in data: print([item.hashrate for item in data])
# safe_parse_api_data parses the data from a miner API
# It will raise an APIError (from API import APIError) if there is a problem
parse_tasks.append(safe_parse_api_data(item, 'SUMMARY', 0, 'MHS 5s'))
# Gather all tasks asynchronously and run them
data = await asyncio.gather(*parse_tasks)
# Print a list of all the hashrates
print(data)
if __name__ == '__main__': if __name__ == '__main__':
@@ -84,7 +76,7 @@ You can also create your own miner without scanning if you know the IP:
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate(ip: str): async def get_miner_hashrate(ip: str):
@@ -95,11 +87,9 @@ async def get_miner_hashrate(ip: str):
# Wait for the factory to return the miner # Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip) miner = await miner_factory.get_miner(miner_ip)
# Get the API data # Get the API data
summary = await miner.api.summary() data = await miner.get_data()
# safe_parse_api_data parses the data from a miner API # print out hashrate
# It will raise an APIError (from API import APIError) if there is a problem print(data.hashrate)
data = await safe_parse_api_data(summary, 'SUMMARY', 0, 'MHS 5s')
print(data)
if __name__ == '__main__': if __name__ == '__main__':
@@ -112,6 +102,7 @@ Now that you know that, lets move on to some common API functions that you might
### Common commands: ### Common commands:
* Get the data used by the config utility, this includes pool data, wattage use, temperature, hashrate, etc: * Get the data used by the config utility, this includes pool data, wattage use, temperature, hashrate, etc:
* All the data from below commands and more are returned from this in a consistent dataclass. Check out the `MinerData` class in `/data/__init__.py` for more information.
```python ```python
import asyncio import asyncio

View File

@@ -1,88 +1,366 @@
""" from dataclasses import dataclass, asdict
SAMPLE CONFIG from typing import List, Literal
------------------- import random
{ import string
"format": {
"version": "1.2+", # -> (default = "1.2+", str, (bos: format.version)) import toml
"model": "Antminer S9", # -> (default = "Antminer S9", str, (bos: format.model)) import yaml
"generator": "upstream_config_util", # -> (hidden, always = "upstream_config_util", str, (bos: format.generator)) import json
"timestamp": 1606842000, # -> (hidden, always = int(time.time()) (current unix time), int, (bos: format.timestamp)) import time
},
"temperature": {
"mode": "auto", # -> (default = "auto", str["auto", "manual", "disabled"], (bos: temp_control.mode))
"target": 70.0, # -> (default = 70.0, float, (bos: temp_control.target_temp))
"hot": 80.0, # -> (default = 80.0, float, (bos: temp_control.hot_temp))
"danger": 90.0, # -> (default = 90.0, float, (bos: temp_control.dangerous_temp))
},
"fans": { # -> (optional, required if temperature["mode"] == "disabled", (bos: fan_control))
"min_fans": 1, # -> (default = 1, int, (bos: fan_control.min_fans))
"speed": 100, # -> (default = 100, 0 < int < 100, (bos: fan_control.speed))
},
"asicboost": True, # -> (default = True, bool, (bos : hash_chain_global.asic_boost))
"pool_groups": [
{
"group_name": "Upstream", # -> (default = "group_{index}" (group_0), str, (bos: group.[index].name))
"quota": 1, # -> (default = 1, int, (bos: group.[index].quota))
"pools": [
{
"url": "stratum+tcp://stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
{
"url": "stratum+tcp://us-east.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
{
"url": "stratum+tcp://ca.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
]
},
{
"group_name": "Upstream2", # -> (default = "group_{index}" (group_1), str, (bos: group.[index].name))
"quota": 4, # -> (default = 1, int, (bos: group.[index].quota))
"pools": [
{
"url": "stratum+tcp://stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
{
"url": "stratum+tcp://us-east.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
{
"url": "stratum+tcp://ca.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
},
]
},
],
"autotuning": {
"enabled": True, # -> (default = True, bool), (bos: autotuning.enabled)
"wattage": 900, # -> (default = 900, int, (bos: autotuning.psu_power_limit))
},
"power_scaling": {
"enabled": False, # -> (default = False, bool, (bos: power_scaling.enabled))
"power_step": 100, # -> (default = 100, int, (bos: power_scaling.power_step))
"min_psu_power_limit": 800, # -> (default = 800, int, (bos: power_scaling.min_psu_power_limit))
"shutdown_enabled": True, # -> (default = False, bool, (bos: power_scaling.shutdown_enabled))
"shutdown_duration": 3.0, # -> (default = 3.0, float, (bos: power_scaling.shutdown_duration))
}
}
"""
def general_config_convert_pools(config: dict): @dataclass
out_config = {} class _Pool:
pools = config.get("pool_groups") """A dataclass for pool information.
if pools:
if len(pools) > 0: :param url: URL of the pool.
pools = pools[0] :param username: Username on the pool.
out_config = pools["pools"][:3] :param password: Worker password on the pool.
return out_config """
url: str = ""
username: str = ""
password: str = ""
def from_dict(self, data: dict):
"""Convert raw pool data as a dict to usable data and save it to this class.
:param data: The raw config data to convert.
"""
for key in data.keys():
if key == "url":
self.url = data[key]
if key in ["user", "username"]:
self.username = data[key]
if key in ["pass", "password"]:
self.password = data[key]
return self
def as_x19(self, user_suffix: str = None):
"""Convert the data in this class to a dict usable by an X19 device.
:param user_suffix: The suffix to append to username.
"""
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "pass": self.password}
return pool
def as_avalon(self, user_suffix: str = None):
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = ",".join([self.url, username, self.password])
return pool
def as_bos(self, user_suffix: str = None):
"""Convert the data in this class to a dict usable by an BOSMiner device.
:param user_suffix: The suffix to append to username.
"""
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "password": self.password}
return pool
@dataclass
class _PoolGroup:
"""A dataclass for pool group information.
:param quota: The group quota.
:param group_name: The name of the pool group.
:param pools: A list of pools in this group.
"""
quota: int = 1
group_name: str = None
pools: List[_Pool] = None
def __post_init__(self):
if not self.group_name:
self.group_name = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
) # generate random pool group name in case it isn't set
def from_dict(self, data: dict):
"""Convert raw pool group data as a dict to usable data and save it to this class.
:param data: The raw config data to convert.
"""
pools = []
for key in data.keys():
if key in ["name", "group_name"]:
self.group_name = data[key]
if key == "quota":
self.quota = data[key]
if key in ["pools", "pool"]:
for pool in data[key]:
pools.append(_Pool().from_dict(pool))
self.pools = pools
return self
def as_x19(self, user_suffix: str = None):
"""Convert the data in this class to a dict usable by an X19 device.
:param user_suffix: The suffix to append to username.
"""
pools = []
for pool in self.pools[:3]:
pools.append(pool.as_x19(user_suffix=user_suffix))
return pools
def as_avalon(self, user_suffix: str = None):
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
return pool
def as_bos(self, user_suffix: str = None):
"""Convert the data in this class to a dict usable by an BOSMiner device.
:param user_suffix: The suffix to append to username.
"""
group = {
"name": self.group_name,
"quota": self.quota,
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
}
return group
@dataclass
class MinerConfig:
"""A dataclass for miner configuration information.
:param pool_groups: A list of pool groups in this config.
:param temp_mode: The temperature control mode.
:param temp_target: The target temp.
:param temp_hot: The hot temp (100% fans).
:param temp_dangerous: The dangerous temp (shutdown).
:param minimum_fans: The minimum numbers of fans to run the miner.
:param fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
:param asicboost: Whether or not to enable asicboost.
:param autotuning_enabled: Whether or not to enable autotuning.
:param autotuning_wattage: The wattage to use when autotuning.
:param dps_enabled: Whether or not to enable dynamic power scaling.
:param dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
:param dps_min_power: The minimum power to reduce autotuning to.
:param dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
:param dps_shutdown_duration: The amount of time to shutdown for (in hours).
"""
pool_groups: List[_PoolGroup] = None
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
temp_target: float = 70.0
temp_hot: float = 80.0
temp_dangerous: float = 10.0
minimum_fans: int = None
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
asicboost: bool = None
autotuning_enabled: bool = True
autotuning_wattage: int = 900
dps_enabled: bool = None
dps_power_step: int = None
dps_min_power: int = None
dps_shutdown_enabled: bool = None
dps_shutdown_duration: float = None
def as_dict(self):
"""Convert the data in this class to a dict."""
data_dict = asdict(self)
for key in asdict(self).keys():
if data_dict[key] is None:
del data_dict[key]
return data_dict
def as_toml(self):
"""Convert the data in this class to toml."""
return toml.dumps(self.as_dict())
def as_yaml(self):
"""Convert the data in this class to yaml."""
return yaml.dump(self.as_dict(), sort_keys=False)
def from_raw(self, data: dict):
"""Convert raw config data as a dict to usable data and save it to this class.
:param data: The raw config data to convert.
"""
pool_groups = []
for key in data.keys():
if key == "pools":
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
elif key == "group":
for group in data[key]:
pool_groups.append(_PoolGroup().from_dict(group))
if key == "bitmain-fan-ctrl":
if data[key]:
self.temp_mode = "manual"
if data.get("bitmain-fan-pwm"):
self.fan_speed = int(data["bitmain-fan-pwm"])
elif key == "fan_control":
for _key in data[key].keys():
if _key == "min_fans":
self.minimum_fans = data[key][_key]
elif _key == "speed":
self.fan_speed = data[key][_key]
elif key == "temp_control":
for _key in data[key].keys():
if _key == "mode":
self.temp_mode = data[key][_key]
elif _key == "target_temp":
self.temp_target = data[key][_key]
elif _key == "hot_temp":
self.temp_hot = data[key][_key]
elif _key == "dangerous_temp":
self.temp_dangerous = data[key][_key]
if key == "hash_chain_global":
if data[key].get("asic_boost"):
self.asicboost = data[key]["asic_boost"]
if key == "autotuning":
for _key in data[key].keys():
if _key == "enabled":
self.autotuning_enabled = data[key][_key]
elif _key == "psu_power_limit":
self.autotuning_wattage = data[key][_key]
if key == "power_scaling":
for _key in data[key].keys():
if _key == "enabled":
self.dps_enabled = data[key][_key]
elif _key == "power_step":
self.dps_power_step = data[key][_key]
elif _key == "min_psu_power_limit":
self.dps_min_power = data[key][_key]
elif _key == "shutdown_enabled":
self.dps_shutdown_enabled = data[key][_key]
elif _key == "shutdown_duration":
self.dps_shutdown_duration = data[key][_key]
self.pool_groups = pool_groups
return self
def from_dict(self, data: dict):
"""Convert an output dict of this class back into usable data and save it to this class.
:param data: The raw config data to convert.
"""
pool_groups = []
for group in data["pool_groups"]:
pool_groups.append(_PoolGroup().from_dict(group))
for key in data.keys():
if getattr(self, key) and not key == "pool_groups":
setattr(self, key, data[key])
self.pool_groups = pool_groups
return self
def from_toml(self, data: str):
"""Convert output toml of this class back into usable data and save it to this class.
:param data: The raw config data to convert.
"""
return self.from_dict(toml.loads(data))
def from_yaml(self, data: str):
"""Convert output yaml of this class back into usable data and save it to this class.
:param data: The raw config data to convert.
"""
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
def as_x19(self, user_suffix: str = None) -> str:
"""Convert the data in this class to a config usable by an X19 device.
:param user_suffix: The suffix to append to username.
"""
cfg = {
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
"bitmain-fan-ctrl": False,
"bitmain-fan-pwn": 100,
}
if not self.temp_mode == "auto":
cfg["bitmain-fan-ctrl"] = True
if self.fan_speed:
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
return json.dumps(cfg)
def as_avalon(self, user_suffix: str = None) -> str:
cfg = self.pool_groups[0].as_avalon()
return cfg
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
"""Convert the data in this class to a config usable by an BOSMiner device.
:param model: The model of the miner to be used in the format portion of the config.
:param user_suffix: The suffix to append to username.
"""
cfg = {
"format": {
"version": "1.2+",
"model": f"Antminer {model}",
"generator": "Upstream Config Utility",
"timestamp": int(time.time()),
},
"group": [
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
],
"temp_control": {
"mode": self.temp_mode,
"target_temp": self.temp_target,
"hot_temp": self.temp_hot,
"dangerous_temp": self.temp_dangerous,
},
}
if self.autotuning_enabled or self.autotuning_wattage:
cfg["autotuning"] = {}
if self.autotuning_enabled:
cfg["autotuning"]["enabled"] = self.autotuning_enabled
if self.autotuning_wattage:
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
if self.asicboost:
cfg["hash_chain_global"] = {}
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
if any(
[
getattr(self, item)
for item in [
"dps_enabled",
"dps_power_step",
"dps_min_power",
"dps_shutdown_enabled",
"dps_shutdown_duration",
]
]
):
cfg["power_scaling"] = {}
if self.dps_enabled:
cfg["power_scaling"]["enabled"] = self.dps_enabled
if self.dps_power_step:
cfg["power_scaling"]["power_step"] = self.dps_power_step
if self.dps_min_power:
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
if self.dps_shutdown_enabled:
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
if self.dps_shutdown_duration:
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
return toml.dumps(cfg)

View File

@@ -1,221 +0,0 @@
import time
import yaml
def bos_config_convert(config: dict):
out_config = {}
for opt in config:
if opt == "format":
out_config["format"] = config[opt]
out_config["format"]["generator"] = "upstream_config_util"
out_config["format"]["timestamp"] = int(time.time())
elif opt == "temp_control":
out_config["temperature"] = {}
if "mode" in config[opt].keys():
out_config["temperature"]["mode"] = config[opt]["mode"]
else:
out_config["temperature"]["mode"] = "auto"
if "target_temp" in config[opt].keys():
out_config["temperature"]["target"] = config[opt]["target_temp"]
else:
out_config["temperature"]["target"] = 70.0
if "hot_temp" in config[opt].keys():
out_config["temperature"]["hot"] = config[opt]["hot_temp"]
else:
out_config["temperature"]["hot"] = 80.0
if "dangerous_temp" in config[opt].keys():
out_config["temperature"]["danger"] = config[opt]["dangerous_temp"]
else:
out_config["temperature"]["danger"] = 90.0
elif opt == "fan_control":
out_config["fans"] = {}
if "min_fans" in config[opt].keys():
out_config["fans"]["min_fans"] = config[opt]["min_fans"]
else:
out_config["fans"]["min_fans"] = 1
if "speed" in config[opt].keys():
out_config["fans"]["speed"] = config[opt]["speed"]
else:
out_config["fans"]["speed"] = 100
elif opt == "group":
out_config["pool_groups"] = [{} for _item in range(len(config[opt]))]
for idx in range(len(config[opt])):
out_config["pool_groups"][idx]["pools"] = []
out_config["pool_groups"][idx] = {}
if "name" in config[opt][idx].keys():
out_config["pool_groups"][idx]["group_name"] = config[opt][idx][
"name"
]
else:
out_config["pool_groups"][idx]["group_name"] = f"group_{idx}"
if "quota" in config[opt][idx].keys():
out_config["pool_groups"][idx]["quota"] = config[opt][idx]["quota"]
else:
out_config["pool_groups"][idx]["quota"] = 1
out_config["pool_groups"][idx]["pools"] = [
{} for _item in range(len(config[opt][idx]["pool"]))
]
for pool_idx in range(len(config[opt][idx]["pool"])):
out_config["pool_groups"][idx]["pools"][pool_idx]["url"] = config[
opt
][idx]["pool"][pool_idx]["url"]
out_config["pool_groups"][idx]["pools"][pool_idx][
"username"
] = config[opt][idx]["pool"][pool_idx]["user"]
out_config["pool_groups"][idx]["pools"][pool_idx][
"password"
] = config[opt][idx]["pool"][pool_idx]["password"]
elif opt == "autotuning":
out_config["autotuning"] = {}
if "enabled" in config[opt].keys():
out_config["autotuning"]["enabled"] = config[opt]["enabled"]
else:
out_config["autotuning"]["enabled"] = True
if "psu_power_limit" in config[opt].keys():
out_config["autotuning"]["wattage"] = config[opt]["psu_power_limit"]
else:
out_config["autotuning"]["wattage"] = 900
elif opt == "power_scaling":
out_config["power_scaling"] = {}
if "enabled" in config[opt].keys():
out_config["power_scaling"]["enabled"] = config[opt]["enabled"]
else:
out_config["power_scaling"]["enabled"] = False
if "power_step" in config[opt].keys():
out_config["power_scaling"]["power_step"] = config[opt]["power_step"]
else:
out_config["power_scaling"]["power_step"] = 100
if "min_psu_power_limit" in config[opt].keys():
out_config["power_scaling"]["min_psu_power_limit"] = config[opt][
"min_psu_power_limit"
]
else:
out_config["power_scaling"]["min_psu_power_limit"] = 800
if "shutdown_enabled" in config[opt].keys():
out_config["power_scaling"]["shutdown_enabled"] = config[opt][
"shutdown_enabled"
]
else:
out_config["power_scaling"]["shutdown_enabled"] = False
if "shutdown_duration" in config[opt].keys():
out_config["power_scaling"]["shutdown_duration"] = config[opt][
"shutdown_duration"
]
else:
out_config["power_scaling"]["shutdown_duration"] = 3.0
return yaml.dump(out_config, sort_keys=False)
def general_config_convert_bos(yaml_config, user_suffix: str = None):
config = yaml.load(yaml_config, Loader=yaml.SafeLoader)
out_config = {}
for opt in config:
if opt == "format":
out_config["format"] = config[opt]
out_config["format"]["generator"] = "upstream_config_util"
out_config["format"]["timestamp"] = int(time.time())
elif opt == "temperature":
out_config["temp_control"] = {}
if "mode" in config[opt].keys():
out_config["temp_control"]["mode"] = config[opt]["mode"]
else:
out_config["temp_control"]["mode"] = "auto"
if "target" in config[opt].keys():
out_config["temp_control"]["target_temp"] = config[opt]["target"]
else:
out_config["temp_control"]["target_temp"] = 70.0
if "hot" in config[opt].keys():
out_config["temp_control"]["hot_temp"] = config[opt]["hot"]
else:
out_config["temp_control"]["hot_temp"] = 80.0
if "danger" in config[opt].keys():
out_config["temp_control"]["dangerous_temp"] = config[opt]["danger"]
else:
out_config["temp_control"]["dangerous_temp"] = 90.0
elif opt == "fans":
out_config["fan_control"] = {}
if "min_fans" in config[opt].keys():
out_config["fan_control"]["min_fans"] = config[opt]["min_fans"]
else:
out_config["fan_control"]["min_fans"] = 1
if "speed" in config[opt].keys():
out_config["fan_control"]["speed"] = config[opt]["speed"]
else:
out_config["fan_control"]["speed"] = 100
elif opt == "pool_groups":
out_config["group"] = [{} for _item in range(len(config[opt]))]
for idx in range(len(config[opt])):
out_config["group"][idx]["pools"] = []
out_config["group"][idx] = {}
if "group_name" in config[opt][idx].keys():
out_config["group"][idx]["name"] = config[opt][idx]["group_name"]
else:
out_config["group"][idx]["name"] = f"group_{idx}"
if "quota" in config[opt][idx].keys():
out_config["group"][idx]["quota"] = config[opt][idx]["quota"]
else:
out_config["group"][idx]["quota"] = 1
out_config["group"][idx]["pool"] = [
{} for _item in range(len(config[opt][idx]["pools"]))
]
for pool_idx in range(len(config[opt][idx]["pools"])):
out_config["group"][idx]["pool"][pool_idx]["url"] = config[opt][
idx
]["pools"][pool_idx]["url"]
username = config[opt][idx]["pools"][pool_idx]["username"]
if user_suffix:
if "." in username:
username = f"{username}x{user_suffix}"
else:
username = f"{username}.{user_suffix}"
out_config["group"][idx]["pool"][pool_idx]["user"] = username
out_config["group"][idx]["pool"][pool_idx]["password"] = config[
opt
][idx]["pools"][pool_idx]["password"]
elif opt == "autotuning":
out_config["autotuning"] = {}
if "enabled" in config[opt].keys():
out_config["autotuning"]["enabled"] = config[opt]["enabled"]
else:
out_config["autotuning"]["enabled"] = True
if "wattage" in config[opt].keys():
out_config["autotuning"]["psu_power_limit"] = config[opt]["wattage"]
else:
out_config["autotuning"]["psu_power_limit"] = 900
elif opt == "power_scaling":
out_config["power_scaling"] = {}
if "enabled" in config[opt].keys():
out_config["power_scaling"]["enabled"] = config[opt]["enabled"]
else:
out_config["power_scaling"]["enabled"] = False
if "power_step" in config[opt].keys():
out_config["power_scaling"]["power_step"] = config[opt]["power_step"]
else:
out_config["power_scaling"]["power_step"] = 100
if "min_psu_power_limit" in config[opt].keys():
out_config["power_scaling"]["min_psu_power_limit"] = config[opt][
"min_psu_power_limit"
]
else:
out_config["power_scaling"]["min_psu_power_limit"] = 800
if "shutdown_enabled" in config[opt].keys():
out_config["power_scaling"]["shutdown_enabled"] = config[opt][
"shutdown_enabled"
]
else:
out_config["power_scaling"]["shutdown_enabled"] = False
if "shutdown_duration" in config[opt].keys():
out_config["power_scaling"]["shutdown_duration"] = config[opt][
"shutdown_duration"
]
else:
out_config["power_scaling"]["shutdown_duration"] = 3.0
return out_config

View File

@@ -1,17 +0,0 @@
config cgminer 'default'
option pool1pw 'x'
option pool2pw 'x'
option pool3pw 'x'
option voltage_level_offset '0'
option fan '10'
option api_allow 'W:0/0'
option power_mode 'balance'
option pool1url 'stratum+tcp://ca.stratum.slushpool.com:3333'
option pool1user 'poolacct.worker1'
option pool2url 'stratum+tcp://ca.stratum.slushpool.com:3333'
option pool2user 'poolacct.worker2'
option pool3url 'stratum+tcp://ca.stratum.slushpool.com:3333'
option pool3user 'poolacct.worker3'
option ntp_enable 'openwrt'

View File

@@ -40,6 +40,7 @@ class MinerData:
hostname: str = "Unknown" hostname: str = "Unknown"
hashrate: float = 0 hashrate: float = 0
temperature_avg: int = field(init=False) temperature_avg: int = field(init=False)
env_temp: float = 0
left_board_temp: int = 0 left_board_temp: int = 0
left_board_chip_temp: int = 0 left_board_chip_temp: int = 0
center_board_temp: int = 0 center_board_temp: int = 0
@@ -100,7 +101,7 @@ class MinerData:
self.center_board_chip_temp, self.center_board_chip_temp,
self.right_board_chip_temp, self.right_board_chip_temp,
]: ]:
if not temp == 0: if temp and not temp == 0:
total_temp += temp total_temp += temp
temp_count += 1 temp_count += 1
if not temp_count > 0: if not temp_count > 0:

Binary file not shown.

View File

@@ -1,14 +1,21 @@
import logging import logging
from settings import DEBUG from settings import DEBUG, LOGFILE
def init_logger(): def init_logger():
logging.basicConfig( if LOGFILE:
# filename="logfile.txt", logging.basicConfig(
# filemode="a", filename="logfile.txt",
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s", filemode="a",
datefmt="%x %X", format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
) datefmt="%x %X",
)
else:
logging.basicConfig(
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
datefmt="%x %X",
)
_logger = logging.getLogger() _logger = logging.getLogger()
if DEBUG: if DEBUG:

View File

@@ -9,6 +9,7 @@ import datetime
import sys import sys
import os import os
from cx_Freeze import setup, Executable from cx_Freeze import setup, Executable
from setuptools import find_packages
base = None base = None
if sys.platform == "win32": if sys.platform == "win32":
@@ -16,7 +17,6 @@ if sys.platform == "win32":
version = datetime.datetime.now() version = datetime.datetime.now()
version = version.strftime("%y.%m.%d") version = version.strftime("%y.%m.%d")
print(version)
setup( setup(
@@ -30,9 +30,7 @@ setup(
os.path.join(os.getcwd(), "settings/settings.toml"), os.path.join(os.getcwd(), "settings/settings.toml"),
os.path.join(os.getcwd(), "static/CFG-Util-README.md"), os.path.join(os.getcwd(), "static/CFG-Util-README.md"),
], ],
"excludes": [ "excludes": ["tests", "tools.web_testbench", "tools.web_monitor"],
os.path.join(os.getcwd(), "tools/web_testbench/files"),
],
}, },
}, },
executables=[ executables=[

View File

@@ -18,6 +18,7 @@ class BaseMiner:
self.nominal_chips = 1 self.nominal_chips = 1
self.version = None self.version = None
self.fan_count = 2 self.fan_count = 2
self.config = None
def __repr__(self): def __repr__(self):
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}" return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
@@ -99,5 +100,5 @@ class BaseMiner:
async def get_mac(self): async def get_mac(self):
return None return None
async def get_data(self): async def get_data(self) -> MinerData:
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))

View File

@@ -203,11 +203,17 @@ class BMMiner(BaseMiner):
) )
board_map = {0: "left_board", 1: "center_board", 2: "right_board"} board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
env_temp_list = []
for item in range(3): for item in range(3):
board_temp = temp[1].get(f"temp{item + board_offset}") board_temp = temp[1].get(f"temp{item + board_offset}")
chip_temp = temp[1].get(f"temp2_{item + board_offset}") chip_temp = temp[1].get(f"temp2_{item + board_offset}")
setattr(data, f"{board_map[item]}_chip_temp", chip_temp) setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
setattr(data, f"{board_map[item]}_temp", board_temp) setattr(data, f"{board_map[item]}_temp", board_temp)
if f"temp_pcb{item}" in temp[1].keys():
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
data.env_temp = sum(env_temp_list) / len(env_temp_list)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -11,7 +11,7 @@ from API import APIError
from data import MinerData from data import MinerData
from config.bos import bos_config_convert, general_config_convert_bos from config import MinerConfig
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
@@ -102,8 +102,9 @@ class BOSMiner(BaseMiner):
async with sftp.open("/etc/bosminer.toml") as file: async with sftp.open("/etc/bosminer.toml") as file:
toml_data = toml.loads(await file.read()) toml_data = toml.loads(await file.read())
logging.debug(f"{self}: Converting config file.") logging.debug(f"{self}: Converting config file.")
cfg = bos_config_convert(toml_data) cfg = MinerConfig().from_raw(toml_data)
self.config = cfg self.config = cfg
return self.config
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
"""Get miner hostname. """Get miner hostname.
@@ -192,11 +193,17 @@ class BOSMiner(BaseMiner):
logging.debug(f"{self}: Sending config.") logging.debug(f"{self}: Sending config.")
if ip_user: if ip_user:
suffix = str(self.ip).split(".")[-1] suffix = str(self.ip).split(".")[-1]
toml_conf = toml.dumps( toml_conf = (
general_config_convert_bos(yaml_config, user_suffix=suffix) MinerConfig()
.from_yaml(yaml_config)
.as_bos(model=self.model.replace(" (BOS)", ""), user_suffix=suffix)
) )
else: else:
toml_conf = toml.dumps(general_config_convert_bos(yaml_config)) toml_conf = (
MinerConfig()
.from_yaml(yaml_config)
.as_bos(model=self.model.replace(" (BOS)", ""))
)
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
logging.debug(f"{self}: Opening SFTP connection.") logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp: async with conn.start_sftp_client() as sftp:
@@ -393,4 +400,4 @@ class BOSMiner(BaseMiner):
async def get_mac(self): async def get_mac(self):
result = await self.send_ssh_command("cat /sys/class/net/eth0/address") result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
return result.upper() return result.upper().strip()

View File

@@ -1,6 +1,6 @@
import logging import logging
import asyncssh import ipaddress
from API.bosminer import BOSMinerAPI from API.bosminer import BOSMinerAPI
from miners import BaseMiner from miners import BaseMiner
@@ -9,6 +9,7 @@ from miners import BaseMiner
class BOSMinerOld(BaseMiner): class BOSMinerOld(BaseMiner):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = BOSMinerAPI(ip) self.api = BOSMinerAPI(ip)
self.api_type = "BOSMiner" self.api_type = "BOSMiner"
self.uname = "root" self.uname = "root"
@@ -22,14 +23,17 @@ class BOSMinerOld(BaseMiner):
result = None result = None
# open an ssh connection # open an ssh connection
async with await asyncssh.connect("192.168.1.11", username="root") as conn: async with (await self._get_ssh_connection()) as conn:
# 3 retries # 3 retries
for i in range(3): for i in range(3):
try: try:
# run the command and get the result # run the command and get the result
result = await conn.run(cmd) result = await conn.run(cmd)
result = result.stdout if result.stdout:
result = result.stdout
except Exception as e: except Exception as e:
if e == "SSH connection closed":
return "Update completed."
# if the command fails, log it # if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}") logging.warning(f"{self} command {cmd} error: {e}")
@@ -40,6 +44,7 @@ class BOSMinerOld(BaseMiner):
# return the result, either command output or None # return the result, either command output or None
return str(result) return str(result)
async def update_to_plus(self): async def update_to_plus(self):
result = await self.send_ssh_command("opkg update && opkg install bos_plus") result = await self.send_ssh_command("opkg update && opkg install bos_plus")
return result return result

View File

@@ -30,7 +30,7 @@ class BTMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}") logging.warning(f"Failed to get model for miner: {self}")
return None return None
async def get_hostname(self) -> str: async def get_hostname(self) -> str or None:
if self.hostname: if self.hostname:
return self.hostname return self.hostname
try: try:
@@ -42,10 +42,10 @@ class BTMiner(BaseMiner):
return self.hostname return self.hostname
except APIError: except APIError:
logging.info(f"Failed to get hostname for miner: {self}") logging.info(f"Failed to get hostname for miner: {self}")
return "?" return None
except Exception: except Exception:
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return "?" return None
async def get_board_info(self) -> dict: async def get_board_info(self) -> dict:
"""Gets data on each board and chain in the miner.""" """Gets data on each board and chain in the miner."""
@@ -147,6 +147,9 @@ class BTMiner(BaseMiner):
if summary_data[0].get("MAC"): if summary_data[0].get("MAC"):
mac = summary_data[0]["MAC"] mac = summary_data[0]["MAC"]
if summary_data[0].get("Env Temp"):
data.env_temp = summary_data[0]["Env Temp"]
data.fan_1 = summary_data[0]["Fan Speed In"] data.fan_1 = summary_data[0]["Fan Speed In"]
data.fan_2 = summary_data[0]["Fan Speed Out"] data.fan_2 = summary_data[0]["Fan Speed Out"]
@@ -237,7 +240,7 @@ class BTMiner(BaseMiner):
logging.info(f"Failed to get mac: {self}") logging.info(f"Failed to get mac: {self}")
mac = None mac = None
if mac: if mac:
data.mac = mac data.mac = mac
return data return data

View File

@@ -33,7 +33,7 @@ class CGMiner(BaseMiner):
return self.model return self.model
return None return None
async def get_hostname(self) -> str: async def get_hostname(self) -> str or None:
if self.hostname: if self.hostname:
return self.hostname return self.hostname
try: try:
@@ -44,9 +44,9 @@ class CGMiner(BaseMiner):
self.hostname = host self.hostname = host
return self.hostname return self.hostname
else: else:
return "?" return None
except Exception: except Exception:
return "?" return None
async def send_ssh_command(self, cmd): async def send_ssh_command(self, cmd):
result = None result = None

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon1026(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 1026"
self.nominal_chips = 80
self.fan_count = 2

View File

@@ -6,5 +6,5 @@ class Avalon1047(BaseMiner):
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "Avalon 1047" self.model = "Avalon 1047"
self.nominal_chips = 114 self.nominal_chips = 80
self.fan_count = 4 self.fan_count = 2

View File

@@ -1,2 +1,3 @@
from .A1026 import Avalon1026
from .A1047 import Avalon1047 from .A1047 import Avalon1047
from .A1066 import Avalon1066 from .A1066 import Avalon1066

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon721(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 721"
self.chip_count = 18 # This miner has 4 boards totaling 72
self.fan_count = 1 # also only 1 fan

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon741(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 741"
self.chip_count = 22 # This miner has 4 boards totaling 88
self.fan_count = 1 # also only 1 fan

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon761(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 761"
self.chip_count = 18 # This miner has 4 boards totaling 72
self.fan_count = 1 # also only 1 fan

View File

@@ -0,0 +1,3 @@
from .A721 import Avalon721
from .A741 import Avalon741
from .A761 import Avalon761

View File

@@ -6,4 +6,5 @@ class Avalon821(BaseMiner):
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "Avalon 821" self.model = "Avalon 821"
self.fan_count = 2 self.chip_count = 26 # This miner has 4 boards totaling 104
self.fan_count = 1 # also only 1 fan

View File

@@ -6,4 +6,5 @@ class Avalon841(BaseMiner):
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "Avalon 841" self.model = "Avalon 841"
self.fan_count = 2 self.chip_count = 26 # This miner has 4 boards totaling 104
self.fan_count = 1 # also only 1 fan

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon851(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 851"
self.chip_count = 26 # This miner has 4 boards totaling 104
self.fan_count = 1 # also only 1 fan

View File

@@ -1,2 +1,3 @@
from .A821 import Avalon821 from .A821 import Avalon821
from .A841 import Avalon841 from .A841 import Avalon841
from .A851 import Avalon851

View File

@@ -0,0 +1,10 @@
from miners import BaseMiner
class Avalon921(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Avalon 921"
self.chip_count = 26 # This miner has 4 boards totaling 104
self.fan_count = 1 # also only 1 fan

View File

@@ -0,0 +1 @@
from .A921 import Avalon921

View File

@@ -1,2 +1,4 @@
from .A7X import *
from .A8X import * from .A8X import *
from .A9X import *
from .A10X import * from .A10X import *

View File

@@ -1,6 +1,15 @@
from miners import BaseMiner from miners import BaseMiner
class M21S(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M21S"
self.nominal_chips = 66
self.fan_count = 2
class M21SV60(BaseMiner): class M21SV60(BaseMiner):
def __init__(self, ip: str): def __init__(self, ip: str):
super().__init__() super().__init__()

View File

@@ -2,5 +2,5 @@ from .M20S import M20S
from .M20S_Plus import M20SPlus from .M20S_Plus import M20SPlus
from .M21 import M21 from .M21 import M21
from .M21S import M21SV20, M21SV60 from .M21S import M21S, M21SV20, M21SV60
from .M21S_Plus import M21SPlus from .M21S_Plus import M21SPlus

View File

@@ -17,3 +17,30 @@ class M30SV50(BaseMiner):
self.model = "M30S V50" self.model = "M30S V50"
self.nominal_chips = 156 self.nominal_chips = 156
self.fan_count = 2 self.fan_count = 2
class M30SVG20(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S VG20"
self.nominal_chips = 70
self.fan_count = 2
class M30SVE20(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S VE20"
self.nominal_chips = 111
self.fan_count = 2
class M30SVE10(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S VE10"
self.nominal_chips = 105
self.fan_count = 2

View File

@@ -10,6 +10,15 @@ class M30SPlus(BaseMiner):
self.fan_count = 2 self.fan_count = 2
class M30SPlusVG60(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S+ VG60"
self.nominal_chips = 86
self.fan_count = 2
class M30SPlusVE40(BaseMiner): class M30SPlusVE40(BaseMiner):
def __init__(self, ip: str): def __init__(self, ip: str):
super().__init__() super().__init__()
@@ -17,3 +26,12 @@ class M30SPlusVE40(BaseMiner):
self.model = "M30S+ VE40" self.model = "M30S+ VE40"
self.nominal_chips = 156 self.nominal_chips = 156
self.fan_count = 2 self.fan_count = 2
class M30SPlusVF20(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S+ VF20"
self.nominal_chips = 111
self.fan_count = 2

View File

@@ -1,6 +1,15 @@
from miners import BaseMiner from miners import BaseMiner
class M30SPlusPlus(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S++"
self.nominal_chips = 111
self.fan_count = 2
class M30SPlusPlusVG30(BaseMiner): class M30SPlusPlusVG30(BaseMiner):
def __init__(self, ip: str): def __init__(self, ip: str):
super().__init__() super().__init__()

View File

@@ -1,6 +1,6 @@
from .M30S import M30S, M30SV50 from .M30S import M30S, M30SVE10, M30SVE20, M30SVG20, M30SV50
from .M30S_Plus import M30SPlus, M30SPlusVE40 from .M30S_Plus import M30SPlus, M30SPlusVG60, M30SPlusVE40, M30SPlusVF20
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40 from .M30S_Plus_Plus import M30SPlusPlus, M30SPlusPlusVG30, M30SPlusPlusVG40
from .M31S import M31S from .M31S import M31S
from .M31S_Plus import M31SPlus, M31SPlusVE20 from .M31S_Plus import M31SPlus, M31SPlusVE20

View File

@@ -1,87 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import S17 # noqa - Ignore access to _module from miners._types import S17 # noqa - Ignore access to _module
import httpx
class BMMinerS17(BMMinerX17, S17):
# TODO add config
class BMMinerS17(BMMiner, S17):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import S17Plus # noqa - Ignore access to _module from miners._types import S17Plus # noqa - Ignore access to _module
import httpx
class BMMinerS17Plus(BMMinerX17, S17Plus):
class BMMinerS17Plus(BMMiner, S17Plus):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import S17Pro # noqa - Ignore access to _module from miners._types import S17Pro # noqa - Ignore access to _module
import httpx
class BMMinerS17Pro(BMMinerX17, S17Pro):
class BMMinerS17Pro(BMMiner, S17Pro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import S17e # noqa - Ignore access to _module from miners._types import S17e # noqa - Ignore access to _module
import httpx
class BMMinerS17e(BMMinerX17, S17e):
class BMMinerS17e(BMMiner, S17e):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import T17 # noqa - Ignore access to _module from miners._types import T17 # noqa - Ignore access to _module
import httpx
class BMMinerT17(BMMinerX17, T17):
class BMMinerT17(BMMiner, T17):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import T17Plus # noqa - Ignore access to _module from miners._types import T17Plus # noqa - Ignore access to _module
import httpx
class BMMinerT17Plus(BMMinerX17, T17Plus):
class BMMinerT17Plus(BMMiner, T17Plus):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,84 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X17 import BMMinerX17
from miners._types import T17e # noqa - Ignore access to _module from miners._types import T17e # noqa - Ignore access to _module
import httpx
class BMMinerT17e(BMMinerX17, T17e):
class BMMinerT17e(BMMiner, T17e):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -0,0 +1,83 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
import httpx
class BMMinerX17(BMMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
try:
await client.post(url, data={"action": "startBlink"}, auth=auth)
except httpx.ReadTimeout:
# Expected behaviour
pass
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
await client.post(url, data={"action": "stopBlink"}, auth=auth)
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if not data["isBlinking"]:
return True
return False
async def check_light(self):
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
if data.status_code == 200:
data = data.json()
if data["isBlinking"]:
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,73 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import S19 # noqa - Ignore access to _module from miners._types import S19 # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19(BMMinerX19, S19):
# TODO add config
class BMMinerS19(BMMiner, S19):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,70 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import S19Pro # noqa - Ignore access to _module from miners._types import S19Pro # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19Pro(BMMinerX19, S19Pro):
class BMMinerS19Pro(BMMiner, S19Pro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,70 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import S19a # noqa - Ignore access to _module from miners._types import S19a # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19a(BMMinerX19, S19a):
class BMMinerS19a(BMMiner, S19a):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,70 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import S19j # noqa - Ignore access to _module from miners._types import S19j # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19j(BMMinerX19, S19j):
class BMMinerS19j(BMMiner, S19j):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,70 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import S19jPro # noqa - Ignore access to _module from miners._types import S19jPro # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19jPro(BMMinerX19, S19jPro):
class BMMinerS19jPro(BMMiner, S19jPro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -1,70 +1,8 @@
from miners._backends import BMMiner # noqa - Ignore access to _module from .X19 import BMMinerX19
from miners._types import T19 # noqa - Ignore access to _module from miners._types import T19 # noqa - Ignore access to _module
import httpx
import json
class BMMinerT19(BMMinerX19, T19):
class BMMinerT19(BMMiner, T19):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -0,0 +1,102 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from config import MinerConfig
import httpx
import json
import asyncio
class BMMinerX19(BMMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_config(self) -> MinerConfig:
url = f"http://{self.ip}/cgi-bin/get_miner_conf.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
self.config = MinerConfig().from_raw(data)
return self.config
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
url = f"http://{self.ip}/cgi-bin/set_miner_conf.cgi"
auth = httpx.DigestAuth("root", "root")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_x19(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_x19()
try:
async with httpx.AsyncClient() as client:
await client.post(url, data=conf, auth=auth)
except httpx.ReadTimeout:
pass
for i in range(7):
data = await self.get_config()
if data.as_x19() == conf:
break
await asyncio.sleep(1)
async def get_hostname(self) -> str or None:
hostname = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "hostname" in data.keys():
hostname = data["hostname"]
return hostname
async def get_mac(self):
mac = None
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
data = data.json()
if len(data.keys()) > 0:
if "macaddr" in data.keys():
mac = data["macaddr"]
return mac
async def fault_light_on(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "true"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B000":
return True
return False
async def fault_light_off(self) -> bool:
url = f"http://{self.ip}/cgi-bin/blink.cgi"
auth = httpx.DigestAuth("root", "root")
data = json.dumps({"blink": "false"})
async with httpx.AsyncClient() as client:
data = await client.post(url, data=data, auth=auth)
if data.status_code == 200:
data = data.json()
if data.get("code") == "B100":
return True
return False
async def reboot(self) -> bool:
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
auth = httpx.DigestAuth("root", "root")
async with httpx.AsyncClient() as client:
data = await client.get(url, auth=auth)
if data.status_code == 200:
return True
return False

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon1026 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon1026(CGMiner, Avalon1026):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -1,8 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon1047 # noqa - Ignore access to _module from miners._types import Avalon1047 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon1047(CGMiner, Avalon1047): class CGMinerAvalon1047(CGMiner, Avalon1047):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -1,8 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon1066 # noqa - Ignore access to _module from miners._types import Avalon1066 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon1066(CGMiner, Avalon1066): class CGMinerAvalon1066(CGMiner, Avalon1066):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -1,2 +1,3 @@
from .A1026 import CGMinerAvalon1026
from .A1047 import CGMinerAvalon1047 from .A1047 import CGMinerAvalon1047
from .A1066 import CGMinerAvalon1066 from .A1066 import CGMinerAvalon1066

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon721 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon721(CGMiner, Avalon721):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon741 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon741(CGMiner, Avalon741):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon761 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon761(CGMiner, Avalon761):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -0,0 +1,3 @@
from .A721 import CGMinerAvalon721
from .A741 import CGMinerAvalon741
from .A761 import CGMinerAvalon761

View File

@@ -1,8 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon821 # noqa - Ignore access to _module from miners._types import Avalon821 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon821(CGMiner, Avalon821): class CGMinerAvalon821(CGMiner, Avalon821):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -1,8 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon841 # noqa - Ignore access to _module from miners._types import Avalon841 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon841(CGMiner, Avalon841): class CGMinerAvalon841(CGMiner, Avalon841):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname:
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon851 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon851(CGMiner, Avalon851):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -1,2 +1,3 @@
from .A821 import CGMinerAvalon821 from .A821 import CGMinerAvalon821
from .A841 import CGMinerAvalon841 from .A841 import CGMinerAvalon841
from .A851 import CGMinerAvalon851

View File

@@ -0,0 +1,238 @@
from miners._backends import CGMiner # noqa - Ignore access to _module
from miners._types import Avalon921 # noqa - Ignore access to _module
from data import MinerData
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import re
from config import MinerConfig
import logging
class CGMinerAvalon921(CGMiner, Avalon921):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
conf = MinerConfig().from_yaml(yaml_config).as_avalon(user_suffix=suffix)
else:
conf = MinerConfig().from_yaml(yaml_config).as_avalon()
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
model = await self.get_model()
mac = None
if model:
data.model = model
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] if not item == "0"]
)
if "PVT_T2" in raw_data:
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -0,0 +1 @@
from .A921 import CGMinerAvalon921

View File

@@ -1,2 +1,4 @@
from .A7X import *
from .A8X import * from .A8X import *
from .A9X import *
from .A10X import * from .A10X import *

View File

@@ -133,8 +133,8 @@ MINER_CLASSES = {
"BTMiner": BTMinerM21, "BTMiner": BTMinerM21,
}, },
"M21S": { "M21S": {
"Default": BTMinerM21SV60, "Default": BTMinerM21S,
"BTMiner": BTMinerM21SV60, "BTMiner": BTMinerM21S,
"60": BTMinerM21SV60, "60": BTMinerM21SV60,
"20": BTMinerM21SV20, "20": BTMinerM21SV20,
}, },
@@ -146,17 +146,22 @@ MINER_CLASSES = {
"Default": BTMinerM30S, "Default": BTMinerM30S,
"BTMiner": BTMinerM30S, "BTMiner": BTMinerM30S,
"50": BTMinerM30SV50, "50": BTMinerM30SV50,
"G20": BTMinerM30SVG20,
"E20": BTMinerM30SVE20,
"E10": BTMinerM30SVE10,
}, },
"M30S+": { "M30S+": {
"Default": BTMinerM30SPlus, "Default": BTMinerM30SPlus,
"BTMiner": BTMinerM30SPlus, "BTMiner": BTMinerM30SPlus,
"40": BTMinerM30SPlusVE40, "F20": BTMinerM30SPlusVF20,
"E40": BTMinerM30SPlusVE40,
"G60": BTMinerM30SPlusVG60,
}, },
"M30S++": { "M30S++": {
"Default": BTMinerM30SPlusPlusVG40, "Default": BTMinerM30SPlusPlus,
"BTMiner": BTMinerM30SPlusPlusVG40, "BTMiner": BTMinerM30SPlusPlus,
"40": BTMinerM30SPlusPlusVG40, "G40": BTMinerM30SPlusPlusVG40,
"30": BTMinerM30SPlusPlusVG30, "G30": BTMinerM30SPlusPlusVG30,
}, },
"M31S": { "M31S": {
"Default": BTMinerM31S, "Default": BTMinerM31S,
@@ -165,12 +170,52 @@ MINER_CLASSES = {
"M31S+": { "M31S+": {
"Default": BTMinerM31SPlus, "Default": BTMinerM31SPlus,
"BTMiner": BTMinerM31SPlus, "BTMiner": BTMinerM31SPlus,
"20": BTMinerM31SPlusVE20, "E20": BTMinerM31SPlusVE20,
}, },
"M32S": { "M32S": {
"Default": BTMinerM32S, "Default": BTMinerM32S,
"BTMiner": BTMinerM32S, "BTMiner": BTMinerM32S,
}, },
"AvalonMiner 721": {
"Default": CGMinerAvalon721,
"CGMiner": CGMinerAvalon721,
},
"AvalonMiner 741": {
"Default": CGMinerAvalon741,
"CGMiner": CGMinerAvalon741,
},
"AvalonMiner 761": {
"Default": CGMinerAvalon761,
"CGMiner": CGMinerAvalon761,
},
"AvalonMiner 821": {
"Default": CGMinerAvalon821,
"CGMiner": CGMinerAvalon821,
},
"AvalonMiner 841": {
"Default": CGMinerAvalon841,
"CGMiner": CGMinerAvalon841,
},
"AvalonMiner 851": {
"Default": CGMinerAvalon851,
"CGMiner": CGMinerAvalon851,
},
"AvalonMiner 921": {
"Default": CGMinerAvalon921,
"CGMiner": CGMinerAvalon921,
},
"AvalonMiner 1026": {
"Default": CGMinerAvalon1026,
"CGMiner": CGMinerAvalon1026,
},
"AvalonMiner 1047": {
"Default": CGMinerAvalon1047,
"CGMiner": CGMinerAvalon1047,
},
"AvalonMiner 1066": {
"Default": CGMinerAvalon1066,
"CGMiner": CGMinerAvalon1066,
},
} }
@@ -251,22 +296,21 @@ class MinerFactory(metaclass=Singleton):
if not api: if not api:
api = "Default" api = "Default"
# Avalonminers if model not in MINER_CLASSES.keys():
if "avalon" in model: if "avalon" in model:
if model == "avalon10": print(model)
miner = CGMinerAvalon1066(str(ip)) if model == "avalon10":
else: miner = CGMinerAvalon1066(str(ip))
miner = CGMinerAvalon821(str(ip)) else:
else: miner = CGMinerAvalon821(str(ip))
if model not in MINER_CLASSES.keys(): miner = UnknownMiner(str(ip))
miner = UnknownMiner(str(ip)) return miner
return miner if api not in MINER_CLASSES[model].keys():
if api not in MINER_CLASSES[model].keys(): api = "Default"
api = "Default" if ver in MINER_CLASSES[model].keys():
if ver in MINER_CLASSES[model].keys(): miner = MINER_CLASSES[model][ver](str(ip))
miner = MINER_CLASSES[model][ver](str(ip)) return miner
return miner miner = MINER_CLASSES[model][api](str(ip))
miner = MINER_CLASSES[model][api](str(ip))
# if we cant find a model, check if we found the API # if we cant find a model, check if we found the API
else: else:
@@ -402,6 +446,18 @@ class MinerFactory(metaclass=Singleton):
): ):
api = "BTMiner" api = "BTMiner"
# check for avalonminers
if version["VERSION"][0].get("PROD"):
_data = version["VERSION"][0]["PROD"].split("-")
model = _data[0]
if len(data) > 1:
ver = _data[1]
elif version["VERSION"][0].get("MODEL"):
_data = version["VERSION"][0]["MODEL"].split("-")
model = f"AvalonMiner {_data[0]}"
if len(data) > 1:
ver = _data[1]
# if we have no model from devdetails but have version, try to get it from there # if we have no model from devdetails but have version, try to get it from there
if version and not model: if version and not model:
# make sure version isn't blank # make sure version isn't blank
@@ -438,10 +494,6 @@ class MinerFactory(metaclass=Singleton):
_ver = model.split("V") _ver = model.split("V")
if len(_ver) > 1: if len(_ver) > 1:
ver = model.split("V")[1] ver = model.split("V")[1]
if "VE" in model:
ver = model.split("VE")[1]
if "VG" in model:
ver = model.split("VG")[1]
model = model.split("V")[0] model = model.split("V")[0]
# don't need "Bitmain", just "Antminer XX" as model # don't need "Bitmain", just "Antminer XX" as model
if "Bitmain " in model: if "Bitmain " in model:

View File

@@ -1,5 +1,11 @@
from miners._backends import BTMiner # noqa - Ignore access to _module from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M21SV20, M21SV60 # noqa - Ignore access to _module from miners._types import M21S, M21SV20, M21SV60 # noqa - Ignore access to _module
class BTMinerM21S(BTMiner, M21S):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM21SV20(BTMiner, M21SV20): class BTMinerM21SV20(BTMiner, M21SV20):

View File

@@ -2,5 +2,5 @@ from .M20S import BTMinerM20S
from .M20S_Plus import BTMinerM20SPlus from .M20S_Plus import BTMinerM20SPlus
from .M21 import BTMinerM21 from .M21 import BTMinerM21
from .M21S import BTMinerM21SV20, BTMinerM21SV60 from .M21S import BTMinerM21S, BTMinerM21SV20, BTMinerM21SV60
from .M21S_Plus import BTMinerM21SPlus from .M21S_Plus import BTMinerM21SPlus

View File

@@ -1,5 +1,11 @@
from miners._backends import BTMiner # noqa - Ignore access to _module from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M30S, M30SV50 # noqa - Ignore access to _module from miners._types import (
M30S,
M30SV50,
M30SVG20,
M30SVE20,
M30SVE10,
) # noqa - Ignore access to _module
class BTMinerM30S(BTMiner, M30S): class BTMinerM30S(BTMiner, M30S):
@@ -12,3 +18,21 @@ class BTMinerM30SV50(BTMiner, M30SV50):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
class BTMinerM30SVG20(BTMiner, M30SVG20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SVE20(BTMiner, M30SVE20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SVE10(BTMiner, M30SVE10):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -1,5 +1,10 @@
from miners._backends import BTMiner # noqa - Ignore access to _module from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M30SPlus, M30SPlusVE40 # noqa - Ignore access to _module from miners._types import (
M30SPlus,
M30SPlusVE40,
M30SPlusVF20,
M30SPlusVG60,
) # noqa - Ignore access to _module
class BTMinerM30SPlus(BTMiner, M30SPlus): class BTMinerM30SPlus(BTMiner, M30SPlus):
@@ -12,3 +17,15 @@ class BTMinerM30SPlusVE40(BTMiner, M30SPlusVE40):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
class BTMinerM30SPlusVF20(BTMiner, M30SPlusVF20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusVG60(BTMiner, M30SPlusVG60):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -1,10 +1,17 @@
from miners._backends import BTMiner # noqa - Ignore access to _module from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import ( # noqa - Ignore access to _module from miners._types import ( # noqa - Ignore access to _module
M30SPlusPlus,
M30SPlusPlusVG40, M30SPlusPlusVG40,
M30SPlusPlusVG30, M30SPlusPlusVG30,
) )
class BTMinerM30SPlusPlus(BTMiner, M30SPlusPlus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40): class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)

View File

@@ -1,6 +1,21 @@
from .M30S import BTMinerM30S, BTMinerM30SV50 from .M30S import (
from .M30S_Plus import BTMinerM30SPlus, BTMinerM30SPlusVE40 BTMinerM30S,
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30 BTMinerM30SVE10,
BTMinerM30SVE20,
BTMinerM30SVG20,
BTMinerM30SV50,
)
from .M30S_Plus import (
BTMinerM30SPlus,
BTMinerM30SPlusVF20,
BTMinerM30SPlusVE40,
BTMinerM30SPlusVG60,
)
from .M30S_Plus_Plus import (
BTMinerM30SPlusPlus,
BTMinerM30SPlusPlusVG40,
BTMinerM30SPlusPlusVG30,
)
from .M31S import BTMinerM31S from .M31S import BTMinerM31S
from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20 from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20

View File

@@ -13,17 +13,19 @@ MINER_FACTORY_GET_VERSION_RETRIES: int = 3
WHATSMINER_PWD = "admin" WHATSMINER_PWD = "admin"
DEBUG = False DEBUG = False
LOGFILE = False
settings_keys = {}
try: try:
with open( with open(
os.path.join(os.path.dirname(__file__), "settings.toml"), "r" os.path.join(os.path.dirname(__file__), "settings.toml"), "r"
) as settings_file: ) as settings_file:
settings = toml.loads(settings_file.read()) settings = toml.loads(settings_file.read())
settings_keys = settings.keys()
except: except:
pass pass
settings_keys = settings.keys()
if "ping_retries" in settings_keys: if "ping_retries" in settings_keys:
NETWORK_PING_RETRIES: int = settings["ping_retries"] NETWORK_PING_RETRIES: int = settings["ping_retries"]
if "ping_timeout" in settings_keys: if "ping_timeout" in settings_keys:
@@ -46,3 +48,6 @@ if "whatsminer_pwd" in settings_keys:
if "debug" in settings_keys: if "debug" in settings_keys:
DEBUG: int = settings["debug"] DEBUG: int = settings["debug"]
if "logfile" in settings_keys:
LOGFILE: bool = settings["logfile"]

View File

@@ -13,6 +13,8 @@ reboot_threads = 300
whatsminer_pwd = "admin" whatsminer_pwd = "admin"
logfile = true
### DEBUG MODE ### ### DEBUG MODE ###
# change this to debug = true # change this to debug = true
# to enable debug mode. # to enable debug mode.

View File

@@ -2,6 +2,7 @@ import datetime
import ipaddress import ipaddress
from base64 import b64decode from base64 import b64decode
from io import BytesIO from io import BytesIO
from copy import copy
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.figure import Figure from matplotlib.figure import Figure
@@ -178,15 +179,26 @@ def create_boards_pie_chart(data):
num_bad_boards = [0, 0, 0, 0] num_bad_boards = [0, 0, 0, 0]
for item in data.keys(): for item in data.keys():
num_bad_boards[len(data[item])] += 1 num_bad_boards[len(data[item])] += 1
idxs = []
graph_labels = copy(labels)
graph_num_bad_board = copy(num_bad_boards)
for idx in range(len(num_bad_boards)):
if num_bad_boards[idx] == 0:
idxs.append(idx)
idxs.sort(reverse=True)
for idx in idxs:
graph_labels.pop(idx)
graph_num_bad_board.pop(idx)
cmap = plt.get_cmap("Blues") cmap = plt.get_cmap("Blues")
cs = cmap(np.linspace(0.2, 0.8, num=len(num_bad_boards))) cs = cmap(np.linspace(0.2, 0.8, num=len(graph_num_bad_board)))
# fig, ax = plt.subplots() -> causes window resizing... # fig, ax = plt.subplots() -> causes window resizing...
fig = Figure() fig = Figure()
ax = fig.add_subplot() ax = fig.add_subplot()
ax.pie( ax.pie(
num_bad_boards, graph_num_bad_board,
labels=labels, labels=graph_labels,
autopct="%1.2f%%", autopct="%1.2f%%",
shadow=True, shadow=True,
startangle=180, startangle=180,

View File

@@ -1,13 +1,13 @@
import PySimpleGUI as sg
from config.bos import bos_config_convert
import time
from tools.cfg_util.layout import window, update_prog_bar
from tools.cfg_util.decorators import disable_buttons
from miners.miner_factory import MinerFactory
import asyncio import asyncio
from settings import CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS
from tools.cfg_util.general import update_miners_data
import PySimpleGUI as sg
from config import MinerConfig
from miners.miner_factory import MinerFactory
from settings import CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS
from tools.cfg_util.decorators import disable_buttons
from tools.cfg_util.general import update_miners_data
from tools.cfg_util.layout import window, update_prog_bar
progress_bar_len = 0 progress_bar_len = 0
@@ -20,7 +20,8 @@ async def btn_import(table, selected):
miner = await MinerFactory().get_miner(ip) miner = await MinerFactory().get_miner(ip)
await miner.get_config() await miner.get_config()
config = miner.config config = miner.config
window["cfg_config_txt"].update(config) if config:
window["cfg_config_txt"].update(config.as_yaml())
@disable_buttons("Configuring") @disable_buttons("Configuring")
@@ -67,7 +68,25 @@ async def send_config_generator(miners: list, config, last_octet_ip_user: bool):
yield await sent_config yield await sent_config
def generate_config(username: str, workername: str, v2_allowed: bool): def generate_config(
username: str,
workername: str,
v2_allowed: bool,
advanced_cfg: bool,
autotuning_enabled: bool,
autotuning_wattage: int,
manual_fan_control: bool,
manual_fan_speed: int,
min_fans: int,
target_temp: int,
hot_temp: int,
dangerous_temp: int,
dps_enabled: bool,
dps_power_step: int,
dps_min_power: int,
dps_shutdown_enabled: bool,
dps_shutdown_duration: int,
):
if username and workername: if username and workername:
user = f"{username}.{workername}" user = f"{username}.{workername}"
elif username and not workername: elif username and not workername:
@@ -83,33 +102,67 @@ def generate_config(username: str, workername: str, v2_allowed: bool):
url_1 = "stratum+tcp://ca.stratum.slushpool.com:3333" url_1 = "stratum+tcp://ca.stratum.slushpool.com:3333"
url_2 = "stratum+tcp://us-east.stratum.slushpool.com:3333" url_2 = "stratum+tcp://us-east.stratum.slushpool.com:3333"
url_3 = "stratum+tcp://stratum.slushpool.com:3333" url_3 = "stratum+tcp://stratum.slushpool.com:3333"
if not advanced_cfg:
config = { config = {
"group": [ "group": [
{ {
"name": "group", "name": "group",
"quota": 1, "quota": 1,
"pool": [ "pool": [
{"url": url_1, "user": user, "password": "123"}, {"url": url_1, "user": user, "password": "123"},
{"url": url_2, "user": user, "password": "123"}, {"url": url_2, "user": user, "password": "123"},
{"url": url_3, "user": user, "password": "123"}, {"url": url_3, "user": user, "password": "123"},
], ],
}
],
"temp_control": {
"target_temp": 80.0,
"hot_temp": 90.0,
"dangerous_temp": 120.0,
},
"autotuning": {"enabled": True, "psu_power_limit": 900},
}
else:
config = {
"group": [
{
"name": "group",
"quota": 1,
"pool": [
{"url": url_1, "user": user, "password": "123"},
{"url": url_2, "user": user, "password": "123"},
{"url": url_3, "user": user, "password": "123"},
],
}
],
"temp_control": {
"mode": "auto",
"target_temp": float(target_temp),
"hot_temp": float(hot_temp),
"dangerous_temp": float(dangerous_temp),
},
"autotuning": {
"enabled": autotuning_enabled,
"psu_power_limit": autotuning_wattage,
},
}
if manual_fan_control:
config["temp_control"]["mode"] = "manual"
config["fan_control"] = {}
config["fan_control"]["speed"] = manual_fan_speed
config["fan_control"]["min_fans"] = min_fans
if dps_enabled:
config["power_scaling"] = {
"enabled": dps_enabled,
"power_step": dps_power_step,
"min_psu_power_limit": dps_min_power,
"shutdown_enabled": dps_shutdown_enabled,
"shutdown_duration": dps_shutdown_duration,
} }
],
"format": { cfg = MinerConfig().from_raw(config)
"version": "1.2+",
"model": "Antminer S9", window["cfg_config_txt"].update(cfg.as_yaml())
"generator": "upstream_config_util",
"timestamp": int(time.time()),
},
"temp_control": {
"target_temp": 80.0,
"hot_temp": 90.0,
"dangerous_temp": 120.0,
},
"autotuning": {"enabled": True, "psu_power_limit": 900},
}
window["cfg_config_txt"].update(bos_config_convert(config))
async def generate_config_ui(): async def generate_config_ui():
@@ -126,9 +179,51 @@ async def generate_config_ui():
values["generate_config_window_username"], values["generate_config_window_username"],
values["generate_config_window_workername"], values["generate_config_window_workername"],
values["generate_config_window_allow_v2"], values["generate_config_window_allow_v2"],
values["show_advanced_options"],
values["autotuning_enabled"],
values["autotuning_wattage"],
values["manual_fan_control"],
values["manual_fan_speed"],
values["min_fans"],
values["target_temp"],
values["hot_temp"],
values["danger_temp"],
values["dps_enabled"],
values["dps_power_step"],
values["dps_min_power"],
values["dps_shutdown_enabled"],
values["dps_shutdown_duration"],
) )
generate_config_window.close() generate_config_window.close()
break break
if event == "show_advanced_options":
generate_config_window["advanced_options"].update(
visible=values["show_advanced_options"]
)
if event == "autotuning_enabled":
generate_config_window["autotuning_wattage"].update(
disabled=not values["autotuning_enabled"]
)
if event == "manual_fan_control":
generate_config_window["manual_fan_speed"].update(
disabled=not values["manual_fan_control"]
)
generate_config_window["min_fans"].update(
disabled=not values["manual_fan_control"]
)
if event == "dps_enabled":
for elem in ["dps_power_step", "dps_min_power", "dps_shutdown_enabled"]:
generate_config_window[elem].update(disabled=not values["dps_enabled"])
if not values["dps_enabled"]:
generate_config_window["dps_shutdown_duration"].update(disabled=True)
if values["dps_enabled"] and values["dps_shutdown_enabled"]:
generate_config_window["dps_shutdown_duration"].update(disabled=False)
if event == "dps_shutdown_enabled":
if values["dps_enabled"]:
generate_config_window["dps_shutdown_duration"].update(
disabled=not values["dps_shutdown_enabled"]
)
def generate_config_layout(): def generate_config_layout():
@@ -155,6 +250,123 @@ def generate_config_layout():
sg.Text("Allow Stratum V2?:", size=(19, 1)), sg.Text("Allow Stratum V2?:", size=(19, 1)),
sg.Checkbox("", key="generate_config_window_allow_v2", default=True), sg.Checkbox("", key="generate_config_window_allow_v2", default=True),
], ],
[
sg.Push(),
sg.Checkbox(
"Advanced Options", enable_events=True, key="show_advanced_options"
),
],
[
sg.pin(
sg.Column(
[
[
sg.Text("Autotuning Enabled:", size=(19, 1)),
sg.Checkbox(
"", key="autotuning_enabled", enable_events=True
),
sg.Text("Power Limit:"),
sg.Spin(
[i for i in range(100, 5001, 100)],
initial_value=900,
size=(5, 1),
key="autotuning_wattage",
disabled=True,
),
],
[
sg.Text("Manual Fan Control:", size=(19, 1)),
sg.Checkbox(
"", key="manual_fan_control", enable_events=True
),
sg.Text("Speed:"),
sg.Spin(
[i for i in range(5, 101, 5)],
initial_value=100,
size=(5, 1),
key="manual_fan_speed",
disabled=True,
),
sg.Text("Min Fans:"),
sg.Spin(
[i for i in range(5)],
initial_value=1,
size=(5, 1),
key="min_fans",
disabled=True,
),
],
[
sg.Text("Target Temp:", size=(19, 1)),
sg.Spin(
[i for i in range(5, 101, 5)],
initial_value=80,
size=(5, 1),
key="target_temp",
),
],
[
sg.Text("Hot Temp:", size=(19, 1)),
sg.Spin(
[i for i in range(5, 111, 5)],
initial_value=90,
size=(5, 1),
key="hot_temp",
),
],
[
sg.Text("Dangerous Temp:", size=(19, 1)),
sg.Spin(
[i for i in range(5, 131, 5)],
initial_value=100,
size=(5, 1),
key="danger_temp",
),
],
[
sg.Text("Dynamic Power Scaling:"),
sg.Checkbox("", key="dps_enabled", enable_events=True),
sg.Text("Power Step:"),
sg.Spin(
[i for i in range(50, 301, 5)],
initial_value=100,
size=(5, 1),
key="dps_power_step",
disabled=True,
),
sg.Text("Min Power:"),
sg.Spin(
[i for i in range(100, 5001, 100)],
initial_value=800,
size=(5, 1),
key="dps_min_power",
disabled=True,
),
],
[
sg.Text("DPS Shutdown:"),
sg.Checkbox(
"",
key="dps_shutdown_enabled",
enable_events=True,
disabled=True,
),
sg.Text("Shutdown Duration (H):"),
sg.Spin(
[i for i in range(1, 11, 1)],
initial_value=3,
size=(5, 1),
key="dps_shutdown_duration",
disabled=True,
),
],
],
key="advanced_options",
visible=False,
pad=0,
)
)
],
[sg.Button("Generate", key="generate_config_window_generate")], [sg.Button("Generate", key="generate_config_window_generate")],
] ]
return config_layout return config_layout

View File

@@ -1,12 +1,12 @@
import aiohttp
import httpx
import shutil
import aiofiles
import asyncio import asyncio
from bs4 import BeautifulSoup
import re
import os
import logging import logging
import os
import re
import shutil
import aiofiles
import httpx
from bs4 import BeautifulSoup
async def get_latest_version(session): async def get_latest_version(session):