diff --git a/README.md b/README.md
index 993cd29..b114aae 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,10 @@ XMR-Stak is a universal Stratum pool miner. This is the CPU-mining version; ther
## HTML reports
+## HTML and JSON API report configuraton
+
+To configure the reports shown above you need to edit the httpd_port variable. Then enable wifi on your phone and navigate to [miner ip address]:[httpd_port] in your phone browser. If you want to use the data in scripts, you can get the JSON version of the data at url [miner ip address]:[httpd_port]/api.json
+
## Usage on Windows
1) Edit the config.txt file to enter your pool login and password.
2) Double click the exe file.
diff --git a/executor.cpp b/executor.cpp
index 383c53e..948b156 100644
--- a/executor.cpp
+++ b/executor.cpp
@@ -460,6 +460,7 @@ void executor::ex_main()
case EV_HTML_HASHRATE:
case EV_HTML_RESULTS:
case EV_HTML_CONNSTAT:
+ case EV_HTML_JSON:
http_report(ev.iName);
break;
@@ -825,6 +826,121 @@ void executor::http_connection_report(std::string& out)
out.append(sHtmlConnectionBodyLow);
}
+inline const char* hps_format_json(double h, char* buf, size_t l)
+{
+ if(std::isnormal(h) || h == 0.0)
+ {
+ snprintf(buf, l, "%.1f", h);
+ return buf;
+ }
+ else
+ return "null";
+}
+
+void executor::http_json_report(std::string& out)
+{
+ const char *a, *b, *c;
+ char num_a[32], num_b[32], num_c[32];
+ char hr_buffer[64];
+ std::string hr_thds, res_error, cn_error;
+
+ size_t nthd = pvThreads->size();
+ double fTotal[3] = { 0.0, 0.0, 0.0};
+ hr_thds.reserve(nthd * 32);
+
+ for(size_t i=0; i < nthd; i++)
+ {
+ if(i != 0) hr_thds.append(1, ',');
+
+ double fHps[3];
+ fHps[0] = telem->calc_telemetry_data(2500, i);
+ fHps[1] = telem->calc_telemetry_data(60000, i);
+ fHps[2] = telem->calc_telemetry_data(900000, i);
+
+ fTotal[0] += fHps[0];
+ fTotal[1] += fHps[1];
+ fTotal[2] += fHps[2];
+
+ a = hps_format_json(fHps[0], num_a, sizeof(num_a));
+ b = hps_format_json(fHps[1], num_b, sizeof(num_b));
+ c = hps_format_json(fHps[2], num_c, sizeof(num_c));
+ snprintf(hr_buffer, sizeof(hr_buffer), sJsonApiThdHashrate, a, b, c);
+ hr_thds.append(hr_buffer);
+ }
+
+ a = hps_format_json(fTotal[0], num_a, sizeof(num_a));
+ b = hps_format_json(fTotal[1], num_b, sizeof(num_b));
+ c = hps_format_json(fTotal[2], num_c, sizeof(num_c));
+ snprintf(hr_buffer, sizeof(hr_buffer), sJsonApiThdHashrate, a, b, c);
+
+ a = hps_format_json(fHighestHps, num_a, sizeof(num_a));
+
+ size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
+ size_t ln = vMineResults.size();
+
+ for(size_t i=1; i < ln; i++)
+ iTotalRes += vMineResults[i].count;
+
+ jpsock* pool = pick_pool_by_id(dev_pool_id + 1);
+
+ size_t iConnSec = 0;
+ if(pool->is_running() && pool->is_logged_in())
+ {
+ using namespace std::chrono;
+ iConnSec = duration_cast(system_clock::now() - tPoolConnTime).count();
+ }
+
+ double fAvgResTime = 0.0;
+ if(iPoolCallTimes.size() > 0)
+ fAvgResTime = double(iConnSec) / iPoolCallTimes.size();
+
+ res_error.reserve((vMineResults.size() - 1) * 128);
+ char buffer[256];
+ for(size_t i=1; i < vMineResults.size(); i++)
+ {
+ using namespace std::chrono;
+ if(i != 1) res_error.append(1, ',');
+
+ snprintf(buffer, sizeof(buffer), sJsonApiResultError, int_port(vMineResults[i].count),
+ int_port(duration_cast(vMineResults[i].time.time_since_epoch()).count()),
+ vMineResults[i].msg.c_str());
+ res_error.append(buffer);
+ }
+
+ size_t n_calls = iPoolCallTimes.size();
+ size_t iPoolPing = 0;
+ if (n_calls > 1)
+ {
+ //Not-really-but-good-enough median
+ std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
+ iPoolPing = iPoolCallTimes[n_calls/2];
+ }
+
+ cn_error.reserve(vSocketLog.size() * 128);
+ for(size_t i=0; i < vSocketLog.size(); i++)
+ {
+ using namespace std::chrono;
+ if(i != 0) cn_error.append(1, ',');
+
+ snprintf(buffer, sizeof(buffer), sJsonApiConnectionError,
+ int_port(duration_cast(vMineResults[i].time.time_since_epoch()).count()),
+ vSocketLog[i].msg.c_str());
+ cn_error.append(buffer);
+ }
+
+ size_t bb_size = 1024 + hr_thds.size() + res_error.size() + cn_error.size();
+ std::unique_ptr bigbuf( new char[ bb_size ] );
+
+ int bb_len = snprintf(bigbuf.get(), bb_size, sJsonApiFormat,
+ hr_thds.c_str(), hr_buffer, a,
+ int_port(iPoolDiff), int_port(iGoodRes), int_port(iTotalRes), fAvgResTime, int_port(iPoolHashes),
+ int_port(iTopDiff[0]), int_port(iTopDiff[1]), int_port(iTopDiff[2]), int_port(iTopDiff[3]), int_port(iTopDiff[4]),
+ int_port(iTopDiff[5]), int_port(iTopDiff[6]), int_port(iTopDiff[7]), int_port(iTopDiff[8]), int_port(iTopDiff[9]),
+ res_error.c_str(), jconf::inst()->GetPoolAddress(), int_port(iConnSec), int_port(iPoolPing), cn_error.c_str());
+
+ out = std::string(bigbuf.get(), bigbuf.get() + bb_len);
+}
+
void executor::http_report(ex_event_name ev)
{
assert(pHttpString != nullptr);
@@ -842,6 +958,11 @@ void executor::http_report(ex_event_name ev)
case EV_HTML_CONNSTAT:
http_connection_report(*pHttpString);
break;
+
+ case EV_HTML_JSON:
+ http_json_report(*pHttpString);
+ break;
+
default:
assert(false);
break;
@@ -855,7 +976,8 @@ void executor::get_http_report(ex_event_name ev_id, std::string& data)
std::lock_guard lck(httpMutex);
assert(pHttpString == nullptr);
- assert(ev_id == EV_HTML_HASHRATE || ev_id == EV_HTML_RESULTS || ev_id == EV_HTML_CONNSTAT);
+ assert(ev_id == EV_HTML_HASHRATE || ev_id == EV_HTML_RESULTS
+ || ev_id == EV_HTML_CONNSTAT || ev_id == EV_HTML_JSON);
pHttpString = &data;
httpReady = std::promise();
diff --git a/executor.h b/executor.h
index 74efec0..968db06 100644
--- a/executor.h
+++ b/executor.h
@@ -77,6 +77,7 @@ private:
void http_hashrate_report(std::string& out);
void http_result_report(std::string& out);
void http_connection_report(std::string& out);
+ void http_json_report(std::string& out);
void http_report(ex_event_name ev);
void print_report(ex_event_name ev);
@@ -126,10 +127,7 @@ private:
bool compare(std::string& err)
{
if(msg == err)
- {
- increment();
return true;
- }
else
return false;
}
diff --git a/httpd.cpp b/httpd.cpp
index 95274a0..53b73f1 100644
--- a/httpd.cpp
+++ b/httpd.cpp
@@ -82,6 +82,13 @@ int httpd::req_handler(void * cls,
MHD_add_response_header(rsp, "ETag", sHtmlCssEtag);
MHD_add_response_header(rsp, "Content-Type", "text/css; charset=utf-8");
}
+ else if(strcasecmp(url, "/api.json") == 0)
+ {
+ executor::inst()->get_http_report(EV_HTML_JSON, str);
+
+ rsp = MHD_create_response_from_buffer(str.size(), (void*)str.c_str(), MHD_RESPMEM_MUST_COPY);
+ MHD_add_response_header(rsp, "Content-Type", "application/json; charset=utf-8");
+ }
else if(strcasecmp(url, "/h") == 0 || strcasecmp(url, "/hashrate") == 0)
{
executor::inst()->get_http_report(EV_HTML_HASHRATE, str);
diff --git a/msgstruct.h b/msgstruct.h
index d3b1709..6f4a6fb 100644
--- a/msgstruct.h
+++ b/msgstruct.h
@@ -41,7 +41,7 @@ struct job_result
enum ex_event_name { EV_INVALID_VAL, EV_SOCK_READY, EV_SOCK_ERROR,
EV_POOL_HAVE_JOB, EV_MINER_HAVE_RESULT, EV_PERF_TICK, EV_RECONNECT,
EV_SWITCH_POOL, EV_DEV_POOL_EXIT, EV_USR_HASHRATE, EV_USR_RESULTS, EV_USR_CONNSTAT,
- EV_HASHRATE_LOOP, EV_HTML_HASHRATE, EV_HTML_RESULTS, EV_HTML_CONNSTAT };
+ EV_HASHRATE_LOOP, EV_HTML_HASHRATE, EV_HTML_RESULTS, EV_HTML_CONNSTAT, EV_HTML_JSON };
/*
This is how I learned to stop worrying and love c++11 =).
diff --git a/webdesign.cpp b/webdesign.cpp
index 958d312..e07b015 100644
--- a/webdesign.cpp
+++ b/webdesign.cpp
@@ -27,12 +27,12 @@ extern const char sHtmlCssFile [] =
"a:active {"
"color: rgb(204, 122, 0);"
"}"
-
+
".all {"
"max-width:600px;"
"margin: auto;"
"}"
-
+
".header {"
"background-color: rgb(30, 30, 30);"
"color: white;"
@@ -167,6 +167,41 @@ extern const char sHtmlResultBodyHigh [] =
extern const char sHtmlResultTableRow [] =
"| %s |
| %llu | %s |
";
-extern const char sHtmlResultBodyLow [] =
+extern const char sHtmlResultBodyLow[] =
"