Compare commits

...

100 Commits

Author SHA1 Message Date
UpstreamData
5e01f7517b version: bump version number. 2024-01-22 13:39:52 -07:00
b-rowan
569f659fac Merge pull request #96 from fdeh75/fix-vnish-wattage-dimension
Fix vnish wattage dimension
2024-01-22 13:38:27 -07:00
fdeh
dd9c6f1f63 Fix vnish wattage dimension
Update backend vnish.py
2024-01-22 23:17:27 +03:00
b-rowan
0958f47cfe version: bump version number. 2024-01-21 14:39:35 -07:00
b-rowan
3820b40f44 bug: Fix DataLocations defaulting to all get_config. 2024-01-21 14:39:01 -07:00
b-rowan
cce1917c00 version: bump version number. 2024-01-21 12:28:44 -07:00
b-rowan
2ee19f47e7 bug: fix failing configuration on BOSminer. 2024-01-21 12:28:11 -07:00
b-rowan
ff526a3273 version: bump version number. 2024-01-21 10:39:48 -07:00
b-rowan
7811245ec9 Merge pull request #95 from UpstreamData/dev_quality
Improve overall code quality, move ssh to `miner.ssh`, remove `pwd` for miners.
2024-01-21 10:39:01 -07:00
b-rowan
cbab76847a refactor: remove BBB check for BOSMiner. 2024-01-21 10:15:44 -07:00
upstreamdata
ce981d1787 refactor: reformat. 2024-01-18 15:47:52 -07:00
upstreamdata
4b5314a8f6 refactor: move ssh to miner.ssh 2024-01-18 15:32:09 -07:00
UpstreamData
3be3086a38 docs: fix issues with docs. 2024-01-16 15:55:01 -07:00
UpstreamData
a0c76fe24f refactor: remove unused imports. 2024-01-16 15:36:55 -07:00
UpstreamData
acdcfd04cd refactor: remove unneeded lambda and remove pass. 2024-01-16 15:34:06 -07:00
UpstreamData
91a5998b4e refactor: remove unneeded lambda. 2024-01-16 15:31:31 -07:00
UpstreamData
7292af450c refactor: improve RPC handlers. 2024-01-16 15:26:32 -07:00
UpstreamData
307926afbb refactor: use protocol for BaseMiner and update attributes to be part of the class rather than a __init__ method. 2024-01-16 14:47:43 -07:00
UpstreamData
10293ae24a refactor: add default values for data locations to reduce duplication. 2024-01-16 09:31:23 -07:00
UpstreamData
f820372532 refactor: remove some duplicated code in rpc APIs. 2024-01-16 08:57:46 -07:00
UpstreamData
22965ffefa refactor: fix not x is None 2024-01-16 08:40:47 -07:00
UpstreamData
34ca5ba68f refactor: shorten some lines. 2024-01-16 08:39:15 -07:00
UpstreamData
468134e754 refactor: fix some not x in y and not x not in y 2024-01-16 08:36:31 -07:00
UpstreamData
5327b3fe3d refactor: remove unused variables. 2024-01-16 08:16:21 -07:00
UpstreamData
68b85aa7da refactor: remove some useless logging statements, and remove some unused imports. 2024-01-16 08:15:24 -07:00
UpstreamData
b78652b279 refactor: remove some unused variables. 2024-01-15 16:23:54 -07:00
UpstreamData
832a276f4b refactor: remove some unused pass statements. 2024-01-15 16:16:08 -07:00
UpstreamData
2b82b29690 refactor: remove some duplicate classes, and rename UnknownMiner get methods. 2024-01-15 16:03:40 -07:00
UpstreamData
56dd1c80b5 refactor: remove and fix some hardcoded passwords. 2024-01-15 15:53:10 -07:00
UpstreamData
d686cdacc8 refactor: change some if statements to if is not None. 2024-01-15 15:43:31 -07:00
UpstreamData
aab8825997 refactor: rename API to rpc, and classes from {X}API to {X}RPCAPI to clarify naming. 2024-01-15 15:09:51 -07:00
UpstreamData
4ed49c2321 version: bump version number. 2024-01-15 14:59:29 -07:00
UpstreamData
c069468803 bug: fix some bugs with epic, update miner repr, and remove get_model from braiinsOS. 2024-01-15 14:58:54 -07:00
UpstreamData
707cf8b848 version: bump version number. 2024-01-15 14:29:04 -07:00
UpstreamData
170843aae7 bug: add handler for failed get_data calls to make errors more verbose. 2024-01-15 14:28:39 -07:00
UpstreamData
f5acf9ec62 Merge branch 'dev_boser'
# Conflicts:
#	pyasic/miners/antminer/hiveon/X9/T9.py
#	pyasic/miners/backends/bosminer_old.py
#	pyasic/miners/backends/braiins_os.py
#	pyasic/miners/backends/btminer.py
#	pyasic/miners/backends/cgminer_avalon.py
#	pyasic/miners/backends/epic.py
#	pyasic/miners/backends/hiveon.py
#	pyasic/miners/backends/innosilicon.py
#	pyasic/miners/base.py
#	tests/miners_tests/__init__.py
2024-01-15 14:25:02 -07:00
UpstreamData
edaf89c73a refactor: fix some formatting issues and bugs. 2024-01-15 14:18:41 -07:00
UpstreamData
ce34dfdde8 bug: fix fault_light check for boser. 2024-01-15 14:00:51 -07:00
UpstreamData
e45e51ce65 refactor: fix merge. 2024-01-15 13:09:23 -07:00
UpstreamData
f1501718a3 feature: finish get_data functions for bosminer 2024-01-15 10:48:03 -07:00
UpstreamData
831d6ee955 feature: add boser fault light functions. 2024-01-15 10:48:02 -07:00
UpstreamData
7be6596fdd refactor: swap except (KeyError, ValueError) to except LookupError. 2024-01-15 10:48:02 -07:00
b-rowan
928e0dd028 feature: start refactoring BOSer and BOSMiner into separate classes. 2024-01-15 10:48:00 -07:00
UpstreamData
672e753afb bug: add test to cross check function arguments, and fix some method implementations and naming. 2024-01-15 10:47:56 -07:00
UpstreamData
269e6aac14 bug: add more tests and finish renaming methods. 2024-01-15 10:47:40 -07:00
UpstreamData
1a4f3f7dc7 bug: make sure all data locations are accurate. 2024-01-15 10:47:40 -07:00
UpstreamData
b0337e8417 refactor: swap (KeyError, IndexError) for LookupError. 2024-01-15 10:47:40 -07:00
UpstreamData
60f3687d02 refactor: optimize imports. 2024-01-15 10:47:39 -07:00
UpstreamData
a8c45cb95d refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-15 10:47:39 -07:00
UpstreamData
aa9ba66f8e bug: add test to cross check function arguments, and fix some method implementations and naming. 2024-01-15 10:47:39 -07:00
UpstreamData
06cc84f16d refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-15 10:47:38 -07:00
fdeh
067d5c98f5 Fix VNish get_hashrate and get_fans errors
Update vnish.py. Fix data locations according to the method arguments
2024-01-15 10:47:38 -07:00
UpstreamData
b4b84c773f refactor: remove bad function. 2024-01-15 10:47:38 -07:00
UpstreamData
cd1768aae9 refactor: swap (KeyError, IndexError) for LookupError. 2024-01-15 10:47:37 -07:00
UpstreamData
2ef85d3868 refactor: optimize imports. 2024-01-15 10:47:36 -07:00
UpstreamData
6f64cc5e0d refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-15 10:47:33 -07:00
b-rowan
d44907435c Merge pull request #91 from UpstreamData/dev_get_params
Move parameters to private methods for `get_{x}` methods
2024-01-15 10:43:08 -07:00
b-rowan
04ca75d00e Merge branch 'master' into dev_get_params 2024-01-15 10:42:37 -07:00
UpstreamData
b56e94ce8c bug: add more tests and finish renaming methods. 2024-01-15 10:35:15 -07:00
UpstreamData
e7d30aad84 bug: make sure all data locations are accurate. 2024-01-15 10:29:39 -07:00
UpstreamData
194fb539a1 refactor: swap (KeyError, IndexError) for LookupError. 2024-01-15 10:23:58 -07:00
UpstreamData
416ea2964b refactor: optimize imports. 2024-01-15 10:23:57 -07:00
UpstreamData
3234f7e06f refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-15 10:23:57 -07:00
UpstreamData
8fb357544b bug: add test to cross check function arguments, and fix some method implementations and naming. 2024-01-15 10:23:55 -07:00
UpstreamData
34006941ad bug: add test to cross check function arguments, and fix some method implementations and naming. 2024-01-15 10:16:47 -07:00
UpstreamData
3c3c34c54b Merge branch 'master' into dev_get_params 2024-01-15 08:10:46 -07:00
b-rowan
5a61a87766 docs: update docs. 2024-01-14 12:59:13 -07:00
b-rowan
ef9a026ee8 docs: update docs. 2024-01-14 12:58:11 -07:00
b-rowan
71c85e0603 bug: fix a possible failed authentication when using gRPC. 2024-01-14 12:09:29 -07:00
UpstreamData
c5224b808e refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-14 10:02:50 -07:00
b-rowan
e4c6d751a1 version: bump version number. 2024-01-14 10:02:38 -07:00
fdeh
ff4dfa124b Fix VNish get_hashrate and get_fans errors
Update vnish.py. Fix data locations according to the method arguments
2024-01-14 10:02:38 -07:00
b-rowan
d0eb5119aa version: bump version number. 2024-01-14 10:00:15 -07:00
fdeh
cfa51623c4 Fix VNish get_hashrate and get_fans errors
Update vnish.py. Fix data locations according to the method arguments
2024-01-14 10:00:15 -07:00
b-rowan
96bb56ebd1 version: bump version number. 2024-01-14 09:59:06 -07:00
b-rowan
cdd7beccbe Merge pull request #92 from fdeh75/fix-vnish-data-gathering
Fix VNish get_hashrate and get_fans errors
2024-01-14 09:58:16 -07:00
fdeh
1a544851df Fix VNish get_hashrate and get_fans errors
Update vnish.py. Fix data locations according to the method arguments
2024-01-14 19:53:47 +03:00
UpstreamData
aa2dc5a53d feature: update some gRPC functions, and add as_boser for some of the MinerConfig values. 2024-01-12 15:06:44 -07:00
UpstreamData
361d6e07cc feature: finish get_data functions for bosminer 2024-01-12 13:29:46 -07:00
UpstreamData
53a018f526 feature: add boser fault light functions. 2024-01-12 11:58:26 -07:00
UpstreamData
6c9a378eee feature: add boser config parsing. 2024-01-12 11:54:17 -07:00
UpstreamData
be67ef3471 refactor: remove bad function. 2024-01-11 15:29:29 -07:00
UpstreamData
a094d28a36 refactor: swap (KeyError, IndexError) for LookupError. 2024-01-11 15:20:33 -07:00
UpstreamData
4156f93c0d refactor: optimize imports. 2024-01-11 15:00:48 -07:00
UpstreamData
ed6eb11653 bug: fix being unable to get fw version as part of multicommand. 2024-01-11 13:57:48 -07:00
snyk-bot
39299f2cfa fix: docs/requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6150717
2024-01-11 11:37:01 -07:00
snyk-bot
c80ca1415a fix: docs/requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6150717
2024-01-11 11:36:05 -07:00
UpstreamData
a8428a2739 refactor: remove parameters from get_{x} functions and move them to _get_{x}(**params). Add miner.fw_str, and miner.raw_model. Remove model from get_data exclude. Swap fan_count to expected_fans. 2024-01-11 11:33:44 -07:00
UpstreamData
895fb1b43e refactor: swap except (KeyError, ValueError) to except LookupError. 2024-01-11 10:20:18 -07:00
UpstreamData
014896ae1b bug: fix data passed by get_version to BOSminer. 2024-01-11 09:53:06 -07:00
snyk-bot
84ac991685 fix: docs/requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6150717
2024-01-11 16:00:03 +00:00
b-rowan
bb481553fa bug: fix missing message in grpc command. 2024-01-10 22:46:58 -07:00
b-rowan
7ab3d8b54e feature: improve data gathering slightly on BOSMiner. 2024-01-10 22:26:28 -07:00
b-rowan
36494f2aca bug: remove boser check in miner_factory, and fix bad syntax on comparison. 2024-01-10 22:15:31 -07:00
b-rowan
bea44a72ea feature: start refactoring BOSer and BOSMiner into separate classes. 2024-01-10 22:12:27 -07:00
b-rowan
9da7b44177 feature: add vnish config parsing. 2024-01-06 11:31:12 -07:00
UpstreamData
e7f05f7a28 version: bump version number. 2024-01-05 16:22:03 -07:00
UpstreamData
2d229be9fd feature: add board serial numbers to whatsminers. 2024-01-05 16:18:03 -07:00
UpstreamData
de5038e57a feature: add AntminerModern serial numbers to Hashboard data. 2024-01-05 15:57:26 -07:00
UpstreamData
8ad1b3f72a refactor: fix formatting issue. 2024-01-05 08:49:44 -07:00
203 changed files with 4960 additions and 7751 deletions

View File

@@ -1,27 +0,0 @@
# pyasic
## Miner APIs
Each miner has a unique API that is used to communicate with it.
Each of these API types has commands that differ between them, and some commands have data that others do not.
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
Use these instead -
#### [BFGMiner API][pyasic.API.bfgminer.BFGMinerAPI]
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
<br>
## BaseMinerAPI
::: pyasic.API.BaseMinerAPI
handler: python
options:
heading_level: 4

View File

@@ -1,6 +1,6 @@
# pyasic
## Miner Data
## Miner Data
::: pyasic.data.MinerData
handler: python
options:
@@ -13,3 +13,10 @@
options:
show_root_heading: false
heading_level: 4
## Fan Data
::: pyasic.data.Fan
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -122,24 +122,24 @@ if __name__ == "__main__":
## Miner control
---
`pyasic` exposes a standard interface for each miner using control functions.
Every miner class in `pyasic` must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
Every miner class in `pyasic` must implement all the control functions defined in [`MinerProtocol`][pyasic.miners.base.MinerProtocol].
These functions are
[`check_light`][pyasic.miners.BaseMiner.check_light],
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off],
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on],
[`get_config`][pyasic.miners.BaseMiner.get_config],
[`get_data`][pyasic.miners.BaseMiner.get_data],
[`get_errors`][pyasic.miners.BaseMiner.get_errors],
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname],
[`get_model`][pyasic.miners.BaseMiner.get_model],
[`reboot`][pyasic.miners.BaseMiner.reboot],
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend],
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining],
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining],
[`is_mining`][pyasic.miners.BaseMiner.is_mining],
[`send_config`][pyasic.miners.BaseMiner.send_config], and
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit].
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
[`get_config`][pyasic.miners.base.MinerProtocol.get_config],
[`get_data`][pyasic.miners.base.MinerProtocol.get_data],
[`get_errors`][pyasic.miners.base.MinerProtocol.get_errors],
[`get_hostname`][pyasic.miners.base.MinerProtocol.get_hostname],
[`get_model`][pyasic.miners.base.MinerProtocol.get_model],
[`reboot`][pyasic.miners.base.MinerProtocol.reboot],
[`restart_backend`][pyasic.miners.base.MinerProtocol.restart_backend],
[`stop_mining`][pyasic.miners.base.MinerProtocol.stop_mining],
[`resume_mining`][pyasic.miners.base.MinerProtocol.resume_mining],
[`is_mining`][pyasic.miners.base.MinerProtocol.is_mining],
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
##### Usage
```python

View File

@@ -1,7 +1,15 @@
# pyasic
## BOSMiner Backend
::: pyasic.miners.backends.bosminer.BOSMiner
::: pyasic.miners.backends.braiins_os.BOSMiner
handler: python
options:
show_root_heading: false
heading_level: 4
## BOSer Backend
::: pyasic.miners.backends.braiins_os.BOSer
handler: python
options:
show_root_heading: false

View File

@@ -1,10 +1,17 @@
# pyasic
## Base Miner
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
[`BaseMiner`][pyasic.miners.base.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`.
This class inherits from the [`MinerProtocol`][pyasic.miners.base.MinerProtocol], which outlines functionality for miners.
::: pyasic.miners.BaseMiner
You may not instantiate this class on its own, only subclass from it.
::: pyasic.miners.base.BaseMiner
handler: python
options:
heading_level: 4
::: pyasic.miners.base.MinerProtocol
handler: python
options:
heading_level: 4

View File

@@ -1,91 +1,91 @@
## Control functionality
### Check Light
::: pyasic.miners.BaseMiner.check_light
::: pyasic.miners.base.MinerProtocol.check_light
handler: python
options:
heading_level: 4
### Fault Light Off
::: pyasic.miners.BaseMiner.fault_light_off
::: pyasic.miners.base.MinerProtocol.fault_light_off
handler: python
options:
heading_level: 4
### Fault Light On
::: pyasic.miners.BaseMiner.fault_light_on
::: pyasic.miners.base.MinerProtocol.fault_light_on
handler: python
options:
heading_level: 4
### Get Config
::: pyasic.miners.BaseMiner.get_config
::: pyasic.miners.base.MinerProtocol.get_config
handler: python
options:
heading_level: 4
### Get Data
::: pyasic.miners.BaseMiner.get_data
::: pyasic.miners.base.MinerProtocol.get_data
handler: python
options:
heading_level: 4
### Get Errors
::: pyasic.miners.BaseMiner.get_errors
::: pyasic.miners.base.MinerProtocol.get_errors
handler: python
options:
heading_level: 4
### Get Hostname
::: pyasic.miners.BaseMiner.get_hostname
::: pyasic.miners.base.MinerProtocol.get_hostname
handler: python
options:
heading_level: 4
### Get Model
::: pyasic.miners.BaseMiner.get_model
::: pyasic.miners.base.MinerProtocol.get_model
handler: python
options:
heading_level: 4
### Reboot
::: pyasic.miners.BaseMiner.reboot
::: pyasic.miners.base.MinerProtocol.reboot
handler: python
options:
heading_level: 4
### Restart Backend
::: pyasic.miners.BaseMiner.restart_backend
::: pyasic.miners.base.MinerProtocol.restart_backend
handler: python
options:
heading_level: 4
### Stop Mining
::: pyasic.miners.BaseMiner.stop_mining
::: pyasic.miners.base.MinerProtocol.stop_mining
handler: python
options:
heading_level: 4
### Resume Mining
::: pyasic.miners.BaseMiner.resume_mining
::: pyasic.miners.base.MinerProtocol.resume_mining
handler: python
options:
heading_level: 4
### Is Mining
::: pyasic.miners.BaseMiner.is_mining
::: pyasic.miners.base.MinerProtocol.is_mining
handler: python
options:
heading_level: 4
### Send Config
::: pyasic.miners.BaseMiner.send_config
::: pyasic.miners.base.MinerProtocol.send_config
handler: python
options:
heading_level: 4
### Set Power Limit
::: pyasic.miners.BaseMiner.set_power_limit
::: pyasic.miners.base.MinerProtocol.set_power_limit
handler: python
options:
heading_level: 4

View File

@@ -2,23 +2,22 @@
## X5 Models
## CK5
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
handler: python
options:
show_root_heading: false
heading_level: 4
## HS5
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
handler: python
options:
show_root_heading: false
heading_level: 4
## KD5
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -2,9 +2,8 @@
## XMax Models
## KD Max
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.KDMax
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -2,9 +2,8 @@
## A10X Models
## A10X
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.CGMinerA10X
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -2,9 +2,8 @@
## T3X Models
## T3H+
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,3 +1,3 @@
jinja2<3.1.0
jinja2<3.1.3
mkdocs
mkdocstrings[python]

27
docs/rpc/api.md Normal file
View File

@@ -0,0 +1,27 @@
# pyasic
## Miner APIs
Each miner has a unique API that is used to communicate with it.
Each of these API types has commands that differ between them, and some commands have data that others do not.
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI], which implements the basic communications protocols.
[`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
[`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
Use these instead -
#### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI]
#### [BMMiner API][pyasic.rpc.bmminer.BMMinerRPCAPI]
#### [BOSMiner API][pyasic.rpc.bosminer.BOSMinerRPCAPI]
#### [BTMiner API][pyasic.rpc.btminer.BTMinerRPCAPI]
#### [CGMiner API][pyasic.rpc.cgminer.CGMinerRPCAPI]
#### [LUXMiner API][pyasic.rpc.luxminer.LUXMinerRPCAPI]
#### [Unknown API][pyasic.rpc.unknown.UnknownRPCAPI]
<br>
## BaseMinerRPCAPI
::: pyasic.rpc.BaseMinerRPCAPI
handler: python
options:
heading_level: 4

View File

@@ -1,6 +1,6 @@
# pyasic
## BFGMinerAPI
::: pyasic.API.bfgminer.BFGMinerAPI
## BFGMinerRPCAPI
::: pyasic.rpc.bfgminer.BFGMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## BMMinerAPI
::: pyasic.API.bmminer.BMMinerAPI
## BMMinerRPCAPI
::: pyasic.rpc.bmminer.BMMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## BOSMinerAPI
::: pyasic.API.bosminer.BOSMinerAPI
## BOSMinerRPCAPI
::: pyasic.rpc.bosminer.BOSMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## BTMinerAPI
::: pyasic.API.btminer.BTMinerAPI
## BTMinerRPCAPI
::: pyasic.rpc.btminer.BTMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## CGMinerAPI
::: pyasic.API.cgminer.CGMinerAPI
## CGMinerRPCAPI
::: pyasic.rpc.cgminer.CGMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## LUXMinerAPI
::: pyasic.API.luxminer.LUXMinerAPI
## LUXMinerRPCAPI
::: pyasic.rpc.luxminer.LUXMinerRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic
## UnknownAPI
::: pyasic.API.unknown.UnknownAPI
## UnknownRPCAPI
::: pyasic.rpc.unknown.UnknownRPCAPI
handler: python
options:
show_root_heading: false

View File

@@ -4,6 +4,7 @@
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
Settings options:
- `network_ping_retries`
- `network_ping_timeout`
- `network_scan_threads`

View File

@@ -13,15 +13,15 @@ nav:
- Error Codes: "data/error_codes.md"
- Miner Config: "config/miner_config.md"
- Advanced:
- Miner APIs:
- Intro: "API/api.md"
- BFGMiner: "API/bfgminer.md"
- BMMiner: "API/bmminer.md"
- BOSMiner: "API/bosminer.md"
- BTMiner: "API/btminer.md"
- CGMiner: "API/cgminer.md"
- LUXMiner: "API/luxminer.md"
- Unknown: "API/unknown.md"
- RPC APIs:
- Intro: "rpc/api.md"
- BFGMiner: "rpc/bfgminer.md"
- BMMiner: "rpc/bmminer.md"
- BOSMiner: "rpc/bosminer.md"
- BTMiner: "rpc/btminer.md"
- CGMiner: "rpc/cgminer.md"
- LUXMiner: "rpc/luxminer.md"
- Unknown: "rpc/unknown.md"
- Backends:
- BMMiner: "miners/backends/bmminer.md"
- BOSMiner: "miners/backends/bosminer.md"

View File

@@ -1,674 +0,0 @@
# ------------------------------------------------------------------------------
# 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 asyncio
import logging
from pyasic.API import APIError, BaseMinerAPI
class BFGMinerAPI(BaseMinerAPI):
"""An abstraction of the BFGMiner API.
Each method corresponds to an API command in BFGMiner.
[BFGMiner API documentation](https://github.com/luke-jr/bfgminer/blob/bfgminer/README.RPC)
This class abstracts use of the BFGMiner API, as well as the
methods for sending commands to it. The self.send_command()
function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which
rely on it to send the command for them.
Parameters:
ip: The IP of the miner to reference the API on.
port: The port to reference the API on. Default is 4028.
"""
def __init__(self, ip: str, api_ver: str = "0.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:
# 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:
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
data = await self._x19_multicommand(*command.split("+"))
data["multicommand"] = True
return data
async def _x19_multicommand(self, *commands) -> dict:
tasks = []
# send all commands individually
for cmd in commands:
tasks.append(
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
)
all_data = await asyncio.gather(*tasks)
data = {}
for item in all_data:
data.update(item)
return data
async def version(self) -> dict:
"""Get miner version info.
<details>
<summary>Expand</summary>
Returns:
Miner version information.
</details>
"""
return await self.send_command("version")
async def config(self) -> dict:
"""Get some basic configuration info.
<details>
<summary>Expand</summary>
Returns:
## Some miner configuration information:
* ASC Count <- the number of ASCs
* PGA Count <- the number of PGAs
* Pool Count <- the number of Pools
* Strategy <- the current pool strategy
* Log Interval <- the interval of logging
* Device Code <- list of compiled device drivers
* OS <- the current operating system
* Failover-Only <- failover-only setting
* Scan Time <- scan-time setting
* Queue <- queue setting
* Expiry <- expiry setting
</details>
"""
return await self.send_command("config")
async def summary(self) -> dict:
"""Get the status summary of the miner.
<details>
<summary>Expand</summary>
Returns:
The status summary of the miner.
</details>
"""
return await self.send_command("summary")
async def pools(self) -> dict:
"""Get pool information.
<details>
<summary>Expand</summary>
Returns:
Miner pool information.
</details>
"""
return await self.send_command("pools")
async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
Returns:
Data on each PGA/ASC with their details.
</details>
"""
return await self.send_command("devs")
async def procs(self) -> dict:
"""Get data on each processor with their details.
<details>
<summary>Expand</summary>
Returns:
Data on each processor with their details.
</details>
"""
return await self.send_command("procs")
async def devscan(self, info: str = "") -> dict:
"""Get data on each processor with their details.
<details>
<summary>Expand</summary>
Parameters:
info: Info to scan for device by.
Returns:
Data on each processor with their details.
</details>
"""
return await self.send_command("devscan", parameters=info)
async def pga(self, n: int) -> dict:
"""Get data from PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA number to get data from.
Returns:
Data on the PGA n.
</details>
"""
return await self.send_command("pga", parameters=n)
async def proc(self, n: int = 0) -> dict:
"""Get data processor n.
<details>
<summary>Expand</summary>
Parameters:
n: The processor to get data on.
Returns:
Data on processor n.
</details>
"""
return await self.send_command("proc", parameters=n)
async def pgacount(self) -> dict:
"""Get data fon all PGAs.
<details>
<summary>Expand</summary>
Returns:
Data on the PGAs connected.
</details>
"""
return await self.send_command("pgacount")
async def proccount(self) -> dict:
"""Get data fon all processors.
<details>
<summary>Expand</summary>
Returns:
Data on the processors connected.
</details>
"""
return await self.send_command("proccount")
async def switchpool(self, n: int) -> dict:
"""Switch pools to pool n.
<details>
<summary>Expand</summary>
Parameters:
n: The pool to switch to.
Returns:
A confirmation of switching to pool n.
</details>
"""
return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict:
"""Enable pool n.
<details>
<summary>Expand</summary>
Parameters:
n: The pool to enable.
Returns:
A confirmation of enabling pool n.
</details>
"""
return await self.send_command("enablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict:
"""Add a pool to the miner.
<details>
<summary>Expand</summary>
Parameters:
url: The URL of the new pool to add.
username: The users username on the new pool.
password: The worker password on the new pool.
Returns:
A confirmation of adding the pool.
</details>
"""
return await self.send_command(
"addpool", parameters=f"{url},{username},{password}"
)
async def poolpriority(self, *n: int) -> dict:
"""Set pool priority.
<details>
<summary>Expand</summary>
Parameters:
*n: Pools in order of priority.
Returns:
A confirmation of setting pool priority.
</details>
"""
pools = f"{','.join([str(item) for item in n])}"
return await self.send_command("poolpriority", parameters=pools)
async def poolquota(self, n: int, q: int) -> dict:
"""Set pool quota.
<details>
<summary>Expand</summary>
Parameters:
n: Pool number to set quota on.
q: Quota to set the pool to.
Returns:
A confirmation of setting pool quota.
</details>
"""
return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict:
"""Disable a pool.
<details>
<summary>Expand</summary>
Parameters:
n: Pool to disable.
Returns:
A confirmation of diabling the pool.
</details>
"""
return await self.send_command("disablepool", parameters=n)
async def removepool(self, n: int) -> dict:
"""Remove a pool.
<details>
<summary>Expand</summary>
Parameters:
n: Pool to remove.
Returns:
A confirmation of removing the pool.
</details>
"""
return await self.send_command("removepool", parameters=n)
async def save(self, filename: str = None) -> dict:
"""Save the config.
<details>
<summary>Expand</summary>
Parameters:
filename: Filename to save the config as.
Returns:
A confirmation of saving the config.
</details>
"""
if filename:
return await self.send_command("save", parameters=filename)
else:
return await self.send_command("save")
async def quit(self) -> dict:
"""Quit CGMiner.
<details>
<summary>Expand</summary>
Returns:
A single "BYE" before CGMiner quits.
</details>
"""
return await self.send_command("quit")
async def notify(self) -> dict:
"""Notify the user of past errors.
<details>
<summary>Expand</summary>
Returns:
The last status and count of each devices problem(s).
</details>
"""
return await self.send_command("notify")
async def privileged(self) -> dict:
"""Check if you have privileged access.
<details>
<summary>Expand</summary>
Returns:
The STATUS section with an error if you have no privileged access, or success if you have privileged access.
</details>
"""
return await self.send_command("privileged")
async def pgaenable(self, n: int) -> dict:
"""Enable PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to enable.
Returns:
A confirmation of enabling PGA n.
</details>
"""
return await self.send_command("pgaenable", parameters=n)
async def pgadisable(self, n: int) -> dict:
"""Disable PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to disable.
Returns:
A confirmation of disabling PGA n.
</details>
"""
return await self.send_command("pgadisable", parameters=n)
async def pgarestart(self, n: int) -> dict:
"""Restart PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to restart.
Returns:
A confirmation of restarting PGA n.
</details>
"""
return await self.send_command("pgadisable", parameters=n)
async def pgaidentify(self, n: int) -> dict:
"""Identify PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to identify.
Returns:
A confirmation of identifying PGA n.
</details>
"""
return await self.send_command("pgaidentify", parameters=n)
async def procenable(self, n: int) -> dict:
"""Enable processor n.
<details>
<summary>Expand</summary>
Parameters:
n: The processor to enable.
Returns:
A confirmation of enabling processor n.
</details>
"""
return await self.send_command("procenable", parameters=n)
async def procdisable(self, n: int) -> dict:
"""Disable processor n.
<details>
<summary>Expand</summary>
Parameters:
n: The processor to disable.
Returns:
A confirmation of disabling processor n.
</details>
"""
return await self.send_command("procdisable", parameters=n)
async def procrestart(self, n: int) -> dict:
"""Restart processor n.
<details>
<summary>Expand</summary>
Parameters:
n: The processor to restart.
Returns:
A confirmation of restarting processor n.
</details>
"""
return await self.send_command("procdisable", parameters=n)
async def procidentify(self, n: int) -> dict:
"""Identify processor n.
<details>
<summary>Expand</summary>
Parameters:
n: The processor to identify.
Returns:
A confirmation of identifying processor n.
</details>
"""
return await self.send_command("procidentify", parameters=n)
async def devdetails(self) -> dict:
"""Get data on all devices with their static details.
<details>
<summary>Expand</summary>
Returns:
Data on all devices with their static details.
</details>
"""
return await self.send_command("devdetails")
async def restart(self) -> dict:
"""Restart CGMiner using the API.
<details>
<summary>Expand</summary>
Returns:
A reply informing of the restart.
</details>
"""
return await self.send_command("restart")
async def stats(self) -> dict:
"""Get stats of each device/pool with more than 1 getwork.
<details>
<summary>Expand</summary>
Returns:
Stats of each device/pool with more than 1 getwork.
</details>
"""
return await self.send_command("stats")
async def check(self, command: str) -> dict:
"""Check if the command command exists in CGMiner.
<details>
<summary>Expand</summary>
Parameters:
command: The command to check.
Returns:
## Information about a command:
* Exists (Y/N) <- the command exists in this version
* Access (Y/N) <- you have access to use the command
</details>
"""
return await self.send_command("check", parameters=command)
async def failover_only(self, failover: bool) -> dict:
"""Set failover-only.
<details>
<summary>Expand</summary>
Parameters:
failover: What to set failover-only to.
Returns:
Confirmation of setting failover-only.
</details>
"""
return await self.send_command("failover-only", parameters=failover)
async def coin(self) -> dict:
"""Get information on the current coin.
<details>
<summary>Expand</summary>
Returns:
## Information about the current coin being mined:
* Hash Method <- the hashing algorithm
* Current Block Time <- blocktime as a float, 0 means none
* Current Block Hash <- the hash of the current block, blank means none
* LP <- whether LP is in use on at least 1 pool
* Network Difficulty: the current network difficulty
</details>
"""
return await self.send_command("coin")
async def debug(self, setting: str) -> dict:
"""Set a debug setting.
<details>
<summary>Expand</summary>
Parameters:
setting: Which setting to switch to.
## Options are:
* Silent
* Quiet
* Verbose
* Debug
* RPCProto
* PerDevice
* WorkTime
* Normal
Returns:
Data on which debug setting was enabled or disabled.
</details>
"""
return await self.send_command("debug", parameters=setting)
async def setconfig(self, name: str, n: int) -> dict:
"""Set config of name to value n.
<details>
<summary>Expand</summary>
Parameters:
name: The name of the config setting to set.
## Options are:
* queue
* scantime
* expiry
n: The value to set the 'name' setting to.
Returns:
The results of setting config of name to n.
</details>
"""
return await self.send_command("setconfig", parameters=f"{name},{n}")
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
"""Set PGA option opt to val on PGA n.
<details>
<summary>Expand</summary>
Options:
```
MMQ -
opt: clock
val: 2 - 250 (multiple of 2)
XBS -
opt: clock
val: 2 - 250 (multiple of 2)
```
Parameters:
n: The PGA to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
Returns:
Confirmation of setting PGA n with opt[,val].
</details>
"""
if val:
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
else:
return await self.send_command("pgaset", parameters=f"{n},{opt}")
async def pprocset(self, n: int, opt: str, val: int = None) -> dict:
"""Set processor option opt to val on processor n.
<details>
<summary>Expand</summary>
Options:
```
MMQ -
opt: clock
val: 2 - 250 (multiple of 2)
XBS -
opt: clock
val: 2 - 250 (multiple of 2)
```
Parameters:
n: The PGA to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
Returns:
Confirmation of setting PGA n with opt[,val].
</details>
"""
if val:
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
else:
return await self.send_command("pgaset", parameters=f"{n},{opt}")
async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device.
<details>
<summary>Expand</summary>
Parameters:
which: Which device to zero. Setting this to 'all' zeros all devices. Setting this to 'bestshare' zeros only the bestshare values for each pool and global.
summary: Whether or not to show a full summary.
Returns:
the STATUS section with info on the zero and optional summary.
</details>
"""
return await self.send_command("zero", parameters=f"{which},{summary}")

View File

@@ -1,731 +0,0 @@
# ------------------------------------------------------------------------------
# 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 asyncio
import logging
from pyasic.API import APIError, BaseMinerAPI
class BMMinerAPI(BaseMinerAPI):
"""An abstraction of the BMMiner API.
Each method corresponds to an API command in BMMiner.
[BMMiner API documentation](https://github.com/jameshilliard/bmminer/blob/master/API-README)
This class abstracts use of the BMMiner API, as well as the
methods for sending commands to it. The `self.send_command()`
function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which
rely on it to send the command for them.
Parameters:
ip: The IP of the miner to reference the API on.
port: The port to reference the API on. Default is 4028.
"""
def __init__(self, ip: str, api_ver: str = "0.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:
# 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:
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
data = await self._x19_multicommand(
*command.split("+"), allow_warning=allow_warning
)
data["multicommand"] = True
return data
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
tasks = []
# send all commands individually
for cmd in commands:
tasks.append(
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
)
all_data = await asyncio.gather(*tasks)
data = {}
for item in all_data:
data.update(item)
return data
async def version(self) -> dict:
"""Get miner version info.
<details>
<summary>Expand</summary>
Returns:
Miner version information.
</details>
"""
return await self.send_command("version")
async def config(self) -> dict:
"""Get some basic configuration info.
<details>
<summary>Expand</summary>
Returns:
## Some miner configuration information:
* ASC Count <- the number of ASCs
* PGA Count <- the number of PGAs
* Pool Count <- the number of Pools
* Strategy <- the current pool strategy
* Log Interval <- the interval of logging
* Device Code <- list of compiled device drivers
* OS <- the current operating system
* Failover-Only <- failover-only setting
* Scan Time <- scan-time setting
* Queue <- queue setting
* Expiry <- expiry setting
</details>
"""
return await self.send_command("config")
async def summary(self) -> dict:
"""Get the status summary of the miner.
<details>
<summary>Expand</summary>
Returns:
The status summary of the miner.
</details>
"""
return await self.send_command("summary")
async def pools(self) -> dict:
"""Get pool information.
<details>
<summary>Expand</summary>
Returns:
Miner pool information.
</details>
"""
return await self.send_command("pools")
async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
Returns:
Data on each PGA/ASC with their details.
</details>
"""
return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict:
"""Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
<details>
<summary>Expand</summary>
Parameters:
old: Include zombie devices that became zombies less than 'old' seconds ago
Returns:
Data on each PGA/ASC with their details.
</details>
"""
if old:
return await self.send_command("edevs", parameters=old)
else:
return await self.send_command("edevs")
async def pga(self, n: int) -> dict:
"""Get data from PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA number to get data from.
Returns:
Data on the PGA n.
</details>
"""
return await self.send_command("pga", parameters=n)
async def pgacount(self) -> dict:
"""Get data fon all PGAs.
<details>
<summary>Expand</summary>
Returns:
Data on the PGAs connected.
</details>
"""
return await self.send_command("pgacount")
async def switchpool(self, n: int) -> dict:
"""Switch pools to pool n.
<details>
<summary>Expand</summary>
Parameters:
n: The pool to switch to.
Returns:
A confirmation of switching to pool n.
</details>
"""
return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict:
"""Enable pool n.
<details>
<summary>Expand</summary>
Parameters:
n: The pool to enable.
Returns:
A confirmation of enabling pool n.
</details>
"""
return await self.send_command("enablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict:
"""Add a pool to the miner.
<details>
<summary>Expand</summary>
Parameters:
url: The URL of the new pool to add.
username: The users username on the new pool.
password: The worker password on the new pool.
Returns:
A confirmation of adding the pool.
</details>
"""
return await self.send_command(
"addpool", parameters=f"{url},{username},{password}"
)
async def poolpriority(self, *n: int) -> dict:
"""Set pool priority.
<details>
<summary>Expand</summary>
Parameters:
*n: Pools in order of priority.
Returns:
A confirmation of setting pool priority.
</details>
"""
pools = f"{','.join([str(item) for item in n])}"
return await self.send_command("poolpriority", parameters=pools)
async def poolquota(self, n: int, q: int) -> dict:
"""Set pool quota.
<details>
<summary>Expand</summary>
Parameters:
n: Pool number to set quota on.
q: Quota to set the pool to.
Returns:
A confirmation of setting pool quota.
</details>
"""
return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict:
"""Disable a pool.
<details>
<summary>Expand</summary>
Parameters:
n: Pool to disable.
Returns:
A confirmation of diabling the pool.
</details>
"""
return await self.send_command("disablepool", parameters=n)
async def removepool(self, n: int) -> dict:
"""Remove a pool.
<details>
<summary>Expand</summary>
Parameters:
n: Pool to remove.
Returns:
A confirmation of removing the pool.
</details>
"""
return await self.send_command("removepool", parameters=n)
async def save(self, filename: str = None) -> dict:
"""Save the config.
<details>
<summary>Expand</summary>
Parameters:
filename: Filename to save the config as.
Returns:
A confirmation of saving the config.
</details>
"""
if filename:
return await self.send_command("save", parameters=filename)
else:
return await self.send_command("save")
async def quit(self) -> dict:
"""Quit BMMiner.
<details>
<summary>Expand</summary>
Returns:
A single "BYE" before BMMiner quits.
</details>
"""
return await self.send_command("quit")
async def notify(self) -> dict:
"""Notify the user of past errors.
<details>
<summary>Expand</summary>
Returns:
The last status and count of each devices problem(s).
</details>
"""
return await self.send_command("notify")
async def privileged(self) -> dict:
"""Check if you have privileged access.
<details>
<summary>Expand</summary>
Returns:
The STATUS section with an error if you have no privileged access, or success if you have privileged access.
</details>
"""
return await self.send_command("privileged")
async def pgaenable(self, n: int) -> dict:
"""Enable PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to enable.
Returns:
A confirmation of enabling PGA n.
</details>
"""
return await self.send_command("pgaenable", parameters=n)
async def pgadisable(self, n: int) -> dict:
"""Disable PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to disable.
Returns:
A confirmation of disabling PGA n.
</details>
"""
return await self.send_command("pgadisable", parameters=n)
async def pgaidentify(self, n: int) -> dict:
"""Identify PGA n.
<details>
<summary>Expand</summary>
Parameters:
n: The PGA to identify.
Returns:
A confirmation of identifying PGA n.
</details>
"""
return await self.send_command("pgaidentify", parameters=n)
async def devdetails(self) -> dict:
"""Get data on all devices with their static details.
<details>
<summary>Expand</summary>
Returns:
Data on all devices with their static details.
</details>
"""
return await self.send_command("devdetails")
async def restart(self) -> dict:
"""Restart BMMiner using the API.
<details>
<summary>Expand</summary>
Returns:
A reply informing of the restart.
</details>
"""
return await self.send_command("restart")
async def stats(self) -> dict:
"""Get stats of each device/pool with more than 1 getwork.
<details>
<summary>Expand</summary>
Returns:
Stats of each device/pool with more than 1 getwork.
</details>
"""
return await self.send_command("stats")
async def estats(self, old: bool = False) -> dict:
"""Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
<details>
<summary>Expand</summary>
Parameters:
old: Include zombie devices that became zombies less than 'old' seconds ago.
Returns:
Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
</details>
"""
if old:
return await self.send_command("estats", parameters=old)
else:
return await self.send_command("estats")
async def check(self, command: str) -> dict:
"""Check if the command command exists in BMMiner.
<details>
<summary>Expand</summary>
Parameters:
command: The command to check.
Returns:
## Information about a command:
* Exists (Y/N) <- the command exists in this version
* Access (Y/N) <- you have access to use the command
</details>
"""
return await self.send_command("check", parameters=command)
async def failover_only(self, failover: bool) -> dict:
"""Set failover-only.
<details>
<summary>Expand</summary>
Parameters:
failover: What to set failover-only to.
Returns:
Confirmation of setting failover-only.
</details>
"""
return await self.send_command("failover-only", parameters=failover)
async def coin(self) -> dict:
"""Get information on the current coin.
<details>
<summary>Expand</summary>
Returns:
## Information about the current coin being mined:
* Hash Method <- the hashing algorithm
* Current Block Time <- blocktime as a float, 0 means none
* Current Block Hash <- the hash of the current block, blank means none
* LP <- whether LP is in use on at least 1 pool
* Network Difficulty: the current network difficulty
</details>
"""
return await self.send_command("coin")
async def debug(self, setting: str) -> dict:
"""Set a debug setting.
<details>
<summary>Expand</summary>
Parameters:
setting: Which setting to switch to.
## Options are:
* Silent
* Quiet
* Verbose
* Debug
* RPCProto
* PerDevice
* WorkTime
* Normal
Returns:
Data on which debug setting was enabled or disabled.
</details>
"""
return await self.send_command("debug", parameters=setting)
async def setconfig(self, name: str, n: int) -> dict:
"""Set config of name to value n.
<details>
<summary>Expand</summary>
Parameters:
name: The name of the config setting to set.
## Options are:
* queue
* scantime
* expiry
n: The value to set the 'name' setting to.
Returns:
The results of setting config of name to n.
</details>
"""
return await self.send_command("setconfig", parameters=f"{name},{n}")
async def usbstats(self) -> dict:
"""Get stats of all USB devices except ztex.
<details>
<summary>Expand</summary>
Returns:
The stats of all USB devices except ztex.
</details>
"""
return await self.send_command("usbstats")
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
"""Set PGA option opt to val on PGA n.
<details>
<summary>Expand</summary>
Options:
```
MMQ -
opt: clock
val: 160 - 230 (multiple of 2)
CMR -
opt: clock
val: 100 - 220
```
Parameters:
n: The PGA to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
Returns:
Confirmation of setting PGA n with opt[,val].
</details>
"""
if val:
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
else:
return await self.send_command("pgaset", parameters=f"{n},{opt}")
async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device.
<details>
<summary>Expand</summary>
Parameters:
which: Which device to zero. Setting this to 'all' zeros all devices. Setting this to 'bestshare' zeros only the bestshare values for each pool and global.
summary: Whether or not to show a full summary.
Returns:
the STATUS section with info on the zero and optional summary.
</details>
"""
return await self.send_command("zero", parameters=f"{which},{summary}")
async def hotplug(self, n: int) -> dict:
"""Enable hotplug.
<details>
<summary>Expand</summary>
Parameters:
n: The device number to set hotplug on.
Returns:
Information on hotplug status.
</details>
"""
return await self.send_command("hotplug", parameters=n)
async def asc(self, n: int) -> dict:
"""Get data for ASC device n.
<details>
<summary>Expand</summary>
Parameters:
n: The device to get data for.
Returns:
The data for ASC device n.
</details>
"""
return await self.send_command("asc", parameters=n)
async def ascenable(self, n: int) -> dict:
"""Enable ASC device n.
<details>
<summary>Expand</summary>
Parameters:
n: The device to enable.
Returns:
Confirmation of enabling ASC device n.
</details>
"""
return await self.send_command("ascenable", parameters=n)
async def ascdisable(self, n: int) -> dict:
"""Disable ASC device n.
<details>
<summary>Expand</summary>
Parameters:
n: The device to disable.
Returns:
Confirmation of disabling ASC device n.
</details>
"""
return await self.send_command("ascdisable", parameters=n)
async def ascidentify(self, n: int) -> dict:
"""Identify ASC device n.
<details>
<summary>Expand</summary>
Parameters:
n: The device to identify.
Returns:
Confirmation of identifying ASC device n.
</details>
"""
return await self.send_command("ascidentify", parameters=n)
async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info.
<details>
<summary>Expand</summary>
Returns:
Data on all ASC devices.
</details>
"""
return await self.send_command("asccount")
async def ascset(self, n: int, opt: str, val: int = None) -> dict:
"""Set ASC n option opt to value val.
<details>
<summary>Expand</summary>
Sets an option on the ASC n to a value. Allowed options are:
```
AVA+BTB -
opt: freq
val: 256 - 1024 (chip frequency)
BTB -
opt: millivolts
val: 1000 - 1400 (core voltage)
MBA -
opt: reset
val: 0 - # of chips (reset a chip)
opt: freq
val: 0 - # of chips, 100 - 1400 (chip frequency)
opt: ledcount
val: 0 - 100 (chip count for LED)
opt: ledlimit
val: 0 - 200 (LED off below GH/s)
opt: spidelay
val: 0 - 9999 (SPI per I/O delay)
opt: spireset
val: i or s, 0 - 9999 (SPI regular reset)
opt: spisleep
val: 0 - 9999 (SPI reset sleep in ms)
BMA -
opt: volt
val: 0 - 9
opt: clock
val: 0 - 15
```
Parameters:
n: The ASC to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
Returns:
Confirmation of setting option opt to value val.
</details>
"""
if val:
return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
else:
return await self.send_command("ascset", parameters=f"{n},{opt}")
async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner.
<details>
<summary>Expand</summary>
Returns:
An all-in-one status summary of the miner.
</details>
"""
return await self.send_command("lcd")
async def lockstats(self) -> dict:
"""Write lockstats to STDERR.
<details>
<summary>Expand</summary>
Returns:
The result of writing the lock stats to STDERR.
</details>
"""
return await self.send_command("lockstats")

View File

@@ -14,11 +14,6 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic import settings
from pyasic.API.bmminer import BMMinerAPI
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.API.btminer import BTMinerAPI
from pyasic.API.cgminer import CGMinerAPI
from pyasic.API.unknown import UnknownAPI
from pyasic.config import MinerConfig
from pyasic.data import (
BraiinsOSError,
@@ -33,13 +28,18 @@ from pyasic.miners.base import AnyMiner, DataOptions
from pyasic.miners.miner_factory import MinerFactory, miner_factory
from pyasic.miners.miner_listener import MinerListener
from pyasic.network import MinerNetwork
from pyasic.rpc.bmminer import BMMinerRPCAPI
from pyasic.rpc.bosminer import BOSMinerRPCAPI
from pyasic.rpc.btminer import BTMinerRPCAPI
from pyasic.rpc.cgminer import CGMinerRPCAPI
from pyasic.rpc.unknown import UnknownRPCAPI
__all__ = [
"BMMinerAPI",
"BOSMinerAPI",
"BTMinerAPI",
"CGMinerAPI",
"UnknownAPI",
"BMMinerRPCAPI",
"BOSMinerRPCAPI",
"BTMinerRPCAPI",
"CGMinerRPCAPI",
"UnknownRPCAPI",
"MinerConfig",
"MinerData",
"BraiinsOSError",

View File

@@ -19,7 +19,7 @@ from dataclasses import asdict, dataclass, field
from pyasic.config.fans import FanModeConfig
from pyasic.config.mining import MiningModeConfig
from pyasic.config.pools import PoolConfig
from pyasic.config.power_scaling import PowerScalingConfig, PowerScalingShutdown
from pyasic.config.power_scaling import PowerScalingConfig
from pyasic.config.temperature import TemperatureConfig
@@ -99,13 +99,13 @@ class MinerConfig:
**self.power_scaling.as_bosminer(),
}
def as_bos_grpc(self, user_suffix: str = None) -> dict:
def as_boser(self, user_suffix: str = None) -> dict:
return {
**self.fan_mode.as_bos_grpc(),
**self.temperature.as_bos_grpc(),
**self.mining_mode.as_bos_grpc(),
**self.pools.as_bos_grpc(user_suffix=user_suffix),
**self.power_scaling.as_bos_grpc(),
**self.fan_mode.as_boser(),
**self.temperature.as_boser(),
**self.mining_mode.as_boser(),
**self.pools.as_boser(user_suffix=user_suffix),
**self.power_scaling.as_boser(),
}
def as_epic(self, user_suffix: str = None) -> dict:
@@ -161,6 +161,16 @@ class MinerConfig:
power_scaling=PowerScalingConfig.from_bosminer(toml_conf),
)
@classmethod
def from_boser(cls, grpc_miner_conf: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_boser(grpc_miner_conf),
mining_mode=MiningModeConfig.from_boser(grpc_miner_conf),
fan_mode=FanModeConfig.from_boser(grpc_miner_conf),
temperature=TemperatureConfig.from_boser(grpc_miner_conf),
power_scaling=PowerScalingConfig.from_boser(grpc_miner_conf),
)
@classmethod
def from_epic(cls, web_conf: dict) -> "MinerConfig":
return cls(
@@ -170,6 +180,15 @@ class MinerConfig:
mining_mode=MiningModeConfig.from_epic(web_conf),
)
@classmethod
def from_vnish(cls, web_settings: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_vnish(web_settings),
fan_mode=FanModeConfig.from_vnish(web_settings),
temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(web_settings),
)
def merge(a: dict, b: dict) -> dict:
result = deepcopy(a)

View File

@@ -44,12 +44,15 @@ class MinerConfigOption(Enum):
def as_bosminer(self) -> dict:
return self.value.as_bosminer()
def as_bos_grpc(self) -> dict:
return self.value.as_bos_grpc()
def as_boser(self) -> dict:
return self.value.as_boser()
def as_epic(self) -> dict:
return self.value.as_epic()
def as_vnish(self) -> dict:
return self.value.as_vnish()
def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs)
@@ -88,8 +91,11 @@ class MinerConfigValue:
def as_bosminer(self) -> dict:
return {}
def as_bos_grpc(self) -> dict:
def as_boser(self) -> dict:
return {}
def as_epic(self) -> dict:
return {}
def as_vnish(self) -> dict:
return {}

View File

@@ -22,10 +22,26 @@ from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass
class FanModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal")
minimum_fans: int = 1
minimum_speed: int = 0
@classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
return cls()
cls_conf = {}
if dict_conf.get("minimum_fans") is not None:
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
if dict_conf.get("minimum_speed") is not None:
cls_conf["minimum_speed"] = dict_conf["minimum_speed"]
return cls(**cls_conf)
@classmethod
def from_vnish(cls, web_cooling_settings: dict):
cls_conf = {}
if web_cooling_settings.get("fan_min_count") is not None:
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
if web_cooling_settings.get("fan_min_duty") is not None:
cls_conf["minimum_speed"] = web_cooling_settings["fan_min_duty"]
return cls(**cls_conf)
def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
@@ -58,6 +74,15 @@ class FanModeManual(MinerConfigValue):
cls_conf["speed"] = toml_fan_conf["speed"]
return cls(**cls_conf)
@classmethod
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeManual":
cls_conf = {}
if web_cooling_settings.get("fan_min_count") is not None:
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
if web_cooling_settings["mode"].get("param") is not None:
cls_conf["speed"] = web_cooling_settings["mode"]["param"]
return cls(**cls_conf)
def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)}
@@ -143,3 +168,37 @@ class FanModeConfig(MinerConfigOption):
return cls.manual()
elif mode == "disabled":
return cls.immersion()
@classmethod
def from_vnish(cls, web_settings: dict):
try:
mode = web_settings["miner"]["cooling"]["mode"]["name"]
except LookupError:
return cls.default()
if mode == "auto":
return cls.normal().from_vnish(web_settings["miner"]["cooling"])
elif mode == "manual":
return cls.manual().from_vnish(web_settings["miner"]["cooling"])
elif mode == "immers":
return cls.immersion()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
temperature_conf = grpc_miner_conf["temperature"]
except LookupError:
return cls.default()
keys = temperature_conf.keys()
if "auto" in keys:
if "minimumRequiredFans" in keys:
return cls.normal(temperature_conf["minimumRequiredFans"])
return cls.normal()
if "manual" in keys:
conf = {}
if "fanSpeedRatio" in temperature_conf["manual"].keys():
conf["speed"] = int(temperature_conf["manual"]["fanSpeedRatio"])
if "minimumRequiredFans" in keys:
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
return cls.manual(**conf)

View File

@@ -17,6 +17,16 @@ from dataclasses import dataclass, field
from typing import Dict, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
HashrateTargetMode,
PerformanceMode,
Power,
PowerTargetMode,
SaveAction,
SetPerformanceModeRequest,
TeraHashrate,
TunerPerformanceMode,
)
@dataclass
@@ -99,6 +109,20 @@ class MiningModePowerTune(MinerConfigValue):
def as_bosminer(self) -> dict:
return {"autotuning": {"enabled": True, "psu_power_limit": self.power}}
def as_boser(self) -> dict:
return {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
power_target=PowerTargetMode(
power_target=Power(watt=self.power)
)
)
),
),
}
@dataclass
class MiningModeHashrateTune(MinerConfigValue):
@@ -112,6 +136,22 @@ class MiningModeHashrateTune(MinerConfigValue):
def as_am_modern(self) -> dict:
return {"miner-mode": "0"}
def as_boser(self) -> dict:
return {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
hashrate_target=HashrateTargetMode(
hashrate_target=TeraHashrate(
terahash_per_second=self.hashrate
)
)
)
),
)
}
@dataclass
class ManualBoardSettings(MinerConfigValue):
@@ -145,6 +185,20 @@ class MiningModeManual(MinerConfigValue):
def as_am_modern(self) -> dict:
return {"miner-mode": "0"}
@classmethod
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
# will raise KeyError if it cant find the settings, values cannot be empty
voltage = web_overclock_settings["globals"]["volt"]
freq = web_overclock_settings["globals"]["freq"]
boards = {
idx: ManualBoardSettings(
freq=board["freq"],
volt=voltage if not board["freq"] == 0 else 0,
)
for idx, board in enumerate(web_overclock_settings["chains"])
}
return cls(global_freq=freq, global_volt=voltage, boards=boards)
class MiningModeConfig(MinerConfigOption):
normal = MiningModeNormal
@@ -234,3 +288,45 @@ class MiningModeConfig(MinerConfigOption):
if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning(autotuning_conf["hashrate_target"])
return cls.hashrate_tuning()
@classmethod
def from_vnish(cls, web_settings: dict):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
return cls.default()
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return cls.power_tuning(int(mode_settings["preset"]))
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
tuner_conf = grpc_miner_conf["tuner"]
if not tuner_conf.get("enabled", False):
return cls.default()
except LookupError:
return cls.default()
if tuner_conf.get("tunerMode") is not None:
if tuner_conf["tunerMode"] == 1:
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(tuner_conf["powerTarget"]["watt"])
return cls.power_tuning()
if tuner_conf["tunerMode"] == 2:
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
)
return cls.hashrate_tuning()
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(tuner_conf["powerTarget"]["watt"])
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
)

View File

@@ -36,7 +36,7 @@ class Pool(MinerConfigValue):
}
return {"url": self.url, "user": self.user, "pass": self.password}
def as_wm(self, idx: int, user_suffix: str = None):
def as_wm(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None:
return {
f"pool_{idx}": self.url,
@@ -49,7 +49,7 @@ class Pool(MinerConfigValue):
f"passwd_{idx}": self.password,
}
def as_am_old(self, idx: int, user_suffix: str = None):
def as_am_old(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None:
return {
f"_ant_pool{idx}url": self.url,
@@ -76,7 +76,7 @@ class Pool(MinerConfigValue):
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
return ",".join([self.url, self.user, self.password])
def as_inno(self, idx: int, user_suffix: str = None):
def as_inno(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None:
return {
f"Pool{idx}": self.url,
@@ -141,6 +141,22 @@ class Pool(MinerConfigValue):
password=toml_pool_conf["password"],
)
@classmethod
def from_vnish(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["url"],
user=web_pool["user"],
password=web_pool["pass"],
)
@classmethod
def from_boser(cls, grpc_pool: dict) -> "Pool":
return cls(
url=grpc_pool["url"],
user=grpc_pool["user"],
password=grpc_pool["password"],
)
@dataclass
class PoolGroup(MinerConfigValue):
@@ -275,6 +291,23 @@ class PoolGroup(MinerConfigValue):
)
return cls()
@classmethod
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
return cls([Pool.from_vnish(p) for p in web_settings_pools])
@classmethod
def from_boser(cls, grpc_pool_group: dict):
try:
return cls(
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
name=grpc_pool_group["name"],
quota=grpc_pool_group["quota"]["value"]
if grpc_pool_group.get("quota") is not None
else 1,
)
except LookupError:
return cls()
@dataclass
class PoolConfig(MinerConfigValue):
@@ -337,7 +370,7 @@ class PoolConfig(MinerConfigValue):
}
return {"group": [PoolGroup().as_bosminer()]}
def as_bos_grpc(self, user_suffix: str = None) -> dict:
def as_boser(self, user_suffix: str = None) -> dict:
return {}
@classmethod
@@ -375,3 +408,22 @@ class PoolConfig(MinerConfigValue):
return cls()
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
@classmethod
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
try:
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])])
except LookupError:
return cls()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
return cls(
groups=[
PoolGroup.from_boser(group)
for group in grpc_miner_conf["poolGroups"]
]
)
except LookupError:
return cls()

View File

@@ -17,7 +17,12 @@ from dataclasses import dataclass, field
from typing import Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.bosminer.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
DpsPowerTarget,
DpsTarget,
Power,
SetDpsRequest,
)
@dataclass
@@ -37,13 +42,8 @@ class PowerScalingShutdownEnabled(MinerConfigValue):
return cfg
def as_bos_grpc(self) -> dict:
cfg = {"enable_shutdown ": True}
if self.duration is not None:
cfg["shutdown_duration"] = Hours(self.duration)
return cfg
def as_boser(self) -> dict:
return {"enable_shutdown": True, "shutdown_duration": self.duration}
@dataclass
@@ -57,7 +57,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue):
def as_bosminer(self) -> dict:
return {"shutdown_enabled": False}
def as_bos_grpc(self) -> dict:
def as_boser(self) -> dict:
return {"enable_shutdown ": False}
@@ -88,6 +88,19 @@ class PowerScalingShutdown(MinerConfigOption):
return cls.disabled()
return None
@classmethod
def from_boser(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdownEnabled")
if sd_enabled is not None:
if sd_enabled:
try:
return cls.enabled(power_scaling_conf["shutdownDuration"]["hours"])
except KeyError:
return cls.enabled()
else:
return cls.disabled()
return None
@dataclass
class PowerScalingEnabled(MinerConfigValue):
@@ -133,20 +146,19 @@ class PowerScalingEnabled(MinerConfigValue):
return {"power_scaling": cfg}
def as_bos_grpc(self) -> dict:
cfg = {"enable": True}
target_conf = {}
if self.power_step is not None:
target_conf["power_step"] = self.power_step
if self.minimum_power is not None:
target_conf["min_power_target"] = self.minimum_power
cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf))
if self.shutdown_enabled is not None:
cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()}
return {"dps": cfg}
def as_boser(self) -> dict:
return {
"set_dps": SetDpsRequest(
enable=True,
**self.shutdown_enabled.as_boser(),
target=DpsTarget(
power_target=DpsPowerTarget(
power_step=Power(self.power_step),
min_power_target=Power(self.minimum_power),
)
),
),
}
@dataclass
@@ -187,3 +199,20 @@ class PowerScalingConfig(MinerConfigOption):
return cls.disabled()
return cls.default()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
dps_conf = grpc_miner_conf["dps"]
if not dps_conf.get("enabled", False):
return cls.disabled()
except LookupError:
return cls.default()
conf = {"shutdown_enabled": PowerScalingShutdown.from_boser(dps_conf)}
if dps_conf.get("minPowerTarget") is not None:
conf["minimum_power"] = dps_conf["minPowerTarget"]["watt"]
if dps_conf.get("powerStep") is not None:
conf["power_step"] = dps_conf["powerStep"]["watt"]
return cls.enabled(**conf)

View File

@@ -59,15 +59,54 @@ class TemperatureConfig(MinerConfigValue):
@classmethod
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
dangerous_temp = None
try:
hot_temp = web_conf["Misc"]["Shutdown Temp"]
dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError:
hot_temp = None
dangerous_temp = None
# Need to do this in two blocks to avoid KeyError if one is missing
try:
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
except KeyError:
target_temp = None
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
return cls(target=target_temp, danger=dangerous_temp)
@classmethod
def from_vnish(cls, web_settings: dict):
try:
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
except KeyError:
pass
return cls()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
temperature_conf = grpc_miner_conf["temperature"]
except KeyError:
return cls.default()
root_key = None
for key in ["auto", "manual", "disabled"]:
if key in temperature_conf.keys():
root_key = key
break
if root_key is None:
return cls.default()
conf = {}
keys = temperature_conf[root_key].keys()
if "targetTemperature" in keys:
conf["target"] = int(
temperature_conf[root_key]["targetTemperature"]["degreeC"]
)
if "hotTemperature" in keys:
conf["hot"] = int(temperature_conf[root_key]["hotTemperature"]["degreeC"])
if "dangerousTemperature" in keys:
conf["danger"] = int(
temperature_conf[root_key]["dangerousTemperature"]["degreeC"]
)
return cls(**conf)
return cls.default()

View File

@@ -16,7 +16,6 @@
import copy
import json
import logging
import time
from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone
@@ -39,6 +38,7 @@ class HashBoard:
chip_temp: The temperature of the chips as an int.
chips: The chip count of the board as an int.
expected_chips: The expected chip count of the board as an int.
serial_number: The serial number of the board.
missing: Whether the board is returned from the miners data as a bool.
"""
@@ -48,6 +48,7 @@ class HashBoard:
chip_temp: int = None
chips: int = None
expected_chips: int = None
serial_number: str = None
missing: bool = True
def get(self, __key: str, default: Any = None):
@@ -109,7 +110,7 @@ class MinerData:
hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically.
_hashrate: Backup for hashrate found via API instead of hashboards.
expected_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
hashboards: A list of hashboards on the miner with their statistics.
hashboards: A list of [`HashBoard`][pyasic.data.HashBoard]s on the miner with their statistics.
temperature_avg: The average temperature across the boards. Calculated automatically.
env_temp: The environment temps as a float.
wattage: Current power draw of the miner as an int.
@@ -122,11 +123,7 @@ class MinerData:
percent_expected_hashrate: The percent of total hashrate out of the expected hashrate. Calculated automatically.
percent_expected_wattage: The percent of total wattage out of the expected wattage. Calculated automatically.
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
pool_split: The pool split as a str.
pool_1_url: The first pool url on the miner as a str.
pool_1_user: The first pool user on the miner as a str.
pool_2_url: The second pool url on the miner as a str.
pool_2_user: The second pool user on the miner as a str.
config: The parsed config of the miner, using [`MinerConfig`][pyasic.config.MinerConfig].
errors: A list of errors on the miner.
fault_light: Whether the fault light is on as a boolean.
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
@@ -161,9 +158,9 @@ class MinerData:
percent_expected_wattage: float = field(init=False)
nominal: bool = field(init=False)
config: MinerConfig = None
errors: List[Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]] = (
field(default_factory=list)
)
errors: List[
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
] = field(default_factory=list)
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
is_mining: bool = True
@@ -353,7 +350,6 @@ class MinerData:
pass
def asdict(self) -> dict:
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
return asdict(self, dict_factory=self.dict_factory)
def as_dict(self) -> dict:
@@ -370,7 +366,6 @@ 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)
@@ -381,7 +376,6 @@ 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 = []
@@ -400,7 +394,6 @@ 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

@@ -66,14 +66,14 @@ class _MinerPhaseBalancer:
str(miner.ip): {
"miner": miner,
"set": 0,
"min": miner.fan_count * FAN_USAGE,
"min": miner.expected_fans * FAN_USAGE,
}
for miner in miners
}
for miner in miners:
if (
isinstance(miner, BTMiner)
and not (miner.model.startswith("M2") if miner.model else True)
and not (miner.raw_model.startswith("M2") if miner.raw_model else True)
) or isinstance(miner, BOSMiner):
if isinstance(miner, S9):
self.miners[str(miner.ip)]["tune"] = True
@@ -98,8 +98,8 @@ class _MinerPhaseBalancer:
self.miners[str(miner.ip)]["tune"] = False
self.miners[str(miner.ip)]["shutdown"] = True
self.miners[str(miner.ip)]["max"] = 3600
if miner.model:
if miner.model.startswith("M2"):
if miner.raw_model:
if miner.raw_model.startswith("M2"):
self.miners[str(miner.ip)]["tune"] = False
self.miners[str(miner.ip)]["shutdown"] = True
self.miners[str(miner.ip)]["max"] = 2400
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
not self.miners[data_point.ip]["shutdown"]
):
# cant do anything with it so need to find a semi-accurate power limit
if not data_point.wattage_limit == None:
if data_point.wattage_limit is not None:
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
elif not data_point.wattage == None:
elif data_point.wattage is not None:
self.miners[data_point.ip]["max"] = int(data_point.wattage)
self.miners[data_point.ip]["min"] = int(data_point.wattage)
@@ -183,13 +183,19 @@ class _MinerPhaseBalancer:
if (not miner["tune"]) and (miner["shutdown"])
]
)
# min_other_wattage = sum([miner["min"] for miner in self.miners.values() if (not miner["tune"]) and (not miner["shutdown"])])
# min_other_wattage = sum(
# [
# miner["min"]
# for miner in self.miners.values()
# if (not miner["tune"]) and (not miner["shutdown"])
# ]
# )
# make sure wattage isnt set too high
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
raise APIError(
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W")
)
# should now know wattage limits and which can be tuned/shutdown
# check if 1/2 max of the miners which can be tuned is low enough

View File

@@ -17,8 +17,6 @@
from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import T19
# noqa - Ignore access to _module
class BMMinerT19(AntminerModern, T19):
pass

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import HS3
class BMMinerHS3(AntminerModern, HS3):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -18,6 +18,4 @@ from pyasic.miners.types import L7
class BMMinerL7(AntminerModern, L7):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import E9Pro
class BMMinerE9Pro(AntminerModern, E9Pro):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -14,21 +14,21 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSMiner
from pyasic.miners.backends import BOSer
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro
class BOSMinerS17(BOSMiner, S17):
class BOSMinerS17(BOSer, S17):
pass
class BOSMinerS17Plus(BOSMiner, S17Plus):
class BOSMinerS17Plus(BOSer, S17Plus):
pass
class BOSMinerS17Pro(BOSMiner, S17Pro):
class BOSMinerS17Pro(BOSer, S17Pro):
pass
class BOSMinerS17e(BOSMiner, S17e):
class BOSMinerS17e(BOSer, S17e):
pass

View File

@@ -14,17 +14,17 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSMiner
from pyasic.miners.backends import BOSer
from pyasic.miners.types import T17, T17e, T17Plus
class BOSMinerT17(BOSMiner, T17):
class BOSMinerT17(BOSer, T17):
pass
class BOSMinerT17Plus(BOSMiner, T17Plus):
class BOSMinerT17Plus(BOSer, T17Plus):
pass
class BOSMinerT17e(BOSMiner, T17e):
class BOSMinerT17e(BOSer, T17e):
pass

View File

@@ -14,7 +14,7 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSMiner
from pyasic.miners.backends import BOSer
from pyasic.miners.types import (
S19,
S19XP,
@@ -30,45 +30,45 @@ from pyasic.miners.types import (
)
class BOSMinerS19(BOSMiner, S19):
class BOSMinerS19(BOSer, S19):
pass
class BOSMinerS19Plus(BOSMiner, S19Plus):
class BOSMinerS19Plus(BOSer, S19Plus):
pass
class BOSMinerS19Pro(BOSMiner, S19Pro):
class BOSMinerS19Pro(BOSer, S19Pro):
pass
class BOSMinerS19a(BOSMiner, S19a):
class BOSMinerS19a(BOSer, S19a):
pass
class BOSMinerS19j(BOSMiner, S19j):
class BOSMinerS19j(BOSer, S19j):
pass
class BOSMinerS19jNoPIC(BOSMiner, S19jNoPIC):
class BOSMinerS19jNoPIC(BOSer, S19jNoPIC):
pass
class BOSMinerS19jPro(BOSMiner, S19jPro):
class BOSMinerS19jPro(BOSer, S19jPro):
pass
class BOSMinerS19kProNoPIC(BOSMiner, S19kProNoPIC):
class BOSMinerS19kProNoPIC(BOSer, S19kProNoPIC):
pass
class BOSMinerS19aPro(BOSMiner, S19aPro):
class BOSMinerS19aPro(BOSer, S19aPro):
pass
class BOSMinerS19jProPlus(BOSMiner, S19jProPlus):
class BOSMinerS19jProPlus(BOSer, S19jProPlus):
pass
class BOSMinerS19XP(BOSMiner, S19XP):
class BOSMinerS19XP(BOSer, S19XP):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSMiner
from pyasic.miners.backends import BOSer
from pyasic.miners.types import T19
class BOSMinerT19(BOSMiner, T19):
class BOSMinerT19(BOSer, T19):
pass

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import Z15
class CGMinerZ15(AntminerOld, Z15):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -18,6 +18,4 @@ from pyasic.miners.types import D3
class CGMinerD3(AntminerOld, D3):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import DR5
class CGMinerDR5(AntminerOld, DR5):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_shutdown = False
supports_shutdown = False

View File

@@ -21,14 +21,53 @@ import asyncssh
from pyasic.data import HashBoard
from pyasic.errors import APIError
from pyasic.miners.backends import Hiveon
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.types import T9
HIVEON_T9_DATA_LOC = DataLocations(
**{
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
}
)
class HiveonT9(Hiveon, T9):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.pwd = "admin"
data_locations = HIVEON_T9_DATA_LOC
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
@@ -45,39 +84,49 @@ class HiveonT9(Hiveon, T9):
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [
HashBoard(slot=board, expected_chips=self.expected_chips)
for board in range(self.expected_hashboards)
]
if api_stats is None:
try:
api_stats = self.api.stats()
except APIError:
return []
board_map = {
0: [2, 9, 10],
1: [3, 11, 12],
2: [4, 13, 14],
}
hashboards = []
for board in board_map:
hashboard = HashBoard(slot=board, expected_chips=self.expected_chips)
hashrate = 0
chips = 0
for chipset in board_map[board]:
if hashboard.chip_temp == None:
if hashboards[board].chip_temp is None:
try:
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
hashboards[board].temp = api_stats["STATS"][1][f"temp{chipset}"]
hashboards[board].chip_temp = api_stats["STATS"][1][
f"temp2_{chipset}"
]
except (KeyError, IndexError):
pass
else:
hashboard.missing = False
hashboards[board].missing = False
try:
hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"]
chips += api_stats["STATS"][1][f"chain_acn{chipset}"]
except (KeyError, IndexError):
pass
hashboard.hashrate = round(hashrate / 1000, 2)
hashboard.chips = chips
hashboards.append(hashboard)
hashboards[board].hashrate = round(hashrate / 1000, 2)
hashboards[board].chips = chips
return hashboards
async def get_wattage(self, api_stats: dict = None) -> Optional[int]:
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
try:
api_stats = await self.api.stats()
@@ -94,7 +143,7 @@ class HiveonT9(Hiveon, T9):
# parse wattage position out of raw data
return round(float(wattage_raw.split(" ")[0]))
async def get_env_temp(self, api_stats: dict = None) -> Optional[float]:
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
env_temp_list = []
board_map = {
0: [2, 9, 10],

View File

@@ -19,5 +19,4 @@ from pyasic.miners.types import L3Plus
class VnishL3Plus(VNish, L3Plus):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1026
class CGMinerAvalon1026(CGMinerAvalon, Avalon1026):
class CGMinerAvalon1026(AvalonMiner, Avalon1026):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1047
class CGMinerAvalon1047(CGMinerAvalon, Avalon1047):
class CGMinerAvalon1047(AvalonMiner, Avalon1047):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1066
class CGMinerAvalon1066(CGMinerAvalon, Avalon1066):
class CGMinerAvalon1066(AvalonMiner, Avalon1066):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1166Pro
class CGMinerAvalon1166Pro(CGMinerAvalon, Avalon1166Pro):
class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1246
class CGMinerAvalon1246(CGMinerAvalon, Avalon1246):
class CGMinerAvalon1246(AvalonMiner, Avalon1246):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon721
class CGMinerAvalon721(CGMinerAvalon, Avalon721):
class CGMinerAvalon721(AvalonMiner, Avalon721):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon741
class CGMinerAvalon741(CGMinerAvalon, Avalon741):
class CGMinerAvalon741(AvalonMiner, Avalon741):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon761
class CGMinerAvalon761(CGMinerAvalon, Avalon761):
class CGMinerAvalon761(AvalonMiner, Avalon761):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon821
class CGMinerAvalon821(CGMinerAvalon, Avalon821):
class CGMinerAvalon821(AvalonMiner, Avalon821):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon841
class CGMinerAvalon841(CGMinerAvalon, Avalon841):
class CGMinerAvalon841(AvalonMiner, Avalon841):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon851
class CGMinerAvalon851(CGMinerAvalon, Avalon851):
class CGMinerAvalon851(AvalonMiner, Avalon851):
pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon
from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon921
class CGMinerAvalon921(CGMinerAvalon, Avalon921):
class CGMinerAvalon921(AvalonMiner, Avalon921):
pass

View File

@@ -14,14 +14,14 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .antminer import AntminerModern, AntminerOld
from .avalonminer import AvalonMiner
from .bfgminer import BFGMiner
from .bfgminer_goldshell import BFGMinerGoldshell
from .bmminer import BMMiner
from .bosminer import BOSMiner
from .braiins_os import BOSer, BOSMiner
from .btminer import BTMiner
from .cgminer import CGMiner
from .cgminer_avalon import CGMinerAvalon
from .epic import ePIC
from .goldshell import GoldshellMiner
from .hiveon import Hiveon
from .luxminer import LUXMiner
from .vnish import VNish

View File

@@ -16,7 +16,6 @@
from typing import List, Optional, Union
from pyasic.API import APIError
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error
@@ -29,68 +28,72 @@ from pyasic.miners.base import (
RPCAPICommand,
WebAPICommand,
)
from pyasic.rpc import APIError
from pyasic.ssh.antminer import AntminerModernSSH
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
ANTMINER_MODERN_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [WebAPICommand("web_get_system_info", "get_system_info")]
"_get_mac",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction(
"get_errors", [WebAPICommand("web_summary", "summary")]
"_get_errors",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light",
"_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
"_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class AntminerModern(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# interfaces
self.web = AntminerModernWebAPI(ip)
"""Handler for AntMiners with the modern web interface, such as S19"""
# static data
# data gathering locations
self.data_locations = ANTMINER_MODERN_DATA_LOC
# autotuning/shutdown support
self.supports_shutdown = True
_web_cls = AntminerModernWebAPI
web: AntminerModernWebAPI
_ssh_cls = AntminerModernSSH
ssh: AntminerModernSSH
data_locations = ANTMINER_MODERN_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf()
@@ -143,27 +146,27 @@ class AntminerModern(BMMiner):
await self.send_config(cfg)
return True
async def get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
if not web_get_system_info:
async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
except APIError:
pass
if web_get_system_info:
if web_get_system_info is not None:
try:
return web_get_system_info["hostname"]
except KeyError:
pass
async def get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
if not web_get_system_info:
async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
except APIError:
pass
if web_get_system_info:
if web_get_system_info is not None:
try:
return web_get_system_info["macaddr"]
except KeyError:
@@ -176,15 +179,15 @@ class AntminerModern(BMMiner):
except KeyError:
pass
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if not web_summary:
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
errors = []
if web_summary:
if web_summary is not None:
try:
for item in web_summary["SUMMARY"][0]["status"]:
try:
@@ -192,35 +195,73 @@ class AntminerModern(BMMiner):
errors.append(X19Error(item["msg"]))
except KeyError:
continue
except (KeyError, IndexError):
except LookupError:
pass
return errors
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
async def _get_hashboards(self) -> List[HashBoard]:
hashboards = [
HashBoard(idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards)
]
try:
api_stats = await self.api.send_command("stats", new_api=True)
except APIError:
return hashboards
if api_stats is not None:
try:
for board in api_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = round(
board["rate_real"] / 1000, 2
)
hashboards[board["index"]].chips = board["asic_num"]
board_temp_data = list(
filter(lambda x: not x == 0, board["temp_pcb"])
)
hashboards[board["index"]].temp = sum(board_temp_data) / len(
board_temp_data
)
chip_temp_data = list(
filter(lambda x: not x == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = sum(chip_temp_data) / len(
chip_temp_data
)
hashboards[board["index"]].serial_number = board["sn"]
hashboards[board["index"]].missing = False
except LookupError:
pass
return hashboards
async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light:
return self.light
if not web_get_blink_status:
if web_get_blink_status is None:
try:
web_get_blink_status = await self.web.get_blink_status()
except APIError:
pass
if web_get_blink_status:
if web_get_blink_status is not None:
try:
self.light = web_get_blink_status["blink"]
except KeyError:
pass
return self.light
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats:
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
expected_rate = api_stats["STATS"][1]["total_rateideal"]
try:
@@ -233,7 +274,7 @@ class AntminerModern(BMMiner):
return round(expected_rate / 1000000, 2)
else:
return round(expected_rate, 2)
except (KeyError, IndexError):
except LookupError:
pass
async def set_static_ip(
@@ -278,14 +319,14 @@ class AntminerModern(BMMiner):
protocol=protocol,
)
async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if not web_get_conf:
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if web_get_conf is None:
try:
web_get_conf = await self.web.get_miner_conf()
except APIError:
pass
if web_get_conf:
if web_get_conf is not None:
try:
if web_get_conf["bitmain-work-mode"].isdigit():
return (
@@ -295,14 +336,14 @@ class AntminerModern(BMMiner):
except LookupError:
pass
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
return int(api_stats["STATS"][1]["Elapsed"])
except LookupError:
@@ -311,58 +352,53 @@ class AntminerModern(BMMiner):
ANTMINER_OLD_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light",
"_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
"_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class AntminerOld(CGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# interfaces
self.web = AntminerOldWebAPI(ip)
"""Handler for AntMiners with the old web interface, such as S17"""
# static data
# data gathering locations
self.data_locations = ANTMINER_OLD_DATA_LOC
_web_cls = AntminerOldWebAPI
web: AntminerOldWebAPI
data_locations = ANTMINER_OLD_DATA_LOC
async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf()
@@ -374,7 +410,7 @@ class AntminerOld(CGMiner):
self.config = config
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
async def get_mac(self) -> Union[str, None]:
async def _get_mac(self) -> Union[str, None]:
try:
data = await self.web.get_system_info()
if data:
@@ -411,45 +447,47 @@ class AntminerOld(CGMiner):
return True
return False
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light:
return self.light
if not web_get_blink_status:
if web_get_blink_status is None:
try:
web_get_blink_status = await self.web.get_blink_status()
except APIError:
pass
if web_get_blink_status:
if web_get_blink_status is not None:
try:
self.light = web_get_blink_status["isBlinking"]
except KeyError:
pass
return self.light
async def get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if not web_get_system_info:
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
except APIError:
pass
if web_get_system_info:
if web_get_system_info is not None:
try:
return web_get_system_info["hostname"]
except KeyError:
pass
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans_data = [Fan() for _ in range(self.fan_count)]
if api_stats:
fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None:
try:
fan_offset = -1
@@ -461,24 +499,24 @@ class AntminerOld(CGMiner):
if fan_offset == -1:
fan_offset = 3
for fan in range(self.fan_count):
for fan in range(self.expected_fans):
fans_data[fan].speed = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError):
except LookupError:
pass
return fans_data
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = []
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
board_offset = -1
boards = api_stats["STATS"]
@@ -518,19 +556,19 @@ class AntminerOld(CGMiner):
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
return hashboards
async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if not web_get_conf:
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if web_get_conf is None:
try:
web_get_conf = await self.web.get_miner_conf()
except APIError:
pass
if web_get_conf:
if web_get_conf is not None:
try:
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
except LookupError:
@@ -548,14 +586,14 @@ class AntminerOld(CGMiner):
else:
return False
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
return int(api_stats["STATS"][1]["Elapsed"])
except LookupError:

View File

@@ -14,69 +14,68 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
import logging
import re
from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner
from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
AVALON_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [RPCAPICommand("api_version", "version")]
"_get_mac",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_devs", "devs")]
"_get_hashrate",
[RPCAPICommand("api_devs", "devs")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"get_env_temp", [RPCAPICommand("api_stats", "stats")]
"_get_env_temp",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", [RPCAPICommand("api_stats", "stats")]
"_get_wattage_limit",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", [RPCAPICommand("api_stats", "stats")]
"_get_fault_light",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class CGMinerAvalon(CGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
class AvalonMiner(CGMiner):
"""Handler for Avalon Miners"""
# data gathering locations
self.data_locations = AVALON_DATA_LOC
data_locations = AVALON_DATA_LOC
async def fault_light_on(self) -> bool:
try:
@@ -109,26 +108,6 @@ class CGMinerAvalon(CGMiner):
return False
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:
pass
# self.config = config
# return None
# logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
# conf = config.as_avalon(user_suffix=user_suffix)
# try:
# data = await self.api.ascset( # noqa
# 0, "setpool", f"root,root,{conf}"
# ) # this should work but doesn't
# except APIError:
# pass
# return data
@staticmethod
def parse_stats(stats):
_stats_items = re.findall(".+?\\[*?]", stats)
@@ -146,9 +125,9 @@ class CGMinerAvalon(CGMiner):
# --avalon args
for arg_item in data_list:
item_data = arg_item[0].split(" ")
for idx in range(len(item_data)):
for idx, val in enumerate(item_data):
if idx % 2 == 0 or idx == 0:
data_dict[item_data[idx]] = item_data[idx + 1]
data_dict[val] = item_data[idx + 1]
raw_data = [data[0].strip(), data_dict]
else:
@@ -176,14 +155,14 @@ class CGMinerAvalon(CGMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self, api_version: dict = None) -> Optional[str]:
if not api_version:
async def _get_mac(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
base_mac = api_version["VERSION"][0]["MAC"]
base_mac = base_mac.upper()
@@ -194,40 +173,32 @@ class CGMinerAvalon(CGMiner):
except (KeyError, ValueError):
pass
async def get_hostname(self, mac: str = None) -> Optional[str]:
return None
# if not mac:
# mac = await self.get_mac()
#
# if mac:
# return f"Avalon{mac.replace(':', '')[-6:]}"
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs:
async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]:
if api_devs is None:
try:
api_devs = await self.api.devs()
except APIError:
pass
if api_devs:
if api_devs is not None:
try:
return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
@@ -263,14 +234,14 @@ class CGMinerAvalon(CGMiner):
return hashboards
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats:
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
@@ -278,14 +249,14 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_env_temp(self, api_stats: dict = None) -> Optional[float]:
if not api_stats:
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
@@ -293,17 +264,14 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_wattage(self) -> Optional[int]:
return None
async def get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
@@ -311,41 +279,38 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans_data = [Fan() for _ in range(self.fan_count)]
if api_stats:
fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
except LookupError:
return fans_data
for fan in range(self.fan_count):
for fan in range(self.expected_fans):
try:
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"])
except (IndexError, KeyError, ValueError, TypeError):
pass
return fans_data
async def get_errors(self) -> List[MinerErrorData]:
return []
async def get_fault_light(self, api_stats: dict = None) -> bool: # noqa
async def _get_fault_light(self, api_stats: dict = None) -> Optional[bool]:
if self.light:
return self.light
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
@@ -364,6 +329,3 @@ class CGMinerAvalon(CGMiner):
except LookupError:
pass
return False
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None

View File

@@ -14,13 +14,10 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from collections import namedtuple
from typing import List, Optional, Tuple
from typing import List, Optional
from pyasic.API.bfgminer import BFGMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import (
BaseMiner,
@@ -29,39 +26,34 @@ from pyasic.miners.base import (
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
BFGMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
@@ -69,18 +61,10 @@ BFGMINER_DATA_LOC = DataLocations(
class BFGMiner(BaseMiner):
"""Base handler for BFGMiner based miners."""
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip)
# interfaces
self.api = BFGMinerAPI(ip, api_ver)
_api_cls = BFGMinerRPCAPI
api: BFGMinerRPCAPI
# static data
self.api_type = "BFGMiner"
# data gathering locations
self.data_locations = BFGMINER_DATA_LOC
# data storage
self.api_ver = api_ver
data_locations = BFGMINER_DATA_LOC
async def get_config(self) -> MinerConfig:
# get pool data
@@ -92,115 +76,64 @@ class BFGMiner(BaseMiner):
self.config = MinerConfig.from_api(pools)
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def set_power_limit(self, wattage: int) -> bool:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.api_ver:
return self.api_ver
if not api_version:
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
except LookupError:
pass
return self.api_ver
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.fw_ver:
return self.fw_ver
if not api_version:
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except (KeyError, IndexError):
except LookupError:
pass
return self.fw_ver
async def get_version(
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
return miner_version(
api_ver=await self.get_api_ver(api_version),
fw_ver=await self.get_fw_ver(api_version=api_version),
)
async def reboot(self) -> bool:
return False
async def get_fan_psu(self):
return None
async def get_hostname(self) -> Optional[str]:
return None
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = []
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
board_offset = -1
boards = api_stats["STATS"]
@@ -240,29 +173,20 @@ class BFGMiner(BaseMiner):
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
return hashboards
async def get_env_temp(self) -> Optional[float]:
return None
async def get_wattage(self) -> Optional[int]:
return None
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans_data = [None, None, None, None]
if api_stats:
if api_stats is not None:
try:
fan_offset = -1
@@ -274,31 +198,25 @@ class BFGMiner(BaseMiner):
if fan_offset == -1:
fan_offset = 1
for fan in range(self.fan_count):
for fan in range(self.expected_fans):
fans_data[fan] = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError):
except LookupError:
pass
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
return fans
async def get_errors(self) -> List[MinerErrorData]:
return []
async def get_fault_light(self) -> bool:
return False
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
# X19 method, not sure compatibility
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
expected_rate = api_stats["STATS"][1]["total_rateideal"]
try:
@@ -311,11 +229,5 @@ class BFGMiner(BaseMiner):
return round(expected_rate / 1000000, 2)
else:
return round(expected_rate, 2)
except (KeyError, IndexError):
except LookupError:
pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -14,14 +14,10 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
import logging
from collections import namedtuple
from typing import List, Optional, Tuple
from typing import List, Optional
from pyasic.API.bmminer import BMMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import (
BaseMiner,
@@ -30,41 +26,38 @@ from pyasic.miners.base import (
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.bmminer import BMMinerRPCAPI
BMMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
@@ -72,46 +65,10 @@ BMMINER_DATA_LOC = DataLocations(
class BMMiner(BaseMiner):
"""Base handler for BMMiner based miners."""
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip)
# interfaces
self.api = BMMinerAPI(ip, api_ver)
_api_cls = BMMinerRPCAPI
api: BMMinerRPCAPI
# static data
self.api_type = "BMMiner"
# data gathering locations
self.data_locations = BMMINER_DATA_LOC
# data storage
self.api_ver = api_ver
async def send_ssh_command(self, cmd: str) -> Optional[str]:
result = None
try:
conn = await self._get_ssh_connection()
except ConnectionError:
return None
# open an ssh connection
async with conn:
# 3 retries
for i in range(3):
try:
# run the command and get the result
result = await conn.run(cmd)
result = result.stdout
except Exception as e:
# if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}")
# on the 3rd retry, return None
if i == 3:
return
continue
# return the result, either command output or None
return result
data_locations = BMMINER_DATA_LOC
async def get_config(self) -> MinerConfig:
# get pool data
@@ -123,121 +80,64 @@ class BMMiner(BaseMiner):
self.config = MinerConfig.from_api(pools)
return self.config
async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.")
if ret is None:
return False
return True
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def set_power_limit(self, wattage: int) -> bool:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.api_ver:
return self.api_ver
if not api_version:
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
except LookupError:
pass
return self.api_ver
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.fw_ver:
return self.fw_ver
if not api_version:
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except (KeyError, IndexError):
except LookupError:
pass
return self.fw_ver
async def get_version(
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
return miner_version(
api_ver=await self.get_api_ver(api_version),
fw_ver=await self.get_fw_ver(api_version=api_version),
)
async def get_fan_psu(self):
return None
async def get_hostname(self) -> Optional[str]:
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
return hn
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = []
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
board_offset = -1
boards = api_stats["STATS"]
@@ -295,24 +195,15 @@ class BMMiner(BaseMiner):
return hashboards
async def get_env_temp(self) -> Optional[float]:
return None
async def get_wattage(self) -> Optional[int]:
return None
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans = [Fan() for _ in range(self.fan_count)]
if api_stats:
fans = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None:
try:
fan_offset = -1
@@ -324,30 +215,24 @@ class BMMiner(BaseMiner):
if fan_offset == -1:
fan_offset = 1
for fan in range(self.fan_count):
for fan in range(self.expected_fans):
fans[fan].speed = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError):
except LookupError:
pass
return fans
async def get_errors(self) -> List[MinerErrorData]:
return []
async def get_fault_light(self) -> bool:
return False
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
# X19 method, not sure compatibility
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
expected_rate = api_stats["STATS"][1]["total_rateideal"]
try:
@@ -360,20 +245,17 @@ class BMMiner(BaseMiner):
return round(expected_rate / 1000000, 2)
else:
return round(expected_rate, 2)
except (KeyError, IndexError):
except LookupError:
pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.web.get_miner_conf()
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
return int(api_stats["STATS"][1]["Elapsed"])
except LookupError:

File diff suppressed because it is too large Load Diff

View File

@@ -1,155 +0,0 @@
# ------------------------------------------------------------------------------
# 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 logging
from typing import List, Optional, Tuple
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.miners.backends import BOSMiner
class BOSMinerOld(BOSMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
async def send_ssh_command(self, cmd: str) -> Optional[str]:
result = None
try:
conn = await self._get_ssh_connection()
except ConnectionError:
return None
# open an ssh connection
async with conn:
# 3 retries
for i in range(3):
try:
# run the command and get the result
result = await conn.run(cmd)
result = result.stdout
except Exception as e:
# if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}")
# on the 3rd retry, return None
if i == 3:
return
continue
# return the result, either command output or None
return result
async def update_to_plus(self):
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
return result
async def check_light(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def fault_light_off(self) -> bool:
return False
async def get_config(self) -> None:
return None
async def reboot(self) -> bool:
return False
async def restart_backend(self) -> bool:
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:
return None
async def set_power_limit(self, wattage: int) -> bool:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self, *args, **kwargs) -> Optional[str]:
return None
async def get_model(self, *args, **kwargs) -> str:
return "S9"
async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
return None, None
async def get_hostname(self, *args, **kwargs) -> Optional[str]:
return None
async def get_hashrate(self, *args, **kwargs) -> Optional[float]:
return None
async def get_hashboards(self, *args, **kwargs) -> List[HashBoard]:
return []
async def get_env_temp(self, *args, **kwargs) -> Optional[float]:
return None
async def get_wattage(self, *args, **kwargs) -> Optional[int]:
return None
async def get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
return None
async def get_fans(
self,
*args,
**kwargs,
) -> List[Fan]:
return [Fan(), Fan(), Fan(), Fan()]
async def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
return None
async def get_api_ver(self, *args, **kwargs) -> Optional[str]:
return None
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
return None
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
return []
async def get_fault_light(self, *args, **kwargs) -> bool:
return False
async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
return None
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
return MinerData(ip=str(self.ip))
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -0,0 +1,963 @@
# ------------------------------------------------------------------------------
# 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 logging
import time
from typing import List, Optional, Union
import toml
from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
GRPCCommand,
RPCAPICommand,
WebAPICommand,
)
from pyasic.rpc.bosminer import BOSMinerRPCAPI
from pyasic.ssh.braiins_os import BOSMinerSSH
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
BOSMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_net_conf", "admin/network/iface_status/lan")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[WebAPICommand("web_bos_info", "bos/info")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("api_devs", "devs")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[
RPCAPICommand("api_temps", "temps"),
RPCAPICommand("api_devdetails", "devdetails"),
RPCAPICommand("api_devs", "devs"),
],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("api_fans", "fans")],
),
str(DataOptions.ERRORS): DataFunction(
"_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_summary", "summary")],
),
}
)
class BOSMiner(BaseMiner):
"""Handler for old versions of BraiinsOS+ (pre-gRPC)"""
_api_cls = BOSMinerRPCAPI
api: BOSMinerRPCAPI
_web_cls = BOSMinerWebAPI
web: BOSMinerWebAPI
_ssh_cls = BOSMinerSSH
ssh: BOSMinerSSH
firmware = "BOS+"
data_locations = BOSMINER_DATA_LOC
supports_shutdown = True
supports_autotuning = True
async def fault_light_on(self) -> bool:
ret = await self.ssh.fault_light_on()
if isinstance(ret, str):
self.light = True
return self.light
return False
async def fault_light_off(self) -> bool:
ret = await self.ssh.fault_light_off()
if isinstance(ret, str):
self.light = False
return True
return False
async def restart_backend(self) -> bool:
return await self.restart_bosminer()
async def restart_bosminer(self) -> bool:
ret = await self.ssh.restart_bosminer()
if isinstance(ret, str):
return True
return False
async def stop_mining(self) -> bool:
try:
data = await self.api.pause()
except APIError:
return False
if data.get("PAUSE"):
if data["PAUSE"][0]:
return True
return False
async def resume_mining(self) -> bool:
try:
data = await self.api.resume()
except APIError:
return False
if data.get("RESUME"):
if data["RESUME"][0]:
return True
return False
async def reboot(self) -> bool:
ret = await self.ssh.reboot()
if isinstance(ret, str):
return True
return False
async def get_config(self) -> MinerConfig:
raw_data = await self.ssh.get_config_file()
try:
toml_data = toml.loads(raw_data)
cfg = MinerConfig.from_bosminer(toml_data)
self.config = cfg
except toml.TomlDecodeError as e:
raise APIError("Failed to decode toml when getting config.") from e
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
toml_conf = toml.dumps(
{
"format": {
"version": "1.2+",
"generator": "pyasic",
"model": f"{self.make.replace('Miner', 'miner')} {self.raw_model.replace('j', 'J')}",
"timestamp": int(time.time()),
},
**config.as_bosminer(user_suffix=user_suffix),
}
)
try:
conn = await self.ssh._get_connection()
except ConnectionError as e:
raise APIError("SSH connection failed when sending config.") from e
async with conn:
await conn.run("/etc/init.d/bosminer stop")
async with conn.start_sftp_client() as sftp:
async with sftp.open("/etc/bosminer.toml", "w+") as file:
await file.write(toml_conf)
await conn.run("/etc/init.d/bosminer start")
async def set_power_limit(self, wattage: int) -> bool:
try:
cfg = await self.get_config()
if cfg is None:
return False
cfg.mining_mode = MiningModePowerTune(wattage)
await self.send_config(cfg)
except APIError:
raise
except Exception as e:
logging.warning(f"{self} - Failed to set power limit: {e}")
return False
else:
return True
async def set_static_ip(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str = "255.255.255.0",
):
cfg_data_lan = "\n\t".join(
[
"config interface 'lan'",
"option type 'bridge'",
"option ifname 'eth0'",
"option proto 'static'",
f"option ipaddr '{ip}'",
f"option netmask '{subnet_mask}'",
f"option gateway '{gateway}'",
f"option dns '{dns}'",
]
)
data = await self.ssh.get_network_config()
split_data = data.split("\n\n")
for idx, val in enumerate(split_data):
if "config interface 'lan'" in val:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
async def set_dhcp(self):
cfg_data_lan = "\n\t".join(
[
"config interface 'lan'",
"option type 'bridge'",
"option ifname 'eth0'",
"option proto 'dhcp'",
]
)
data = await self.ssh.get_network_config()
split_data = data.split("\n\n")
for idx, val in enumerate(split_data):
if "config interface 'lan'" in val:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
if web_net_conf is None:
try:
web_net_conf = await self.web.luci.get_net_conf()
except APIError:
pass
if isinstance(web_net_conf, dict):
if "admin/network/iface_status/lan" in web_net_conf.keys():
web_net_conf = web_net_conf["admin/network/iface_status/lan"]
if web_net_conf is not None:
try:
return web_net_conf[0]["macaddr"]
except LookupError:
pass
# could use ssh, but its slow and buggy
# result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
# if result:
# return result.upper().strip()
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
# Now get the API version
if api_version is not None:
try:
api_ver = api_version["VERSION"][0]["API"]
except LookupError:
api_ver = None
self.api_ver = api_ver
self.api.api_ver = self.api_ver
return self.api_ver
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
if web_bos_info is None:
try:
web_bos_info = await self.web.luci.get_bos_info()
except APIError:
return None
if isinstance(web_bos_info, dict):
if "bos/info" in web_bos_info.keys():
web_bos_info = web_bos_info["bos/info"]
try:
ver = web_bos_info["version"].split("-")[5]
if "." in ver:
self.fw_ver = ver
except (LookupError, AttributeError):
return None
return self.fw_ver
async def _get_hostname(self) -> Union[str, None]:
try:
hostname = (await self.ssh.get_hostname()).strip()
except Exception as e:
logging.error(f"{self} - Getting hostname failed: {e}")
return None
return hostname
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError):
pass
async def _get_hashboards(
self,
api_temps: dict = None,
api_devdetails: dict = None,
api_devs: dict = None,
) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
cmds = []
if api_temps is None:
cmds.append("temps")
if api_devdetails is None:
cmds.append("devdetails")
if api_devs is None:
cmds.append("devs")
if len(cmds) > 0:
try:
d = await self.api.multicommand(*cmds)
except APIError:
d = {}
try:
api_temps = d["temps"][0]
except LookupError:
api_temps = None
try:
api_devdetails = d["devdetails"][0]
except (KeyError, IndexError):
api_devdetails = None
try:
api_devs = d["devs"][0]
except LookupError:
api_devs = None
if api_temps is not None:
try:
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
for board in api_temps["TEMPS"]:
_id = board["ID"] - offset
chip_temp = round(board["Chip"])
board_temp = round(board["Board"])
hashboards[_id].chip_temp = chip_temp
hashboards[_id].temp = board_temp
except (IndexError, KeyError, ValueError, TypeError):
pass
if api_devdetails is not None:
try:
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devdetails["DEVDETAILS"]:
_id = board["ID"] - offset
chips = board["Chips"]
hashboards[_id].chips = chips
hashboards[_id].missing = False
except (IndexError, KeyError):
pass
if api_devs is not None:
try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devs["DEVS"]:
_id = board["ID"] - offset
hashrate = round(float(board["MHS 1m"] / 1000000), 2)
hashboards[_id].hashrate = hashrate
except (IndexError, KeyError):
pass
return hashboards
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
if api_tunerstatus is None:
try:
api_tunerstatus = await self.api.tunerstatus()
except APIError:
pass
if api_tunerstatus is not None:
try:
return api_tunerstatus["TUNERSTATUS"][0][
"ApproximateMinerPowerConsumption"
]
except LookupError:
pass
async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]:
if api_tunerstatus is None:
try:
api_tunerstatus = await self.api.tunerstatus()
except APIError:
pass
if api_tunerstatus is not None:
try:
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
except LookupError:
pass
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
if api_fans is None:
try:
api_fans = await self.api.fans()
except APIError:
pass
if api_fans is not None:
fans = []
for n in range(self.expected_fans):
try:
fans.append(Fan(api_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError):
pass
return fans
return [Fan() for _ in range(self.expected_fans)]
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
if api_tunerstatus is None:
try:
api_tunerstatus = await self.api.tunerstatus()
except APIError:
pass
if api_tunerstatus is not None:
errors = []
try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
if chain_status and len(chain_status) > 0:
offset = (
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
)
for board in chain_status:
_id = board["HashchainIndex"] - offset
if board["Status"] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips",
]:
_error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}"))
return errors
except (KeyError, IndexError):
pass
async def _get_fault_light(self) -> bool:
if self.light:
return self.light
try:
data = (await self.ssh.get_led_status()).strip()
self.light = False
if data == "50":
self.light = True
return self.light
except (TypeError, AttributeError):
return self.light
async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
if api_devs is None:
try:
api_devs = await self.api.devs()
except APIError:
pass
if api_devs is not None:
try:
hr_list = []
for board in api_devs["DEVS"]:
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
if expected_hashrate:
hr_list.append(expected_hashrate)
if len(hr_list) == 0:
return 0
else:
return round(
(sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2
)
except (IndexError, KeyError):
pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if api_devdetails is None:
try:
api_devdetails = await self.api.send_command(
"devdetails", ignore_errors=True, allow_warning=False
)
except APIError:
pass
if api_devdetails is not None:
try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError:
pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary is not None:
try:
return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError:
pass
BOSER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[GRPCCommand("grpc_miner_details", "get_miner_details")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[GRPCCommand("api_version", "get_api_version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[GRPCCommand("grpc_miner_details", "get_miner_details")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[GRPCCommand("grpc_miner_details", "get_miner_details")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[GRPCCommand("grpc_miner_details", "get_miner_details")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[GRPCCommand("grpc_hashboards", "get_hashboards")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[GRPCCommand("grpc_miner_stats", "get_miner_stats")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[
GRPCCommand(
"grpc_active_performance_mode", "get_active_performance_mode"
)
],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[GRPCCommand("grpc_cooling_state", "get_cooling_state")],
),
str(DataOptions.ERRORS): DataFunction(
"_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[GRPCCommand("grpc_locate_device_status", "get_locate_device_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_summary", "summary")],
),
}
)
class BOSer(BaseMiner):
"""Handler for new versions of BraiinsOS+ (post-gRPC)"""
_api_cls = BOSMinerRPCAPI
web: BOSMinerRPCAPI
_web_cls = BOSerWebAPI
web: BOSerWebAPI
data_locations = BOSER_DATA_LOC
supports_autotuning = True
supports_shutdown = True
async def fault_light_on(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(True)
if resp.get("enabled", False):
return True
return False
async def fault_light_off(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(False)
if resp == {}:
return True
return False
async def restart_backend(self) -> bool:
return await self.restart_boser()
async def restart_boser(self) -> bool:
await self.web.grpc.restart()
return True
async def stop_mining(self) -> bool:
try:
await self.web.grpc.pause_mining()
except APIError:
return False
return True
async def resume_mining(self) -> bool:
try:
await self.web.grpc.resume_mining()
except APIError:
return False
return True
async def reboot(self) -> bool:
ret = await self.web.grpc.reboot()
if ret == {}:
return True
return False
async def get_config(self) -> MinerConfig:
grpc_conf = await self.web.grpc.get_miner_configuration()
return MinerConfig.from_boser(grpc_conf)
async def set_power_limit(self, wattage: int) -> bool:
try:
result = await self.web.grpc.set_power_target(wattage)
except APIError:
return False
try:
if result["powerTarget"]["watt"] == wattage:
return True
except KeyError:
pass
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None:
try:
grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError:
pass
if grpc_miner_details is not None:
try:
return grpc_miner_details["macAddress"].upper()
except (LookupError, TypeError):
pass
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version is not None:
try:
api_ver = api_version["VERSION"][0]["API"]
except LookupError:
api_ver = None
self.api_ver = api_ver
self.api.api_ver = self.api_ver
return self.api_ver
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None:
try:
grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError:
pass
fw_ver = None
if grpc_miner_details is not None:
try:
fw_ver = grpc_miner_details["bosVersion"]["current"]
except (KeyError, TypeError):
pass
# if we get the version data, parse it
if fw_ver is not None:
ver = fw_ver.split("-")[5]
if "." in ver:
self.fw_ver = ver
return self.fw_ver
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None:
try:
grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError:
pass
if grpc_miner_details is not None:
try:
return grpc_miner_details["hostname"]
except LookupError:
pass
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError):
pass
async def _get_expected_hashrate(
self, grpc_miner_details: dict = None
) -> Optional[float]:
if grpc_miner_details is None:
try:
grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError:
pass
if grpc_miner_details is not None:
try:
return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000
except LookupError:
pass
async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if grpc_hashboards is None:
try:
grpc_hashboards = await self.web.grpc.get_hashboards()
except APIError:
pass
if grpc_hashboards is not None:
for board in grpc_hashboards["hashboards"]:
idx = int(board["id"]) - 1
if board.get("chipsCount") is not None:
hashboards[idx].chips = board["chipsCount"]
if board.get("boardTemp") is not None:
hashboards[idx].temp = board["boardTemp"]["degreeC"]
if board.get("highestChipTemp") is not None:
hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][
"degreeC"
]
if board.get("stats") is not None:
if not board["stats"]["realHashrate"]["last5S"] == {}:
hashboards[idx].hashrate = round(
board["stats"]["realHashrate"]["last5S"][
"gigahashPerSecond"
]
/ 1000,
2,
)
hashboards[idx].missing = False
return hashboards
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
if grpc_miner_stats is None:
try:
grpc_miner_stats = self.web.grpc.get_miner_stats()
except APIError:
pass
if grpc_miner_stats is not None:
try:
return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
except KeyError:
pass
async def _get_wattage_limit(
self, grpc_active_performance_mode: dict = None
) -> Optional[int]:
if grpc_active_performance_mode is None:
try:
grpc_active_performance_mode = (
self.web.grpc.get_active_performance_mode()
)
except APIError:
pass
if grpc_active_performance_mode is not None:
try:
return grpc_active_performance_mode["tunerMode"]["powerTarget"][
"powerTarget"
]["watt"]
except KeyError:
pass
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None:
try:
grpc_cooling_state = self.web.grpc.get_cooling_state()
except APIError:
pass
if grpc_cooling_state is not None:
fans = []
for n in range(self.expected_fans):
try:
fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"]))
except (IndexError, KeyError):
pass
return fans
return [Fan() for _ in range(self.expected_fans)]
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
if api_tunerstatus is None:
try:
api_tunerstatus = await self.api.tunerstatus()
except APIError:
pass
if api_tunerstatus is not None:
errors = []
try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
if chain_status and len(chain_status) > 0:
offset = (
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
)
for board in chain_status:
_id = board["HashchainIndex"] - offset
if board["Status"] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips",
]:
_error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:]
errors.append(BraiinsOSError(f"Slot {_id} {_error}"))
return errors
except LookupError:
pass
async def _get_fault_light(self, grpc_locate_device_status: dict = None) -> bool:
if self.light is not None:
return self.light
if grpc_locate_device_status is None:
try:
grpc_locate_device_status = (
await self.web.grpc.get_locate_device_status()
)
except APIError:
pass
if grpc_locate_device_status is not None:
if grpc_locate_device_status == {}:
return False
try:
return grpc_locate_device_status["enabled"]
except LookupError:
pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if api_devdetails is None:
try:
api_devdetails = await self.api.send_command(
"devdetails", ignore_errors=True, allow_warning=False
)
except APIError:
pass
if api_devdetails is not None:
try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError:
pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary is not None:
try:
return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError:
pass

View File

@@ -15,10 +15,8 @@
# ------------------------------------------------------------------------------
import logging
from collections import namedtuple
from typing import List, Optional, Tuple
from typing import List, Optional
from pyasic.API.btminer import BTMinerAPI
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
@@ -29,97 +27,103 @@ from pyasic.miners.base import (
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.rpc.btminer import BTMinerRPCAPI
BTMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac",
"_get_mac",
[
RPCAPICommand("api_summary", "summary"),
RPCAPICommand("api_get_miner_info", "get_miner_info"),
],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_get_version", "get_version")]
"_get_api_ver",
[RPCAPICommand("api_get_version", "get_version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver",
"_get_fw_ver",
[
RPCAPICommand("api_get_version", "get_version"),
RPCAPICommand("api_summary", "summary"),
],
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")]
"_get_hostname",
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_expected_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_devs", "devs")]
"_get_hashboards",
[RPCAPICommand("api_devs", "devs")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"get_env_temp", [RPCAPICommand("api_summary", "summary")]
"_get_env_temp",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.WATTAGE): DataFunction(
"get_wattage", [RPCAPICommand("api_summary", "summary")]
"_get_wattage",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", [RPCAPICommand("api_summary", "summary")]
"_get_wattage_limit",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.FANS): DataFunction(
"get_fans",
"_get_fans",
[
RPCAPICommand("api_summary", "summary"),
RPCAPICommand("api_get_psu", "get_psu"),
],
),
str(DataOptions.FAN_PSU): DataFunction(
"get_fan_psu",
"_get_fan_psu",
[
RPCAPICommand("api_summary", "summary"),
RPCAPICommand("api_get_psu", "get_psu"),
],
),
str(DataOptions.ERRORS): DataFunction(
"get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")]
"_get_errors",
[
RPCAPICommand("api_get_error_code", "get_error_code"),
RPCAPICommand("api_summary", "summary"),
],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light",
"_get_fault_light",
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
),
str(DataOptions.IS_MINING): DataFunction(
"is_mining", [RPCAPICommand("api_status", "status")]
"_is_mining",
[RPCAPICommand("api_status", "status")],
),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_summary", "summary")]
"_get_uptime",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BTMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip)
# interfaces
self.api = BTMinerAPI(ip, api_ver)
"""Base handler for BTMiner based miners."""
# static data
self.api_type = "BTMiner"
# data gathering locations
self.data_locations = BTMINER_DATA_LOC
# autotuning/shutdown support
self.supports_shutdown = True
_api_cls = BTMinerRPCAPI
api: BTMinerRPCAPI
# data storage
self.api_ver = api_ver
data_locations = BTMINER_DATA_LOC
supports_shutdown = True
async def _reset_api_pwd_to_admin(self, pwd: str):
try:
@@ -239,7 +243,7 @@ class BTMiner(BaseMiner):
else:
cfg = MinerConfig()
is_mining = await self.is_mining(status)
is_mining = await self._is_mining(status)
if not is_mining:
cfg.mining_mode = MiningModeConfig.sleep()
return cfg
@@ -283,57 +287,43 @@ class BTMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(
async def _get_mac(
self, api_summary: dict = None, api_get_miner_info: dict = None
) -> Optional[str]:
if not api_get_miner_info:
if api_get_miner_info is None:
try:
api_get_miner_info = await self.api.get_miner_info()
except APIError:
pass
if api_get_miner_info:
if api_get_miner_info is not None:
try:
mac = api_get_miner_info["Msg"]["mac"]
return str(mac).upper()
except KeyError:
pass
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
mac = api_summary["SUMMARY"][0]["MAC"]
return str(mac).upper()
except (KeyError, IndexError):
except LookupError:
pass
async def get_version(
self, api_get_version: dict = None, api_summary: dict = None
) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
api_ver = await self.get_api_ver(api_get_version=api_get_version)
fw_ver = await self.get_fw_ver(
api_get_version=api_get_version, api_summary=api_summary
)
return miner_version(api_ver, fw_ver)
async def get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.api_ver:
return self.api_ver
if not api_get_version:
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
if api_get_version is None:
try:
api_get_version = await self.api.get_version()
except APIError:
pass
if api_get_version:
if api_get_version is not None:
if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131:
try:
@@ -349,20 +339,16 @@ class BTMiner(BaseMiner):
return self.api_ver
async def get_fw_ver(
async def _get_fw_ver(
self, api_get_version: dict = None, api_summary: dict = None
) -> Optional[str]:
# Check to see if the version info is already cached
if self.fw_ver:
return self.fw_ver
if not api_get_version:
if api_get_version is None:
try:
api_get_version = await self.api.get_version()
except APIError:
pass
if api_get_version:
if api_get_version is not None:
if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131:
try:
@@ -372,7 +358,7 @@ class BTMiner(BaseMiner):
else:
return self.fw_ver
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
@@ -383,20 +369,20 @@ class BTMiner(BaseMiner):
self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace(
"'", ""
)
except (KeyError, IndexError):
except LookupError:
pass
return self.fw_ver
async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
hostname = None
if not api_get_miner_info:
if api_get_miner_info is None:
try:
api_get_miner_info = await self.api.get_miner_info()
except APIError:
return None # only one way to get this
if api_get_miner_info:
if api_get_miner_info is not None:
try:
hostname = api_get_miner_info["Msg"]["hostname"]
except KeyError:
@@ -404,33 +390,32 @@ class BTMiner(BaseMiner):
return hostname
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError):
except LookupError:
pass
async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if not api_devs:
if api_devs is None:
try:
api_devs = await self.api.devs()
except APIError:
pass
if api_devs:
if api_devs is not None:
try:
for board in api_devs["DEVS"]:
if len(hashboards) < board["ASC"] + 1:
@@ -446,160 +431,163 @@ class BTMiner(BaseMiner):
float(board["MHS 1m"] / 1000000), 2
)
hashboards[board["ASC"]].chips = board["Effective Chips"]
hashboards[board["ASC"]].serial_number = board["PCB SN"]
hashboards[board["ASC"]].missing = False
except (KeyError, IndexError):
except LookupError:
pass
return hashboards
async def get_env_temp(self, api_summary: dict = None) -> Optional[float]:
if not api_summary:
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return api_summary["SUMMARY"][0]["Env Temp"]
except (KeyError, IndexError):
except LookupError:
pass
async def get_wattage(self, api_summary: dict = None) -> Optional[int]:
if not api_summary:
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
wattage = api_summary["SUMMARY"][0]["Power"]
return wattage if not wattage == -1 else None
except (KeyError, IndexError):
except LookupError:
pass
async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
if not api_summary:
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return api_summary["SUMMARY"][0]["Power Limit"]
except (KeyError, IndexError):
except LookupError:
pass
async def get_fans(
async def _get_fans(
self, api_summary: dict = None, api_get_psu: dict = None
) -> List[Fan]:
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
fans = [Fan() for _ in range(self.fan_count)]
if api_summary:
fans = [Fan() for _ in range(self.expected_fans)]
if api_summary is not None:
try:
if self.fan_count > 0:
if self.expected_fans > 0:
fans = [
Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)),
Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
]
except (KeyError, IndexError):
except LookupError:
pass
return fans
async def get_fan_psu(
async def _get_fan_psu(
self, api_summary: dict = None, api_get_psu: dict = None
) -> Optional[int]:
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
except (KeyError, IndexError):
except LookupError:
pass
if not api_get_psu:
if api_get_psu is None:
try:
api_get_psu = await self.api.get_psu()
except APIError:
pass
if api_get_psu:
if api_get_psu is not None:
try:
return int(api_get_psu["Msg"]["fan_speed"])
except (KeyError, TypeError):
pass
async def get_errors(
async def _get_errors(
self, api_summary: dict = None, api_get_error_code: dict = None
) -> List[MinerErrorData]:
errors = []
if not api_summary and not api_get_error_code:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
try:
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
if err:
errors.append(WhatsminerError(error_code=err))
except (KeyError, IndexError, ValueError, TypeError):
pass
if not api_get_error_code:
if api_get_error_code is None and api_summary is None:
try:
api_get_error_code = await self.api.get_error_code()
except APIError:
pass
if api_get_error_code:
for err in api_get_error_code["Msg"]["error_code"]:
if isinstance(err, dict):
for code in err:
errors.append(WhatsminerError(error_code=int(code)))
else:
errors.append(WhatsminerError(error_code=int(err)))
if api_get_error_code is not None:
try:
for err in api_get_error_code["Msg"]["error_code"]:
if isinstance(err, dict):
for code in err:
errors.append(WhatsminerError(error_code=int(code)))
else:
errors.append(WhatsminerError(error_code=int(err)))
except KeyError:
pass
return errors
async def get_expected_hashrate(self, api_summary: dict = None):
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
if err:
errors.append(WhatsminerError(error_code=err))
except (LookupError, ValueError, TypeError):
pass
return errors
async def _get_expected_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary is not None:
try:
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
if expected_hashrate:
return round(expected_hashrate / 1000, 2)
except (KeyError, IndexError):
except LookupError:
pass
async def get_fault_light(self, api_get_miner_info: dict = None) -> bool:
if not api_get_miner_info:
async def _get_fault_light(self, api_get_miner_info: dict = None) -> Optional[bool]:
if api_get_miner_info is None:
try:
api_get_miner_info = await self.api.get_miner_info()
except APIError:
if not self.light:
self.light = False
if api_get_miner_info:
if api_get_miner_info is not None:
try:
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto")
except KeyError:
@@ -629,14 +617,14 @@ class BTMiner(BaseMiner):
async def set_hostname(self, hostname: str):
await self.api.set_hostname(hostname)
async def is_mining(self, api_status: dict = None) -> Optional[bool]:
if not api_status:
async def _is_mining(self, api_status: dict = None) -> Optional[bool]:
if api_status is None:
try:
api_status = await self.api.status()
except APIError:
pass
if api_status:
if api_status is not None:
try:
if api_status["Msg"].get("btmineroff"):
try:
@@ -648,14 +636,14 @@ class BTMiner(BaseMiner):
except LookupError:
pass
async def get_uptime(self, api_summary: dict = None) -> Optional[int]:
if not api_summary:
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError:

View File

@@ -14,14 +14,9 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
import logging
from collections import namedtuple
from typing import List, Optional, Tuple
from typing import Optional
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import (
BaseMiner,
@@ -30,130 +25,49 @@ from pyasic.miners.base import (
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.cgminer import CGMinerRPCAPI
CGMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")]
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class CGMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip)
# interfaces
self.api = CGMinerAPI(ip, api_ver)
"""Base handler for CGMiner based miners"""
# static data
self.api_type = "CGMiner"
# data gathering locations
self.data_locations = CGMINER_DATA_LOC
_api_cls = CGMinerRPCAPI
api: CGMinerRPCAPI
# data storage
self.api_ver = api_ver
async def send_ssh_command(self, cmd: str) -> Optional[str]:
result = None
try:
conn = await self._get_ssh_connection()
except ConnectionError:
return None
# open an ssh connection
async with conn:
# 3 retries
for i in range(3):
try:
# run the command and get the result
result = await conn.run(cmd)
result = result.stdout
except Exception as e:
# if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}")
# on the 3rd retry, return None
if i == 3:
return
continue
# return the result, either command output or None
return result
async def restart_backend(self) -> bool:
return await self.restart_cgminer()
async def restart_cgminer(self) -> bool:
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot")
if ret is None:
return False
return True
async def resume_mining(self) -> bool:
commands = [
"mkdir -p /etc/tmp/",
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
"crontab -u root /etc/tmp/root",
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
async def stop_mining(self) -> bool:
commands = [
"mkdir -p /etc/tmp/",
'echo "" > /etc/tmp/root',
"crontab -u root /etc/tmp/root",
"killall cgminer",
]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
data_locations = CGMINER_DATA_LOC
async def get_config(self) -> MinerConfig:
# get pool data
@@ -165,225 +79,63 @@ class CGMiner(BaseMiner):
self.config = MinerConfig.from_api(pools)
return self.config
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def set_power_limit(self, wattage: int) -> bool:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self) -> Optional[str]:
return None
async def get_version(
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
return miner_version(
api_ver=await self.get_api_ver(api_version=api_version),
fw_ver=await self.get_fw_ver(api_version=api_version),
)
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
if self.api_ver:
return self.api_ver
if not api_version:
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
except LookupError:
pass
return self.api_ver
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if self.fw_ver:
return self.fw_ver
if not api_version:
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if api_version is None:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
if api_version is not None:
try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
except (KeyError, IndexError):
except LookupError:
pass
return self.fw_ver
async def get_hostname(self) -> Optional[str]:
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
return hn
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = []
if not api_stats:
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
try:
board_offset = -1
boards = api_stats["STATS"]
if len(boards) > 1:
for board_num in range(1, 16, 5):
for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}")
if b and not b == 0 and board_offset == -1:
board_offset = board_num
if board_offset == -1:
board_offset = 1
for i in range(
board_offset, board_offset + self.expected_hashboards
):
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.expected_chips
)
chip_temp = boards[1].get(f"temp{i}")
if chip_temp:
hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}")
if temp:
hashboard.temp = round(temp)
hashrate = boards[1].get(f"chain_rate{i}")
if hashrate:
hashboard.hashrate = round(float(hashrate) / 1000, 2)
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (IndexError, KeyError, ValueError, TypeError):
pass
return hashboards
async def get_env_temp(self) -> Optional[float]:
return None
async def get_wattage(self) -> Optional[int]:
return None
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans = [Fan() for _ in range(self.fan_count)]
if api_stats:
try:
fan_offset = -1
for fan_num in range(1, 8, 4):
for _f_num in range(4):
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num
if fan_offset == -1:
fan_offset = 1
for fan in range(self.fan_count):
fans[fan].speed = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError):
pass
return fans
async def get_fan_psu(self) -> Optional[int]:
return None
async def get_errors(self) -> List[MinerErrorData]:
return []
async def get_fault_light(self) -> bool:
return False
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
# X19 method, not sure compatibility
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
try:
expected_rate = api_stats["STATS"][1]["total_rateideal"]
try:
rate_unit = api_stats["STATS"][1]["rate_unit"]
except KeyError:
rate_unit = "GH"
if rate_unit == "GH":
return round(expected_rate / 1000, 2)
if rate_unit == "MH":
return round(expected_rate / 1000000, 2)
else:
return round(expected_rate, 2)
except (KeyError, IndexError):
pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
return int(api_stats["STATS"][1]["Elapsed"])
except LookupError:

View File

@@ -14,12 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional, Tuple
from typing import List, Optional
from pyasic import MinerConfig
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.base import (
@@ -34,68 +33,67 @@ from pyasic.web.epic import ePICWebAPI
EPIC_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [WebAPICommand("web_network", "network")]
"_get_mac",
[WebAPICommand("web_network", "network")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [WebAPICommand("web_summary", "summary")]
"_get_fw_ver",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [WebAPICommand("web_summary", "summary")]
"_get_hostname",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [WebAPICommand("web_summary", "summary")]
"_get_hashrate",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [WebAPICommand("web_summary", "summary")]
"_get_expected_hashrate",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards",
"_get_hashboards",
[
WebAPICommand("web_summary", "summary"),
WebAPICommand("web_hashrate", "hashrate"),
],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction(
"get_wattage", [WebAPICommand("web_summary", "summary")]
"_get_wattage",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [WebAPICommand("web_summary", "summary")]
"_get_fans",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction(
"get_errors", [WebAPICommand("web_summary", "summary")]
"_get_errors",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", [WebAPICommand("web_summary", "summary")]
"_get_fault_light",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [WebAPICommand("web_summary", "summary")]
"_get_uptime",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class ePIC(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# interfaces
self.web = ePICWebAPI(ip)
"""Handler for miners with the ePIC board"""
# static data
self.api_type = "ePIC"
# data gathering locations
self.data_locations = EPIC_DATA_LOC
_web_cls = ePICWebAPI
web: ePICWebAPI
async def get_model(self) -> Optional[str]:
if self.model is not None:
return self.model + " (ePIC)"
return "? (ePIC)"
firmware = "ePIC"
data_locations = EPIC_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig:
summary = None
@@ -150,32 +148,43 @@ class ePIC(BaseMiner):
pass
return False
async def get_mac(self, web_network: dict = None) -> str:
if not web_network:
web_network = await self.web.network()
if web_network:
async def _get_mac(self, web_network: dict = None) -> Optional[str]:
if web_network is None:
try:
web_network = await self.web.network()
except APIError:
pass
if web_network is not None:
try:
for network in web_network:
mac = web_network[network]["mac_address"]
return mac
return mac
except KeyError:
pass
async def get_hostname(self, web_summary: dict = None) -> str:
if not web_summary:
web_summary = await self.web.summary()
if web_summary:
async def _get_hostname(self, web_summary: dict = None) -> Optional[str]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try:
hostname = web_summary["Hostname"]
return hostname
except KeyError:
pass
async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
if not web_summary:
web_summary = await self.web.summary()
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary:
if web_summary is not None:
try:
wattage = web_summary["Power Supply Stats"]["Input Power"]
wattage = round(wattage)
@@ -183,37 +192,34 @@ class ePIC(BaseMiner):
except KeyError:
pass
async def get_hashrate(self, web_summary: dict = None) -> Optional[float]:
# get hr from API
if not web_summary:
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary:
if web_summary is not None:
try:
hashrate = 0
if web_summary["HBs"] != None:
if web_summary["HBs"] is not None:
for hb in web_summary["HBs"]:
hashrate += hb["Hashrate"][0]
return round(float(float(hashrate / 1000000)), 2)
except (LookupError, ValueError, TypeError) as e:
logger.error(e)
except (LookupError, ValueError, TypeError):
pass
async def get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
# get hr from API
if not web_summary:
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary:
if web_summary is not None:
try:
hashrate = 0
if web_summary["HBs"] != None:
if web_summary.get("HBs") is not None:
for hb in web_summary["HBs"]:
if hb["Hashrate"][1] == 0:
ideal = 1.0
@@ -222,15 +228,17 @@ class ePIC(BaseMiner):
hashrate += hb["Hashrate"][0] / ideal
return round(float(float(hashrate / 1000000)), 2)
except (IndexError, KeyError, ValueError, TypeError) as e:
logger.error(e)
except (LookupError, ValueError, TypeError):
pass
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if not web_summary:
web_summary = await self.web.summary()
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary:
if web_summary is not None:
try:
fw_ver = web_summary["Software"]
fw_ver = fw_ver.split(" ")[1].replace("v", "")
@@ -238,8 +246,8 @@ class ePIC(BaseMiner):
except KeyError:
pass
async def get_fans(self, web_summary: dict = None) -> List[Fan]:
if not web_summary:
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
@@ -247,7 +255,7 @@ class ePIC(BaseMiner):
fans = []
if web_summary:
if web_summary is not None:
for fan in web_summary["Fans Rpm"]:
try:
fans.append(Fan(web_summary["Fans Rpm"][fan]))
@@ -255,15 +263,16 @@ class ePIC(BaseMiner):
fans.append(Fan())
return fans
async def get_hashboards(
async def _get_hashboards(
self, web_summary: dict = None, web_hashrate: dict = None
) -> List[HashBoard]:
if not web_summary:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if not web_hashrate:
if web_hashrate is not None:
try:
web_hashrate = await self.web.hashrate()
except APIError:
@@ -272,7 +281,8 @@ class ePIC(BaseMiner):
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if web_summary["HBs"] != None:
if web_summary.get("HBs") is not None:
for hb in web_summary["HBs"]:
for hr in web_hashrate:
if hr["Index"] == hb["Index"]:
@@ -286,13 +296,17 @@ class ePIC(BaseMiner):
hb_list[hr["Index"]].temp = hb["Temperature"]
return hb_list
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, web_summary: dict = None) -> Optional[int]:
if not web_summary:
web_summary = await self.web.summary()
if web_summary:
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try:
uptime = web_summary["Session"]["Uptime"]
return uptime
@@ -300,10 +314,14 @@ class ePIC(BaseMiner):
pass
return None
async def get_fault_light(self, web_summary: dict = None) -> bool:
if not web_summary:
web_summary = await self.web.summary()
if web_summary:
async def _get_fault_light(self, web_summary: dict = None) -> Optional[bool]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try:
light = web_summary["Misc"]["Locate Miner State"]
return light
@@ -311,46 +329,20 @@ class ePIC(BaseMiner):
pass
return False
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if not web_summary:
web_summary = await self.web.summary()
try:
web_summary = await self.web.summary()
except APIError:
pass
errors = []
if web_summary:
if web_summary is not None:
try:
error = web_summary["Status"]["Last Error"]
if error != None:
if error is not None:
errors.append(X19Error(str(error)))
return errors
except KeyError:
pass
return errors
def fault_light_off(self) -> bool:
return False
def fault_light_on(self) -> bool:
return False
def get_api_ver(self, *args, **kwargs) -> Optional[str]:
pass
def get_config(self) -> MinerConfig:
return self.config
def get_env_temp(self, *args, **kwargs) -> Optional[float]:
pass
def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
pass
def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
pass
def get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
pass
def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional
from typing import List
from pyasic.config import MinerConfig
from pyasic.data import HashBoard
@@ -32,54 +32,47 @@ from pyasic.web.goldshell import GoldshellWebAPI
GOLDSHELL_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [WebAPICommand("web_setting", "setting")]
"_get_mac",
[WebAPICommand("web_setting", "setting")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [WebAPICommand("web_status", "status")]
"_get_fw_ver",
[WebAPICommand("web_status", "status")],
),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards",
"_get_hashboards",
[
RPCAPICommand("api_devs", "devs"),
RPCAPICommand("api_devdetails", "devdetails"),
],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction("get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BFGMinerGoldshell(BFGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# interfaces
self.web = GoldshellWebAPI(ip)
class GoldshellMiner(BFGMiner):
"""Handler for goldshell miners"""
# static data
# data gathering locations
self.data_locations = GOLDSHELL_DATA_LOC
_web_cls = GoldshellWebAPI
web: GoldshellWebAPI
data_locations = GOLDSHELL_DATA_LOC
async def get_config(self) -> MinerConfig:
# get pool data
@@ -110,36 +103,36 @@ class BFGMinerGoldshell(BFGMiner):
url=pool["url"], user=pool["user"], password=pool["pass"]
)
async def get_mac(self, web_setting: dict = None) -> str:
if not web_setting:
async def _get_mac(self, web_setting: dict = None) -> str:
if web_setting is None:
try:
web_setting = await self.web.setting()
except APIError:
pass
if web_setting:
if web_setting is not None:
try:
return web_setting["name"]
except KeyError:
pass
async def get_fw_ver(self, web_status: dict = None) -> str:
if not web_status:
async def _get_fw_ver(self, web_status: dict = None) -> str:
if web_status is None:
try:
web_status = await self.web.setting()
except APIError:
pass
if web_status:
if web_status is not None:
try:
return web_status["firmware"]
except KeyError:
pass
async def get_hashboards(
async def _get_hashboards(
self, api_devs: dict = None, api_devdetails: dict = None
) -> List[HashBoard]:
if not api_devs:
if api_devs is None:
try:
api_devs = await self.api.devs()
except APIError:
@@ -150,7 +143,7 @@ class BFGMinerGoldshell(BFGMiner):
for i in range(self.expected_hashboards)
]
if api_devs:
if api_devs is not None:
if api_devs.get("DEVS"):
for board in api_devs["DEVS"]:
if board.get("ID") is not None:
@@ -166,13 +159,13 @@ class BFGMinerGoldshell(BFGMiner):
else:
logger.error(self, api_devs)
if not api_devdetails:
if api_devdetails is None:
try:
api_devdetails = await self.api.devdetails()
except APIError:
pass
if api_devdetails:
if api_devdetails is not None:
if api_devdetails.get("DEVS"):
for board in api_devdetails["DEVS"]:
if board.get("ID") is not None:
@@ -185,9 +178,3 @@ class BFGMinerGoldshell(BFGMiner):
logger.error(self, api_devdetails)
return hashboards
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -14,18 +14,8 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import Optional
from pyasic.miners.backends import BMMiner
class Hiveon(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# static data
self.api_type = "Hiveon"
async def get_model(self) -> Optional[str]:
if self.model is not None:
return self.model + " (Hiveon)"
return "? (Hiveon)"
firmware = "Hive"

View File

@@ -0,0 +1,366 @@
# ------------------------------------------------------------------------------
# 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 typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.data.error_codes.innosilicon import InnosiliconError
from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.innosilicon import InnosiliconWebAPI
INNOSILICON_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[
WebAPICommand("web_get_all", "getAll"),
WebAPICommand("web_overview", "overview"),
],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[
RPCAPICommand("api_summary", "summary"),
WebAPICommand("web_get_all", "getAll"),
],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[
RPCAPICommand("api_stats", "stats"),
WebAPICommand("web_get_all", "getAll"),
],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[
WebAPICommand("web_get_all", "getAll"),
RPCAPICommand("api_stats", "stats"),
],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[
WebAPICommand("web_get_all", "getAll"),
],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[
WebAPICommand("web_get_all", "getAll"),
],
),
str(DataOptions.ERRORS): DataFunction(
"_get_errors",
[
WebAPICommand("web_get_error_detail", "getErrorDetail"),
],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
}
)
class Innosilicon(CGMiner):
"""Base handler for Innosilicon miners"""
_web_cls = InnosiliconWebAPI
web: InnosiliconWebAPI
data_locations = INNOSILICON_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig:
# get pool data
try:
pools = await self.web.pools()
except APIError:
return self.config
self.config = MinerConfig.from_inno(pools)
return self.config
async def reboot(self) -> bool:
try:
data = await self.web.reboot()
except APIError:
pass
else:
return data["success"]
async def restart_cgminer(self) -> bool:
try:
data = await self.web.restart_cgminer()
except APIError:
pass
else:
return data["success"]
async def restart_backend(self) -> bool:
return await self.restart_cgminer()
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def _get_mac(
self, web_get_all: dict = None, web_overview: dict = None
) -> Optional[str]:
if web_get_all:
web_get_all = web_get_all["all"]
if web_get_all is None and web_overview is None:
try:
web_overview = await self.web.overview()
except APIError:
pass
if web_get_all is not None:
try:
mac = web_get_all["mac"]
return mac.upper()
except KeyError:
pass
if web_overview is not None:
try:
mac = web_overview["version"]["ethaddr"]
return mac.upper()
except KeyError:
pass
async def _get_hashrate(
self, api_summary: dict = None, web_get_all: dict = None
) -> Optional[float]:
if web_get_all:
web_get_all = web_get_all["all"]
if api_summary is None and web_get_all is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if web_get_all is not None:
try:
if "Hash Rate H" in web_get_all["total_hash"].keys():
return round(
float(web_get_all["total_hash"]["Hash Rate H"] / 1000000000000),
2,
)
elif "Hash Rate" in web_get_all["total_hash"].keys():
return round(
float(web_get_all["total_hash"]["Hash Rate"] / 1000000), 5
)
except KeyError:
pass
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError):
pass
async def _get_hashboards(
self, api_stats: dict = None, web_get_all: dict = None
) -> List[HashBoard]:
if web_get_all:
web_get_all = web_get_all["all"]
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if web_get_all is None:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if api_stats is not None:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
idx = board["Chain ID"]
chips = board["Num active chips"]
except KeyError:
pass
else:
hashboards[idx].chips = chips
hashboards[idx].missing = False
if web_get_all is not None:
if web_get_all.get("chain"):
for board in web_get_all["chain"]:
idx = board.get("ASC")
if idx is not None:
temp = board.get("Temp min")
if temp:
hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
hashboards[idx].hashrate = round(
hashrate / 1000000000000, 2
)
chip_temp = board.get("Temp max")
if chip_temp:
hashboards[idx].chip_temp = round(chip_temp)
return hashboards
async def _get_wattage(
self, web_get_all: dict = None, api_stats: dict = None
) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if web_get_all is None:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all is not None:
try:
return web_get_all["power"]
except KeyError:
pass
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats is not None:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
wattage = board["power"]
except KeyError:
pass
else:
wattage = int(wattage)
return wattage
async def _get_fans(self, web_get_all: dict = None) -> List[Fan]:
if web_get_all:
web_get_all = web_get_all["all"]
if web_get_all is None:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
fans = [Fan() for _ in range(self.expected_fans)]
if web_get_all is not None:
try:
spd = web_get_all["fansSpeed"]
except KeyError:
pass
else:
round((int(spd) * 6000) / 100)
for i in range(self.expected_fans):
fans[i].speed = spd
return fans
async def _get_errors(
self, web_get_error_detail: dict = None
) -> List[MinerErrorData]:
errors = []
if web_get_error_detail is None:
try:
web_get_error_detail = await self.web.get_error_detail()
except APIError:
pass
if web_get_error_detail is not None:
try:
# only 1 error?
# TODO: check if this should be a loop, can't remember.
err = web_get_error_detail["code"]
except KeyError:
pass
else:
err = int(err)
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def _get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if web_get_all is None:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all is not None:
try:
level = web_get_all["running_mode"]["level"]
except KeyError:
pass
else:
# this is very possibly not correct.
level = int(level)
limit = 1250 + (250 * level)
return limit

View File

@@ -13,18 +13,10 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import asyncio
import logging
from collections import namedtuple
from typing import List, Optional, Tuple, Union
from typing import List, Optional
import toml
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.API.luxminer import LUXMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import (
BaseMiner,
@@ -32,65 +24,51 @@ from pyasic.miners.base import (
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.bosminer import BOSMinerWebAPI
from pyasic.rpc.luxminer import LUXMinerRPCAPI
LUXMINER_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [RPCAPICommand("api_config", "config")]
"_get_mac",
[RPCAPICommand("api_config", "config")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction(
"get_wattage", [RPCAPICommand("api_power", "power")]
"_get_wattage",
[RPCAPICommand("api_power", "power")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_fans", "fans")]
"_get_fans",
[RPCAPICommand("api_fans", "fans")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class LUXMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip)
# interfaces
self.api = LUXMinerAPI(ip, api_ver)
# self.web = BOSMinerWebAPI(ip)
"""Handler for LuxOS miners"""
# static data
self.api_type = "LUXMiner"
# data gathering locations
self.data_locations = LUXMINER_DATA_LOC
# autotuning/shutdown support
# self.supports_autotuning = True
# self.supports_shutdown = True
_api_cls = LUXMinerRPCAPI
api: LUXMinerRPCAPI
# data storage
self.api_ver = api_ver
firmware = "LuxOS"
data_locations = LUXMINER_DATA_LOC
async def _get_session(self) -> Optional[str]:
try:
@@ -171,25 +149,19 @@ class LUXMiner(BaseMiner):
async def get_config(self) -> MinerConfig:
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
async def set_power_limit(self, wattage: int) -> bool:
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(self, api_config: dict = None) -> Optional[str]:
async def _get_mac(self, api_config: dict = None) -> Optional[str]:
mac = None
if not api_config:
if api_config is None:
try:
api_config = await self.api.config()
except APIError:
return None
if api_config:
if api_config is not None:
try:
mac = api_config["CONFIG"][0]["MACAddr"]
except KeyError:
@@ -197,46 +169,29 @@ class LUXMiner(BaseMiner):
return mac
async def get_model(self) -> Optional[str]:
if self.model is not None:
return self.model + " (LuxOS)"
return "? (LuxOS)"
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
pass
async def get_api_ver(self) -> Optional[str]:
pass
async def get_fw_ver(self) -> Optional[str]:
pass
async def get_hostname(self) -> Union[str, None]:
pass
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if not api_summary:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = []
if not api_stats:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
board_offset = -1
boards = api_stats["STATS"]
@@ -276,32 +231,26 @@ class LUXMiner(BaseMiner):
if (not chips) or (not chips > 0):
hashboard.missing = True
hashboards.append(hashboard)
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
return hashboards
async def get_env_temp(self) -> Optional[float]:
return None
async def get_wattage(self, api_power: dict) -> Optional[int]:
if not api_power:
async def _get_wattage(self, api_power: dict = None) -> Optional[int]:
if api_power is None:
try:
api_power = await self.api.power()
except APIError:
pass
if api_power:
if api_power is not None:
try:
return api_power["POWER"][0]["Watts"]
except (IndexError, KeyError, ValueError, TypeError):
except (LookupError, ValueError, TypeError):
pass
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(self, api_fans: dict = None) -> List[Fan]:
if not api_fans:
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
if api_fans is None:
try:
api_fans = await self.api.fans()
except APIError:
@@ -309,31 +258,22 @@ class LUXMiner(BaseMiner):
fans = []
if api_fans:
for fan in range(self.fan_count):
if api_fans is not None:
for fan in range(self.expected_fans):
try:
fans.append(Fan(api_fans["FANS"][0]["RPM"]))
except (IndexError, KeyError, ValueError, TypeError):
fans.append(Fan(api_fans["FANS"][fan]["RPM"]))
except (LookupError, ValueError, TypeError):
fans.append(Fan())
return fans
async def get_fan_psu(self) -> Optional[int]:
return None
async def get_errors(self) -> List[MinerErrorData]:
pass
async def get_fault_light(self) -> bool:
pass
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats:
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
expected_rate = api_stats["STATS"][1]["total_rateideal"]
try:
@@ -346,20 +286,17 @@ class LUXMiner(BaseMiner):
return round(expected_rate / 1000000, 2)
else:
return round(expected_rate, 2)
except (KeyError, IndexError):
except LookupError:
pass
async def is_mining(self) -> Optional[bool]:
pass
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats:
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if api_stats is None:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats is not None:
try:
return int(api_stats["STATS"][1]["Elapsed"])
except LookupError:

View File

@@ -16,8 +16,8 @@
from typing import Optional
from pyasic import MinerConfig
from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.base import (
DataFunction,
@@ -31,62 +31,62 @@ from pyasic.web.vnish import VNishWebAPI
VNISH_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"get_mac", [WebAPICommand("web_summary", "summary")]
"_get_mac",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")]
"_get_api_ver",
[RPCAPICommand("api_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [WebAPICommand("web_summary", "summary")]
"_get_fw_ver",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", [WebAPICommand("web_summary", "summary")]
"_get_hostname",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [WebAPICommand("web_summary", "summary")]
"_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")]
"_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
str(DataOptions.WATTAGE): DataFunction(
"get_wattage", [WebAPICommand("web_summary", "summary")]
"_get_wattage",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", [WebAPICommand("web_settings", "settings")]
"_get_wattage_limit",
[WebAPICommand("web_settings", "settings")],
),
str(DataOptions.FANS): DataFunction(
"get_fans", [WebAPICommand("web_summary", "summary")]
"_get_fans",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("api_stats", "stats")],
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class VNish(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver)
# interfaces
self.web = VNishWebAPI(ip)
"""Handler for VNish miners"""
# static data
self.api_type = "VNish"
# data gathering locations
self.data_locations = VNISH_DATA_LOC
_web_cls = VNishWebAPI
web: VNishWebAPI
async def get_model(self) -> Optional[str]:
if self.model is not None:
return self.model + " (VNish)"
return "? (VNish)"
firmware = "VNish"
data_locations = VNISH_DATA_LOC
async def restart_backend(self) -> bool:
data = await self.web.restart_vnish()
@@ -124,76 +124,75 @@ class VNish(BMMiner):
pass
return False
async def get_mac(self, web_summary: dict = None) -> str:
if not web_summary:
async def _get_mac(self, web_summary: dict = None) -> str:
if web_summary is None:
web_info = await self.web.info()
if web_info:
if web_info is not None:
try:
mac = web_info["system"]["network_status"]["mac"]
return mac
except KeyError:
pass
if web_summary:
if web_summary is not None:
try:
mac = web_summary["system"]["network_status"]["mac"]
return mac
except KeyError:
pass
async def get_hostname(self, web_summary: dict = None) -> str:
if not web_summary:
async def _get_hostname(self, web_summary: dict = None) -> str:
if web_summary is None:
web_info = await self.web.info()
if web_info:
if web_info is not None:
try:
hostname = web_info["system"]["network_status"]["hostname"]
return hostname
except KeyError:
pass
if web_summary:
if web_summary is not None:
try:
hostname = web_summary["system"]["network_status"]["hostname"]
return hostname
except KeyError:
pass
async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
if not web_summary:
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
if web_summary is None:
web_summary = await self.web.summary()
if web_summary:
if web_summary is not None:
try:
wattage = web_summary["miner"]["power_usage"]
wattage = round(wattage * 1000)
wattage = round(wattage)
return wattage
except KeyError:
pass
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary:
if api_summary is None:
try:
api_summary = await self.api.summary()
except APIError:
pass
if api_summary:
if api_summary is not None:
try:
return round(
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
)
except (IndexError, KeyError, ValueError, TypeError) as e:
logger.error(e)
except (LookupError, ValueError, TypeError):
pass
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
if not web_settings:
async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
if web_settings is None:
web_settings = await self.web.summary()
if web_settings:
if web_settings is not None:
try:
wattage_limit = web_settings["miner"]["overclock"]["preset"]
if wattage_limit == "disabled":
@@ -202,11 +201,11 @@ class VNish(BMMiner):
except (KeyError, TypeError):
pass
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if not web_summary:
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if web_summary is None:
web_summary = await self.web.summary()
if web_summary:
if web_summary is not None:
try:
fw_ver = web_summary["miner"]["miner_type"]
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
@@ -214,8 +213,10 @@ class VNish(BMMiner):
except KeyError:
pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None
async def get_config(self) -> MinerConfig:
try:
web_settings = await self.web.settings()
except APIError:
return self.config
self.config = MinerConfig.from_vnish(web_settings)
return self.config

View File

@@ -17,21 +17,15 @@ from pyasic.miners.backends.btminer import BTMiner
class M6X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_autotuning = True
supports_autotuning = True
class M5X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_autotuning = True
supports_autotuning = True
class M3X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.supports_autotuning = True
supports_autotuning = True
class M2X(BTMiner):

View File

@@ -15,23 +15,20 @@
# ------------------------------------------------------------------------------
import asyncio
import ipaddress
import logging
from abc import ABC, abstractmethod
import warnings
from dataclasses import dataclass, field, make_dataclass
from enum import Enum
from typing import List, Optional, Tuple, TypeVar, Union
import asyncssh
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.logger import logger
class DataOptions(Enum):
MAC = "mac"
MODEL = "model"
API_VERSION = "api_ver"
FW_VERSION = "fw_ver"
HOSTNAME = "hostname"
@@ -52,6 +49,14 @@ class DataOptions(Enum):
def __str__(self):
return self.value
def default_command(self):
if str(self.value) == "config":
return "get_config"
elif str(self.value) == "is_mining":
return "_is_mining"
else:
return f"_get_{str(self.value)}"
@dataclass
class RPCAPICommand:
@@ -80,56 +85,57 @@ class GraphQLCommand(WebAPICommand):
@dataclass
class DataFunction:
cmd: str
kwargs: list[
kwargs: List[
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
] = field(default_factory=list)
def __call__(self, *args, **kwargs):
return self
DataLocations = make_dataclass(
"DataLocations",
[(enum_value.value, str) for enum_value in DataOptions],
[
(
enum_value.value,
DataFunction,
field(default_factory=DataFunction(enum_value.default_command())),
)
for enum_value in DataOptions
],
)
# add default value with
# [(enum_value.value, str, , DataFunction(enum_value.value)) for enum_value in DataOptions],
class BaseMiner(ABC):
def __init__(self, ip: str, *args, **kwargs) -> None:
# interfaces
self.api = None
self.web = None
class MinerProtocol(Protocol):
_api_cls: Type = None
_web_cls: Type = None
_ssh_cls: Type = None
self.ssh_pwd = "root"
ip: str = None
api: _api_cls = None
web: _web_cls = None
ssh: _ssh_cls = None
# static data
self.ip = ip
self.api_type = None
# type
self.make = None
self.model = None
# physical attributes
self.expected_hashboards = 3
self.expected_chips = 0
self.fan_count = 2
# data gathering locations
self.data_locations: DataLocations = None
# autotuning/shutdown support
self.supports_autotuning = False
self.supports_shutdown = False
make: str = None
raw_model: str = None
firmware: str = None
# data storage
self.api_ver = None
self.fw_ver = None
self.light = None
self.config = None
expected_hashboards: int = 3
expected_chips: int = None
expected_fans: int = 2
def __new__(cls, *args, **kwargs):
if cls is BaseMiner:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
return object.__new__(cls)
data_locations: DataLocations = None
supports_shutdown: bool = False
supports_autotuning: bool = False
api_ver: str = None
fw_ver: str = None
light: bool = None
config: MinerConfig = None
def __repr__(self):
return f"{'' if not self.api_type else self.api_type}{'' if not self.model else ' ' + self.model}: {str(self.ip)}"
return f"{self.model}: {str(self.ip)}"
def __lt__(self, other):
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
@@ -141,103 +147,31 @@ class BaseMiner(ABC):
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
@property
def pwd(self): # noqa - Skip PyCharm inspection
data = []
try:
if self.web is not None:
data.append(f"web={self.web.pwd}")
except TypeError:
pass
try:
if self.api is not None:
data.append(f"api={self.api.pwd}")
except TypeError:
pass
return ",".join(data)
@pwd.setter
def pwd(self, val):
self.ssh_pwd = val
try:
if self.web is not None:
self.web.pwd = val
except TypeError:
pass
try:
if self.api is not None:
self.api.pwd = val
except TypeError:
pass
@property
def username(self): # noqa - Skip PyCharm inspection
data = []
try:
if self.web is not None:
data.append(f"web={self.web.username}")
except TypeError:
pass
return ",".join(data)
@username.setter
def username(self, val):
try:
if self.web is not None:
self.web.username = val
except TypeError:
pass
async def _get_ssh_connection(self) -> asyncssh.connect:
"""Create a new asyncssh connection"""
try:
conn = await asyncssh.connect(
str(self.ip),
known_hosts=None,
username="root",
password=self.ssh_pwd,
server_host_key_algs=["ssh-rsa"],
)
return conn
except asyncssh.misc.PermissionDenied:
try:
conn = await asyncssh.connect(
str(self.ip),
known_hosts=None,
username="root",
password="admin",
server_host_key_algs=["ssh-rsa"],
)
return conn
except Exception as e:
raise ConnectionError from e
except OSError as e:
logging.warning(f"Connection refused: {self}")
raise ConnectionError from e
except Exception as e:
raise ConnectionError from e
def model(self) -> str:
model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
if self.firmware is not None:
model_data.append(f"({self.firmware})")
return " ".join(model_data)
async def check_light(self) -> bool:
return await self.get_fault_light()
@abstractmethod
async def fault_light_on(self) -> bool:
"""Turn the fault light of the miner on and return success as a boolean.
Returns:
A boolean value of the success of turning the light on.
"""
pass
return False
@abstractmethod
async def fault_light_off(self) -> bool:
"""Turn the fault light of the miner off and return success as a boolean.
Returns:
A boolean value of the success of turning the light off.
"""
pass
return False
@abstractmethod
async def get_config(self) -> MinerConfig:
# Not a data gathering function, since this is used for configuration
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
@@ -245,27 +179,24 @@ class BaseMiner(ABC):
Returns:
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
"""
pass
return MinerConfig()
@abstractmethod
async def reboot(self) -> bool:
"""Reboot the miner and return success as a boolean.
Returns:
A boolean value of the success of rebooting the miner.
"""
pass
return False
@abstractmethod
async def restart_backend(self) -> bool:
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
Returns:
A boolean value of the success of restarting the mining process.
"""
pass
return False
@abstractmethod
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Set the mining configuration of the miner.
@@ -275,25 +206,22 @@ class BaseMiner(ABC):
"""
return None
@abstractmethod
async def stop_mining(self) -> bool:
"""Stop the mining process of the miner.
Returns:
A boolean value of the success of stopping the mining process.
"""
pass
return False
@abstractmethod
async def resume_mining(self) -> bool:
"""Resume the mining process of the miner.
Returns:
A boolean value of the success of resuming the mining process.
"""
pass
return False
@abstractmethod
async def set_power_limit(self, wattage: int) -> bool:
"""Set the power limit to be used by the miner.
@@ -303,20 +231,19 @@ class BaseMiner(ABC):
Returns:
A boolean value of the success of setting the power limit.
"""
pass
return False
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
@abstractmethod
async def get_mac(self, *args, **kwargs) -> Optional[str]:
async def get_mac(self) -> Optional[str]:
"""Get the MAC address of the miner and return it as a string.
Returns:
A string representing the MAC address of the miner.
"""
pass
return await self._get_mac()
async def get_model(self) -> Optional[str]:
"""Get the model of the miner and return it as a string.
@@ -326,148 +253,182 @@ class BaseMiner(ABC):
"""
return self.model
@abstractmethod
async def get_api_ver(self, *args, **kwargs) -> Optional[str]:
async def get_api_ver(self) -> Optional[str]:
"""Get the API version of the miner and is as a string.
Returns:
API version as a string.
"""
pass
return await self._get_api_ver()
@abstractmethod
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
async def get_fw_ver(self) -> Optional[str]:
"""Get the firmware version of the miner and is as a string.
Returns:
Firmware version as a string.
"""
pass
return await self._get_fw_ver()
@abstractmethod
async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
"""Get the API version and firmware version of the miner and return them as strings.
Returns:
A tuple of (API version, firmware version) as strings.
"""
pass
api_ver = await self.get_api_ver()
fw_ver = await self.get_fw_ver()
return api_ver, fw_ver
@abstractmethod
async def get_hostname(self, *args, **kwargs) -> Optional[str]:
async def get_hostname(self) -> Optional[str]:
"""Get the hostname of the miner and return it as a string.
Returns:
A string representing the hostname of the miner.
"""
pass
return await self._get_hostname()
@abstractmethod
async def get_hashrate(self, *args, **kwargs) -> Optional[float]:
async def get_hashrate(self) -> Optional[float]:
"""Get the hashrate of the miner and return it as a float in TH/s.
Returns:
Hashrate of the miner in TH/s as a float.
"""
pass
return await self._get_hashrate()
@abstractmethod
async def get_hashboards(self, *args, **kwargs) -> List[HashBoard]:
async def get_hashboards(self) -> List[HashBoard]:
"""Get hashboard data from the miner in the form of [`HashBoard`][pyasic.data.HashBoard].
Returns:
A [`HashBoard`][pyasic.data.HashBoard] instance containing hashboard data from the miner.
"""
pass
return await self._get_hashboards()
@abstractmethod
async def get_env_temp(self, *args, **kwargs) -> Optional[float]:
async def get_env_temp(self) -> Optional[float]:
"""Get environment temp from the miner as a float.
Returns:
Environment temp of the miner as a float.
"""
pass
return await self._get_env_temp()
@abstractmethod
async def get_wattage(self, *args, **kwargs) -> Optional[int]:
async def get_wattage(self) -> Optional[int]:
"""Get wattage from the miner as an int.
Returns:
Wattage of the miner as an int.
"""
pass
return await self._get_wattage()
@abstractmethod
async def get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
async def get_wattage_limit(self) -> Optional[int]:
"""Get wattage limit from the miner as an int.
Returns:
Wattage limit of the miner as an int.
"""
pass
return await self._get_wattage_limit()
@abstractmethod
async def get_fans(self, *args, **kwargs) -> List[Fan]:
async def get_fans(self) -> List[Fan]:
"""Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4].
Returns:
A list of fan data.
"""
pass
return await self._get_fans()
@abstractmethod
async def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
async def get_fan_psu(self) -> Optional[int]:
"""Get PSU fan speed from the miner.
Returns:
PSU fan speed.
"""
pass
return await self._get_fan_psu()
@abstractmethod
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
async def get_errors(self) -> List[MinerErrorData]:
"""Get a list of the errors the miner is experiencing.
Returns:
A list of error classes representing different errors.
"""
pass
return await self._get_errors()
@abstractmethod
async def get_fault_light(self, *args, **kwargs) -> bool:
async def get_fault_light(self) -> bool:
"""Check the status of the fault light and return on or off as a boolean.
Returns:
A boolean value where `True` represents on and `False` represents off.
"""
pass
return await self._get_fault_light()
@abstractmethod
async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
async def get_expected_hashrate(self) -> Optional[float]:
"""Get the nominal hashrate from factory if available.
Returns:
A float value of nominal hashrate in TH/s.
"""
pass
return await self._get_expected_hashrate()
@abstractmethod
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
async def is_mining(self) -> Optional[bool]:
"""Check whether the miner is mining.
Returns:
A boolean value representing if the miner is mining.
"""
pass
return await self._is_mining()
@abstractmethod
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
async def get_uptime(self) -> Optional[int]:
"""Get the uptime of the miner in seconds.
Returns:
The uptime of the miner in seconds.
"""
return await self._get_uptime()
async def _get_mac(self) -> Optional[str]:
pass
async def _get_api_ver(self) -> Optional[str]:
pass
async def _get_fw_ver(self) -> Optional[str]:
pass
async def _get_hostname(self) -> Optional[str]:
pass
async def _get_hashrate(self) -> Optional[float]:
pass
async def _get_hashboards(self) -> List[HashBoard]:
return []
async def _get_env_temp(self) -> Optional[float]:
pass
async def _get_wattage(self) -> Optional[int]:
pass
async def _get_wattage_limit(self) -> Optional[int]:
pass
async def _get_fans(self) -> List[Fan]:
return []
async def _get_fan_psu(self) -> Optional[int]:
pass
async def _get_errors(self) -> List[MinerErrorData]:
return []
async def _get_fault_light(self) -> Optional[bool]:
pass
async def _get_expected_hashrate(self) -> Optional[float]:
pass
async def _is_mining(self) -> Optional[bool]:
pass
async def _get_uptime(self) -> Optional[int]:
pass
async def _get_data(
@@ -547,9 +508,13 @@ class BaseMiner(ABC):
args_to_send[arg.name] = None
except LookupError:
continue
function = getattr(self, getattr(self.data_locations, data_name).cmd)
miner_data[data_name] = await function(**args_to_send)
try:
function = getattr(self, getattr(self.data_locations, data_name).cmd)
miner_data[data_name] = await function(**args_to_send)
except Exception as e:
raise APIError(
f"Failed to call {data_name} on {self} while getting data."
) from e
return miner_data
async def get_data(
@@ -571,7 +536,10 @@ class BaseMiner(ABC):
data = MinerData(
ip=str(self.ip),
make=self.make,
expected_chips=self.expected_chips * self.expected_hashboards,
model=self.model,
expected_chips=self.expected_chips * self.expected_hashboards
if self.expected_chips is not None
else 0,
expected_hashboards=self.expected_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.expected_chips)
@@ -589,4 +557,23 @@ class BaseMiner(ABC):
return data
class BaseMiner(MinerProtocol):
def __init__(self, ip: str) -> None:
self.ip = ip
if self.expected_chips is None and self.raw_model is not None:
warnings.warn(
f"Unknown chip count for miner type {self.raw_model}, "
f"please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
# interfaces
if self._api_cls is not None:
self.api = self._api_cls(ip)
if self._web_cls is not None:
self.web = self._web_cls(ip)
if self._ssh_cls is not None:
self.ssh = self._ssh_cls(ip)
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell
from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import CK5
class BFGMinerCK5(BFGMinerGoldshell, CK5):
class GoldshellCK5(GoldshellMiner, CK5):
pass

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell
from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import HS5
class BFGMinerHS5(BFGMinerGoldshell, HS5):
class GoldshellHS5(GoldshellMiner, HS5):
pass

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell
from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KD5
class BFGMinerKD5(BFGMinerGoldshell, KD5):
class GoldshellKD5(GoldshellMiner, KD5):
pass

View File

@@ -13,6 +13,6 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .CK5 import BFGMinerCK5
from .HS5 import BFGMinerHS5
from .KD5 import BFGMinerKD5
from .CK5 import GoldshellCK5
from .HS5 import GoldshellHS5
from .KD5 import GoldshellKD5

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell
from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KDMax
class BFGMinerKDMax(BFGMinerGoldshell, KDMax):
class GoldshellKDMax(GoldshellMiner, KDMax):
pass

View File

@@ -13,4 +13,4 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .KDMax import BFGMinerKDMax
from .KDMax import GoldshellKDMax

View File

@@ -13,320 +13,10 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import logging
from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner
from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.types import A10X
from pyasic.web.inno import InnosiliconWebAPI
class CGMinerA10X(CGMiner, A10X):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.web = InnosiliconWebAPI(ip)
async def fault_light_on(self) -> bool:
return False
async def fault_light_off(self) -> bool:
return False
async def get_config(self, web_pools: dict = None) -> MinerConfig:
if not web_pools:
try:
web_pools = await self.web.pools()
except APIError as e:
logging.warning(e)
if web_pools:
if "pools" in web_pools.keys():
cfg = MinerConfig().from_raw(web_pools)
self.config = cfg
return self.config
async def reboot(self) -> bool:
try:
data = await self.web.reboot()
except APIError:
pass
else:
return data["success"]
async def restart_cgminer(self) -> bool:
try:
data = await self.web.restart_cgminer()
except APIError:
pass
else:
return data["success"]
async def restart_backend(self) -> bool:
return await self.restart_cgminer()
async def stop_mining(self) -> bool:
return False
# data = await self.web.poweroff()
# try:
# return data["success"]
# except KeyError:
# return False
async def resume_mining(self) -> bool:
return False
# data = await self.web.restart_cgminer()
# print(data)
# try:
# return data["success"]
# except KeyError:
# return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
# doesnt work for some reason
# self.config = config
# await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(
self, web_get_all: dict = None, web_overview: dict = None
) -> Optional[str]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all and not web_overview:
try:
web_overview = await self.web.overview()
except APIError:
pass
if web_get_all:
try:
mac = web_get_all["mac"]
return mac.upper()
except KeyError:
pass
if web_overview:
try:
mac = web_overview["version"]["ethaddr"]
return mac.upper()
except KeyError:
pass
async def get_hashrate(
self, api_summary: dict = None, web_get_all: dict = None
) -> Optional[float]:
if web_get_all:
web_get_all = web_get_all["all"]
if not api_summary and not web_get_all:
try:
api_summary = await self.api.summary()
except APIError:
pass
if web_get_all:
try:
return round(float(web_get_all["total_hash"]["Hash Rate"] / 1000000), 5)
except KeyError:
pass
if api_summary:
try:
return round(
float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000000000), 5
)
except (KeyError, IndexError):
pass
async def get_hashboards(
self, api_stats: dict = None, web_get_all: dict = None
) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if web_get_all:
web_get_all = web_get_all["all"]
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if api_stats:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
idx = board["Chain ID"]
chips = board["Num active chips"]
except KeyError:
pass
else:
hashboards[idx].chips = chips
hashboards[idx].missing = False
if web_get_all:
if web_get_all.get("chain"):
for board in web_get_all["chain"]:
idx = board.get("ASC")
if idx is not None:
temp = board.get("Temp min")
if temp:
hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
hashboards[idx].hashrate = round(
hashrate / 1000000000000, 5
)
chip_temp = board.get("Temp max")
if chip_temp:
hashboards[idx].chip_temp = round(chip_temp)
return hashboards
async def get_wattage(
self, web_get_all: dict = None, api_stats: dict = None
) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all:
try:
return web_get_all["power"]
except KeyError:
pass
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
wattage = board["power"]
except KeyError:
pass
else:
wattage = int(wattage)
return wattage
async def get_fans(self, web_get_all: dict = None) -> List[Fan]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
fans = [Fan() for _ in range(self.fan_count)]
if web_get_all:
try:
spd = web_get_all["fansSpeed"]
except KeyError:
pass
else:
round((int(spd) * 6000) / 100)
for i in range(self.fan_count):
fans[i].speed = spd
return fans
async def get_errors(
self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality
errors = []
if not web_get_error_detail:
try:
web_get_error_detail = await self.web.get_error_detail()
except APIError:
pass
if web_get_error_detail:
try:
# only 1 error?
# TODO: check if this should be a loop, can't remember.
err = web_get_error_detail["code"]
except KeyError:
pass
else:
err = int(err)
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if self.fw_ver:
return self.fw_ver
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"].split("-")[-1:][0]
except (KeyError, IndexError):
pass
return self.fw_ver
async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all:
try:
level = web_get_all["running_mode"]["level"]
except KeyError:
pass
else:
# this is very possibly not correct.
level = int(level)
limit = 1250 + (250 * level)
return limit
class InnosiliconA10X(Innosilicon, A10X):
pass

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .A10X import CGMinerA10X
from .A10X import InnosiliconA10X

View File

@@ -13,281 +13,10 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import logging
from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner
from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.types import T3HPlus
from pyasic.web.inno import InnosiliconWebAPI
class CGMinerT3HPlus(CGMiner, T3HPlus):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.web = InnosiliconWebAPI(ip)
async def fault_light_on(self) -> bool:
return False
async def fault_light_off(self) -> bool:
return False
async def get_config(self, api_pools: dict = None) -> MinerConfig:
# get pool data
try:
pools = await self.api.pools()
except APIError:
return self.config
self.config = MinerConfig.from_api(pools)
return self.config
async def reboot(self) -> bool:
try:
data = await self.web.reboot()
except APIError:
pass
else:
return data["success"]
async def restart_cgminer(self) -> bool:
try:
data = await self.web.restart_cgminer()
except APIError:
pass
else:
return data["success"]
async def restart_backend(self) -> bool:
return await self.restart_cgminer()
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(
self, web_get_all: dict = None, web_overview: dict = None
) -> Optional[str]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all and not web_overview:
try:
web_overview = await self.web.overview()
except APIError:
pass
if web_get_all:
try:
mac = web_get_all["mac"]
return mac.upper()
except KeyError:
pass
if web_overview:
try:
mac = web_overview["version"]["ethaddr"]
return mac.upper()
except KeyError:
pass
async def get_hashrate(
self, api_summary: dict = None, web_get_all: dict = None
) -> Optional[float]:
if web_get_all:
web_get_all = web_get_all["all"]
if not api_summary and not web_get_all:
try:
api_summary = await self.api.summary()
except APIError:
pass
if web_get_all:
try:
return round(
float(web_get_all["total_hash"]["Hash Rate H"] / 1000000000000), 2
)
except KeyError:
pass
if api_summary:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError):
pass
async def get_hashboards(
self, api_stats: dict = None, web_get_all: dict = None
) -> List[HashBoard]:
if web_get_all:
web_get_all = web_get_all["all"]
hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards)
]
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if api_stats:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
idx = board["Chain ID"]
chips = board["Num active chips"]
except KeyError:
pass
else:
hashboards[idx].chips = chips
hashboards[idx].missing = False
if web_get_all:
if web_get_all.get("chain"):
for board in web_get_all["chain"]:
idx = board.get("ASC")
if idx is not None:
temp = board.get("Temp min")
if temp:
hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
hashboards[idx].hashrate = round(
hashrate / 1000000000000, 2
)
chip_temp = board.get("Temp max")
if chip_temp:
hashboards[idx].chip_temp = round(chip_temp)
return hashboards
async def get_wattage(
self, web_get_all: dict = None, api_stats: dict = None
) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all:
try:
return web_get_all["power"]
except KeyError:
pass
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
wattage = board["power"]
except KeyError:
pass
else:
wattage = int(wattage)
return wattage
async def get_fans(self, web_get_all: dict = None) -> List[Fan]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
fans = [Fan() for _ in range(self.fan_count)]
if web_get_all:
try:
spd = web_get_all["fansSpeed"]
except KeyError:
pass
else:
round((int(spd) * 6000) / 100)
for i in range(self.fan_count):
fans[i].speed = spd
return fans
async def get_errors(
self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality
errors = []
if not web_get_error_detail:
try:
web_get_error_detail = await self.web.get_error_detail()
except APIError:
pass
if web_get_error_detail:
try:
# only 1 error?
# TODO: check if this should be a loop, can't remember.
err = web_get_error_detail["code"]
except KeyError:
pass
else:
err = int(err)
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
if web_get_all:
web_get_all = web_get_all["all"]
if not web_get_all:
try:
web_get_all = await self.web.get_all()
except APIError:
pass
else:
web_get_all = web_get_all["all"]
if web_get_all:
try:
level = web_get_all["running_mode"]["level"]
except KeyError:
pass
else:
# this is very possibly not correct.
level = int(level)
limit = 1250 + (250 * level)
return limit
class InnosiliconT3HPlus(Innosilicon, T3HPlus):
pass

View File

@@ -14,4 +14,4 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from .T3H import CGMinerT3HPlus
from .T3H import InnosiliconT3HPlus

View File

@@ -17,31 +17,21 @@
from pyasic.miners.base import BaseMiner
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.make = "WhatsMiner"
class WhatsMinerMake(BaseMiner):
make = "WhatsMiner"
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.make = "AntMiner"
class AntMinerMake(BaseMiner):
make = "AntMiner"
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.make = "AvalonMiner"
class AvalonMinerMake(BaseMiner):
make = "AvalonMiner"
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.make = "Innosilicon"
class InnosiliconMake(BaseMiner):
make = "Innosilicon"
class GoldshellMiner(BaseMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.make = "Goldshell"
class GoldshellMake(BaseMiner):
make = "Goldshell"

View File

@@ -28,17 +28,17 @@ from pyasic.logger import logger
from pyasic.miners.antminer import *
from pyasic.miners.avalonminer import *
from pyasic.miners.backends import (
BFGMiner,
AvalonMiner,
BMMiner,
BOSMiner,
BTMiner,
CGMiner,
CGMinerAvalon,
GoldshellMiner,
Hiveon,
LUXMiner,
VNish,
ePIC,
)
from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.base import AnyMiner
from pyasic.miners.goldshell import *
from pyasic.miners.innosilicon import *
@@ -310,7 +310,7 @@ MINER_CLASSES = {
"M66SVK40": BTMinerM66SVK40,
},
MinerTypes.AVALONMINER: {
None: CGMinerAvalon,
None: AvalonMiner,
"AVALONMINER 721": CGMinerAvalon721,
"AVALONMINER 741": CGMinerAvalon741,
"AVALONMINER 761": CGMinerAvalon761,
@@ -325,16 +325,16 @@ MINER_CLASSES = {
"AVALONMINER 1246": CGMinerAvalon1246,
},
MinerTypes.INNOSILICON: {
None: CGMiner,
"T3H+": CGMinerT3HPlus,
"A10X": CGMinerA10X,
None: Innosilicon,
"T3H+": InnosiliconT3HPlus,
"A10X": InnosiliconA10X,
},
MinerTypes.GOLDSHELL: {
None: BFGMiner,
"GOLDSHELL CK5": BFGMinerCK5,
"GOLDSHELL HS5": BFGMinerHS5,
"GOLDSHELL KD5": BFGMinerKD5,
"GOLDSHELL KDMAX": BFGMinerKDMax,
None: GoldshellMiner,
"GOLDSHELL CK5": GoldshellCK5,
"GOLDSHELL HS5": GoldshellHS5,
"GOLDSHELL KD5": GoldshellKD5,
"GOLDSHELL KDMAX": GoldshellKDMax,
},
MinerTypes.BRAIINS_OS: {
None: BOSMiner,
@@ -476,6 +476,7 @@ class MinerFactory:
fn = miner_model_fns.get(miner_type)
if fn is not None:
# noinspection PyArgumentList
task = asyncio.create_task(fn(ip))
try:
miner_model = await asyncio.wait_for(
@@ -484,15 +485,10 @@ class MinerFactory:
except asyncio.TimeoutError:
pass
boser_enabled = None
if miner_type == MinerTypes.BRAIINS_OS:
boser_enabled = await self.get_boser_braiins_os(ip)
miner = self._select_miner_from_classes(
ip,
miner_type=miner_type,
miner_model=miner_model,
boser_enabled=boser_enabled,
)
if miner is not None and not isinstance(miner, UnknownMiner):
@@ -658,7 +654,7 @@ class MinerFactory:
return MinerTypes.HIVEON
if "LUXMINER" in upper_data:
return MinerTypes.LUX_OS
if "ANTMINER" in upper_data and not "DEVDETAILS" in upper_data:
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data:
return MinerTypes.GOLDSHELL
@@ -765,7 +761,7 @@ class MinerFactory:
str_data = ",".join(str_data.split(",")[:-1]) + "}"
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
if re.search(r"\"error_code\":\[\".+\"]", str_data):
str_data = str_data.replace("[", "{").replace("]", "}")
return str_data
@@ -775,13 +771,9 @@ class MinerFactory:
ip: ipaddress.ip_address,
miner_model: Union[str, None],
miner_type: Union[MinerTypes, None],
boser_enabled: bool = None,
) -> AnyMiner:
kwargs = {}
if boser_enabled is not None:
kwargs["boser"] = boser_enabled
try:
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, **kwargs)
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
except LookupError:
if miner_type in MINER_CLASSES:
return MINER_CLASSES[miner_type][None](ip)
@@ -909,15 +901,6 @@ class MinerFactory:
except (httpx.HTTPError, LookupError):
pass
async def get_boser_braiins_os(self, ip: str):
# TODO: refine this check
try:
sock_json_data = await self.send_api_command(ip, "version")
return sock_json_data["STATUS"][0]["Msg"].split(" ")[0].upper() == "BOSER"
except LookupError:
# let the bosminer class decide
return None
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
sock_json_data = await self.send_api_command(ip, "stats")
try:

View File

@@ -55,11 +55,11 @@ class MinerListener(metaclass=Singleton):
loop = asyncio.get_running_loop()
transport_14235, protocol_14235 = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 14235) # noqa
transport_14235, _ = await loop.create_datagram_endpoint(
_MinerListener, local_addr=("0.0.0.0", 14235)
)
transport_8888, protocol_8888 = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 8888) # noqa
transport_8888, _ = await loop.create_datagram_endpoint(
_MinerListener, local_addr=("0.0.0.0", 8888)
)
while True:

View File

@@ -14,13 +14,10 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class Z15(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "Z15"
self.expected_chips = 3
self.fan_count = 2
class Z15(AntMinerMake):
raw_model = "Z15"
expected_chips = 3
expected_fans = 2

View File

@@ -14,39 +14,28 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class S17(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S17"
self.expected_chips = 48
self.fan_count = 4
class S17(AntMinerMake):
raw_model = "S17"
expected_chips = 48
expected_fans = 4
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.model = "S17+"
self.expected_chips = 65
self.fan_count = 4
class S17Plus(AntMinerMake):
raw_model = "S17+"
expected_chips = 65
expected_fans = 4
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S17 Pro"
self.expected_chips = 48
self.fan_count = 4
class S17Pro(AntMinerMake):
raw_model = "S17 Pro"
expected_chips = 48
expected_fans = 4
class S17e(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S17e"
self.expected_chips = 135
self.fan_count = 4
class S17e(AntMinerMake):
raw_model = "S17e"
expected_chips = 135
expected_fans = 4

View File

@@ -14,31 +14,22 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class T17(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "T17"
self.expected_chips = 30
self.fan_count = 4
class T17(AntMinerMake):
raw_model = "T17"
expected_chips = 30
expected_fans = 4
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "T17+"
self.expected_chips = 44
self.fan_count = 4
class T17Plus(AntMinerMake):
raw_model = "T17+"
expected_chips = 44
expected_fans = 4
class T17e(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "T17e"
self.expected_chips = 78
self.fan_count = 4
class T17e(AntMinerMake):
raw_model = "T17e"
expected_chips = 78
expected_fans = 4

View File

@@ -14,158 +14,107 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class S19(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19"
self.expected_chips = 76
self.fan_count = 4
class S19(AntMinerMake):
raw_model = "S19"
expected_chips = 76
expected_fans = 4
class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 No PIC"
self.expected_chips = 88
self.fan_count = 4
class S19NoPIC(AntMinerMake):
raw_model = "S19 No PIC"
expected_chips = 88
expected_fans = 4
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 Pro"
self.expected_chips = 114
self.fan_count = 4
class S19Pro(AntMinerMake):
raw_model = "S19 Pro"
expected_chips = 114
expected_fans = 4
class S19i(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19i"
self.expected_chips = 80
self.fan_count = 4
class S19i(AntMinerMake):
raw_model = "S19i"
expected_chips = 80
expected_fans = 4
class S19Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19+"
self.expected_chips = 80
self.fan_count = 4
class S19Plus(AntMinerMake):
raw_model = "S19+"
expected_chips = 80
expected_fans = 4
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 Pro+"
self.expected_chips = 120
self.fan_count = 4
class S19ProPlus(AntMinerMake):
raw_model = "S19 Pro+"
expected_chips = 120
expected_fans = 4
class S19XP(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 XP"
self.expected_chips = 110
self.fan_count = 4
class S19XP(AntMinerMake):
raw_model = "S19 XP"
expected_chips = 110
expected_fans = 4
class S19a(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19a"
self.expected_chips = 72
self.fan_count = 4
class S19a(AntMinerMake):
raw_model = "S19a"
expected_chips = 72
expected_fans = 4
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19a Pro"
self.expected_chips = 100
self.fan_count = 4
class S19aPro(AntMinerMake):
raw_model = "S19a Pro"
expected_chips = 100
expected_fans = 4
class S19j(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19j"
self.expected_chips = 114
self.fan_count = 4
class S19j(AntMinerMake):
raw_model = "S19j"
expected_chips = 114
expected_fans = 4
class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19j No PIC"
self.expected_chips = 88
self.fan_count = 4
class S19jNoPIC(AntMinerMake):
raw_model = "S19j No PIC"
expected_chips = 88
expected_fans = 4
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19j Pro"
self.expected_chips = 126
self.fan_count = 4
class S19jPro(AntMinerMake):
raw_model = "S19j Pro"
expected_chips = 126
expected_fans = 4
class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19j Pro+"
self.expected_chips = 120
self.fan_count = 4
class S19jProPlus(AntMinerMake):
raw_model = "S19j Pro+"
expected_chips = 120
expected_fans = 4
class S19kPro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19k Pro"
self.expected_chips = 77
self.fan_count = 4
class S19kPro(AntMinerMake):
raw_model = "S19k Pro"
expected_chips = 77
expected_fans = 4
class S19L(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19L"
self.expected_chips = 76
self.fan_count = 4
class S19L(AntMinerMake):
raw_model = "S19L"
expected_chips = 76
expected_fans = 4
class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19k Pro No PIC"
self.expected_chips = 77
self.fan_count = 4
class S19kProNoPIC(AntMinerMake):
raw_model = "S19k Pro No PIC"
expected_chips = 77
expected_fans = 4
class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 Pro Hydro"
self.expected_chips = 180
self.expected_hashboards = 4
self.fan_count = 0
class S19ProHydro(AntMinerMake):
raw_model = "S19 Pro Hydro"
expected_chips = 180
expected_hashboards = 4
expected_fans = 0

View File

@@ -14,13 +14,10 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class T19(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "T19"
self.expected_chips = 76
self.fan_count = 4
class T19(AntMinerMake):
raw_model = "T19"
expected_chips = 76
expected_fans = 4

View File

@@ -14,14 +14,12 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class D3(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "D3"
self.expected_chips = 60
self.expected_hashboards = 3
self.fan_count = 2
class D3(AntMinerMake):
raw_model = "D3"
expected_chips = 60
expected_hashboards = 3
expected_fans = 2

View File

@@ -14,14 +14,12 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class HS3(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "HS3"
self.expected_chips = 92
self.expected_hashboards = 3
self.fan_count = 2
class HS3(AntMinerMake):
raw_model = "HS3"
expected_chips = 92
expected_hashboards = 3
expected_fans = 2

View File

@@ -13,13 +13,10 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class L3Plus(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "L3+"
self.expected_chips = 72
self.fan_count = 2
class L3Plus(AntMinerMake):
raw_model = "L3+"
expected_chips = 72
expected_fans = 2

View File

@@ -14,14 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class DR5(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "DR5"
self.expected_chips = 72
self.expected_hashboards = 3
self.fan_count = 2
class DR5(AntMinerMake):
raw_model = "DR5"
expected_chips = 72
expected_hashboards = 3
expected_fans = 2

View File

@@ -13,13 +13,10 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class L7(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "L7"
self.expected_chips = 120
self.fan_count = 4
class L7(AntMinerMake):
raw_model = "L7"
expected_chips = 120
expected_fans = 4

View File

@@ -14,14 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner
from pyasic.miners.makes import AntMinerMake
class E9Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "E9Pro"
self.expected_chips = 8
self.expected_hashboards = 2
self.fan_count = 4
class E9Pro(AntMinerMake):
raw_model = "E9Pro"
expected_chips = 8
expected_hashboards = 2
expected_fans = 4

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