Compare commits
42 Commits
v0.8.0
...
v0.8.3-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa83e61249 | ||
|
|
2f3411e12d | ||
|
|
3e7311687e | ||
|
|
bc2d549ce5 | ||
|
|
3d31d89c9e | ||
|
|
15fc27e6fa | ||
|
|
943ebc77a1 | ||
|
|
733437ef03 | ||
|
|
b444245e98 | ||
|
|
481d31a0f1 | ||
|
|
264db3bdd6 | ||
|
|
d292b9c195 | ||
|
|
dce25a679f | ||
|
|
c903631742 | ||
|
|
e70bfdc886 | ||
|
|
8e1803add1 | ||
|
|
7d61056ea3 | ||
|
|
0d497baa45 | ||
|
|
d3a71c5a93 | ||
|
|
895a5b7ac8 | ||
|
|
7a5a0b287c | ||
|
|
c7d73276c8 | ||
|
|
4bbb9d0b08 | ||
|
|
3ee49e6fd7 | ||
|
|
dcd3e99d73 | ||
|
|
64018cdad8 | ||
|
|
e7d269008c | ||
|
|
7dfe25e5d2 | ||
|
|
382f9cff76 | ||
|
|
a5195ff1db | ||
|
|
b1ec726d18 | ||
|
|
5ae2cb2b22 | ||
|
|
472a15f4ca | ||
|
|
7cc7973587 | ||
|
|
ab964e4c88 | ||
|
|
4087874b4a | ||
|
|
844deec0d3 | ||
|
|
d36eef4c33 | ||
|
|
69d4ee5570 | ||
|
|
e6d3ec01fe | ||
|
|
e7b01ccdab | ||
|
|
38506903ea |
29
README.md
29
README.md
@@ -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.*
|
||||
|
||||
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
|
||||
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```
|
||||
(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.
|
||||
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
|
||||
<br>
|
||||
@@ -48,7 +48,6 @@ A basic script to find all miners on the network and get the hashrate from them
|
||||
```python
|
||||
import asyncio
|
||||
from network import MinerNetwork
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
|
||||
|
||||
async def get_hashrate():
|
||||
@@ -60,18 +59,11 @@ async def get_hashrate():
|
||||
# Miner Network scan function returns Miner classes for all miners found
|
||||
miners = await miner_network.scan_network_for_miners()
|
||||
# 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
|
||||
data = await asyncio.gather(*tasks)
|
||||
parse_tasks = []
|
||||
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)
|
||||
# now we have a list of MinerData, and can get .hashrate
|
||||
print([item.hashrate for item in data])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -84,7 +76,7 @@ You can also create your own miner without scanning if you know the IP:
|
||||
import asyncio
|
||||
import ipaddress
|
||||
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):
|
||||
@@ -95,11 +87,9 @@ async def get_miner_hashrate(ip: str):
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the API data
|
||||
summary = await miner.api.summary()
|
||||
# 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
|
||||
data = await safe_parse_api_data(summary, 'SUMMARY', 0, 'MHS 5s')
|
||||
print(data)
|
||||
data = await miner.get_data()
|
||||
# print out hashrate
|
||||
print(data.hashrate)
|
||||
|
||||
|
||||
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:
|
||||
* 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
|
||||
import asyncio
|
||||
|
||||
@@ -1,78 +1,350 @@
|
||||
"""
|
||||
SAMPLE CONFIG
|
||||
-------------------
|
||||
{
|
||||
"format": {
|
||||
"version": "1.2+", # -> (default = "1.2+", str, (bos: format.version))
|
||||
"model": "Antminer S9", # -> (default = "Antminer S9", str, (bos: format.model))
|
||||
"generator": "upstream_config_util", # -> (hidden, always = "upstream_config_util", str, (bos: format.generator))
|
||||
"timestamp": 1606842000, # -> (hidden, always = int(time.time()) (current unix time), int, (bos: format.timestamp))
|
||||
},
|
||||
"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))
|
||||
},
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import List, Literal
|
||||
import random
|
||||
import string
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Pool:
|
||||
"""A dataclass for pool information.
|
||||
|
||||
:param url: URL of the pool.
|
||||
:param username: Username on the pool.
|
||||
:param password: Worker password on the pool.
|
||||
"""
|
||||
|
||||
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_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_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):
|
||||
"""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_bos(self, model: str = "S9", user_suffix: str = None):
|
||||
"""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",
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"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))
|
||||
}
|
||||
}
|
||||
"""
|
||||
):
|
||||
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)
|
||||
|
||||
221
config/bos.py
221
config/bos.py
@@ -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
|
||||
@@ -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'
|
||||
|
||||
@@ -1,9 +1,41 @@
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerData:
|
||||
"""A Dataclass to standardize data returned from miners (specifically AnyMiner().get_data())
|
||||
|
||||
:param ip: The IP of the miner as a str.
|
||||
:param datetime: The time and date this data was generated.
|
||||
:param model: The model of the miner as a str.
|
||||
:param hostname: The network hostname of the miner as a str.
|
||||
:param hashrate: The hashrate of the miner in TH/s as a int.
|
||||
:param left_board_temp: The temp of the left PCB as an int.
|
||||
:param left_board_chip_temp: The temp of the left board chips as an int.
|
||||
:param center_board_temp: The temp of the center PCB as an int.
|
||||
:param center_board_chip_temp: The temp of the center board chips as an int.
|
||||
:param right_board_temp: The temp of the right PCB as an int.
|
||||
:param right_board_chip_temp: The temp of the right board chips as an int.
|
||||
:param wattage: Wattage of the miner as an int.
|
||||
:param fan_1: The speed of the first fan as an int.
|
||||
:param fan_2: The speed of the second fan as an int.
|
||||
:param fan_3: The speed of the third fan as an int.
|
||||
:param fan_4: The speed of the fourth fan as an int.
|
||||
:param left_chips: The number of chips online in the left board as an int.
|
||||
:param center_chips: The number of chips online in the left board as an int.
|
||||
:param right_chips: The number of chips online in the left board as an int.
|
||||
:param ideal_chips: The ideal number of chips in the miner as an int.
|
||||
:param pool_split: The pool split as a str.
|
||||
:param pool_1_url: The first pool url on the miner as a str.
|
||||
:param pool_1_user: The first pool user on the miner as a str.
|
||||
:param pool_2_url: The second pool url on the miner as a str.
|
||||
:param pool_2_user: The second pool user on the miner as a str.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
@@ -26,12 +58,15 @@ class MinerData:
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
nominal: int = field(init=False)
|
||||
pool_split: str = 0
|
||||
pool_split: str = "0"
|
||||
pool_1_url: str = "Unknown"
|
||||
pool_1_user: str = "Unknown"
|
||||
pool_2_url: str = ""
|
||||
pool_2_user: str = ""
|
||||
|
||||
def __post_init__(self):
|
||||
self.datetime = datetime.now()
|
||||
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
return self.right_chips + self.center_chips + self.left_chips
|
||||
|
||||
Binary file not shown.
@@ -2,17 +2,23 @@ import logging
|
||||
from settings import DEBUG
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
# filename="logfile.txt",
|
||||
# filemode="a",
|
||||
format="[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
logger = logging.getLogger()
|
||||
def init_logger():
|
||||
logging.basicConfig(
|
||||
# filename="logfile.txt",
|
||||
# filemode="a",
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
_logger = logging.getLogger()
|
||||
|
||||
if DEBUG:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
if DEBUG:
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
_logger.setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
|
||||
return _logger
|
||||
|
||||
|
||||
logger = init_logger()
|
||||
|
||||
@@ -9,6 +9,7 @@ import datetime
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
from setuptools import find_packages
|
||||
|
||||
base = None
|
||||
if sys.platform == "win32":
|
||||
@@ -16,7 +17,6 @@ if sys.platform == "win32":
|
||||
|
||||
version = datetime.datetime.now()
|
||||
version = version.strftime("%y.%m.%d")
|
||||
print(version)
|
||||
|
||||
|
||||
setup(
|
||||
@@ -30,9 +30,7 @@ setup(
|
||||
os.path.join(os.getcwd(), "settings/settings.toml"),
|
||||
os.path.join(os.getcwd(), "static/CFG-Util-README.md"),
|
||||
],
|
||||
"excludes": [
|
||||
os.path.join(os.getcwd(), "tools/web_testbench/files"),
|
||||
],
|
||||
"excludes": ["tests", "tools.web_testbench", "tools.web_monitor"],
|
||||
},
|
||||
},
|
||||
executables=[
|
||||
|
||||
@@ -18,6 +18,7 @@ class BaseMiner:
|
||||
self.nominal_chips = 1
|
||||
self.version = None
|
||||
self.fan_count = 2
|
||||
self.config = None
|
||||
|
||||
def __repr__(self):
|
||||
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):
|
||||
return None
|
||||
|
||||
async def get_data(self):
|
||||
async def get_data(self) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
|
||||
@@ -135,6 +135,7 @@ class BMMiner(BaseMiner):
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
@@ -142,6 +143,9 @@ class BMMiner(BaseMiner):
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand(
|
||||
@@ -171,7 +175,7 @@ class BMMiner(BaseMiner):
|
||||
if len(boards) > 0:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"fan{board_num + _b_num}")
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
import json
|
||||
|
||||
import toml
|
||||
|
||||
|
||||
from miners import BaseMiner
|
||||
from API.bosminer import BOSMinerAPI
|
||||
from API import APIError
|
||||
|
||||
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
|
||||
|
||||
@@ -101,8 +102,9 @@ class BOSMiner(BaseMiner):
|
||||
async with sftp.open("/etc/bosminer.toml") as file:
|
||||
toml_data = toml.loads(await file.read())
|
||||
logging.debug(f"{self}: Converting config file.")
|
||||
cfg = bos_config_convert(toml_data)
|
||||
cfg = MinerConfig().from_raw(toml_data)
|
||||
self.config = cfg
|
||||
return self.config
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
"""Get miner hostname.
|
||||
@@ -137,7 +139,17 @@ class BOSMiner(BaseMiner):
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# get devdetails data
|
||||
version_data = await self.api.devdetails()
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError as e:
|
||||
version_data = None
|
||||
if e.message == "Not ready":
|
||||
cfg = json.loads(await self.send_ssh_command("bosminer config --data"))
|
||||
model = cfg.get("data").get("format").get("model")
|
||||
if model:
|
||||
model = model.replace("Antminer ", "")
|
||||
self.model = model
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
@@ -181,11 +193,17 @@ class BOSMiner(BaseMiner):
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
if ip_user:
|
||||
suffix = str(self.ip).split(".")[-1]
|
||||
toml_conf = toml.dumps(
|
||||
general_config_convert_bos(yaml_config, user_suffix=suffix)
|
||||
toml_conf = (
|
||||
MinerConfig()
|
||||
.from_yaml(yaml_config)
|
||||
.as_bos(model=self.model.replace(" (BOS)", ""), user_suffix=suffix)
|
||||
)
|
||||
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:
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
@@ -247,8 +265,13 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
@@ -256,24 +279,33 @@ class BOSMiner(BaseMiner):
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "temps", "tunerstatus", "pools", "devdetails", "fans"
|
||||
)
|
||||
try:
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "temps", "tunerstatus", "pools", "devdetails", "fans"
|
||||
)
|
||||
except APIError as e:
|
||||
if str(e.message) == "Not ready":
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "tunerstatus", "pools", "fans"
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
if not miner_data:
|
||||
return data
|
||||
summary = miner_data.get("summary")[0]
|
||||
temps = miner_data.get("temps")[0]
|
||||
tunerstatus = miner_data.get("tunerstatus")[0]
|
||||
pools = miner_data.get("pools")[0]
|
||||
devdetails = miner_data.get("devdetails")[0]
|
||||
fans = miner_data.get("fans")[0]
|
||||
summary = miner_data.get("summary")
|
||||
temps = miner_data.get("temps")
|
||||
tunerstatus = miner_data.get("tunerstatus")
|
||||
pools = miner_data.get("pools")
|
||||
devdetails = miner_data.get("devdetails")
|
||||
fans = miner_data.get("fans")
|
||||
|
||||
if summary:
|
||||
hr = summary.get("SUMMARY")
|
||||
hr = summary[0].get("SUMMARY")
|
||||
if hr:
|
||||
if len(hr) > 0:
|
||||
hr = hr[0].get("MHS 1m")
|
||||
@@ -281,11 +313,11 @@ class BOSMiner(BaseMiner):
|
||||
data.hashrate = round(hr / 1000000, 2)
|
||||
|
||||
if temps:
|
||||
temp = temps.get("TEMPS")
|
||||
temp = temps[0].get("TEMPS")
|
||||
if temp:
|
||||
if len(temp) > 0:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
offset = temp[0]["ID"]
|
||||
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
||||
for board in temp:
|
||||
_id = board["ID"] - offset
|
||||
chip_temp = round(board["Chip"])
|
||||
@@ -294,7 +326,7 @@ class BOSMiner(BaseMiner):
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
|
||||
if fans:
|
||||
fan_data = fans.get("FANS")
|
||||
fan_data = fans[0].get("FANS")
|
||||
if fan_data:
|
||||
for fan in range(self.fan_count):
|
||||
setattr(data, f"fan_{fan+1}", fan_data[fan]["RPM"])
|
||||
@@ -307,7 +339,7 @@ class BOSMiner(BaseMiner):
|
||||
pool_1_quota = 1
|
||||
pool_2_quota = 1
|
||||
quota = 0
|
||||
for pool in pools.get("POOLS"):
|
||||
for pool in pools[0].get("POOLS"):
|
||||
if not pool_1_user:
|
||||
pool_1_user = pool.get("User")
|
||||
pool_1 = pool["URL"]
|
||||
@@ -346,7 +378,7 @@ class BOSMiner(BaseMiner):
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if tunerstatus:
|
||||
tuner = tunerstatus.get("TUNERSTATUS")
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
if tuner:
|
||||
if len(tuner) > 0:
|
||||
wattage = tuner[0].get("PowerLimit")
|
||||
@@ -354,11 +386,11 @@ class BOSMiner(BaseMiner):
|
||||
data.wattage = wattage
|
||||
|
||||
if devdetails:
|
||||
boards = devdetails.get("DEVDETAILS")
|
||||
boards = devdetails[0].get("DEVDETAILS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
offset = boards[0]["ID"]
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
chips = board["Chips"]
|
||||
|
||||
@@ -41,7 +41,7 @@ class BTMiner(BaseMiner):
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
except APIError:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
logging.info(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
@@ -80,23 +80,41 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def get_mac(self):
|
||||
mac = ""
|
||||
data = await self.api.get_miner_info()
|
||||
data = await self.api.summary()
|
||||
if data:
|
||||
if "Msg" in data.keys():
|
||||
if "mac" in data["Msg"].keys():
|
||||
mac = data["Msg"]["mac"]
|
||||
if data.get("SUMMARY"):
|
||||
if len(data["SUMMARY"]) > 0:
|
||||
_mac = data["SUMMARY"][0].get("MAC")
|
||||
if _mac:
|
||||
mac = _mac
|
||||
if mac == "":
|
||||
try:
|
||||
data = await self.api.get_miner_info()
|
||||
if data:
|
||||
if "Msg" in data.keys():
|
||||
if "mac" in data["Msg"].keys():
|
||||
mac = data["Msg"]["mac"]
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
return str(mac).upper()
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
mac = None
|
||||
|
||||
try:
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
except APIError:
|
||||
logging.warning(f"Failed to get hostname and model: {self}")
|
||||
logging.info(f"Failed to get model: {self}")
|
||||
model = None
|
||||
data.model = "Whatsminer"
|
||||
|
||||
try:
|
||||
hostname = await self.get_hostname()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get hostname: {self}")
|
||||
hostname = None
|
||||
data.hostname = "Whatsminer"
|
||||
|
||||
@@ -105,6 +123,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
@@ -125,6 +144,9 @@ class BTMiner(BaseMiner):
|
||||
summary_data = summary.get("SUMMARY")
|
||||
if summary_data:
|
||||
if len(summary_data) > 0:
|
||||
if summary_data[0].get("MAC"):
|
||||
mac = summary_data[0]["MAC"]
|
||||
|
||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
||||
|
||||
@@ -208,4 +230,14 @@ class BTMiner(BaseMiner):
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if not mac:
|
||||
try:
|
||||
mac = await self.get_mac()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get mac: {self}")
|
||||
mac = None
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
return data
|
||||
|
||||
@@ -113,12 +113,17 @@ class CGMiner(BaseMiner):
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand("summary", "pools", "stats")
|
||||
|
||||
@@ -6,5 +6,23 @@ class M21S(BaseMiner):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M21SV60(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S V60"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M21SV20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S V20"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -2,5 +2,5 @@ from .M20S import M20S
|
||||
from .M20S_Plus import M20SPlus
|
||||
|
||||
from .M21 import M21
|
||||
from .M21S import M21S
|
||||
from .M21S import M21S, M21SV20, M21SV60
|
||||
from .M21S_Plus import M21SPlus
|
||||
|
||||
@@ -8,3 +8,39 @@ class M30S(BaseMiner):
|
||||
self.model = "M30S"
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SV50(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S V50"
|
||||
self.nominal_chips = 156
|
||||
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
|
||||
|
||||
@@ -8,3 +8,30 @@ class M30SPlus(BaseMiner):
|
||||
self.model = "M30S+"
|
||||
self.nominal_chips = 156
|
||||
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):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VE40"
|
||||
self.nominal_chips = 156
|
||||
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
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
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):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S++V30"
|
||||
self.model = "M30S++ V30"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -14,6 +23,6 @@ class M30SPlusPlusVG40(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S++V40"
|
||||
self.model = "M30S++ V40"
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -8,3 +8,12 @@ class M31SPlus(BaseMiner):
|
||||
self.model = "M31S+"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M31SPlusVE20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE20"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from .M30S import M30S
|
||||
from .M30S_Plus import M30SPlus
|
||||
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40
|
||||
from .M30S import M30S, M30SVE10, M30SVE20, M30SVG20, M30SV50
|
||||
from .M30S_Plus import M30SPlus, M30SPlusVG60, M30SPlusVE40, M30SPlusVF20
|
||||
from .M30S_Plus_Plus import M30SPlusPlus, M30SPlusPlusVG30, M30SPlusPlusVG40
|
||||
|
||||
from .M31S import M31S
|
||||
from .M31S_Plus import M31SPlus
|
||||
from .M31S_Plus import M31SPlus, M31SPlusVE20
|
||||
|
||||
from .M32S import M32S
|
||||
|
||||
@@ -1,8 +1,87 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
# TODO add config
|
||||
|
||||
|
||||
class BMMinerS17(BMMiner, S17):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerS17Plus(BMMiner, S17Plus):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerS17Pro(BMMiner, S17Pro):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerS17e(BMMiner, S17e):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerT17(BMMiner, T17):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerT17Plus(BMMiner, T17Plus):
|
||||
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
|
||||
|
||||
@@ -1,8 +1,84 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerT17e(BMMiner, T17e):
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerS19(BMMiner, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerS19Pro(BMMiner, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19a # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerS19a(BMMiner, S19a):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerS19j(BMMiner, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerS19jPro(BMMiner, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -1,11 +1,103 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
from config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerT19(BMMiner, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
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
|
||||
|
||||
@@ -28,6 +28,8 @@ from settings import (
|
||||
NETWORK_PING_TIMEOUT as PING_TIMEOUT,
|
||||
)
|
||||
|
||||
import asyncssh
|
||||
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -133,6 +135,8 @@ MINER_CLASSES = {
|
||||
"M21S": {
|
||||
"Default": BTMinerM21S,
|
||||
"BTMiner": BTMinerM21S,
|
||||
"60": BTMinerM21SV60,
|
||||
"20": BTMinerM21SV20,
|
||||
},
|
||||
"M21S+": {
|
||||
"Default": BTMinerM21SPlus,
|
||||
@@ -141,16 +145,23 @@ MINER_CLASSES = {
|
||||
"M30S": {
|
||||
"Default": BTMinerM30S,
|
||||
"BTMiner": BTMinerM30S,
|
||||
"50": BTMinerM30SV50,
|
||||
"G20": BTMinerM30SVG20,
|
||||
"E20": BTMinerM30SVE20,
|
||||
"E10": BTMinerM30SVE10,
|
||||
},
|
||||
"M30S+": {
|
||||
"Default": BTMinerM30SPlus,
|
||||
"BTMiner": BTMinerM30SPlus,
|
||||
"F20": BTMinerM30SPlusVF20,
|
||||
"E40": BTMinerM30SPlusVE40,
|
||||
"G60": BTMinerM30SPlusVG60,
|
||||
},
|
||||
"M30S++": {
|
||||
"Default": BTMinerM30SPlusPlusVG40,
|
||||
"BTMiner": BTMinerM30SPlusPlusVG40,
|
||||
"40": BTMinerM30SPlusPlusVG40,
|
||||
"30": BTMinerM30SPlusPlusVG30,
|
||||
"Default": BTMinerM30SPlusPlus,
|
||||
"BTMiner": BTMinerM30SPlusPlus,
|
||||
"G40": BTMinerM30SPlusPlusVG40,
|
||||
"G30": BTMinerM30SPlusPlusVG30,
|
||||
},
|
||||
"M31S": {
|
||||
"Default": BTMinerM31S,
|
||||
@@ -159,6 +170,7 @@ MINER_CLASSES = {
|
||||
"M31S+": {
|
||||
"Default": BTMinerM31SPlus,
|
||||
"BTMiner": BTMinerM31SPlus,
|
||||
"E20": BTMinerM31SPlusVE20,
|
||||
},
|
||||
"M32S": {
|
||||
"Default": BTMinerM32S,
|
||||
@@ -411,15 +423,30 @@ class MinerFactory(metaclass=Singleton):
|
||||
elif "am2-s17" in version["STATUS"][0]["Description"]:
|
||||
model = "Antminer S17"
|
||||
|
||||
# final try on a braiins OS bug with devdetails not returning
|
||||
else:
|
||||
async with asyncssh.connect(
|
||||
str(ip),
|
||||
known_hosts=None,
|
||||
username="root",
|
||||
password="admin",
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
) as conn:
|
||||
cfg = await conn.run("bosminer config --data")
|
||||
if cfg:
|
||||
cfg = json.loads(cfg.stdout)
|
||||
model = cfg.get("data").get("format").get("model")
|
||||
|
||||
if model:
|
||||
# whatsminer have a V in their version string (M20SV41), remove everything after it
|
||||
if "V" in model:
|
||||
ver = model.split("VG")[1]
|
||||
model = model.split("V")[0]
|
||||
_ver = model.split("V")
|
||||
if len(_ver) > 1:
|
||||
ver = model.split("V")[1]
|
||||
model = model.split("V")[0]
|
||||
# don't need "Bitmain", just "Antminer XX" as model
|
||||
if "Bitmain " in model:
|
||||
model = model.replace("Bitmain ", "")
|
||||
|
||||
return model, api, ver
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21S # 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):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM21SV60(BTMiner, M21SV60):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
@@ -2,5 +2,5 @@ from .M20S import BTMinerM20S
|
||||
from .M20S_Plus import BTMinerM20SPlus
|
||||
|
||||
from .M21 import BTMinerM21
|
||||
from .M21S import BTMinerM21S
|
||||
from .M21S import BTMinerM21S, BTMinerM21SV20, BTMinerM21SV60
|
||||
from .M21S_Plus import BTMinerM21SPlus
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30S # noqa - Ignore access to _module
|
||||
from miners._types import (
|
||||
M30S,
|
||||
M30SV50,
|
||||
M30SVG20,
|
||||
M30SVE20,
|
||||
M30SVE10,
|
||||
) # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30S(BTMiner, M30S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM30SV50(BTMiner, M30SV50):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(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
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30SPlus # noqa - Ignore access to _module
|
||||
from miners._types import (
|
||||
M30SPlus,
|
||||
M30SPlusVE40,
|
||||
M30SPlusVF20,
|
||||
M30SPlusVG60,
|
||||
) # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30SPlus(BTMiner, M30SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM30SPlusVE40(BTMiner, M30SPlusVE40):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(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
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import ( # noqa - Ignore access to _module
|
||||
M30SPlusPlus,
|
||||
M30SPlusPlusVG40,
|
||||
M30SPlusPlusVG30,
|
||||
)
|
||||
|
||||
|
||||
class BTMinerM30SPlusPlus(BTMiner, M30SPlusPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M31SPlus # noqa - Ignore access to _module
|
||||
from miners._types import M31SPlus, M31SPlusVE20 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31SPlus(BTMiner, M31SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM31SPlusVE20(BTMiner, M31SPlusVE20):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
from .M30S import BTMinerM30S
|
||||
from .M30S_Plus import BTMinerM30SPlus
|
||||
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30
|
||||
from .M30S import (
|
||||
BTMinerM30S,
|
||||
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_Plus import BTMinerM31SPlus
|
||||
from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20
|
||||
|
||||
from .M32S import BTMinerM32S
|
||||
|
||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -13,6 +13,10 @@ if (
|
||||
|
||||
|
||||
def main():
|
||||
from logger import init_logger
|
||||
|
||||
init_logger()
|
||||
|
||||
asyncio.run(ui())
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PySimpleGUI as sg
|
||||
from config.bos import bos_config_convert
|
||||
from config import MinerConfig
|
||||
import time
|
||||
from tools.cfg_util.layout import window, update_prog_bar
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
@@ -20,7 +20,8 @@ async def btn_import(table, selected):
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
await miner.get_config()
|
||||
config = miner.config
|
||||
window["cfg_config_txt"].update(config)
|
||||
if config:
|
||||
window["cfg_config_txt"].update(config.as_yaml())
|
||||
|
||||
|
||||
@disable_buttons("Configuring")
|
||||
@@ -109,7 +110,10 @@ def generate_config(username: str, workername: str, v2_allowed: bool):
|
||||
},
|
||||
"autotuning": {"enabled": True, "psu_power_limit": 900},
|
||||
}
|
||||
window["cfg_config_txt"].update(bos_config_convert(config))
|
||||
|
||||
cfg = MinerConfig().from_raw(config)
|
||||
|
||||
window["cfg_config_txt"].update(cfg.as_yaml())
|
||||
|
||||
|
||||
async def generate_config_ui():
|
||||
|
||||
@@ -4,29 +4,17 @@ import webbrowser
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
from tools.cfg_util.layout import TABLE_KEYS
|
||||
from tools.cfg_util.layout import window, update_prog_bar
|
||||
from tools.cfg_util.layout import window, update_prog_bar, TABLE_HEADERS
|
||||
from tools.cfg_util.tables import TableManager, DATA_HEADER_MAP
|
||||
|
||||
progress_bar_len = 0
|
||||
|
||||
DEFAULT_DATA = [
|
||||
"Model",
|
||||
"Hostname",
|
||||
"Hashrate",
|
||||
"Temperature",
|
||||
"Total Chips",
|
||||
"Nominal Chips",
|
||||
"Left Board",
|
||||
"Center Board",
|
||||
"Right Board",
|
||||
"Pool User",
|
||||
"Pool 1",
|
||||
"Pool 1 User",
|
||||
"Pool 2",
|
||||
"Pool 2 User",
|
||||
"Wattage",
|
||||
"Split",
|
||||
]
|
||||
headers = []
|
||||
for key in TABLE_HEADERS.keys():
|
||||
for item in TABLE_HEADERS[key]:
|
||||
headers.append(item)
|
||||
|
||||
DEFAULT_DATA = set(headers)
|
||||
|
||||
|
||||
def btn_all(table, selected):
|
||||
|
||||
@@ -167,6 +167,7 @@ BUTTON_KEYS = [
|
||||
"cfg_all",
|
||||
"cfg_web",
|
||||
"cmd_listen",
|
||||
"record",
|
||||
]
|
||||
|
||||
TABLE_HEIGHT = 27
|
||||
@@ -217,7 +218,7 @@ def get_scan_layout():
|
||||
scan_layout = [
|
||||
[
|
||||
sg.Text("Scan IP", background_color=MAIN_TABS_BG, pad=((0, 5), (1, 1))),
|
||||
sg.InputText(key="scan_ip", size=(31, 1)),
|
||||
sg.InputText(key="scan_ip", size=(31, 1), focus=True),
|
||||
sg.Button(
|
||||
"Scan",
|
||||
key="btn_scan",
|
||||
@@ -243,6 +244,7 @@ def get_scan_layout():
|
||||
key="scan_web",
|
||||
border_width=BTN_BORDER,
|
||||
),
|
||||
sg.Button("RECORD DATA", key="record", border_width=BTN_BORDER),
|
||||
sg.Button(
|
||||
"STOP LISTENING",
|
||||
key="scan_cancel_listen",
|
||||
|
||||
1
tools/cfg_util/record/__init__.py
Normal file
1
tools/cfg_util/record/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from tools.cfg_util.record.ui import record_ui
|
||||
39
tools/cfg_util/record/func.py
Normal file
39
tools/cfg_util/record/func.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from typing import List
|
||||
from tools.cfg_util.record.manager import RecordingManager
|
||||
import PySimpleGUI as sg
|
||||
|
||||
|
||||
async def start_recording(
|
||||
ips: List[str], file: str, record_window: sg.Window, interval: int = 10
|
||||
):
|
||||
record_window["start_recording"].update(visible=False)
|
||||
record_window["stop_recording"].update(visible=True)
|
||||
record_window["pause_recording"].update(visible=True)
|
||||
record_window["resume_recording"].update(visible=False)
|
||||
record_window["_placeholder"].update(visible=False)
|
||||
await RecordingManager().record(ips, file, record_window, interval=interval)
|
||||
|
||||
|
||||
async def pause_recording(record_window):
|
||||
await RecordingManager().pause()
|
||||
record_window["resume_recording"].update(visible=True)
|
||||
record_window["start_recording"].update(visible=False)
|
||||
record_window["stop_recording"].update(visible=True)
|
||||
record_window["pause_recording"].update(visible=False)
|
||||
|
||||
|
||||
async def stop_recording(record_window):
|
||||
await RecordingManager().stop()
|
||||
record_window["start_recording"].update(visible=True)
|
||||
record_window["stop_recording"].update(visible=False)
|
||||
record_window["pause_recording"].update(visible=False)
|
||||
record_window["resume_recording"].update(visible=False)
|
||||
record_window["_placeholder"].update(visible=True)
|
||||
|
||||
|
||||
async def resume_recording(record_window):
|
||||
await RecordingManager().resume()
|
||||
record_window["start_recording"].update(visible=False)
|
||||
record_window["stop_recording"].update(visible=True)
|
||||
record_window["pause_recording"].update(visible=True)
|
||||
record_window["resume_recording"].update(visible=False)
|
||||
117
tools/cfg_util/record/layout.py
Normal file
117
tools/cfg_util/record/layout.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
import PySimpleGUI as sg
|
||||
from PIL import Image
|
||||
|
||||
PAUSE_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTUTB1ciCQAAArVJREFUaN7dmktrU0EUx38Zal20leITF2mrBbuyirFiBQt1E7+AFvWbqO13EAqFgqB1IT52LoxQddcHtctKU62P2oUL+wBdpqmLnAvjNcnNnbn35k7+cCBkhpn53XmdMzMZolMvMApcBAaAPuAI0Cnpf4At4CuwBiwB74ENUqBTwARQBPYNbRUYF/DENQjMACULAL/tAa+AoSQATghAOUKAavZc6opFt4CdmAF02wbGogRoB6YTBPDblLTBSh1AoYkQnr0FDplCdAELKYDwbF5bykMNp0KKIPSeORgGZDqFEPqcaUg3Uwzh2Z1G9okdB0B2gZP1QJ44AOHZo1oQFxLYsaO0MnCuGshLhyA8e+Y1PqN5sZ8BZbjn7AOLwDcgCwzXKasMzAGbUu8lrR1hVQZOA9+9PyYsvspPIO+rYETiDH/eDeCqL29eyjCt/55eWNFinOZrfK1hn5tfkv+qKW8xPz/qkZ3p11gI6Po3Wt5CQN5Fi3ZkFXDNwqn8FJC+Zpg3rEYVkLMooBQivWRZVj3llBwUuK4BJcuX6+pXQHcLgHQrk2AlhepStIiUnAC6rt9KfHvXtauALy0Asq7Ez3JdRQV8sCigLUR6m2VZ9bSs5GjfVGeCdlzDvGH1zvuxGoMbfyWEG3/dwo1f0QsaTzCwGqkCYRNY3dVD3T5g3TLUXZIVsAe4HBDqzgM/xM8bsgh194B+PdQFeOHg4cPTanTnHTwOGqzVVY8dAnlYb8wdp3JTlHaIbeBY0AS64QDI7UZXg6kUQ0yGWdYOAK9TCDGLwX1ip6z3aYGYs4lmO1LSM7NU7jSt1N7kOTMpQz0yjSW8NG/JChqLDgMPxMeJc8eekT0tdp0lvkc1OZqgXuC+HO2bAqxQuePosWlIJkKoLP8/PDvKvw/PflF5eFYEliWy24yi8r9Buqx661BEjQAAAABJRU5ErkJggg=="
|
||||
RECORD_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9Ta0UqDlYQcchQnSwUFXGUKhbBQmkrtOpg8tIfoUlDkuLiKLgWHPxZrDq4OOvq4CoIgj8gjk5Oii5S4n1JoUWMFx7v47x7Du/dBwiNClPNrhigapaRTsTFXH5FDL6iGz4MIoaAxEw9mVnIwrO+7qmX6i7Ks7z7/qw+pWAywCcSzzLdsIjXiac3LZ3zPnGYlSWF+Jx43KALEj9yXXb5jXPJYYFnho1seo44TCyWOljuYFY2VOIp4oiiapQv5FxWOG9xVis11ronf2GooC1nuE5rBAksIokURMioYQMVWIjSrpFiIk3ncQ//sONPkUsm1wYYOeZRhQrJ8YP/we/ZmsXJCTcpFAcCL7b9MQoEd4Fm3ba/j227eQL4n4Erre2vNoCZT9LrbS1yBPRvAxfXbU3eAy53gKEnXTIkR/LTEopF4P2MvikPDNwCvavu3FrnOH0AsjSrpRvg4BAYK1H2mse7ezrn9m9Pa34/WbVynangsIwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfmBgEVMgjCc30iAAAC7klEQVRo3t2az2sTQRTHPztiFdqqNP5A0CZWUClYxejFg1CPnrVF/Qu8+A/4438QC4WCoBVBojcP9lDrQVBBe6wasdhIj01biUeb9bBv7DYmaXdmdrPrF96hKftmPjPz5r2dWQ93ygPDwFngOFAAckCP/P8XUAW+A1+BD8Br4Acp0BHgLlAGfEP7AtwR8MQ1BEwCvy0AGm0NeAGcSwLggADUHQI0s5K0FYuuAisxA4RtGRh1CdAFTCQI0Gjj0gcrdQNTHYTQ9grYZQrRC7xPAYS2d6GtPNJymkoRRHhmdkQBmUghRDhmtqSRFENou76VPLGSAZBV4GA7kMcZgND2sBXEmQQytkurA6d057eFQO4Dgy4z6QhwQYqnAWDObaXhAX3Ac/2HrmK/AcrW+w1JQIDvrfsPfghG0qtF2Xraqy5jVNEzchO4aDv6l4CdMjqNEDJqnieJ4DxwyH6WPGAJeKMbKwPHbCAKTWZgM/ngL4BXsoP5DAx68ma3kDSEY5h+Zbuk8i2WUYS14eXtY2VYAUWbwFYOIlaJLwsVlRwUGJfHrmTp64SS7ctUvkMWG18DCthtGuQ2sdEsVkbMH9+jTGc1F8OhgIXPXsV/IgXUTB6sxtAZC581Bfw0ebK0Xju5inTfIimuKmDestZxWc2aal7JgbLZfDqksPRVVsBH06fHpY52UYtblvWziuBo31gVy1jxwa/Yj8WMkr6UTT2UgtLZM4FxVPl+AhZ1Hnli40nD1CMuJwcQf/uud4qC7F5Ze9VdA44ClXBDz4DLrg8fcqFkV8K5nhJcd2zQ6QweBw21InyUIZAH7aZqP8FNUdohloF9m627KxkAuRYlaacVYizKbrAdeJlCiGkM7hN7CK670gLxFoOrN63ulMzMtItDm64Ox8yYLHVnGk14a67KDhqL+oB7UuPEmbEnJafFrpPE91FNkQ4oD9yWdwJTgDngFtDfqRf+Rh3m3w/P9rLxw7Mlgg/PysAsMAMsumj8D9eE1oYpe82nAAAAAElFTkSuQmCC"
|
||||
RESUME_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTUqWFKqAQAAApRJREFUaN7dms9LVFEUxz8+tBZp9NMISq0Wuskia9Gumr+ghUkF9X9k+gfUKihGhKCMiH4sgiglSltVULYrfEFF0aocNQwqcKYWnRev8c0w8+bc++71C2clnOOHN/fc+z33NqGnTuAwsB/oBrqAjUCr/P07UAA+AG+BF8AU8AkHtAMYBkLgd8qYAYYE3Lp6gTFgqQGA8igC94ADNgC2CEBJESApbkktIzoOzBsGiMccMKAJsAoYtQhQHnn5HxrSGmAiQ4goHgNr00K0Ac8dgIjiWayV1/VzmnAIIv5lVtcDMuogRHzN1KRjDkNEcbKWfWLeA5AFYGs1kGseQERxpRLEPgs7tmaUgD1JIHc8gojiZtIptqiQ+CNwH/hhCaQo9uGfhhWSFoD1kq8HmLYEMxgHCRUSPknYVM8pfelq8SYq2KGUcKpCEzkCfDYM0xEAOcMeZlK6y12DNQ4FQJ8FQ1YAjgKnxbtrqy+QQYEtjclw4pVy3u4A2GnZ74fAQeC8bGoa2gUwa3ixV1MO+KJQ+yvArwxBAPoVav8MWCEKgMUM6+eASwp5FgPgWwYA0a7/ENiskG+hGXhvuXP1ANfFNmjpXSDt0JZOAS+VIQDCZjmlmtY6YER7chjT9Eo5NG6Lis14fIx/HS865LGxOhMH6VK0ug8sWt2lcqsLcNvD4cONpEW518NxUG+lNnbVI5DL1fpxu9wUuQ4xV8vRpt8DkBO17pR5hyEu1rPltwDjDkI8SnOf2CrXXa5APE1z9Ra/DB135Eu0aZigfMZrokXzqDxguTUXpIMa0QbgguETbUkGee02nN5uzD2qsTHCXaZO4KyM9hvxE4Ni8FKrSRFqO8sfnm3i/4dns/x9eBaKX5kU99iw/gAVDZKvjiX0kgAAAABJRU5ErkJggg=="
|
||||
STOP_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTYF2K7EmwAAAohJREFUaN7dmk1r00EQh58sWsG2NviKaG21oCer2HpUqN9Bi/pNrO13EIoNBcHWg/hy82APVQ+KCtKj0ohVFEUUm0ZabzZ6yPxhjUmb7Ow/2c0P5pKQ2X2yu7M7s5vBn/qAEWAYOAb0A7uALvl+DVgGPgBvgVfAE+ATAegwMAHkgT+OtgiMC3jTNQjMAr8VAJW2DjwATjcDYJ8AlDwCVLO70lYqugispAxgWwEY9QnQAUw3EaDSpqQPKnUCcy2ESOwRsMMVoht4GQBEYi+sUN7QdJoLCMIemW2NgEwHCGGvmbp0IWCIxC7Xs0+sRABSBPZvBHIrAojEbtaCONWEHdunlYAT1UDuRwSR2J2k8xnrFPsOMIrNcw14CnyVw+RG2iJz/Kxsuq4qAUeAj8kHE8p/5jrQ49CRHiCnbHvMdphXQmilgXljZ3auTlYdR6JSWZmarv3oNcA5RQeeAT89gBTFl6tGDDCkcPDdY7rwTfHbYSOFAk3U8CWNr6NGwlfsGjCy0GJX1rgkKwGq29AmMhK/Y9eqkRgeu4oGeN8GIEtGaq+aqelzmrsqb4AFhYO9nsuxrlpol0PjwcTRosJJzgOIpvz02nY0rkxuco4nhKyHGtoVO9XtB5aUC+6XHMW/1JnqHgDOANsVba4DA3aqC3AvwuLD7Wp0JyMsBw3WGqqZiEBubLYvFCKAKAB7NltA5yMAuVRvNJgKGGKykbC2FXgYIMQ8DveJXZSvu0KBeK7JZjsDGZl5yneaKnW0eM1MylT3ptEmh+ZliaCpaCdwTc44ae7Ys55znZo6TnqPaoZogfqAq1La1+QTY8AhTUcyHqF6+f/h2W7+fXj2g/LDs7ykp4+Bzz4a/wvuXq9nlKOgSQAAAABJRU5ErkJggg=="
|
||||
|
||||
|
||||
def record_layout():
|
||||
buffer = BytesIO(base64.b64decode(sg.EMOJI_BASE64_HAPPY_BIG_SMILE))
|
||||
im1 = Image.open(buffer)
|
||||
with BytesIO() as output:
|
||||
im1.save(output, format="PNG")
|
||||
blank = output.getvalue()
|
||||
|
||||
im2 = Image.new("RGBA", (50, 50), "#ffffff00")
|
||||
with BytesIO() as output:
|
||||
im2.save(output, format="PNG")
|
||||
blank = output.getvalue()
|
||||
|
||||
record_layout = [
|
||||
[sg.Text("", key="record_status")],
|
||||
[
|
||||
sg.Text("Data Output File:"),
|
||||
sg.Input(key="record_file"),
|
||||
sg.SaveAs(
|
||||
"Select File",
|
||||
key="pick_record_file",
|
||||
file_types=(("PDF Files", "*.pdf"),),
|
||||
target="record_file",
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Spin(
|
||||
[i for i in range(5, 101, 5)],
|
||||
initial_value=10,
|
||||
size=(5, 1),
|
||||
key="record_interval",
|
||||
),
|
||||
sg.Text("Data Interval (seconds)"),
|
||||
],
|
||||
[
|
||||
sg.Push(),
|
||||
sg.pin(
|
||||
sg.Button(
|
||||
image_data=RECORD_BTN,
|
||||
button_color=(
|
||||
sg.theme_background_color(),
|
||||
sg.theme_background_color(),
|
||||
),
|
||||
tooltip="Start Recording",
|
||||
border_width=0,
|
||||
key="start_recording",
|
||||
)
|
||||
),
|
||||
sg.pin(
|
||||
sg.Button(
|
||||
image_data=blank,
|
||||
button_color=(
|
||||
sg.theme_background_color(),
|
||||
sg.theme_background_color(),
|
||||
),
|
||||
border_width=0,
|
||||
key="_placeholder",
|
||||
)
|
||||
),
|
||||
sg.pin(
|
||||
sg.Button(
|
||||
image_data=STOP_BTN,
|
||||
tooltip="Stop Recording",
|
||||
button_color=(
|
||||
sg.theme_background_color(),
|
||||
sg.theme_background_color(),
|
||||
),
|
||||
border_width=0,
|
||||
visible=False,
|
||||
key="stop_recording",
|
||||
)
|
||||
),
|
||||
sg.pin(
|
||||
sg.Button(
|
||||
image_data=PAUSE_BTN,
|
||||
tooltip="Pause Recording",
|
||||
button_color=(
|
||||
sg.theme_background_color(),
|
||||
sg.theme_background_color(),
|
||||
),
|
||||
border_width=0,
|
||||
visible=False,
|
||||
key="pause_recording",
|
||||
)
|
||||
),
|
||||
sg.pin(
|
||||
sg.Button(
|
||||
image_data=RESUME_BTN,
|
||||
tooltip="Resume Recording",
|
||||
button_color=(
|
||||
sg.theme_background_color(),
|
||||
sg.theme_background_color(),
|
||||
),
|
||||
border_width=0,
|
||||
visible=False,
|
||||
key="resume_recording",
|
||||
)
|
||||
),
|
||||
sg.Push(),
|
||||
],
|
||||
]
|
||||
return record_layout
|
||||
|
||||
|
||||
def get_record_window():
|
||||
return sg.Window("Record Miner Data", record_layout(), modal=True)
|
||||
96
tools/cfg_util/record/manager.py
Normal file
96
tools/cfg_util/record/manager.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import asyncio
|
||||
|
||||
from tools.cfg_util.record.pdf import generate_pdf
|
||||
|
||||
from miners.miner_factory import MinerFactory
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
(RECORDING, PAUSING, PAUSED, RESUMING, STOPPING, DONE) = range(6)
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class RecordingManager(metaclass=Singleton):
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.state = DONE
|
||||
self.data: Dict[str:list] = {}
|
||||
self.miners = []
|
||||
self.output_file = None
|
||||
self.interval: int = 10
|
||||
self.record_window = None
|
||||
|
||||
async def _check_pause(self):
|
||||
if self.state == PAUSING:
|
||||
self.state = PAUSED
|
||||
self.record_window["record_status"].update("Paused.")
|
||||
while not self.state == RESUMING and not self.state == STOPPING:
|
||||
await asyncio.sleep(0.1)
|
||||
self.record_window["record_status"].update("Recording...")
|
||||
|
||||
async def _record_loop(self):
|
||||
while True:
|
||||
await self._check_pause()
|
||||
|
||||
if self.state == STOPPING:
|
||||
break
|
||||
|
||||
tasks = []
|
||||
for miner in self.miners:
|
||||
tasks.append(miner.get_data())
|
||||
|
||||
for complete in asyncio.as_completed(tasks):
|
||||
data = await complete
|
||||
self.data[data.ip].append(data)
|
||||
for i in range(self.interval * 10):
|
||||
await self._check_pause()
|
||||
if self.state == STOPPING:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
self.state = DONE
|
||||
self.record_window["record_status"].update(
|
||||
"Writing to file (this could take a minute)..."
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
await asyncio.create_task(self.write_output())
|
||||
self.record_window["record_status"].update("")
|
||||
|
||||
async def write_output(self):
|
||||
await generate_pdf(self.data, self.output_file)
|
||||
|
||||
async def record(
|
||||
self, ips: List[str], output_file: str, record_window, interval: int = 10
|
||||
):
|
||||
self.record_window = record_window
|
||||
for ip in ips:
|
||||
self.data[ip] = []
|
||||
self.output_file = output_file
|
||||
self.interval = interval
|
||||
self.state = RECORDING
|
||||
self.record_window["record_status"].update("Recording...")
|
||||
async for miner in MinerFactory().get_miner_generator(ips):
|
||||
self.miners.append(miner)
|
||||
|
||||
asyncio.create_task(self._record_loop())
|
||||
|
||||
async def pause(self):
|
||||
self.state = PAUSING
|
||||
self.record_window["record_status"].update("Pausing...")
|
||||
|
||||
async def resume(self):
|
||||
self.state = RESUMING
|
||||
self.record_window["record_status"].update("Resuming...")
|
||||
|
||||
async def stop(self):
|
||||
self.state = STOPPING
|
||||
self.record_window["record_status"].update("Stopping...")
|
||||
174
tools/cfg_util/record/pdf.py
Normal file
174
tools/cfg_util/record/pdf.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from io import BytesIO
|
||||
from typing import List, Dict
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.dates import DateFormatter
|
||||
from matplotlib.ticker import MultipleLocator
|
||||
from reportlab.lib.pagesizes import letter, inch
|
||||
from reportlab.lib.styles import (
|
||||
ParagraphStyle,
|
||||
TA_CENTER, # noqa - not declared in __all__
|
||||
)
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate,
|
||||
KeepInFrame,
|
||||
Image,
|
||||
Paragraph,
|
||||
PageBreak,
|
||||
Spacer,
|
||||
)
|
||||
from svglib.svglib import svg2rlg
|
||||
|
||||
from data import MinerData
|
||||
|
||||
|
||||
async def generate_pdf(data: Dict[str, List[MinerData]], file_loc):
|
||||
doc = SimpleDocTemplate(
|
||||
file_loc,
|
||||
pagesize=letter,
|
||||
topMargin=0.25 * inch,
|
||||
leftMargin=1 * inch,
|
||||
rightMargin=1 * inch,
|
||||
bottomMargin=1 * inch,
|
||||
title=f"Recorded Data",
|
||||
)
|
||||
|
||||
elements = []
|
||||
i = 0
|
||||
for item in data.keys():
|
||||
i += 1
|
||||
if not i == 1:
|
||||
elements.append(PageBreak())
|
||||
page_elem = await generate_page(data[item])
|
||||
for elem in page_elem:
|
||||
elements.append(elem)
|
||||
|
||||
doc.build(
|
||||
elements,
|
||||
)
|
||||
|
||||
|
||||
async def generate_page(data):
|
||||
title_style = ParagraphStyle(
|
||||
"Title",
|
||||
alignment=TA_CENTER,
|
||||
fontSize=25,
|
||||
spaceAfter=40,
|
||||
spaceBefore=150,
|
||||
fontName="Helvetica-Bold",
|
||||
)
|
||||
|
||||
hr_graph = create_hr_graph(data)
|
||||
fan_graph = create_fans_graph(data)
|
||||
temp_graph = create_temp_graph(data)
|
||||
title = Paragraph(data[0].ip, style=title_style)
|
||||
|
||||
elements = [
|
||||
title,
|
||||
await hr_graph,
|
||||
Spacer(0, 40),
|
||||
await temp_graph,
|
||||
Spacer(0, 40),
|
||||
await fan_graph,
|
||||
]
|
||||
return elements
|
||||
|
||||
|
||||
async def create_hr_graph(data):
|
||||
fig, ax = plt.subplots(figsize=(6, 2))
|
||||
xpoints = []
|
||||
ypoints = []
|
||||
for item in data:
|
||||
xpoints.append(item.datetime)
|
||||
ypoints.append(item.hashrate)
|
||||
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
||||
label.set_fontsize(6)
|
||||
ax.plot(xpoints, ypoints)
|
||||
ylim = max(ypoints) * 1.4
|
||||
if ylim == 0:
|
||||
ylim = 10
|
||||
ax.set_ylim(0, ylim)
|
||||
date_form = DateFormatter("%H:%M:%S")
|
||||
ax.xaxis.set_major_formatter(date_form)
|
||||
ax.yaxis.set_major_formatter("{x:1.1f} TH/s")
|
||||
ax.set_title("Hashrate", fontsize=15)
|
||||
ax.yaxis.set_major_locator(MultipleLocator(5))
|
||||
ax.yaxis.set_minor_locator(MultipleLocator(1))
|
||||
|
||||
imgdata = BytesIO()
|
||||
fig.savefig(imgdata, format="svg")
|
||||
imgdata.seek(0) # rewind the data
|
||||
drawing = svg2rlg(imgdata)
|
||||
imgdata.close()
|
||||
plt.close("all")
|
||||
|
||||
hr_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
|
||||
|
||||
return hr_graph
|
||||
|
||||
|
||||
async def create_fans_graph(data):
|
||||
fig, ax = plt.subplots(figsize=(6, 2))
|
||||
xpoints = []
|
||||
ypoints_f1 = []
|
||||
ypoints_f2 = []
|
||||
ypoints_f3 = []
|
||||
ypoints_f4 = []
|
||||
for item in data:
|
||||
xpoints.append(item.datetime)
|
||||
ypoints_f1.append(item.fan_1)
|
||||
ypoints_f2.append(item.fan_2)
|
||||
ypoints_f3.append(item.fan_3)
|
||||
ypoints_f4.append(item.fan_4)
|
||||
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
||||
label.set_fontsize(6)
|
||||
for ypoints in [ypoints_f1, ypoints_f2, ypoints_f3, ypoints_f4]:
|
||||
if not ypoints == [-1 for x in range(len(ypoints))]:
|
||||
ax.plot(xpoints, ypoints)
|
||||
ax.set_ylim(0, 10000)
|
||||
date_form = DateFormatter("%H:%M:%S")
|
||||
ax.xaxis.set_major_formatter(date_form)
|
||||
ax.yaxis.set_major_formatter("{x:1.0f} RPM")
|
||||
ax.set_title("Fans", fontsize=15)
|
||||
|
||||
imgdata = BytesIO()
|
||||
fig.savefig(imgdata, format="svg")
|
||||
imgdata.seek(0) # rewind the data
|
||||
drawing = svg2rlg(imgdata)
|
||||
imgdata.close()
|
||||
plt.close("all")
|
||||
|
||||
fans_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
|
||||
|
||||
return fans_graph
|
||||
|
||||
|
||||
async def create_temp_graph(data):
|
||||
fig, ax = plt.subplots(figsize=(6, 2))
|
||||
# plt.figure()
|
||||
xpoints = []
|
||||
ypoints = []
|
||||
for item in data:
|
||||
xpoints.append(item.datetime)
|
||||
ypoints.append(item.temperature_avg)
|
||||
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
||||
label.set_fontsize(6)
|
||||
ax.plot(xpoints, ypoints)
|
||||
ax.set_ylim(0, 130)
|
||||
ax.yaxis.set_major_locator(MultipleLocator(20))
|
||||
ax.yaxis.set_minor_locator(MultipleLocator(5))
|
||||
date_form = DateFormatter("%H:%M:%S")
|
||||
ax.xaxis.set_major_formatter(date_form)
|
||||
ax.yaxis.set_major_formatter("{x:1.1f} C")
|
||||
ax.set_title("Temperature", fontsize=15)
|
||||
|
||||
imgdata = BytesIO()
|
||||
fig.savefig(imgdata, format="svg")
|
||||
imgdata.seek(0) # rewind the data
|
||||
drawing = svg2rlg(imgdata)
|
||||
imgdata.close()
|
||||
plt.close("all")
|
||||
|
||||
temp_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
|
||||
|
||||
return temp_graph
|
||||
40
tools/cfg_util/record/ui.py
Normal file
40
tools/cfg_util/record/ui.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import asyncio
|
||||
|
||||
import PySimpleGUI as sg
|
||||
from tools.cfg_util.record.layout import get_record_window
|
||||
from tools.cfg_util.record.func import (
|
||||
start_recording,
|
||||
stop_recording,
|
||||
pause_recording,
|
||||
resume_recording,
|
||||
)
|
||||
|
||||
|
||||
async def record_ui(ips: list):
|
||||
if not len(ips) > 0:
|
||||
return
|
||||
record_window = get_record_window()
|
||||
while True:
|
||||
event, values = record_window.read(0.001)
|
||||
if event in (None, "Close", sg.WIN_CLOSED):
|
||||
break
|
||||
|
||||
if event == "start_recording":
|
||||
if values["record_file"]:
|
||||
asyncio.create_task(
|
||||
start_recording(
|
||||
ips,
|
||||
values["record_file"],
|
||||
record_window,
|
||||
interval=int(values["record_interval"]),
|
||||
)
|
||||
)
|
||||
if event == "stop_recording":
|
||||
asyncio.create_task(stop_recording(record_window))
|
||||
if event == "resume_recording":
|
||||
asyncio.create_task(resume_recording(record_window))
|
||||
if event == "pause_recording":
|
||||
asyncio.create_task(pause_recording(record_window))
|
||||
|
||||
if event == "__TIMEOUT__":
|
||||
await asyncio.sleep(0.001)
|
||||
@@ -209,6 +209,10 @@ class TableManager(metaclass=Singleton):
|
||||
return ipaddress.ip_address(self.data[data_key]["IP"])
|
||||
|
||||
if self.sort_key == "Chip %":
|
||||
if self.data[data_key]["Chip %"] == "":
|
||||
return 0
|
||||
if isinstance(self.data[data_key]["Chip %"], int):
|
||||
return self.data[data_key]["Chip %"]
|
||||
return int((self.data[data_key]["Chip %"]).replace("%", ""))
|
||||
|
||||
if self.sort_key == "Hashrate":
|
||||
@@ -225,9 +229,9 @@ class TableManager(metaclass=Singleton):
|
||||
"Temp",
|
||||
"Total",
|
||||
"Ideal",
|
||||
"Left Chips",
|
||||
"Center Chips",
|
||||
"Right Chips",
|
||||
"Left Board",
|
||||
"Center Board",
|
||||
"Right Board",
|
||||
]:
|
||||
if isinstance(self.data[data_key][self.sort_key], str):
|
||||
return -300
|
||||
|
||||
@@ -17,6 +17,7 @@ from tools.cfg_util.configure import (
|
||||
btn_import,
|
||||
btn_config,
|
||||
)
|
||||
from tools.cfg_util.record import record_ui
|
||||
from tools.cfg_util.layout import window, TABLE_KEYS
|
||||
from tools.cfg_util.general import btn_all, btn_web, btn_refresh
|
||||
from tools.cfg_util.tables import TableManager
|
||||
@@ -102,7 +103,7 @@ async def ui():
|
||||
window["cmd_table"].Widget.column("#0", stretch=tk.NO, anchor=tk.CENTER)
|
||||
|
||||
while True:
|
||||
event, value = window.read(0)
|
||||
event, value = window.read(0.001)
|
||||
if event in (None, "Close", sg.WIN_CLOSED):
|
||||
sys.exit()
|
||||
|
||||
@@ -125,6 +126,16 @@ async def ui():
|
||||
asyncio.create_task(btn_refresh(_table, value[_table]))
|
||||
if event == "btn_scan":
|
||||
asyncio.create_task(btn_scan(value["scan_ip"]))
|
||||
if event == "record":
|
||||
_table = "scan_table"
|
||||
if value[_table]:
|
||||
ips = [window[_table].Values[row][0] for row in value[_table]]
|
||||
else:
|
||||
ips = [
|
||||
window[_table].Values[row][0]
|
||||
for row in range(len(window[_table].Values))
|
||||
]
|
||||
asyncio.create_task(record_ui(ips))
|
||||
|
||||
# boards tab
|
||||
if event == "boards_all":
|
||||
@@ -202,7 +213,7 @@ async def ui():
|
||||
asyncio.create_task(btn_cancel_listen())
|
||||
|
||||
if event == "__TIMEOUT__":
|
||||
await asyncio.sleep(0)
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -229,6 +229,10 @@ class TestbenchMiner:
|
||||
if "ERROR:Auth" in stdout_data:
|
||||
error = "AUTH"
|
||||
proc.kill()
|
||||
if "INFO:Remote target is already running Braiins OS" in stdout_data:
|
||||
proc.kill()
|
||||
self.state = UPDATE
|
||||
return
|
||||
await self.add_to_output(stdout_data)
|
||||
if stdout == b"":
|
||||
break
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import aiohttp
|
||||
import httpx
|
||||
import shutil
|
||||
import aiofiles
|
||||
import asyncio
|
||||
@@ -11,8 +12,8 @@ import logging
|
||||
async def get_latest_version(session):
|
||||
feeds_url = "http://feeds.braiins-os.com"
|
||||
|
||||
async with session.get(feeds_url) as resp:
|
||||
data = await resp.read()
|
||||
resp = await session.get(feeds_url)
|
||||
data = resp.text
|
||||
|
||||
soup = BeautifulSoup(data, "html.parser")
|
||||
|
||||
@@ -32,8 +33,8 @@ async def get_latest_version(session):
|
||||
async def get_feeds_file(session, version):
|
||||
feeds_url = "http://feeds.braiins-os.com"
|
||||
|
||||
async with session.get(feeds_url + "/" + version) as resp:
|
||||
data = await resp.read()
|
||||
resp = await session.get(feeds_url + "/" + version, follow_redirects=True)
|
||||
data = resp.text
|
||||
|
||||
soup = BeautifulSoup(data, "html.parser")
|
||||
|
||||
@@ -52,8 +53,8 @@ async def get_feeds_file(session, version):
|
||||
async def get_update_file(session, version):
|
||||
feeds_url = "http://feeds.braiins-os.com"
|
||||
|
||||
async with session.get(feeds_url + "/am1-s9") as resp:
|
||||
data = await resp.read()
|
||||
resp = await session.get(feeds_url + "/am1-s9")
|
||||
data = resp.text
|
||||
|
||||
soup = BeautifulSoup(data, "html.parser")
|
||||
|
||||
@@ -77,14 +78,14 @@ async def get_latest_update_file(session, update_file):
|
||||
if os.path.exists(update_file_dir):
|
||||
os.remove(update_file_dir)
|
||||
|
||||
async with session.get(update_file_loc) as update_file_data:
|
||||
if update_file_data.status == 200:
|
||||
f = await aiofiles.open(
|
||||
os.path.join(os.path.dirname(__file__), "files", "update.tar"),
|
||||
mode="wb",
|
||||
)
|
||||
await f.write(await update_file_data.read())
|
||||
await f.close()
|
||||
update_file_data = await session.get(update_file_loc)
|
||||
if update_file_data.status_code == 200:
|
||||
f = await aiofiles.open(
|
||||
os.path.join(os.path.dirname(__file__), "files", "update.tar"),
|
||||
mode="wb",
|
||||
)
|
||||
await f.write(update_file_data.text)
|
||||
await f.close()
|
||||
|
||||
|
||||
async def get_latest_install_file(session, version, feeds_path, install_file):
|
||||
@@ -99,13 +100,14 @@ async def get_latest_install_file(session, version, feeds_path, install_file):
|
||||
shutil.rmtree(install_file_folder)
|
||||
os.mkdir(install_file_folder)
|
||||
|
||||
async with session.get(install_file_loc) as install_file_data:
|
||||
if install_file_data.status == 200:
|
||||
f = await aiofiles.open(
|
||||
os.path.join(install_file_folder, install_file), mode="wb"
|
||||
)
|
||||
await f.write(await install_file_data.read())
|
||||
await f.close()
|
||||
install_file_data = await session.get(install_file_loc)
|
||||
if install_file_data.status_code == 200:
|
||||
f = await aiofiles.open(
|
||||
os.path.join(install_file_folder, install_file), mode="wb"
|
||||
)
|
||||
for chunk in install_file_data.iter_bytes():
|
||||
await f.write(chunk)
|
||||
await f.close()
|
||||
|
||||
|
||||
async def update_installer_files():
|
||||
@@ -113,7 +115,7 @@ async def update_installer_files():
|
||||
os.path.dirname(__file__), "files", "bos-toolbox", "feeds"
|
||||
)
|
||||
feeds_versions = await get_local_versions()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient() as session:
|
||||
version = await get_latest_version(session)
|
||||
|
||||
if version not in feeds_versions:
|
||||
@@ -148,4 +150,4 @@ async def get_local_versions():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.get_event_loop().run_until_complete(update_installer_files())
|
||||
asyncio.run(update_installer_files())
|
||||
|
||||
Reference in New Issue
Block a user