Initial commit
This commit is contained in:
		
							
								
								
									
										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
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					     )
 | 
				
			||||||
		Reference in New Issue
	
	Block a user