Compare commits

..

31 Commits

Author SHA1 Message Date
Colin Crossman
67b3e2f312 version: bump version number 2022-12-19 08:42:56 -07:00
Arceris
82006de30f Merge dev branch into main (#25) 2022-12-19 08:35:29 -07:00
Arceris
5bde9d7fe6 Improvement to fault_light_on
Allow user to set a flash pattern, and if not, set a nice default flash pattern.
2022-12-18 16:30:06 -07:00
Arceris
f7cd428366 updates to set_led
Fix typo in param=auto, and set better raw defaults for flashing
2022-12-18 16:27:34 -07:00
Colin Crossman
d5e797de9e Squash Whatsminer LED bug
old implementation broke command syntax due to since required command parameters would be left off in certain cases.
2022-12-13 11:42:36 -07:00
UpstreamData
b9d5e7b206 version: bump version number. 2022-12-13 10:05:55 -07:00
UpstreamData
2d8c7eb4fd feature: add support for whatsminer M30S+ VG40 2022-12-13 10:05:07 -07:00
UpstreamData
f69e07fe68 version: bump version number. 2022-12-05 09:49:03 -07:00
UpstreamData
84aab38954 bug: properly shorten stratum URLs when gathering data from graphql. 2022-12-05 09:48:40 -07:00
UpstreamData
dcf37481bd version: bump version number. 2022-12-05 09:35:44 -07:00
UpstreamData
1a9cca84d5 bug: fix pool split not being found correctly with braiinsOS. 2022-12-05 09:34:43 -07:00
UpstreamData
c5272d67de version: bump version number 2022-12-03 14:20:57 -07:00
UpstreamData
3bcfb14177 feature: add support for Whatsminer M31SV20, and fix a bug with miner factory not identifying the miners properly by removing a V prefix. 2022-12-03 14:20:37 -07:00
UpstreamData
566280f280 docs: fix some missing data in the docs. 2022-12-02 16:10:30 -07:00
UpstreamData
a814f7eefb Update README.md 2022-12-02 16:06:22 -07:00
UpstreamData
097b8ed534 version: bump version number. 2022-12-02 15:57:52 -07:00
UpstreamData
da47d72749 feature: add wattage limit in get_config when getting config from whatsminers. 2022-12-02 15:57:31 -07:00
UpstreamData
abd4d18a01 feature: add whatsminer M31SV10 and V60. 2022-12-02 15:51:27 -07:00
UpstreamData
2adbce3c21 version: bump version number. 2022-12-02 09:26:53 -07:00
UpstreamData
c41324b324 bug: fix a bug with MinerAPI.commands causing an infinite recursion loop when checking a list of commands with get_commands, and ficx some weirdness where BTMiner doesnt return any data. 2022-12-02 09:26:17 -07:00
UpstreamData
151a4f6c2d version: bump version number. 2022-12-01 16:18:07 -07:00
UpstreamData
d23777a83f feature: Switch to using semaphores in miner network to rate limit as they are much more friendly. 2022-12-01 16:17:46 -07:00
UpstreamData
bb0aee8337 version: bump version number. 2022-12-01 15:54:09 -07:00
UpstreamData
2a23acb73a bug: fix BTMiner not responding to pause_mining and resume_mining causing an exception. 2022-12-01 15:53:47 -07:00
UpstreamData
ad5eb0cef6 bug: fix a bug with updated logging. 2022-12-01 15:40:40 -07:00
UpstreamData
07dd8f55fe format: improve logging. 2022-12-01 15:02:17 -07:00
UpstreamData
bafa91cc47 version: bump version number. 2022-11-30 10:48:20 -07:00
UpstreamData
16399510fa feature: add set_power_limit to all miners that support it. 2022-11-30 10:47:45 -07:00
UpstreamData
e9f54eec0a version: bump version number. 2022-11-30 10:01:27 -07:00
UpstreamData
fbbbc9f215 bug: Add VH60 to miner factory as it was missing. 2022-11-30 10:01:01 -07:00
UpstreamData
69e4f575c0 bug: fix a bug wth bosminer where it will sometimes not get data from graphql 2022-11-30 09:59:12 -07:00
149 changed files with 1355 additions and 1596 deletions

View File

@@ -46,7 +46,7 @@ from pyasic.network import MinerNetwork
async def scan_and_get_data():
# Define network range to be used for scanning
# This can take a list of IPs, a constructor string, or an IP and subnet mask
# The standard mask is /24, and you can pass any IP address in the subnet
# The standard mask is /24 (x.x.x.0-255), and you can pass any IP address in the subnet
net = MinerNetwork("192.168.1.69", mask=24)
# Scan the network for miners
# This function returns a list of miners of the correct type as a class
@@ -93,6 +93,10 @@ if __name__ == "__main__":
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
You can see more information on basic usage of the APIs past this example in the docs [here](https://pyasic.readthedocs.io/en/latest/API/api/).
Please see the appropriate API documentation page (pyasic docs -> Advanced -> Miner APIs -> your API type) for a link to that specific miner's API documentation page and more information.
#### List available API commands
```python
import asyncio
@@ -105,7 +109,8 @@ async def get_api_commands(miner_ip: str):
miner = await get_miner(miner_ip)
# List all available commands
print(miner.api.get_commands())
# Can also be called explicitly with the function miner.api.get_commands()
print(miner.api.commands)
if __name__ == "__main__":

View File

@@ -117,10 +117,11 @@ These functions are
[`get_hostname`](#get-hostname),
[`get_model`](#get-model),
[`reboot`](#reboot),
[`restart_backend`](#restart-backend), and
[`stop_mining`](#stop-mining), and
[`resume_mining`](#resume-mining), and
[`send_config`](#send-config).
[`restart_backend`](#restart-backend),
[`stop_mining`](#stop-mining),
[`resume_mining`](#resume-mining),
[`send_config`](#send-config), and
[`set_power_limit`](#set-power-limit).
<br>
@@ -228,6 +229,14 @@ These functions are
<br>
### Set Power Limit
::: pyasic.miners.BaseMiner.set_power_limit
handler: python
options:
heading_level: 4
<br>
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.

View File

@@ -77,6 +77,7 @@ details {
<ul>
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
</ul>
</details>
@@ -90,6 +91,9 @@ details {
</details>
<details>
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv10">M31SV10</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv20">M31SV20</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv60">M31SV60</a></summary>
<summary><a href="../whatsminer/M3X/#m31sv70">M31SV70</a></summary>
</details>
<details>

View File

@@ -65,6 +65,14 @@
show_root_heading: false
heading_level: 4
## M30S+VG40
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG40
handler: python
options:
show_root_heading: false
heading_level: 4
## M30S+VG60
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60
@@ -114,9 +122,33 @@
show_root_heading: false
heading_level: 4
## M31SV10
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV20
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV20
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV60
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV60
handler: python
options:
show_root_heading: false
heading_level: 4
## M31SV70
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV70
handler: python
options:
show_root_heading: false

View File

@@ -35,6 +35,81 @@ class BaseMinerAPI:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
return object.__new__(cls)
def __repr__(self):
return f"{self.__class__.__name__}: {str(self.ip)}"
async def send_command(
self,
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
allow_warning: bool = True,
) -> dict:
"""Send an API command to the miner and return the result.
Parameters:
command: The command to sent to the miner.
parameters: Any additional parameters to be sent with the command.
ignore_errors: Whether to raise APIError when the command returns an error.
allow_warning: Whether to warn if the command fails.
Returns:
The return data from the API command parsed from JSON into a dict.
"""
logging.debug(f"{self} - (Send Privileged Command) - {command} " + f'with args {parameters}' if parameters else '')
# create the command
cmd = {"command": command}
if parameters:
cmd["parameter"] = parameters
# send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
data = self._load_api_data(data)
# check for if the user wants to allow errors to return
if not ignore_errors:
# validate the command succeeded
validation = self._validate_command_output(data)
if not validation[0]:
if allow_warning:
logging.warning(
f"{self.ip}: API Command Error: {command}: {validation[1]}"
)
raise APIError(validation[1])
logging.debug(f"{self} - (Send Command) - Received data.")
return data
# Privileged command handler, only used by whatsminers, defined here for consistency.
async def send_privileged_command(self, *args, **kwargs) -> dict:
return await self.send_command(*args, **kwargs)
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
"""Creates and sends multiple commands as one command to the miner.
Parameters:
*commands: The commands to send as a multicommand to the miner.
allow_warning: A boolean to supress APIWarnings.
"""
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
# standard format doesn't work for X19
command = "+".join(commands)
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
return {}
logging.debug(f"{self} - (Multicommand) - Received data")
return data
@property
def commands(self) -> list:
return self.get_commands()
def get_commands(self) -> list:
"""Get a list of command accessible to a specific type of API on the miner.
@@ -46,6 +121,7 @@ class BaseMinerAPI:
for func in
# each function in self
dir(self)
if not func == "commands"
if callable(getattr(self, func)) and
# no __ or _ methods
not func.startswith("__") and not func.startswith("_") and
@@ -72,103 +148,49 @@ If you are sure you want to use this command please use API.send_command("{comma
)
return return_commands
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
"""Creates and sends multiple commands as one command to the miner.
Parameters:
*commands: The commands to send as a multicommand to the miner.
allow_warning: A boolean to supress APIWarnings.
"""
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
# doesn't work for S19 which uses the backup _x19_multicommand
command = "+".join(commands)
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
return {}
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _send_bytes(self, data: bytes) -> bytes:
async def _send_bytes(self, data: bytes, timeout: int=100) -> bytes:
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
# handle OSError 121
except OSError as e:
if getattr(e, "winerror") == "121":
logging.warning("Semaphore Timeout has Expired.")
logging.warning(f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired.")
return b"{}"
# send the command
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
writer.write(data)
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
await writer.drain()
# instantiate data
ret_data = b""
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
try:
while True:
d = await reader.read(4096)
if not d:
break
ret_data += d
try:
d = await asyncio.wait_for(reader.read(4096), timeout=timeout)
if not d:
break
ret_data += d
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except Exception as e:
logging.warning(f"{self.ip}: API Command Error: - {e}")
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
# close the connection
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
writer.close()
await writer.wait_closed()
return ret_data
async def send_command(
self,
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
allow_warning: bool = True,
) -> dict:
"""Send an API command to the miner and return the result.
Parameters:
command: The command to sent to the miner.
parameters: Any additional parameters to be sent with the command.
ignore_errors: Whether to raise APIError when the command returns an error.
allow_warning: Whether to warn if the command fails.
Returns:
The return data from the API command parsed from JSON into a dict.
"""
# create the command
cmd = {"command": command}
if parameters:
cmd["parameter"] = parameters
# send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
data = self._load_api_data(data)
# check for if the user wants to allow errors to return
if not ignore_errors:
# validate the command succeeded
validation = self._validate_command_output(data)
if not validation[0]:
if allow_warning:
logging.warning(
f"{self.ip}: API Command Error: {command}: {validation[1]}"
)
raise APIError(validation[1])
return data
async def send_privileged_command(self, *args, **kwargs) -> dict:
return await self.send_command(*args, **kwargs)
@staticmethod
def _validate_command_output(data: dict) -> tuple:
# check if the data returned is correct or an error

View File

@@ -34,13 +34,13 @@ class BMMinerAPI(BaseMinerAPI):
port: The port to reference the API on. Default is 4028.
"""
def __init__(self, ip: str, port: int = 4028) -> None:
super().__init__(ip, port)
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028) -> None:
super().__init__(ip, port=port)
self.api_ver = api_ver
async def multicommand(
self, *commands: str, allow_warning: bool = True
) -> dict:
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
@@ -49,9 +49,8 @@ class BMMinerAPI(BaseMinerAPI):
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
logging.debug(f"{self.ip}: Handling X19 multicommand.")
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
data = await self._x19_multicommand(*command.split("+"))
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _x19_multicommand(self, *commands):
@@ -65,7 +64,7 @@ class BMMinerAPI(BaseMinerAPI):
except APIError as e:
raise APIError(e)
except Exception as e:
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
logging.warning(f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}")
return data
async def version(self) -> dict:

View File

@@ -33,8 +33,10 @@ class BOSMinerAPI(BaseMinerAPI):
port: The port to reference the API on. Default is 4028.
"""
def __init__(self, ip: str, port: int = 4028):
super().__init__(ip, port)
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028) -> None:
super().__init__(ip, port=port)
self.api_ver = api_ver
async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info.
@@ -166,7 +168,7 @@ class BOSMinerAPI(BaseMinerAPI):
return await self.send_command("estats")
async def check(self, command: str) -> dict:
"""Check if the command command exists in BOSMiner.
"""Check if the command `command` exists in BOSMiner.
<details>
<summary>Expand</summary>

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import base64
import binascii
import hashlib
@@ -26,13 +27,14 @@ from passlib.handlers.md5_crypt import md5_crypt
from pyasic.API import BaseMinerAPI
from pyasic.errors import APIError
from pyasic.settings import PyasicSettings
from pyasic.misc import api_min_version
### IMPORTANT ###
# you need to change the password of the miners using the Whatsminer
# tool, then you can set them back to admin with this tool, but they
# must be changed to something else and set back to admin with this
# or the privileged API will not work using admin as the password. If
# you change the password, you can pass that to the this class as pwd,
# you change the password, you can pass that to this class as pwd,
# or add it as the Whatsminer_pwd in the settings.toml file.
@@ -81,7 +83,7 @@ def _add_to_16(string: str) -> bytes:
def parse_btminer_priviledge_data(token_data: dict, data: dict):
"""Parses data returned from the BTMiner privileged API.
Parses data from the BTMiner privileged API using the the token
Parses data from the BTMiner privileged API using the token
from the API in an AES format.
Parameters:
@@ -123,6 +125,7 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
Returns:
The encrypted privileged command to be sent to the miner.
"""
logging.debug(f"(Create Prilileged Command) - Creating Privileged Command")
# add token to command
command["token"] = token_data["host_sign"]
# encode host_passwd data and get hexdigest
@@ -178,25 +181,39 @@ class BTMinerAPI(BaseMinerAPI):
def __init__(
self,
ip: str,
api_ver: str = "1.0.0",
port: int = 4028,
pwd: str = PyasicSettings().global_whatsminer_password,
):
super().__init__(ip, port)
self.pwd = pwd
self.current_token = None
self.api_ver = api_ver
async def send_privileged_command(
self, command: Union[str, bytes], ignore_errors: bool = False, **kwargs
self, command: Union[str, bytes], ignore_errors: bool = False, timeout: int = 10, **kwargs
) -> dict:
command = {"cmd": command}
for kwarg in kwargs:
if kwargs[kwarg]:
command[kwarg] = kwargs[kwarg]
logging.debug(f"{self} - (Send Privileged Command) - {command} " + f'with args {kwargs}' if len(kwargs) > 0 else '')
command = {"cmd": command, **kwargs}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
data = await self._send_bytes(enc_command)
logging.debug(f"{self} - (Send Privileged Command) - Sending")
try:
data = await self._send_bytes(enc_command, timeout)
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
if command['cmd'] in ['reboot', 'restart']:
logging.info(f"{self} - (reboot/restart) - Whatsminers currently break this. "
f"Ignoring exception. Command probably worked.")
# FAKING IT HERE
data = b'{"STATUS": "S", "When": 1670966423, "Code": 131, "Msg": "API command OK", "Description": "Reboot"}'
else:
raise APIError("No data was returned from the API.")
if not data:
raise APIError("No data was returned from the API.")
data = self._load_api_data(data)
try:
@@ -222,6 +239,7 @@ class BTMinerAPI(BaseMinerAPI):
An encoded token and md5 password, which are used for the privileged API.
</details>
"""
logging.debug(f"{self} - (Get Token) - Getting token")
# get the token
data = await self.send_command("get_token")
@@ -244,6 +262,7 @@ class BTMinerAPI(BaseMinerAPI):
"host_sign": host_sign,
"host_passwd_md5": host_passwd_md5,
}
logging.debug(f"{self} - (Get Token) - Gathered token data: {self.current_token}")
return self.current_token
#### PRIVILEGED COMMANDS ####
@@ -364,8 +383,8 @@ class BTMinerAPI(BaseMinerAPI):
self,
auto: bool = True,
color: str = "red",
period: int = 60,
duration: int = 20,
period: int = 400,
duration: int = 200,
start: int = 0,
) -> dict:
"""Set the LED on the miner using the API.
@@ -387,7 +406,7 @@ class BTMinerAPI(BaseMinerAPI):
</details>
"""
if auto:
return await self.send_privileged_command("set_led", param=auto)
return await self.send_privileged_command("set_led", param="auto")
return await self.send_privileged_command(
"set_led", color=color, period=period, duration=duration, start=start
)
@@ -413,7 +432,7 @@ class BTMinerAPI(BaseMinerAPI):
# requires a file stream in bytes
return NotImplementedError
async def reboot(self) -> dict:
async def reboot(self, timeout: int = 10) -> dict:
"""Reboot the miner using the API.
<details>
<summary>Expand</summary>
@@ -423,7 +442,12 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of the reboot.
</details>
"""
return await self.send_privileged_command("reboot")
try:
d = await asyncio.wait_for(self.send_privileged_command("reboot"), timeout=timeout)
except (asyncio.CancelledError, asyncio.TimeoutError):
return {}
else:
return d
async def factory_reset(self) -> dict:
"""Reset the miner to factory defaults.
@@ -642,6 +666,7 @@ class BTMinerAPI(BaseMinerAPI):
)
### ADDED IN V2.0.5 Whatsminer API ###
@api_min_version("2.0.5")
async def set_temp_offset(self, temp_offset: int):
"""Set the offset of miner hash board target temperature.
@@ -668,6 +693,7 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_privileged_command("set_temp_offset", temp_offset=temp_offset)
@api_min_version("2.0.5")
async def adjust_power_limit(self, power_limit: int):
"""Set the upper limit of the miner's power. Cannot be higher than the ordinary power of the machine.
@@ -689,6 +715,7 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_privileged_command("adjust_power_limit", power_limit=power_limit)
@api_min_version("2.0.5")
async def adjust_upfreq_speed(self, upfreq_speed: int):
"""Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed.
@@ -715,6 +742,7 @@ class BTMinerAPI(BaseMinerAPI):
)
return await self.send_privileged_command("adjust_upfreq_speed", upfreq_speed=upfreq_speed)
@api_min_version("2.0.5")
async def set_poweroff_cool(self, poweroff_cool: bool):
"""Set whether to cool the machine when mining is stopped.
@@ -734,6 +762,7 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_privileged_command("set_poweroff_cool", poweroff_cool=int(poweroff_cool))
@api_min_version("2.0.5")
async def set_fan_zero_speed(self, fan_zero_speed: bool):
"""Sets whether the fan speed supports the lowest 0 speed.
@@ -880,6 +909,7 @@ class BTMinerAPI(BaseMinerAPI):
"""
return await self.send_command("get_miner_info", allow_warning=False)
@api_min_version("2.0.1")
async def get_error_code(self) -> dict:
"""Get a list of error codes from the miner.

View File

@@ -35,24 +35,24 @@ class CGMinerAPI(BaseMinerAPI):
port: The port to reference the API on. Default is 4028.
"""
def __init__(self, ip: str, port: int = 4028):
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028):
super().__init__(ip, port)
self.api_ver = api_ver
async def multicommand(
self, *commands: str, allow_warning: bool = True
) -> dict:
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
# doesnt work for S19 which uses the backup _x19_multicommand
# doesn't work for S19 which uses the backup _x19_multicommand
command = "+".join(commands)
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
logging.debug(f"{self.ip}: Handling X19 multicommand.")
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
data = await self._x19_multicommand(*command.split("+"))
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _x19_multicommand(self, *commands):
@@ -66,7 +66,7 @@ class CGMinerAPI(BaseMinerAPI):
except APIError as e:
raise APIError(e)
except Exception as e:
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
logging.warning(f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}")
return data
async def version(self) -> dict:

View File

@@ -18,13 +18,15 @@ from pyasic.API import BaseMinerAPI
class UnknownAPI(BaseMinerAPI):
"""An abstraction of an API for a miner which is unknown.
This class is designed to try to be a intersection of as many miner APIs
This class is designed to try to be an intersection of as many miner APIs
and API commands as possible (API ⋂ API), to ensure that it can be used
with as many APIs as possible.
"""
def __init__(self, ip, port=4028):
def __init__(self, ip, api_ver: str = "1.0.0", port: int = 4028):
super().__init__(ip, port)
self.api_ver = api_ver
async def asccount(self) -> dict:
return await self.send_command("asccount")

View File

@@ -13,11 +13,12 @@
# limitations under the License.
import json
import logging
import random
import string
import time
from dataclasses import asdict, dataclass, fields
from typing import List, Literal, Dict
from typing import Dict, List, Literal
import toml
import yaml
@@ -277,6 +278,7 @@ class MinerConfig:
def as_dict(self) -> dict:
"""Convert the data in this class to a dict."""
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
data_dict = asdict(self)
for key in asdict(self).keys():
if data_dict[key] is None:
@@ -285,10 +287,12 @@ class MinerConfig:
def as_toml(self) -> str:
"""Convert the data in this class to toml."""
logging.debug(f"MinerConfig - (To TOML) - Dumping TOML config")
return toml.dumps(self.as_dict())
def as_yaml(self) -> str:
"""Convert the data in this class to yaml."""
logging.debug(f"MinerConfig - (To YAML) - Dumping YAML config")
return yaml.dump(self.as_dict(), sort_keys=False)
def from_raw(self, data: dict):
@@ -298,6 +302,7 @@ class MinerConfig:
Parameters:
data: The raw config data to convert.
"""
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
pool_groups = []
for key in data.keys():
if key == "pools":
@@ -360,6 +365,12 @@ class MinerConfig:
return self
def from_api(self, pools: list):
"""Convert list output from the `AnyMiner.api.pools()` command into a usable data and save it to this class.
Parameters:
pools: The list of pool data to convert.
"""
logging.debug(f"MinerConfig - (From API) - Loading API config")
_pools = []
for pool in pools:
url = pool.get("URL")
@@ -374,6 +385,7 @@ class MinerConfig:
Parameters:
data: The dict config data to convert.
"""
logging.debug(f"MinerConfig - (From Dict) - Loading Dict config")
pool_groups = []
for group in data["pool_groups"]:
pool_groups.append(_PoolGroup().from_dict(group))
@@ -389,6 +401,7 @@ class MinerConfig:
Parameters:
data: The toml config data to convert.
"""
logging.debug(f"MinerConfig - (From TOML) - Loading TOML config")
return self.from_dict(toml.loads(data))
def from_yaml(self, data: str):
@@ -397,6 +410,7 @@ class MinerConfig:
Parameters:
data: The yaml config data to convert.
"""
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
def as_wm(self, user_suffix: str = None) -> Dict[str, int]:
@@ -405,6 +419,7 @@ class MinerConfig:
Parameters:
user_suffix: The suffix to append to username.
"""
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
return {"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix), "wattage": self.autotuning_wattage}
def as_inno(self, user_suffix: str = None) -> dict:
@@ -413,6 +428,7 @@ class MinerConfig:
Parameters:
user_suffix: The suffix to append to username.
"""
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
def as_x19(self, user_suffix: str = None) -> str:
@@ -421,6 +437,7 @@ class MinerConfig:
Parameters:
user_suffix: The suffix to append to username.
"""
logging.debug(f"MinerConfig - (As X19) - Generating X19 config")
cfg = {
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
"bitmain-fan-ctrl": False,
@@ -444,6 +461,7 @@ class MinerConfig:
Parameters:
user_suffix: The suffix to append to username.
"""
logging.debug(f"MinerConfig - (As Avalon) - Generating AvalonMiner config")
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
return cfg
@@ -454,6 +472,7 @@ class MinerConfig:
model: The model of the miner to be used in the format portion of the config.
user_suffix: The suffix to append to username.
"""
logging.debug(f"MinerConfig - (As BOS) - Generating BOSMiner config")
cfg = {
"format": {
"version": "1.2+",

View File

@@ -14,6 +14,7 @@
import copy
import json
import logging
import time
from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone
@@ -41,6 +42,9 @@ class MinerData:
ip: The IP of the miner as a str.
datetime: The time and date this data was generated.
model: The model of the miner as a str.
make: The make of the miner as a str.
api_ver: The current api version on the miner as a str.
fw_ver: The current firmware version on the miner as a str.
hostname: The network hostname of the miner as a str.
hashrate: The hashrate of the miner in TH/s as a float.
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
@@ -82,6 +86,9 @@ class MinerData:
datetime: datetime = None
mac: str = "00:00:00:00:00:00"
model: str = "Unknown"
make: str = "Unknown"
api_ver: str = "Unknown"
fw_ver: str = "Unknown"
hostname: str = "Unknown"
hashrate: float = 0
hashboards: List[HashBoard] = field(default_factory=list)
@@ -122,6 +129,7 @@ class MinerData:
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
@classmethod
def fields(cls):
return [f.name for f in fields(cls)]
@@ -371,6 +379,7 @@ class MinerData:
Returns:
A dictionary version of this class.
"""
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
return asdict(self)
def as_json(self) -> str:
@@ -379,6 +388,7 @@ class MinerData:
Returns:
A JSON version of this class.
"""
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data)
@@ -389,6 +399,7 @@ class MinerData:
Returns:
A CSV version of this class with no headers.
"""
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
errs = []
@@ -407,6 +418,7 @@ class MinerData:
Returns:
A influxdb line protocol version of this class.
"""
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
tag_data = [measurement_name]
field_data = []

View File

@@ -17,3 +17,4 @@ from .bosminer import BOSMiner
from .btminer import BTMiner
from .cgminer import CGMiner
from .hiveon import Hiveon
from .cgminer_avalon import CGMinerAvalon

View File

@@ -27,11 +27,12 @@ from pyasic.settings import PyasicSettings
class BMMiner(BaseMiner):
"""Base handler for BMMiner based miners."""
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = BMMinerAPI(ip)
self.api = BMMinerAPI(ip, api_ver)
self.api_type = "BMMiner"
self.api_ver = api_ver
self.uname = "root"
self.pwd = "admin"
@@ -167,6 +168,24 @@ class BMMiner(BaseMiner):
async def fault_light_on(self) -> bool:
return False
async def get_version(self) -> dict:
"""Get miner firmware version.
Returns:
Miner api & firmware version or None.
"""
# check if version is cached
if self.fw_ver and self.api_ver:
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
# Now get the API version
version = await self.api.version()
self.api_ver = version['VERSION'][0]['API']
self.fw_ver = version['VERSION'][0]['CompileTime']
self.api.api_ver = self.api_ver
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
async def get_errors(self) -> List[MinerErrorData]:
return []
@@ -211,6 +230,13 @@ class BMMiner(BaseMiner):
if mac:
data.mac = mac
if self.make:
data.make = self.make
await self.get_version()
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
if errors:
for error in errors:
data.errors.append(error)
@@ -349,3 +375,7 @@ class BMMiner(BaseMiner):
data.pool_split = str(quota)
return data
async def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -17,8 +17,8 @@ import json
import logging
from typing import List, Union
import toml
import httpx
import toml
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig
@@ -30,11 +30,12 @@ from pyasic.settings import PyasicSettings
class BOSMiner(BaseMiner):
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = BOSMinerAPI(ip)
self.api = BOSMinerAPI(ip, api_ver)
self.api_type = "BOSMiner"
self.api_ver = api_ver
self.uname = "root"
self.pwd = "admin"
self.config = None
@@ -228,21 +229,25 @@ class BOSMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}")
return None
async def get_version(self) -> Union[str, None]:
async def get_version(self) -> Union[dict, None]:
"""Get miner firmware version.
Returns:
Miner firmware version or None.
Miner api & firmware version or None.
"""
# check if version is cached
if self.version:
logging.debug(f"Found version for {self.ip}: {self.version}")
return self.version
if self.fw_ver and self.api_ver:
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
version_data = None
# try to get data from graphql
data = await self.send_graphql_query("{bos{info{version{full}}}}")
if data:
version_data = data["bos"]["info"]["version"]["full"]
try:
version_data = data["bos"]["info"]["version"]["full"]
except KeyError:
version_data = data["data"]["bos"]["info"]["version"]["full"]
if not version_data:
# try version data file
@@ -250,9 +255,14 @@ class BOSMiner(BaseMiner):
# if we get the version data, parse it
if version_data:
self.version = version_data.split("-")[5]
logging.debug(f"Found version for {self.ip}: {self.version}")
return self.version
self.fw_ver = version_data.split("-")[5]
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
# Now get the API version
version = await self.api.version()
self.api_ver = version['VERSION'][0]['API']
self.api.api_ver = self.api_ver
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
# if we fail to get version, log a failed attempt
logging.warning(f"Failed to get model for miner: {self}")
@@ -327,7 +337,7 @@ class BOSMiner(BaseMiner):
if board["Status"] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips"
"Tuning individual chips",
]:
_error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:]
@@ -359,6 +369,7 @@ class BOSMiner(BaseMiner):
model = await self.get_model()
hostname = await self.get_hostname()
mac = await self.get_mac()
await self.get_version()
if model:
data.model = model
@@ -369,6 +380,11 @@ class BOSMiner(BaseMiner):
if mac:
data.mac = mac
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
data.make = self.make
data.fault_light = await self.check_light()
miner_data = None
@@ -471,6 +487,16 @@ class BOSMiner(BaseMiner):
data.pool_2_user = pool_2_user
if quota:
if not quota == "0":
cfg = await self.get_config()
if cfg:
if len(cfg.pool_groups) > 1:
quota = (
str(cfg.pool_groups[0].quota)
+ "/"
+ str(cfg.pool_groups[1].quota)
)
data.pool_split = str(quota)
if tunerstatus:
@@ -548,17 +574,29 @@ class BOSMiner(BaseMiner):
if not query_data:
return None
query_data = query_data["data"]
if not query_data:
return None
data.mac = await self.get_mac()
data.model = await self.get_model()
await self.get_version()
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
data.make = self.make
if query_data.get("bos"):
if query_data["bos"].get("hostname"):
data.hostname = query_data["bos"]["hostname"]
try:
if query_data["bosminer"]["info"]["workSolver"]["realHashrate"].get("mhs1M"):
if query_data["bosminer"]["info"]["workSolver"]["realHashrate"].get(
"mhs1M"
):
data.hashrate = round(
query_data["bosminer"]["info"]["workSolver"]["realHashrate"]["mhs1M"]
query_data["bosminer"]["info"]["workSolver"]["realHashrate"][
"mhs1M"
]
/ 1000000,
2,
)
@@ -569,9 +607,13 @@ class BOSMiner(BaseMiner):
if query_data.get("bosminer"):
if query_data["bosminer"].get("info"):
if query_data["bosminer"]["info"].get("workSolver"):
boards = query_data["bosminer"]["info"]["workSolver"].get("childSolvers")
boards = query_data["bosminer"]["info"]["workSolver"].get(
"childSolvers"
)
if boards:
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else int(boards[0]["name"])
offset = (
6 if int(boards[0]["name"]) in [6, 7, 8] else int(boards[0]["name"])
)
for hb in boards:
_id = int(hb["name"]) - offset
@@ -598,24 +640,33 @@ class BOSMiner(BaseMiner):
if hb["tuner"]["statusMessages"][0] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips"
"Tuning individual chips",
]:
data.errors.append(
BraiinsOSError(f"Slot {_id} {hb['tuner']['statusMessages'][0]}")
BraiinsOSError(
f"Slot {_id} {hb['tuner']['statusMessages'][0]}"
)
)
try:
data.wattage = query_data["bosminer"]["info"]["workSolver"]["power"]["approxConsumptionW"]
data.wattage = query_data["bosminer"]["info"]["workSolver"]["power"][
"approxConsumptionW"
]
except (TypeError, KeyError, ValueError, IndexError):
data.wattage = 0
try:
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"]["limitW"]
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"][
"limitW"
]
except (TypeError, KeyError, ValueError, IndexError):
pass
for n in range(self.fan_count):
try:
setattr(data, f"fan_{n + 1}", query_data["bosminer"]["info"]["fans"][n]["rpm"])
setattr(
data,
f"fan_{n + 1}",
query_data["bosminer"]["info"]["fans"][n]["rpm"],
)
except (TypeError, KeyError, ValueError, IndexError):
pass
@@ -630,7 +681,11 @@ class BOSMiner(BaseMiner):
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
data.pool_1_url = (
groups[0]["pools"][0]["url"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
@@ -638,7 +693,11 @@ class BOSMiner(BaseMiner):
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[0]["pools"][1]["url"]
data.pool_2_url = (
groups[0]["pools"][1]["url"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
except (TypeError, KeyError, ValueError, IndexError):
pass
data.quota = 0
@@ -648,7 +707,11 @@ class BOSMiner(BaseMiner):
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
data.pool_1_url = (
groups[0]["pools"][0]["url"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
@@ -656,11 +719,19 @@ class BOSMiner(BaseMiner):
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[1]["pools"][0]["url"]
data.pool_2_url = (
groups[1]["pools"][0]["url"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
except (TypeError, KeyError, ValueError, IndexError):
pass
if groups[0]["strategy"].get("quota"):
data.quota = str(groups[0]["strategy"]["quota"]) + "/" + str(groups[1]["strategy"]["quota"])
data.pool_split = (
str(groups[0]["strategy"]["quota"])
+ "/"
+ str(groups[1]["strategy"]["quota"])
)
data.fault_light = await self.check_light()
@@ -669,3 +740,14 @@ class BOSMiner(BaseMiner):
async def get_mac(self):
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
return result.upper().strip()
async def set_power_limit(self, wattage: int) -> bool:
try:
cfg = await self.get_config()
cfg.autotuning_wattage = wattage
await self.send_config(cfg)
except Exception as e:
logging.warning(f"{self} set_power_limit: {e}")
return False
else:
return True

View File

@@ -24,10 +24,10 @@ from pyasic.miners.base import BaseMiner
class BOSMinerOld(BaseMiner):
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = BOSMinerAPI(ip)
self.api = BOSMinerAPI(ip, api_ver)
self.api_type = "BOSMiner"
self.uname = "root"
self.pwd = "admin"
@@ -106,3 +106,6 @@ class BOSMinerOld(BaseMiner):
async def get_data(self, **kwargs) -> MinerData:
return MinerData(ip=str(self.ip))
async def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -26,11 +26,12 @@ from pyasic.settings import PyasicSettings
class BTMiner(BaseMiner):
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = BTMinerAPI(ip)
self.api = BTMinerAPI(ip, api_ver)
self.api_type = "BTMiner"
self.api_ver = api_ver
async def get_model(self) -> Union[str, None]:
"""Get miner model.
@@ -137,12 +138,14 @@ class BTMiner(BaseMiner):
return True
return False
async def fault_light_on(self) -> bool:
async def fault_light_on(self, flash: list = []) -> bool:
if flash == []:
# If no flash pattern is provided, use a red-green semi-slow alternating flash
flash = [{"color": "green", "start":0, "period":400, "duration":200},
{"color": "red", "start":200, "period":400, "duration":200}]
try:
data = await self.api.set_led(auto=False)
await self.api.set_led(
auto=False, color="green", start=0, period=1, duration=0
)
for x in flash:
data = await self.api.set_led(auto=False, **x)
except APIError:
return False
if data:
@@ -196,14 +199,20 @@ class BTMiner(BaseMiner):
return False
async def stop_mining(self) -> bool:
data = await self.api.power_off(respbefore=True)
try:
data = await self.api.power_off(respbefore=True)
except APIError:
return False
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
return False
async def resume_mining(self) -> bool:
data = await self.api.power_on()
try:
data = await self.api.power_on()
except APIError:
return False
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
@@ -232,16 +241,24 @@ class BTMiner(BaseMiner):
async def get_config(self) -> MinerConfig:
pools = None
summary = None
cfg = MinerConfig()
try:
pools = await self.api.pools()
data = await self.api.multicommand("pools", "summary")
pools = data["pools"][0]
summary = data["summary"][0]
except APIError as e:
logging.warning(e)
if pools:
if "POOLS" in pools.keys():
if "POOLS" in pools:
cfg = cfg.from_api(pools["POOLS"])
if summary:
if "SUMMARY" in summary:
if wattage := summary["SUMMARY"][0].get("Power Limit"):
cfg.autotuning_wattage = wattage
return cfg
async def get_data(self, allow_warning: bool = True) -> MinerData:
@@ -275,33 +292,44 @@ class BTMiner(BaseMiner):
if model:
data.model = model
if self.make:
data.make = self.make
await self.get_version()
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
if hostname:
data.hostname = hostname
data.fault_light = await self.check_light()
miner_data = None
err_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand("summary", "devs", "pools", allow_warning=allow_warning)
if miner_data:
break
else:
err_data = await self.api.get_error_code()
except APIError:
pass
if not miner_data:
if not (miner_data or err_data):
return data
summary = miner_data.get("summary")[0]
devs = miner_data.get("devs")[0]
pools = miner_data.get("pools")[0]
summary = miner_data["summary"][0] if miner_data.get("summary") else None
devs = miner_data["devs"][0] if miner_data.get("devs") else None
pools = miner_data["pools"][0] if miner_data.get("pools") else None
try:
psu_data = await self.api.get_psu()
except APIError:
psu_data = None
try:
err_data = await self.api.get_error_code()
except APIError:
err_data = None
if not err_data:
try:
err_data = await self.api.get_error_code()
except APIError:
err_data = None
if summary:
summary_data = summary.get("SUMMARY")
@@ -320,8 +348,9 @@ class BTMiner(BaseMiner):
if summary_data[0].get("Power Fanspeed"):
data.fan_psu = summary_data[0]["Power Fanspeed"]
data.fan_1 = summary_data[0]["Fan Speed In"]
data.fan_2 = summary_data[0]["Fan Speed Out"]
if self.fan_count > 0:
data.fan_1 = summary_data[0]["Fan Speed In"]
data.fan_2 = summary_data[0]["Fan Speed Out"]
hr = summary_data[0].get("MHS 1m")
if hr:
@@ -438,3 +467,30 @@ class BTMiner(BaseMiner):
data.mac = mac
return data
async def get_version(self) -> Union[dict, bool]:
"""Get miner firmware version.
Returns:
Miner api & firmware version or None.
"""
# Check to see if the version info is already cached
if self.api_ver and self.fw_ver:
return {"api_ver": self.api_ver, "fw_ver": self.fw_ver}
data = await self.api.get_version()
if "Code" in data.keys():
if data["Code"] == 131:
self.api_ver = data["Msg"]["api_ver"].replace("whatsminer v", "")
self.fw_ver = data["Msg"]["fw_ver"]
self.api.api_ver = self.api_ver
return {"api_ver": self.api_ver, "fw_ver": self.fw_ver}
return False
async def set_power_limit(self, wattage: int) -> bool:
try:
await self.api.adjust_power_limit(wattage)
except Exception as e:
logging.warning(f"{self} set_power_limit: {e}")
return False
else:
return True

View File

@@ -26,10 +26,11 @@ from pyasic.settings import PyasicSettings
class CGMiner(BaseMiner):
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip)
self.ip = ipaddress.ip_address(ip)
self.api = CGMinerAPI(ip)
self.api = CGMinerAPI(ip, api_ver)
self.api_ver = api_ver
self.api_type = "CGMiner"
self.uname = "root"
self.pwd = "admin"
@@ -170,6 +171,23 @@ class CGMiner(BaseMiner):
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def get_version(self) -> dict:
"""Get miner firmware version.
Returns:
Miner api & firmware version or None.
"""
# check if version is cached
if self.fw_ver and self.api_ver:
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
# Now get the API version
version = await self.api.version()
self.api_ver = version['VERSION'][0]['API']
self.fw_ver = version['VERSION'][0]['CGMiner']
self.api.api_ver = self.api_ver
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner.
@@ -335,3 +353,6 @@ class CGMiner(BaseMiner):
data.pool_split = str(quota)
return data
async def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -0,0 +1,276 @@
# 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 ipaddress
import logging
from typing import List, Union
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
from pyasic.miners._backends import CGMiner
class CGMinerAvalon(CGMiner):
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver)
self.ip = ip
async def check_light(self) -> bool:
if self.light:
return self.light
data = await self.api.ascset(0, "led", "1-255")
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
return True
return False
async def fault_light_on(self) -> bool:
data = await self.api.ascset(0, "led", "1-1")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def fault_light_off(self) -> bool:
data = await self.api.ascset(0, "led", "1-0")
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
return True
return False
async def reboot(self) -> bool:
if (await self.api.restart())["STATUS"] == "RESTART":
return True
return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config."""
raise NotImplementedError
logging.debug(f"{self}: Sending config.")
conf = config.as_avalon(user_suffix=user_suffix)
data = await self.api.ascset(
0, "setpool", f"root,root,{conf}"
) # this should work but doesn't
return data
async def get_mac(self) -> str:
mac = None
version = await self.api.version()
if version:
if "VERSION" in version.keys():
if "MAC" in version["VERSION"][0].keys():
base_mac = version["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
return mac
async def get_data(self, allow_warning: bool = True):
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model()
mac = None
if model:
data.model = model
if self.make:
data.make = self.make
await self.get_version()
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
data.fault_light = await self.check_light()
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
if not miner_data:
hostname = await self.get_hostname()
mac = await self.get_mac()
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
if mac:
data.mac = mac
return data
summary = miner_data.get("summary")
version = miner_data.get("version")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
if version:
if "VERSION" in version[0].keys():
if "MAC" in version[0]["VERSION"][0].keys():
base_mac = version[0]["VERSION"][0]["MAC"].upper()
# parse the MAC into a recognizable form
mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
)
if stats:
stats_data = stats[0].get("STATS")
if stats_data:
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
if f"Fan{fan+1}" in raw_data:
setattr(
data,
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
data.hashboards[board].chip_temp = chip_temp[board]
temp = raw_data.get("MTavg")
if temp:
data.hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
data.hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
if pool_1_user:
data.pool_1_user = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if pool_2_user:
data.pool_2_user = pool_2_user
if quota:
data.pool_split = str(quota)
hostname = await self.get_hostname()
if mac:
data.mac = mac
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if hostname and not hostname == "?":
data.hostname = hostname
elif mac:
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats)
stats_items = []
stats_dict = {}
for item in _stats_items:
if ":" in item:
data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {}
for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
raw_data = [data[0].strip(), data_dict]
else:
raw_data = [
value
for value in item.replace("[", " ")
.replace("]", " ")
.split(" ")[:-1]
if value != ""
]
if len(raw_data) == 1:
raw_data.append("")
if raw_data[0] == "":
raw_data = raw_data[1:]
if len(raw_data) == 2:
stats_dict[raw_data[0]] = raw_data[1]
else:
stats_dict[raw_data[0]] = raw_data[1:]
stats_items.append(raw_data)
return stats_dict

View File

@@ -18,8 +18,8 @@ from pyasic.miners._backends import BMMiner
class Hiveon(BMMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver)
self.ip = ipaddress.ip_address(ip)
self.api_type = "Hiveon"
self.uname = "root"
@@ -61,3 +61,6 @@ class Hiveon(BMMiner):
bad_boards[board] = []
bad_boards[board].append(chain)
return bad_boards
async def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S17(BaseMiner): # noqa - ignore ABC method implementation
class S17(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S17Plus(BaseMiner): # noqa - ignore ABC method implementation
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S17Pro(BaseMiner): # noqa - ignore ABC method implementation
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S17e(BaseMiner): # noqa - ignore ABC method implementation
class S17e(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class T17(BaseMiner): # noqa - ignore ABC method implementation
class T17(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class T17Plus(BaseMiner): # noqa - ignore ABC method implementation
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class T17e(BaseMiner): # noqa - ignore ABC method implementation
class T17e(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19(BaseMiner): # noqa - ignore ABC method implementation
class S19(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19Pro(BaseMiner): # noqa - ignore ABC method implementation
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19XP(BaseMiner): # noqa - ignore ABC method implementation
class S19XP(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19a(BaseMiner): # noqa - ignore ABC method implementation
class S19a(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19aPro(BaseMiner): # noqa - ignore ABC method implementation
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19j(BaseMiner): # noqa - ignore ABC method implementation
class S19j(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S19jPro(BaseMiner): # noqa - ignore ABC method implementation
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class T19(BaseMiner): # noqa - ignore ABC method implementation
class T19(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S9(BaseMiner): # noqa - ignore ABC method implementation
class S9(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class S9i(BaseMiner): # noqa - ignore ABC method implementation
class S9i(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AntMiner
class T9(BaseMiner): # noqa - ignore ABC method implementation
class T9(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon1026(BaseMiner): # noqa - ignore ABC method implementation
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon1047(BaseMiner): # noqa - ignore ABC method implementation
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon1066(BaseMiner): # noqa - ignore ABC method implementation
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon721(BaseMiner): # noqa - ignore ABC method implementation
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon741(BaseMiner): # noqa - ignore ABC method implementation
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon761(BaseMiner): # noqa - ignore ABC method implementation
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon821(BaseMiner): # noqa - ignore ABC method implementation
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon841(BaseMiner): # noqa - ignore ABC method implementation
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon851(BaseMiner): # noqa - ignore ABC method implementation
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import AvalonMiner
class Avalon921(BaseMiner): # noqa - ignore ABC method implementation
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import InnosiliconMiner
class InnosiliconT3HPlus(BaseMiner): # noqa - ignore ABC method implementation
class InnosiliconT3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str) -> None:
super().__init__()
self.ip = ip

View File

@@ -0,0 +1,35 @@
# 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.base import BaseMiner
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self):
super().__init__()
self.make = "WhatsMiner"
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self):
super().__init__()
self.make = "AntMiner"
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self):
super().__init__()
self.make = "AvalonMiner"
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self):
super().__init__()
self.make = "Innosilicon"

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M20(BaseMiner): # noqa - ignore ABC method implementation
class M20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M20(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M20V10(BaseMiner): # noqa - ignore ABC method implementation
class M20V10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M20S(BaseMiner): # noqa - ignore ABC method implementation
class M20S(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M20S(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M20SV10(BaseMiner): # noqa - ignore ABC method implementation
class M20SV10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -33,7 +32,7 @@ class M20SV10(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M20SV20(BaseMiner): # noqa - ignore ABC method implementation
class M20SV20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M20SPlus(BaseMiner): # noqa - ignore ABC method implementation
class M20SPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M21(BaseMiner): # noqa - ignore ABC method implementation
class M21(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M21S(BaseMiner): # noqa - ignore ABC method implementation
class M21S(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M21S(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M21SV60(BaseMiner): # noqa - ignore ABC method implementation
class M21SV60(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -33,7 +32,7 @@ class M21SV60(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M21SV20(BaseMiner): # noqa - ignore ABC method implementation
class M21SV20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M21SPlus(BaseMiner): # noqa - ignore ABC method implementation
class M21SPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M30S(BaseMiner): # noqa - ignore ABC method implementation
class M30S(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M30S(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SV50(BaseMiner): # noqa - ignore ABC method implementation
class M30SV50(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -33,7 +32,7 @@ class M30SV50(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SVG20(BaseMiner): # noqa - ignore ABC method implementation
class M30SVG20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -42,7 +41,7 @@ class M30SVG20(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SVE20(BaseMiner): # noqa - ignore ABC method implementation
class M30SVE20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -51,7 +50,7 @@ class M30SVE20(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SVE10(BaseMiner): # noqa - ignore ABC method implementation
class M30SVE10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M30SPlus(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M30SPlus(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SPlusVG60(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusVG60(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -32,8 +31,16 @@ class M30SPlusVG60(BaseMiner): # noqa - ignore ABC method implementation
self.nominal_chips = 86
self.fan_count = 2
class M30SPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S+ VG40"
self.nominal_chips = 105
self.fan_count = 2
class M30SPlusVE40(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -42,7 +49,7 @@ class M30SPlusVE40(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SPlusVF20(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M30SPlusPlus(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M30SPlusPlus(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SPlusPlusVG30(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -33,7 +32,7 @@ class M30SPlusPlusVG30(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SPlusPlusVG40(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -42,7 +41,7 @@ class M30SPlusPlusVG40(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M30SPlusPlusVH60(BaseMiner): # noqa - ignore ABC method implementation
class M30SPlusPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M31S(BaseMiner): # noqa - ignore ABC method implementation
class M31S(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,32 @@ class M31S(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SV70(BaseMiner): # noqa - ignore ABC method implementation
class M31SV10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V10"
self.nominal_chips = 105
self.fan_count = 2
class M31SV20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V20"
self.nominal_chips = 111
self.fan_count = 2
class M31SV60(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S V60"
self.nominal_chips = 105
self.fan_count = 2
class M31SV70(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M31SPlus(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M31SPlus(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusVE20(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusVE20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -33,7 +32,7 @@ class M31SPlusVE20(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusV30(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -42,7 +41,7 @@ class M31SPlusV30(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusV40(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -51,7 +50,7 @@ class M31SPlusV40(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusV60(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -60,7 +59,7 @@ class M31SPlusV60(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusV80(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -69,7 +68,7 @@ class M31SPlusV80(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M31SPlusV90(BaseMiner): # noqa - ignore ABC method implementation
class M31SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M32(BaseMiner): # noqa - ignore ABC method implementation
class M32(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +23,7 @@ class M32(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M32V20(BaseMiner): # noqa - ignore ABC method implementation
class M32V20(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M32S(BaseMiner): # noqa - ignore ABC method implementation
class M32S(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M34SPlus(BaseMiner): # noqa - ignore ABC method implementation
class M34SPlus(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -25,7 +24,7 @@ class M34SPlus(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 0
class M34SPlusVE10(BaseMiner): # noqa - ignore ABC method implementation
class M34SPlusVE10(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -13,14 +13,14 @@
# limitations under the License.
from .M30S import M30S, M30SV50, M30SVE10, M30SVE20, M30SVG20
from .M30S_Plus import M30SPlus, M30SPlusVE40, M30SPlusVF20, M30SPlusVG60
from .M30S_Plus import M30SPlus, M30SPlusVE40, M30SPlusVF20, M30SPlusVG60, M30SPlusVG40
from .M30S_Plus_Plus import (
M30SPlusPlus,
M30SPlusPlusVG30,
M30SPlusPlusVG40,
M30SPlusPlusVH60,
)
from .M31S import M31S, M31SV70
from .M31S import M31S, M31SV10, M31SV20, M31SV60, M31SV70
from .M31S_Plus import (
M31SPlus,
M31SPlusV30,

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
from pyasic.miners._types.makes import WhatsMiner
class M50(BaseMiner): # noqa - ignore ABC method implementation
class M50(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
@@ -24,7 +24,7 @@ class M50(BaseMiner): # noqa - ignore ABC method implementation
self.fan_count = 2
class M50VH50(BaseMiner): # noqa - ignore ABC method implementation
class M50VH50(WhatsMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerS17(BMMinerX17, S17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerS17Plus(BMMinerX17, S17Plus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerS17Pro(BMMinerX17, S17Pro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerS17e(BMMinerX17, S17e):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerT17(BMMinerX17, T17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerT17Plus(BMMinerX17, T17Plus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X17 import BMMinerX17
class BMMinerT17e(BMMinerX17, T17e):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -21,8 +21,8 @@ from pyasic.settings import PyasicSettings
class BMMinerX17(BMMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.uname = "root"
self.pwd = PyasicSettings().global_x17_password

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19(BMMinerX19, S19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19Pro(BMMinerX19, S19Pro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19XP(BMMinerX19, S19XP):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19a(BMMinerX19, S19a):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19aPro(BMMinerX19, S19aPro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19j(BMMinerX19, S19j):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerS19jPro(BMMinerX19, S19jPro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -18,6 +18,6 @@ from .X19 import BMMinerX19
class BMMinerT19(BMMinerX19, T19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -25,8 +25,8 @@ from pyasic.settings import PyasicSettings
class BMMinerX19(BMMiner):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.uname = "root"
self.pwd = PyasicSettings().global_x19_password

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S9 # noqa - Ignore access to _module
class BMMinerS9(BMMiner, S9):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S9i # noqa - Ignore access to _module
class BMMinerS9i(BMMiner, S9i):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import T9 # noqa - Ignore access to _module
class BMMinerT9(BMMiner, T9):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S17 # noqa - Ignore access to _module
class BOSMinerS17(BOSMiner, S17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
class BOSMinerS17Plus(BOSMiner, S17Plus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
class BOSMinerS17Pro(BOSMiner, S17Pro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S17e # noqa - Ignore access to _module
class BOSMinerS17e(BOSMiner, S17e):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import T17 # noqa - Ignore access to _module
class BOSMinerT17(BOSMiner, T17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import T17Plus # noqa - Ignore access to _module
class BOSMinerT17Plus(BOSMiner, T17Plus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import T17e # noqa - Ignore access to _module
class BOSMinerT17e(BOSMiner, T17e):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S19 # noqa - Ignore access to _module
class BOSMinerS19(BOSMiner, S19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
class BOSMinerS19Pro(BOSMiner, S19Pro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S19j # noqa - Ignore access to _module
class BOSMinerS19j(BOSMiner, S19j):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
class BOSMinerS19jPro(BOSMiner, S19jPro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import T19 # noqa - Ignore access to _module
class BOSMinerT19(BOSMiner, T19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S9 # noqa - Ignore access to _module
class BOSMinerS9(BOSMiner, S9):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

View File

@@ -17,6 +17,6 @@ from pyasic.miners._types import S17 # noqa - Ignore access to _module
class CGMinerS17(CGMiner, S17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip

Some files were not shown because too many files have changed in this diff Show More