├── LICENSE
├── README.md
└── Duco_esp32_oled
└── Duco_esp32_oled.ino
/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 |

4 |
DucoMiner Monitor ESP32
5 |
6 |
7 | Mine DuinoCoin and Monitor Details with ESP32
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |

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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------