Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
revoxhere
GitHub Repository: revoxhere/duino-coin
Path: blob/master/AVR_Miner.py
922 views
1
#!/usr/bin/env python3
2
"""
3
Duino-Coin Official AVR Miner 4.3 © MIT licensed
4
https://duinocoin.com
5
https://github.com/revoxhere/duino-coin
6
Duino-Coin Team & Community 2019-2025
7
"""
8
9
from os import _exit, mkdir
10
from os import name as osname
11
from os import path
12
from os import system as ossystem
13
from platform import machine as osprocessor
14
from platform import system
15
import sys
16
17
from configparser import ConfigParser
18
from pathlib import Path
19
20
from json import load as jsonload
21
from random import choice
22
from locale import LC_ALL, getdefaultlocale, getlocale, setlocale
23
import zipfile
24
25
from re import sub
26
from socket import socket
27
from datetime import datetime
28
from statistics import mean
29
from signal import SIGINT, signal
30
from collections import deque
31
from time import ctime, sleep, strptime, time
32
import pip
33
34
from subprocess import DEVNULL, Popen, check_call, call
35
from threading import Thread, Lock
36
import base64 as b64
37
import os
38
39
printlock = Lock()
40
41
42
# Python <3.5 check
43
f"Your Python version is too old. Duino-Coin Miner requires version 3.6 or above. Update your packages and try again"
44
45
46
def install(package):
47
try:
48
pip.main(["install", package])
49
except AttributeError:
50
check_call([sys.executable, '-m', 'pip', 'install', package])
51
call([sys.executable, __file__])
52
53
54
try:
55
from serial import Serial
56
import serial.tools.list_ports
57
except ModuleNotFoundError:
58
print("Pyserial is not installed. "
59
+ "Miner will try to automatically install it "
60
+ "If it fails, please manually execute "
61
+ "python3 -m pip install pyserial")
62
install('pyserial')
63
64
try:
65
import requests
66
except ModuleNotFoundError:
67
print("Requests is not installed. "
68
+ "Miner will try to automatically install it "
69
+ "If it fails, please manually execute "
70
+ "python3 -m pip install requests")
71
install('requests')
72
73
try:
74
from colorama import Back, Fore, Style, init
75
init(autoreset=True)
76
except ModuleNotFoundError:
77
print("Colorama is not installed. "
78
+ "Miner will try to automatically install it "
79
+ "If it fails, please manually execute "
80
+ "python3 -m pip install colorama")
81
install("colorama")
82
83
try:
84
from pypresence import Presence
85
except ModuleNotFoundError:
86
print("Pypresence is not installed. "
87
+ "Miner will try to automatically install it "
88
+ "If it fails, please manually execute "
89
+ "python3 -m pip install pypresence")
90
install("pypresence")
91
92
try:
93
import psutil
94
except ModuleNotFoundError:
95
print("Psutil is not installed. "
96
+ "Miner will try to automatically install it "
97
+ "If it fails, please manually execute "
98
+ "python3 -m pip install psutil")
99
install("psutil")
100
101
def now():
102
return datetime.now()
103
104
105
def port_num(com):
106
return str(''.join(filter(str.isdigit, com)))
107
108
109
class Settings:
110
VER = '4.3'
111
SOC_TIMEOUT = 10
112
REPORT_TIME = 300
113
AVR_TIMEOUT = 10
114
BAUDRATE = 115200
115
DATA_DIR = "Duino-Coin AVR Miner " + str(VER)
116
SEPARATOR = ","
117
ENCODING = "utf-8"
118
TEMP_FOLDER = "Temp"
119
disable_title = False
120
121
try:
122
# Raspberry Pi latin users can't display this character
123
"‖".encode(sys.stdout.encoding)
124
BLOCK = " ‖ "
125
except:
126
BLOCK = " | "
127
PICK = ""
128
COG = " @"
129
if (osname != "nt"
130
or bool(osname == "nt"
131
and os.environ.get("WT_SESSION"))):
132
# Windows' cmd does not support emojis, shame!
133
# And some codecs same, for example the Latin-1 encoding don`t support emoji
134
try:
135
"⛏ ⚙".encode(sys.stdout.encoding) # if the terminal support emoji
136
PICK = " ⛏"
137
COG = " ⚙"
138
except UnicodeEncodeError: # else
139
PICK = ""
140
COG = " @"
141
142
def check_updates():
143
"""
144
Function that checks if the miner is updated.
145
Downloads the new version and restarts the miner.
146
"""
147
try:
148
data = requests.get(
149
"https://api.github.com/repos/revoxhere/duino-coin/releases/latest"
150
).json()
151
152
zip_file = "Duino-Coin_" + data["tag_name"] + "_linux.zip"
153
if sys.platform == "win32":
154
zip_file = "Duino-Coin_" + data["tag_name"] + "_windows.zip"
155
156
process = psutil.Process(os.getpid())
157
running_script = False # If the process is from script
158
if "python" in process.name():
159
running_script = True
160
161
if float(Settings.VER) < float(data["tag_name"]): # If is outdated
162
update = input(Style.BRIGHT + get_string("new_version"))
163
if update == "Y" or update == "y":
164
pretty_print("sys0", get_string("updating"), "warning")
165
166
DATA_DIR = "Duino-Coin AVR Miner " + str(data["tag_name"]) # Create new version config folder
167
if not path.exists(DATA_DIR):
168
mkdir(DATA_DIR)
169
170
try:
171
config.read(str(Settings.DATA_DIR) + '/Settings.cfg') # read the previous config
172
173
config["AVR Miner"] = {
174
'username': config["AVR Miner"]['username'],
175
'avrport': config["AVR Miner"]['avrport'],
176
'donate': int(config["AVR Miner"]['donate']),
177
'language': config["AVR Miner"]['language'],
178
'identifier': config["AVR Miner"]['identifier'],
179
'debug': config["AVR Miner"]['debug'],
180
"soc_timeout": int(config["AVR Miner"]["soc_timeout"]),
181
"avr_timeout": float(config["AVR Miner"]["avr_timeout"]),
182
"discord_presence": config["AVR Miner"]["discord_presence"],
183
"periodic_report": int(config["AVR Miner"]["periodic_report"]),
184
"mining_key": config["AVR Miner"]["mining_key"]
185
}
186
187
with open(str(DATA_DIR) # save it on the new version folder
188
+ '/Settings.cfg', 'w') as configfile:
189
config.write(configfile)
190
191
pretty_print("sys0", Style.RESET_ALL + get_string('config_saved'), "success")
192
except Exception as e:
193
pretty_print("sys0", f"Error saving configfile: {e}" + str(e), "error")
194
pretty_print("sys0", "Config won't be carried to the next version", "warning")
195
196
if not os.path.exists(Settings.TEMP_FOLDER): # Make the Temp folder
197
os.makedirs(Settings.TEMP_FOLDER)
198
199
file_path = os.path.join(Settings.TEMP_FOLDER, zip_file)
200
download_url = "https://github.com/revoxhere/duino-coin/releases/download/" + data["tag_name"] + "/" + zip_file
201
202
if running_script:
203
file_path = os.path.join(".", "AVR_Miner_"+data["tag_name"]+".py")
204
download_url = "https://raw.githubusercontent.com/revoxhere/duino-coin/master/AVR_Miner.py"
205
206
r = requests.get(download_url, stream=True)
207
if r.ok:
208
start = time()
209
dl = 0
210
file_size = int(r.headers["Content-Length"]) # Get file size
211
pretty_print("sys0",
212
f"Saving update to: {os.path.abspath(file_path)}", "warning")
213
with open(file_path, 'wb') as f:
214
for chunk in r.iter_content(chunk_size=1024 * 8): # Download file in chunks
215
if chunk:
216
dl += len(chunk)
217
done = int(50 * dl / file_size)
218
dl_perc = str(int(100 * dl / file_size))
219
220
if running_script:
221
done = int(12.5 * dl / file_size)
222
dl_perc = str(int(22.5 * dl / file_size))
223
224
sys.stdout.write(
225
"\r%s [%s%s] %s %s" % (
226
dl_perc + "%",
227
'#' * done,
228
' ' * (50-done),
229
str(round(os.path.getsize(file_path) / 1024 / 1024, 2)) + " MB ",
230
str((dl // (time() - start)) // 1024) + " KB/s")) # ProgressBar
231
sys.stdout.flush()
232
f.write(chunk)
233
f.flush()
234
os.fsync(f.fileno())
235
pretty_print("sys0", "Download complete", "success")
236
if not running_script:
237
pretty_print("sys0", "Unpacking archive", "warning")
238
with zipfile.ZipFile(file_path, 'r') as zip_ref: # Unzip the file
239
for file in zip_ref.infolist():
240
if "AVR_Miner" in file.filename:
241
if sys.platform == "win32":
242
file.filename = "AVR_Miner_"+data["tag_name"]+".exe" # Rename the file
243
else:
244
file.filename = "AVR_Miner_"+data["tag_name"]
245
zip_ref.extract(file, ".")
246
pretty_print("sys0", "Unpacking complete", "success")
247
os.remove(file_path) # Delete the zip file
248
os.rmdir(Settings.TEMP_FOLDER) # Delete the temp folder
249
250
if sys.platform == "win32":
251
os.startfile(os.getcwd() + "\\AVR_Miner_"+data["tag_name"]+".exe") # Start the miner
252
else: # os.startfile is only for windows
253
os.system(os.getcwd() + "/AVR_Miner_"+data["tag_name"])
254
else:
255
if sys.platform == "win32":
256
os.system(file_path)
257
else:
258
os.system("python3 " + file_path)
259
sys.exit() # Exit the program
260
else: # HTTP status code 4XX/5XX
261
pretty_print( "sys0", f"Update failed: {r.status_code}: {r.text}", "error")
262
else:
263
pretty_print("sys0", "Update aborted", "warning")
264
except Exception as e:
265
print(e)
266
sys.exit()
267
268
269
def has_mining_key(username):
270
response = requests.get(
271
"https://server.duinocoin.com/mining_key"
272
+ "?u=" + username,
273
timeout=10
274
).json()
275
return response["has_key"]
276
277
278
def check_mining_key(user_settings):
279
user_settings = user_settings["AVR Miner"]
280
281
if user_settings["mining_key"] != "None":
282
key = "&k=" + b64.b64decode(user_settings["mining_key"]).decode('utf-8')
283
else:
284
key = ''
285
286
response = requests.get(
287
"https://server.duinocoin.com/mining_key"
288
+ "?u=" + user_settings["username"]
289
+ key,
290
timeout=10
291
).json()
292
debug_output(response)
293
294
if response["success"] and not response["has_key"]: # if the user doesn't have a mining key
295
user_settings["mining_key"] = "None"
296
config["AVR Miner"] = user_settings
297
298
with open(Settings.DATA_DIR + '/Settings.cfg',
299
"w") as configfile:
300
config.write(configfile)
301
print("sys0",
302
Style.RESET_ALL + get_string("config_saved"),
303
"info")
304
return
305
306
if not response["success"]:
307
if response["message"] == "Too many requests":
308
debug_output("Skipping mining key check - getting 429")
309
return
310
311
if user_settings["mining_key"] == "None":
312
pretty_print(
313
"sys0",
314
get_string("mining_key_required"),
315
"warning")
316
317
mining_key = input("Enter your mining key: ")
318
user_settings["mining_key"] = b64.b64encode(mining_key.encode("utf-8")).decode('utf-8')
319
config["AVR Miner"] = user_settings
320
321
with open(Settings.DATA_DIR + '/Settings.cfg',
322
"w") as configfile:
323
config.write(configfile)
324
print("sys0",
325
Style.RESET_ALL + get_string("config_saved"),
326
"info")
327
check_mining_key(config)
328
else:
329
pretty_print(
330
"sys0",
331
get_string("invalid_mining_key"),
332
"error")
333
334
retry = input("Do you want to retry? (y/n): ")
335
if retry == "y" or retry == "Y":
336
mining_key = input("Enter your mining key: ")
337
user_settings["mining_key"] = b64.b64encode(mining_key.encode("utf-8")).decode('utf-8')
338
config["AVR Miner"] = user_settings
339
340
with open(Settings.DATA_DIR + '/Settings.cfg',
341
"w") as configfile:
342
config.write(configfile)
343
print("sys0",
344
Style.RESET_ALL + get_string("config_saved"),
345
"info")
346
sleep(1.5)
347
check_mining_key(config)
348
else:
349
return
350
351
352
class Client:
353
"""
354
Class helping to organize socket connections
355
"""
356
def connect(pool: tuple):
357
s = socket()
358
s.settimeout(Settings.SOC_TIMEOUT)
359
s.connect((pool))
360
return s
361
362
def send(s, msg: str):
363
sent = s.sendall(str(msg).encode(Settings.ENCODING))
364
return True
365
366
def recv(s, limit: int = 128):
367
data = s.recv(limit).decode(Settings.ENCODING).rstrip("\n")
368
return data
369
370
def fetch_pool():
371
while True:
372
pretty_print("net0", get_string("connection_search"),
373
"info")
374
try:
375
response = requests.get(
376
"https://server.duinocoin.com/getPool",
377
timeout=10).json()
378
379
if response["success"] == True:
380
pretty_print("net0", get_string("connecting_node")
381
+ response["name"],
382
"info")
383
384
NODE_ADDRESS = response["ip"]
385
NODE_PORT = response["port"]
386
387
return (NODE_ADDRESS, NODE_PORT)
388
389
elif "message" in response:
390
pretty_print(f"Warning: {response['message']}"
391
+ ", retrying in 15s", "warning", "net0")
392
sleep(15)
393
else:
394
raise Exception(
395
"no response - IP ban or connection error")
396
except Exception as e:
397
if "Expecting value" in str(e):
398
pretty_print("net0", get_string("node_picker_unavailable")
399
+ f"15s {Style.RESET_ALL}({e})",
400
"warning")
401
else:
402
pretty_print("net0", get_string("node_picker_error")
403
+ f"15s {Style.RESET_ALL}({e})",
404
"error")
405
sleep(15)
406
407
408
class Donate:
409
def load(donation_level):
410
if donation_level > 0:
411
if osname == 'nt':
412
if not Path(
413
f"{Settings.DATA_DIR}/Donate.exe").is_file():
414
url = ('https://server.duinocoin.com/'
415
+ 'donations/DonateExecutableWindows.exe')
416
r = requests.get(url, timeout=15)
417
with open(f"{Settings.DATA_DIR}/Donate.exe",
418
'wb') as f:
419
f.write(r.content)
420
elif osname == "posix":
421
if osprocessor() == "aarch64":
422
url = ('https://server.duinocoin.com/'
423
+ 'donations/DonateExecutableAARCH64')
424
elif osprocessor() == "armv7l":
425
url = ('https://server.duinocoin.com/'
426
+ 'donations/DonateExecutableAARCH32')
427
else:
428
url = ('https://server.duinocoin.com/'
429
+ 'donations/DonateExecutableLinux')
430
if not Path(
431
f"{Settings.DATA_DIR}/Donate").is_file():
432
r = requests.get(url, timeout=15)
433
with open(f"{Settings.DATA_DIR}/Donate",
434
"wb") as f:
435
f.write(r.content)
436
437
def start(donation_level):
438
donation_settings = requests.get(
439
"https://server.duinocoin.com/donations/settings.json").json()
440
441
if os.name == 'nt':
442
cmd = (f'cd "{Settings.DATA_DIR}" & Donate.exe '
443
+ f'-o {donation_settings["url"]} '
444
+ f'-u {donation_settings["user"]} '
445
+ f'-p {donation_settings["pwd"]} '
446
+ f'-s 4 -e {donation_level*2}')
447
elif os.name == 'posix':
448
cmd = (f'cd "{Settings.DATA_DIR}" && chmod +x Donate '
449
+ '&& nice -20 ./Donate '
450
+ f'-o {donation_settings["url"]} '
451
+ f'-u {donation_settings["user"]} '
452
+ f'-p {donation_settings["pwd"]} '
453
+ f'-s 4 -e {donation_level*2}')
454
455
if donation_level <= 0:
456
pretty_print(
457
'sys0', Fore.YELLOW
458
+ get_string('free_network_warning').lstrip()
459
+ get_string('donate_warning').replace("\n", "\n\t\t")
460
+ Fore.GREEN + 'https://duinocoin.com/donate'
461
+ Fore.YELLOW + get_string('learn_more_donate'),
462
'warning')
463
sleep(5)
464
465
if donation_level > 0:
466
debug_output(get_string('starting_donation'))
467
donateExecutable = Popen(cmd, shell=True, stderr=DEVNULL)
468
pretty_print('sys0',
469
get_string('thanks_donation').replace("\n", "\n\t\t"),
470
'error')
471
472
473
shares = [0, 0, 0]
474
hashrate_mean = deque(maxlen=25)
475
ping_mean = deque(maxlen=25)
476
diff = 0
477
donator_running = False
478
job = ''
479
debug = 'n'
480
discord_presence = 'y'
481
rig_identifier = 'None'
482
donation_level = 0
483
hashrate = 0
484
config = ConfigParser()
485
mining_start_time = time()
486
487
if not path.exists(Settings.DATA_DIR):
488
mkdir(Settings.DATA_DIR)
489
490
if not Path(Settings.DATA_DIR + '/Translations.json').is_file():
491
url = ('https://raw.githubusercontent.com/'
492
+ 'revoxhere/'
493
+ 'duino-coin/master/Resources/'
494
+ 'AVR_Miner_langs.json')
495
r = requests.get(url, timeout=5)
496
with open(Settings.DATA_DIR + '/Translations.json', 'wb') as f:
497
f.write(r.content)
498
499
# Load language file
500
with open(Settings.DATA_DIR + '/Translations.json', 'r',
501
encoding='utf8') as lang_file:
502
lang_file = jsonload(lang_file)
503
504
# OS X invalid locale hack
505
if system() == 'Darwin':
506
if getlocale()[0] is None:
507
setlocale(LC_ALL, 'en_US.UTF-8')
508
509
try:
510
if not Path(Settings.DATA_DIR + '/Settings.cfg').is_file():
511
locale = getdefaultlocale()[0]
512
if locale.startswith('es'):
513
lang = 'spanish'
514
elif locale.startswith('sk'):
515
lang = 'slovak'
516
elif locale.startswith('ru'):
517
lang = 'russian'
518
elif locale.startswith('pl'):
519
lang = 'polish'
520
elif locale.startswith('de'):
521
lang = 'german'
522
elif locale.startswith('fr'):
523
lang = 'french'
524
elif locale.startswith('jp'):
525
lang = 'japanese'
526
elif locale.startswith('tr'):
527
lang = 'turkish'
528
elif locale.startswith('it'):
529
lang = 'italian'
530
elif locale.startswith('pt'):
531
lang = 'portuguese'
532
if locale.startswith("zh_TW"):
533
lang = "chinese_Traditional"
534
elif locale.startswith('zh'):
535
lang = 'chinese_simplified'
536
elif locale.startswith('th'):
537
lang = 'thai'
538
elif locale.startswith('az'):
539
lang = 'azerbaijani'
540
elif locale.startswith('nl'):
541
lang = 'dutch'
542
elif locale.startswith('ko'):
543
lang = 'korean'
544
elif locale.startswith("id"):
545
lang = "indonesian"
546
elif locale.startswith("cz"):
547
lang = "czech"
548
elif locale.startswith("fi"):
549
lang = "finnish"
550
else:
551
lang = 'english'
552
else:
553
try:
554
config.read(Settings.DATA_DIR + '/Settings.cfg')
555
lang = config["AVR Miner"]['language']
556
except Exception:
557
lang = 'english'
558
except:
559
lang = 'english'
560
561
562
def get_string(string_name: str):
563
if string_name in lang_file[lang]:
564
return lang_file[lang][string_name]
565
elif string_name in lang_file['english']:
566
return lang_file['english'][string_name]
567
else:
568
return string_name
569
570
571
def get_prefix(symbol: str,
572
val: float,
573
accuracy: int):
574
"""
575
H/s, 1000 => 1 kH/s
576
"""
577
if val >= 1_000_000_000_000: # Really?
578
val = str(round((val / 1_000_000_000_000), accuracy)) + " T"
579
elif val >= 1_000_000_000:
580
val = str(round((val / 1_000_000_000), accuracy)) + " G"
581
elif val >= 1_000_000:
582
val = str(round((val / 1_000_000), accuracy)) + " M"
583
elif val >= 1_000:
584
val = str(round((val / 1_000))) + " k"
585
else:
586
if symbol:
587
val = str(round(val)) + " "
588
else:
589
val = str(round(val))
590
return val + symbol
591
592
593
def debug_output(text: str):
594
if debug == 'y':
595
print(Style.RESET_ALL + Fore.WHITE
596
+ now().strftime(Style.DIM + '%H:%M:%S.%f ')
597
+ Style.NORMAL + f'DEBUG: {text}')
598
599
600
def title(title: str):
601
if not Settings.disable_title:
602
if osname == 'nt':
603
"""
604
Changing the title in Windows' cmd
605
is easy - just use the built-in
606
title command
607
"""
608
ossystem('title ' + title)
609
else:
610
"""
611
Most *nix terminals use
612
this escape sequence to change
613
the console window title
614
"""
615
try:
616
print('\33]0;' + title + '\a', end='')
617
sys.stdout.flush()
618
except Exception as e:
619
debug_output("Error setting title: " +str(e))
620
Settings.disable_title = True
621
622
623
def handler(signal_received, frame):
624
pretty_print(
625
'sys0', get_string('sigint_detected')
626
+ Style.NORMAL + Fore.RESET
627
+ get_string('goodbye'), 'warning')
628
629
_exit(0)
630
631
632
# Enable signal handler
633
signal(SIGINT, handler)
634
635
636
def load_config():
637
global username
638
global donation_level
639
global avrport
640
global hashrate_list
641
global debug
642
global rig_identifier
643
global discord_presence
644
global SOC_TIMEOUT
645
646
if not Path(str(Settings.DATA_DIR) + '/Settings.cfg').is_file():
647
print(
648
Style.BRIGHT + get_string('basic_config_tool')
649
+ Settings.DATA_DIR
650
+ get_string('edit_config_file_warning'))
651
652
print(
653
Style.RESET_ALL + get_string('dont_have_account')
654
+ Fore.YELLOW + get_string('wallet') + Fore.RESET
655
+ get_string('register_warning'))
656
657
correct_username = False
658
while not correct_username:
659
username = input(
660
Style.RESET_ALL + Fore.YELLOW
661
+ get_string('ask_username')
662
+ Fore.RESET + Style.BRIGHT)
663
if not username:
664
username = choice(["revox", "Bilaboz"])
665
666
r = requests.get(f"https://server.duinocoin.com/users/{username}",
667
timeout=Settings.SOC_TIMEOUT).json()
668
correct_username = r["success"]
669
if not correct_username:
670
print(get_string("incorrect_username"))
671
672
mining_key = "None"
673
if has_mining_key(username):
674
mining_key = input(Style.RESET_ALL + Fore.YELLOW
675
+ get_string("ask_mining_key")
676
+ Fore.RESET + Style.BRIGHT)
677
mining_key = b64.b64encode(mining_key.encode("utf-8")).decode('utf-8')
678
679
print(Style.RESET_ALL + Fore.YELLOW
680
+ get_string('ports_message'))
681
portlist = serial.tools.list_ports.comports(include_links=True)
682
683
for port in portlist:
684
print(Style.RESET_ALL
685
+ Style.BRIGHT + Fore.RESET
686
+ ' ' + str(port))
687
print(Style.RESET_ALL + Fore.YELLOW
688
+ get_string('ports_notice'))
689
690
port_names = []
691
for port in portlist:
692
port_names.append(port.device)
693
694
avrport = ''
695
rig_identifier = ''
696
while True:
697
current_port = input(
698
Style.RESET_ALL + Fore.YELLOW
699
+ get_string('ask_avrport')
700
+ Fore.RESET + Style.BRIGHT)
701
702
if current_port in port_names:
703
confirm_identifier = input(
704
Style.RESET_ALL + Fore.YELLOW
705
+ get_string('ask_rig_identifier')
706
+ Fore.RESET + Style.BRIGHT)
707
if confirm_identifier == 'y' or confirm_identifier == 'Y':
708
current_identifier = input(
709
Style.RESET_ALL + Fore.YELLOW
710
+ get_string('ask_rig_name')
711
+ Fore.RESET + Style.BRIGHT)
712
rig_identifier += current_identifier
713
else:
714
rig_identifier += "None"
715
716
avrport += current_port
717
confirmation = input(
718
Style.RESET_ALL + Fore.YELLOW
719
+ get_string('ask_anotherport')
720
+ Fore.RESET + Style.BRIGHT)
721
if confirmation == 'y' or confirmation == 'Y':
722
avrport += ','
723
rig_identifier += ','
724
else:
725
break
726
else:
727
print(Style.RESET_ALL + Fore.RED
728
+ 'Please enter a valid COM port from the list above')
729
730
else:
731
rig_identifier = 'None'
732
733
donation_level = '0'
734
if osname == 'nt' or osname == 'posix':
735
donation_level = input(
736
Style.RESET_ALL + Fore.YELLOW
737
+ get_string('ask_donation_level')
738
+ Fore.RESET + Style.BRIGHT)
739
740
donation_level = sub(r'\D', '', donation_level)
741
if donation_level == '':
742
donation_level = 1
743
if float(donation_level) > int(5):
744
donation_level = 5
745
if float(donation_level) < int(0):
746
donation_level = 0
747
donation_level = int(donation_level)
748
749
config["AVR Miner"] = {
750
'username': username,
751
'avrport': avrport,
752
'donate': donation_level,
753
'language': lang,
754
'identifier': rig_identifier,
755
'debug': 'n',
756
"soc_timeout": 10,
757
"avr_timeout": 10,
758
"discord_presence": "y",
759
"periodic_report": 300,
760
"mining_key": mining_key}
761
762
with open(str(Settings.DATA_DIR)
763
+ '/Settings.cfg', 'w') as configfile:
764
config.write(configfile)
765
766
avrport = avrport.split(',')
767
rig_identifier = rig_identifier.split(',')
768
print(Style.RESET_ALL + get_string('config_saved'))
769
hashrate_list = [0] * len(avrport)
770
771
else:
772
config.read(str(Settings.DATA_DIR) + '/Settings.cfg')
773
username = config["AVR Miner"]['username']
774
avrport = config["AVR Miner"]['avrport']
775
avrport = avrport.replace(" ", "").split(',')
776
donation_level = int(config["AVR Miner"]['donate'])
777
debug = config["AVR Miner"]['debug']
778
rig_identifier = config["AVR Miner"]['identifier'].split(',')
779
Settings.SOC_TIMEOUT = int(config["AVR Miner"]["soc_timeout"])
780
Settings.AVR_TIMEOUT = float(config["AVR Miner"]["avr_timeout"])
781
discord_presence = config["AVR Miner"]["discord_presence"]
782
Settings.REPORT_TIME = int(config["AVR Miner"]["periodic_report"])
783
hashrate_list = [0] * len(avrport)
784
785
786
def greeting():
787
global greeting
788
print(Style.RESET_ALL)
789
790
current_hour = strptime(ctime(time())).tm_hour
791
if current_hour < 12:
792
greeting = get_string('greeting_morning')
793
elif current_hour == 12:
794
greeting = get_string('greeting_noon')
795
elif current_hour > 12 and current_hour < 18:
796
greeting = get_string('greeting_afternoon')
797
elif current_hour >= 18:
798
greeting = get_string('greeting_evening')
799
else:
800
greeting = get_string('greeting_back')
801
802
print(
803
Style.DIM + Fore.MAGENTA
804
+ Settings.BLOCK + Fore.YELLOW
805
+ Style.BRIGHT + get_string('banner')
806
+ Style.RESET_ALL + Fore.MAGENTA
807
+ f' {Settings.VER}' + Fore.RESET
808
+ ' 2019-2025')
809
810
print(
811
Style.DIM + Fore.MAGENTA
812
+ Settings.BLOCK + Style.NORMAL + Fore.MAGENTA
813
+ 'https://github.com/revoxhere/duino-coin')
814
815
if lang != "english":
816
print(
817
Style.DIM + Fore.MAGENTA
818
+ Settings.BLOCK + Style.NORMAL
819
+ Fore.RESET + lang.capitalize()
820
+ " translation: " + Fore.MAGENTA
821
+ get_string("translation_autor"))
822
823
print(
824
Style.DIM + Fore.MAGENTA
825
+ Settings.BLOCK + Style.NORMAL
826
+ Fore.RESET + get_string('avr_on_port')
827
+ Style.BRIGHT + Fore.YELLOW
828
+ ', '.join(avrport))
829
830
if osname == 'nt' or osname == 'posix':
831
print(
832
Style.DIM + Fore.MAGENTA + Settings.BLOCK
833
+ Style.NORMAL + Fore.RESET
834
+ get_string('donation_level') + Style.BRIGHT
835
+ Fore.YELLOW + str(donation_level))
836
837
print(
838
Style.DIM + Fore.MAGENTA
839
+ Settings.BLOCK + Style.NORMAL
840
+ Fore.RESET + get_string('algorithm')
841
+ Style.BRIGHT + Fore.YELLOW
842
+ 'DUCO-S1A ⚙ AVR diff')
843
844
if rig_identifier[0] != "None" or len(rig_identifier) > 1:
845
print(
846
Style.DIM + Fore.MAGENTA
847
+ Settings.BLOCK + Style.NORMAL
848
+ Fore.RESET + get_string('rig_identifier')
849
+ Style.BRIGHT + Fore.YELLOW + ", ".join(rig_identifier))
850
851
print(
852
Style.DIM + Fore.MAGENTA
853
+ Settings.BLOCK + Style.NORMAL
854
+ Fore.RESET + get_string("using_config")
855
+ Style.BRIGHT + Fore.YELLOW
856
+ str(Settings.DATA_DIR + '/Settings.cfg'))
857
858
print(
859
Style.DIM + Fore.MAGENTA
860
+ Settings.BLOCK + Style.NORMAL
861
+ Fore.RESET + str(greeting) + ', '
862
+ Style.BRIGHT + Fore.YELLOW
863
+ str(username) + '!\n')
864
865
866
def init_rich_presence():
867
# Initialize Discord rich presence
868
global RPC
869
try:
870
RPC = Presence(905158274490441808)
871
RPC.connect()
872
Thread(target=update_rich_presence).start()
873
except Exception as e:
874
#print("Error launching Discord RPC thread: " + str(e))
875
pass
876
877
878
def update_rich_presence():
879
startTime = int(time())
880
while True:
881
try:
882
total_hashrate = get_prefix("H/s", sum(hashrate_list), 2)
883
RPC.update(details="Hashrate: " + str(total_hashrate),
884
start=mining_start_time,
885
state=str(shares[0]) + "/"
886
+ str(shares[0] + shares[1])
887
+ " accepted shares",
888
large_image="avrminer",
889
large_text="Duino-Coin, "
890
+ "a coin that can be mined with almost everything"
891
+ ", including AVR boards",
892
buttons=[{"label": "Visit duinocoin.com",
893
"url": "https://duinocoin.com"},
894
{"label": "Join the Discord",
895
"url": "https://discord.gg/k48Ht5y"}])
896
except Exception as e:
897
print("Error updating Discord RPC thread: " + str(e))
898
899
sleep(15)
900
901
902
def pretty_print(sender: str = "sys0",
903
msg: str = None,
904
state: str = "success"):
905
"""
906
Produces nicely formatted CLI output for messages:
907
HH:MM:S |sender| msg
908
"""
909
if sender.startswith("net"):
910
bg_color = Back.BLUE
911
elif sender.startswith("avr"):
912
bg_color = Back.MAGENTA
913
else:
914
bg_color = Back.GREEN
915
916
if state == "success":
917
fg_color = Fore.GREEN
918
elif state == "info":
919
fg_color = Fore.BLUE
920
elif state == "error":
921
fg_color = Fore.RED
922
else:
923
fg_color = Fore.YELLOW
924
925
926
print_queue.append(Fore.RESET + datetime.now().strftime(Style.DIM + "%H:%M:%S ")
927
+ Style.RESET_ALL + Fore.WHITE + bg_color + Style.BRIGHT + f" {sender} "
928
+ Style.RESET_ALL + " " + fg_color + msg.strip())
929
930
931
def share_print(id, type, accept, reject, thread_hashrate,
932
total_hashrate, computetime, diff, ping, reject_cause=None):
933
"""
934
Produces nicely formatted CLI output for shares:
935
HH:MM:S |avrN| ⛏ Accepted 0/0 (100%) ∙ 0.0s ∙ 0 kH/s ⚙ diff 0 k ∙ ping 0ms
936
"""
937
thread_hashrate = get_prefix("H/s", thread_hashrate, 2)
938
total_hashrate = get_prefix("H/s", total_hashrate, 1)
939
940
if type == "accept":
941
share_str = get_string("accepted")
942
fg_color = Fore.GREEN
943
elif type == "block":
944
share_str = get_string("block_found")
945
fg_color = Fore.YELLOW
946
else:
947
share_str = get_string("rejected")
948
if reject_cause:
949
share_str += f"{Style.NORMAL}({reject_cause}) "
950
fg_color = Fore.RED
951
952
print_queue.append(
953
Fore.RESET + datetime.now().strftime(Style.DIM + "%H:%M:%S ")
954
+ Style.RESET_ALL + Fore.WHITE + Style.BRIGHT + Back.MAGENTA
955
+ " avr" + str(id) + " " + Style.RESET_ALL + fg_color
956
+ Settings.PICK + share_str + Fore.RESET
957
+ str(accept) + "/" + str(accept + reject) + Fore.MAGENTA
958
+ " (" + str(round(accept / (accept + reject) * 100)) + "%)"
959
+ Style.NORMAL + Fore.RESET
960
+ " ∙ " + str("%04.1f" % float(computetime)) + "s"
961
+ Style.NORMAL + " ∙ " + Fore.BLUE + Style.BRIGHT
962
+ f"{thread_hashrate}" + Style.DIM
963
+ f" ({total_hashrate} {get_string('hashrate_total')})" + Fore.RESET + Style.NORMAL
964
+ Settings.COG + f" {get_string('diff')} {diff} ∙ " + Fore.CYAN
965
+ f"ping {(int(ping))}ms")
966
967
968
def mine_avr(com, threadid, fastest_pool, thread_rigid):
969
global hashrate, shares
970
start_time = time()
971
report_shares = 0
972
last_report_share = 0
973
while True:
974
shares = [0, 0, 0]
975
while True:
976
try:
977
ser.close()
978
pretty_print('sys' + port_num(com),
979
f"No response from the board. Closed port {com}",
980
'success')
981
sleep(2)
982
except:
983
pass
984
try:
985
ser = Serial(com, baudrate=int(Settings.BAUDRATE),
986
timeout=int(Settings.AVR_TIMEOUT))
987
"""
988
Sleep after opening the port to make
989
sure the board resets properly after
990
receiving the DTR signal
991
"""
992
sleep(2)
993
break
994
except Exception as e:
995
pretty_print(
996
'sys'
997
+ port_num(com),
998
get_string('board_connection_error')
999
+ str(com)
1000
+ get_string('board_connection_error2')
1001
+ Style.NORMAL
1002
+ Fore.RESET
1003
+ f' (avr connection err: {e})',
1004
'error')
1005
sleep(10)
1006
1007
retry_counter = 0
1008
while True:
1009
try:
1010
if retry_counter > 3:
1011
fastest_pool = Client.fetch_pool()
1012
retry_counter = 0
1013
1014
debug_output(f'Connecting to {fastest_pool}')
1015
s = Client.connect(fastest_pool)
1016
server_version = Client.recv(s, 6)
1017
1018
if threadid == 0:
1019
if float(server_version) <= float(Settings.VER):
1020
pretty_print(
1021
'net0', get_string('connected')
1022
+ Style.NORMAL + Fore.RESET
1023
+ get_string('connected_server')
1024
+ str(server_version) + ")",
1025
'success')
1026
else:
1027
pretty_print(
1028
'sys0', f"{get_string('miner_is_outdated')} (v{Settings.VER}) -"
1029
+ get_string('server_is_on_version')
1030
+ server_version + Style.NORMAL
1031
+ Fore.RESET + get_string('update_warning'),
1032
'warning')
1033
sleep(10)
1034
1035
Client.send(s, "MOTD")
1036
motd = Client.recv(s, 1024)
1037
1038
if "\n" in motd:
1039
motd = motd.replace("\n", "\n\t\t")
1040
1041
pretty_print("net" + str(threadid),
1042
get_string("motd") + Fore.RESET
1043
+ Style.NORMAL + str(motd),
1044
"success")
1045
break
1046
except Exception as e:
1047
pretty_print('net0', get_string('connecting_error')
1048
+ Style.NORMAL + f' (connection err: {e})',
1049
'error')
1050
retry_counter += 1
1051
sleep(10)
1052
1053
pretty_print('sys' + port_num(com),
1054
get_string('mining_start') + Style.NORMAL + Fore.RESET
1055
+ get_string('mining_algorithm') + str(com) + ')',
1056
'success')
1057
1058
# Perform a hash test to assign the starting diff
1059
prev_hash = "ba29a15896fd2d792d5c4b60668bf2b9feebc51d"
1060
exp_hash = "d0beba883d7e8cd119ea2b0e09b78f60f29e0968"
1061
exp_result = 50
1062
retries = 0
1063
while retries < 3:
1064
try:
1065
debug_output(com + ': Sending hash test to the board')
1066
ser.write(bytes(str(prev_hash
1067
+ Settings.SEPARATOR
1068
+ exp_hash
1069
+ Settings.SEPARATOR
1070
+ "10"
1071
+ Settings.SEPARATOR),
1072
encoding=Settings.ENCODING))
1073
debug_output(com + ': Reading hash test from the board')
1074
result = ser.read_until(b'\n').decode().strip().split(',')
1075
ser.flush()
1076
1077
if result[0] and result[1]:
1078
_ = int(result[0], 2)
1079
debug_output(com + f': Result: {result[0]}')
1080
else:
1081
raise Exception("No data received from the board")
1082
if int(result[0], 2) != exp_result:
1083
raise Exception(com + f': Incorrect result received!')
1084
1085
computetime = round(int(result[1], 2) / 1000000, 5)
1086
num_res = int(result[0], 2)
1087
hashrate_test = round(num_res / computetime, 2)
1088
break
1089
except Exception as e:
1090
debug_output(str(e))
1091
retries += 1
1092
else:
1093
pretty_print('sys' + port_num(com),
1094
f"Can't start mining on {com}" + Fore.RESET
1095
+ f" - board keeps responding improperly. "
1096
+ "Check if the code has been uploaded correctly "
1097
+ "and your device is supported by Duino-Coin.",
1098
'error')
1099
break
1100
1101
start_diff = "AVR"
1102
if hashrate_test > 1000:
1103
start_diff = "DUE"
1104
elif hashrate_test > 550:
1105
start_diff = "ARM"
1106
elif hashrate_test > 380:
1107
start_diff = "MEGA"
1108
1109
pretty_print('sys' + port_num(com),
1110
get_string('hashrate_test')
1111
+ get_prefix("H/s", hashrate_test, 2)
1112
+ Fore.RESET + Style.BRIGHT
1113
+ get_string('hashrate_test_diff')
1114
+ start_diff)
1115
1116
while True:
1117
try:
1118
if config["AVR Miner"]["mining_key"] != "None":
1119
key = b64.b64decode(config["AVR Miner"]["mining_key"]).decode()
1120
else:
1121
key = config["AVR Miner"]["mining_key"]
1122
1123
debug_output(com + ': Requesting job')
1124
Client.send(s, 'JOB'
1125
+ Settings.SEPARATOR
1126
+ str(username)
1127
+ Settings.SEPARATOR
1128
+ start_diff
1129
+ Settings.SEPARATOR
1130
+ str(key)
1131
)
1132
job = Client.recv(s, 128).split(Settings.SEPARATOR)
1133
debug_output(com + f": Received: {job[0]}")
1134
1135
try:
1136
diff = int(job[2])
1137
except:
1138
pretty_print("sys" + port_num(com),
1139
f" Node message: {job[1]}", "warning")
1140
sleep(3)
1141
except Exception as e:
1142
pretty_print('net' + port_num(com),
1143
get_string('connecting_error')
1144
+ Style.NORMAL + Fore.RESET
1145
+ f' (err handling result: {e})', 'error')
1146
sleep(3)
1147
break
1148
1149
retry_counter = 0
1150
while True:
1151
if retry_counter > 3:
1152
break
1153
1154
try:
1155
debug_output(com + ': Sending job to the board')
1156
ser.write(bytes(str(job[0]
1157
+ Settings.SEPARATOR
1158
+ job[1]
1159
+ Settings.SEPARATOR
1160
+ job[2]
1161
+ Settings.SEPARATOR),
1162
encoding=Settings.ENCODING))
1163
debug_output(com + ': Reading result from the board')
1164
result = ser.read_until(b'\n').decode().strip().split(',')
1165
1166
if result[0] and result[1]:
1167
_ = int(result[0], 2)
1168
debug_output(com + f': Result: {result[0]}')
1169
break
1170
else:
1171
raise Exception("No data received from AVR")
1172
except Exception as e:
1173
debug_output(com + f': Retrying data read: {e}')
1174
ser.flush()
1175
retry_counter += 1
1176
continue
1177
1178
if retry_counter > 3:
1179
break
1180
1181
try:
1182
computetime = round(int(result[1], 2) / 1000000, 5)
1183
num_res = int(result[0], 2)
1184
hashrate_t = round(num_res / computetime, 2)
1185
1186
hashrate_mean.append(hashrate_t)
1187
hashrate = mean(hashrate_mean)
1188
hashrate_list[threadid] = hashrate
1189
total_hashrate = sum(hashrate_list)
1190
except Exception as e:
1191
pretty_print('sys' + port_num(com),
1192
get_string('mining_avr_connection_error')
1193
+ Style.NORMAL + Fore.RESET
1194
+ ' (no response from the board: '
1195
+ f'{e}, please check the connection, '
1196
+ 'port setting or reset the AVR)', 'warning')
1197
break
1198
1199
try:
1200
Client.send(s, str(num_res)
1201
+ Settings.SEPARATOR
1202
+ str(hashrate_t)
1203
+ Settings.SEPARATOR
1204
+ f'Official AVR Miner {Settings.VER}'
1205
+ Settings.SEPARATOR
1206
+ str(thread_rigid)
1207
+ Settings.SEPARATOR
1208
+ str(result[2]))
1209
1210
responsetimetart = now()
1211
feedback = Client.recv(s, 64).split(",")
1212
responsetimestop = now()
1213
1214
time_delta = (responsetimestop -
1215
responsetimetart).microseconds
1216
ping_mean.append(round(time_delta / 1000))
1217
ping = mean(ping_mean)
1218
diff = get_prefix("", int(diff), 0)
1219
debug_output(com + f': retrieved feedback: {" ".join(feedback)}')
1220
except Exception as e:
1221
pretty_print('net' + port_num(com),
1222
get_string('connecting_error')
1223
+ Style.NORMAL + Fore.RESET
1224
+ f' (err handling result: {e})', 'error')
1225
debug_output(com + f': error parsing response: {e}')
1226
sleep(5)
1227
break
1228
1229
if feedback[0] == 'GOOD':
1230
shares[0] += 1
1231
share_print(port_num(com), "accept",
1232
shares[0], shares[1], hashrate, total_hashrate,
1233
computetime, diff, ping)
1234
1235
elif feedback[0] == 'BLOCK':
1236
shares[0] += 1
1237
shares[2] += 1
1238
share_print(port_num(com), "block",
1239
shares[0], shares[1], hashrate, total_hashrate,
1240
computetime, diff, ping)
1241
1242
elif feedback[0] == 'BAD':
1243
shares[1] += 1
1244
share_print(port_num(com), "reject",
1245
shares[0], shares[1], hashrate, total_hashrate,
1246
computetime, diff, ping, feedback[1])
1247
1248
else:
1249
share_print(port_num(com), "reject",
1250
shares[0], shares[1], hashrate, total_hashrate,
1251
computetime, diff, ping, feedback)
1252
1253
if shares[0] % 100 == 0 and shares[0] > 1:
1254
pretty_print("sys0",
1255
f"{get_string('surpassed')} {shares[0]} {get_string('surpassed_shares')}",
1256
"success")
1257
1258
title(get_string('duco_avr_miner') + str(Settings.VER)
1259
+ f') - {shares[0]}/{(shares[0] + shares[1])}'
1260
+ get_string('accepted_shares'))
1261
1262
end_time = time()
1263
elapsed_time = end_time - start_time
1264
if threadid == 0 and elapsed_time >= Settings.REPORT_TIME:
1265
report_shares = shares[0] - last_report_share
1266
uptime = calculate_uptime(mining_start_time)
1267
1268
periodic_report(start_time, end_time, report_shares,
1269
shares[2], hashrate, uptime)
1270
start_time = time()
1271
last_report_share = shares[0]
1272
1273
1274
def periodic_report(start_time, end_time, shares,
1275
blocks, hashrate, uptime):
1276
"""
1277
Displays nicely formated uptime stats
1278
"""
1279
seconds = round(end_time - start_time)
1280
pretty_print("sys0", get_string("periodic_mining_report")
1281
+ Fore.RESET + Style.NORMAL
1282
+ get_string("report_period")
1283
+ str(seconds) + get_string("report_time")
1284
+ get_string("report_body1")
1285
+ str(shares) + get_string("report_body2")
1286
+ str(round(shares/seconds, 1))
1287
+ get_string("report_body3")
1288
+ get_string("report_body7")
1289
+ str(blocks)
1290
+ get_string("report_body4")
1291
+ str(get_prefix("H/s", hashrate, 2))
1292
+ get_string("report_body5")
1293
+ str(int(hashrate*seconds))
1294
+ get_string("report_body6")
1295
+ get_string("total_mining_time")
1296
+ str(uptime) + "\n", "success")
1297
1298
1299
def calculate_uptime(start_time):
1300
uptime = time() - start_time
1301
if uptime >= 7200: # 2 hours, plural
1302
return str(uptime // 3600) + get_string('uptime_hours')
1303
elif uptime >= 3600: # 1 hour, not plural
1304
return str(uptime // 3600) + get_string('uptime_hour')
1305
elif uptime >= 120: # 2 minutes, plural
1306
return str(uptime // 60) + get_string('uptime_minutes')
1307
elif uptime >= 60: # 1 minute, not plural
1308
return str(uptime // 60) + get_string('uptime_minute')
1309
else: # less than 1 minute
1310
return str(round(uptime)) + get_string('uptime_seconds')
1311
1312
1313
print_queue = []
1314
def print_queue_handler():
1315
"""
1316
Prevents broken console logs with many threads
1317
"""
1318
while True:
1319
if len(print_queue):
1320
message = print_queue[0]
1321
with printlock:
1322
print(message)
1323
print_queue.pop(0)
1324
sleep(0.01)
1325
1326
1327
if __name__ == '__main__':
1328
init(autoreset=True)
1329
Thread(target=print_queue_handler).start()
1330
title(f"{get_string('duco_avr_miner')}{str(Settings.VER)})")
1331
1332
if sys.platform == "win32":
1333
os.system('') # Enable VT100 Escape Sequence for WINDOWS 10 Ver. 1607
1334
1335
check_updates()
1336
1337
try:
1338
load_config()
1339
debug_output('Config file loaded')
1340
except Exception as e:
1341
pretty_print(
1342
'sys0', get_string('load_config_error')
1343
+ Settings.DATA_DIR + get_string('load_config_error_warning')
1344
+ Style.NORMAL + Fore.RESET + f' ({e})', 'error')
1345
debug_output(f'Error reading configfile: {e}')
1346
sleep(10)
1347
_exit(1)
1348
1349
try:
1350
greeting()
1351
debug_output('Greeting displayed')
1352
except Exception as e:
1353
debug_output(f'Error displaying greeting message: {e}')
1354
1355
try:
1356
check_mining_key(config)
1357
except Exception as e:
1358
debug_output(f'Error checking miner key: {e}')
1359
1360
if donation_level > 0:
1361
try:
1362
Donate.load(donation_level)
1363
Donate.start(donation_level)
1364
except Exception as e:
1365
debug_output(f'Error launching donation thread: {e}')
1366
1367
try:
1368
fastest_pool = Client.fetch_pool()
1369
threadid = 0
1370
for port in avrport:
1371
Thread(target=mine_avr,
1372
args=(port, threadid,
1373
fastest_pool, rig_identifier[threadid])).start()
1374
threadid += 1
1375
except Exception as e:
1376
debug_output(f'Error launching AVR thread(s): {e}')
1377
1378
if discord_presence == "y":
1379
try:
1380
init_rich_presence()
1381
except Exception as e:
1382
debug_output(f'Error launching Discord RPC thread: {e}')
1383
1384