pyasic

A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.

Code style: black pypi python Read the Docs GitHub CodeFactor Grade

Intro

Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.

Supported Miner Types

Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.


Scanning for miners

To scan for miners in pyasic, we use the class MinerNetwork, which abstracts the search, communication, identification, setup, and return of a miner to 1 command. The command MinerNetwork().scan_network_for_miners() returns a list that contains any miners found.

import asyncio  # asyncio for handling the async part
from pyasic.network import MinerNetwork  # miner network handles the scanning


async def scan_miners():  # define async scan function to allow awaiting
    # create a miner network
    # you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
    network = MinerNetwork("192.168.1.50")  # this uses the 192.168.1.0-255 network

    # scan for miners asynchronously
    # this will return the correct type of miners if they are supported with all functionality.
    miners = await network.scan_network_for_miners()
    print(miners)

if __name__ == "__main__":
    asyncio.run(scan_miners())  # run the scan asynchronously with asyncio.run()


Creating miners based on IP

If you already know the IP address of your miner or miners, you can use the MinerFactory to communicate and identify the miners. The function MinerFactory().get_miner() will return any miner it found at the IP address specified, or an UnknownMiner if it cannot identify the miner.

import asyncio  # asyncio for handling the async part
from pyasic.miners.miner_factory import MinerFactory  # miner factory handles miners creation


async def get_miners():  # define async scan function to allow awaiting
    # get the miner with miner factory
    # miner factory is a singleton, and will always use the same object and cache
    # this means you can always call it as MinerFactory().get_miner()
    miner_1 = await MinerFactory().get_miner("192.168.1.75")
    miner_2 = await MinerFactory().get_miner("192.168.1.76")
    print(miner_1, miner_2)

if __name__ == "__main__":
    asyncio.run(get_miners())  # get the miners asynchronously with asyncio.run()


Getting data from miners

Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called get_data(). This function will return a instance of the dataclass MinerData with all data it can gather from the miner. Each piece of data in a MinerData instance can be referenced by getting it as an attribute, such as MinerData().hashrate.

import asyncio
from pyasic.miners.miner_factory import MinerFactory

async def gather_miner_data():
    miner = await MinerFactory().get_miner("192.168.1.75")
    miner_data = await miner.get_data()
    print(miner_data)  # all data from the dataclass
    print(miner_data.hashrate)  # hashrate of the miner in TH/s

if __name__ == "__main__":
    asyncio.run(gather_miner_data())

You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.

import asyncio  # asyncio for handling the async part
from pyasic.network import MinerNetwork  # miner network handles the scanning


async def gather_miner_data():  # define async scan function to allow awaiting
    network = MinerNetwork("192.168.1.50")
    miners = await network.scan_network_for_miners()

    # we need to asyncio.gather() all the miners get_data() functions to make them run together
    all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])

    for miner_data in all_miner_data:
        print(miner_data)    # print out all the data one by one

if __name__ == "__main__":
    asyncio.run(gather_miner_data())


Controlling miners via pyasic

Every miner class in pyasic must implement all the control functions defined in BaseMiner.

These functions are check_light, fault_light_off, fault_light_on, get_config, get_data, get_errors, get_hostname, get_model, reboot, restart_backend, stop_mining, resume_mining, send_config, and set_power_limit.


Check Light

Source code in pyasic/miners/base.py
96
97
async def check_light(self) -> bool:
    return await self.get_fault_light()


Fault Light Off

Turn the fault light of the miner off and return success as a boolean.

Returns:

Type Description
bool

A boolean value of the success of turning the light off.

Source code in pyasic/miners/base.py
108
109
110
111
112
113
114
115
@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


Fault Light On

Turn the fault light of the miner on and return success as a boolean.

Returns:

Type Description
bool

A boolean value of the success of turning the light on.

Source code in pyasic/miners/base.py
 99
100
101
102
103
104
105
106
@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


Get Config

Get the mining configuration of the miner and return it as a MinerConfig.

Returns:

Type Description
MinerConfig

A MinerConfig containing the pool information and mining configuration.

Source code in pyasic/miners/base.py
117
118
119
120
121
122
123
124
125
@abstractmethod
async def get_config(self) -> MinerConfig:
    # Not a data gathering function, since this is used for configuration and not MinerData
    """Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].

    Returns:
        A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
    """
    pass


Get Data

Get data from the miner in the form of MinerData.

Parameters:

Name Type Description Default
allow_warning bool

Allow warning when an API command fails.

False
data_to_get list

Names of data items you want to gather. Defaults to all data.

None

Returns:

Type Description
MinerData

A MinerData instance containing data from the miner.

Source code in pyasic/miners/base.py
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
async def get_data(
    self, allow_warning: bool = False, data_to_get: list = None
) -> MinerData:
    """Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].

    Parameters:
        allow_warning: Allow warning when an API command fails.
        data_to_get: Names of data items you want to gather. Defaults to all data.

    Returns:
        A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
    """
    data = MinerData(
        ip=str(self.ip),
        make=self.make,
        ideal_chips=self.nominal_chips * self.ideal_hashboards,
        ideal_hashboards=self.ideal_hashboards,
        hashboards=[
            HashBoard(slot=i, expected_chips=self.nominal_chips)
            for i in range(self.ideal_hashboards)
        ],
    )

    gathered_data = await self._get_data(allow_warning, data_to_get=data_to_get)
    for item in gathered_data:
        if gathered_data[item] is not None:
            setattr(data, item, gathered_data[item])

    return data


Get Errors

Get a list of the errors the miner is experiencing.

Returns:

Type Description
List[MinerErrorData]

A list of error classes representing different errors.

Source code in pyasic/miners/base.py
315
316
317
318
319
320
321
322
@abstractmethod
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
    """Get a list of the errors the miner is experiencing.

    Returns:
        A list of error classes representing different errors.
    """
    pass


Get Hostname

Get the hostname of the miner and return it as a string.

Returns:

Type Description
Optional[str]

A string representing the hostname of the miner.

Source code in pyasic/miners/base.py
234
235
236
237
238
239
240
241
@abstractmethod
async def get_hostname(self, *args, **kwargs) -> Optional[str]:
    """Get the hostname of the miner and return it as a string.

    Returns:
        A string representing the hostname of the miner.
    """
    pass


Get Model

Get the model of the miner and return it as a string.

Returns:

Type Description
Optional[str]

A string representing the model of the miner.

Source code in pyasic/miners/base.py
198
199
200
201
202
203
204
205
@abstractmethod
async def get_model(self) -> Optional[str]:
    """Get the model of the miner and return it as a string.

    Returns:
        A string representing the model of the miner.
    """
    pass


Reboot

Reboot the miner and return success as a boolean.

Returns:

Type Description
bool

A boolean value of the success of rebooting the miner.

Source code in pyasic/miners/base.py
127
128
129
130
131
132
133
134
@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


Restart Backend

Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.

Returns:

Type Description
bool

A boolean value of the success of restarting the mining process.

Source code in pyasic/miners/base.py
136
137
138
139
140
141
142
143
@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


Stop Mining

Stop the mining process of the miner.

Returns:

Type Description
bool

A boolean value of the success of stopping the mining process.

Source code in pyasic/miners/base.py
155
156
157
158
159
160
161
162
@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


Resume Mining

Resume the mining process of the miner.

Returns:

Type Description
bool

A boolean value of the success of resuming the mining process.

Source code in pyasic/miners/base.py
164
165
166
167
168
169
170
171
@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


Send Config

Set the mining configuration of the miner.

Parameters:

Name Type Description Default
config MinerConfig

A MinerConfig containing the mining config you want to switch the miner to.

required
user_suffix str

A suffix to append to the username when sending to the miner.

None
Source code in pyasic/miners/base.py
145
146
147
148
149
150
151
152
153
@abstractmethod
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
    """Set the mining configuration of the miner.

    Parameters:
        config: A [`MinerConfig`][pyasic.config.MinerConfig] containing the mining config you want to switch the miner to.
        user_suffix: A suffix to append to the username when sending to the miner.
    """
    return None


Set Power Limit

Set the power limit to be used by the miner.

Parameters:

Name Type Description Default
wattage int

The power limit to set on the miner.

required

Returns:

Type Description
bool

A boolean value of the success of setting the power limit.

Source code in pyasic/miners/base.py
173
174
175
176
177
178
179
180
181
182
183
@abstractmethod
async def set_power_limit(self, wattage: int) -> bool:
    """Set the power limit to be used by the miner.

    Parameters:
        wattage: The power limit to set on the miner.

    Returns:
        A boolean value of the success of setting the power limit.
    """
    pass


MinerConfig and MinerData

Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod cls.fields().


MinerData

MinerData is a return from the get_data() function, and is used to have a consistent dataset across all returns.

You can call MinerData.asdict() to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.

MinerData instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -

from pyasic import MinerData

# examples of miner data
d1 = MinerData("192.168.1.1")
d2 = MinerData("192.168.1.2")

list_of_miner_data = [d1, d2]

average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)


MinerConfig

MinerConfig is pyasic's way to represent a configuration file from a miner. It is the return from get_config().

Each miner has a unique way to convert the MinerConfig to their specific type, there are helper functions in the class. In most cases these helper functions should not be used, as send_config() takes a MinerConfig and will do the conversion to the right type for you.