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
|
||||
*.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