How-To Software

Using Python to manage your miners; Part 1

Happy Friday the 13th!  Let’s jump right into the code this week, and avoid black cats and broken mirrors.

Last week I walked you through the basic setup we’ll be using to write and execute our Python scripts to manage our miners.  As a recap, we’ll be using an ASIC miner running the cgminer API on the same network as the machine that will be running our scripts.  A standard home network should work fine.  Mr. Lewis told me he’s working on a how-to for finding your new miners on your network, but in this guide we’re making the assumption that you’ve already configure your miner and it is mining on a pool.  For cgminer to respond to API calls we’ll need the ASIC running and connected to an active pool.

Once you’ve got a machine hashing on a pool, the last step is to make sure your cgminer is accepting responses from remote machines.  By default most miners won’t respond to API requests unless the remote machine is added to the API allow list.  You’ll need to know the IP address of the computer you’ll be running monitor software on, then you’ll want to include that on your miner.  Different miners have different locations for that settings.  Here’s a screenshot of our Avalon 741 allowing our computer with the IP address of 10.1.1.64 to communicate:

print(‘Hello World!’)

We’re going to start with the ancient programming tradition that is “Hello World.”  This script will make sure that your environment is setup correctly and able to run Python scripts.  If you are having issues getting Python to run this code on your machine, drop me a line and we’ll troubleshoot it together!

To begin, let’s create a new project in PyCharm to keep all our work.  I’m going to name my project “manage-cgminer” and then add a Python file (right click the ‘manage-cgminer’ folder on the left) that we’ll call hello.py:

Double-click the new hello.py file in the left pane to open the file on the right.  It should be blank to begin and we’ll input our Hello World script in this file.  The code is pretty straight forward:

print('Hello World')

Easy, right?  Now let’s run the file and make sure everything works like we expect.  Right-click on the ‘hello.py’ file in the left pane, then click the ‘Run’ option next to the green play button:

If all goes well, you should see the output of the script in the bottom pane that appears when you run your script.  It’s should read something like this:

Hello World!
Process finished with exit code 0

Talking to cgminer

Now that we’ve appeased the Python gods with our Hello World script, it’s time to really dig into some Python.  Unlike our ‘Hello World’ script, we’re going to build a function or two so we can grow our management script in later installments.  Let’s start by adding the libraries we’ll be using to talk to our miners:

#!/usr/bin/env python3
# A simple script for managing cgminer devices

import socket  # import the built-in package to open a TCP/IP socket to connect to miners
import json  # use the JSON package to format messages sent to the miner

We’ll be using the socket Python library to communicate directly with the miner. The socket library is generally installed by default, but here’s a guide for installing python packages in case you need it.  We’ll also be using the ‘json’ package to format our messages to the miner correctly. Let’s take a second to build a function and create the default main() function as well:

#!/usr/bin/env python3

# A simple script for managing cgminer devices

import socket  # import the built-in package to open a TCP/IP socket to connect to miners
import json  # use the JSON package to format messages sent to the miner


def check_status(hostaddress):  # take the passed host IP address and send it a status command
    pass

def main():
    check_status('10.3.0.10')


main()

The “pass” command in the ‘check_status’ function (line 10) is used as a placeholder in this case, as PyCharm will complain if you create an empty function. Now that we have the framework for the script we’ll add a few delicious ingredients:

#!/usr/bin/env python3

# A simple script for managing cgminer devices

import socket  # import the built-in package to open a TCP/IP socket to connect to miners
import json  # use the JSON package to format messages sent to the miner


def check_status(hostaddress):  # take the passed host IP address and send it a status command

    try:  # use the try command to help catch errors
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # use the socket package to create a new connection
        sock.settimeout(5)  # set the socket timeout to five second
        sock.connect((hostaddress, 4028))  # connect to the provided host

    except Exception as e:
        print("Error: " + e)


def main():
    check_status('10.3.0.10')


main()

The above code adds the initial connection via the socket library package. This connection is wrapped in a ‘try’ statement on line 11 to help us track down any errors that may pop up. Notice that the lines inside the try statement are indented–Python expects proper indentation to run, so if the script complains about syntax double check your spacing and indention.

Line 13 sets the socket timeout to five seconds which will keep the script from getting stuck forever waiting for a reply. Line 14 actually open a connection to the device, and if it can’t the ‘try’ wrapper will return an error and finish execution. Now that we have a connection open to the miner, let’s send it a status request:

#!/usr/bin/env python3

# A simple script for managing cgminer devices

import socket  # import the built-in package to open a TCP/IP socket to connect to miners
import json  # use the JSON package to format messages sent to the miner


def check_status(hostaddress):  # take the passed host IP address and send it a status command

    try:  # use the try command to help catch errors
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # use the socket package to create a new connection
        sock.settimeout(5)  # set the socket timeout to five second
        sock.connect((hostaddress, 4028))  # connect to the provided host

        message = json.dumps({'command': 'summary'})  # create the JSON formatted 'summary' command
        sock.sendto(message.encode(), (hostaddress, 4028))  # send the command to the miner
        response = sock.recv(4096).decode("ascii").rstrip(' \t\r\n\0')  # receive and clean up the response

        sock.shutdown(socket.SHUT_RDWR)  # send the shutdown to the socket so it closes the connection cleanly
        sock.close()  # close our socket

        response_decoded = json.loads(response)  # decode the JSON response so we can access the individual data later
        print(response_decoded)  # print the entire response received

    except Exception as e:
        print("Error: " + str(e))  # if there is an error, print it


def main():
    check_status("10.3.1.10")


main()

Cgminer expects all messages to be JSON formatted, so we use the JSON package we imported on line six. For this tutorial we’re going to be sending the ‘summary’ command on line 16 and receiving the response back on line 18. Once we’ve jammed the response into the ‘devbuf’ variable we’ll be a responsible citizen and close the socket connection behind us on line 20 and 21.

Finally, line 23 decodes the JSON response and line 24 prints our decoded response. Here’s the one line response I got back from a miner in our lab:

{'id': 1, 'SUMMARY': [{'Local Work': 823771, 'Discarded': 65690, 'Device Rejected%': 1.1347, 'MHS 5s': 9477273.36, 'Network Blocks': 191, 'Get Failures': 3, 'Pool Rejected%': 1.1374, 'Work Utility': 96901.61, 'MHS av': 6857775.14, 'MHS 1m': 7778772.41, 'Pool Stale%': 0.0, 'Remote Failures': 0, 'Found Blocks': 12, 'Rejected': 171, 'Total MH': 798932420667.0, 'Stale': 0, 'Difficulty Accepted': 185575360.0, 'MHS 15m': 7026118.23, 'Difficulty Stale': 0.0, 'Hardware Errors': 2174, 'MHS 5m': 7193833.22, 'Getworks': 3903, 'Device Hardware%': 0.0012, 'Best Share': 105136006, 'Last getwork': 1523980307, 'Difficulty Rejected': 2135009.0, 'Utility': 8.37, 'Elapsed': 116500, 'Accepted': 16261}], 'STATUS': [{'When': 1523980308, 'Msg': 'Summary', 'STATUS': 'S', 'Description': 'cgminer 4.10.0', 'Code': 11}]}

If you see a similar response from your machines, we’ve successfully had our first conversation with cgminer running on a remote device! In our next installment we’ll parse the data response and store it in a database so we can start actively monitoring our miners. As always, if you’ve ran into a problem or just have a question drop me a line in the comments and we’ll figure it out together.

Leave a Reply