Compare commits

...

8 Commits

11 changed files with 103 additions and 52 deletions

View File

@@ -108,31 +108,31 @@ class MiningModeHPM(MinerConfigValue):
return {"mode": {"mode": "turbo"}}
class StandardPowerTuneAlgo(MinerConfigValue):
class StandardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self) -> str:
return VOptPowerTuneAlgo().as_epic()
return VOptAlgo().as_epic()
class VOptPowerTuneAlgo(MinerConfigValue):
class VOptAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self) -> str:
return "VoltageOptimizer"
class ChipTunePowerTuneAlgo(MinerConfigValue):
class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self) -> str:
return "ChipTune"
class PowerTunerAlgo(MinerConfigOption):
standard = StandardPowerTuneAlgo
voltage_optimizer = VOptPowerTuneAlgo
chip_tune = ChipTunePowerTuneAlgo
class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo
chip_tune = ChipTuneAlgo
@classmethod
def default(cls):
@@ -143,7 +143,7 @@ class PowerTunerAlgo(MinerConfigOption):
class MiningModePowerTune(MinerConfigValue):
mode: str = field(init=False, default="power_tuning")
power: int = None
algo: PowerTunerAlgo = field(default_factory=PowerTunerAlgo.default)
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
@@ -183,14 +183,12 @@ class MiningModePowerTune(MinerConfigValue):
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
def as_epic(self) -> dict:
return {"ptune": {"algo": self.algo.as_epic(), "target": self.power}}
@dataclass
class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
@@ -218,6 +216,9 @@ class MiningModeHashrateTune(MinerConfigValue):
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
def as_epic(self) -> dict:
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
@dataclass
class ManualBoardSettings(MinerConfigValue):
@@ -313,14 +314,14 @@ class MiningModeConfig(MinerConfigOption):
if tuner_running:
algo_info = web_conf["PerpetualTune"]["Algorithm"]
if algo_info.get("VoltageOptimizer") is not None:
return cls.power_tuning(
power=algo_info["VoltageOptimizer"]["Target"],
algo=PowerTunerAlgo.voltage_optimizer,
return cls.hashrate_tuning(
hashrate=algo_info["VoltageOptimizer"]["Target"],
algo=TunerAlgo.voltage_optimizer,
)
else:
return cls.power_tuning(
power=algo_info["ChipTune"]["Target"],
algo=PowerTunerAlgo.chip_tune,
return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"]["Target"],
algo=TunerAlgo.chip_tune,
)
else:
return cls.normal()

View File

@@ -205,13 +205,14 @@ class VNish(BMMiner):
if web_summary is None:
web_summary = await self.web.summary()
fw_ver = None
if web_summary is not None:
try:
fw_ver = web_summary["miner"]["miner_type"]
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
return fw_ver
except KeyError:
pass
except LookupError:
return fw_ver
async def get_config(self) -> MinerConfig:
try:

View File

@@ -289,7 +289,9 @@ MINER_CLASSES = {
"M50S++VK30": BTMinerM50SPlusPlusVK30,
"M53VH30": BTMinerM53VH30,
"M53SVH30": BTMinerM53SVH30,
"M53SVJ40": BTMinerM53SVJ40,
"M53S+VJ30": BTMinerM53SPlusVJ30,
"M53S++VK10": BTMinerM53SPlusPlusVK10,
"M56VH30": BTMinerM56VH30,
"M56SVH30": BTMinerM56SVH30,
"M56S+VJ30": BTMinerM56SPlusVJ30,
@@ -968,6 +970,7 @@ class MinerFactory:
miner_factory = MinerFactory()
# abstracted version of get miner that is easier to access
async def get_miner(ip: ipaddress.ip_address | str) -> AnyMiner:
return await miner_factory.get_miner(ip)

View File

@@ -20,3 +20,8 @@ from pyasic.miners.makes import WhatsMinerMake
class M53SVH30(WhatsMinerMake):
raw_model = "M53S VH30"
expected_fans = 0
class M53SVJ40(WhatsMinerMake):
raw_model = "M53S VJ40"
expected_fans = 0

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# 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.makes import WhatsMinerMake
class M53SPlusPlusVK10(WhatsMinerMake):
raw_model = "M53S++ VK10"
expected_fans = 0

View File

@@ -42,8 +42,9 @@ from .M50S import (
from .M50S_Plus import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30, M50SPlusVK20
from .M50S_Plus_Plus import M50SPlusPlusVK10, M50SPlusPlusVK20, M50SPlusPlusVK30
from .M53 import M53VH30
from .M53S import M53SVH30
from .M53S import M53SVH30, M53SVJ40
from .M53S_Plus import M53SPlusVJ30
from .M53S_Plus_Plus import M53SPlusPlusVK10
from .M56 import M56VH30
from .M56S import M56SVH30
from .M56S_Plus import M56SPlusVJ30

View File

@@ -15,8 +15,12 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import M5X
from pyasic.miners.models import M53SVH30
from pyasic.miners.models import M53SVH30, M53SVJ40
class BTMinerM53SVH30(M5X, M53SVH30):
pass
class BTMinerM53SVJ40(M5X, M53SVJ40):
pass

View File

@@ -0,0 +1,22 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import M5X
from pyasic.miners.models import M53SPlusPlusVK10
class BTMinerM53SPlusPlusVK10(M5X, M53SPlusPlusVK10):
pass

View File

@@ -51,8 +51,9 @@ from .M50S_Plus_Plus import (
BTMinerM50SPlusPlusVK30,
)
from .M53 import BTMinerM53VH30
from .M53S import BTMinerM53SVH30
from .M53S import BTMinerM53SVH30, BTMinerM53SVJ40
from .M53S_Plus import BTMinerM53SPlusVJ30
from .M53S_Plus_Plus import BTMinerM53SPlusPlusVK10
from .M56 import BTMinerM56VH30
from .M56S import BTMinerM56SVH30
from .M56S_Plus import BTMinerM56SPlusVJ30

View File

@@ -78,6 +78,9 @@ class BaseMinerRPCAPI:
# send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
if data is None:
raise APIError("No data returned from the API.")
if data == b"Socket connect failed: Connection refused\n":
if not ignore_errors:
raise APIError(data.decode("utf-8"))
@@ -208,39 +211,14 @@ If you are sure you want to use this command please use API.send_command("{comma
return b"{}"
# send the command
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
writer.write(data)
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
await writer.drain()
try:
# TO address a situation where a whatsminer has an unknown PW -AND-
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
# have to receive, save the data, check if there is more data by reading with a short timeout
# append that data if there is more, and then onto the main loop.
# the password timeout might need to be longer than 1, but it seems to work for now.
ret_data = await asyncio.wait_for(reader.read(1), timeout=1)
except asyncio.TimeoutError:
return b"{}"
try:
ret_data += await asyncio.wait_for(reader.read(4096), timeout=timeout)
except ConnectionAbortedError:
return b"{}"
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
try:
while True:
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} - ([Hidden] Send Bytes) - API Command Error {e}")
await data_task
ret_data = data_task.result()
# close the connection
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
@@ -249,6 +227,19 @@ If you are sure you want to use this command please use API.send_command("{comma
return ret_data
async def _read_bytes(self, reader: asyncio.StreamReader, timeout: int) -> bytes:
ret_data = b""
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
try:
ret_data = await asyncio.wait_for(reader.read(), timeout=timeout)
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
raise e
except Exception as e:
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
return ret_data
@staticmethod
def _load_api_data(data: bytes) -> dict:
# some json from the API returns with a null byte (\x00) on the end

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.50.6"
version = "0.50.9"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"