Initial commit
This commit is contained in:
commit
d30a15567b
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.egg-info/
|
||||||
|
build/
|
||||||
|
dist/
|
43
config/_Config.py
Normal file
43
config/_Config.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
class Config():
|
||||||
|
def __init__(self):
|
||||||
|
self.__config_file = os.path.join(os.path.expanduser('~'), ".config/sauna/config.json")
|
||||||
|
self.__config = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.__config_file, "r") as handle:
|
||||||
|
self.__config = json.load(handle)
|
||||||
|
except FileNotFoundError:
|
||||||
|
os.makedirs(os.path.dirname(self.__config_file), exist_ok=True)
|
||||||
|
self.__config = {
|
||||||
|
"target_temperature": "80",
|
||||||
|
"temperature_step": "5",
|
||||||
|
"max_temperature": "120",
|
||||||
|
"runtime": "2:30",
|
||||||
|
"heat_pin": "26"
|
||||||
|
}
|
||||||
|
with open(self.__config_file, "w") as handle:
|
||||||
|
json.dump(self.__config, handle)
|
||||||
|
|
||||||
|
def target_temperature(self):
|
||||||
|
return self.__config['target_temperature']
|
||||||
|
|
||||||
|
def temperature_step(self):
|
||||||
|
res =self.__config['temperature_step']
|
||||||
|
print(res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def heat_pin(self):
|
||||||
|
return self.__config['heat_pin']
|
||||||
|
|
||||||
|
def runtime(self):
|
||||||
|
return self.__config['runtime']
|
||||||
|
|
||||||
|
def update_target_temperature(self, temperature: int):
|
||||||
|
if temperature <= int(self.__config['max_temperature']):
|
||||||
|
self.__config['target_temperature'] = str(temperature)
|
||||||
|
with open(self.__config_file, "w") as handle:
|
||||||
|
json.dump(self.__config, handle)
|
1
config/__init__.py
Normal file
1
config/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from ._Config import Config
|
81
control/_Control.py
Normal file
81
control/_Control.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
from threading import Thread
|
||||||
|
import time
|
||||||
|
|
||||||
|
from w1thermsensor import W1ThermSensor
|
||||||
|
|
||||||
|
import heat
|
||||||
|
|
||||||
|
class Control(Thread):
|
||||||
|
def __init__(self, heat_pin: int):
|
||||||
|
super(Control, self).__init__()
|
||||||
|
self.__run_condition = True
|
||||||
|
self.__runtime = datetime.timedelta(hours=2)
|
||||||
|
self.__on_time = None
|
||||||
|
self.__off_time = datetime.datetime.now() + self.__runtime
|
||||||
|
self.__heat = heat.Heat(heat_pin)
|
||||||
|
self.__state = False
|
||||||
|
self.__target_temperature = 80.0
|
||||||
|
self.__span_temperature = 1.5
|
||||||
|
self.__sensor = W1ThermSensor()
|
||||||
|
self.__log = logging.getLogger()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.__run_condition:
|
||||||
|
if self.__state:
|
||||||
|
# check for elapsed runtime
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if now > self.__off_time:
|
||||||
|
self.__state = False
|
||||||
|
self.__log.info("switch off after %s", self.__runtime)
|
||||||
|
continue
|
||||||
|
# check for regulating
|
||||||
|
temperature = float(self.__sensor.get_temperature())
|
||||||
|
if self.__heat.state() and temperature > (self.__target_temperature + self.__span_temperature):
|
||||||
|
self.__heat.off()
|
||||||
|
self.__log.info("switch heat off for regulating")
|
||||||
|
self.__log.info("current temperature: %s", temperature)
|
||||||
|
else:
|
||||||
|
if not self.__heat.state() and temperature < (self.__target_temperature - self.__span_temperature):
|
||||||
|
self.__heat.on()
|
||||||
|
self.__log.info("switch heat on for regulating")
|
||||||
|
self.__log.info("current temperature: %s", temperature)
|
||||||
|
else:
|
||||||
|
if self.__heat.state():
|
||||||
|
self.__heat.off()
|
||||||
|
time.sleep(0.8)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.__run_condition = False
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def set_runtime(self, runtime):
|
||||||
|
dtime = datetime.datetime.strptime(runtime, "%H:%M")
|
||||||
|
self.__runtime = datetime.timedelta(hours=dtime.hour, minutes=dtime.minute)
|
||||||
|
|
||||||
|
def get_runtime(self):
|
||||||
|
return self.__runtime
|
||||||
|
|
||||||
|
def set_target_temperature(self, target_temperature):
|
||||||
|
self.__target_temperature = float(target_temperature)
|
||||||
|
|
||||||
|
def get_target_temperature(self):
|
||||||
|
return self.__target_temperature
|
||||||
|
|
||||||
|
def switch_on(self):
|
||||||
|
self.__on_time = datetime.datetime.now()
|
||||||
|
self.__off_time = self.__on_time + self.__runtime
|
||||||
|
self.__state = True
|
||||||
|
|
||||||
|
def switch_off(self):
|
||||||
|
self.__state = False
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
return self.__state
|
||||||
|
|
||||||
|
def time_to_switch_off(self):
|
||||||
|
if self.__state:
|
||||||
|
return self.__off_time - datetime.datetime.now()
|
||||||
|
return None
|
1
control/__init__.py
Normal file
1
control/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from ._Control import Control
|
22
heat/_Heat.py
Normal file
22
heat/_Heat.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
class Heat():
|
||||||
|
def __init__(self, pin):
|
||||||
|
self.__pin = pin
|
||||||
|
self.__state = False
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setup(pin, GPIO.OUT)
|
||||||
|
if GPIO.input(pin):
|
||||||
|
self.__state = True
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
self.__state = True
|
||||||
|
GPIO.output(self.__pin, 1)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
self.__state = False
|
||||||
|
GPIO.output(self.__pin, 0)
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
return self.__state
|
1
heat/__init__.py
Normal file
1
heat/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from ._Heat import Heat
|
72
remotectrl/_RemoteCtrl.py
Normal file
72
remotectrl/_RemoteCtrl.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import logging
|
||||||
|
from xmlrpc.server import SimpleXMLRPCServer
|
||||||
|
|
||||||
|
import config
|
||||||
|
import control
|
||||||
|
|
||||||
|
class RemoteCtrl:
|
||||||
|
def __init__(self, host="localhost", port=64001):
|
||||||
|
self.__log = logging.getLogger()
|
||||||
|
self.__server = SimpleXMLRPCServer((host, port), allow_none=True)
|
||||||
|
self.__server.register_function(self.set_runtime, 'set_runtime')
|
||||||
|
self.__server.register_function(self.get_runtime, 'get_runtime')
|
||||||
|
self.__server.register_function(self.get_target_temperature, 'get_target_temperature')
|
||||||
|
self.__server.register_function(self.get_temperature_step, 'get_temperature_step')
|
||||||
|
self.__server.register_function(self.set_target_temperature, 'set_target_temperature')
|
||||||
|
self.__server.register_function(self.switch_on, 'switch_on')
|
||||||
|
self.__server.register_function(self.switch_off, 'switch_off')
|
||||||
|
self.__server.register_function(self.state, 'state')
|
||||||
|
self.__server.register_function(self.time_to_switch_off, 'time_to_switch_off')
|
||||||
|
|
||||||
|
self.__config = config.Config()
|
||||||
|
self.__ctrl = control.Control(int(self.__config.heat_pin()))
|
||||||
|
self.__ctrl.set_target_temperature(self.__config.target_temperature())
|
||||||
|
self.__ctrl.set_runtime(self.__config.runtime())
|
||||||
|
|
||||||
|
|
||||||
|
def set_runtime(self, runtime):
|
||||||
|
self.__ctrl.set_runtime(runtime)
|
||||||
|
|
||||||
|
|
||||||
|
def get_runtime(self):
|
||||||
|
return str(self.__ctrl.get_runtime)
|
||||||
|
|
||||||
|
|
||||||
|
def get_target_temperature(self):
|
||||||
|
return self.__ctrl.get_target_temperature()
|
||||||
|
|
||||||
|
|
||||||
|
def get_temperature_step(self):
|
||||||
|
return self.__config.temperature_step()
|
||||||
|
|
||||||
|
|
||||||
|
def set_target_temperature(self, target_temperature):
|
||||||
|
self.__ctrl.set_target_temperature(target_temperature)
|
||||||
|
self.__config.update_target_temperature(target_temperature)
|
||||||
|
|
||||||
|
|
||||||
|
def switch_on(self):
|
||||||
|
self.__ctrl.switch_on()
|
||||||
|
|
||||||
|
|
||||||
|
def switch_off(self):
|
||||||
|
self.__ctrl.switch_off()
|
||||||
|
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
return self.__ctrl.state()
|
||||||
|
|
||||||
|
|
||||||
|
def time_to_switch_off(self):
|
||||||
|
return str(self.__ctrl.time_to_switch_off())
|
||||||
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.__log.info('Control-c to quit')
|
||||||
|
self.__ctrl.start()
|
||||||
|
self.__server.serve_forever()
|
||||||
|
|
||||||
|
self.__log.info("Shutting down...")
|
||||||
|
self.__ctrl.stop()
|
||||||
|
self.switch_off()
|
||||||
|
self.__log.info("...done. Exiting...")
|
1
remotectrl/__init__.py
Normal file
1
remotectrl/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from ._RemoteCtrl import RemoteCtrl
|
10
saunacontrol.service
Normal file
10
saunacontrol.service
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Saunacontrol
|
||||||
|
After=multi-user.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
ExecStart=/usr/local/bin/saunacontrol
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
0
saunacontrol/__init__.py
Normal file
0
saunacontrol/__init__.py
Normal file
26
saunacontrol/__main__.py
Normal file
26
saunacontrol/__main__.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import remotectrl
|
||||||
|
|
||||||
|
LOG_LEVEL = logging.INFO
|
||||||
|
LOG_FILE = "/var/log/sauna.log"
|
||||||
|
LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL, filename=LOG_FILE)
|
||||||
|
# logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL)
|
||||||
|
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
log.info("Starting...")
|
||||||
|
server = remotectrl.RemoteCtrl()
|
||||||
|
server.start()
|
||||||
|
|
||||||
|
server.stop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
44
setup.py
Normal file
44
setup.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.command.install import install
|
||||||
|
|
||||||
|
NAME = 'Sauna control'
|
||||||
|
VERSION = '1'
|
||||||
|
AUTHOR = 'Thomas Klaehn'
|
||||||
|
EMAIL = 'tkl@blackfinn.de'
|
||||||
|
PACKAGES = ['config', 'control', 'heat', 'remotectrl', 'saunacontrol']
|
||||||
|
REQUIRES = ['w1thermsensor', 'RPi.GPIO']
|
||||||
|
|
||||||
|
SERVICEDIR = "/lib/systemd/system"
|
||||||
|
DAEMON_START_SCRIPT = os.path.join(SERVICEDIR, 'saunacontrol.service')
|
||||||
|
|
||||||
|
LOGFILE = "/var/log/sauna.log"
|
||||||
|
|
||||||
|
ENTRY_POINTS = {
|
||||||
|
'console_scripts': [
|
||||||
|
'saunacontrol = saunacontrol.__main__:main'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
class Install(install):
|
||||||
|
def run(self):
|
||||||
|
install.run(self)
|
||||||
|
os.makedirs(SERVICEDIR, exist_ok=True)
|
||||||
|
shutil.copyfile('saunacontrol.service', os.path.join(SERVICEDIR, DAEMON_START_SCRIPT))
|
||||||
|
os.chmod(DAEMON_START_SCRIPT, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
|
||||||
|
|
||||||
|
try:
|
||||||
|
open(LOGFILE, 'r')
|
||||||
|
except FileNotFoundError:
|
||||||
|
os.makedirs(os.path.dirname(LOGFILE), exist_ok=True)
|
||||||
|
open(LOGFILE, 'x')
|
||||||
|
os.chmod(LOGFILE, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH)
|
||||||
|
|
||||||
|
setup(name=NAME, version=VERSION, long_description=__doc__, author=AUTHOR, author_email=EMAIL,
|
||||||
|
packages=PACKAGES, zip_safe=False, install_requires=REQUIRES, entry_points=ENTRY_POINTS,
|
||||||
|
cmdclass={
|
||||||
|
'install': Install
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user