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