Compare commits

..

4 Commits

Author SHA1 Message Date
83e838c9b7 Add models and API routes 2022-08-07 20:33:06 +00:00
741baa3c7a Improve logging 2022-08-07 20:33:06 +00:00
0d7b2a4935 Make script more reliable 2022-06-17 05:09:07 +01:00
cd8204e020 Pull get_temp out of main loop 2022-06-17 03:41:09 +01:00
10 changed files with 326 additions and 29 deletions

View File

@@ -1,6 +1,4 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
name = 'apiserver.api'

View File

@@ -0,0 +1,40 @@
# Generated by Django 4.0.4 on 2022-04-27 23:47
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CoolerData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField()),
('cooler_id', models.CharField(blank=True, max_length=36)),
],
),
migrations.CreateModel(
name='MinerData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField()),
('miner_id', models.CharField(blank=True, max_length=36)),
('summary', models.JSONField()),
('fans', models.JSONField()),
('devdetails', models.JSONField()),
('version', models.JSONField()),
('devs', models.JSONField()),
('config', models.JSONField()),
('coin', models.JSONField()),
('pools', models.JSONField()),
('tunerstatus', models.JSONField()),
('temps', models.JSONField()),
],
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 4.0.4 on 2022-06-23 21:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='coolerdata',
name='fan',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='coolerdata',
name='max_temp',
field=models.DecimalField(decimal_places=4, default=0, max_digits=7),
preserve_default=False,
),
migrations.AddField(
model_name='coolerdata',
name='pump',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='coolerdata',
name='rad_temp',
field=models.DecimalField(decimal_places=4, default=0, max_digits=7),
preserve_default=False,
),
migrations.AddField(
model_name='coolerdata',
name='tub_temp',
field=models.DecimalField(decimal_places=4, default=0, max_digits=7),
preserve_default=False,
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.0.4 on 2022-06-24 00:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0002_coolerdata_fan_coolerdata_max_temp_coolerdata_pump_and_more'),
]
operations = [
migrations.AddField(
model_name='minerdata',
name='json_version',
field=models.IntegerField(default=1),
preserve_default=False,
),
]

View File

@@ -1,3 +1,28 @@
from django.db import models
# Create your models here.
class MinerData(models.Model):
time = models.DateTimeField()
miner_id = models.CharField(max_length=36, blank=True)
json_version = models.IntegerField()
summary = models.JSONField()
fans = models.JSONField()
devdetails = models.JSONField()
version = models.JSONField()
devs = models.JSONField()
config = models.JSONField()
coin = models.JSONField()
pools = models.JSONField()
tunerstatus = models.JSONField()
temps = models.JSONField()
class CoolerData(models.Model):
time = models.DateTimeField()
cooler_id = models.CharField(max_length=36, blank=True)
tub_temp = models.DecimalField(max_digits=7, decimal_places=4)
rad_temp = models.DecimalField(max_digits=7, decimal_places=4)
max_temp = models.DecimalField(max_digits=7, decimal_places=4)
fan = models.IntegerField()
pump = models.IntegerField()

View File

@@ -1,7 +1,21 @@
import logging
logger = logging.getLogger(__name__)
from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework import viewsets, views, mixins
from rest_framework import permissions
from apiserver.api import serializers
from rest_framework.decorators import action
from rest_framework.response import Response
from apiserver.api import serializers, models
from datetime import datetime, timezone
Base = viewsets.GenericViewSet
List = mixins.ListModelMixin
Retrieve = mixins.RetrieveModelMixin
Create = mixins.CreateModelMixin
Update = mixins.UpdateModelMixin
Destroy = mixins.DestroyModelMixin
class UserViewSet(viewsets.ModelViewSet):
@@ -9,3 +23,52 @@ class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
permission_classes = [permissions.IsAuthenticated]
class DataViewSet(Base, List, Retrieve):
@action(detail=False, methods=['post'])
def push(self, request):
miner_data = request.data['miner_data']
cooler_data = request.data['cooler_data']
for miner_id, miner in miner_data.items():
time = miner['summary'][0]['STATUS'][0]['When']
models.MinerData.objects.update_or_create(
time=datetime.fromtimestamp(time, tz=timezone.utc),
miner_id=miner_id,
defaults=dict(
json_version=2,
summary=miner['summary'][0]['SUMMARY'][0],
fans=miner['fans'][0]['FANS'],
devdetails=miner['devdetails'][0]['DEVDETAILS'],
version=miner['version'][0]['VERSION'][0],
devs=miner['devs'][0]['DEVS'],
config=miner['config'][0]['CONFIG'][0],
coin=miner['coin'][0]['COIN'][0],
pools=miner['pools'][0]['POOLS'],
tunerstatus=miner['tunerstatus'][0]['TUNERSTATUS'][0],
temps=miner['temps'][0]['TEMPS'],
),
)
time = cooler_data['time']
cooler_id = cooler_data['cooler_id']
models.CoolerData.objects.update_or_create(
time=datetime.fromtimestamp(time, tz=timezone.utc),
cooler_id=cooler_id,
defaults=dict(
tub_temp=cooler_data['tub_temp'],
rad_temp=cooler_data['rad_temp'],
max_temp=cooler_data['max_temp'],
fan=cooler_data['fan'],
pump=cooler_data['pump'],
),
)
logging.info('Added {} miner data points from cooler {}.'.format(
len(miner_data),
cooler_id,
))
return Response(200)

View File

@@ -10,6 +10,10 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
import logging.config
logger = logging.getLogger(__name__)
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -22,8 +26,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-au(y+z)$-iy#(obif&ilg*_pn0j_+0u=q*p7h(3c-ii-euncwx'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG_ENV = os.environ.get('DEBUG', False)
DEBUG = DEBUG_ENV or False
ALLOWED_HOSTS = [
'api.soak.stctech.ca',
@@ -40,6 +44,8 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
#'rest_framework.authtoken',
'apiserver.api',
]
MIDDLEWARE = [
@@ -127,3 +133,43 @@ STATIC_URL = 'static/'
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'medium': {
'format': '[%(asctime)s] [%(process)d] [%(levelname)7s] %(message)s'
},
},
'filters': {
},
'handlers': {
'console': {
'level': 'DEBUG',
'filters': [],
'class': 'logging.StreamHandler',
'formatter': 'medium'
},
},
'loggers': {
'gunicorn': {
'handlers': ['console'],
'level': 'DEBUG' if DEBUG else 'INFO',
'propagate': False,
},
'': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
},
'root': {
'level': 'DEBUG' if DEBUG else 'INFO',
'handlers': ['console'],
},
}
logging.config.dictConfig(LOGGING)
if DEBUG: logger.info('Debug mode ON')
logger.info('Test logging for each thread')

View File

@@ -4,6 +4,7 @@ from apiserver.api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'data', views.DataViewSet, basename='data')
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.

View File

@@ -1,55 +1,117 @@
import os
import logging
DEBUG = os.environ.get('DEBUG', False)
logging.basicConfig(
format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s',
level=logging.DEBUG if os.environ.get('DEBUG') else logging.INFO)
#logging.getLogger('aiohttp').setLevel(logging.DEBUG if os.environ.get('DEBUG') else logging.WARNING)
level=logging.DEBUG if DEBUG else logging.INFO)
import asyncio
from statistics import mean
from signal import *
import time
import w1therm
import relays
import data
import requests
HI_SETPOINT = 40.0
LO_SETPOINT = 30.0
LO_SETPOINT = HI_SETPOINT - 5.0
PUMP_RELAY = relays.RELAY1
FAN_RELAY = relays.RELAY3
async def main():
relays.init_relays()
def controller_message(message):
payload = dict(misc=message)
r = requests.post('https://tbot.tannercollin.com/message', data=payload, timeout=10)
if r.status_code == 200:
return True
else:
logging.exception('Unable to communicate with controller! Message: ' + message)
return False
def set_fan_on():
relays.set_relay(FAN_RELAY, relays.RELAY_ON)
def set_fan_off():
relays.set_relay(FAN_RELAY, relays.RELAY_OFF)
def set_pump_on():
relays.set_relay(PUMP_RELAY, relays.RELAY_ON)
async def get_temp():
temps = await w1therm.get_temperatures()
if len(temps) != len(data.PROBES):
raise
temperature_log = []
for id_, temp in temps.items():
temperature_log.append('{}: {} C'.format(data.PROBES[id_], temp))
logging.info('Temperature probe ' + ', '.join(temperature_log))
temperature_list = list(temps.values())
temperature_mean = mean(temperature_list)
return temperature_mean
async def run_thermostat():
while True:
try:
temps = await w1therm.get_temperatures()
if len(temps) != len(data.PROBES):
raise
temperature_mean = await get_temp()
except:
logging.error('Problem reading temperature probes! Setting fan on and sleeping.')
logging.error('Problem reading temperature probes! Turning fan on and sleeping 60s.')
relays.set_relay(FAN_RELAY, relays.RELAY_ON)
await asyncio.sleep(60)
continue
temperature_log = []
for id_, temp in temps.items():
temperature_log.append('{}: {} C'.format(data.PROBES[id_], temp))
logging.info('Temperature probe ' + ', '.join(temperature_log))
temperature_list = list(temps.values())
temperature_mean = mean(temperature_list)
if temperature_mean > HI_SETPOINT:
logging.info('Turning fan on')
relays.set_relay(FAN_RELAY, relays.RELAY_ON)
set_fan_on()
elif temperature_mean < LO_SETPOINT:
logging.info('Turning fan off')
relays.set_relay(FAN_RELAY, relays.RELAY_OFF)
set_fan_off()
await asyncio.sleep(10)
def run_on_exit(*args):
logging.info('Cryptosoak exiting, turning pump and fan on.')
controller_message('Cryptosoak exiting.')
set_pump_on()
set_fan_on()
exit()
async def feed_watchdog():
while True:
with open('/dev/watchdog', 'w') as wdt:
wdt.write('1')
await asyncio.sleep(5)
def init():
relays.init_relays()
set_pump_on()
for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM):
signal(sig, run_on_exit)
logging.info('Signals initialized')
async def main():
controller_message('Cryptosoak booting up...')
logging.info('Initialization complete')
await run_thermostat()
if __name__ == '__main__':
init()
if not DEBUG:
logging.info('Waiting 60 seconds to boot...')
time.sleep(60)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_until_complete(main()).add_done_callback(run_on_exit)
if not DEBUG:
loop.run_until_complete(feed_watchdog())
loop.close()

View File

@@ -31,7 +31,7 @@ async def test():
for r in RELAYS:
set_relay(r, RELAY_ON)
await asyncio.sleep(0.5)
#set_relay(r, RELAY_OFF)
set_relay(r, RELAY_OFF)
if __name__ == '__main__':
init_relays()