|
import argparse |
|
import asyncio |
|
# import boto3 |
|
import configparser |
|
import datetime |
|
import json |
|
import time |
|
|
|
from decimal import Decimal |
|
from pyasic import get_miner |
|
from pyasic.miners.base import BaseMiner |
|
|
|
import comed_api as comed_api |
|
|
|
|
|
|
|
async def run(arg_config: configparser.ConfigParser): |
|
ip_address = arg_config.get('ASIC', 'IP_ADDRESS') |
|
max_freq = arg_config.getint('ASIC', 'MAX_FREQ') |
|
min_freq = arg_config.getint('ASIC', 'MIN_FREQ') |
|
freq_step = arg_config.getint('ASIC', 'FREQ_STEP') |
|
max_electricity_price = Decimal(arg_config.get('ASIC', 'MAX_ELECTRICITY_PRICE')) |
|
resume_after = arg_config.getint('ASIC', 'RESUME_AFTER') |
|
|
|
# weather_api_key = arg_config.get('APIS', 'WEATHER_API_KEY') |
|
# weather_zip_code = arg_config.get('APIS', 'WEATHER_ZIP_CODE') |
|
|
|
# sns_topic = arg_config.get('AWS', 'SNS_TOPIC') |
|
# aws_access_key_id = arg_config.get('AWS', 'AWS_ACCESS_KEY_ID') |
|
# aws_secret_access_key = arg_config.get('AWS', 'AWS_SECRET_ACCESS_KEY') |
|
|
|
# # Prep boto SNS client for email notifications |
|
# sns = boto3.client( |
|
# "sns", |
|
# aws_access_key_id=aws_access_key_id, |
|
# aws_secret_access_key=aws_secret_access_key, |
|
# region_name="us-east-1" # N. Virginia |
|
# ) |
|
|
|
# if force_power_off: |
|
# # Shut down the miner and exit |
|
# whatsminer_token.enable_write_access(admin_password=admin_password) |
|
# response = WhatsminerAPI.exec_command(whatsminer_token, cmd='power_off', additional_params={"respbefore": "false"}) |
|
# subject = f"STOPPING miner via force_power_off" |
|
# msg = "force_power_off called" |
|
# sns.publish( |
|
# TopicArn=sns_topic, |
|
# Subject=subject, |
|
# Message=msg |
|
# ) |
|
# print(f"{datetime.datetime.now()}: {subject}") |
|
# print(msg) |
|
# print(json.dumps(response, indent=4)) |
|
# exit() |
|
|
|
# Get the current electricity price |
|
try: |
|
prices = comed_api.get_last_hour() |
|
except Exception as e: |
|
print(f"First attempt to reach ComEd API: {repr(e)}") |
|
# Wait and try again before giving up |
|
time.sleep(30) |
|
try: |
|
prices = comed_api.get_last_hour() |
|
except Exception as e: |
|
print(f"Second attempt to reach ComEd API: {repr(e)}") |
|
|
|
# if the real-time price API is down, assume the worst and shut down |
|
# subject = f"STOPPING miner @ UNKNOWN ¢/kWh" |
|
# msg = "ComEd real-time price API is down" |
|
# sns.publish( |
|
# TopicArn=sns_topic, |
|
# Subject=subject, |
|
# Message=msg |
|
# ) |
|
# print(f"{datetime.datetime.now()}: {subject}") |
|
exit() |
|
|
|
(cur_timestamp, cur_electricity_price) = prices[0] |
|
|
|
# Get the miner |
|
miner: BaseMiner = await get_miner(ip_address) |
|
if not miner: |
|
print(f"{datetime.datetime.now()}: Miner not found at {ip_address}") |
|
exit() |
|
|
|
config = await miner.get_config() |
|
cur_freq = int(config.mining_mode.global_freq) |
|
|
|
subject = "Error?" |
|
|
|
if cur_electricity_price > max_electricity_price: |
|
# Reduce miner freq, we've passed the price threshold |
|
new_freq = cur_freq - freq_step |
|
if new_freq < min_freq: |
|
subject = "Already at min freq" |
|
|
|
else: |
|
config.mining_mode.global_freq = new_freq |
|
result = await miner.send_config(config) |
|
|
|
subject = f"REDUCING miner freq @ {cur_electricity_price:0.2f}¢/kWh to {new_freq}" |
|
# sns.publish( |
|
# TopicArn=sns_topic, |
|
# Subject=subject, |
|
# Message=msg |
|
# ) |
|
# print(msg) |
|
|
|
elif cur_electricity_price < max_electricity_price: |
|
# Resume mining? Electricity price has fallen below our threshold; but don't |
|
# get faked out by a single period dropping. Must see n periods in a row |
|
# (`resume_mining_after`) below the price threshold before resuming. |
|
resume_mining = True |
|
for i in range(1, resume_after + 1): |
|
(ts, price) = prices[i] |
|
if price >= max_electricity_price: |
|
resume_mining = False |
|
break |
|
|
|
if resume_mining: |
|
new_freq = cur_freq + freq_step |
|
if new_freq > max_freq: |
|
subject = "Already at max freq" |
|
|
|
else: |
|
config.mining_mode.global_freq = new_freq |
|
result = await miner.send_config(config) |
|
|
|
subject = f"INCREASING miner freq @ {cur_electricity_price:0.2f}¢/kWh to {new_freq}" |
|
# sns.publish( |
|
# TopicArn=sns_topic, |
|
# Subject=subject, |
|
# Message=msg |
|
# ) |
|
# print(msg) |
|
|
|
else: |
|
subject = f"Holding freq, pending {resume_after} periods below threshold" |
|
|
|
print(f"{datetime.datetime.now()}: freq: {cur_freq} MHz ({min_freq}-{max_freq}) | {cur_electricity_price:0.2f}¢/kWh ({max_electricity_price}) | {subject}") |
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='vnish custom manager') |
|
|
|
# Required positional arguments |
|
# parser.add_argument('max_electricity_price', type=Decimal, |
|
# help="Threshold above which the ASIC reduces chip frequency") |
|
|
|
# Optional switches |
|
parser.add_argument('-c', '--settings', |
|
default="settings.conf", |
|
dest="settings_config", |
|
help="Override default settings config file location") |
|
|
|
# parser.add_argument('-f', '--force_power_off', |
|
# action="store_true", |
|
# default=False, |
|
# dest="force_power_off", |
|
# help="Stops mining and exits") |
|
|
|
|
|
|
|
args = parser.parse_args() |
|
|
|
# force_power_off = args.force_power_off |
|
|
|
# Read settings |
|
arg_config = configparser.ConfigParser() |
|
arg_config.read(args.settings_config) |
|
|
|
asyncio.run(run(arg_config)) |