Compare commits
44 Commits
v0.8.1
...
v0.9.1-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fb4237e51 | ||
|
|
eeffdecde1 | ||
|
|
477a411c87 | ||
|
|
ca77573624 | ||
|
|
3ec147990b | ||
|
|
082240bdb6 | ||
|
|
7a7fc2c5a6 | ||
|
|
dcc3e07998 | ||
|
|
5261b00aad | ||
|
|
f18d37a19e | ||
|
|
7c3af3da41 | ||
|
|
8948af55f2 | ||
|
|
dd8fe41ad1 | ||
|
|
198eedcd43 | ||
|
|
f7309decdb | ||
|
|
078579d8e1 | ||
|
|
39eeb13409 | ||
|
|
dfccd67ccb | ||
|
|
10949225c0 | ||
|
|
3a60a3584a | ||
|
|
480aab550c | ||
|
|
fa83e61249 | ||
|
|
2f3411e12d | ||
|
|
3e7311687e | ||
|
|
bc2d549ce5 | ||
|
|
3d31d89c9e | ||
|
|
15fc27e6fa | ||
|
|
943ebc77a1 | ||
|
|
733437ef03 | ||
|
|
b444245e98 | ||
|
|
481d31a0f1 | ||
|
|
264db3bdd6 | ||
|
|
d292b9c195 | ||
|
|
dce25a679f | ||
|
|
c903631742 | ||
|
|
e70bfdc886 | ||
|
|
8e1803add1 | ||
|
|
7d61056ea3 | ||
|
|
0d497baa45 | ||
|
|
d3a71c5a93 | ||
|
|
895a5b7ac8 | ||
|
|
7a5a0b287c | ||
|
|
c7d73276c8 | ||
|
|
4bbb9d0b08 |
22
.github/workflows/python-publish.yml
vendored
Normal file
22
.github/workflows/python-publish.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
- 'docsrc/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish GH release
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
- name: Build using poetry and publish to PyPi
|
||||
uses: JRubics/poetry-publish@v1.11
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
venv/
|
||||
build/
|
||||
dist/
|
||||
__pycache__/
|
||||
pyvenv.cfg
|
||||
.env/
|
||||
bin/
|
||||
lib/
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,13 +0,0 @@
|
||||
FROM python:3.10-slim-buster
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
WORKDIR /minerInterface-web_monitor
|
||||
|
||||
COPY tools/web_monitor/requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "tools.web_monitor.app:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
66
README.md
66
README.md
@@ -1,27 +1,15 @@
|
||||
# minerInterface
|
||||
# pyasic
|
||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
|
||||
## Usage
|
||||
To use this repo, first download it, create a virtual environment, enter the virtual environment, and install relevant packages by navigating to this directory and running ```pip install -r requirements.txt``` on Windows or ```pip3 install -r requirements.txt``` on Mac or UNIX if the first command fails.
|
||||
|
||||
You can also use poetry by initializing and running ```poetry install```
|
||||
|
||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of the GUI applications here -> (https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing)
|
||||
|
||||
### CFG Util
|
||||
*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 -
|
||||
|
||||
```python
|
||||
from tools.cfg_util import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
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.
|
||||
|
||||
### Interfacing with miners programmatically
|
||||
<br>
|
||||
|
||||
@@ -47,8 +35,7 @@ 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
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
|
||||
async def get_hashrate():
|
||||
@@ -60,18 +47,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__':
|
||||
@@ -83,8 +63,7 @@ You can also create your own miner without scanning if you know the IP:
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
from pyasic.miners.miner_factory import Mine~~~~rFactory
|
||||
|
||||
|
||||
async def get_miner_hashrate(ip: str):
|
||||
@@ -95,11 +74,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,11 +89,12 @@ 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
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_pool_data(ip: str):
|
||||
@@ -128,7 +106,7 @@ async def get_miner_pool_data(ip: str):
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the data
|
||||
data = await miner.get_data()
|
||||
|
||||
|
||||
print(data)
|
||||
|
||||
|
||||
@@ -144,7 +122,7 @@ if __name__ == '__main__':
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_pool_data(ip: str):
|
||||
@@ -183,7 +161,7 @@ A pretty good example of really trying to make this robust is in ```cfg_util.fun
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_temperature_data(ip: str):
|
||||
@@ -212,7 +190,7 @@ How about data on the power usage of the miner? This one only works for Whatsmi
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_power_data(ip: str):
|
||||
@@ -237,7 +215,7 @@ async def get_miner_power_data(ip: str):
|
||||
data = summary['SUMMARY'][0]["Power"]
|
||||
|
||||
if data:
|
||||
print(data)
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -253,7 +231,7 @@ How about we get the current pool user and hashrate in 1 command?
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
"""
|
||||
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))
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"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))
|
||||
}
|
||||
}
|
||||
"""
|
||||
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,4 +0,0 @@
|
||||
from tools.cfg_util import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -1,18 +0,0 @@
|
||||
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()
|
||||
|
||||
if DEBUG:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
@@ -1,46 +0,0 @@
|
||||
"""
|
||||
Make a build of the config tool.
|
||||
|
||||
Usage: make_config_tool.py build
|
||||
|
||||
The build will show up in the build directory.
|
||||
"""
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
base = None
|
||||
if sys.platform == "win32":
|
||||
base = "Win32GUI"
|
||||
|
||||
version = datetime.datetime.now()
|
||||
version = version.strftime("%y.%m.%d")
|
||||
print(version)
|
||||
|
||||
|
||||
setup(
|
||||
name="UpstreamCFGUtil.exe",
|
||||
version=version,
|
||||
description="Upstream Data Config Utility Build",
|
||||
options={
|
||||
"build_exe": {
|
||||
"build_exe": f"{os.getcwd()}\\build\\UpstreamCFGUtil-{version}-{sys.platform}\\",
|
||||
"include_files": [
|
||||
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"),
|
||||
],
|
||||
},
|
||||
},
|
||||
executables=[
|
||||
Executable(
|
||||
"config_tool.py",
|
||||
base=base,
|
||||
icon="icon.ico",
|
||||
target_name="UpstreamCFGUtil.exe",
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon821(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 821"
|
||||
self.fan_count = 2
|
||||
@@ -1,9 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon841(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 841"
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M21S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M30S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S"
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M30SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+"
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M31SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S+"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
@@ -1,8 +0,0 @@
|
||||
from .M30S import M30S
|
||||
from .M30S_Plus import M30SPlus
|
||||
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40
|
||||
|
||||
from .M31S import M31S
|
||||
from .M31S_Plus import M31SPlus
|
||||
|
||||
from .M32S import M32S
|
||||
@@ -1,71 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
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 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,71 +0,0 @@
|
||||
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 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,71 +0,0 @@
|
||||
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 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,71 +0,0 @@
|
||||
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 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,71 +0,0 @@
|
||||
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 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,71 +0,0 @@
|
||||
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 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,57 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
import json
|
||||
|
||||
|
||||
class BMMinerS19(BMMiner, S19):
|
||||
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 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,57 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
import json
|
||||
|
||||
|
||||
class BMMinerS19Pro(BMMiner, S19Pro):
|
||||
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 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,57 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19a # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
import json
|
||||
|
||||
|
||||
class BMMinerS19a(BMMiner, S19a):
|
||||
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 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,57 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
import json
|
||||
|
||||
|
||||
class BMMinerS19j(BMMiner, S19j):
|
||||
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 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,57 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
import json
|
||||
|
||||
|
||||
class BMMinerS19jPro(BMMiner, S19jPro):
|
||||
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 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,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9(BMMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9i # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9i(BMMiner, S9i):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT9(BMMiner, T9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17(BOSMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17e(BOSMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17(BOSMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17e(BOSMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19(BOSMiner, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19j(BOSMiner, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT19(BOSMiner, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS9(BOSMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17(CGMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17Plus(CGMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17Pro(CGMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17e(CGMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17(CGMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17Plus(CGMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17e(CGMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS9(CGMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import Hiveon # noqa - Ignore access to _module
|
||||
from miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class HiveonT9(Hiveon, T9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon1047 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon1047(CGMiner, Avalon1047):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon1066 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon1066(CGMiner, Avalon1066):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon821 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon821(CGMiner, Avalon821):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon841 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon841(CGMiner, Avalon841):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M20S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM20S(BTMiner, M20S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M20SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM20SPlus(BTMiner, M20SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21(BTMiner, M21):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21S(BTMiner, M21S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21SPlus(BTMiner, M21SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30S(BTMiner, M30S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30SPlus(BTMiner, M30SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M31S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31S(BTMiner, M31S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M31SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31SPlus(BTMiner, M31SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M32S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM32S(BTMiner, M32S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from .M30S import BTMinerM30S
|
||||
from .M30S_Plus import BTMinerM30SPlus
|
||||
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30
|
||||
|
||||
from .M31S import BTMinerM31S
|
||||
from .M31S_Plus import BTMinerM31SPlus
|
||||
|
||||
from .M32S import BTMinerM32S
|
||||
396
poetry.lock
generated
Normal file
396
poetry.lock
generated
Normal file
@@ -0,0 +1,396 @@
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
|
||||
[[package]]
|
||||
name = "asyncssh"
|
||||
version = "2.11.0"
|
||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">= 3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=3.1"
|
||||
typing-extensions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
bcrypt = ["bcrypt (>=3.1.3)"]
|
||||
fido2 = ["fido2 (>=0.9.2)"]
|
||||
gssapi = ["gssapi (>=1.2.0)"]
|
||||
libnacl = ["libnacl (>=1.4.2)"]
|
||||
pkcs11 = ["python-pkcs11 (>=0.7.0)"]
|
||||
pyopenssl = ["pyOpenSSL (>=17.0.0)"]
|
||||
pywin32 = ["pywin32 (>=227)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.15.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "37.0.4"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.12"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
sdist = ["setuptools_rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.15.0"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0,<4.0.0"
|
||||
certifi = "*"
|
||||
h11 = ">=0.11,<0.13"
|
||||
sniffio = ">=1.0.0,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.23.0"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
httpcore = ">=0.15.0,<0.16.0"
|
||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi", "brotli"]
|
||||
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "pyaml"
|
||||
version = "21.10.1"
|
||||
description = "PyYAML-based module to produce pretty and readable YAML-serialized data"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
PyYAML = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
description = "Validating URI References per RFC 3986"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||
|
||||
[package.extras]
|
||||
idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8d93eafd928d7fed4b0a00d13e46982c2d4310c37acb2faec7e7a477b3f35e9c"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
]
|
||||
asyncssh = [
|
||||
{file = "asyncssh-2.11.0-py3-none-any.whl", hash = "sha256:7302348cbd54c58d3259da17f13e77912de1b005e366b15c8b183d948c8a91a8"},
|
||||
{file = "asyncssh-2.11.0.tar.gz", hash = "sha256:59c36ce77ba9dda8dd57ad875776e7105ddb1fa851bc039bb3aeadeac4f67b56"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
||||
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
|
||||
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
passlib = [
|
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||
]
|
||||
pyaml = [
|
||||
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
|
||||
{file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
]
|
||||
@@ -171,7 +171,10 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
if type(data["STATUS"]) == str:
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
return True, None
|
||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
@@ -197,8 +200,12 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
str_data = str_data.replace("}{", "},{")
|
||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("[,{", "[{")
|
||||
# fix an error with a btminer return having a specific comma that breaks json.loads()
|
||||
# fix an error with Avalonminers returning inf and nan
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
# parse the json
|
||||
parsed_data = json.loads(str_data)
|
||||
# handle bad json
|
||||
@@ -1,4 +1,4 @@
|
||||
from API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class BMMinerAPI(BaseMinerAPI):
|
||||
@@ -126,7 +126,7 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
:return: A confirmation of adding the pool.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url}, " f"{username}, " f"{password}"
|
||||
"addpool", parameters=f"{url},{username},{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
@@ -147,7 +147,7 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
|
||||
:return: A confirmation of setting pool quota.
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}")
|
||||
return await self.send_command("poolquota", parameters=f"{n},{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
@@ -326,7 +326,7 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
|
||||
:return: The results of setting config of name to n.
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}")
|
||||
return await self.send_command("setconfig", parameters=f"{name},{n}")
|
||||
|
||||
async def usbstats(self) -> dict:
|
||||
"""Get stats of all USB devices except ztex.
|
||||
@@ -354,11 +354,9 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
:return: Confirmation of setting PGA n with opt[,val].
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}")
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
@@ -373,7 +371,7 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
:return: the STATUS section with info on the zero and optional
|
||||
summary.
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which}, {summary}")
|
||||
return await self.send_command("zero", parameters=f"{which},{summary}")
|
||||
|
||||
async def hotplug(self, n: int) -> dict:
|
||||
"""Enable hotplug.
|
||||
@@ -474,9 +472,9 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
:return: Confirmation of setting option opt to value val.
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("ascset", parameters=f"{n}, {opt}, {val}")
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("ascset", parameters=f"{n}, {opt}")
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt}")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner.
|
||||
@@ -1,4 +1,4 @@
|
||||
from API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class BOSMinerAPI(BaseMinerAPI):
|
||||
@@ -158,7 +158,7 @@ class BOSMinerAPI(BaseMinerAPI):
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}")
|
||||
# return await self.send_command("addpool", parameters=f"{url},{username},{password}")
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
@@ -9,8 +9,8 @@ import logging
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from API import BaseMinerAPI, APIError
|
||||
from settings import WHATSMINER_PWD
|
||||
from pyasic.API import BaseMinerAPI, APIError
|
||||
from pyasic.settings import WHATSMINER_PWD
|
||||
|
||||
|
||||
### IMPORTANT ###
|
||||
@@ -1,4 +1,4 @@
|
||||
from API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
@@ -122,7 +122,7 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
:return: A confirmation of adding the pool.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url}, " f"{username}, " f"{password}"
|
||||
"addpool", parameters=f"{url},{username},{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
@@ -143,7 +143,7 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
|
||||
:return: A confirmation of setting pool quota.
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}")
|
||||
return await self.send_command("poolquota", parameters=f"{n},{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
@@ -322,7 +322,7 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
|
||||
:return: The results of setting config of name to n.
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}")
|
||||
return await self.send_command("setconfig", parameters=f"{name},{n}")
|
||||
|
||||
async def usbstats(self) -> dict:
|
||||
"""Get stats of all USB devices except ztex.
|
||||
@@ -350,11 +350,9 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
:return: Confirmation of setting PGA n with opt[,val].
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}")
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
@@ -369,7 +367,7 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
:return: the STATUS section with info on the zero and optional
|
||||
summary.
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which}, " f"{summary}")
|
||||
return await self.send_command("zero", parameters=f"{which},{summary}")
|
||||
|
||||
async def hotplug(self, n: int) -> dict:
|
||||
"""Enable hotplug.
|
||||
@@ -470,11 +468,9 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
:return: Confirmation of setting option opt to value val.
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"ascset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("ascset", parameters=f"{n}, " f"{opt}")
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt}")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner.
|
||||
@@ -1,4 +1,4 @@
|
||||
from API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class UnknownAPI(BaseMinerAPI):
|
||||
@@ -72,7 +72,7 @@ class UnknownAPI(BaseMinerAPI):
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}")
|
||||
# return await self.send_command("addpool", parameters=f"{url},{username},{password}")
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
366
pyasic/config/__init__.py
Normal file
366
pyasic/config/__init__.py
Normal file
@@ -0,0 +1,366 @@
|
||||
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_avalon(self, user_suffix: str = None):
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = ",".join([self.url, username, self.password])
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None):
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
:param user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "password": self.password}
|
||||
return pool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _PoolGroup:
|
||||
"""A dataclass for pool group information.
|
||||
|
||||
:param quota: The group quota.
|
||||
:param group_name: The name of the pool group.
|
||||
:param pools: A list of pools in this group.
|
||||
"""
|
||||
|
||||
quota: int = 1
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
||||
) # generate random pool group name in case it isn't set
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
||||
|
||||
:param data: The raw config data to convert.
|
||||
"""
|
||||
pools = []
|
||||
for key in data.keys():
|
||||
if key in ["name", "group_name"]:
|
||||
self.group_name = data[key]
|
||||
if key == "quota":
|
||||
self.quota = data[key]
|
||||
if key in ["pools", "pool"]:
|
||||
for pool in data[key]:
|
||||
pools.append(_Pool().from_dict(pool))
|
||||
self.pools = pools
|
||||
return self
|
||||
|
||||
def as_x19(self, user_suffix: str = None):
|
||||
"""Convert the data in this class to a dict usable by an X19 device.
|
||||
|
||||
:param user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_avalon(self, user_suffix: str = None):
|
||||
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None):
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
:param user_suffix: The suffix to append to username.
|
||||
"""
|
||||
group = {
|
||||
"name": self.group_name,
|
||||
"quota": self.quota,
|
||||
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
|
||||
}
|
||||
return group
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
"""A dataclass for miner configuration information.
|
||||
|
||||
:param pool_groups: A list of pool groups in this config.
|
||||
:param temp_mode: The temperature control mode.
|
||||
:param temp_target: The target temp.
|
||||
:param temp_hot: The hot temp (100% fans).
|
||||
:param temp_dangerous: The dangerous temp (shutdown).
|
||||
:param minimum_fans: The minimum numbers of fans to run the miner.
|
||||
:param fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
||||
:param asicboost: Whether or not to enable asicboost.
|
||||
:param autotuning_enabled: Whether or not to enable autotuning.
|
||||
:param autotuning_wattage: The wattage to use when autotuning.
|
||||
:param dps_enabled: Whether or not to enable dynamic power scaling.
|
||||
:param dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
||||
:param dps_min_power: The minimum power to reduce autotuning to.
|
||||
:param dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
||||
:param dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
||||
"""
|
||||
|
||||
pool_groups: List[_PoolGroup] = None
|
||||
|
||||
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
|
||||
temp_target: float = 70.0
|
||||
temp_hot: float = 80.0
|
||||
temp_dangerous: float = 10.0
|
||||
|
||||
minimum_fans: int = None
|
||||
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
|
||||
|
||||
asicboost: bool = None
|
||||
|
||||
autotuning_enabled: bool = True
|
||||
autotuning_wattage: int = 900
|
||||
|
||||
dps_enabled: bool = None
|
||||
dps_power_step: int = None
|
||||
dps_min_power: int = None
|
||||
dps_shutdown_enabled: bool = None
|
||||
dps_shutdown_duration: float = None
|
||||
|
||||
def as_dict(self):
|
||||
"""Convert the data in this class to a dict."""
|
||||
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
|
||||
def as_toml(self):
|
||||
"""Convert the data in this class to toml."""
|
||||
return toml.dumps(self.as_dict())
|
||||
|
||||
def as_yaml(self):
|
||||
"""Convert the data in this class to yaml."""
|
||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||
|
||||
def from_raw(self, data: dict):
|
||||
"""Convert raw config data as a dict to usable data and save it to this class.
|
||||
|
||||
:param data: The raw config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for key in data.keys():
|
||||
if key == "pools":
|
||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||
elif key == "group":
|
||||
for group in data[key]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
|
||||
if key == "bitmain-fan-ctrl":
|
||||
if data[key]:
|
||||
self.temp_mode = "manual"
|
||||
if data.get("bitmain-fan-pwm"):
|
||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||
elif key == "fan_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "min_fans":
|
||||
self.minimum_fans = data[key][_key]
|
||||
elif _key == "speed":
|
||||
self.fan_speed = data[key][_key]
|
||||
elif key == "temp_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "mode":
|
||||
self.temp_mode = data[key][_key]
|
||||
elif _key == "target_temp":
|
||||
self.temp_target = data[key][_key]
|
||||
elif _key == "hot_temp":
|
||||
self.temp_hot = data[key][_key]
|
||||
elif _key == "dangerous_temp":
|
||||
self.temp_dangerous = data[key][_key]
|
||||
|
||||
if key == "hash_chain_global":
|
||||
if data[key].get("asic_boost"):
|
||||
self.asicboost = data[key]["asic_boost"]
|
||||
|
||||
if key == "autotuning":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.autotuning_enabled = data[key][_key]
|
||||
elif _key == "psu_power_limit":
|
||||
self.autotuning_wattage = data[key][_key]
|
||||
|
||||
if key == "power_scaling":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.dps_enabled = data[key][_key]
|
||||
elif _key == "power_step":
|
||||
self.dps_power_step = data[key][_key]
|
||||
elif _key == "min_psu_power_limit":
|
||||
self.dps_min_power = data[key][_key]
|
||||
elif _key == "shutdown_enabled":
|
||||
self.dps_shutdown_enabled = data[key][_key]
|
||||
elif _key == "shutdown_duration":
|
||||
self.dps_shutdown_duration = data[key][_key]
|
||||
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||
|
||||
:param data: The raw config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data.keys():
|
||||
if getattr(self, key) and not key == "pool_groups":
|
||||
setattr(self, key, data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_toml(self, data: str):
|
||||
"""Convert output toml of this class back into usable data and save it to this class.
|
||||
|
||||
:param data: The raw config data to convert.
|
||||
"""
|
||||
return self.from_dict(toml.loads(data))
|
||||
|
||||
def from_yaml(self, data: str):
|
||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
||||
|
||||
:param data: The raw config data to convert.
|
||||
"""
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
:param user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
}
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
cfg = self.pool_groups[0].as_avalon()
|
||||
return cfg
|
||||
|
||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
||||
|
||||
:param model: The model of the miner to be used in the format portion of the config.
|
||||
:param user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": f"Antminer {model}",
|
||||
"generator": "Upstream Config Utility",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"group": [
|
||||
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
|
||||
],
|
||||
"temp_control": {
|
||||
"mode": self.temp_mode,
|
||||
"target_temp": self.temp_target,
|
||||
"hot_temp": self.temp_hot,
|
||||
"dangerous_temp": self.temp_dangerous,
|
||||
},
|
||||
}
|
||||
|
||||
if self.autotuning_enabled or self.autotuning_wattage:
|
||||
cfg["autotuning"] = {}
|
||||
if self.autotuning_enabled:
|
||||
cfg["autotuning"]["enabled"] = self.autotuning_enabled
|
||||
if self.autotuning_wattage:
|
||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
|
||||
if any(
|
||||
[
|
||||
getattr(self, item)
|
||||
for item in [
|
||||
"dps_enabled",
|
||||
"dps_power_step",
|
||||
"dps_min_power",
|
||||
"dps_shutdown_enabled",
|
||||
"dps_shutdown_duration",
|
||||
]
|
||||
]
|
||||
):
|
||||
cfg["power_scaling"] = {}
|
||||
if self.dps_enabled:
|
||||
cfg["power_scaling"]["enabled"] = self.dps_enabled
|
||||
if self.dps_power_step:
|
||||
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
||||
if self.dps_min_power:
|
||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
||||
if self.dps_shutdown_enabled:
|
||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
||||
if self.dps_shutdown_duration:
|
||||
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
|
||||
|
||||
return toml.dumps(cfg)
|
||||
@@ -7,6 +7,7 @@ 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.
|
||||
@@ -16,7 +17,8 @@ class MinerData:
|
||||
: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 wattage: Current power draw of the miner as an int.
|
||||
:param wattage_limit: Power limit 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.
|
||||
@@ -34,10 +36,12 @@ class MinerData:
|
||||
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = 0
|
||||
left_board_temp: int = 0
|
||||
left_board_chip_temp: int = 0
|
||||
center_board_temp: int = 0
|
||||
@@ -45,6 +49,7 @@ class MinerData:
|
||||
right_board_temp: int = 0
|
||||
right_board_chip_temp: int = 0
|
||||
wattage: int = 0
|
||||
wattage_limit: int = 0
|
||||
fan_1: int = -1
|
||||
fan_2: int = -1
|
||||
fan_3: int = -1
|
||||
@@ -98,7 +103,7 @@ class MinerData:
|
||||
self.center_board_chip_temp,
|
||||
self.right_board_chip_temp,
|
||||
]:
|
||||
if not temp == 0:
|
||||
if temp and not temp == 0:
|
||||
total_temp += temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
31
pyasic/logger/__init__.py
Normal file
31
pyasic/logger/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import logging
|
||||
from pyasic.settings import DEBUG, LOGFILE
|
||||
|
||||
|
||||
def init_logger():
|
||||
if LOGFILE:
|
||||
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",
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
|
||||
_logger = logging.getLogger()
|
||||
|
||||
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()
|
||||
@@ -2,7 +2,7 @@ import asyncssh
|
||||
import logging
|
||||
import ipaddress
|
||||
|
||||
from data import MinerData
|
||||
from pyasic.data import MinerData
|
||||
|
||||
|
||||
class BaseMiner:
|
||||
@@ -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))
|
||||
@@ -2,12 +2,12 @@ import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from API.bmminer import BMMinerAPI
|
||||
from miners import BaseMiner
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
from data import MinerData
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BMMiner(BaseMiner):
|
||||
@@ -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(
|
||||
@@ -199,11 +203,17 @@ class BMMiner(BaseMiner):
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
@@ -5,15 +5,15 @@ import json
|
||||
import toml
|
||||
|
||||
|
||||
from miners import BaseMiner
|
||||
from API.bosminer import BOSMinerAPI
|
||||
from API import APIError
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API import APIError
|
||||
|
||||
from data import MinerData
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from config.bos import bos_config_convert, general_config_convert_bos
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
@@ -102,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.
|
||||
@@ -192,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:
|
||||
@@ -264,6 +271,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
@@ -271,6 +279,9 @@ class BOSMiner(BaseMiner):
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
@@ -370,7 +381,10 @@ class BOSMiner(BaseMiner):
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
if tuner:
|
||||
if len(tuner) > 0:
|
||||
wattage = tuner[0].get("PowerLimit")
|
||||
wattage = tuner[0].get("ApproximateMinerPowerConsumption")
|
||||
wattage_limit = tuner[0].get("PowerLimit")
|
||||
if wattage_limit:
|
||||
data.wattage_limit = wattage_limit
|
||||
if wattage:
|
||||
data.wattage = wattage
|
||||
|
||||
@@ -389,4 +403,4 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def get_mac(self):
|
||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
return result.upper()
|
||||
return result.upper().strip()
|
||||
@@ -1,14 +1,15 @@
|
||||
import logging
|
||||
|
||||
import asyncssh
|
||||
import ipaddress
|
||||
|
||||
from API.bosminer import BOSMinerAPI
|
||||
from miners import BaseMiner
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class BOSMinerOld(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
@@ -22,14 +23,17 @@ class BOSMinerOld(BaseMiner):
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with await asyncssh.connect("192.168.1.11", username="root") as conn:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
if result.stdout:
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
if e == "SSH connection closed":
|
||||
return "Update completed."
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
@@ -40,6 +44,7 @@ class BOSMinerOld(BaseMiner):
|
||||
# return the result, either command output or None
|
||||
return str(result)
|
||||
|
||||
|
||||
async def update_to_plus(self):
|
||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
||||
return result
|
||||
@@ -2,13 +2,13 @@ import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from API.btminer import BTMinerAPI
|
||||
from miners import BaseMiner
|
||||
from API import APIError
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from data import MinerData
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
@@ -30,7 +30,7 @@ class BTMiner(BaseMiner):
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
async def get_hostname(self) -> str or None:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
@@ -41,11 +41,11 @@ class BTMiner(BaseMiner):
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
except APIError:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
logging.info(f"Failed to get hostname for miner: {self}")
|
||||
return None
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
return None
|
||||
|
||||
async def get_board_info(self) -> dict:
|
||||
"""Gets data on each board and chain in the miner."""
|
||||
@@ -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,12 @@ 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"]
|
||||
|
||||
if summary_data[0].get("Env Temp"):
|
||||
data.env_temp = summary_data[0]["Env Temp"]
|
||||
|
||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
||||
|
||||
@@ -135,6 +160,7 @@ class BTMiner(BaseMiner):
|
||||
wattage = summary_data[0].get("Power")
|
||||
if wattage:
|
||||
data.wattage = round(wattage)
|
||||
data.wattage_limit = round(wattage)
|
||||
|
||||
if devs:
|
||||
temp_data = devs.get("DEVS")
|
||||
@@ -208,4 +234,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
|
||||
@@ -2,13 +2,13 @@ import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from API.cgminer import CGMinerAPI
|
||||
from miners import BaseMiner
|
||||
from API import APIError
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from data import MinerData
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class CGMiner(BaseMiner):
|
||||
@@ -33,7 +33,7 @@ class CGMiner(BaseMiner):
|
||||
return self.model
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
async def get_hostname(self) -> str or None:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
@@ -44,9 +44,9 @@ class CGMiner(BaseMiner):
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
return "?"
|
||||
return None
|
||||
except Exception:
|
||||
return "?"
|
||||
return None
|
||||
|
||||
async def send_ssh_command(self, cmd):
|
||||
result = None
|
||||
@@ -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")
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners._backends import BMMiner
|
||||
from pyasic.miners._backends import BMMiner
|
||||
import ipaddress
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Plus(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Pro(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17e(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17Plus(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17e(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19(BaseMiner):
|
||||
@@ -1,4 +1,4 @@
|
||||
from miners import BaseMiner
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19Pro(BaseMiner):
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user