Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7d4891140 | ||
|
|
5f5cbd9060 | ||
|
|
8379359caf | ||
|
|
62238192ce | ||
|
|
1997003643 | ||
|
|
3a81844898 | ||
|
|
0ac80fb205 | ||
|
|
9494018c12 | ||
|
|
0bc86c98c5 | ||
|
|
f0d69c9ca7 | ||
|
|
b81590bd2e | ||
|
|
a53e01df6f | ||
|
|
f63e063954 | ||
|
|
9cbaf7076a | ||
|
|
daa5ac5870 | ||
|
|
0b8c08016b | ||
|
|
8c768d351b | ||
|
|
c9e7fa2629 | ||
|
|
9d3f2b5968 | ||
|
|
283e3d5e11 | ||
|
|
add4b575c2 | ||
|
|
af2f1e9ad5 |
@@ -5,8 +5,8 @@
|
|||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pyasic.readthedocs.io/en/latest/)
|
[](https://pyasic.readthedocs.io/en/latest/)
|
||||||

|
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||||

|
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||||
## Documentation
|
## Documentation
|
||||||
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/)
|
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
## Miner APIs
|
## Miner APIs
|
||||||
Each miner has a unique API that is used to communicate with it.
|
Each miner has a unique API that is used to communicate with it.
|
||||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||||
Each miner that is a subclass of `BaseMiner` should have an API linked to it as `Miner.api`.
|
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||||
|
|
||||||
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
||||||
|
|
||||||
BaseMinerAPI should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||||
|
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||||
Use these instead -
|
Use these instead -
|
||||||
|
|
||||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||||
|
|||||||
25
docs/data/error_codes.md
Normal file
25
docs/data/error_codes.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# pyasic
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Whatsminer Error Codes
|
||||||
|
::: pyasic.data.error_codes.WhatsminerError
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Braiins OS Error Codes
|
||||||
|
::: pyasic.data.error_codes.BraiinsOSError
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## X19 Error Codes
|
||||||
|
::: pyasic.data.error_codes.X19Error
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pyasic.readthedocs.io/en/latest/)
|
[](https://pyasic.readthedocs.io/en/latest/)
|
||||||
|
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||||
|
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||||
|
|
||||||
## Intro
|
## Intro
|
||||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
||||||
|
|||||||
10
docs/miners/base_miner.md
Normal file
10
docs/miners/base_miner.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## Base Miner
|
||||||
|
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||||
|
|
||||||
|
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`.
|
||||||
|
|
||||||
|
::: pyasic.miners.BaseMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
@@ -6,3 +6,15 @@
|
|||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## AnyMiner
|
||||||
|
::: pyasic.miners.miner_factory.AnyMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||||
|
A function returning [`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||||
|
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
|
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
|
||||||
|
|
||||||
## Miner List
|
|
||||||
|
|
||||||
##### pyasic currently supports the following miners and subtypes:
|
##### pyasic currently supports the following miners and subtypes:
|
||||||
* Braiins OS+ Devices:
|
* Braiins OS+ Devices:
|
||||||
* All devices supported by BraiinsOS+ are supported here.
|
* All devices supported by BraiinsOS+ are supported here.
|
||||||
@@ -27,6 +25,8 @@ Supported miner types are here on this list. If your miner (or miner version) i
|
|||||||
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20]
|
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20]
|
||||||
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S]
|
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S]
|
||||||
* M2X Series:
|
* M2X Series:
|
||||||
|
* [M20][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20]:
|
||||||
|
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10]
|
||||||
* [M20S][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S]:
|
* [M20S][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S]:
|
||||||
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10]
|
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10]
|
||||||
* [V20][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20]
|
* [V20][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20]
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## M2X Models
|
## M2X Models
|
||||||
|
|
||||||
|
## M20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20V10
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M20S
|
## M20S
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Miner Network Range
|
## Miner Network Range
|
||||||
|
|
||||||
`MinerNetworkRange` is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||||
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
||||||
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
||||||
|
|
||||||
|
|||||||
@@ -28,17 +28,21 @@ nav:
|
|||||||
- Miner Network Range: "network/miner_network_range.md"
|
- Miner Network Range: "network/miner_network_range.md"
|
||||||
- Data:
|
- Data:
|
||||||
- Miner Data: "data/miner_data.md"
|
- Miner Data: "data/miner_data.md"
|
||||||
|
- Error Codes: "data/error_codes.md"
|
||||||
- Config:
|
- Config:
|
||||||
- Miner Config: "config/miner_config.md"
|
- Miner Config: "config/miner_config.md"
|
||||||
- Advanced:
|
- Advanced:
|
||||||
- Miner APIs:
|
- Miner APIs:
|
||||||
- Base: "API/api.md"
|
- Intro: "API/api.md"
|
||||||
- BMMiner: "API/bmminer.md"
|
- BMMiner: "API/bmminer.md"
|
||||||
- BOSMiner: "API/bosminer.md"
|
- BOSMiner: "API/bosminer.md"
|
||||||
- BTMiner: "API/btminer.md"
|
- BTMiner: "API/btminer.md"
|
||||||
- CGMiner: "API/cgminer.md"
|
- CGMiner: "API/cgminer.md"
|
||||||
- Unknown: "API/unknown.md"
|
- Unknown: "API/unknown.md"
|
||||||
|
|
||||||
|
- Base Miner: "miners/base_miner.md"
|
||||||
|
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- mkdocstrings
|
- mkdocstrings
|
||||||
- search
|
- search
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ class BaseMinerAPI:
|
|||||||
# ip address of the miner
|
# ip address of the miner
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls is BaseMinerAPI:
|
||||||
|
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||||
|
return object.__new__(cls)
|
||||||
|
|
||||||
def get_commands(self) -> list:
|
def get_commands(self) -> list:
|
||||||
"""Get a list of command accessible to a specific type of API on the miner.
|
"""Get a list of command accessible to a specific type of API on the miner.
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import hashlib
|
|||||||
import binascii
|
import binascii
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
@@ -187,8 +188,8 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
command: str or bytes,
|
command: Union[str, bytes],
|
||||||
parameters: str or int or bool = None,
|
parameters: Union[str, int, bool] = None,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|||||||
@@ -287,6 +287,15 @@ class MinerConfig:
|
|||||||
self.pool_groups = pool_groups
|
self.pool_groups = pool_groups
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def from_api(self, pools: list):
|
||||||
|
_pools = []
|
||||||
|
for pool in pools:
|
||||||
|
url = pool.get("URL")
|
||||||
|
user = pool.get("User")
|
||||||
|
_pools.append({"url": url, "user": user, "pass": "123"})
|
||||||
|
self.pool_groups = [_PoolGroup().from_dict({"pools": _pools})]
|
||||||
|
return self
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import Union
|
from typing import Union, List
|
||||||
from dataclasses import dataclass, field, asdict
|
from dataclasses import dataclass, field, asdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .error_codes import X19Error, WhatsminerError, BraiinsOSError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MinerData:
|
class MinerData:
|
||||||
@@ -57,6 +59,7 @@ class MinerData:
|
|||||||
pool_2_url: The second pool url on the miner as a str.
|
pool_2_url: The second pool url on the miner as a str.
|
||||||
pool_2_user: The second pool user on the miner as a str.
|
pool_2_user: The second pool user on the miner as a str.
|
||||||
errors: A list of errors on the miner.
|
errors: A list of errors on the miner.
|
||||||
|
fault_light: Whether or not the fault light is on as a boolean.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ip: str
|
ip: str
|
||||||
@@ -94,7 +97,9 @@ class MinerData:
|
|||||||
pool_1_user: str = "Unknown"
|
pool_1_user: str = "Unknown"
|
||||||
pool_2_url: str = ""
|
pool_2_url: str = ""
|
||||||
pool_2_user: str = ""
|
pool_2_user: str = ""
|
||||||
errors: list = field(default_factory=list)
|
errors: List[Union[WhatsminerError, BraiinsOSError, X19Error]] = field(
|
||||||
|
default_factory=list
|
||||||
|
)
|
||||||
fault_light: Union[bool, None] = None
|
fault_light: Union[bool, None] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
|||||||
29
pyasic/data/error_codes/X19.py
Normal file
29
pyasic/data/error_codes/X19.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class X19Error:
|
||||||
|
"""A Dataclass to handle error codes of X19 miners.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
error_message: The error message as a string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
error_message: str
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return asdict(self)
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
|
|
||||||
from .whatsminer import WhatsminerError
|
from .whatsminer import WhatsminerError
|
||||||
from .bos import BraiinsOSError
|
from .bos import BraiinsOSError
|
||||||
|
from .X19 import X19Error
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ from dataclasses import dataclass, asdict
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BraiinsOSError:
|
class BraiinsOSError:
|
||||||
"""A Dataclass to handle error codes of BraiinsOS+ miners."""
|
"""A Dataclass to handle error codes of BraiinsOS+ miners.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
error_message: The error message as a string.
|
||||||
|
"""
|
||||||
|
|
||||||
error_message: str
|
error_message: str
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ from dataclasses import dataclass, field, asdict
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WhatsminerError:
|
class WhatsminerError:
|
||||||
"""A Dataclass to handle error codes of Whatsminers."""
|
"""A Dataclass to handle error codes of Whatsminers.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
error_code: The error code as an int.
|
||||||
|
error_message: The error message as a string. Automatically found from the error code.
|
||||||
|
"""
|
||||||
|
|
||||||
error_code: int
|
error_code: int
|
||||||
error_message: str = field(init=False)
|
error_message: str = field(init=False)
|
||||||
|
|||||||
@@ -15,11 +15,13 @@
|
|||||||
import asyncssh
|
import asyncssh
|
||||||
import logging
|
import logging
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
|
|
||||||
class BaseMiner:
|
class BaseMiner(ABC):
|
||||||
def __init__(self, *args) -> None:
|
def __init__(self, *args) -> None:
|
||||||
self.ip = None
|
self.ip = None
|
||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
@@ -34,6 +36,11 @@ class BaseMiner:
|
|||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls is BaseMiner:
|
||||||
|
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||||
|
return object.__new__(cls)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
|
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
|
||||||
|
|
||||||
@@ -75,42 +82,56 @@ class BaseMiner:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
return False
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
return False
|
pass
|
||||||
|
|
||||||
async def send_file(self, src, dest):
|
# async def send_file(self, src, dest):
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
# async with (await self._get_ssh_connection()) as conn:
|
||||||
await asyncssh.scp(src, (conn, dest))
|
# await asyncssh.scp(src, (conn, dest))
|
||||||
|
|
||||||
async def check_light(self):
|
@abstractmethod
|
||||||
return self.light
|
async def check_light(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# @abstractmethod
|
||||||
async def get_board_info(self):
|
async def get_board_info(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_config(self):
|
@abstractmethod
|
||||||
return None
|
async def get_config(self) -> MinerConfig:
|
||||||
|
pass
|
||||||
|
|
||||||
async def get_hostname(self):
|
@abstractmethod
|
||||||
return None
|
async def get_hostname(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
async def get_model(self):
|
@abstractmethod
|
||||||
return None
|
async def get_model(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
async def reboot(self):
|
@abstractmethod
|
||||||
return False
|
async def reboot(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
async def restart_backend(self):
|
@abstractmethod
|
||||||
return False
|
async def restart_backend(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
async def send_config(self, *args, **kwargs):
|
async def send_config(self, *args, **kwargs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_mac(self):
|
@abstractmethod
|
||||||
return None
|
async def get_mac(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
pass
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
return MinerData(ip=str(self.ip))
|
return MinerData(ip=str(self.ip))
|
||||||
|
|||||||
@@ -154,6 +154,26 @@ class BMMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_mac(self) -> str:
|
||||||
|
return "00:00:00:00:00:00"
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
"""Get data from the miner.
|
"""Get data from the miner.
|
||||||
|
|
||||||
@@ -168,6 +188,7 @@ class BMMiner(BaseMiner):
|
|||||||
model = await self.get_model()
|
model = await self.get_model()
|
||||||
hostname = await self.get_hostname()
|
hostname = await self.get_hostname()
|
||||||
mac = await self.get_mac()
|
mac = await self.get_mac()
|
||||||
|
errors = await self.get_errors()
|
||||||
|
|
||||||
if model:
|
if model:
|
||||||
data.model = model
|
data.model = model
|
||||||
@@ -178,6 +199,10 @@ class BMMiner(BaseMiner):
|
|||||||
if mac:
|
if mac:
|
||||||
data.mac = mac
|
data.mac = mac
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for error in errors:
|
||||||
|
data.errors.append(error)
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
data.fault_light = await self.check_light()
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
|
|||||||
@@ -240,6 +240,49 @@ class BOSMiner(BaseMiner):
|
|||||||
logging.debug(f"{self}: Restarting BOSMiner")
|
logging.debug(f"{self}: Restarting BOSMiner")
|
||||||
await conn.run("/etc/init.d/bosminer start")
|
await conn.run("/etc/init.d/bosminer start")
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
tunerstatus = None
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
tunerstatus = await self.api.tunerstatus()
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(e)
|
||||||
|
|
||||||
|
if tunerstatus:
|
||||||
|
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||||
|
if tuner:
|
||||||
|
if len(tuner) > 0:
|
||||||
|
chain_status = tuner[0].get("TunerChainStatus")
|
||||||
|
if chain_status and len(chain_status) > 0:
|
||||||
|
board_map = {
|
||||||
|
0: "Left board",
|
||||||
|
1: "Center board",
|
||||||
|
2: "Right board",
|
||||||
|
}
|
||||||
|
offset = (
|
||||||
|
6
|
||||||
|
if chain_status[0]["HashchainIndex"] in [6, 7, 8]
|
||||||
|
else chain_status[0]["HashchainIndex"]
|
||||||
|
)
|
||||||
|
for board in chain_status:
|
||||||
|
_id = board["HashchainIndex"] - offset
|
||||||
|
if board["Status"] not in [
|
||||||
|
"Stable",
|
||||||
|
"Testing performance profile",
|
||||||
|
]:
|
||||||
|
_error = board["Status"]
|
||||||
|
_error = _error[0].lower() + _error[1:]
|
||||||
|
errors.append(
|
||||||
|
BraiinsOSError(f"{board_map[_id]} {_error}")
|
||||||
|
)
|
||||||
|
return errors
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
"""Get data from the miner.
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
from pyasic.API.bosminer import BOSMinerAPI
|
||||||
from pyasic.miners import BaseMiner
|
from pyasic.miners import BaseMiner
|
||||||
@@ -29,7 +30,7 @@ class BOSMinerOld(BaseMiner):
|
|||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||||
"""Send a command to the miner over ssh.
|
"""Send a command to the miner over ssh.
|
||||||
|
|
||||||
:return: Result of the command or None.
|
:return: Result of the command or None.
|
||||||
@@ -61,3 +62,33 @@ class BOSMinerOld(BaseMiner):
|
|||||||
async def update_to_plus(self):
|
async def update_to_plus(self):
|
||||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_config(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_hostname(self) -> str:
|
||||||
|
return "?"
|
||||||
|
|
||||||
|
async def get_mac(self) -> str:
|
||||||
|
return "00:00:00:00:00:00"
|
||||||
|
|
||||||
|
async def get_model(self) -> str:
|
||||||
|
return "S9"
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
return False
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from pyasic.API import APIError
|
|||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.data.error_codes import WhatsminerError
|
from pyasic.data.error_codes import WhatsminerError
|
||||||
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
@@ -99,6 +100,40 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return str(mac).upper()
|
return str(mac).upper()
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_config(self) -> MinerConfig:
|
||||||
|
pools = None
|
||||||
|
cfg = MinerConfig()
|
||||||
|
|
||||||
|
try:
|
||||||
|
pools = await self.api.pools()
|
||||||
|
except APIError as e:
|
||||||
|
logging.warning(e)
|
||||||
|
|
||||||
|
if pools:
|
||||||
|
if "POOLS" in pools.keys():
|
||||||
|
cfg = cfg.from_api(pools["POOLS"])
|
||||||
|
return cfg
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
"""Get data from the miner.
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,23 @@ class CGMiner(BaseMiner):
|
|||||||
self.config = result.stdout
|
self.config = result.stdout
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_mac(self) -> str:
|
||||||
|
return "00:00:00:00:00:00"
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
"""Get data from the miner.
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
|||||||
33
pyasic/miners/_types/whatsminer/M2X/M20.py
Normal file
33
pyasic/miners/_types/whatsminer/M2X/M20.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from pyasic.miners import BaseMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M20(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20"
|
||||||
|
self.nominal_chips = 70
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M20V10(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20 V10"
|
||||||
|
self.nominal_chips = 70
|
||||||
|
self.fan_count = 2
|
||||||
@@ -28,7 +28,7 @@ class M20SV10(BaseMiner):
|
|||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S"
|
self.model = "M20S V10"
|
||||||
self.nominal_chips = 105
|
self.nominal_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
@@ -37,6 +37,6 @@ class M20SV20(BaseMiner):
|
|||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S"
|
self.model = "M20S V20"
|
||||||
self.nominal_chips = 111
|
self.nominal_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from .M20 import M20, M20V10
|
||||||
from .M20S import M20S, M20SV10, M20SV20
|
from .M20S import M20S, M20SV10, M20SV20
|
||||||
from .M20S_Plus import M20SPlus
|
from .M20S_Plus import M20SPlus
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||||
|
|
||||||
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
@@ -22,11 +24,13 @@ class BMMinerX17(BMMiner):
|
|||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
self.uname = "root"
|
||||||
|
self.pwd = PyasicSettings().global_x17_password
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
hostname = None
|
hostname = None
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
@@ -36,10 +40,10 @@ class BMMinerX17(BMMiner):
|
|||||||
hostname = data["hostname"]
|
hostname = data["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def get_mac(self):
|
async def get_mac(self) -> Union[str, None]:
|
||||||
mac = None
|
mac = None
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
@@ -51,7 +55,7 @@ class BMMinerX17(BMMiner):
|
|||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
await client.post(url, data={"action": "startBlink"}, auth=auth)
|
await client.post(url, data={"action": "startBlink"}, auth=auth)
|
||||||
@@ -62,35 +66,43 @@ class BMMinerX17(BMMiner):
|
|||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
data = data.json()
|
||||||
if data["isBlinking"]:
|
if data["isBlinking"]:
|
||||||
|
self.light = True
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
await client.post(url, data={"action": "stopBlink"}, auth=auth)
|
await client.post(url, data={"action": "stopBlink"}, auth=auth)
|
||||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
data = data.json()
|
||||||
if not data["isBlinking"]:
|
if not data["isBlinking"]:
|
||||||
|
self.light = False
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def check_light(self):
|
async def check_light(self) -> Union[bool, None]:
|
||||||
|
if self.light:
|
||||||
|
return self.light
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
data = data.json()
|
||||||
if data["isBlinking"]:
|
if data["isBlinking"]:
|
||||||
|
self.light = True
|
||||||
return True
|
return True
|
||||||
return False
|
else:
|
||||||
|
self.light = False
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
|
|||||||
@@ -15,33 +15,39 @@
|
|||||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
from pyasic.data.error_codes import X19Error
|
||||||
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Union, List
|
||||||
|
|
||||||
|
|
||||||
class BMMinerX19(BMMiner):
|
class BMMinerX19(BMMiner):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
self.uname = "root"
|
||||||
|
self.pwd = PyasicSettings().global_x19_password
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
async def check_light(self) -> Union[bool, None]:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
url = f"http://{self.ip}/cgi-bin/get_blink_status.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_blink_status.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
data = data.json()
|
||||||
light = data["blink"]
|
light = data["blink"]
|
||||||
|
self.light = light
|
||||||
return light
|
return light
|
||||||
return False
|
return None
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
url = f"http://{self.ip}/cgi-bin/get_miner_conf.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_miner_conf.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
@@ -51,7 +57,7 @@ class BMMinerX19(BMMiner):
|
|||||||
|
|
||||||
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
|
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
|
||||||
url = f"http://{self.ip}/cgi-bin/set_miner_conf.cgi"
|
url = f"http://{self.ip}/cgi-bin/set_miner_conf.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
if ip_user:
|
if ip_user:
|
||||||
suffix = str(self.ip).split(".")[-1]
|
suffix = str(self.ip).split(".")[-1]
|
||||||
conf = MinerConfig().from_yaml(yaml_config).as_x19(user_suffix=suffix)
|
conf = MinerConfig().from_yaml(yaml_config).as_x19(user_suffix=suffix)
|
||||||
@@ -69,10 +75,10 @@ class BMMinerX19(BMMiner):
|
|||||||
break
|
break
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
async def get_hostname(self) -> str or None:
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
hostname = None
|
hostname = None
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
@@ -82,10 +88,10 @@ class BMMinerX19(BMMiner):
|
|||||||
hostname = data["hostname"]
|
hostname = data["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def get_mac(self):
|
async def get_mac(self) -> Union[str, None]:
|
||||||
mac = None
|
mac = None
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
@@ -97,7 +103,7 @@ class BMMinerX19(BMMiner):
|
|||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
data = json.dumps({"blink": "true"})
|
data = json.dumps({"blink": "true"})
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.post(url, data=data, auth=auth)
|
data = await client.post(url, data=data, auth=auth)
|
||||||
@@ -110,7 +116,7 @@ class BMMinerX19(BMMiner):
|
|||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
data = json.dumps({"blink": "false"})
|
data = json.dumps({"blink": "false"})
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.post(url, data=data, auth=auth)
|
data = await client.post(url, data=data, auth=auth)
|
||||||
@@ -123,9 +129,24 @@ class BMMinerX19(BMMiner):
|
|||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = await client.get(url, auth=auth)
|
data = await client.get(url, auth=auth)
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[X19Error]:
|
||||||
|
errors = []
|
||||||
|
url = f"http://{self.ip}/cgi-bin/summary.cgi"
|
||||||
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
data = await client.get(url, auth=auth)
|
||||||
|
if data:
|
||||||
|
data = data.json()
|
||||||
|
if "SUMMARY" in data.keys():
|
||||||
|
if "status" in data["SUMMARY"][0].keys():
|
||||||
|
for item in data["SUMMARY"][0]["status"]:
|
||||||
|
if not item["status"] == "s":
|
||||||
|
errors.append(X19Error(item["msg"]))
|
||||||
|
return errors
|
||||||
|
|||||||
@@ -45,93 +45,98 @@ import asyncssh
|
|||||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
"Antminer S9": {
|
"ANTMINER S9": {
|
||||||
"Default": BOSMinerS9,
|
"Default": BOSMinerS9,
|
||||||
"BOSMiner": BOSMinerOld,
|
"BOSMiner": BOSMinerOld,
|
||||||
"BOSMiner+": BOSMinerS9,
|
"BOSMiner+": BOSMinerS9,
|
||||||
"BMMiner": BMMinerS9,
|
"BMMiner": BMMinerS9,
|
||||||
"CGMiner": CGMinerS9,
|
"CGMiner": CGMinerS9,
|
||||||
},
|
},
|
||||||
"Antminer S9i": {
|
"ANTMINER S9I": {
|
||||||
"Default": BMMinerS9i,
|
"Default": BMMinerS9i,
|
||||||
"BMMiner": BMMinerS9i,
|
"BMMiner": BMMinerS9i,
|
||||||
},
|
},
|
||||||
"Antminer S17": {
|
"ANTMINER S17": {
|
||||||
"Default": BMMinerS17,
|
"Default": BMMinerS17,
|
||||||
"BOSMiner+": BOSMinerS17,
|
"BOSMiner+": BOSMinerS17,
|
||||||
"BMMiner": BMMinerS17,
|
"BMMiner": BMMinerS17,
|
||||||
"CGMiner": CGMinerS17,
|
"CGMiner": CGMinerS17,
|
||||||
},
|
},
|
||||||
"Antminer S17+": {
|
"ANTMINER S17+": {
|
||||||
"Default": BMMinerS17Plus,
|
"Default": BMMinerS17Plus,
|
||||||
"BOSMiner+": BOSMinerS17Plus,
|
"BOSMiner+": BOSMinerS17Plus,
|
||||||
"BMMiner": BMMinerS17Plus,
|
"BMMiner": BMMinerS17Plus,
|
||||||
"CGMiner": CGMinerS17Plus,
|
"CGMiner": CGMinerS17Plus,
|
||||||
},
|
},
|
||||||
"Antminer S17 Pro": {
|
"ANTMINER S17 PRO": {
|
||||||
"Default": BMMinerS17Pro,
|
"Default": BMMinerS17Pro,
|
||||||
"BOSMiner+": BOSMinerS17Pro,
|
"BOSMiner+": BOSMinerS17Pro,
|
||||||
"BMMiner": BMMinerS17Pro,
|
"BMMiner": BMMinerS17Pro,
|
||||||
"CGMiner": CGMinerS17Pro,
|
"CGMiner": CGMinerS17Pro,
|
||||||
},
|
},
|
||||||
"Antminer S17e": {
|
"ANTMINER S17E": {
|
||||||
"Default": BMMinerS17e,
|
"Default": BMMinerS17e,
|
||||||
"BOSMiner+": BOSMinerS17e,
|
"BOSMiner+": BOSMinerS17e,
|
||||||
"BMMiner": BMMinerS17e,
|
"BMMiner": BMMinerS17e,
|
||||||
"CGMiner": CGMinerS17e,
|
"CGMiner": CGMinerS17e,
|
||||||
},
|
},
|
||||||
"Antminer T17": {
|
"ANTMINER T17": {
|
||||||
"Default": BMMinerT17,
|
"Default": BMMinerT17,
|
||||||
"BOSMiner+": BOSMinerT17,
|
"BOSMiner+": BOSMinerT17,
|
||||||
"BMMiner": BMMinerT17,
|
"BMMiner": BMMinerT17,
|
||||||
"CGMiner": CGMinerT17,
|
"CGMiner": CGMinerT17,
|
||||||
},
|
},
|
||||||
"Antminer T17+": {
|
"ANTMINER T17+": {
|
||||||
"Default": BMMinerT17Plus,
|
"Default": BMMinerT17Plus,
|
||||||
"BOSMiner+": BOSMinerT17Plus,
|
"BOSMiner+": BOSMinerT17Plus,
|
||||||
"BMMiner": BMMinerT17Plus,
|
"BMMiner": BMMinerT17Plus,
|
||||||
"CGMiner": CGMinerT17Plus,
|
"CGMiner": CGMinerT17Plus,
|
||||||
},
|
},
|
||||||
"Antminer T17e": {
|
"ANTMINER T17E": {
|
||||||
"Default": BMMinerT17e,
|
"Default": BMMinerT17e,
|
||||||
"BOSMiner+": BOSMinerT17e,
|
"BOSMiner+": BOSMinerT17e,
|
||||||
"BMMiner": BMMinerT17e,
|
"BMMiner": BMMinerT17e,
|
||||||
"CGMiner": CGMinerT17e,
|
"CGMiner": CGMinerT17e,
|
||||||
},
|
},
|
||||||
"Antminer S19": {
|
"ANTMINER S19": {
|
||||||
"Default": BMMinerS19,
|
"Default": BMMinerS19,
|
||||||
"BOSMiner+": BOSMinerS19,
|
"BOSMiner+": BOSMinerS19,
|
||||||
"BMMiner": BMMinerS19,
|
"BMMiner": BMMinerS19,
|
||||||
"CGMiner": CGMinerS19,
|
"CGMiner": CGMinerS19,
|
||||||
},
|
},
|
||||||
"Antminer S19 Pro": {
|
"ANTMINER S19 PRO": {
|
||||||
"Default": BMMinerS19Pro,
|
"Default": BMMinerS19Pro,
|
||||||
"BOSMiner+": BOSMinerS19Pro,
|
"BOSMiner+": BOSMinerS19Pro,
|
||||||
"BMMiner": BMMinerS19Pro,
|
"BMMiner": BMMinerS19Pro,
|
||||||
"CGMiner": CGMinerS19Pro,
|
"CGMiner": CGMinerS19Pro,
|
||||||
},
|
},
|
||||||
"Antminer S19j": {
|
"ANTMINER S19J": {
|
||||||
"Default": BMMinerS19j,
|
"Default": BMMinerS19j,
|
||||||
"BOSMiner+": BOSMinerS19j,
|
"BOSMiner+": BOSMinerS19j,
|
||||||
"BMMiner": BMMinerS19j,
|
"BMMiner": BMMinerS19j,
|
||||||
"CGMiner": CGMinerS19j,
|
"CGMiner": CGMinerS19j,
|
||||||
},
|
},
|
||||||
"Antminer S19j Pro": {
|
"ANTMINER S19J PRO": {
|
||||||
"Default": BMMinerS19jPro,
|
"Default": BMMinerS19jPro,
|
||||||
"BOSMiner+": BOSMinerS19jPro,
|
"BOSMiner+": BOSMinerS19jPro,
|
||||||
"BMMiner": BMMinerS19jPro,
|
"BMMiner": BMMinerS19jPro,
|
||||||
"CGMiner": CGMinerS19jPro,
|
"CGMiner": CGMinerS19jPro,
|
||||||
},
|
},
|
||||||
"Antminer S19a": {
|
"ANTMINER S19A": {
|
||||||
"Default": BMMinerS19a,
|
"Default": BMMinerS19a,
|
||||||
"BMMiner": BMMinerS19a,
|
"BMMiner": BMMinerS19a,
|
||||||
},
|
},
|
||||||
"Antminer T19": {
|
"ANTMINER T19": {
|
||||||
"Default": BMMinerT19,
|
"Default": BMMinerT19,
|
||||||
"BOSMiner+": BOSMinerT19,
|
"BOSMiner+": BOSMinerT19,
|
||||||
"BMMiner": BMMinerT19,
|
"BMMiner": BMMinerT19,
|
||||||
"CGMiner": CGMinerT19,
|
"CGMiner": CGMinerT19,
|
||||||
},
|
},
|
||||||
|
"M20": {
|
||||||
|
"Default": BTMinerM20,
|
||||||
|
"BTMiner": BTMinerM20,
|
||||||
|
"10": BTMinerM20V10,
|
||||||
|
},
|
||||||
"M20S": {
|
"M20S": {
|
||||||
"Default": BTMinerM20S,
|
"Default": BTMinerM20S,
|
||||||
"BTMiner": BTMinerM20S,
|
"BTMiner": BTMinerM20S,
|
||||||
@@ -230,6 +235,7 @@ MINER_CLASSES = {
|
|||||||
"Default": CGMinerAvalon1066,
|
"Default": CGMinerAvalon1066,
|
||||||
"CGMiner": CGMinerAvalon1066,
|
"CGMiner": CGMinerAvalon1066,
|
||||||
},
|
},
|
||||||
|
"Unknown": {"Default": UnknownMiner},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -426,9 +432,9 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
|
|
||||||
if board_name:
|
if board_name:
|
||||||
if board_name == "am1-s9":
|
if board_name == "am1-s9":
|
||||||
model = "Antminer S9"
|
model = "ANTMINER S9"
|
||||||
if board_name == "am2-s17":
|
if board_name == "am2-s17":
|
||||||
model = "Antminer S17"
|
model = "ANTMINER S17"
|
||||||
api = "BOSMiner+"
|
api = "BOSMiner+"
|
||||||
return model, api, None
|
return model, api, None
|
||||||
|
|
||||||
@@ -441,7 +447,7 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
data = data.json()
|
||||||
if "minertype" in data.keys():
|
if "minertype" in data.keys():
|
||||||
model = data["minertype"]
|
model = data["minertype"].upper()
|
||||||
if "bmminer" in "\t".join(data.keys()):
|
if "bmminer" in "\t".join(data.keys()):
|
||||||
api = "BMMiner"
|
api = "BMMiner"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -454,15 +460,15 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# check for model, for most miners
|
# check for model, for most miners
|
||||||
if not devdetails["DEVDETAILS"][0]["Model"] == "":
|
if not devdetails["DEVDETAILS"][0]["Model"] == "":
|
||||||
# model of most miners
|
# model of most miners
|
||||||
model = devdetails["DEVDETAILS"][0]["Model"]
|
model = devdetails["DEVDETAILS"][0]["Model"].upper()
|
||||||
|
|
||||||
# if model fails, try driver
|
# if model fails, try driver
|
||||||
else:
|
else:
|
||||||
# some avalonminers have model in driver
|
# some avalonminers have model in driver
|
||||||
model = devdetails["DEVDETAILS"][0]["Driver"]
|
model = devdetails["DEVDETAILS"][0]["Driver"].upper()
|
||||||
else:
|
else:
|
||||||
if "s9" in devdetails["STATUS"][0]["Description"]:
|
if "s9" in devdetails["STATUS"][0]["Description"]:
|
||||||
model = "Antminer S9"
|
model = "ANTMINER S9"
|
||||||
|
|
||||||
# if we have version we can get API type from here
|
# if we have version we can get API type from here
|
||||||
if version:
|
if version:
|
||||||
@@ -497,7 +503,7 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# check for avalonminers
|
# check for avalonminers
|
||||||
if version["VERSION"][0].get("PROD"):
|
if version["VERSION"][0].get("PROD"):
|
||||||
_data = version["VERSION"][0]["PROD"].split("-")
|
_data = version["VERSION"][0]["PROD"].split("-")
|
||||||
model = _data[0]
|
model = _data[0].upper()
|
||||||
if len(data) > 1:
|
if len(data) > 1:
|
||||||
ver = _data[1]
|
ver = _data[1]
|
||||||
elif version["VERSION"][0].get("MODEL"):
|
elif version["VERSION"][0].get("MODEL"):
|
||||||
@@ -522,11 +528,24 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
):
|
):
|
||||||
# try to get "Type" which is model
|
# try to get "Type" which is model
|
||||||
if version["VERSION"][0].get("Type"):
|
if version["VERSION"][0].get("Type"):
|
||||||
model = version["VERSION"][0]["Type"]
|
model = version["VERSION"][0]["Type"].upper()
|
||||||
|
|
||||||
# braiins OS bug check just in case
|
# braiins OS bug check just in case
|
||||||
elif "am2-s17" in version["STATUS"][0]["Description"]:
|
elif "am2-s17" in version["STATUS"][0]["Description"]:
|
||||||
model = "Antminer S17"
|
model = "ANTMINER S17"
|
||||||
|
|
||||||
|
if not model:
|
||||||
|
stats = await self._send_api_command(str(ip), "stats")
|
||||||
|
if stats:
|
||||||
|
if "STATS" in stats.keys():
|
||||||
|
if stats["STATS"][0].get("Type"):
|
||||||
|
_model = stats["STATS"][0]["Type"].upper()
|
||||||
|
if " BB" in _model:
|
||||||
|
_model = _model.split(" BB")[0]
|
||||||
|
if " XILINX" in _model:
|
||||||
|
_model = _model.split(" XILINX")[0]
|
||||||
|
if "PRO" in _model and not " PRO" in _model:
|
||||||
|
model = _model.replace("PRO", " PRO")
|
||||||
|
|
||||||
if model:
|
if model:
|
||||||
# whatsminer have a V in their version string (M20SV41), remove everything after it
|
# whatsminer have a V in their version string (M20SV41), remove everything after it
|
||||||
@@ -535,9 +554,9 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
if len(_ver) > 1:
|
if len(_ver) > 1:
|
||||||
ver = model.split("V")[1]
|
ver = model.split("V")[1]
|
||||||
model = model.split("V")[0]
|
model = model.split("V")[0]
|
||||||
# don't need "Bitmain", just "Antminer XX" as model
|
# don't need "Bitmain", just "ANTMINER XX" as model
|
||||||
if "Bitmain " in model:
|
if "BITMAIN " in model:
|
||||||
model = model.replace("Bitmain ", "")
|
model = model.replace("BITMAIN ", "")
|
||||||
return model, api, ver
|
return model, api, ver
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -31,3 +31,29 @@ class UnknownMiner(BaseMiner):
|
|||||||
|
|
||||||
async def get_hostname(self):
|
async def get_hostname(self):
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
|
async def check_light(self) -> bool:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_config(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_errors(self) -> list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_mac(self) -> str:
|
||||||
|
return "00:00:00:00:00:00"
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
return False
|
||||||
|
|||||||
31
pyasic/miners/whatsminer/btminer/M2X/M20.py
Normal file
31
pyasic/miners/whatsminer/btminer/M2X/M20.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
||||||
|
from pyasic.miners._types import ( # noqa - Ignore access to _module
|
||||||
|
M20,
|
||||||
|
M20V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20(BTMiner, M20):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20V10(BTMiner, M20V10):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from .M20 import BTMinerM20, BTMinerM20V10
|
||||||
from .M20S import BTMinerM20S, BTMinerM20SV10, BTMinerM20SV20
|
from .M20S import BTMinerM20S, BTMinerM20SV10, BTMinerM20SV20
|
||||||
from .M20S_Plus import BTMinerM20SPlus
|
from .M20S_Plus import BTMinerM20SPlus
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class PyasicSettings(metaclass=Singleton):
|
|||||||
miner_get_data_retries: int = 1
|
miner_get_data_retries: int = 1
|
||||||
|
|
||||||
global_whatsminer_password = "admin"
|
global_whatsminer_password = "admin"
|
||||||
|
global_x19_password = "root"
|
||||||
|
global_x17_password = "root"
|
||||||
|
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
logfile: bool = False
|
logfile: bool = False
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from pyasic.tests.miners_tests import MinersTest
|
||||||
|
from pyasic.tests.network_tests import NetworkTest
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
54
pyasic/tests/miners_tests/__init__.py
Normal file
54
pyasic/tests/miners_tests/__init__.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from pyasic.miners.miner_factory import MINER_CLASSES
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class MinersTest(unittest.TestCase):
|
||||||
|
def test_miner_model_creation(self):
|
||||||
|
for miner_model in MINER_CLASSES.keys():
|
||||||
|
for miner_api in MINER_CLASSES[miner_model].keys():
|
||||||
|
with self.subTest(miner_model=miner_model, miner_api=miner_api):
|
||||||
|
miner = MINER_CLASSES[miner_model][miner_api]("0.0.0.0")
|
||||||
|
self.assertTrue(
|
||||||
|
isinstance(miner, MINER_CLASSES[miner_model][miner_api])
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_miner_backend_backup_creation(self):
|
||||||
|
backends = inspect.getmembers(
|
||||||
|
sys.modules["pyasic.miners._backends"], inspect.isclass
|
||||||
|
)
|
||||||
|
for backend in backends:
|
||||||
|
miner_class = backend[1]
|
||||||
|
with self.subTest(miner_class=miner_class):
|
||||||
|
miner = miner_class("0.0.0.0")
|
||||||
|
self.assertTrue(isinstance(miner, miner_class))
|
||||||
|
|
||||||
|
def test_miner_type_creation_failure(self):
|
||||||
|
backends = inspect.getmembers(
|
||||||
|
sys.modules["pyasic.miners._types"], inspect.isclass
|
||||||
|
)
|
||||||
|
for backend in backends:
|
||||||
|
miner_class = backend[1]
|
||||||
|
with self.subTest(miner_class=miner_class):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
miner_class("0.0.0.0")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.13.1"
|
version = "0.15.0"
|
||||||
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
||||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||||
repository = "https://github.com/UpstreamData/pyasic"
|
repository = "https://github.com/UpstreamData/pyasic"
|
||||||
|
|||||||
Reference in New Issue
Block a user