├── Duco_esp32_oled └── Duco_esp32_oled.ino ├── LICENSE └── README.md /Duco_esp32_oled/Duco_esp32_oled.ino: -------------------------------------------------------------------------------- 1 | const char *wifi_ssid = "C1F3R"; 2 | const char *wifi_password = "314159265"; 3 | const char *username = "CiferTech"; 4 | const char *rig_identifier = "None"; 5 | 6 | #define LED_BUILTIN 2 7 | #define BLINK_SHARE_FOUND 1 8 | #define BLINK_SETUP_COMPLETE 2 9 | #define BLINK_CLIENT_CONNECT 3 10 | #define BLINK_RESET_DEVICE 5 11 | 12 | #define WDT_TIMEOUT 60 13 | 14 | #include "hwcrypto/sha.h" 15 | //#include "sha/sha_parallel_engine.h" 16 | 17 | /* If you would like to use mqtt monitoring uncomment 18 | the ENABLE_MQTT defition line(#define ENABLE_MQTT). 19 | NOTE: enabling MQTT could slightly decrease hashrate */ 20 | // #define ENABLE_MQTT 21 | // Change this to specify MQTT server (ip only no prefixes) 22 | const char *mqtt_server = ""; 23 | // Port mqtt server is listening at (default: 1883) 24 | const int mqtt_port = 1883; 25 | 26 | /* If you're using the ESP32-CAM board or other board 27 | that doesn't support OTA (Over-The-Air programming) 28 | comment the ENABLE_OTA definition line (#define ENABLE_OTA) 29 | NOTE: enabling OTA support could decrease hashrate (up to 40%) */ 30 | // #define ENABLE_OTA 31 | 32 | /* If you don't want to use the Serial interface comment 33 | the ENABLE_SERIAL definition line (#define ENABLE_SERIAL)*/ 34 | #define ENABLE_SERIAL 35 | /****************** END OF MINER CONFIGURATION SECTION ******************/ 36 | 37 | #ifndef ENABLE_SERIAL 38 | #define Serial DummySerial 39 | static class { 40 | public: 41 | void begin(...) {} 42 | void print(...) {} 43 | void println(...) {} 44 | void printf(...) {} 45 | } Serial; 46 | #endif 47 | 48 | #ifndef ENABLE_MQTT 49 | #define PubSubClient DummyPubSubClient 50 | class PubSubClient{ 51 | public: 52 | PubSubClient(Client& client) {} 53 | bool connect(...) { return false; } 54 | bool connected(...) { return true; } 55 | void loop(...) {} 56 | void publish(...) {} 57 | void subscribe(...) {} 58 | void setServer(...) {} 59 | }; 60 | #endif 61 | 62 | // Data Structures 63 | typedef struct TaskData 64 | { 65 | TaskHandle_t handler; 66 | byte taskId; 67 | float hashrate; 68 | unsigned long shares; 69 | unsigned int difficulty; 70 | 71 | } TaskData_t; 72 | 73 | // Include required libraries 74 | 75 | #define ARDUINOJSON_USE_DOUBLE 1 76 | #include 77 | 78 | #include 79 | #include 80 | #include 81 | #include 82 | #define SCREEN_WIDTH 128 83 | #define SCREEN_HEIGHT 32 84 | 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include //Include WDT libary 91 | 92 | #ifdef ENABLE_OTA 93 | #include 94 | #endif 95 | 96 | #ifdef ENABLE_MQTT 97 | #include 98 | #endif 99 | 100 | // Global Definitions 101 | #define NUMBEROFCORES 2 102 | #define MSGDELIMITER ',' 103 | #define MSGNEWLINE '\n' 104 | 105 | #define OLED_RESET 4 106 | #define SCREEN_ADDRESS 0x3C 107 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 108 | 109 | // Handles for additional threads 110 | TaskHandle_t WiFirec; 111 | TaskData_t TaskThreadData[NUMBEROFCORES]; 112 | TaskHandle_t MqttPublishHandle; 113 | SemaphoreHandle_t xMutex; 114 | 115 | // Internal Variables 116 | 117 | uint32_t lastDrawTime; 118 | uint32_t deauths = 0; 119 | 120 | int start; 121 | int wait = 0; 122 | int miners = 0; 123 | float oldb = 0.0; 124 | float userbalance; 125 | float balance; 126 | float ducomadesincesartdaily = 0.0; 127 | float daily = 0; 128 | String ducosmem; 129 | String price; 130 | 131 | String serverName = "https://server.duinocoin.com/users/"; 132 | String serverPrice = "https://server.duinocoin.com/api.json"; 133 | const char* root_ca= \ 134 | "-----BEGIN CERTIFICATE-----\n" \ 135 | "MIIFLTCCBBWgAwIBAgISBLrlR4aCBlmofGjUwxvNkJ9IMA0GCSqGSIb3DQEBCwUA\n" \ 136 | "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD\n" \ 137 | "EwJSMzAeFw0yMTA0MjAxMjAwMThaFw0yMTA3MTkxMjAwMThaMB8xHTAbBgNVBAMT\n" \ 138 | "FHNlcnZlci5kdWlub2NvaW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" \ 139 | "CgKCAQEAv7SZV8V8uikoSOnw9KVa9M3HQsyB6l3QdinCuyZz7sRckOdSrUKtYFK/\n" \ 140 | "g+RmBqWVrwRLgnVmLtUlulHI7/H7Rbz0Exiv0beyjakd+p40O8V7Y1kuhE9z8WT6\n" \ 141 | "gtDd4grLNBMtUkd2Y8MmYFed2SP5OUoIdySdcUuo50NiS2KH5j3DhZB+ZmZn0lXw\n" \ 142 | "E91O2eNgEaryrnDoMftlJAUOSGh3uuCS27y1SXm8IC1Cxkv+OUyjXu9HnZoIX5v0\n" \ 143 | "59cTAFzrSo5tF/XtJlZ7fJU7VC98NaurfWKrF1XWgoKHJcjsFgep1K6PRIb7g5xq\n" \ 144 | "wlWUOFYTlDIYMAocGYJabw6jZrSphQIDAQABo4ICTjCCAkowDgYDVR0PAQH/BAQD\n" \ 145 | "AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA\n" \ 146 | "MB0GA1UdDgQWBBSGBC/8G5lAbUvFVjt4wL7kzsnzDjAfBgNVHSMEGDAWgBQULrMX\n" \ 147 | "t1hWy65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0\n" \ 148 | "dHA6Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVu\n" \ 149 | "Y3Iub3JnLzAfBgNVHREEGDAWghRzZXJ2ZXIuZHVpbm9jb2luLmNvbTBMBgNVHSAE\n" \ 150 | "RTBDMAgGBmeBDAECATA3BgsrBgEEAYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRw\n" \ 151 | "Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCCAQMGCisGAQQB1nkCBAIEgfQEgfEA7wB2\n" \ 152 | "APZclC/RdzAiFFQYCDCUVo7jTRMZM7/fDC8gC8xO8WTjAAABeO9eAZAAAAQDAEcw\n" \ 153 | "RQIgXLdccQKNKjfPXVV1dbVW8LPmCKb80E8FOVBNw6WswN8CIQDerRKSvtgx4PxF\n" \ 154 | "1lOq8sFWwlMvchIkpiELDpmSQ7/6iAB1AFzcQ5L+5qtFRLFemtRW5hA3+9X6R9yh\n" \ 155 | "c5SyXub2xw7KAAABeO9eAYAAAAQDAEYwRAIgAkQ5SNxBduS8ckP7z0wEMMLdcNmf\n" \ 156 | "rR76s3MD8KIG/UQCIB4txuVGP/citMxxQYKFan1H0l4l2dYJuV4IAizWJ5GHMA0G\n" \ 157 | "CSqGSIb3DQEBCwUAA4IBAQCGELdxTgyUsqnCgE6bgOkRyAiVZHcSqNMg17DWw+ek\n" \ 158 | "IqfXFMbfuTFAs3+VRRCZS2BUiCttsmNzdiPIGgZLvyyv20RLDEjg7Kq22mudGg3F\n" \ 159 | "i/koB5DlGWWv9t2Ng/qnM/4+y6kAwpbJ8R1v4P3NpZgGFIccYgP+N41IOMT+OIzT\n" \ 160 | "6PxYyUH9yQx58e0t2FNTRjJSwIPZKfRUdLVIYb4sjXHqvLA9s76SkrUebDlEt16O\n" \ 161 | "vjDNTcK0q6jXGQjjRwjzPPmze36jvzhhGBypU3tXJifciRSl/4766tlvLwszMaOe\n" \ 162 | "yx+m+83OOchAq+N7/QxbXkNhAXrEzIOXOoYmA6QpfndQ\n" \ 163 | "-----END CERTIFICATE-----\n" \ 164 | "-----BEGIN CERTIFICATE-----\n" \ 165 | "MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/\n" \ 166 | "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ 167 | "DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow\n" \ 168 | "MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT\n" \ 169 | "AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs\n" \ 170 | "jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp\n" \ 171 | "Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB\n" \ 172 | "U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7\n" \ 173 | "gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel\n" \ 174 | "/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R\n" \ 175 | "oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E\n" \ 176 | "BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p\n" \ 177 | "ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE\n" \ 178 | "p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE\n" \ 179 | "AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu\n" \ 180 | "Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0\n" \ 181 | "LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf\n" \ 182 | "r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B\n" \ 183 | "AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH\n" \ 184 | "ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8\n" \ 185 | "S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL\n" \ 186 | "qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p\n" \ 187 | "O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw\n" \ 188 | "UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg==\n" \ 189 | "-----END CERTIFICATE-----\n" \ 190 | "-----BEGIN CERTIFICATE-----\n" \ 191 | "MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \ 192 | "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ 193 | "DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \ 194 | "PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \ 195 | "Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \ 196 | "AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \ 197 | "rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \ 198 | "OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \ 199 | "xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \ 200 | "7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \ 201 | "aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \ 202 | "HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \ 203 | "SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \ 204 | "ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \ 205 | "AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \ 206 | "R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \ 207 | "JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \ 208 | "Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \ 209 | "-----END CERTIFICATE-----\n"; 210 | 211 | 212 | const char *get_pool_api[] = {"https://server.duinocoin.com/getPool"}; 213 | const char *miner_version = "Official ESP32 Miner 2.76"; 214 | String pool_name = ""; 215 | String host = ""; 216 | int port = 0; 217 | int walletid = 0; 218 | volatile int wifi_state = 0; 219 | volatile int wifi_prev_state = WL_CONNECTED; 220 | volatile bool ota_state = false; 221 | volatile char chip_id[23]; // DUCO MCU ID 222 | char rigname_auto[23]; // SPACE TO STORE RIG NAME 223 | int mqttUpdateTrigger = 0; 224 | String mqttRigTopic = ""; 225 | 226 | // Wifi and Mqtt Clients 227 | WiFiClient wifiMqttClient; 228 | PubSubClient mqttClient(wifiMqttClient); 229 | 230 | // Util Functions 231 | void blink(uint8_t count, uint8_t pin = LED_BUILTIN) { 232 | uint8_t state = LOW; 233 | 234 | for (int x = 0; x < (count << 1); ++x) { 235 | digitalWrite(pin, state ^= HIGH); 236 | delay(75); 237 | } 238 | } 239 | 240 | // Communication Functions 241 | void UpdatePool() { 242 | String input = ""; 243 | int waitTime = 1; 244 | int poolIndex = 0; 245 | int poolSize = sizeof(get_pool_api) / sizeof(char*); 246 | 247 | while (input == "") { 248 | Serial.println("Fetching pool (" + String(get_pool_api[poolIndex]) + ")... "); 249 | input = httpGetString(get_pool_api[poolIndex]); 250 | poolIndex += 1; 251 | 252 | // Check if pool index needs to roll over 253 | if( poolIndex >= poolSize ){ 254 | Serial.println("Retrying pool list in: " + String(waitTime) + "s"); 255 | poolIndex %= poolSize; 256 | delay(waitTime * 1000); 257 | 258 | // Increase wait time till a maximum of 16 seconds (addresses: Limit connection requests on failure in ESP boards #1041) 259 | waitTime *= 2; 260 | if( waitTime > 16 ) 261 | waitTime = 16; 262 | } 263 | } 264 | 265 | // Setup pool with new input 266 | UpdateHostPort(input); 267 | } 268 | 269 | void UpdateHostPort(String input) { 270 | // Thanks @ricaun for the code 271 | StaticJsonDocument<256> doc; 272 | DeserializationError error = deserializeJson(doc, input); 273 | 274 | if (error) { 275 | Serial.print(F("deserializeJson() failed: ")); 276 | Serial.println(error.f_str()); 277 | return; 278 | } 279 | 280 | const char *name = doc["name"]; 281 | const char *h = doc["ip"]; 282 | int p = doc["port"]; 283 | 284 | host = h; 285 | port = p; 286 | 287 | // Send to MQTT 288 | mqttClient.publish((mqttRigTopic + "pool_name").c_str(), name); 289 | mqttClient.publish((mqttRigTopic + "pool_ip").c_str(), h); 290 | mqttClient.publish((mqttRigTopic + "pool_port").c_str(), String(p).c_str()); 291 | 292 | // Send to Serial 293 | Serial.println("Fetched pool: " + String(name) + " - " + String(host) + ":" + String(port)); 294 | } 295 | 296 | String httpGetString(String URL) { 297 | String payload = ""; 298 | WiFiClientSecure client; 299 | client.setInsecure(); 300 | HTTPClient http; 301 | 302 | if (http.begin(client, URL)) { 303 | int httpCode = http.GET(); 304 | if (httpCode == HTTP_CODE_OK) { 305 | payload = http.getString(); 306 | } else { 307 | Serial.printf("[HTTP] GET... failed, error: %s\n", 308 | http.errorToString(httpCode).c_str()); 309 | } 310 | http.end(); 311 | } 312 | return payload; 313 | } 314 | 315 | void HandleMqttConnection() 316 | { 317 | // Check Connection 318 | if (!mqttClient.connected()) { 319 | // Setup MQTT Client 320 | Serial.println("Connecting to mqtt server: " + String(mqtt_server) + " on port: " + String(mqtt_port)); 321 | mqttClient.setServer(mqtt_server, mqtt_port); 322 | 323 | // Setup Rig Topic 324 | mqttRigTopic = "duinocoin/" + String(rig_identifier) + "/"; 325 | 326 | // Try to connect 327 | if (mqttClient.connect(rig_identifier, (mqttRigTopic + "state").c_str(), 0, true, String(0).c_str())) { 328 | // Connection Succesfull 329 | Serial.println("Succesfully connected to mqtt server"); 330 | 331 | // Output connection info 332 | mqttClient.publish((mqttRigTopic + "ip").c_str(), WiFi.localIP().toString().c_str()); 333 | mqttClient.publish((mqttRigTopic + "name").c_str(), String(rig_identifier).c_str()); 334 | } 335 | else{ 336 | // Connection Failed 337 | Serial.println("Failed to connect to mqtt server"); 338 | } 339 | } 340 | 341 | // Default MQTT Loop 342 | mqttClient.loop(); 343 | } 344 | 345 | void WiFireconnect(void *pvParameters) { 346 | int n = 0; 347 | unsigned long previousMillis = 0; 348 | const long interval = 500; 349 | esp_task_wdt_add(NULL); 350 | for (;;) { 351 | wifi_state = WiFi.status(); 352 | 353 | #ifdef ENABLE_OTA 354 | ArduinoOTA.handle(); 355 | #endif 356 | 357 | if (ota_state) // If OTA is working, reset the watchdog 358 | esp_task_wdt_reset(); 359 | 360 | // check if WiFi status has changed. 361 | if ((wifi_state == WL_CONNECTED) && (wifi_prev_state != WL_CONNECTED)) { 362 | esp_task_wdt_reset(); // Reset watchdog timer 363 | 364 | // Connect to MQTT (will do nothing if MQTT is disabled) 365 | HandleMqttConnection(); 366 | 367 | // Write Data to Serial 368 | Serial.println(F("\nConnected to WiFi!")); 369 | Serial.println(" IP address: " + WiFi.localIP().toString()); 370 | Serial.println(" Rig name: " + String(rig_identifier)); 371 | Serial.println(); 372 | 373 | // Notify Setup Complete 374 | blink(BLINK_SETUP_COMPLETE);// Sucessfull connection with wifi network 375 | 376 | // Update Pool and wait a bit 377 | UpdatePool(); 378 | yield(); 379 | delay(100); 380 | } 381 | 382 | else if ((wifi_state != WL_CONNECTED) && 383 | (wifi_prev_state == WL_CONNECTED)) { 384 | esp_task_wdt_reset(); // Reset watchdog timer 385 | Serial.println(F("\nWiFi disconnected!")); 386 | WiFi.disconnect(); 387 | 388 | Serial.println(F("Scanning for WiFi networks")); 389 | n = WiFi.scanNetworks(false, true); 390 | Serial.println(F("Scan done")); 391 | 392 | if (n == 0) { 393 | Serial.println(F("No networks found. Resetting ESP32.")); 394 | blink(BLINK_RESET_DEVICE); 395 | esp_restart(); 396 | } 397 | else { 398 | Serial.print(n); 399 | Serial.println(F(" networks found")); 400 | for (int i = 0; i < n; ++i) { 401 | // Print wifi_ssid and RSSI for each network found 402 | Serial.print(i + 1); 403 | Serial.print(F(": ")); 404 | Serial.print(WiFi.SSID(i)); 405 | Serial.print(F(" (")); 406 | Serial.print(WiFi.RSSI(i)); 407 | Serial.print(F(")")); 408 | Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " 409 | : "*"); 410 | delay(10); 411 | } 412 | } 413 | 414 | esp_task_wdt_reset(); // Reset watchdog timer 415 | Serial.println(); 416 | Serial.println( 417 | F("Please, check if your WiFi network is on the list and check if " 418 | "it's strong enough (greater than -90).")); 419 | Serial.println("ESP32 will reset itself after " + String(WDT_TIMEOUT) + 420 | " seconds if can't connect to the network"); 421 | 422 | Serial.print("Connecting to: " + String(wifi_ssid)); 423 | WiFi.reconnect(); 424 | } 425 | 426 | else if ((wifi_state == WL_CONNECTED) && 427 | (wifi_prev_state == WL_CONNECTED)) { 428 | esp_task_wdt_reset(); // Reset watchdog timer 429 | delay(1000); 430 | } 431 | 432 | else { 433 | // Don't reset watchdog timer 434 | unsigned long currentMillis = millis(); 435 | if (currentMillis - previousMillis >= interval) { 436 | previousMillis = currentMillis; 437 | Serial.print(F(".")); 438 | } 439 | } 440 | wifi_prev_state = wifi_state; 441 | } 442 | } 443 | 444 | // Miner Code 445 | void TaskMining(void *pvParameters) { 446 | // Setup Thread 447 | esp_task_wdt_add(NULL); // Disable watchdogtimer for this thread 448 | 449 | // Setup thread data 450 | String taskCoreName = "CORE" + String(xPortGetCoreID()); 451 | int taskId = xPortGetCoreID(); 452 | 453 | // Start main thread loop 454 | for ( ;; ) { 455 | // If OTA needs to be preformed reset the task watchdog 456 | if (ota_state) 457 | esp_task_wdt_reset(); 458 | 459 | // Wait for a valid network connection 460 | while (wifi_state != WL_CONNECTED){ 461 | delay(1000); 462 | esp_task_wdt_reset(); 463 | } 464 | 465 | // Wait for server to get pool information 466 | while( port == 0 ){ 467 | Serial.println(String("MinerThread on " + taskCoreName + " waiting for pool")); 468 | delay(1000); 469 | esp_task_wdt_reset(); 470 | } 471 | 472 | // Setup WiFi Client and connection details 473 | Serial.println(String("MinerThread on " + taskCoreName + " connecting to Duino-Coin server...")); 474 | WiFiClient jobClient; 475 | jobClient.setTimeout(1); 476 | jobClient.flush(); 477 | yield(); 478 | 479 | // Start connection to Duino-Coin server 480 | if (!jobClient.connect(host.c_str(), port)) { 481 | Serial.println(String("MinerThread on " + taskCoreName + " failed to connect")); 482 | delay(500); 483 | continue; 484 | } 485 | 486 | // Wait for server connection 487 | Serial.println(String("MinerThread on " + taskCoreName + " is connected")); 488 | while (!jobClient.available()) { 489 | yield(); 490 | if (!jobClient.connected()) break; 491 | delay(10); 492 | } 493 | 494 | // Server sends SERVER_VERSION after connecting 495 | String SERVER_VER = jobClient.readString(); 496 | Serial.println(String("MinerThread on " + taskCoreName + " received server version: " + SERVER_VER)); 497 | blink(BLINK_CLIENT_CONNECT); // Sucessfull connection with the server 498 | 499 | // Define job loop variables 500 | int jobClientBufferSize = 0; 501 | 502 | // Start Job loop 503 | while (jobClient.connected()) { 504 | // Reset watchdog timer before each job 505 | esp_task_wdt_reset(); 506 | 507 | // We are connected and are able to request a job 508 | Serial.println(String("MinerThread on " + taskCoreName + " asking for a new job for user: " + username)); 509 | jobClient.flush(); 510 | jobClient.print("JOB," + String(username) + ",ESP32"); 511 | while (!jobClient.available()) { 512 | if (!jobClient.connected()) break; 513 | delay(10); 514 | } 515 | yield(); 516 | 517 | // Check buffer size is larget than 10 518 | jobClientBufferSize = jobClient.available(); 519 | if (jobClientBufferSize <= 10) { 520 | Serial.println(String("MinerThread on " + taskCoreName + " buffer size is: " + jobClientBufferSize + " (FAILED, requesting new job...)")); 521 | continue; 522 | } 523 | else{ 524 | Serial.println(String("MinerThread on " + taskCoreName + " buffer size is: " + jobClientBufferSize + " (OK)")); 525 | } 526 | 527 | // Read hash, expected hash and difficulty from job description 528 | String previousHash = jobClient.readStringUntil(MSGDELIMITER); 529 | String expectedHash = jobClient.readStringUntil(MSGDELIMITER); 530 | TaskThreadData[taskId].difficulty = jobClient.readStringUntil(MSGNEWLINE).toInt() * 100; 531 | jobClient.flush(); 532 | 533 | // Global Definitions 534 | unsigned int job_size_task_one = 100; 535 | unsigned char *expectedHashBytes = (unsigned char *)malloc(job_size_task_one * sizeof(unsigned char)); 536 | 537 | // Clear expectedHashBytes 538 | memset(expectedHashBytes, 0, job_size_task_one); 539 | size_t expectedHashLength = expectedHash.length() / 2; 540 | 541 | // Convert expected hash to byte array (for easy comparison) 542 | const char *cExpectedHash = expectedHash.c_str(); 543 | for (size_t i = 0, j = 0; j < expectedHashLength; i += 2, j++) 544 | expectedHashBytes[j] = (cExpectedHash[i] % 32 + 9) % 25 * 16 + (cExpectedHash [i + 1] % 32 + 9) % 25; 545 | 546 | // Start measurement 547 | unsigned long startTime = micros(); 548 | byte shaResult[20]; 549 | String hashUnderTest; 550 | unsigned int hashUnderTestLength; 551 | bool ignoreHashrate = false; 552 | 553 | // Try to find the nonce which creates the expected hash 554 | for (unsigned long nonceCalc = 0; nonceCalc <= TaskThreadData[taskId].difficulty; nonceCalc++) { 555 | // Define hash under Test 556 | hashUnderTest = previousHash + String(nonceCalc); 557 | hashUnderTestLength = hashUnderTest.length(); 558 | 559 | // Wait for hash module lock 560 | while( xSemaphoreTake(xMutex, portMAX_DELAY) != pdTRUE ); 561 | 562 | // We are allowed to perform our hash 563 | esp_sha(SHA1, (const unsigned char *)hashUnderTest.c_str(), hashUnderTestLength, shaResult); 564 | 565 | // Release hash module lock 566 | xSemaphoreGive(xMutex); 567 | 568 | // Check if we have found the nonce for the expected hash 569 | if( memcmp( shaResult, expectedHashBytes, sizeof(shaResult) ) == 0 ){ 570 | // Found the nonce submit it to the server 571 | Serial.println(String("MinerThread on " + taskCoreName + " found hash with nonce: " + nonceCalc )); 572 | 573 | // Calculate mining time 574 | float elapsedTime = (micros() - startTime) / 1000.0 / 1000.0; // Total elapsed time in seconds 575 | TaskThreadData[taskId].hashrate = nonceCalc / elapsedTime; 576 | 577 | // Validate connection 578 | if (!jobClient.connected()) { 579 | Serial.println(String("MinerThread on " + taskCoreName + " Lost connection. Trying to reconnect")); 580 | if (!jobClient.connect(host.c_str(), port)) { 581 | Serial.println(String("MinerThread on " + taskCoreName + " connection failed")); 582 | break; 583 | } 584 | Serial.println(String("MinerThread on " + taskCoreName + " Reconnection successful.")); 585 | } 586 | 587 | // Send result to server 588 | jobClient.flush(); 589 | jobClient.print( 590 | String(nonceCalc) + MSGDELIMITER + String(TaskThreadData[taskId].hashrate) + MSGDELIMITER + 591 | String(miner_version) + MSGDELIMITER + String(rig_identifier) + MSGDELIMITER + 592 | "DUCOID" + String((char *)chip_id) + MSGDELIMITER + String(walletid)); 593 | jobClient.flush(); 594 | 595 | // Wait for job result 596 | while (!jobClient.available()) { 597 | if (!jobClient.connected()) { 598 | Serial.println(String("MinerThread on " + taskCoreName + " Lost connection. Didn't receive feedback.")); 599 | break; 600 | } 601 | delay(10); 602 | yield(); 603 | } 604 | delay(50); 605 | yield(); 606 | 607 | // Handle feedback 608 | String feedback = jobClient.readStringUntil(MSGNEWLINE); 609 | jobClient.flush(); 610 | TaskThreadData[taskId].shares++; 611 | blink(BLINK_SHARE_FOUND); 612 | 613 | // Validate Hashrate 614 | if( TaskThreadData[taskId].hashrate < 4000 && !ignoreHashrate){ 615 | // Hashrate is low so restart esp 616 | Serial.println(String("MinerThread on " + taskCoreName + " Low hashrate (" + TaskThreadData[taskId].hashrate + "), Feedback: " + feedback + ". Restarting...")); 617 | jobClient.flush(); 618 | jobClient.stop(); 619 | blink(BLINK_RESET_DEVICE); 620 | esp_restart(); 621 | } 622 | else{ 623 | // Print statistics 624 | Serial.println(String("MinerThread on " + taskCoreName + " Job Feedback: " + feedback + ", Hashrate: " + TaskThreadData[taskId].hashrate + ", shares found: #" + TaskThreadData[taskId].shares)); 625 | } 626 | 627 | // Stop current loop and ask for a new job 628 | break; 629 | } 630 | } 631 | } 632 | 633 | Serial.println(String("MinerThread on " + taskCoreName + " Not connected. Restarting core")); 634 | jobClient.flush(); 635 | jobClient.stop(); 636 | } 637 | } 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | void setup() { 665 | Serial.begin(500000); // Start serial connection 666 | 667 | display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS); 668 | 669 | display.clearDisplay(); 670 | display.display(); 671 | 672 | Serial.println("\n\nDuino-Coin " + String(miner_version)); 673 | 674 | WiFi.mode(WIFI_STA); // Setup ESP in client mode 675 | btStop(); 676 | WiFi.begin(wifi_ssid, wifi_password); // Connect to wifi 677 | 678 | uint64_t chipid = ESP.getEfuseMac(); // Getting chip chip_id 679 | uint16_t chip = 680 | (uint16_t)(chipid >> 32); // Preparing for printing a 64 bit value (it's 681 | // actually 48 bits long) into a char array 682 | snprintf( 683 | (char *)chip_id, 23, "%04X%08X", chip, 684 | (uint32_t)chipid); // Storing the 48 bit chip chip_id into a char array. 685 | walletid = random(0, 2811); 686 | 687 | // Autogenerate ID if required 688 | if( strcmp(rig_identifier, "Auto") == 0 ){ 689 | snprintf(rigname_auto, 23, "ESP32-%04X%08X", chip, (uint32_t)chipid); 690 | rig_identifier = &rigname_auto[0]; 691 | } 692 | 693 | ota_state = false; 694 | 695 | #ifdef ENABLE_OTA 696 | ArduinoOTA 697 | .onStart([]() { 698 | String type; 699 | if (ArduinoOTA.getCommand() == U_FLASH) 700 | type = "sketch"; 701 | else // U_SPIFFS 702 | type = "filesystem"; 703 | 704 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS 705 | // using SPIFFS.end() 706 | Serial.println("Start updating " + type); 707 | ota_state = true; 708 | }) 709 | .onEnd([]() { Serial.println(F("\nEnd")); }) 710 | .onProgress([](unsigned int progress, unsigned int total) { 711 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 712 | esp_task_wdt_reset(); 713 | ota_state = true; 714 | }) 715 | .onError([](ota_error_t error) { 716 | Serial.printf("Error[%u]: ", error); 717 | if (error == OTA_AUTH_ERROR) 718 | Serial.println(F("Auth Failed")); 719 | else if (error == OTA_BEGIN_ERROR) 720 | Serial.println(F("Begin Failed")); 721 | else if (error == OTA_CONNECT_ERROR) 722 | Serial.println(F("Connect Failed")); 723 | else if (error == OTA_RECEIVE_ERROR) 724 | Serial.println(F("Receive Failed")); 725 | else if (error == OTA_END_ERROR) 726 | Serial.println(F("End Failed")); 727 | ota_state = false; 728 | blink(BLINK_RESET_DEVICE); 729 | esp_restart(); 730 | }); 731 | 732 | ArduinoOTA.setHostname(rig_identifier); 733 | ArduinoOTA.begin(); 734 | #endif 735 | 736 | esp_task_wdt_init(WDT_TIMEOUT, true); // Init Watchdog timer 737 | pinMode(LED_BUILTIN, OUTPUT); 738 | 739 | // Determine which cores to use 740 | int wifiCore = 0; 741 | int mqttCore = 0; 742 | if( NUMBEROFCORES >= 2 ) mqttCore = 1; 743 | 744 | // Create Semaphore and main Wifi Monitoring Thread 745 | xMutex = xSemaphoreCreateMutex(); 746 | xTaskCreatePinnedToCore( 747 | WiFireconnect, "WiFirec", 10000, NULL, NUMBEROFCORES + 2, &WiFirec, 748 | mqttCore); // create a task with highest priority and executed on core 0 749 | delay(250); 750 | 751 | // If MQTT is enabled create a sending thread 752 | #ifdef ENABLE_MQTT 753 | Serial.println("Creating mqtt thread on core: " + String(mqttCore)); 754 | xTaskCreatePinnedToCore( 755 | MqttPublishCode, "MqttPublishCode", 10000, NULL, 1, &MqttPublishHandle, 756 | mqttCore); //create a task with lowest priority and executed on core 1 757 | delay(250); 758 | #endif 759 | 760 | // Create Mining Threads 761 | for( int i = 0; i < NUMBEROFCORES; i++ ){ 762 | Serial.println("Creating mining thread on core: " + String(i)); 763 | xTaskCreatePinnedToCore( 764 | TaskMining, String("Task" + String(i)).c_str(), 10000, NULL, 2 + i, &TaskThreadData[NUMBEROFCORES].handler, 765 | i); // create a task with priority 2 (+ core id) and executed on a specific core 766 | delay(250); 767 | } 768 | } 769 | 770 | // ************************************************************ 771 | void MqttPublishCode( void * pvParameters ) { 772 | unsigned long lastWdtReset = 0; 773 | unsigned long wdtResetDelay = 30000; 774 | 775 | for(;;) { 776 | if ((millis() - lastWdtReset) > wdtResetDelay) { 777 | // Reset timers 778 | esp_task_wdt_reset(); 779 | lastWdtReset = millis(); 780 | 781 | // Calculate combined hashrate and average difficulty 782 | float avgDiff = 0.0; 783 | float totHash = 0.0; 784 | unsigned long totShares = 0; 785 | for (int i=0; i 0) { 815 | DynamicJsonDocument doc(9216); 816 | String jsonbalance = http.getString(); 817 | deserializeJson(doc, jsonbalance); 818 | JsonObject datadoc = doc.as(); 819 | String ducos = datadoc["result"]["balance"]["balance"]; 820 | ducosmem = ducos; 821 | userbalance = ducos.toFloat(); 822 | //Serial.println("Ducos: " + String(ducos)); 823 | 824 | // Miners 825 | miners = 0; 826 | for (JsonObject elem : datadoc["result"]["miners"].as()) { 827 | miners++; 828 | } 829 | //Serial.println("Miners: " + String(miners)); 830 | doc.clear(); 831 | } else { 832 | ducosmem = "-"; 833 | Serial.println("Error http get: " + String(httpCode)); 834 | } 835 | http.end(); 836 | 837 | if(wait == 0){ 838 | oldb = userbalance; 839 | } if(wait == 5){ 840 | wait = 0; 841 | calcule_daily(); 842 | } else{ 843 | wait++; 844 | } 845 | 846 | display.clearDisplay(); 847 | display.setTextSize(1); 848 | display.setTextColor(SSD1306_WHITE); 849 | 850 | display.setCursor(6,0); 851 | display.print("Ducos:"); 852 | display.println(ducosmem); 853 | 854 | display.setCursor(6,10); 855 | display.print("Miners:"); 856 | display.println(miners); 857 | 858 | display.setCursor(6,20); 859 | display.print("Daily:"); 860 | display.println(daily); 861 | 862 | display.display(); 863 | } else { 864 | display.clearDisplay(); 865 | 866 | display.setTextSize(1); 867 | display.setTextColor(SSD1306_WHITE); 868 | for (int i=0; i<3 ; i++){ 869 | for (int a=95; a<99; a++){ 870 | display.setCursor(a,16); 871 | display.print(" . "); 872 | delay(500); 873 | } 874 | } 875 | 876 | display.setCursor(29,16); 877 | display.print("CONNECTING"); 878 | 879 | display.display(); 880 | 881 | //WiFi.begin(wifi_ssid, wifi_password); 882 | while (WiFi.status() != WL_CONNECTED) { 883 | delay(500); 884 | } 885 | } 886 | delay(500); 887 | } 888 | 889 | void calcule_daily() { 890 | float ducomadein = userbalance - oldb; 891 | float dayduco = ducomadein * 960; 892 | daily = dayduco * 100 / 100; 893 | int ducomadesincestart = userbalance - balance; 894 | int secondssincestart = millis() - start; 895 | start = millis(); 896 | ducomadesincesartdaily = ((86400/secondssincestart)*ducomadesincestart)*10 / 10; 897 | } 898 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 CiferTech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

DucoMiner Monitor ESP32

5 | 6 |

7 | Mine DuinoCoin and Monitor Details with ESP32 8 |

9 | 10 | 11 | 12 | 13 |

14 | cifertech - DucoMiner-Monitor-ESP32 15 | stars - DucoMiner-Monitor-ESP32 16 | forks - DucoMiner-Monitor-ESP32 17 | 18 |

19 | TWITTER 20 | · 21 | INSTAGRAM 22 | · 23 | YOUTUBE 24 | · 25 | WEBSITE 26 |

27 |
28 | 29 |
30 | 31 | 32 | # :notebook_with_decorative_cover: Table of Contents 33 | 34 | - [About the Project](#star2-about-the-project) 35 | * [Pictures](#camera-Pictures) 36 | * [Features](#dart-features) 37 | - [Getting Started](#toolbox-getting-started) 38 | * [Schematic](#electric_plug-Schematic) 39 | * [Installation](#gear-installation) 40 | - [Usage](#eyes-usage) 41 | - [Contributing](#wave-contributing) 42 | - [License](#warning-license) 43 | - [Contact](#handshake-contact) 44 | 45 | 46 | 47 | 48 | ## :star2: About the Project 49 | A few months ago, we published a tutorial on extracting a currency called DuinoCoin. Now we performed the extraction operation with the help of ESP32 and with OLED, we displays Walt inventory values, number of miners, and various values at the same time as mining. 50 | 51 | 52 | ### :camera: Pictures 53 | 54 |
55 | screenshot 56 |
57 | 58 | 59 | ### :dart: Features 60 | 61 | - Show wallet balance 62 | - Display the number of miners 63 | - Mine at the same time as displaying wallet information 64 | 65 | 66 | ## :toolbox: Getting Started 67 | 68 | We will use ESP32 as a processor. and we will add an OLED displays Walt inventory values, number of miners, and various values at the same time as mining. 69 | 70 | - ESP32 71 | - Oled 0.91 SSD1306 72 | 73 | 74 | ### :electric_plug: Schematic 75 | 76 | We have the pins related to the I2C protocol to communicate between the OLED display and the ESP32 board. In the ESP32 board, we will use pins D21, and D22, and in the OLED display, SCL and SDA pins. We will also use VIN or 5V pins on the ESP32 board for the power supply. 77 | 78 | Make the connections according to the table and schematic below. 79 | 80 | | ESP32| Oled 0.96| 81 | | ---- | -----| 82 | | D22 | SCL | 83 | | D21 | SDA | 84 | | 5v | VDD | 85 | | GND | GND | 86 | 87 | 88 | * Complete Schematic 89 | 90 | screenshot 91 | 92 | 93 | 94 | ### :gear: Installation 95 | 96 | Change the following according to your Wi-Fi network information and your Doinocoin account name. 97 | ```c++ 98 | const char *wifi_ssid = "C1F3R"; 99 | const char *wifi_password = "314159265"; 100 | const char *username = "CiferTech"; 101 | ``` 102 | 103 | This is the case for your ESP32 chip version. If you have a serious problem uploading the code, comment on the first line and remove the second line from the comment. 104 | ```c++ 105 | #include "hwcrypto/sha.h" 106 | //#include "sha/sha_parallel_engine.h" 107 | ``` 108 | 109 | 110 | 111 | 112 | ## :eyes: Usage 113 | 114 | Finally, after changing the information mentioned in the code, establishing connections and finally uploading the code, the values in the wallet, the number of miners and the estimated amount of mines based on the number of miners and HASH RATE are displayed in the OLED display, values after a short period They are updated according to the time of mining your board and changing the received information. 115 | 116 | 117 | ## :wave: Contributing 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | ## :warning: License 126 | 127 | Distributed under the MIT License. See LICENSE.txt for more information. 128 | 129 | 130 | 131 | ## :handshake: Contact 132 | 133 | CiferTech - [@twitter](https://twitter.com/cifertech1) - CiferTech@gmali.com 134 | 135 | Project Link: [https://github.com/cifertech/DucoMiner-Monitor-ESP32](https://github.com/cifertech/DucoMiner-Monitor-ESP32) 136 | 137 | 138 | ## :gem: Acknowledgements 139 | 140 | Special Thanks to ... 141 | 142 | - [ponsato](https://github.com/ponsato) 143 | 144 | 145 | --------------------------------------------------------------------------------