Compare commits
14 Commits
2011f0a4cf
...
master
Author | SHA1 | Date | |
---|---|---|---|
83e838c9b7 | |||
741baa3c7a | |||
0d7b2a4935 | |||
cd8204e020 | |||
be6fb4bc3c | |||
ae30c39954 | |||
4a8d130c72 | |||
ad34a4b2d2 | |||
127c6ef91c | |||
84a643e69c | |||
6c0054e72d | |||
7be3e6a39d | |||
ee5236e43a | |||
8f62efd1ae |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -102,4 +102,4 @@ ENV/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
settings.py
|
out.txt
|
||||||
|
0
apiserver/apiserver/__init__.py
Normal file
0
apiserver/apiserver/__init__.py
Normal file
0
apiserver/apiserver/api/__init__.py
Normal file
0
apiserver/apiserver/api/__init__.py
Normal file
3
apiserver/apiserver/api/admin.py
Normal file
3
apiserver/apiserver/api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
4
apiserver/apiserver/api/apps.py
Normal file
4
apiserver/apiserver/api/apps.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
name = 'apiserver.api'
|
40
apiserver/apiserver/api/migrations/0001_initial.py
Normal file
40
apiserver/apiserver/api/migrations/0001_initial.py
Normal 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()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@@ -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,
|
||||||
|
),
|
||||||
|
]
|
@@ -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,
|
||||||
|
),
|
||||||
|
]
|
0
apiserver/apiserver/api/migrations/__init__.py
Normal file
0
apiserver/apiserver/api/migrations/__init__.py
Normal file
28
apiserver/apiserver/api/models.py
Normal file
28
apiserver/apiserver/api/models.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
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()
|
8
apiserver/apiserver/api/serializers.py
Normal file
8
apiserver/apiserver/api/serializers.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['url', 'username', 'email', 'groups']
|
||||||
|
|
3
apiserver/apiserver/api/tests.py
Normal file
3
apiserver/apiserver/api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
74
apiserver/apiserver/api/views.py
Normal file
74
apiserver/apiserver/api/views.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import viewsets, views, mixins
|
||||||
|
from rest_framework import permissions
|
||||||
|
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):
|
||||||
|
queryset = User.objects.all().order_by('-date_joined')
|
||||||
|
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)
|
||||||
|
|
16
apiserver/apiserver/asgi.py
Normal file
16
apiserver/apiserver/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for apiserver project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'apiserver.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
175
apiserver/apiserver/settings.py
Normal file
175
apiserver/apiserver/settings.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
"""
|
||||||
|
Django settings for apiserver project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.0.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/topics/settings/
|
||||||
|
|
||||||
|
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'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
DEBUG_ENV = os.environ.get('DEBUG', False)
|
||||||
|
DEBUG = DEBUG_ENV or False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
'api.soak.stctech.ca',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
#'rest_framework.authtoken',
|
||||||
|
'apiserver.api',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'apiserver.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'apiserver.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
'NAME': 'django',
|
||||||
|
'USER': 'django',
|
||||||
|
'PASSWORD': 'django',
|
||||||
|
'HOST': '127.0.0.1',
|
||||||
|
'PORT': '5432',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# 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')
|
14
apiserver/apiserver/urls.py
Normal file
14
apiserver/apiserver/urls.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework import routers
|
||||||
|
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.
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||||
|
]
|
16
apiserver/apiserver/wsgi.py
Normal file
16
apiserver/apiserver/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for apiserver project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'apiserver.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
apiserver/manage.py
Executable file
22
apiserver/manage.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'apiserver.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
6
apiserver/requirements.txt
Normal file
6
apiserver/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
asgiref==3.5.0
|
||||||
|
Django==4.0.4
|
||||||
|
djangorestframework==3.13.1
|
||||||
|
psycopg2==2.9.3
|
||||||
|
pytz==2022.1
|
||||||
|
sqlparse==0.4.2
|
41
rpiserver/bosminer.py
Normal file
41
rpiserver/bosminer.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
|
||||||
|
async def bosminer_cmd(host, command, param=None):
|
||||||
|
reader, writer = await asyncio.open_connection(host, 4028)
|
||||||
|
|
||||||
|
payload = dict(command=command)
|
||||||
|
if param:
|
||||||
|
payload['parameter'] = param
|
||||||
|
|
||||||
|
message = json.dumps(payload)
|
||||||
|
writer.write(message.encode())
|
||||||
|
await writer.drain()
|
||||||
|
data = await reader.readuntil(separator=b'\00')
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
return data[:-1].decode()
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) == 3:
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
param = sys.argv[2]
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
param = None
|
||||||
|
else:
|
||||||
|
cmd = 'temps'
|
||||||
|
param = None
|
||||||
|
|
||||||
|
res = await bosminer_cmd('192.168.69.4', cmd, param)
|
||||||
|
j = json.loads(res)
|
||||||
|
print(json.dumps(j, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(test())
|
||||||
|
loop.close()
|
4
rpiserver/data.py
Normal file
4
rpiserver/data.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
PROBES = {
|
||||||
|
'0218404f8bff': 'Rad',
|
||||||
|
'0218405068ff': 'Tub',
|
||||||
|
}
|
117
rpiserver/main.py
Normal file
117
rpiserver/main.py
Normal file
@@ -0,0 +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 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 = HI_SETPOINT - 5.0
|
||||||
|
|
||||||
|
PUMP_RELAY = relays.RELAY1
|
||||||
|
FAN_RELAY = relays.RELAY3
|
||||||
|
|
||||||
|
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:
|
||||||
|
temperature_mean = await get_temp()
|
||||||
|
except:
|
||||||
|
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
|
||||||
|
|
||||||
|
if temperature_mean > HI_SETPOINT:
|
||||||
|
logging.info('Turning fan on')
|
||||||
|
set_fan_on()
|
||||||
|
elif temperature_mean < LO_SETPOINT:
|
||||||
|
logging.info('Turning fan 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()).add_done_callback(run_on_exit)
|
||||||
|
if not DEBUG:
|
||||||
|
loop.run_until_complete(feed_watchdog())
|
||||||
|
loop.close()
|
||||||
|
|
41
rpiserver/relays.py
Normal file
41
rpiserver/relays.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import asyncio
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
RELAY1 = 4
|
||||||
|
RELAY2 = 22
|
||||||
|
RELAY3 = 6
|
||||||
|
RELAY4 = 26
|
||||||
|
|
||||||
|
RELAY_ON = True
|
||||||
|
RELAY_OFF = False
|
||||||
|
|
||||||
|
RELAYS = [RELAY1, RELAY2, RELAY3, RELAY4]
|
||||||
|
|
||||||
|
def set_relay(r, state):
|
||||||
|
GPIO.output(r, state)
|
||||||
|
|
||||||
|
def all_off():
|
||||||
|
for r in RELAYS:
|
||||||
|
set_relay(r, RELAY_OFF)
|
||||||
|
|
||||||
|
def init_relays():
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
for r in RELAYS:
|
||||||
|
GPIO.setup(r, GPIO.OUT)
|
||||||
|
|
||||||
|
all_off()
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
for r in RELAYS:
|
||||||
|
set_relay(r, RELAY_ON)
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
set_relay(r, RELAY_OFF)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_relays()
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(test())
|
||||||
|
loop.close()
|
37
rpiserver/requirements.txt
Normal file
37
rpiserver/requirements.txt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
aiofiles==0.8.0
|
||||||
|
aiohttp==3.8.1
|
||||||
|
aiosignal==1.2.0
|
||||||
|
appdirs==1.4.4
|
||||||
|
async-timeout==4.0.2
|
||||||
|
attrs==21.4.0
|
||||||
|
certifi==2020.6.20
|
||||||
|
chardet==4.0.0
|
||||||
|
charset-normalizer==2.0.12
|
||||||
|
click==8.1.2
|
||||||
|
colorzero==1.1
|
||||||
|
dbus-python==1.2.16
|
||||||
|
distlib==0.3.1
|
||||||
|
distro==1.5.0
|
||||||
|
distro-info==1.0
|
||||||
|
filelock==3.0.12
|
||||||
|
frozenlist==1.3.0
|
||||||
|
gpiozero==1.6.2
|
||||||
|
idna==2.10
|
||||||
|
importlib-metadata==1.6.0
|
||||||
|
more-itertools==4.2.0
|
||||||
|
multidict==6.0.2
|
||||||
|
PyGObject==3.38.0
|
||||||
|
python-apt==2.2.1
|
||||||
|
requests==2.25.1
|
||||||
|
RPi.GPIO==0.7.0
|
||||||
|
six==1.16.0
|
||||||
|
spidev==3.5
|
||||||
|
ssh-import-id==5.10
|
||||||
|
supervisor==4.2.2
|
||||||
|
ufw==0.36
|
||||||
|
unattended-upgrades==0.1
|
||||||
|
urllib3==1.26.5
|
||||||
|
virtualenv==20.4.0+ds
|
||||||
|
w1thermsensor==2.0.0
|
||||||
|
yarl==1.7.2
|
||||||
|
zipp==1.0.0
|
22
rpiserver/w1therm.py
Normal file
22
rpiserver/w1therm.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import asyncio
|
||||||
|
from w1thermsensor import AsyncW1ThermSensor, Unit
|
||||||
|
|
||||||
|
async def get_temperatures():
|
||||||
|
temps = {}
|
||||||
|
|
||||||
|
for sensor in AsyncW1ThermSensor.get_available_sensors():
|
||||||
|
temps[sensor.id] = await sensor.get_temperature()
|
||||||
|
|
||||||
|
return temps
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
temps = await get_temperatures()
|
||||||
|
|
||||||
|
for id_, temp in temps.items():
|
||||||
|
print('sensor', id_, ':' , temp, 'C')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(test())
|
||||||
|
loop.close()
|
||||||
|
|
Reference in New Issue
Block a user