From 343b5a1c503444a19dd05161463a0c7d7f88ab5e Mon Sep 17 00:00:00 2001 From: Upstream Data Date: Tue, 20 Aug 2024 09:45:31 -0600 Subject: [PATCH 1/4] feature: add basic iceriver framework. --- pyasic/device/models.py | 8 ++ pyasic/miners/backends/__init__.py | 1 + pyasic/miners/backends/iceriver.py | 14 ++++ pyasic/miners/device/makes.py | 4 + pyasic/miners/device/models/__init__.py | 1 + .../miners/device/models/iceriver/KSX/KS2.py | 23 ++++++ .../device/models/iceriver/KSX/__init__.py | 1 + .../miners/device/models/iceriver/__init__.py | 1 + pyasic/miners/factory.py | 8 ++ pyasic/miners/iceriver/__init__.py | 1 + pyasic/miners/iceriver/iceminer/KSX/KS2.py | 6 ++ .../miners/iceriver/iceminer/KSX/__init__.py | 1 + pyasic/miners/iceriver/iceminer/__init__.py | 1 + pyasic/settings/__init__.py | 1 + pyasic/web/__init__.py | 1 + pyasic/web/iceriver.py | 73 +++++++++++++++++++ 16 files changed, 145 insertions(+) create mode 100644 pyasic/miners/backends/iceriver.py create mode 100644 pyasic/miners/device/models/iceriver/KSX/KS2.py create mode 100644 pyasic/miners/device/models/iceriver/KSX/__init__.py create mode 100644 pyasic/miners/device/models/iceriver/__init__.py create mode 100644 pyasic/miners/iceriver/__init__.py create mode 100644 pyasic/miners/iceriver/iceminer/KSX/KS2.py create mode 100644 pyasic/miners/iceriver/iceminer/KSX/__init__.py create mode 100644 pyasic/miners/iceriver/iceminer/__init__.py create mode 100644 pyasic/web/iceriver.py diff --git a/pyasic/device/models.py b/pyasic/device/models.py index 24678d72..81a53427 100644 --- a/pyasic/device/models.py +++ b/pyasic/device/models.py @@ -339,6 +339,13 @@ class BitAxeModels(str, Enum): return self.value +class IceRiverModels(str, Enum): + KS2 = "KS2" + + def __str__(self): + return self.value + + class MinerModel: ANTMINER = AntminerModels WHATSMINER = WhatsminerModels @@ -348,3 +355,4 @@ class MinerModel: AURADINE = AuradineModels EPIC = ePICModels BITAXE = BitAxeModels + ICERIVER = IceRiverModels diff --git a/pyasic/miners/backends/__init__.py b/pyasic/miners/backends/__init__.py index 283a598c..a510d001 100644 --- a/pyasic/miners/backends/__init__.py +++ b/pyasic/miners/backends/__init__.py @@ -24,6 +24,7 @@ from .cgminer import CGMiner from .epic import ePIC from .goldshell import GoldshellMiner from .hiveon import Hiveon +from .iceriver import IceRiver from .innosilicon import Innosilicon from .luxminer import LUXMiner from .marathon import MaraMiner diff --git a/pyasic/miners/backends/iceriver.py b/pyasic/miners/backends/iceriver.py new file mode 100644 index 00000000..cff01f90 --- /dev/null +++ b/pyasic/miners/backends/iceriver.py @@ -0,0 +1,14 @@ +from pyasic.miners.data import DataLocations +from pyasic.miners.device.firmware import StockFirmware +from pyasic.web.iceriver import IceRiverWebAPI + +ICERIVER_DATA_LOC = DataLocations() + + +class IceRiver(StockFirmware): + """Handler for IceRiver miners""" + + _web_cls = IceRiverWebAPI + web: IceRiverWebAPI + + data_locations = ICERIVER_DATA_LOC diff --git a/pyasic/miners/device/makes.py b/pyasic/miners/device/makes.py index 6bfbe40b..615b98e3 100644 --- a/pyasic/miners/device/makes.py +++ b/pyasic/miners/device/makes.py @@ -48,3 +48,7 @@ class ePICMake(BaseMiner): class BitAxeMake(BaseMiner): make = MinerMake.BITAXE + + +class IceRiverMake(BaseMiner): + make = MinerMake.BITAXE diff --git a/pyasic/miners/device/models/__init__.py b/pyasic/miners/device/models/__init__.py index a74bf0a9..666a9c8f 100644 --- a/pyasic/miners/device/models/__init__.py +++ b/pyasic/miners/device/models/__init__.py @@ -19,5 +19,6 @@ from .auradine import * from .avalonminer import * from .epic import * from .goldshell import * +from .iceriver import * from .innosilicon import * from .whatsminer import * diff --git a/pyasic/miners/device/models/iceriver/KSX/KS2.py b/pyasic/miners/device/models/iceriver/KSX/KS2.py new file mode 100644 index 00000000..888067e6 --- /dev/null +++ b/pyasic/miners/device/models/iceriver/KSX/KS2.py @@ -0,0 +1,23 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 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.device.models import MinerModel +from pyasic.miners.device.makes import IceRiverMake + + +class KS2(IceRiverMake): + raw_model = MinerModel.ICERIVER.KS2 + + expected_fans = 4 diff --git a/pyasic/miners/device/models/iceriver/KSX/__init__.py b/pyasic/miners/device/models/iceriver/KSX/__init__.py new file mode 100644 index 00000000..2f2a4251 --- /dev/null +++ b/pyasic/miners/device/models/iceriver/KSX/__init__.py @@ -0,0 +1 @@ +from .KS2 import KS2 diff --git a/pyasic/miners/device/models/iceriver/__init__.py b/pyasic/miners/device/models/iceriver/__init__.py new file mode 100644 index 00000000..54c0f2b8 --- /dev/null +++ b/pyasic/miners/device/models/iceriver/__init__.py @@ -0,0 +1 @@ +from .KSX import * diff --git a/pyasic/miners/factory.py b/pyasic/miners/factory.py index bf5d25b2..2795d2d7 100644 --- a/pyasic/miners/factory.py +++ b/pyasic/miners/factory.py @@ -38,6 +38,7 @@ from pyasic.miners.bitaxe import * from pyasic.miners.blockminer import * from pyasic.miners.device.makes import * from pyasic.miners.goldshell import * +from pyasic.miners.iceriver import * from pyasic.miners.innosilicon import * from pyasic.miners.whatsminer import * @@ -56,6 +57,7 @@ class MinerTypes(enum.Enum): AURADINE = 10 MARATHON = 11 BITAXE = 12 + ICERIVER = 13 MINER_CLASSES = { @@ -451,6 +453,10 @@ MINER_CLASSES = { "BM1366": BitAxeUltra, "BM1397": BitAxeMax, }, + MinerTypes.ICERIVER: { + None: type("IceRiverUnknown", (IceRiver, IceRiverMake), {}), + "KS2": IceRiverKS2, + }, } @@ -623,6 +629,8 @@ class MinerFactory: return MinerTypes.INNOSILICON if "Miner UI" in web_text: return MinerTypes.AURADINE + if "用户界面" in web_text: + return MinerTypes.ICERIVER async def _get_miner_socket(self, ip: str) -> MinerTypes | None: commands = ["version", "devdetails"] diff --git a/pyasic/miners/iceriver/__init__.py b/pyasic/miners/iceriver/__init__.py new file mode 100644 index 00000000..91378053 --- /dev/null +++ b/pyasic/miners/iceriver/__init__.py @@ -0,0 +1 @@ +from .iceminer import * diff --git a/pyasic/miners/iceriver/iceminer/KSX/KS2.py b/pyasic/miners/iceriver/iceminer/KSX/KS2.py new file mode 100644 index 00000000..629f8095 --- /dev/null +++ b/pyasic/miners/iceriver/iceminer/KSX/KS2.py @@ -0,0 +1,6 @@ +from pyasic.miners.backends.iceriver import IceRiver +from pyasic.miners.device.models import KS2 + + +class IceRiverKS2(IceRiver, KS2): + pass diff --git a/pyasic/miners/iceriver/iceminer/KSX/__init__.py b/pyasic/miners/iceriver/iceminer/KSX/__init__.py new file mode 100644 index 00000000..4d6be18f --- /dev/null +++ b/pyasic/miners/iceriver/iceminer/KSX/__init__.py @@ -0,0 +1 @@ +from .KS2 import IceRiverKS2 diff --git a/pyasic/miners/iceriver/iceminer/__init__.py b/pyasic/miners/iceriver/iceminer/__init__.py new file mode 100644 index 00000000..54c0f2b8 --- /dev/null +++ b/pyasic/miners/iceriver/iceminer/__init__.py @@ -0,0 +1 @@ +from .KSX import * diff --git a/pyasic/settings/__init__.py b/pyasic/settings/__init__.py index 8258ae5f..6e394a59 100644 --- a/pyasic/settings/__init__.py +++ b/pyasic/settings/__init__.py @@ -39,6 +39,7 @@ _settings = { # defaults "default_auradine_web_password": "admin", "default_epic_web_password": "letmein", "default_hive_web_password": "admin", + "default_iceriver_web_password": "12345678", "default_antminer_ssh_password": "miner", "default_bosminer_ssh_password": "root", "socket_linger_time": 1000, diff --git a/pyasic/web/__init__.py b/pyasic/web/__init__.py index 5e030e65..f6576a0a 100644 --- a/pyasic/web/__init__.py +++ b/pyasic/web/__init__.py @@ -19,5 +19,6 @@ from .base import BaseWebAPI from .braiins_os import BOSerWebAPI, BOSMinerWebAPI from .epic import ePICWebAPI from .goldshell import GoldshellWebAPI +from .iceriver import IceRiverWebAPI from .innosilicon import InnosiliconWebAPI from .vnish import VNishWebAPI diff --git a/pyasic/web/iceriver.py b/pyasic/web/iceriver.py new file mode 100644 index 00000000..55927642 --- /dev/null +++ b/pyasic/web/iceriver.py @@ -0,0 +1,73 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 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 __future__ import annotations + +import asyncio +import warnings +from typing import Any + +import httpx + +from pyasic import settings +from pyasic.errors import APIError +from pyasic.web.base import BaseWebAPI + + +class IceRiverWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + super().__init__(ip) + self.username = "admin" + self.pwd = settings.get("default_iceriver_web_password", "12345678") + + async def multicommand( + self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True + ) -> dict: + tasks = {c: asyncio.create_task(getattr(self, c)()) for c in commands} + await asyncio.gather(*[t for t in tasks.values()]) + return {t: tasks[t].result() for t in tasks} + + async def send_command( + self, + command: str | bytes, + ignore_errors: bool = False, + allow_warning: bool = True, + privileged: bool = False, + **parameters: Any, + ) -> dict: + async with httpx.AsyncClient(transport=settings.transport()) as client: + try: + # auth + await client.post( + f"http://{self.ip}:{self.port}/user/loginpost", + params={"post": "6", "user": self.username, "pwd": self.pwd}, + ) + except httpx.HTTPError: + warnings.warn(f"Could not authenticate with miner web: {self}") + try: + resp = await client.post( + f"http://{self.ip}:{self.port}/user/{command}", params=parameters + ) + return resp.json() + except httpx.HTTPError: + raise APIError(f"Command failed: {command}") + + async def locate(self, enable: bool): + return await self.send_command( + "userpanel", post="5", locate="1" if enable else "0" + ) + + async def userpanel(self): + return await self.send_command("userpanel", post="4") From fba25cba611110478f3d336bee6d478171ae13bb Mon Sep 17 00:00:00 2001 From: Upstream Data Date: Tue, 20 Aug 2024 09:59:33 -0600 Subject: [PATCH 2/4] feature: add a couple iceriver data gathering functions. --- pyasic/miners/backends/iceriver.py | 93 +++++++++++++++++++++++++++++- pyasic/web/iceriver.py | 4 ++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/pyasic/miners/backends/iceriver.py b/pyasic/miners/backends/iceriver.py index cff01f90..80b8ad26 100644 --- a/pyasic/miners/backends/iceriver.py +++ b/pyasic/miners/backends/iceriver.py @@ -1,8 +1,31 @@ -from pyasic.miners.data import DataLocations +from typing import List, Optional + +from pyasic.data import AlgoHashRate, Fan +from pyasic.errors import APIError +from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.device.firmware import StockFirmware from pyasic.web.iceriver import IceRiverWebAPI -ICERIVER_DATA_LOC = DataLocations() +ICERIVER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "_get_mac", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.FANS): DataFunction( + "_get_fans", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.HOSTNAME): DataFunction( + "_get_hostname", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.HASHRATE): DataFunction( + "_get_hashrate", + [WebAPICommand("web_userpanel", "userpanel")], + ), + } +) class IceRiver(StockFirmware): @@ -12,3 +35,69 @@ class IceRiver(StockFirmware): web: IceRiverWebAPI data_locations = ICERIVER_DATA_LOC + + async def fault_light_off(self) -> bool: + try: + await self.web.locate(False) + except APIError: + return False + return True + + async def fault_light_on(self) -> bool: + try: + await self.web.locate(True) + except APIError: + return False + return True + + async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return [Fan(spd) for spd in web_userpanel["fans"]] + except (LookupError, ValueError, TypeError): + pass + + async def _get_mac(self, web_userpanel: dict = None) -> Optional[str]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return web_userpanel["mac"].upper() + except (LookupError, ValueError, TypeError): + pass + + async def _get_hostname(self, web_userpanel: dict = None) -> Optional[str]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return web_userpanel["host"] + except (LookupError, ValueError, TypeError): + pass + + async def _get_hashrate(self, web_userpanel: dict = None) -> Optional[AlgoHashRate]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return AlgoHashRate.SHA256(web_userpanel["rtpow"]) + except (LookupError, ValueError, TypeError): + pass diff --git a/pyasic/web/iceriver.py b/pyasic/web/iceriver.py index 55927642..1ba1e555 100644 --- a/pyasic/web/iceriver.py +++ b/pyasic/web/iceriver.py @@ -60,6 +60,10 @@ class IceRiverWebAPI(BaseWebAPI): resp = await client.post( f"http://{self.ip}:{self.port}/user/{command}", params=parameters ) + if not resp.status_code == 200: + if not ignore_errors: + raise APIError(f"Command failed: {command}") + warnings.warn(f"Command failed: {command}") return resp.json() except httpx.HTTPError: raise APIError(f"Command failed: {command}") From 0ea5ee823962c16795d1a733abbf5d22eddb4527 Mon Sep 17 00:00:00 2001 From: Upstream Data Date: Tue, 20 Aug 2024 10:16:35 -0600 Subject: [PATCH 3/4] feature: add more iceriver functionality. --- pyasic/miners/backends/iceriver.py | 101 ++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/pyasic/miners/backends/iceriver.py b/pyasic/miners/backends/iceriver.py index 80b8ad26..feb6c756 100644 --- a/pyasic/miners/backends/iceriver.py +++ b/pyasic/miners/backends/iceriver.py @@ -1,6 +1,7 @@ from typing import List, Optional -from pyasic.data import AlgoHashRate, Fan +from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit +from pyasic.device import MinerAlgo from pyasic.errors import APIError from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand from pyasic.miners.device.firmware import StockFirmware @@ -24,6 +25,22 @@ ICERIVER_DATA_LOC = DataLocations( "_get_hashrate", [WebAPICommand("web_userpanel", "userpanel")], ), + str(DataOptions.IS_MINING): DataFunction( + "_is_mining", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "_get_fault_light", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.HASHBOARDS): DataFunction( + "_get_hashboards", + [WebAPICommand("web_userpanel", "userpanel")], + ), + str(DataOptions.UPTIME): DataFunction( + "_get_uptime", + [WebAPICommand("web_userpanel", "userpanel")], + ), } ) @@ -72,7 +89,7 @@ class IceRiver(StockFirmware): if web_userpanel is not None: try: - return web_userpanel["mac"].upper() + return web_userpanel["mac"].upper().replace("-", ":") except (LookupError, ValueError, TypeError): pass @@ -98,6 +115,84 @@ class IceRiver(StockFirmware): if web_userpanel is not None: try: - return AlgoHashRate.SHA256(web_userpanel["rtpow"]) + base_unit = web_userpanel["unit"] + return AlgoHashRate.SHA256( + float(web_userpanel["rtpow"].replace(base_unit, "")), + unit=MinerAlgo.SHA256.unit.from_str(base_unit + "H"), + ).into(MinerAlgo.SHA256.unit.default) + except (LookupError, ValueError, TypeError): + pass + + async def _get_fault_light(self, web_userpanel: dict = None) -> bool: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return web_userpanel["locate"] + except (LookupError, ValueError, TypeError): + pass + return False + + async def _is_mining(self, web_userpanel: dict = None) -> Optional[bool]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + return web_userpanel["powstate"] + except (LookupError, ValueError, TypeError): + pass + + async def _get_hashboards(self, web_userpanel: dict = None) -> List[HashBoard]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + hb_list = [ + HashBoard(slot=i, expected_chips=self.expected_chips) + for i in range(self.expected_hashboards) + ] + + if web_userpanel is not None: + try: + for board in web_userpanel["boards"]: + idx = board["no"] - 1 + hb_list[idx].chip_temp = round(board["outtmp"]) + hb_list[idx].temp = round(board["intmp"]) + hb_list[idx].hashrate = AlgoHashRate.SHA256( + float(board["rtpow"].replace("G", "")), HashUnit.SHA256.GH + ).into(self.algo.unit.default) + hb_list[idx].chips = board["chipnum"] + hb_list[idx].missing = False + except LookupError: + pass + return hb_list + + async def _get_uptime(self, web_userpanel: dict = None) -> Optional[int]: + if web_userpanel is None: + try: + web_userpanel = await self.web.userpanel() + except APIError: + pass + + if web_userpanel is not None: + try: + runtime = web_userpanel["runtime"] + days, hours, minutes, seconds = runtime.split(":") + return ( + (int(days) * 24 * 60 * 60) + + (int(hours) * 60 * 60) + + (int(minutes) * 60) + + int(seconds) + ) except (LookupError, ValueError, TypeError): pass From e9fcf25ad36357f61e8c6c4a60b7d67980622d37 Mon Sep 17 00:00:00 2001 From: Upstream Data Date: Tue, 20 Aug 2024 10:24:06 -0600 Subject: [PATCH 4/4] docs: update docs with iceriver support. --- docs/generate_miners.py | 2 ++ docs/miners/antminer/X19.md | 14 ++++++++++++++ docs/miners/antminer/X21.md | 14 ++++++++++++++ docs/miners/iceriver/KSX.md | 10 ++++++++++ docs/miners/supported_types.md | 15 +++++++++++++++ 5 files changed, 55 insertions(+) create mode 100644 docs/miners/iceriver/KSX.md diff --git a/docs/generate_miners.py b/docs/generate_miners.py index 87f23886..d7c67587 100644 --- a/docs/generate_miners.py +++ b/docs/generate_miners.py @@ -51,6 +51,8 @@ def backend_str(backend: MinerTypes) -> str: return "Mara Firmware Miners" case MinerTypes.BITAXE: return "Stock Firmware BitAxe Miners" + case MinerTypes.ICERIVER: + return "Stock Firmware IceRiver Miners" def create_url_str(mtype: str): diff --git a/docs/miners/antminer/X19.md b/docs/miners/antminer/X19.md index f01ba1ba..80a8af38 100644 --- a/docs/miners/antminer/X19.md +++ b/docs/miners/antminer/X19.md @@ -225,6 +225,13 @@ show_root_heading: false heading_level: 4 +## S19 Pro+ Hydro (BOS+) +::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19ProPlusHydro + handler: python + options: + show_root_heading: false + heading_level: 4 + ## T19 (BOS+) ::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19 handler: python @@ -281,6 +288,13 @@ show_root_heading: false heading_level: 4 +## S19 Pro Hydro (VNish) +::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProHydro + handler: python + options: + show_root_heading: false + heading_level: 4 + ## T19 (VNish) ::: pyasic.miners.antminer.vnish.X19.T19.VNishT19 handler: python diff --git a/docs/miners/antminer/X21.md b/docs/miners/antminer/X21.md index fc7539d8..6f38803c 100644 --- a/docs/miners/antminer/X21.md +++ b/docs/miners/antminer/X21.md @@ -8,6 +8,13 @@ show_root_heading: false heading_level: 4 +## S21 Pro (Stock) +::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro + handler: python + options: + show_root_heading: false + heading_level: 4 + ## T21 (Stock) ::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21 handler: python @@ -36,6 +43,13 @@ show_root_heading: false heading_level: 4 +## S21 Pro (ePIC) +::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro + handler: python + options: + show_root_heading: false + heading_level: 4 + ## T21 (ePIC) ::: pyasic.miners.antminer.epic.X21.T21.ePICT21 handler: python diff --git a/docs/miners/iceriver/KSX.md b/docs/miners/iceriver/KSX.md new file mode 100644 index 00000000..3218ff87 --- /dev/null +++ b/docs/miners/iceriver/KSX.md @@ -0,0 +1,10 @@ +# pyasic +## KSX Models + +## KS2 (Stock) +::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2 + handler: python + options: + show_root_heading: false + heading_level: 4 + diff --git a/docs/miners/supported_types.md b/docs/miners/supported_types.md index 27cd9120..cec1f5b3 100644 --- a/docs/miners/supported_types.md +++ b/docs/miners/supported_types.md @@ -89,6 +89,7 @@ details { X21 Series: @@ -461,6 +462,7 @@ details {
  • S19k Pro No PIC (BOS+)
  • S19k Pro No PIC (BOS+)
  • S19 XP (BOS+)
  • +
  • S19 Pro+ Hydro (BOS+)
  • T19 (BOS+)
  • @@ -505,6 +507,7 @@ details {
  • S19j Pro (VNish)
  • S19a (VNish)
  • S19a Pro (VNish)
  • +
  • S19 Pro Hydro (VNish)
  • T19 (VNish)
  • @@ -535,6 +538,7 @@ details { X21 Series: @@ -650,4 +654,15 @@ details { + +
    +Stock Firmware IceRiver Miners: +
    \ No newline at end of file