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