1
|
|
|
'''# Miner Helper functions |
2
|
|
|
# Skylake Software, Inc |
3
|
|
|
#"bitmain-fan-ctrl" : true, |
4
|
|
|
#"bitmain-fan-pwm" : "80", |
5
|
|
|
''' |
6
|
|
|
import time |
7
|
|
|
import datetime |
8
|
|
|
from helpers.minerapi import MinerApi |
9
|
|
|
from helpers.ssh import Ssh |
10
|
|
|
import domain.minerstatistics |
11
|
|
|
from domain.mining import Miner, MinerInfo, MinerCurrentPool, MinerAccessLevel, MinerApiCall |
12
|
|
|
|
13
|
|
|
class MinerMonitorException(Exception): |
14
|
|
|
"""Happens during monitoring of miner""" |
15
|
|
|
def friendlymessage(self): |
16
|
|
|
msg = getattr(self, 'message', repr(self)) |
17
|
|
|
return msg |
18
|
|
|
|
19
|
|
|
def istimedout(self): |
20
|
|
|
return self.friendlymessage().find('timed out') >= 0 |
21
|
|
|
def isconnectionrefused(self): |
22
|
|
|
return self.friendlymessage().find('ConnectionRefusedError') >= 0 |
23
|
|
|
|
24
|
|
|
class MinerCommandException(Exception): |
25
|
|
|
"""Happens during executing miner command""" |
26
|
|
|
|
27
|
|
|
def stats(miner: Miner): |
28
|
|
|
'''returns MinerStatistics, MinerInfo, and MinerApiCall''' |
29
|
|
|
if not miner.can_monitor(): |
30
|
|
|
raise MinerMonitorException('miner {0} cannot be monitored. ip={1} port={2}'.format(miner.name, miner.ipaddress, miner.port)) |
31
|
|
|
|
32
|
|
|
try: |
33
|
|
|
thecall = MinerApiCall(miner) |
34
|
|
|
entity = domain.minerstatistics.MinerStatistics(miner, when=datetime.datetime.utcnow()) |
35
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
36
|
|
|
|
37
|
|
|
thecall.start() |
38
|
|
|
#jstats = api.stats() |
39
|
|
|
stats_and_pools = api.command('stats+pools') |
40
|
|
|
thecall.stop() |
41
|
|
|
if 'stats' in stats_and_pools: |
42
|
|
|
jstats = stats_and_pools['stats'][0] |
43
|
|
|
else: |
44
|
|
|
#if call failed then only one result is returned, so parse it |
45
|
|
|
jstats = stats_and_pools |
46
|
|
|
entity.rawstats = jstats |
47
|
|
|
jstatus = jstats['STATUS'] |
48
|
|
|
if jstatus[0]['STATUS'] == 'error': |
49
|
|
|
if not miner.is_disabled(): |
50
|
|
|
raise MinerMonitorException(jstatus[0]['description']) |
51
|
|
|
else: |
52
|
|
|
miner_software = parse_miner_software(jstats) |
53
|
|
|
if miner_software.startswith('sgminer'): |
54
|
|
|
jstats = stats_and_pools['STATS'] |
55
|
|
|
jsonstats = jstats |
56
|
|
|
status = jstats[0] |
57
|
|
|
jstatus = stats_and_pools['STATUS'] |
58
|
|
|
minerinfo = helpers.antminerhelper.parse_statistics_inno(entity, jsonstats, status) |
|
|
|
|
59
|
|
|
|
60
|
|
|
else: |
61
|
|
|
status = jstats['STATS'][0] |
62
|
|
|
jsonstats = jstats['STATS'][1] |
63
|
|
|
|
64
|
|
|
minerinfo = parse_minerinfo(status) |
65
|
|
|
|
66
|
|
|
#build MinerStatistics from stats |
67
|
|
|
parse_statistics(entity, jsonstats, status) |
68
|
|
|
minerpool = parse_minerpool(miner, stats_and_pools['pools'][0]) |
69
|
|
|
|
70
|
|
|
return entity, minerinfo, thecall, minerpool |
|
|
|
|
71
|
|
|
except BaseException as ex: |
72
|
|
|
print('Failed to call miner stats api: ' + str(ex)) |
73
|
|
|
raise MinerMonitorException(ex) |
74
|
|
|
return None, None, None, None |
75
|
|
|
|
76
|
|
|
def parse_miner_software(jsonstats): |
77
|
|
|
if 'STATUS' in jsonstats: |
78
|
|
|
status = jsonstats['STATUS'] |
79
|
|
|
if len(status) > 0 and 'Description' in status[0]: |
80
|
|
|
return status[0]['Description'] |
81
|
|
|
return 'unknown' |
82
|
|
|
|
83
|
|
|
def parse_statistics_inno(entity, jsonstats, status): |
84
|
|
|
miner_stats = [x for x in jsonstats if 'ID' in x and x['ID'].startswith('HLT')] |
85
|
|
|
|
86
|
|
|
entity.minercount = len(miner_stats) |
87
|
|
|
elapsed =[x['Elapsed'] for x in miner_stats] |
88
|
|
|
entity.elapsed = max(elapsed) |
89
|
|
|
entity.currenthash = sum([int(float(x['MHS av'])) for x in miner_stats]) |
90
|
|
|
entity.hash_avg = sum([int(float(x['MHS av'])) for x in miner_stats]) |
91
|
|
|
entity.hardware_errors = sum([sum({v for (k, v) in y.items() if k.endswith('HW errors')}) for y in miner_stats]) |
92
|
|
|
|
93
|
|
|
#entity.frequency = jsonstats['frequency'] |
94
|
|
|
#entity.frequency = str(int(sum(frequencies.values()) / len(frequencies))) |
95
|
|
|
|
96
|
|
|
controllertemps = [int(float(x['Temp'])) for x in miner_stats] |
97
|
|
|
entity.controllertemp = max(controllertemps) |
98
|
|
|
dict_temps = {'Temp_'+x['ID']: x['Temp'] for x in miner_stats} |
99
|
|
|
parse_board_temps(entity, dict_temps, 'Temp') |
100
|
|
|
#some stats are not ready to parse yet |
101
|
|
|
#parse_fans(entity, jsonstats) |
102
|
|
|
#parse_board_status(entity, jsonstats) |
103
|
|
|
|
104
|
|
|
def parse_statistics(entity, jsonstats, status): |
105
|
|
|
entity.minercount = int(jsonstats['miner_count']) |
106
|
|
|
entity.elapsed = int(jsonstats['Elapsed']) |
107
|
|
|
entity.currenthash = int(float(jsonstats['GHS 5s'])) |
108
|
|
|
entity.hash_avg = int(float(jsonstats['GHS av'])) |
109
|
|
|
if 'Hardware Errors' in jsonstats: |
110
|
|
|
entity.hardware_errors = int(float(jsonstats['Hardware Errors'])) |
111
|
|
|
entity.frequency = jsonstats['frequency'] |
112
|
|
|
|
113
|
|
|
frequencies = {k:v for (k, v) in jsonstats.items() if k.startswith('freq_avg') and v != 0} |
114
|
|
|
entity.frequency = str(int(sum(frequencies.values()) / len(frequencies))) |
115
|
|
|
|
116
|
|
|
controllertemps = {k:v for (k, v) in jsonstats.items() if k in ['temp6', 'temp7', 'temp8']} |
117
|
|
|
entity.controllertemp = max(controllertemps.values()) |
118
|
|
|
parse_board_temps(entity, jsonstats) |
119
|
|
|
parse_fans(entity, jsonstats) |
120
|
|
|
parse_board_status(entity, jsonstats) |
121
|
|
|
|
122
|
|
|
def parse_board_status(entity, jsonstats): |
123
|
|
|
chains = {k:v for (k, v) in jsonstats.items() if k.startswith('chain_acs') and v != ''} |
124
|
|
|
chainkeys = list(chains.keys()) |
125
|
|
|
if len(chains) > 0: |
126
|
|
|
entity.boardstatus1 = chains[chainkeys[0]] |
127
|
|
|
if len(chains) > 1: |
128
|
|
|
entity.boardstatus2 = chains[chainkeys[1]] |
129
|
|
|
if len(chains) > 2: |
130
|
|
|
entity.boardstatus3 = chains[chainkeys[2]] |
131
|
|
|
|
132
|
|
|
def parse_fans(entity, jsonstats): |
133
|
|
|
fans = {k:v for (k, v) in jsonstats.items() if k.startswith('fan') and k != 'fan_num' and v != 0} |
134
|
|
|
fankeys = list(fans.keys()) |
135
|
|
|
if len(fans) > 0: |
136
|
|
|
entity.fan1 = fans[fankeys[0]] |
137
|
|
|
if len(fans) > 1: |
138
|
|
|
entity.fan2 = fans[fankeys[1]] |
139
|
|
|
if len(fans) > 2: |
140
|
|
|
entity.fan3 = fans[fankeys[2]] |
141
|
|
|
|
142
|
|
|
def parse_board_temps(entity, jsonstats, key = 'temp2_'): |
143
|
|
|
#should be 3 |
144
|
|
|
boardtemps = {k:v for (k, v) in jsonstats.items() if k.startswith(key) and v != 0} |
145
|
|
|
boardtempkeys = list(boardtemps.keys()) |
146
|
|
|
if len(boardtemps) > 0: |
147
|
|
|
entity.tempboard1 = boardtemps[boardtempkeys[0]] |
148
|
|
|
if len(boardtemps) > 1: |
149
|
|
|
entity.tempboard2 = boardtemps[boardtempkeys[1]] |
150
|
|
|
if len(boardtemps) > 2: |
151
|
|
|
entity.tempboard3 = boardtemps[boardtempkeys[2]] |
152
|
|
|
|
153
|
|
|
def parse_minerinfo(status): |
154
|
|
|
#build MinerInfo from stats |
155
|
|
|
minerid = 'unknown' |
156
|
|
|
minertype = 'unknown' |
157
|
|
|
if 'Type' in status: |
158
|
|
|
minertype = status['Type'] |
159
|
|
|
else: |
160
|
|
|
if status['Description'].startswith('cgminer'): |
161
|
|
|
minertype = status['Description'] |
162
|
|
|
if 'miner_id' in status: |
163
|
|
|
minerid = status['miner_id'] |
164
|
|
|
minerinfo = MinerInfo(minertype, minerid) |
165
|
|
|
return minerinfo |
166
|
|
|
|
167
|
|
|
def pools(miner: Miner): |
168
|
|
|
'''Gets the current pool for the miner''' |
169
|
|
|
try: |
170
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
171
|
|
|
jstatuspools = api.pools() |
172
|
|
|
if jstatuspools['STATUS'][0]['STATUS'] == 'error': |
173
|
|
|
if not miner.is_disabled(): |
174
|
|
|
raise MinerMonitorException(jstatuspools['STATUS'][0]['description']) |
175
|
|
|
else: |
176
|
|
|
return parse_minerpool(miner, jstatuspools) |
177
|
|
|
except BaseException as ex: |
178
|
|
|
print('Failed to call miner pools api: ' + str(ex)) |
179
|
|
|
return None |
180
|
|
|
|
181
|
|
|
def parse_minerpool(miner, jstatuspools): |
182
|
|
|
def sort_by_priority(j): |
183
|
|
|
return j['Priority'] |
184
|
|
|
|
185
|
|
|
jpools = jstatuspools["POOLS"] |
186
|
|
|
#sort by priority |
187
|
|
|
jpools.sort(key=sort_by_priority) |
188
|
|
|
#try to do elegant way, but not working |
189
|
|
|
#cPool = namedtuple('Pool', 'POOL, URL, Status,Priority,Quota,Getworks,Accepted,Rejected,Long Poll') |
190
|
|
|
#colpools = [cPool(**k) for k in jsonpools["POOLS"]] |
191
|
|
|
#for pool in colpools: |
192
|
|
|
# print(pool.POOL) |
193
|
|
|
currentpool = currentworker = '' |
194
|
|
|
for pool in jpools: |
195
|
|
|
if str(pool["Status"]) == "Alive": |
196
|
|
|
currentpool = pool["URL"] |
197
|
|
|
currentworker = pool["User"] |
198
|
|
|
#print("{0} {1} {2} {3} {4} {5}".format(pool["POOL"],pool["Priority"],pool["URL"],pool["User"],pool["Status"],pool["Stratum Active"])) |
199
|
|
|
break |
200
|
|
|
minerpool = MinerCurrentPool(miner, currentpool, currentworker, jstatuspools) |
201
|
|
|
return minerpool |
202
|
|
|
|
203
|
|
|
def getminerlcd(miner: Miner): |
204
|
|
|
'''gets a summary (quick display values) for the miner''' |
205
|
|
|
try: |
206
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
207
|
|
|
jstatuspools = api.lcd() |
208
|
|
|
return jstatuspools |
209
|
|
|
except BaseException as ex: |
210
|
|
|
return str(ex) |
211
|
|
|
|
212
|
|
|
def setminertoprivileged(miner, login, allowsetting): |
213
|
|
|
try: |
214
|
|
|
mineraccess = privileged(miner) |
215
|
|
|
except MinerMonitorException as ex: |
216
|
|
|
if ex.istimedout(): |
217
|
|
|
mineraccess = MinerAccessLevel.Waiting |
218
|
|
|
if mineraccess == MinerAccessLevel.Restricted or mineraccess == MinerAccessLevel.Waiting: |
219
|
|
|
if mineraccess == MinerAccessLevel.Restricted: |
220
|
|
|
setprivileged(miner, login, allowsetting) |
221
|
|
|
#todo: not ideal to wait in a loop here, need a pattern that will wait in non blocking mode |
222
|
|
|
waitforonline(miner) |
223
|
|
|
mineraccess = privileged(miner) |
224
|
|
|
return mineraccess |
225
|
|
|
|
226
|
|
|
def privileged(miner: Miner): |
227
|
|
|
'''gets status: privileged or restricted''' |
228
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
229
|
|
|
apiresponse = api.privileged() |
230
|
|
|
jstatus = apiresponse["STATUS"][0] |
231
|
|
|
if jstatus is not None and jstatus["STATUS"] == "S": |
232
|
|
|
return MinerAccessLevel.Privileged |
233
|
|
|
return MinerAccessLevel.Restricted |
234
|
|
|
|
235
|
|
|
#restart (*) none Status is a single "RESTART" reply before cgminer restarts |
236
|
|
|
def restart(miner: Miner): |
237
|
|
|
'''restart miner through api''' |
238
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
239
|
|
|
apiresponse = api.restart() |
240
|
|
|
print(apiresponse) |
241
|
|
|
return apiresponse |
242
|
|
|
|
243
|
|
|
def print_connection_data(connection): |
244
|
|
|
if connection.strdata: |
245
|
|
|
print(connection.strdata) # print the last line of received data |
246
|
|
|
print('==========================') |
247
|
|
|
if connection.alldata: |
248
|
|
|
print(connection.alldata) # This contains the complete data received. |
249
|
|
|
print('==========================') |
250
|
|
|
|
251
|
|
|
def print_response(response): |
252
|
|
|
for line in response: |
253
|
|
|
print(line) |
254
|
|
|
|
255
|
|
|
def getportfromminer(miner: Miner): |
256
|
|
|
if isinstance(miner.ftpport, int): |
257
|
|
|
return miner.ftpport |
258
|
|
|
if isinstance(miner.ftpport, str) and miner.ftpport: |
259
|
|
|
tryport = int(miner.ftpport) |
260
|
|
|
if tryport > 0: |
261
|
|
|
return tryport |
262
|
|
|
return 22 |
263
|
|
|
|
264
|
|
|
def getminerconfig(miner: Miner, login): |
265
|
|
|
'''ger the miner config file''' |
266
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
267
|
|
|
config = connection.exec_command('cat /config/{0}.conf'.format(getminerfilename(miner))) |
268
|
|
|
connection.close_connection() |
269
|
|
|
return config |
270
|
|
|
|
271
|
|
|
def stopmining(miner: Miner, login): |
272
|
|
|
miner_shell_command(miner, login, 'restart', 15) |
273
|
|
|
|
274
|
|
|
def restartmining(miner: Miner, login): |
275
|
|
|
miner_shell_command(miner, login, 'restart', 15) |
276
|
|
|
|
277
|
|
|
def miner_shell_command(miner: Miner, login, command, timetorun): |
278
|
|
|
'''send the command stop/restart to miner shell command''' |
279
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
280
|
|
|
connection.open_shell() |
281
|
|
|
connection.send_shell('/etc/init.d/{0}.sh {1}'.format(getminerfilename(miner), command)) |
282
|
|
|
time.sleep(timetorun) |
283
|
|
|
print_connection_data(connection) |
284
|
|
|
connection.close_connection() |
285
|
|
|
|
286
|
|
|
def changesshpassword(miner: Miner, oldlogin, newlogin): |
287
|
|
|
""" change the factory ssh password """ |
288
|
|
|
if newlogin.username != oldlogin.username: |
289
|
|
|
print("changesshpassword: currently username change is not supported. only change password") |
290
|
|
|
return |
291
|
|
|
connection = Ssh(miner.ipaddress, oldlogin.username, oldlogin.password, port=getportfromminer(miner)) |
292
|
|
|
connection.open_shell() |
293
|
|
|
connection.send_shell('echo "{0}:{1}" | chpasswd'.format(newlogin.username, newlogin.password)) |
294
|
|
|
time.sleep(5) |
295
|
|
|
print_connection_data(connection) |
296
|
|
|
connection.close_connection() |
297
|
|
|
|
298
|
|
|
def reboot(miner: Miner, login): |
299
|
|
|
""" reboot the miner through ftp """ |
300
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
301
|
|
|
connection.open_shell() |
302
|
|
|
response = connection.exec_command('/sbin/reboot') |
303
|
|
|
print_connection_data(connection) |
304
|
|
|
connection.close_connection() |
305
|
|
|
return response |
306
|
|
|
|
307
|
|
|
def shutdown(miner: Miner, login): |
308
|
|
|
""" shutdown the miner through ftp |
309
|
|
|
Warning! this will not turn off the power to the machine! |
310
|
|
|
It will only shut down the operating system. Machine will still be consuming power if power supply |
311
|
|
|
does not have on/off switch. You will have to manually restart the machine. |
312
|
|
|
""" |
313
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
314
|
|
|
connection.open_shell() |
315
|
|
|
connection.send_shell('/sbin/poweroff') |
316
|
|
|
time.sleep(5) |
317
|
|
|
print_connection_data(connection) |
318
|
|
|
connection.close_connection() |
319
|
|
|
|
320
|
|
|
def waitforonline(miner: Miner): |
321
|
|
|
#poll miner until it comes up, returns MinerInfo or None for timeout |
322
|
|
|
cnt = 60 |
323
|
|
|
sleeptime = 5 |
324
|
|
|
minerinfo = None |
325
|
|
|
while cnt > 0: |
326
|
|
|
try: |
327
|
|
|
minerstats, minerinfo, apicall, minerpool = stats(miner) |
328
|
|
|
return minerinfo |
329
|
|
|
except MinerMonitorException as ex: |
330
|
|
|
if not ex.istimedout() and not ex.isconnectionrefused(): |
331
|
|
|
raise ex |
332
|
|
|
else: |
333
|
|
|
print(ex.friendlymessage()) |
334
|
|
|
|
335
|
|
|
if minerinfo is not None: |
336
|
|
|
if minerinfo.miner_type: |
337
|
|
|
print(' found {0} {1}'.format(minerinfo.miner_type, minerinfo.minerid)) |
338
|
|
|
cnt = 0 |
339
|
|
|
if cnt > 0: |
340
|
|
|
cnt -= sleeptime |
341
|
|
|
print('Waiting for {0}...'.format(miner.name)) |
342
|
|
|
time.sleep(sleeptime) |
343
|
|
|
return None |
344
|
|
|
|
345
|
|
|
#The 'poolpriority' command can be used to reset the priority order of multiple |
346
|
|
|
#pools with a single command - 'switchpool' only sets a single pool to first priority |
347
|
|
|
#Each pool should be listed by id number in order of preference (first = most preferred) |
348
|
|
|
#Any pools not listed will be prioritised after the ones that are listed, in the |
349
|
|
|
#priority order they were originally |
350
|
|
|
#If the priority change affects the miner's preference for mining, it may switch immediately |
351
|
|
|
def switch(miner: Miner, poolnumber): |
352
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
353
|
|
|
jswitch = api.switchpool("{0}".format(poolnumber)) |
354
|
|
|
print(jswitch["STATUS"][0]["Msg"]) |
355
|
|
|
|
356
|
|
|
#addpool|URL,USR,PASS (*) |
357
|
|
|
# none There is no reply section just the STATUS section |
358
|
|
|
# stating the results of attempting to add pool N |
359
|
|
|
# The Msg includes the pool number and URL |
360
|
|
|
# Use '\\' to get a '\' and '\,' to include a comma |
361
|
|
|
# inside URL, USR or PASS |
362
|
|
|
def addpool(miner: Miner, pool): |
363
|
|
|
""" Add a pool to a miner. Allows user to select it from drop down and easily switch to it """ |
364
|
|
|
api = MinerApi(host=miner.ipaddress, port=int(miner.port)) |
365
|
|
|
jaddpool = api.addpool("{0},{1},{2}".format(pool.url, pool.user, "x")) |
366
|
|
|
return jaddpool["STATUS"][0]["Msg"] |
367
|
|
|
|
368
|
|
|
def getminerfilename(miner: Miner): |
369
|
|
|
'''cgminer for D3 and A3''' |
370
|
|
|
minerfilename = 'cgminer' |
371
|
|
|
if miner.miner_type.startswith('Antminer S9'): |
372
|
|
|
minerfilename = 'bmminer' |
373
|
|
|
return minerfilename |
374
|
|
|
|
375
|
|
|
def change_setting(settingkey, newvalue): |
376
|
|
|
'''todo:there is bug here if updating the last line of config file! command (,) not needed at end''' |
377
|
|
|
return "sed -i \'s_^\\(\"{0}\" : \\).*_\\1\"{1}\",_\'".format(settingkey, newvalue) |
378
|
|
|
|
379
|
|
|
def get_changeconfigcommands(configfilename, setting, newvalue): |
380
|
|
|
commands = [] |
381
|
|
|
commands.append('cd /config') |
382
|
|
|
commands.append('cp {0}.conf {1}_last.conf'.format(configfilename, configfilename)) |
383
|
|
|
commands.append('chmod u=rw {0}.conf'.format(configfilename)) |
384
|
|
|
commands.append("{0} {1}.conf".format(change_setting(setting, newvalue), configfilename)) |
385
|
|
|
commands.append('chmod u=r {0}.conf'.format(configfilename)) |
386
|
|
|
return commands |
387
|
|
|
|
388
|
|
|
def sendcommands_and_restart(miner: Miner, login, commands): |
389
|
|
|
stopmining(miner, login) |
390
|
|
|
try: |
391
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
392
|
|
|
connection.open_shell() |
393
|
|
|
for cmd in commands: |
394
|
|
|
connection.send_shell(cmd) |
395
|
|
|
time.sleep(5) |
396
|
|
|
print_connection_data(connection) |
397
|
|
|
connection.close_connection() |
398
|
|
|
finally: |
399
|
|
|
restartmining(miner, login) |
400
|
|
|
|
401
|
|
|
def setprivileged(miner: Miner, login, allowsetting): |
402
|
|
|
""" Set miner to privileged mode """ |
403
|
|
|
commands = get_changeconfigcommands(getminerfilename(miner), 'api-allow', allowsetting) |
404
|
|
|
sendcommands_and_restart(miner, login, commands) |
405
|
|
|
|
406
|
|
|
def setrestricted(miner: Miner, login, allowsetting): |
407
|
|
|
""" Set miner to restricted mode """ |
408
|
|
|
commands = get_changeconfigcommands(getminerfilename(miner), 'api-allow', allowsetting) |
409
|
|
|
sendcommands_and_restart(miner, login, commands) |
410
|
|
|
|
411
|
|
|
def set_frequency(miner: Miner, login, frequency): |
412
|
|
|
""" Set miner frequency |
413
|
|
|
Does not work for S9 with auto tune. |
414
|
|
|
Fixed frequency firmware (650m) has to be loaded first before frequency can be adjusted |
415
|
|
|
""" |
416
|
|
|
#default for S9 is 550 |
417
|
|
|
#"bitmain-freq" : "550", |
418
|
|
|
commands = get_changeconfigcommands(getminerfilename(miner), 'bitmain-freq', frequency) |
419
|
|
|
sendcommands_and_restart(miner, login, commands) |
420
|
|
|
|
421
|
|
|
def load_firmware(): |
422
|
|
|
""" |
423
|
|
|
TODO: load firmware file |
424
|
|
|
this will probably change the ip address of the miner |
425
|
|
|
""" |
426
|
|
|
pass |
427
|
|
|
|
428
|
|
|
def load_miner(miner, login): |
429
|
|
|
""" |
430
|
|
|
change the miner software |
431
|
|
|
""" |
432
|
|
|
#ftp the new miner |
433
|
|
|
commands = [] |
434
|
|
|
commands.append('cd /usr/bin') |
435
|
|
|
commands.append('cp bmminer bmminer.original') |
436
|
|
|
commands.append('cp bmminer880 bmminer') |
437
|
|
|
commands.append('chmod +x bmminer') |
438
|
|
|
sendcommands_and_restart(miner, login, commands) |
439
|
|
|
|
440
|
|
|
def file_upload(miner, login, localfile, remotefile): |
441
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
442
|
|
|
connection.put(localfile, remotefile) |
443
|
|
|
|
444
|
|
|
def file_download(miner, login, localfile, remotefile): |
445
|
|
|
connection = Ssh(miner.ipaddress, login.username, login.password, port=getportfromminer(miner)) |
446
|
|
|
connection.get(localfile, remotefile) |
447
|
|
|
|