/* ____ __ __ ____ _ _ _____ ___ _____ ____ _ _ ( _ \( )( )(_ _)( \( )( _ )___ / __)( _ )(_ _)( \( ) )(_) ))(__)( _)(_ ) ( )(_)((___)( (__ )(_)( _)(_ ) ( (____/(______)(____)(_)\_)(_____) \___)(_____)(____)(_)\_) Official code for all ESP8266/32 boards version 4.3 Main .ino file The Duino-Coin Team & Community 2019-2024 © MIT Licensed https://duinocoin.com https://github.com/revoxhere/duino-coin If you don't know where to start, visit official website and navigate to the Getting Started page. Have fun mining! To edit the variables (username, WiFi settings, etc.) use the Settings.h tab! */ /* If optimizations cause problems, change them to -O0 (the default) */ #pragma GCC optimize("-Ofast") /* If during compilation the line below causes a "fatal error: arduinoJson.h: No such file or directory" message to occur; it means that you do NOT have the ArduinoJSON library installed. To install it, go to the below link and follow the instructions: https://github.com/revoxhere/duino-coin/issues/832 */ #include <ArduinoJson.h> #if defined(ESP8266) #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #include <ESP8266HTTPClient.h> #include <ESP8266WebServer.h> #else #include <ESPmDNS.h> #include <WiFi.h> #include <HTTPClient.h> #include <WebServer.h> #include <WiFiClientSecure.h> #endif #include <WiFiUdp.h> #include <ArduinoOTA.h> #include <WiFiClient.h> #include <Ticker.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include "MiningJob.h" #include "Settings.h" #ifdef USE_LAN #include <ETH.h> #endif #if defined(WEB_DASHBOARD) #include "Dashboard.h" #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) #include "DisplayHal.h" #endif #if !defined(ESP8266) && defined(DISABLE_BROWNOUT) #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #endif // Auto adjust physical core count // (ESP32-S2/C3 have 1 core, ESP32 has 2 cores, ESP8266 has 1 core) #if defined(ESP8266) #define CORE 1 typedef ESP8266WebServer WebServer; #elif defined(CONFIG_FREERTOS_UNICORE) #define CORE 1 #else #define CORE 2 // Install TridentTD_EasyFreeRTOS32 if you get an error #include <TridentTD_EasyFreeRTOS32.h> void Task1Code( void * parameter ); void Task2Code( void * parameter ); TaskHandle_t Task1; TaskHandle_t Task2; #endif #if defined(WEB_DASHBOARD) WebServer server(80); #endif #if defined(CAPTIVE_PORTAL) #include <FS.h> // This needs to be first, or it all crashes and burns... #include <WiFiManager.h> #include <Preferences.h> char duco_username[40]; char duco_password[40]; char duco_rigid[24]; WiFiManager wifiManager; Preferences preferences; WiFiManagerParameter custom_duco_username("duco_usr", "Duino-Coin username", duco_username, 40); WiFiManagerParameter custom_duco_password("duco_pwd", "Duino-Coin mining key (if enabled in the wallet)", duco_password, 40); WiFiManagerParameter custom_duco_rigid("duco_rig", "Custom miner identifier (optional)", duco_rigid, 24); void saveConfigCallback() { preferences.begin("duino_config", false); preferences.putString("duco_username", custom_duco_username.getValue()); preferences.putString("duco_password", custom_duco_password.getValue()); preferences.putString("duco_rigid", custom_duco_rigid.getValue()); preferences.end(); RestartESP("Settings saved"); } void reset_settings() { server.send(200, "text/html", "Settings have been erased. Please redo the configuration by connecting to the WiFi network that will be created"); delay(500); wifiManager.resetSettings(); RestartESP("Manual settings reset"); } void saveParamCallback(){ Serial.println("[CALLBACK] saveParamCallback fired"); Serial.println("PARAM customfieldid = " + getParam("customfieldid")); } String getParam(String name){ //read parameter from server, for customhmtl input String value; if(wifiManager.server->hasArg(name)) { value = wifiManager.server->arg(name); } return value; } #endif void RestartESP(String msg) { #if defined(SERIAL_PRINTING) Serial.println(msg); Serial.println("Restarting ESP..."); #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info("Restarting ESP..."); #endif #if defined(ESP8266) ESP.reset(); #else ESP.restart(); abort(); #endif } #if defined(BLUSHYBOX) Ticker blinker; bool lastLedState = false; void changeState() { analogWrite(LED_BUILTIN, lastLedState ? 255 : 0); lastLedState = !lastLedState; } #endif #if defined(ESP8266) // WDT Loop // See lwdtcb() and lwdtFeed() below Ticker lwdTimer; unsigned long lwdCurrentMillis = 0; unsigned long lwdTimeOutMillis = LWD_TIMEOUT; void ICACHE_RAM_ATTR lwdtcb(void) { if ((millis() - lwdCurrentMillis > LWD_TIMEOUT) || (lwdTimeOutMillis - lwdCurrentMillis != LWD_TIMEOUT)) RestartESP("Loop WDT Failed!"); } void lwdtFeed(void) { lwdCurrentMillis = millis(); lwdTimeOutMillis = lwdCurrentMillis + LWD_TIMEOUT; } #else void lwdtFeed(void) { Serial.println("lwdtFeed()"); } #endif namespace { MiningConfig *configuration = new MiningConfig( DUCO_USER, RIG_IDENTIFIER, MINER_KEY ); #if defined(ESP32) && CORE == 2 EasyMutex mutexClientData, mutexConnectToServer; #endif #ifdef USE_LAN static bool eth_connected = false; #endif void UpdateHostPort(String input) { // Thanks @ricaun for the code DynamicJsonDocument doc(256); deserializeJson(doc, input); const char *name = doc["name"]; configuration->host = doc["ip"].as<String>().c_str(); configuration->port = doc["port"].as<int>(); node_id = String(name); #if defined(SERIAL_PRINTING) Serial.println("Poolpicker selected the best mining node: " + node_id); #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info(node_id); #endif } void VerifyWifi() { #ifdef USE_LAN while ((!eth_connected) || (ETH.localIP() == IPAddress(0, 0, 0, 0))) { #if defined(SERIAL_PRINTING) Serial.println("Ethernet connection lost. Reconnect..." ); #endif SetupWifi(); } #else while (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0, 0, 0, 0) || WiFi.localIP() == IPAddress(192, 168, 4, 2) || WiFi.localIP() == IPAddress(192, 168, 4, 3)) { #if defined(SERIAL_PRINTING) Serial.println("WiFi reconnecting..."); #endif WiFi.disconnect(); delay(500); WiFi.reconnect(); delay(500); } #endif } String httpGetString(String URL) { String payload = ""; WiFiClientSecure client; HTTPClient https; client.setInsecure(); https.begin(client, URL); https.addHeader("Accept", "*/*"); int httpCode = https.GET(); #if defined(SERIAL_PRINTING) Serial.printf("HTTP Response code: %d\n", httpCode); #endif if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { payload = https.getString(); } else { #if defined(SERIAL_PRINTING) Serial.printf("Error fetching node from poolpicker: %s\n", https.errorToString(httpCode).c_str()); VerifyWifi(); #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info(https.errorToString(httpCode)); #endif } https.end(); return payload; } void SelectNode() { String input = ""; int waitTime = 1; int poolIndex = 0; while (input == "") { #if defined(SERIAL_PRINTING) Serial.println("Fetching mining node from the poolpicker in " + String(waitTime) + "s"); #endif delay(waitTime * 1000); input = httpGetString("https://server.duinocoin.com/getPool"); // Increase wait time till a maximum of 32 seconds // (addresses: Limit connection requests on failure in ESP boards #1041) waitTime *= 2; if (waitTime > 32) RestartESP("Node fetch unavailable"); } UpdateHostPort(input); } #ifdef USE_LAN void WiFiEvent(WiFiEvent_t event) { switch (event) { case ARDUINO_EVENT_ETH_START: #if defined(SERIAL_PRINTING) Serial.println("ETH Started"); #endif // The hostname must be set after the interface is started, but needs // to be set before DHCP, so set it from the event handler thread. ETH.setHostname("esp32-ethernet"); break; case ARDUINO_EVENT_ETH_CONNECTED: #if defined(SERIAL_PRINTING) Serial.println("ETH Connected"); #endif break; case ARDUINO_EVENT_ETH_GOT_IP: #if defined(SERIAL_PRINTING) Serial.println("ETH Got IP"); #endif eth_connected = true; break; case ARDUINO_EVENT_ETH_DISCONNECTED: #if defined(SERIAL_PRINTING) Serial.println("ETH Disconnected"); #endif eth_connected = false; break; case ARDUINO_EVENT_ETH_STOP: #if defined(SERIAL_PRINTING) Serial.println("ETH Stopped"); #endif eth_connected = false; break; default: break; } } #endif void SetupWifi() { #ifdef USE_LAN #if defined(SERIAL_PRINTING) Serial.println("Connecting to Ethernet..."); #endif WiFi.onEvent(WiFiEvent); // Will call WiFiEvent() from another thread. ETH.begin(); while (!eth_connected) { delay(500); #if defined(SERIAL_PRINTING) Serial.print("."); #endif } #if defined(SERIAL_PRINTING) Serial.println("\n\nSuccessfully connected to Ethernet"); Serial.println("Local IP address: " + ETH.localIP().toString()); Serial.println("Rig name: " + String(RIG_IDENTIFIER)); Serial.println(); #endif #else #if defined(SERIAL_PRINTING) Serial.println("Connecting to: " + String(SSID)); #endif WiFi.begin(SSID, PASSWORD); while(WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(100); } VerifyWifi(); #if !defined(ESP8266) WiFi.config(WiFi.localIP(), WiFi.gatewayIP(), WiFi.subnetMask(), DNS_SERVER); #endif #if defined(SERIAL_PRINTING) Serial.println("\n\nSuccessfully connected to WiFi"); Serial.println("Rig name: " + String(RIG_IDENTIFIER)); Serial.println("Local IP address: " + WiFi.localIP().toString()); Serial.println("Gateway: " + WiFi.gatewayIP().toString()); Serial.println("DNS: " + WiFi.dnsIP().toString()); Serial.println(); #endif #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info("Waiting for node..."); #endif SelectNode(); } void SetupOTA() { // Prepare OTA handler ArduinoOTA.onStart([]() { #if defined(SERIAL_PRINTING) Serial.println("Start"); #endif }); ArduinoOTA.onEnd([]() { #if defined(SERIAL_PRINTING) Serial.println("\nEnd"); #endif }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { #if defined(SERIAL_PRINTING) Serial.printf("Progress: %u%%\r", (progress / (total / 100))); #endif }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); #if defined(SERIAL_PRINTING) if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); #endif }); ArduinoOTA.setHostname(RIG_IDENTIFIER); // Give port a name ArduinoOTA.begin(); } #if defined(WEB_DASHBOARD) void dashboard() { #if defined(SERIAL_PRINTING) Serial.println("Handling HTTP client"); #endif String s = WEBSITE; #ifdef USE_LAN s.replace("@@IP_ADDR@@", ETH.localIP().toString()); #else s.replace("@@IP_ADDR@@", WiFi.localIP().toString()); #endif s.replace("@@HASHRATE@@", String((hashrate+hashrate_core_two) / 1000)); s.replace("@@DIFF@@", String(difficulty / 100)); s.replace("@@SHARES@@", String(share_count)); s.replace("@@NODE@@", String(node_id)); #if defined(ESP8266) s.replace("@@DEVICE@@", "ESP8266"); #elif defined(CONFIG_FREERTOS_UNICORE) s.replace("@@DEVICE@@", "ESP32-S2/C3"); #else s.replace("@@DEVICE@@", "ESP32"); #endif s.replace("@@ID@@", String(RIG_IDENTIFIER)); s.replace("@@MEMORY@@", String(ESP.getFreeHeap())); s.replace("@@VERSION@@", String(SOFTWARE_VERSION)); #if defined(CAPTIVE_PORTAL) s.replace("@@RESET_SETTINGS@@", "• <a href='/reset'>Reset settings</a>"); #else s.replace("@@RESET_SETTINGS@@", ""); #endif #if defined(USE_DS18B20) sensors.requestTemperatures(); float temp = sensors.getTempCByIndex(0); s.replace("@@SENSOR@@", "DS18B20: " + String(temp) + "*C"); #elif defined(USE_DHT) float temp = dht.readTemperature(); float hum = dht.readHumidity(); s.replace("@@SENSOR@@", "DHT11/22: " + String(temp) + "*C, " + String(hum) + "rh%"); #elif defined(USE_HSU07M) float temp = read_hsu07m(); s.replace("@@SENSOR@@", "HSU07M: " + String(temp) + "*C"); #elif defined(USE_INTERNAL_SENSOR) float temp = 0; temp_sensor_read_celsius(&temp); s.replace("@@SENSOR@@", "CPU: " + String(temp) + "*C"); #else s.replace("@@SENSOR@@", "None"); #endif server.send(200, "text/html", s); } #endif } // End of namespace MiningJob *job[CORE]; #if CORE == 2 EasyFreeRTOS32 task1, task2; #endif void task1_func(void *) { #if defined(ESP32) && CORE == 2 VOID SETUP() { } VOID LOOP() { job[0]->mine(); #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) float hashrate_float = (hashrate+hashrate_core_two) / 1000.0; float accept_rate = (accepted_share_count / 0.01 / share_count); long millisecs = millis(); int uptime_secs = int((millisecs / 1000) % 60); int uptime_mins = int((millisecs / (1000 * 60)) % 60); int uptime_hours = int((millisecs / (1000 * 60 * 60)) % 24); String uptime = String(uptime_hours) + "h" + String(uptime_mins) + "m" + String(uptime_secs) + "s"; float sharerate = share_count / (millisecs / 1000.0); display_mining_results(String(hashrate_float, 1), String(accepted_share_count), String(share_count), String(uptime), String(node_id), String(difficulty / 100), String(sharerate, 1), String(ping), String(accept_rate, 1)); #endif } #endif } void task2_func(void *) { #if defined(ESP32) && CORE == 2 VOID SETUP() { job[1] = new MiningJob(1, configuration); } VOID LOOP() { job[1]->mine(); #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) float hashrate_float = (hashrate+hashrate_core_two) / 1000.0; float accept_rate = (accepted_share_count / 0.01 / share_count); long millisecs = millis(); int uptime_secs = int((millisecs / 1000) % 60); int uptime_mins = int((millisecs / (1000 * 60)) % 60); int uptime_hours = int((millisecs / (1000 * 60 * 60)) % 24); String uptime = String(uptime_hours) + "h" + String(uptime_mins) + "m" + String(uptime_secs) + "s"; float sharerate = share_count / (millisecs / 1000.0); display_mining_results(String(hashrate_float, 1), String(accepted_share_count), String(share_count), String(uptime), String(node_id), String(difficulty / 100), String(sharerate, 1), String(ping), String(accept_rate, 1)); #endif } #endif } void setup() { #if !defined(ESP8266) && defined(DISABLE_BROWNOUT) WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); #endif #if defined(SERIAL_PRINTING) Serial.begin(SERIAL_BAUDRATE); Serial.println("\n\nDuino-Coin " + String(configuration->MINER_VER)); #endif pinMode(LED_BUILTIN, OUTPUT); #if defined(BLUSHYBOX) analogWrite(LED_BUILTIN, 255); for (int i = 255; i > 0; i--) { analogWrite(LED_BUILTIN, i); delay(1); } pinMode(GAUGE_PIN, OUTPUT); // Gauge up and down effect on startup for (int i = GAUGE_MIN; i < GAUGE_MAX; i++) { analogWrite(GAUGE_PIN, i); delay(10); } for (int i = GAUGE_MAX; i > GAUGE_MIN; i--) { analogWrite(GAUGE_PIN, i); delay(10); } #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) screen_setup(); display_boot(); delay(2500); #endif assert(CORE == 1 || CORE == 2); WALLET_ID = String(random(0, 2811)); // Needed for miner grouping in the wallet job[0] = new MiningJob(0, configuration); #if defined(USE_DHT) #if defined(SERIAL_PRINTING) Serial.println("Initializing DHT sensor (Duino IoT)"); #endif dht.begin(); #if defined(SERIAL_PRINTING) Serial.println("Test reading: " + String(dht.readHumidity()) + "% humidity"); Serial.println("Test reading: temperature " + String(dht.readTemperature()) + "°C"); #endif #endif #if defined(USE_DS18B20) #if defined(SERIAL_PRINTING) Serial.println("Initializing DS18B20 sensor (Duino IoT)"); #endif sensors.begin(); sensors.requestTemperatures(); #if defined(SERIAL_PRINTING) Serial.println("Test reading: " + String(sensors.getTempCByIndex(0)) + "°C"); #endif #endif #if defined(USE_HSU07M) #if defined(SERIAL_PRINTING) Serial.println("Initializing HSU07M sensor (Duino IoT)"); Serial.println("Test reading: " + String(read_hsu07m()) + "°C"); #endif #endif #if defined(USE_INTERNAL_SENSOR) #if defined(SERIAL_PRINTING) Serial.println("Initializing internal ESP32 temperature sensor (Duino IoT)"); #endif temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT(); temp_sensor.dac_offset = TSENS_DAC_L2; temp_sensor_set_config(temp_sensor); temp_sensor_start(); float result = 0; temp_sensor_read_celsius(&result); #if defined(SERIAL_PRINTING) Serial.println("Test reading: " + String(result) + "°C"); #endif #endif WiFi.mode(WIFI_STA); // Setup ESP in client mode //WiFi.disconnect(true); #if defined(ESP8266) WiFi.setSleepMode(WIFI_NONE_SLEEP); #else WiFi.setSleep(false); #endif #if defined(CAPTIVE_PORTAL) preferences.begin("duino_config", false); strcpy(duco_username, preferences.getString("duco_username", "username").c_str()); strcpy(duco_password, preferences.getString("duco_password", "None").c_str()); strcpy(duco_rigid, preferences.getString("duco_rigid", "None").c_str()); preferences.end(); configuration->DUCO_USER = duco_username; configuration->RIG_IDENTIFIER = duco_rigid; configuration->MINER_KEY = duco_password; RIG_IDENTIFIER = duco_rigid; String captivePortalHTML = R"( <title>Duino BlushyBox</title> <style> body { background-color: #d4bff2; color: #363636; } button { box-shadow: 0px 0px 23px -7px #051700; background-color:#8645ef; border-radius:28px; border:1px solid #8645ef; display:inline-block; cursor:pointer; color:#ffffff; font-family:Arial; font-size:18px; padding:8px 16px; text-decoration:none; } input { box-shadow: 0px 0px 23px -7px #051700; background-color:#ffffff; border-radius:28px; border:1px solid #777777; font-family:Arial; font-size:18px; text-decoration:none; } </style> )"; wifiManager.setCustomHeadElement(captivePortalHTML.c_str()); wifiManager.setSaveConfigCallback(saveConfigCallback); wifiManager.addParameter(&custom_duco_username); wifiManager.addParameter(&custom_duco_password); wifiManager.addParameter(&custom_duco_rigid); #if defined(BLUSHYBOX) blinker.attach_ms(200, changeState); #endif wifiManager.autoConnect("Duino-Coin"); delay(1000); VerifyWifi(); #if defined(BLUSHYBOX) blinker.detach(); #endif #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info("Waiting for node..."); #endif #if defined(BLUSHYBOX) blinker.attach_ms(500, changeState); #endif SelectNode(); #if defined(BLUSHYBOX) blinker.detach(); #endif #else #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) display_info("Waiting for WiFi..."); #endif SetupWifi(); #endif SetupOTA(); #if defined(WEB_DASHBOARD) if (!MDNS.begin(RIG_IDENTIFIER)) { #if defined(SERIAL_PRINTING) Serial.println("mDNS unavailable"); #endif } MDNS.addService("http", "tcp", 80); #if defined(SERIAL_PRINTING) #ifdef USE_LAN Serial.println("Configured mDNS for dashboard on http://" + String(RIG_IDENTIFIER) + ".local (or http://" + ETH.localIP().toString() + ")"); #else Serial.println("Configured mDNS for dashboard on http://" + String(RIG_IDENTIFIER) + ".local (or http://" + WiFi.localIP().toString() + ")"); #endif #endif server.on("/", dashboard); #if defined(CAPTIVE_PORTAL) server.on("/reset", reset_settings); #endif server.begin(); #endif #if defined(ESP8266) // Start the WDT watchdog lwdtFeed(); lwdTimer.attach_ms(LWD_TIMEOUT, lwdtcb); #endif #if defined(ESP8266) // Fastest clock mode for 8266s system_update_cpu_freq(160); os_update_cpu_frequency(160); // Feed the watchdog lwdtFeed(); #else // Fastest clock mode for 32s setCpuFrequencyMhz(240); #endif job[0]->blink(BLINK_SETUP_COMPLETE); #if defined(ESP32) && CORE == 2 mutexClientData = xSemaphoreCreateMutex(); mutexConnectToServer = xSemaphoreCreateMutex(); xTaskCreatePinnedToCore(system_events_func, "system_events_func", 10000, NULL, 1, NULL, 0); xTaskCreatePinnedToCore(task1_func, "task1_func", 10000, NULL, 1, &Task1, 0); xTaskCreatePinnedToCore(task2_func, "task2_func", 10000, NULL, 1, &Task2, 1); #endif } void system_events_func(void* parameter) { while (true) { delay(10); #if defined(WEB_DASHBOARD) server.handleClient(); #endif ArduinoOTA.handle(); } } void single_core_loop() { job[0]->mine(); lwdtFeed(); #if defined(DISPLAY_SSD1306) || defined(DISPLAY_16X2) float hashrate_float = (hashrate+hashrate_core_two) / 1000.0; float accept_rate = (accepted_share_count / 0.01 / share_count); long millisecs = millis(); int uptime_secs = int((millisecs / 1000) % 60); int uptime_mins = int((millisecs / (1000 * 60)) % 60); int uptime_hours = int((millisecs / (1000 * 60 * 60)) % 24); String uptime = String(uptime_hours) + "h" + String(uptime_mins) + "m" + String(uptime_secs) + "s"; float sharerate = share_count / (millisecs / 1000.0); display_mining_results(String(hashrate_float, 1), String(accepted_share_count), String(share_count), String(uptime), String(node_id), String(difficulty / 100), String(sharerate, 1), String(ping), String(accept_rate, 1)); #endif VerifyWifi(); ArduinoOTA.handle(); #if defined(WEB_DASHBOARD) server.handleClient(); #endif } void loop() { #if defined(ESP8266) || defined(CONFIG_FREERTOS_UNICORE) single_core_loop(); #endif delay(10); }