Read Antminer API and show details

Trying to Read Antminer API and show the details…

The following is how I am trying to call.
‘’’ - platform: rest
name: “antminer-1”
resource: http://192.168.2.121:4028
headers:
User-Agent: Home Assistant
Content-Type: application/json
‘’’
The following is my current output:
{“STATUS”:[{“STATUS”:“E”,“When”:1561653377,“Code”:14,“Msg”:“Invalid command”,“Description”:“bmminer bOS_am1-s9-20190605-0_0de55997”}],“id”:1}

I need to pass the following in the sensor, how to do that:
“command”: “stats” or “command”: “summary”

What is the best way to do this?

Did you find a solution?

I just made a python script to read this. Seems you do not need authentication to grab basic data.

The script will dump all data in JSON. I use a command line sensor to grab the data I want. Example for temp below.

/config/shell/antminer.py


#!/usr/bin/env python3

import socket
import json

HOST = '192.168.1.155'  # Antminer IP
PORT = 4028        

m = {"command": "stats"}
jdata = json.dumps(m)


try:            
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.connect((HOST, PORT))
      s.sendall(bytes(jdata,encoding="utf-8"))
      data = ''
      while 1:
          chunk = s.recv(4026)
          if chunk:
              data += chunk.decode("utf-8") 
          else:
              break
      datajs = json.loads(data[:-1])
finally:
    s.close


print (json.dumps(datajs))

configuration.yaml


  - platform: command_line
    name: antminerTemp1
    command: "python3 /config/shell/antminer.py"
    unit_of_measurement: "°C"
    value_template: '{{ value_json["STATS"][1].temp1 }}'

  - platform: command_line
    name: antminerTemp2
    command: "python3 /config/shell/antminer.py"
    unit_of_measurement: "°C"
    value_template: '{{ value_json["STATS"][1].temp2 }}'
    
  - platform: command_line
    name: antminerTemp3
    command: 'python3 /config/shell/antminer.py'
    unit_of_measurement: "°C"
    value_template: '{{ value_json["STATS"][1].temp3 }}'
2 Likes

Thank you! I used you script to read the status of my Avalon Miner ASIC (AvalonMiner 1166Pro-S-72) and changed the configuration file to capture the fans speed percentage (quite important to estimate the noise level, if you mining at home), so if fan rate is over 80% I will receive an notification.

sensor:
  - platform: command_line
    name: minerStatus
    command: "python3 /config/shell/antminer.py"
    value_template: '{{ value_json["STATS"][0]["MM ID0"] | regex_findall_index ("FanR\[(\d+)") | int }}'  
    json_attributes:
      - "STATS"
  - platform: template
    sensors:
      avalon_asic_fanrate:
        value_template: '{{ states.sensor.minerStatus.attributes["STATS"][0]["MM ID0"] | regex_findall_index ("FanR\[(\d+)") | int }}'
      avalon_asic_networklatency:
        value_template: '{{ states.sensor.minerStatus.attributes["STATS"][0]["MM ID0"] | regex_findall_index ("PING\[(\d+)") | int }}'

could someone please help me here with a hint:

how do I see what that python script is collecting form the API for that I could choose what of the data I want to import as sensors?

Its working for temp by copy->paste, awesome work, thanks.

Oh gosh, still mining? Hope you’re not losing too much money :slight_smile:

Anyway. Happy to help if I can.

One thing you could do is get the scrip to dump (json.dumps(datajs)) into a file. Should be lots of python examples out there to help with that. Otherwise, you may be able to change the value template for a sensor to somthing like {{ value_json }}. There is a character limit. But it may show what you need.

Either way, your goal is to get the entire json data. This will show all data you can pull from. Not sure if you are you familiar with JSON? It’s sort of like XML if that helps. If you can get the JSON data and share it here I can help a bit more. If I have time later I can help with the script as well. Just let me know if you’re struggling.

Hope that helps for now.

hm, no, I never worked with json, i just copy pasted your work into my system and it did work out of the box. :smiley: thx btw.

i did try to find anything that would lead me to how to do that. in the end i did go back to linux cli.

echo -n "stats" | nc IP_OF_MINER_GOES_HERE 4028

and

echo -n "pools" | nc IP_OF_MINER_GOES_HERE 4028

gave me a good starting point.

copied the output to file, opened in notepad ++ and find and replace the , by ,\n and you get a nice long list of all the data the bitmain API makes available.

regarding loosing money on mining. well currently one needs real cheap electricity, or being able to sell the waste heat :slight_smile: but thas goes only if you need to convert the btc to pay the electricity in fiat

Im actively mining now for more than a decade, in a nutshell I use my solar power to run those miners and then I use the waste heat to heat my house and pool with it… Back in the old days it was a PITA, but now with Braiins, its kind of easy if you got the right hardware, but then, you only really need one unit to be PWM controllable, the rest is just on and off and you get a 5-160% scale able heater. Now what to do during summer you may ask. Well in the summer i use a heat siphon powered by the waste heat to cool my house and dump that combined heat into hot water making and the pool. Indeed, instead I could use a heat pump for all that, but then if I feed my solar power to the grid they wont pay zilch for it, and a heat-pump is an expensive thing to replace and only lasts that many operational hours and its efficiency (even with the new R32 stuff) really takes a dive to the bottom of the Mariana trench when the temp. difference is high, no matter cooling or heating.

list of data available under stats:

STATUS=S,
When=1675526691,
Code=70,
Msg=CGMiner stats,
Description=cgminer 1.0.0|BMMiner=1.0.0,
Miner=uart_trans.1.3,
CompileTime=Wed Apr 27 15:19:27 CST 2022,
Type=Antminer S19|,
STATS=0,
ID=BTM_SOC0,
Elapsed=8892913,
Calls=0,
Wait=0,
Max=0,
Min=99999999,
GHS 5s=90973.59,
GHS av=91530.48,
rate_30m=91402.71,
Mode=2,
miner_count=3,
frequency=675,
fan_num=4,
fan1=4800,
fan2=4770,
fan3=4770,
fan4=4950,
temp_num=3,
temp1=65,
temp2_1=70,
temp2=64,
temp2_2=69,
temp3=64,
temp2_3=69,
temp_pcb1=50-49-65-64,
temp_pcb2=52-48-64-63,
temp_pcb3=51-48-64-64,
temp_pcb4=0-0-0-0,
temp_chip1=55-54-70-69,
temp_chip2=57-53-69-68,
temp_chip3=56-53-69-69,
temp_chip4=0-0-0-0,
temp_pic1=40-39-55-54,
temp_pic2=42-38-54-53,
temp_pic3=41-38-54-54,
temp_pic4=0-0-0-0,
total_rateideal=90000.00,
rate_unit=GH,
total_freqavg=675,
total_acn=264,
total rate=91530.48,
temp_max=0,
no_matching_work=787,
chain_acn1=88,
chain_acn2=88,
chain_acn3=88,
chain_acn4=0,
chain_acs1= oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo,
chain_acs2= oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo,
chain_acs3= oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo,
chain_acs4=,
chain_hw1=198,
chain_hw2=195,
chain_hw3=394,
chain_hw4=0,
chain_rate1=29840.75,
chain_rate2=31138.17,
chain_rate3=29994.68,
chain_rate4=,
freq1=675,
freq2=675,
freq3=675,
freq4=0,
miner_version=uart_trans.1.3,
miner_id=no miner id

and under pools (of course i did take my credentials out :slight_smile: )

STATUS=S,
When=1675531620,
Code=7,
Msg=3 Pool(s),
Description=cgminer 1.0.0|POOL=0,
URL=stratum+tcp://url,
Status=Deed,
Priority=0,
Quota=1,
Long Poll=N,
Getworks=0,
Accepted=0,
Rejected=0,
Discarded=0,
Stale=0,
Get Failures=0,
Remote Failures=0,
User=,
Last Share Time=0,
Diff=,
Diff1 Shares=0,
Proxy Type=,
Proxy=,
Difficulty Accepted=0.00,
Difficulty Rejected=0.00,
Difficulty Stale=0.00,
Last Share Difficulty=0.00,
Has Stratum=true,
Stratum Active=false,
Stratum URL=,
Has GBT=false,
Best Share=0.00,
Pool Rejected%=0.00,
Pool Stale%%=0.00|,
POOL=1,
URL=stratum+tcp://url,
Status=Deed,
Priority=1,
Quota=1,
Long Poll=N,
Getworks=0,
Accepted=0,
Rejected=0,
Discarded=0,
Stale=0,
Get Failures=0,
Remote Failures=0,
User=,
Last Share Time=0,
Diff=,
Diff1 Shares=0,
Proxy Type=,
Proxy=,
Difficulty Accepted=0.00,
Difficulty Rejected=0.00,
Difficulty Stale=0.00,
Last Share Difficulty=0.00,
Has Stratum=true,
Stratum Active=false,
Stratum URL=,
Has GBT=false,
Best Share=0.00,
Pool Rejected%=0.00,
Pool Stale%%=0.00|,
POOL=2,
URL=stratum+tcp://url,
Status=Alive,
Priority=2,
Quota=1,
Long Poll=N,
Getworks=473464,
Accepted=1108464,
Rejected=18,
Discarded=4931003,
Stale=3,
Get Failures=4,
Remote Failures=8,
User=,
Last Share Time=0:00:07,
Diff=524K,
Diff1 Shares=0,
Proxy Type=,
Proxy=,
Difficulty Accepted=188766192032.00,
Difficulty Rejected=328369762.00,
Difficulty Stale=0.00,
Last Share Difficulty=524288.00,
Has Stratum=true,
Stratum Active=true,
Stratum URL=url,
Has GBT=false,
Best Share=4637945584303.00,
Pool Rejected%=0.00,
Pool Stale%%=0.00

so now i got a question for you @jscarlett5 :slight_smile:

how would i go fishing for say the “Rejected” behind the 3rd pipe symbol (|) in the pools output?

and how do i go for a field name that is not one string but contains a space like “Get Failures”?

Nice one. Thanks for sharing. May help the next person too. Lots of fun data to pull there.

Cool use for heating. I had mine in the garage for some time. I was amazed at how warm it kept it in the winter. Not a bad space heater lol.

For the get failures, you’ll just need some quotes I think.

Somthing like this may do it:

value_template: '{{ value_json["POOLS"][1]."Get Failures" }}'

Not sure what you mean by the third pipe symbol though. But you can likely pull it if you play around with templating. Not my favourite thing to work on, but you can do just about anything with strings.

i suspected that, but was not quite sure where to put the quotes, they could screw code up real fast. :slight_smile: thx for the suggestion, ill try, no further report back means it did work that way.

what i meant was the | in the quoted code. it kind of separates a table, is the best way to describe it.

before the first | is the header informations, between first and second | is the first pool infos, between second and third | is the second pool and after the third | is the third pool informaions.

could it be i need to change from [1] to [2]?

indeed thats how I started, but i kind of got in hot water for the noise, so I had to change that real fast, but indeed, i never hat better days working on one of my classic cars during winter, and I still do that once in a while when I need spot heat somewhere. I still have some old ebit trash machines, they are as good as a space heater, but get me a few cents back per day. not much but better than nothing. Not worth running them to make money, but better than a space heater thats for dam sure.

so for reading the pool infos, one needs to modify or create a new python script. for not being able to on the fly integrate the second source into the first python script, i made a second one.

looks like this:

#!/usr/bin/env python3
# https://community.home-assistant.io/t/read-antminer-api-and-show-details/123795/4

import socket
import json

HOST = '192.168.1.2'  # Antminer IP
PORT = 4028        

m = {"command": "pools"}
jdata = json.dumps(m)


try:            
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.connect((HOST, PORT))
      s.sendall(bytes(jdata,encoding="utf-8"))
      data = ''
      while 1:
          chunk = s.recv(4026)
          if chunk:
              data += chunk.decode("utf-8") 
          else:
              break
      datajs = json.loads(data[:-1])
finally:
    s.close


print (json.dumps(datajs))

and for the sensor its like i suspected, the [] defines the row of the table that is addressed

its like this

[0] = pool 1
[1] = pool 2
[2] = pool 3

here is my example for the sensor

# Pool-Infos-Test
# 30
- platform: command_line
  name: S19-30-Accepted-1
  command: "python3 /config/shell/S19-30-pools.py"
  value_template: '{{ value_json["POOLS"][0].Accepted }}'
  
- platform: command_line
  name: S19-30-Accepted-2
  command: "python3 /config/shell/S19-30-pools.py"
  value_template: '{{ value_json["POOLS"][1].Accepted }}'
  
- platform: command_line
  name: S19-30-Accepted-3
  command: "python3 /config/shell/S19-30-pools.py"
  value_template: '{{ value_json["POOLS"][2].Accepted }}'
  

well, that does not work…

i got this form the dev tools

Invalid config for [sensor.command_line]: invalid template (TemplateSyntaxError: expected name or number) for dictionary value @ data[‘value_template’]. Got ‘{{ value_json[“POOLS”][0].“Pool Rejected” }}’
string value is None for dictionary value @ data[‘unique_id’]. Got None. (See ?, line ?).

I tried this one

no dice. did not work, what ever combination i used.

edit: maybe i was toooo fast here.

indeed i was too fast

thats how it works:

- platform: command_line
  name: S19-30-Pool Rejected-1
  command: "python3 /config/shell/S19-30-pools.py"
  unit_of_measurement: "%"
  value_template: '{{ value_json["POOLS"][0]["Pool Rejected%"] }}'

ditch the dot behind [0] and get the field name into [" "]

Sorry for going dark there. Daughter was/is sick. I completely forgot the script needed to be changed for the pools.

This is sort of how I operate with JSON too. Just muck about until you get the value you want lol. They can be structured so many different ways, so it’s hard to ask for help on how to build your template. All of this is clearly purged from my memory already.

1 Like

no worrys, i know exactly how that is (sick kid i mean)

you got me started, and gave me the right direction, that was what I needed so I could figure the rest out my self and put it in here to help the next guy.

but dont get too exyted about the jason, the bitmain api only reports back 0.00%, in the end I did do the calculations myself. rejected devided by submitted…

here is my currently good working code:

in my template.yaml i have now this code sniped

# calculate the rejected rate of the miners
- sensor:
  - name: "S19-30 Pool Rejected"
    unique_id: #put your own uiid here, i have it with uiid to be able to change the code from the frontend, since when I change the pool i need to change the _3 to something else.
    unit_of_measurement: "%"
    state: >
      {% set accepted = states('sensor.s19_30_accepted_3') | float %}
      {% set rejected = states('sensor.s19_30_rejected_3') | float %}
      {{ (100 / accepted * rejected) | round(2, default=0) }}

so, a bunch of days later, ill report that the integration works stable and reliable.

i love it, i now know exactly when my stale and rejected shares happen and could fix em… OT: it was a network issue when the DSL got reconnected during the night. so now I switch the internet over to backup LTE when DSL goes dark for a bunch of minutes each early morning.

Thanks for this code, it is working well with S19 and new models but old models such as L3++ doesn’t response to commands such as “pools” and “stats”
how can I get the correct commands?
also I need to retrieve the miners mac_address, could you help me about this?

im sorry, i dont poll for mac, i use unifi network equipment, so I could pull it from there… dont know your setup

regarding the old, i dont have any of those, so i cant help either with that question, im sorry, but im sure there are some api tutorials regarding the L3 units out there. from there you need to adopt the above code. but i cant really help, i did mostly copy paste too here.

1 Like

you da man, mate.
i’m running an L7 on solar with 12,2kwp panel in north italy, now working at immersion cooling and THE to run pool heater. I’m so happy to find pioneers like you .
I’m now implementing your script, as with new firwmare i have also now migrated an python script to change the working mode so i can now run on stock firmware on 2 powers low power 2.4kw and 3.5kw full

Such a shame i can’t get the mode running.
I thought it was the variable “Mode” but that seems to stay always in 2.

I successfully created the shell command to switch to low power , but i cant get the variable to control back if the command has changed the state or not . any ideas?

nope, sorry.