diff --git a/pyasic/config/__init__.py b/pyasic/config/__init__.py index e433217f..1ed04b5b 100644 --- a/pyasic/config/__init__.py +++ b/pyasic/config/__init__.py @@ -15,6 +15,8 @@ # ------------------------------------------------------------------------------ from dataclasses import dataclass +import toml + from pyasic.config.fans import FanModeConfig from pyasic.config.mining import MiningModeConfig from pyasic.config.pools import PoolConfig @@ -85,11 +87,41 @@ class MinerConfig: **self.power_scaling.as_inno(), } + def as_bosminer(self, user_suffix: str = None): + + return { + **merge(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()), + **self.mining_mode.as_bosminer(), + **self.pools.as_bosminer(user_suffix=user_suffix), + **self.power_scaling.as_bosminer(), + } + @classmethod def from_api(cls, api_pools: dict): return cls(pools=PoolConfig.from_api(api_pools)) + +def merge(a: dict, b: dict): + ret = {} + for k in a: + v = a[k] + if k in b.keys(): + if isinstance(v, dict): + ret[k] = merge(a[k], b[k]) + elif isinstance(v, list): + ret[k] = [*v, *b[k]] + else: + ret[k] = v + else: + ret[k] = v + for k in b: + v = b[k] + if k not in ret.keys(): + ret[k] = v + return ret + + if __name__ == "__main__": config = MinerConfig( pools=PoolConfig.simple( @@ -102,6 +134,8 @@ if __name__ == "__main__": ] ), mining_mode=MiningModeConfig.power_tuning(3000), + temperature=TemperatureConfig(hot=100), + fan_mode=FanModeConfig.manual(minimum_fans=2, speed=70), ) print("WM:", config.as_wm()) print("AM Modern:", config.as_am_modern()) @@ -109,3 +143,6 @@ if __name__ == "__main__": print("GS:", config.as_goldshell()) print("Avalon:", config.as_avalon()) print("Inno:", config.as_inno()) + print("BOS+ .toml:", config.as_bosminer()) + print("BOS+ .toml as toml:") + print(toml.dumps(config.as_bosminer())) diff --git a/pyasic/config/base.py b/pyasic/config/base.py index 2a39477a..590d2778 100644 --- a/pyasic/config/base.py +++ b/pyasic/config/base.py @@ -35,7 +35,7 @@ class MinerConfigOption(Enum): def as_avalon(self) -> dict: return self.value.as_avalon() - def as_bos(self) -> dict: + def as_bosminer(self) -> dict: return self.value.as_bos() def __call__(self, *args, **kwargs): @@ -61,5 +61,5 @@ class MinerConfigValue: def as_avalon(self) -> dict: return {} - def as_bos(self) -> dict: + def as_bosminer(self) -> dict: return {} diff --git a/pyasic/config/fans.py b/pyasic/config/fans.py index c26fbc30..7a608056 100644 --- a/pyasic/config/fans.py +++ b/pyasic/config/fans.py @@ -25,7 +25,7 @@ class FanModeNormal(MinerConfigValue): def as_am_modern(self): return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"} - def as_bos(self): + def as_bosminer(self): return {"temp_control": {"mode": "auto"}} @@ -38,7 +38,7 @@ class FanModeManual(MinerConfigValue): def as_am_modern(self): return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)} - def as_bos(self): + def as_bosminer(self): return { "temp_control": {"mode": "manual"}, "fan_control": {"min_fans": self.minimum_fans, "speed": self.speed}, @@ -52,8 +52,8 @@ class FanModeImmersion(MinerConfigValue): def as_am_modern(self): return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": "0"} - def as_bos(self): - return {"temp_control": {"mode": "manual"}, "fan_control": {"min_fans": 0}} + def as_bosminer(self): + return {"temp_control": {"mode": "disabled"}} class FanModeConfig(MinerConfigOption): diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index 41ffb7f2..17b87214 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -73,6 +73,9 @@ class MiningModePowerTune(MinerConfigValue): def as_wm(self): return {"mode": self.mode, self.mode: {"wattage": self.power}} + def as_bosminer(self) -> dict: + return {"autotuning": {"enabled": True, "psu_power_limit": self.power}} + @dataclass class MiningModeHashrateTune(MinerConfigValue): diff --git a/pyasic/config/pools.py b/pyasic/config/pools.py index d1bd3d1e..660b5151 100644 --- a/pyasic/config/pools.py +++ b/pyasic/config/pools.py @@ -93,6 +93,16 @@ class Pool(MinerConfigValue): def from_api(cls, api_pool: dict): return cls(url=api_pool["URL"], user=api_pool["User"], password="x") + def as_bosminer(self, user_suffix: str = None): + if user_suffix is not None: + return { + "url": self.url, + "user": f"{self.user}{user_suffix}", + "pass": self.password, + } + return {"url": self.url, "user": self.user, "pass": self.password} + + @dataclass class PoolGroup(MinerConfigValue): pools: list[Pool] = field(default_factory=list) @@ -178,6 +188,18 @@ class PoolGroup(MinerConfigValue): pools.append(Pool.from_api(pool)) return cls(pools=pools) + def as_bosminer(self, user_suffix: str = None) -> dict: + if len(self.pools) > 0: + return { + "name": self.name, + "quota": self.quota, + "pool": [ + pool.as_bosminer(user_suffix=user_suffix) for pool in self.pools + ], + } + return {"name": "Group", "quota": 1, "pool": [Pool.as_bosminer()]} + + @dataclass class PoolConfig(MinerConfigValue): groups: list[PoolGroup] = field(default_factory=list) @@ -230,4 +252,11 @@ class PoolConfig(MinerConfigValue): pool_data = api_pools["POOLS"] pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) - return cls([PoolGroup.from_api(pool_data)]) \ No newline at end of file + return cls([PoolGroup.from_api(pool_data)]) + + def as_bosminer(self, user_suffix: str = None) -> dict: + if len(self.groups) > 0: + return { + "group": [g.as_bosminer(user_suffix=user_suffix) for g in self.groups] + } + return {"group": [PoolGroup().as_bosminer()]} diff --git a/pyasic/config/temperature.py b/pyasic/config/temperature.py index 0e2b416a..3bfe6d47 100644 --- a/pyasic/config/temperature.py +++ b/pyasic/config/temperature.py @@ -27,3 +27,13 @@ class TemperatureConfig(MinerConfigValue): @classmethod def default(cls): return cls() + + def as_bosminer(self) -> dict: + temp_cfg = {} + if self.target is not None: + temp_cfg["target_temp"] = self.target + if self.hot is not None: + temp_cfg["hot_temp"] = self.hot + if self.danger is not None: + temp_cfg["dangerous_temp"] = self.danger + return {"temp_control": temp_cfg}