nxducominer
A multithreaded DUCO miner for the Nintendo Switch
 
Loading...
Searching...
No Matches
main.c
Go to the documentation of this file.
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <time.h>
5#include <unistd.h>
6#include <stdint.h>
7#include <arpa/inet.h>
8#include <netdb.h>
9#include <sys/socket.h>
10#include <switch.h>
11#include <curl/curl.h>
12#include <pthread.h>
13#include <errno.h>
14#include <stdatomic.h>
15#include "dashboard.h"
16#include "switch/crypto/sha1.h"
17#include "jsmn.h"
18
19#define CONFIG_FILE "config.txt"
20#define SOFTWARE "nxducominer"
21#define GET_POOL "https://server.duinocoin.com/getPool"
22
23
24#define DUCO_ORANGE CONSOLE_ESC(38;2;252;104;3m)
25#define ERROR_RED CONSOLE_ESC(31m)
26#define NOTICE_BLUE CONSOLE_ESC(38;2;135;206;235m)
27#define DARK_GREY CONSOLE_ESC(38;2;90;90;90m)
28#define LIGHT_GREY CONSOLE_ESC(38;2;135;135;135m)
29#define RESET CONSOLE_ESC(0m)
30
31#define CONSOLE_ESC_NSTR(fmt) "\033[" fmt
32
50
52 .total_hashrate = 0.0f,
53 .total_difficulty = 0,
54 .avg_difficulty = 0.0f,
55 .total_shares = 0,
56 .good_shares = 0,
57 .bad_shares = 0,
58 .blocks = 0
59};
60
64typedef struct {
67 float hashrate;
72 int blocks;
73 char* error;
74 sig_atomic_t stop_mining;
75
77
81typedef struct {
84 pthread_t wd_thread;
86
88 .server_fd = -1,
89 .client_fd = -1,
90 .wd_thread = NULL
91};
92
107
109 .last_share = 0,
110 .charge = 0,
111 .chargeType = 0,
112 .skin_temp_milli_c = 0,
113 .lock = PTHREAD_MUTEX_INITIALIZER,
114 .mining_threads = NULL,
115 .thread_data = NULL,
116 .single_miner_id = 0,
117 .web_dashboard = &web
118};
119
123typedef struct {
124 char* node;
125 int port;
126 char* name;
130 char* rig_id;
132 bool iot;
136
138 .node = NULL,
139 .name = NULL,
140 .wallet_address = NULL,
141 .miner_key = NULL,
142 .difficulty = NULL,
143 .rig_id = NULL,
144 .port = 0,
145 .cpu_boost = false,
146 .iot = false,
147 .threads = 1,
148 .web_dashboard = false
149};
150
155 char* memory;
156 size_t size;
157};
165void cleanup(char* msg) {
166 printf(CONSOLE_ESC(80;1H)); //move cursor
167 printf(CONSOLE_ESC(2K)); //clear line
168
169 if (msg == NULL) {
170 printf(CONSOLE_ESC(80;1H) "Exiting...");
171 consoleUpdate(NULL);
172 }
173 else {
174 printf(ERROR_RED);
175 printf(CONSOLE_ESC(80;1H) "ERROR: %s. Exiting...", msg);
176 printf(RESET);
177 consoleUpdate(NULL);
178 }
179 sleep(3);
180
181 //cleanup threads
183 for (int i = 0; i < mc.threads; i++) {
184 if (res.thread_data[i].socket_fd >= 0) {
185 close(res.thread_data[i].socket_fd); //close sockets
186 res.thread_data[i].socket_fd = -1;
187 }
188 res.thread_data[i].stop_mining = 1; //set atomic signal
189
190 if (res.mining_threads[i]) {
191 pthread_cancel(res.mining_threads[i]); //cancel threads
192 }
193 }
194
195 for (int i = 0; i < mc.threads; i++) {
196 if (res.mining_threads[i]) {
197 pthread_join(res.mining_threads[i], NULL); //join threads
198 }
199 }
200 }
201
202 //cleanup web dashboard
203 if (mc.web_dashboard && res.web_dashboard->wd_thread != NULL) {
206 pthread_cancel(res.web_dashboard->wd_thread);
207 pthread_join(res.web_dashboard->wd_thread, NULL);
208 free(res.web_dashboard);
209 }
210
211 // free resources
212 if (mc.name) free(mc.name);
213 if (mc.difficulty) free(mc.difficulty);
214 if (mc.miner_key) free(mc.miner_key);
215 if (mc.node) free(mc.node);
216 if (mc.rig_id) free(mc.rig_id);
218
219 psmExit();
220 tcExit();
221 socketExit();
222 consoleExit(NULL);
223 exit(0);
224}
225
236const char* get_psm_charger_type(PsmChargerType type) {
237 switch (type) {
238 case PsmChargerType_Unconnected: return "discharging";
239 case PsmChargerType_EnoughPower: return "charging";
240 case PsmChargerType_LowPower: return "charging slowly";
241 case PsmChargerType_NotSupported: return "plugged in, not charging";
242 default: return "unknown charger state";
243 }
244}
245
251void get_time_string(char* buffer, int size) {
252 time_t raw_time = time(NULL);
253 struct tm* time_info = localtime(&raw_time);
254 strftime(buffer, size, "%H:%M:%S", time_info);
255}
256
263void* safe_malloc(size_t size) {
264 void* ptr = malloc(size);
265 if (!ptr) {
266 cleanup("memory allocation failed");
267 }
268 memset(ptr, 0, size);
269 return ptr;
270}
271
278char* safe_strdup(const char* src) {
279 if (!src) return NULL;
280 char* dst = safe_malloc(strlen(src) + 1);
281 strcpy(dst, src);
282 return dst;
283}
284
291void set_dynamic_string(char** field, const char* value) {
292 free(*field);
293 *field = safe_strdup(value);
294 if (*field == NULL) {
295 cleanup("memory allocation failed");
296 }
297}
298
306ssize_t safe_write(int fd, const char* buf, size_t len) {
307 if (fd < 0) return -1;
308
309 ssize_t total = 0;
310 while (total < len) {
311 ssize_t sent = write(fd, buf + total, len - total);
312 if (sent <= 0) return -1; //error
313 total += sent;
314 }
315 return total;
316}
317
326static size_t write_memory_callback(void* contents, size_t size, size_t nmemb, void* userp) {
327 size_t realsize = size * nmemb;
328 struct MemoryStruct* mem = (struct MemoryStruct*)userp;
329
330 char* ptr = realloc(mem->memory, mem->size + realsize + 1);
331 if (!ptr) {
332 free(mem->memory);
333 mem->memory = NULL;
334 mem->size = 0;
335 return 0;
336 }
337
338 mem->memory = ptr;
339 memcpy(&(mem->memory[mem->size]), contents, realsize);
340 mem->size += realsize;
341 mem->memory[mem->size] = 0;
342
343 return realsize;
344}
345
352void replace_placeholder(char** str, const char* placeholder, const char* value) {
353 if (!str || !*str || !placeholder || !value) return;
354
355 char* current = *str;
356 size_t placeholder_len = strlen(placeholder);
357 size_t value_len = strlen(value);
358
359 size_t new_len = strlen(current) + 1;
360 char* result = malloc(new_len);
361 strcpy(result, current);
362
363 char* pos;
364 while ((pos = strstr(result, placeholder)) != NULL) {
365 size_t prefix_len = pos - result;
366 size_t suffix_len = strlen(pos + placeholder_len);
367 new_len = prefix_len + value_len + suffix_len + 1;
368
369 char* new_result = realloc(result, new_len);
370 if (!new_result) {
371 free(result);
372 return;
373 }
374 result = new_result;
375 pos = result + prefix_len;
376
377 memmove(pos + value_len, pos + placeholder_len, suffix_len + 1);
378 memcpy(pos, value, value_len);
379 }
380
381 free(*str);
382 *str = result;
383}
392 printf("Reading %s\n", CONFIG_FILE);
393 FILE* file = fopen(CONFIG_FILE, "r");
394 if (file == NULL) {
395 cleanup("Failed to open config file");
396 }
397
398 char line[100];
399 while (fgets(line, sizeof(line), file) != NULL) {
400 line[strcspn(line, "\r\n")] = '\0';
401 char* sep = strchr(line, ':');
402 if (!sep) continue;
403
404 *sep = '\0';
405 char* key = line;
406 char* value = sep + 1;
407
408 while (*value == ' ') value++;
409
410 printf("%s:%s\n", key, value);
411 consoleUpdate(NULL);
412
413 if (strcmp(key, "node") == 0) {
414 set_dynamic_string(&config->node, value);
415 }
416 else if (strcmp(key, "port") == 0) {
417 config->port = atoi(value);
418 }
419 else if (strcmp(key, "wallet_address") == 0) {
420 if (strlen(value) < 1)
421 cleanup("wallet_address not set");
422 set_dynamic_string(&config->wallet_address, value);
423 }
424 else if (strcmp(key, "miner_key") == 0) {
425 set_dynamic_string(&config->miner_key, value);
426 }
427 else if (strcmp(key, "difficulty") == 0) {
428 if (strlen(value) < 1)
429 cleanup("difficulty not set");
430 set_dynamic_string(&config->difficulty, value);
431 }
432 else if (strcmp(key, "rig_id") == 0) {
433 if (strlen(value) < 1)
434 cleanup("rig_id not set");
435 set_dynamic_string(&config->rig_id, value);
436 }
437 else if (strcmp(key, "cpu_boost") == 0) {
438 if (strlen(value) < 1)
439 cleanup("cpu_boost not set");
440 config->cpu_boost = (strcmp(value, "true") == 0) ? true : false;
441 }
442 else if (strcmp(key, "iot") == 0) {
443 if (strlen(value) < 1)
444 cleanup("iot not set");
445 config->iot = (strcmp(value, "true") == 0) ? true : false;
446 }
447 else if (strcmp(key, "threads") == 0) {
448 config->threads = atoi(value);
449 if (config->threads < 1 || config->threads > 6)
450 cleanup("threads value out of range");
451 }
452 else if (strcmp(key, "web_dashboard") == 0) {
453 if (strlen(value) < 1)
454 cleanup("web_dashboard not set");
455 config->web_dashboard = (strcmp(value, "true") == 0) ? true : false;
456 }
457 }
458 fclose(file);
459 printf(CONSOLE_ESC(80;1H)"File parsing completed");
460}
461
462
469void get_node(char** ip, int* port, char** name) {
470 printf(CONSOLE_ESC(2J));
471 printf(CONSOLE_ESC(1;1H) "Finding a node from master server...");
472 consoleUpdate(NULL);
473 sleep(2);
474
475 CURL* curl;
476 CURLcode res;
477
478 char* json_copy = NULL;
479 struct MemoryStruct chunk;
480 chunk.memory = safe_malloc(1); // start with empty buffer
481 chunk.size = 0;
482
483 curl_global_init(CURL_GLOBAL_DEFAULT);
484 curl = curl_easy_init();
485
486 if (!curl) {
487 cleanup("curl init failed");
488 }
489
490 curl_easy_setopt(curl, CURLOPT_URL, GET_POOL);
491 curl_easy_setopt(curl, CURLOPT_USERAGENT, "libnx curl nxducominer");
492 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
493 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&chunk);
494
495 res = curl_easy_perform(curl);
496 if (res != CURLE_OK) {
497 cleanup("curl failed");
498 }
499
500 curl_easy_cleanup(curl);
501 json_copy = safe_strdup(chunk.memory);
502 if (!json_copy) {
503 curl_easy_cleanup(curl);
504 free(chunk.memory);
505 cleanup("curl stdrup failed");
506 }
507
508 jsmn_parser parser;
509 jsmntok_t tokens[13];
510 jsmn_init(&parser);
511
512 int ret = jsmn_parse(&parser, json_copy, strlen(json_copy), tokens, 13);
513 if (ret < 0) {
514 if(json_copy) free(json_copy);
515 if(chunk.memory) free(chunk.memory);
516 curl_easy_cleanup(curl);
517 cleanup("failed to parse JSON");
518 }
519
520 for (int i = 1; i < ret; i++) {
521 if (tokens[i].type == JSMN_STRING) {
522 if (strncmp(json_copy + tokens[i].start, "ip", tokens[i].end - tokens[i].start) == 0) {
523 if (i + 1 < ret) {
524 char ip_str[16];
525 int length = tokens[i + 1].end - tokens[i + 1].start;
526 strncpy(ip_str, json_copy + tokens[i + 1].start, length);
527 *ip = safe_malloc(length + 1);
528 strncpy(*ip, json_copy + tokens[i + 1].start, length);
529 (*ip)[length] = '\0';
530 i++;
531 }
532 }
533 else if (strncmp(json_copy + tokens[i].start, "port", tokens[i].end - tokens[i].start) == 0) {
534 if (i + 1 < ret) {
535 char port_str[16];
536 int length = tokens[i + 1].end - tokens[i + 1].start;
537 strncpy(port_str, json_copy + tokens[i + 1].start, length);
538 port_str[length] = '\0';
539 *port = atoi(port_str);
540 i++;
541 }
542 }
543 else if (strncmp(json_copy + tokens[i].start, "name", tokens[i].end - tokens[i].start) == 0) {
544 if (i + 1 < ret) {
545 char name_str[32];
546 int length = tokens[i + 1].end - tokens[i + 1].start;
547 strncpy(name_str, json_copy + tokens[i + 1].start, length);
548 *name = safe_malloc(length + 1);
549 strncpy(*name, json_copy + tokens[i + 1].start, length);
550 (*name)[length] = '\0';
551 i++;
552 }
553 }
554 }
555 }
556
557 printf(CONSOLE_ESC(3;1H)"Using %s (%s:%i)...", mc.name, mc.node, mc.port);
558 consoleUpdate(NULL);
559 sleep(2);
560
561 if(chunk.memory) free(chunk.memory);
562 if(json_copy) free(json_copy);
563}
564
570void* do_mining_work(void* arg) {
571 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
572 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
573
574 ThreadData* td = (ThreadData*)arg;
575 char recv_buf[1024];
576
577 while (!td->stop_mining) {
578 reconnect:
579 pthread_testcancel();
580 int s = socket(AF_INET, SOCK_STREAM, 0);
581 if (s < 0) {
582 char errbuf[256];
583 snprintf(errbuf, sizeof(errbuf), "Socket creation failed: %s", strerror(errno));
584 td->error = safe_strdup(errbuf);
585 td->socket_fd = -1;
586 sleep(1);
587 continue;
588 }
589 else {
590 td->socket_fd = s;
591 }
592
593 struct hostent* server = gethostbyname(mc.node);
594 if (!server) {
595 td->error = safe_strdup("No such host");
596 close(td->socket_fd);
597 sleep(1);
598 continue;
599 }
600
601 struct sockaddr_in serv_addr;
602 memset(&serv_addr, 0, sizeof(serv_addr));
603 serv_addr.sin_family = AF_INET;
604 serv_addr.sin_port = htons(mc.port);
605 memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
606
607 if (connect(td->socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
608 td->error = safe_strdup("Failed to connect to host");
609 close(td->socket_fd);
610 sleep(1);
611 continue;
612 }
613 read(td->socket_fd, recv_buf, sizeof(4));
614
615 td->error = NULL; //if we got to this point, errors from above are no longer applicable
616 while (!td->stop_mining) {
617 pthread_testcancel();
618
619 // request job
620 char job_request[256];
621 if (mc.iot && td->thread_id == 0) { //only send iot information on one thread
622 char iot[64];
623 snprintf(iot, sizeof(iot), "Charge:%u%%@Temp:%.2f*C", res.charge, res.skin_temp_milli_c / 1000.0f);
624 snprintf(job_request, sizeof(job_request),
625 "JOB,%s,%s,%s,%s",
627 }
628 else {
629 snprintf(job_request, sizeof(job_request),
630 "JOB,%s,%s,%s",
632 }
633
634 int write_job = safe_write(td->socket_fd, job_request, strlen(job_request));
635 if (write_job <= 0) goto reconnect; //failed to send job request
636
637 // receive job
638 memset(recv_buf, 0, 1024);
639 int read_job = read(td->socket_fd, recv_buf, 1024 - 1);
640 if (read_job <= 0) goto reconnect; //failed to recieve job
641
642 // split job parts
643 char* job_parts[3];
644 char* saveptr;
645 char* token = strtok_r(recv_buf, ",", &saveptr);
646 for (int i = 0; i < 3 && token; i++) {
647 job_parts[i] = token;
648 token = strtok_r(NULL, ",", &saveptr);
649 }
650
651 int difficulty = atoi(job_parts[2]);
652 char base_str[128];
653 char expected_hash[41];
654 strcpy(base_str, job_parts[0]);
655 strcpy(expected_hash, job_parts[1]);
656
657 // initialize sha1 context
658 Sha1Context base_ctx;
659 Sha1Context temp_ctx;
660 sha1ContextCreate(&base_ctx);
661 sha1ContextUpdate(&base_ctx, (const unsigned char*)base_str, strlen(base_str));
662
663 time_t start_time = time(NULL);
664 char result_hash[41];
665 int nonce;
666
667 for (nonce = 0; nonce <= (100 * difficulty + 1); nonce++) {
668 pthread_testcancel();
669
670 unsigned char hash[20];
671 char result_str[16];
672
673 temp_ctx = base_ctx; // copy base_ctx to temp_ctx
674 int len = sprintf(result_str, "%d", nonce);
675 sha1ContextUpdate(&temp_ctx, (const unsigned char*)result_str, len);
676 sha1ContextGetHash(&temp_ctx, hash);
677
678 // compare hash
679 for (int i = 0; i < 20; i++) {
680 snprintf(result_hash + (i * 2), 3, "%02x", hash[i]);
681 }
682
683 if (memcmp(result_hash, expected_hash, 20) == 0) {
684 double elapsed = difftime(time(NULL), start_time);
685 double hashrate = nonce / (elapsed > 0 ? elapsed : 1);
686
687 // send result
688 char submit_buf[128];
689 int len = snprintf(submit_buf, sizeof(submit_buf), "%d,%.2f,%s,%s,,%i",
690 nonce, hashrate, SOFTWARE, mc.rig_id, res.single_miner_id);
691 int write_result = safe_write(td->socket_fd, submit_buf, len);
692 if (write_result <= 0) goto reconnect; //failed to send job result
693 // read response
694 int read_result = read(td->socket_fd, recv_buf, 1024 - 1);
695 if(read_result <= 0) goto reconnect; //failed to recieve job result feedback
696
697 if (strncmp(recv_buf, "GOOD", 4) == 0) {
698 td->good_shares++;
699 }
700 else if (strncmp(recv_buf, "BLOCK", 5) == 0) {
701 td->blocks++;
702 }
703 else {
704 td->bad_shares++;
705 }
706
707 res.last_share = nonce;
708 td->difficulty = difficulty;
709 td->hashrate = hashrate / 1000.0f;
710 td->total_shares++;
711
712 break;
713 }
714 td->error = NULL;
715 }
716 }
717 return NULL;
718 }
719 return NULL;
720}
721
727void* web_dashboard(void* arg) {
728 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
729 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
730
731 struct sockaddr_in address = { 0 };
732 int addrlen = sizeof(address);
733
734 if ((res.web_dashboard->server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == 0) {
735 cleanup("web dashboard socket failed.");
736 }
737
738 int opt = 1;
739 setsockopt(res.web_dashboard->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
740
741 address.sin_family = AF_INET;
742 address.sin_addr.s_addr = INADDR_ANY;
743 address.sin_port = htons(8080);
744
745 if (bind(res.web_dashboard->server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
746 cleanup("failed to bind");
747 }
748
749 if (listen(res.web_dashboard->server_fd, 3) < 0) {
750 cleanup("web dashboard socket failed to listen");
751 }
752
753 while (1) {
754 pthread_testcancel();
755
756 res.web_dashboard->client_fd = accept(res.web_dashboard->server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
757 if (res.web_dashboard->client_fd < 0) {
758 if (errno == EWOULDBLOCK || errno == EAGAIN) {
759 svcSleepThread(1000000);
760 continue;
761 }
762 else {
763 break;
764 }
765 }
766
767 char* template = safe_strdup(html);
768 if (!template) return NULL;
769
770 char threads_buf[12];
771 char hashrate_buf[64];
772 char diff_buf[64];
773 char shares_buf[64];
774 char sensors_buf[48];
775
776
777 snprintf(hashrate_buf, sizeof(hashrate_buf), "%.2f", mr.total_hashrate);
778 snprintf(diff_buf, sizeof(diff_buf), "%d", (int)mr.avg_difficulty);
779 snprintf(shares_buf, sizeof(shares_buf), "%d", mr.total_shares);
780 snprintf(sensors_buf, sizeof(sensors_buf), "Temperature: %.2f*C Battery charge: %d%%", res.skin_temp_milli_c / 1000.0f, res.charge);
781 snprintf(threads_buf, sizeof(threads_buf), "%i", mc.threads);
782
783 replace_placeholder(&template, "@@DEVICE@@", "Nintendo Switch");
784 replace_placeholder(&template, "@@HASHRATE@@", hashrate_buf);
785 replace_placeholder(&template, "@@DIFF@@", diff_buf);
786 replace_placeholder(&template, "@@SHARES@@", shares_buf);
787 replace_placeholder(&template, "@@NODE@@", mc.node);
788 replace_placeholder(&template, "@@ID@@", mc.rig_id);
789 replace_placeholder(&template, "@@VERSION@@", APP_VERSION);
790 replace_placeholder(&template, "@@SENSOR@@", sensors_buf);
791 replace_placeholder(&template, "@@THREADS@@", threads_buf);
792
793 send(res.web_dashboard->client_fd, template, strlen(template), 0);
794 free(template);
796 svcSleepThread(1000000);
797 }
798 return NULL;
799}
800
806int main() {
807 consoleInit(NULL);
808
809 // set up joycons
810 padConfigureInput(1, HidNpadStyleSet_NpadStandard);
811 PadState pad;
812 padInitializeDefault(&pad);
813
814 // prevent sleeping
815 appletSetAutoSleepDisabled(true);
816 appletSetTvPowerStateMatchingMode(AppletTvPowerStateMatchingMode_Unknown1);
817
818 socketInitializeDefault();
819
820 //for nxlink server
821 nxlinkStdio();
822
823 // parse config
825 consoleUpdate(NULL);
826 sleep(1);
827
828 // find a node if node not set
829 if (mc.node == NULL || mc.port == 0) {
830 get_node(&mc.node, &mc.port, &mc.name);
831 }
832
833 // toggle CPU boost
834 if (mc.cpu_boost) {
835 appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad);
836 }
837
838 // init temperature
839 Result tcrc = tcInitialize();
840 if (R_FAILED(tcrc)) {
841 cleanup("failed to initialize tc");
842 }
843
844 // init battery management
845 Result psmrc = psmInitialize();
846 if (R_FAILED(psmrc)) {
847 cleanup("failed to initialize psm");
848 }
849
850 // allocate thread data
851 res.mining_threads = safe_malloc(mc.threads * sizeof(pthread_t));
853 srand(time(NULL));
854 res.single_miner_id = rand() % 2812;
855
857 cleanup("memory allocation failed");
858 }
859
860 //create web dashboard
861 if (mc.web_dashboard) {
862 res.web_dashboard = malloc(sizeof(WebDashboard));
863 if (pthread_create(&res.web_dashboard->wd_thread, NULL, web_dashboard, (void*)res.web_dashboard) != 0) {
864 cleanup("failed to start web dashboard thread");
865 }
866 }
867
868
869 // create mining threads
870 for (int i = 0; i < mc.threads; i++) {
872
873 if (pthread_create(&res.mining_threads[i], NULL, do_mining_work, (void*)&res.thread_data[i]) != 0) {
874 cleanup("failed to start mining thread");
875 }
876 sleep(1); // stagger thread creation
877 }
878
879 char timebuf[16];
880 time_t last_draw = 0;
881 while (appletMainLoop()) {
882
883 //handle joycon input
884 padUpdate(&pad);
885 u64 kDown = padGetButtonsDown(&pad);
886
887 if (kDown & HidNpadButton_Plus) {
888 cleanup(NULL);
889 break;
890 }
891
892 time_t current_time;
893 time(&current_time);
894 //draw display once every two seconds
895 if (difftime(current_time, last_draw) >= 2) {
896 get_time_string(timebuf, sizeof(timebuf));
897
898 //charging behavior
899 PsmChargerType chargeType;
900 psmrc = psmGetChargerType(&chargeType);
902
903 //charge percentage
904 psmrc = psmGetBatteryChargePercentage(&res.charge);
905
906 //temp
907 tcrc = tcGetSkinTemperatureMilliC(&res.skin_temp_milli_c);
908
909 mr.total_shares = 0;
910 mr.total_hashrate = 0;
912 mr.good_shares = 0;
913 mr.bad_shares = 0;
914 mr.blocks = 0;
915 for (int i = 0; i < mc.threads; i++) {
922 }
924
925 printf(CONSOLE_ESC(2J)); // clear screen
926
927 if (mc.name != NULL && strlen(mc.name) > 0) {
928 printf(CONSOLE_ESC(1;1H) "Node: %s", mc.name);
929 }
930 else {
931 printf(CONSOLE_ESC(1;1H) "Node: %s:%i", mc.node, mc.port);
932 }
933
934
935 printf(CONSOLE_ESC(2;1H) "Current Time: %s", timebuf);
936
937 //row 3 lb
938
939 if (res.charge > 25) {
940 printf(CONSOLE_ESC(4;1H) "Battery Level: %u%% (%s)", res.charge, res.chargeType);
941 }
942 else {
943 printf(CONSOLE_ESC(4;1H) ERROR_RED "Battery Level: %u%% " RESET " (%s)" , res.charge, res.chargeType);
944 }
945
946 if (res.skin_temp_milli_c / 1000.0f > 55.0f) {
947 printf(CONSOLE_ESC(5;1H) ERROR_RED "Temperature: %.2f C" RESET, res.skin_temp_milli_c / 1000.0f);
948 }
949 else {
950 printf(CONSOLE_ESC(5;1H) "Temperature: %.2f C", res.skin_temp_milli_c / 1000.0f);
951 }
952 // row 6 lb
953 printf(CONSOLE_ESC(7;1H) "Rig ID: %s", mc.rig_id);
954 //row 8 lb
955 printf(CONSOLE_ESC(9;1H) "Hashrate: %.2f kH/s %s", mr.total_hashrate, mc.cpu_boost ? "(CPU Boosted)" : "");
956 printf(CONSOLE_ESC(10;1H) "Difficulty: %d", (int)mr.avg_difficulty);
957 // row 11 lb
958 printf(CONSOLE_ESC(12;1H) "Shares");
959 printf(CONSOLE_ESC(13;1H) LIGHT_GREY "|_ " RESET "Last share: %i", res.last_share);
960 printf(CONSOLE_ESC(14;1H) LIGHT_GREY "|_ " RESET "Total: %i", mr.total_shares);
961 printf(CONSOLE_ESC(15;1H) LIGHT_GREY "|_ " RESET "Accepted: %i", mr.good_shares);
962 printf(CONSOLE_ESC(16;1H) LIGHT_GREY "|_ " RESET "Rejected: %i", mr.bad_shares);
963 printf(CONSOLE_ESC(17;1H) LIGHT_GREY "|_ " RESET "Accepted %i/%i Rejected (%d%% Accepted)",
964 mr.good_shares, mr.bad_shares, (int)((double)mr.good_shares / mr.total_shares * 100));
965 printf(CONSOLE_ESC(18;1H) LIGHT_GREY "|_ " RESET "Blocks Found: %i", mr.blocks);
966 //row 19 lb
967 //thread info - removed alternate formatting for singlethreaded.
968 printf(CONSOLE_ESC(20;1H) "Threads (%i)", mc.threads);
969 int startLine = 21;
970 for (int i = 0; i < mc.threads; i++) {
971 if (res.thread_data[i].error) {
972 printf(CONSOLE_ESC_NSTR("%d;1H") "%i" LIGHT_GREY "|_ " ERROR_RED "ERROR: %s", startLine + (i * 4), i, res.thread_data[i].error);
973 printf(CONSOLE_ESC_NSTR("%d;1H") LIGHT_GREY " |_ " ERROR_RED "Difficulty: %i", startLine + (i * 4) + 1, res.thread_data[i].difficulty);
974 printf(CONSOLE_ESC_NSTR("%d;1H") LIGHT_GREY " |_ " ERROR_RED "Accepted %i/%i Rejected", startLine + (i * 4) + 2,
976 printf(RESET);
977 }
978 else {
979 printf(CONSOLE_ESC_NSTR("%d;1H") "%i" LIGHT_GREY "|_ " RESET "Hashrate: %.2f kH/s", startLine + (i * 4), i, res.thread_data[i].hashrate);
980 printf(CONSOLE_ESC_NSTR("%d;1H") LIGHT_GREY " |_ " RESET "Difficulty: %i", startLine + (i * 4) + 1, res.thread_data[i].difficulty);
981 printf(CONSOLE_ESC_NSTR("%d;1H") LIGHT_GREY " |_ " RESET "Accepted %i/%i Rejected", startLine + (i * 4) + 2,
983 }
984 }
985
986 //logo
987 printf(DUCO_ORANGE);
988 printf(CONSOLE_ESC(1;53H) " ######## ");
989 printf(CONSOLE_ESC(2;53H) " ############### ");
990 printf(CONSOLE_ESC(3;53H) " ################### ");
991 printf(CONSOLE_ESC(4;53H) " #### ######## ");
992 printf(CONSOLE_ESC(5;53H) " ############ ####### ");
993 printf(CONSOLE_ESC(6;53H) " ###### ### ###### ");
994 printf(CONSOLE_ESC(7;53H) " ########### ## ###### ");
995 printf(CONSOLE_ESC(8;53H) " ########### ## ###### ");
996 printf(CONSOLE_ESC(9;53H) " ###### ### ###### ");
997 printf(CONSOLE_ESC(10;53H) " ############ ####### ");
998 printf(CONSOLE_ESC(11;53H) " #### ######## ");
999 printf(CONSOLE_ESC(12;53H) " ################### ");
1000 printf(CONSOLE_ESC(13;53H) " ############### ");
1001 printf(CONSOLE_ESC(14;53H) " ####### ");
1002 printf(RESET);
1003 printf(CONSOLE_ESC(15;52H) "github.com/tbwcjw/nxducominer");
1004
1005 //version string
1006 printf(CONSOLE_ESC(80;67H) DARK_GREY "%s" RESET, APP_VERSION);
1007
1008 //exit helper
1009 printf(CONSOLE_ESC(80;1H) NOTICE_BLUE "Press [+] to exit..." RESET);
1010
1011
1012 last_draw = current_time;
1013
1014 consoleUpdate(NULL);
1015 }
1016 svcSleepThread(1000000);
1017 }
1018}
1019
const char * html
Definition dashboard.h:4
MiningConfig mc
Definition main.c:137
WebDashboard web
Definition main.c:87
MiningResults mr
Definition main.c:51
ResourceManager res
Definition main.c:108
const char * get_psm_charger_type(PsmChargerType type)
string representation of charger type
Definition main.c:236
char * safe_strdup(const char *src)
safe strdup
Definition main.c:278
void get_time_string(char *buffer, int size)
get current time as formatted string
Definition main.c:251
void set_dynamic_string(char **field, const char *value)
set dynamic string field with memory management
Definition main.c:291
void * safe_malloc(size_t size)
safe malloc with zero-initialization
Definition main.c:263
static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp)
CURL write callback for memory buffer.
Definition main.c:326
void replace_placeholder(char **str, const char *placeholder, const char *value)
replace all occurrences of placeholder in string (html) with value
Definition main.c:352
ssize_t safe_write(int fd, const char *buf, size_t len)
safe socket write with retry
Definition main.c:306
#define GET_POOL
pool server url
Definition main.c:21
#define DUCO_ORANGE
duinocoin branding https://github.com/revoxhere/duino-coin/tree/useful-tools#branding
Definition main.c:24
#define CONFIG_FILE
config file/path
Definition main.c:19
#define CONSOLE_ESC_NSTR(fmt)
console escape helper, does not stringify.
Definition main.c:31
#define DARK_GREY
Definition main.c:27
void * web_dashboard(void *arg)
web dashboard server thread
Definition main.c:727
#define NOTICE_BLUE
Definition main.c:26
void * do_mining_work(void *arg)
mining thread worker function
Definition main.c:570
void get_node(char **ip, int *port, char **name)
get mining node from DuinoCoin server
Definition main.c:469
#define RESET
Definition main.c:29
#define LIGHT_GREY
Definition main.c:28
void cleanup(char *msg)
clean up resources and exit
Definition main.c:165
#define SOFTWARE
software identifier
Definition main.c:20
void parse_config_file(MiningConfig *config)
parse configuration file
Definition main.c:391
#define ERROR_RED
Definition main.c:25
int main()
entry point
Definition main.c:806
memory buffer for CURL operation
Definition main.c:154
char * memory
ptr to allocated memory
Definition main.c:155
size_t size
size of allocated memory
Definition main.c:156
mining configuration from config file
Definition main.c:123
char * wallet_address
Definition main.c:127
char * rig_id
Definition main.c:130
int threads
Definition main.c:133
char * name
Definition main.c:126
char * difficulty
Definition main.c:129
int port
Definition main.c:125
bool iot
Definition main.c:132
char * node
Definition main.c:124
bool cpu_boost
Definition main.c:131
bool web_dashboard
Definition main.c:134
char * miner_key
Definition main.c:128
aggregated mining results of all threads
Definition main.c:41
float total_hashrate
combined hashrate (kH/s)
Definition main.c:42
int good_shares
shares accepted
Definition main.c:46
int total_difficulty
combined difficulty
Definition main.c:43
int blocks
blocks found
Definition main.c:48
int bad_shares
shares rejected
Definition main.c:47
float avg_difficulty
average difficulty
Definition main.c:44
int total_shares
shares submitted
Definition main.c:45
resource manager for system-wide state
Definition main.c:96
pthread_mutex_t lock
UNUSED.
Definition main.c:101
int last_share
last nonce value submitted
Definition main.c:97
u32 charge
raw charge
Definition main.c:98
pthread_t * mining_threads
array of mining threads
Definition main.c:102
char * chargeType
charger type string
Definition main.c:99
WebDashboard * web_dashboard
Definition main.c:105
int single_miner_id
single id for all threads, to display as a single device in wallet
Definition main.c:104
ThreadData * thread_data
array of thread-specific data
Definition main.c:103
s32 skin_temp_milli_c
temp in milliC
Definition main.c:100
thread-specific mining data and state
Definition main.c:64
int difficulty
Definition main.c:68
char * error
Definition main.c:73
int good_shares
Definition main.c:70
sig_atomic_t stop_mining
Definition main.c:74
int thread_id
Definition main.c:66
int blocks
Definition main.c:72
float hashrate
Definition main.c:67
int socket_fd
Definition main.c:65
int bad_shares
Definition main.c:71
int total_shares
Definition main.c:69
web dashboard server information
Definition main.c:81
int server_fd
Definition main.c:82
pthread_t wd_thread
Definition main.c:84
int client_fd
Definition main.c:83