Skip to content

Instantly share code, notes, and snippets.

@masonticehurst
Last active June 21, 2021 02:52
Show Gist options
  • Save masonticehurst/1fddf0042fb520deba80100259f73e7b to your computer and use it in GitHub Desktop.
Save masonticehurst/1fddf0042fb520deba80100259f73e7b to your computer and use it in GitHub Desktop.
Antminer Monitor Script
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntminerMonitor
{
public class STATUS
{
// API return status
public int Code { get; set; }
public string Description { get; set; }
}
public class STAT
{
// Version Info
public string BMMiner { get; set; }
public string Miner { get; set; }
// API Info
public string CompileTime { get; set; }
public int? STATS { get; set; }
// Device type (Antminer S9i, etc)
public string Type { get; set; }
// ????
public string ID { get; set; }
// 5 second & total GH/s averages
public double GHS_FiveSecond { get; set; }
public double GHS_Average { get; set; }
// Total miners
public int? miner_count { get; set; }
// Miner hash frequency
public string frequency { get; set; }
// Number of fans and RPM (Rounds per min)
public int? fan_num { get; set; }
public int? fan3 { get; set; }
public int? fan6 { get; set; }
// Total number of temperature relays
public int? temp_num { get; set; }
// PCB Temps
public int? temp6 { get; set; }
public int? temp7 { get; set; }
public int? temp8 { get; set; }
// Chip Temps
public int? temp2_6 { get; set; }
public int? temp2_7 { get; set; }
public int? temp2_8 { get; set; }
// Board frequencies
public double? freq_avg6 { get; set; }
public double? freq_avg7 { get; set; }
public double? freq_avg8 { get; set; }
// Ideal hashrate
public double? total_rateideal { get; set; }
// Total frequency average
public double? total_freqavg { get; set; }
// ????
public int? total_acn { get; set; }
public double? total_rate { get; set; }
// Average hashrate per chair
public double? chain_rateideal6 { get; set; }
public double? chain_rateideal7 { get; set; }
public double? chain_rateideal8 { get; set; }
// Max temperature
public int? temp_max { get; set; }
// ??????
public int? no_matching_work { get; set; }
// ??????
public int? chain_acn6 { get; set; }
public int? chain_acn7 { get; set; }
public int? chain_acn8 { get; set; }
// Hashboard chain status
public string chain_acs6 { get; set; }
public string chain_acs7 { get; set; }
public string chain_acs8 { get; set; }
// Hashboard chain hardware errors
public int? chain_hw6 { get; set; }
public int? chain_hw7 { get; set; }
public int? chain_hw8 { get; set; }
// Hashboard GH/s
public double chain_rate6 { get; set; }
public double chain_rate7 { get; set; }
public double chain_rate8 { get; set; }
// Unique miner ID and firmware number
public string miner_version { get; set; }
public string miner_id { get; set; }
}
public class APIStatus
{
public List<STATUS> STATUS { get; set; }
public List<STAT> STATS { get; set; }
public int id { get; set; }
}
public class Extras
{
public string pool_name { get; set; }
public string pool_link { get; set; }
}
public class TXN_DATA
{
public int height { get; set; }
public int reward_block { get; set; }
public int reward_fees { get; set; }
public long timestamp { get; set; }
public Extras extras { get; set; }
}
public class BlockData
{
public TXN_DATA data { get; set; }
public int err_no { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntminerMonitor
{
public class USD
{
public string code { get; set; }
public string symbol { get; set; }
public string rate { get; set; }
public string description { get; set; }
public double rate_float { get; set; }
}
public class Bpi
{
public USD USD { get; set; }
}
public class BTCPrice
{
public Bpi bpi { get; set; }
}
}
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Net.NetworkInformation;
using System.Drawing;
using Console = Colorful.Console;
using System.Threading.Tasks;
using Telegram.Bot.Types.Enums;
using System.Threading;
using System.Net;
using TimeAgo;
namespace AntminerMonitor
{
class Program
{
public class Miner
{
public string id { get; set; }
public string ip { get; set; }
}
const string TELEGRAM_API = "HIDDEN_FOR_SECURITY";
const string TELEGRAM_USER = "HIDDEN_FOR_SECURITY";
const string POOL_NAME = "SlushPool";
const string POOL_LINK = "https://slushpool.com/";
const string POOL_ENDPOINT = "https://slushpool.com/stats/json/";
const string POOL_API_KEY = "HIDDEN_FOR_SECURITY";
const string BOARD_OK = " oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo ooooooo";
const int NOT_MATURE = -1;
// Global temp data
static int? g_LATEST_BLOCK = null;
// Telegram init & send message
static async Task SendTelegramMessage( string sMessage )
{
var botClient = new Telegram.Bot.TelegramBotClient(TELEGRAM_API);
var me = await botClient.GetMeAsync();
botClient.SendTextMessageAsync(TELEGRAM_USER, sMessage, ParseMode.Markdown, true, true).GetAwaiter().GetResult();
}
// Pull recent BTC price from coindesk API (GET)
static double getBTCPrice()
{
string url = "https://api.coindesk.com/v1/bpi/currentprice.json";
string target = string.Empty;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader streamReader = new StreamReader(response.GetResponseStream(), true);
try
{
target = streamReader.ReadToEnd();
}
finally
{
streamReader.Close();
}
BTCPrice latest_price = JsonConvert.DeserializeObject<BTCPrice>(target);
return latest_price.bpi.USD.rate_float;
}
// Pull recent SlushPool stats/block rewards (GET)
static SlushAPI getRewardAmount()
{
string url = POOL_ENDPOINT + POOL_API_KEY;
string target = string.Empty;
// Yes this part is stolen from StackOverflow, come at me
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader streamReader = new StreamReader(response.GetResponseStream(), true);
try
{
target = streamReader.ReadToEnd();
}
finally
{
streamReader.Close();
}
SlushAPI latest_slush = JsonConvert.DeserializeObject<SlushAPI>(target);
return latest_slush;
}
// Space filler
static void createSpacer(int iAmount)
{
for (int i = 0; i < iAmount; i++)
{
Console.WriteLine("");
}
}
// Warn user (Console beep & telegram notification)
static void warnUser(string sMinerID, string sMessage)
{
for (int i = 0; i < 5; i++)
{
// Fuck this is annoying
Console.Beep(3000, 200);
}
SendTelegramMessage("[" + sMinerID + "] " + sMessage).Wait();
}
// Pull latest block number from btc.com API (version 3) -- (GET)
static BlockData getLatestBlock(string iBlock)
{
string toQuery = iBlock;
if (toQuery == null)
{
toQuery = "latest";
}
string url = "https://chain.api.btc.com/v3/block/" + toQuery;
string target = string.Empty;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader streamReader = new StreamReader(response.GetResponseStream(), true);
try
{
target = streamReader.ReadToEnd();
}
finally
{
streamReader.Close();
}
BlockData latest_block = JsonConvert.DeserializeObject<BlockData>(target);
g_LATEST_BLOCK = latest_block.data.height;
return latest_block;
}
// Convert returned unix timestamp to local time
static void ReportTimestamp(long dTimeStamp)
{
DateTime UNIX_TIMESTAMP = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
UNIX_TIMESTAMP = UNIX_TIMESTAMP.AddSeconds(dTimeStamp).ToLocalTime();
Console.WriteLine("\t\tLast SlushPool block solved: ", Color.WhiteSmoke);
Console.WriteLine("\t\t\t" + UNIX_TIMESTAMP.TimeAgo(), Color.Fuchsia);
createSpacer(1);
}
// Loop through btc.com blocks until block hash been solved by SlushPool/slushpool.com
static long getSlushBlockTime()
{
BlockData b = getLatestBlock(null);
int iCurBlock = b.data.height;
// Reference const globals
while (b.data.extras.pool_name != POOL_NAME && b.data.extras.pool_link != POOL_LINK)
{
// Sleep main thread to prevent rate-limiting
Thread.Sleep(500);
iCurBlock -= 1;
b = getLatestBlock(iCurBlock.ToString());
}
// Push timestamp report
ReportTimestamp(b.data.timestamp);
return b.data.timestamp;
}
// Begin running async
// Moved to wait for countdown func
//Task.Run(() => getSlushBlockTime());
static string getData(string sIP)
{
string JSONReturn = "";
var p = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/k echo {\"command\":\"stats\"} | nc -w 1 " + sIP + " 4028",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
}
};
p.Start();
p.StandardInput.Close();
JSONReturn = p.StandardOutput.ReadToEnd();
// Fix broken returned JSON structure
JSONReturn = JSONReturn.Replace("\"Type\":\"Antminer S9i\"}", "\"Type\":\"Antminer S9i\"},");
// Remove remaining directory text from exit
JSONReturn = JSONReturn.Replace(Directory.GetCurrentDirectory() + ">", "");
// Allow "GHS 5s" and "GHS av" to be deserialized without errors
JSONReturn = JSONReturn.Replace("GHS 5s", "GHS_FiveSecond");
JSONReturn = JSONReturn.Replace("GHS av", "GHS_Average");
return JSONReturn;
}
// Determine if miner is active on network
static bool pingMiner(string sIP)
{
bool OK = false;
Ping pingRequest = new Ping();
PingReply pingReply = pingRequest.Send(sIP, 1 * 1000);
if (pingReply.Status == IPStatus.Success)
{
OK = true;
}
return OK;
}
static void Main(string[] args)
{
// Allow use of special unicode characters (bitcoin, etc)
Console.OutputEncoding = System.Text.Encoding.UTF8;
// Initialization Message (Telegram)
SendTelegramMessage("Antminer monitor starting...").Wait();
// Grab list of listening addresses
string sListeningJSON = System.IO.File.ReadAllText(@"C:\Users\kpjVideo\Downloads\listen_addresses.txt");
dynamic dData = JsonConvert.DeserializeObject(sListeningJSON);
while (true)
{
foreach ( var miner in dData )
{
// Returned as JSON objects, get their actual values and cache
var _IP = miner.ip.Value;
var _ID = miner.id.Value;
if ( pingMiner( _IP ) )
{
try
{
string d = getData(_IP);
Console.Write("Status: ");
Console.WriteLine(_ID, Color.Magenta);
APIStatus api_return = JsonConvert.DeserializeObject<APIStatus>(d);
STAT api_stats = api_return.STATS[1];
// Hashboard array for iteration
double[] hashboard_rates = { api_stats.chain_rate6, api_stats.chain_rate7, api_stats.chain_rate8 };
int i = 0;
foreach (double dRate in hashboard_rates)
{
i++;
if (dRate < 3500.00)
{
Console.Write("ERROR: ");
string err = "Hashboard #" + i + " performing very far from ideal hashrates: " + dRate + "!!!";
Console.WriteLine(err, Color.Red);
warnUser(_ID, "ERROR: " + err);
}
else if (dRate < 4300.00)
{
Console.Write("Warning: ");
string err = "Hashboard #" + i + " performing below ideal hashrate: " + dRate;
Console.WriteLine(err, Color.Orange);
warnUser(_ID, "Warning: " + err);
}
}
// Fan #
int? fan_num = api_stats.fan_num;
// Fan Speed
int? fanSpeed1 = api_stats.fan3;
int? fanSpeed2 = api_stats.fan6;
// PCB Temps
int? PCBTemp1 = api_stats.temp6;
int? PCBTemp2 = api_stats.temp7;
int? PCBTemp3 = api_stats.temp8;
// Chip temps
int? ChipTemp1 = api_stats.temp2_6;
int? ChipTemp2 = api_stats.temp2_7;
int? ChipTemp3 = api_stats.temp2_8;
// Board status'
string boardOK1 = api_stats.chain_acs6;
string boardOK2 = api_stats.chain_acs7;
string boardOK3 = api_stats.chain_acs8;
// Board HR (Hashrate in GH/s)
double boardRate1 = api_stats.chain_rate6;
double boardRate2 = api_stats.chain_rate7;
double boardRate3 = api_stats.chain_rate8;
// Create array of temps for iteration
int?[] temps = { PCBTemp1, PCBTemp2, PCBTemp3 };
Console.WriteLine("\t\t====== Temperature Report =====", Color.White);
int iCnt = 0;
foreach (int iTemperature in temps)
{
iCnt++;
Color cCol = Color.Red;
if (iTemperature <= 60)
{
cCol = Color.DarkGreen;
}
else if (iTemperature <= 70)
{
cCol = Color.Green;
}
else if (iTemperature <= 80)
{
cCol = Color.Orange;
}
else
{
cCol = Color.Red;
warnUser(_ID, "Temperature of PCB on board #" + iCnt + "above 80C degrees! (" + iTemperature + ")");
}
Console.Write("\t#" + iCnt + " (PCB): ", Color.WhiteSmoke);
Console.Write(iTemperature, cCol);
}
createSpacer(1);
int?[] ctemps = { ChipTemp1, ChipTemp2, ChipTemp3 };
int iCntChipTemp = 0;
foreach (int iTemperature in ctemps)
{
iCntChipTemp++;
Color cCol = Color.Red;
if (iTemperature <= 70)
{
cCol = Color.DarkGreen;
}
else if (iTemperature <= 80)
{
cCol = Color.Green;
}
else if (iTemperature <= 90)
{
cCol = Color.DarkOrange;
}
else if (iTemperature <= 100)
{
cCol = Color.Orange;
}
else
{
cCol = Color.Red;
warnUser(_ID, "Temperature of chip on board #" + iCntChipTemp + " above 100C degrees! (" + iTemperature + ")");
}
Console.Write("\t#" + iCnt + " (Chip): ", Color.WhiteSmoke);
Console.Write(iTemperature, cCol);
}
createSpacer(1);
Console.WriteLine("\t\t====== Fan Airflow Report =====", Color.White);
int?[] fanReport = { fanSpeed1, fanSpeed2 };
int iFanNum = 0;
foreach (int iFanSpeed in fanReport)
{
iFanNum++;
Color cFanOK = Color.Red;
if (iFanSpeed > 5500)
{
cFanOK = Color.Green;
}
else if (iFanSpeed > 5000)
{
cFanOK = Color.DarkGreen;
}
else if (iFanSpeed > 4000)
{
cFanOK = Color.Orange;
}
else if (iFanSpeed > 3000)
{
cFanOK = Color.DarkOrange;
}
else
{
cFanOK = Color.Red;
warnUser(_ID, "Fan speed on fan #" + iFanNum + " below 3000 RPM!");
}
string fanType = "Intake";
if (iFanNum == 2)
{
fanType = "Exhaust";
}
Console.Write("\t#" + iFanNum + " (" + fanType + "): ", Color.WhiteSmoke);
Console.Write(iFanSpeed + " RPM", cFanOK);
}
createSpacer(1);
double[] boardHashRates = { boardRate1, boardRate2, boardRate3 };
Console.WriteLine("\t\t======= HashRate Report =======", Color.White);
int iCurrentBoard = 0;
foreach( double boardHashRate in boardHashRates)
{
double boardTerraHash = boardHashRate / 1000;
iCurrentBoard++;
Color cBoardColor = Color.Red;
if (boardTerraHash >= 4.4)
{
cBoardColor = Color.Green;
}
else if (boardTerraHash < 4.4)
{
cBoardColor = Color.Orange;
}
else
{
cBoardColor = Color.Red;
warnUser(_ID, "ERROR: Hashboard #" + iCurrentBoard + " below 3 T/H! (" + boardTerraHash + ")");
}
Console.Write("\tHash Board #" + iCurrentBoard + ": ", Color.WhiteSmoke);
Console.Write("\t\t\t " + String.Format("{0:0.00}", Math.Round( boardTerraHash, 2 )), cBoardColor);
Console.WriteLine(" TH/s", Color.WhiteSmoke);
}
Console.Write("\t");
for (int iEqual = 0; iEqual <= 45; iEqual++)
{
Console.Write("=", Color.WhiteSmoke);
}
createSpacer(1);
double realHR = Math.Round(api_stats.GHS_Average / 1000, 2);
Color cColHR = Color.Red;
if(realHR >= 13.80)
{
cColHR = Color.Green;
}
else if(realHR >= 13.00)
{
cColHR = Color.Orange;
}
Console.Write("\tTotal Hash Rate:\t\t", Color.WhiteSmoke);
Console.Write(" " + String.Format("{0:0.00}", realHR), cColHR);
Console.WriteLine(" TH/s", Color.GhostWhite);
createSpacer(1);
if (realHR < 13.00)
{
Console.WriteLine("\tLOW HASHRATE DETECTED -- CHECK HASHBOARDS!", Color.Red);
}
const string HASH_PROBLEM = " Hashboard issue detected!";
const string HASH_OK = " All hashboards are reporting OK status";
Console.Write("\t");
if (boardOK1 == BOARD_OK && boardOK2 == BOARD_OK && boardOK3 == BOARD_OK)
{
// Board reported 100% OK status
Console.WriteLine(HASH_OK, Color.DarkGreen);
}
else
{
// Board reported less than 100% OK status
Console.WriteLine(HASH_PROBLEM, Color.Red);
warnUser(_ID, HASH_PROBLEM);
}
createSpacer(1);
}
catch (Exception s)
{
Console.WriteLine("ERROR: " + s.ToString());
}
}
else
{
// Cannot ping miner. Warn user and continue
string err_conn = "Error contacting " + _ID + " @ " + _IP;
Console.WriteLine(err_conn, Color.Red);
warnUser(_ID, err_conn);
}
}
getSlushBlockTime();
void getBlockReward()
{
try
{
SlushAPI currData = getRewardAmount();
foreach (KeyValuePair<int, Block> b in currData.Blocks)
{
if (b.Key == g_LATEST_BLOCK)
{
float reward = float.Parse(b.Value.Reward);
Console.WriteLine("\t\tLast SlushPool block reward:", Color.WhiteSmoke);
// Determine block maturity, less than 0 is not mature
if (b.Value.is_mature <= NOT_MATURE)
{
Console.WriteLine("\t\t Block rewards processing", Color.Orange);
}
else
{
Console.WriteLine("\t\t\t$" + Math.Round(reward * getBTCPrice(), 2) + " USD", Color.Green);
Console.WriteLine("\t\t\tɃ" + Math.Round(reward, 6), Color.DarkOrange);
}
}
}
}
catch
{
Console.WriteLine("\tError contacting CoinDesk or " + POOL_NAME + " API!", Color.Red);
}
}
// Async block reward
//Task.Run(() => getBlockReward());
getBlockReward();
Console.Write("Next query: ", Color.WhiteSmoke);
for (int a = 60; a >= 0; a--)
{
Console.CursorLeft = 14;
Console.Write(a + " seconds", Color.WhiteSmoke);
System.Threading.Thread.Sleep(1000);
}
Console.Clear();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntminerMonitor
{
public partial class SlushAPI
{
public Dictionary<int, Block> Blocks { get; set; }
public string GhashesPs { get; set; }
}
public partial class Block
{
public int is_mature { get; set; }
public string Reward { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment