Initial commit
This commit is contained in:
commit
523732d482
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
dist/
|
||||
build/
|
||||
__pycache__/
|
||||
*.egg-info/
|
56
setup.py
Executable file
56
setup.py
Executable file
@ -0,0 +1,56 @@
|
||||
"""Setup"""
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
from setuptools import setup
|
||||
from setuptools.command.install import install
|
||||
|
||||
NAME = 'Watercontrol'
|
||||
VERSION = '1'
|
||||
AUTHOR = 'Thomas Klaehn'
|
||||
EMAIL = 'tkl@blackfinn.de'
|
||||
PACKAGES = ['watercontrol']
|
||||
REQUIRES = ['RPi.GPIO']
|
||||
CONFIG_FILE = 'config.json'
|
||||
PACKAGE_DATA = {
|
||||
'watercontrol': [
|
||||
'watercontrol/config.json'
|
||||
]
|
||||
}
|
||||
|
||||
SERVICEDIR = "/lib/systemd/system"
|
||||
START_SCRIPT = 'watercontrol.service'
|
||||
DAEMON_START_SCRIPT = os.path.join(SERVICEDIR, START_SCRIPT)
|
||||
LOGFILE = "/var/log/watercontrol.log"
|
||||
|
||||
ENTRY_POINTS = {
|
||||
'console_scripts': [
|
||||
'watercontrol = watercontrol.main:main'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class Install(install):
|
||||
"""Installer"""
|
||||
def run(self):
|
||||
install.run(self)
|
||||
os.makedirs(SERVICEDIR, exist_ok=True)
|
||||
shutil.copyfile(START_SCRIPT, DAEMON_START_SCRIPT)
|
||||
os.chmod(DAEMON_START_SCRIPT, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
|
||||
|
||||
try:
|
||||
open(LOGFILE, 'r', encoding="UTF-8")
|
||||
except FileNotFoundError:
|
||||
os.makedirs(os.path.dirname(LOGFILE), exist_ok=True)
|
||||
open(LOGFILE, 'x', encoding="UTF-8")
|
||||
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, include_package_data=True, package_data=PACKAGE_DATA, zip_safe=False,
|
||||
install_requires=REQUIRES, entry_points=ENTRY_POINTS, cmdclass={'install': Install})
|
11
watercontrol.service
Normal file
11
watercontrol.service
Normal file
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Watercontrol service
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
ExecStart=/usr/local/bin/watercontrol
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
0
watercontrol/__init__.py
Normal file
0
watercontrol/__init__.py
Normal file
36
watercontrol/config.json
Normal file
36
watercontrol/config.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"hostname": "localhost",
|
||||
"port": "64001",
|
||||
"water": [
|
||||
{
|
||||
"id": "1",
|
||||
"autostate": false,
|
||||
"pin": "27",
|
||||
"times": [
|
||||
{
|
||||
"on_time": "7:00",
|
||||
"off_time": "7:20"
|
||||
},
|
||||
{
|
||||
"on_time": "19:00",
|
||||
"off_time": "19:20"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"autostate": false,
|
||||
"pin": "17",
|
||||
"times": [
|
||||
{
|
||||
"on_time": "7:20",
|
||||
"off_time": "7:40"
|
||||
},
|
||||
{
|
||||
"on_time": "19:20",
|
||||
"off_time": "19:40"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
72
watercontrol/config.py
Normal file
72
watercontrol/config.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""config Module"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
class Config():
|
||||
"""Config class"""
|
||||
def __init__(self, config_file) -> None:
|
||||
self.config_file = config_file
|
||||
self.config = None
|
||||
|
||||
try:
|
||||
with open(self.config_file, "r", encoding="UTF-8") as handle:
|
||||
self.config = json.load(handle)
|
||||
except FileNotFoundError:
|
||||
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
||||
shutil.copyfile("hochbeet/config.json", self.config_file)
|
||||
with open(self.config_file, "r", encoding="UTF-8") as handle:
|
||||
self.config = json.load(handle)
|
||||
|
||||
|
||||
def hostinfo(self):
|
||||
"""Deliver hostinfo"""
|
||||
res = self.config["hostname"], int(self.config["port"])
|
||||
return res
|
||||
|
||||
|
||||
def get_water_autostate(self, ident: int):
|
||||
"""Return water auto state of entry belonging to ident"""
|
||||
for entry in self.config['water']:
|
||||
if int(entry['id']) == ident:
|
||||
return entry['autostate']
|
||||
return None
|
||||
|
||||
|
||||
def get_water_times(self, ident: int):
|
||||
"""Return water times of entry belonging to ident"""
|
||||
for entry in self.config['water']:
|
||||
if int(entry['id']) == ident:
|
||||
return entry['times']
|
||||
return None
|
||||
|
||||
|
||||
def set_water_autostate(self, ident: str):
|
||||
"""Set water auto state of entry belonging to ident"""
|
||||
for idx in range(len(self.config['water'])):
|
||||
if self.config['water'][idx]['id'] == ident:
|
||||
self.config['water'][idx]['autostate'] = True
|
||||
with open(self.config_file, "w", encoding="UTF-8") as handle:
|
||||
json.dump(self.config, handle)
|
||||
return
|
||||
|
||||
|
||||
def clear_water_autostate(self, ident: str):
|
||||
"""Clear water auto state of entry belonging to ident"""
|
||||
for idx in range(len(self.config['water'])):
|
||||
if self.config['water'][idx]['id'] == ident:
|
||||
self.config['water'][idx]['autostate'] = False
|
||||
with open(self.config_file, "w", encoding="UTF-8") as handle:
|
||||
json.dump(self.config, handle)
|
||||
return
|
||||
|
||||
|
||||
def set_water_times(self, ident: str, times):
|
||||
"""Set water times of entry belonging to ident"""
|
||||
for idx in range(len(self.config['water'])):
|
||||
if self.config['water'][idx]['id'] == ident:
|
||||
self.config['water'][idx]['times'] = times
|
||||
with open(self.config_file, "w", encoding="UTF-8") as handle:
|
||||
json.dump(self.config, handle)
|
||||
return
|
117
watercontrol/control.py
Normal file
117
watercontrol/control.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""control module"""
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
class Control(threading.Thread):
|
||||
"""Control class"""
|
||||
def __init__(self, configfile):
|
||||
threading.Thread.__init__(self)
|
||||
self.run_condition = True
|
||||
self.config_file = configfile
|
||||
self.config = None
|
||||
self.log = logging.getLogger()
|
||||
self.trigger_read_config = True
|
||||
self.water_state = []
|
||||
|
||||
def reload_config(self):
|
||||
"""Reload the config"""
|
||||
self.log.info("Reloading configuration triggered")
|
||||
self.trigger_read_config = True
|
||||
|
||||
def load_config(self):
|
||||
"""load the config"""
|
||||
try:
|
||||
with open(self.config_file, "r", encoding="UTF-8") as handle:
|
||||
self.config = json.load(handle)
|
||||
except FileNotFoundError:
|
||||
# create default config
|
||||
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
||||
shutil.copyfile("config/config.json", self.config_file)
|
||||
with open(self.config_file, "r", encoding="UTF-8") as handle:
|
||||
self.config = json.load(handle)
|
||||
for _ in range(len(self.config['water'])):
|
||||
self.water_state.append(False)
|
||||
# Configure all water pins
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
for entry in self.config['water']:
|
||||
pin = int(entry['pin'])
|
||||
GPIO.setup(pin, GPIO.OUT)
|
||||
GPIO.output(pin, 1)
|
||||
|
||||
def run(self):
|
||||
while self.run_condition:
|
||||
if self.trigger_read_config:
|
||||
self.trigger_read_config = False
|
||||
self.load_config()
|
||||
|
||||
# handle water entries
|
||||
water = self.config['water']
|
||||
water_index = 0
|
||||
for entry in water:
|
||||
now = datetime.datetime.now()
|
||||
if entry['autostate']:
|
||||
idx = 0
|
||||
if int(now.hour) >= 12:
|
||||
idx = 1
|
||||
on_time_pattern = entry['times'][idx]['on_time']
|
||||
on_time_pattern = on_time_pattern.split(':')
|
||||
on_time = now.replace(hour=int(on_time_pattern[0]),
|
||||
minute=int(on_time_pattern[1]),
|
||||
second=0,
|
||||
microsecond=0)
|
||||
off_time_pattern = entry['times'][idx]['off_time']
|
||||
off_time_pattern = off_time_pattern.split(':')
|
||||
off_time = now.replace(hour=int(off_time_pattern[0]),
|
||||
minute=int(off_time_pattern[1]),
|
||||
second=0,
|
||||
microsecond=0)
|
||||
pin = int(entry['pin'])
|
||||
|
||||
if now > on_time and now <= off_time and not self.water_state[water_index]:
|
||||
GPIO.output(pin, 0)
|
||||
self.water_state[water_index] = True
|
||||
self.log.info("water on")
|
||||
elif now > off_time and self.water_state[water_index]:
|
||||
GPIO.output(pin, 1)
|
||||
self.water_state[water_index] = False
|
||||
self.log.info("water off")
|
||||
water_index += 1
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def stop(self):
|
||||
"""Stop execution"""
|
||||
self.run_condition = False
|
||||
self.join()
|
||||
|
||||
def get_current_water_state(self, ident: int):
|
||||
"""Get water state"""
|
||||
if ident > 0 and ident <= len(self.water_state):
|
||||
return self.water_state[ident -1]
|
||||
return None
|
||||
|
||||
def set_water_state(self, ident: str):
|
||||
"""Set water state"""
|
||||
ident = int(ident)
|
||||
if ident > 0 and ident <= len(self.water_state):
|
||||
pin = int(self.config['water'][ident - 1]['pin'])
|
||||
self.water_state[ident - 1] = True
|
||||
self.log.info("water on by button")
|
||||
GPIO.output(pin, 0)
|
||||
|
||||
def clear_water_state(self, ident: str):
|
||||
"""Clear water state"""
|
||||
ident = int(ident)
|
||||
if ident > 0 and ident <= len(self.water_state):
|
||||
pin = int(self.config['water'][ident - 1]['pin'])
|
||||
self.water_state[ident - 1] = False
|
||||
self.log.info("water off by button")
|
||||
GPIO.output(pin, 1)
|
27
watercontrol/main.py
Normal file
27
watercontrol/main.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Entry point"""
|
||||
#!/usr/bin/env python
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from . import remote
|
||||
|
||||
LOG_LEVEL = logging.INFO
|
||||
LOG_FILE = "/var/log/watercontrol.log"
|
||||
LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
||||
|
||||
def main():
|
||||
"""Entry point"""
|
||||
logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL, filename=LOG_FILE)
|
||||
# logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL)
|
||||
|
||||
log = logging.getLogger()
|
||||
config_file = os.path.join(os.path.expanduser('~'), ".config/watercontrol/config.json")
|
||||
|
||||
log.info("Starting...")
|
||||
server = remote.RemoteControl(config_file)
|
||||
server.start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
53
watercontrol/remote.py
Normal file
53
watercontrol/remote.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""Remote module"""
|
||||
|
||||
import logging
|
||||
|
||||
from xmlrpc.server import SimpleXMLRPCServer
|
||||
from . import config, control
|
||||
|
||||
|
||||
class RemoteControl():
|
||||
"""RemoteControl"""
|
||||
def __init__(self, configfile):
|
||||
self.log = logging.getLogger()
|
||||
self.config = config.Config(configfile)
|
||||
|
||||
host = (self.config.hostinfo())
|
||||
self.server = SimpleXMLRPCServer(host, allow_none=True)
|
||||
self.control = control.Control(configfile)
|
||||
|
||||
self.server.register_function(self.set_water_autostate, 'set_water_autostate')
|
||||
self.server.register_function(self.clear_water_autostate, 'clear_water_autostate')
|
||||
self.server.register_function(self.set_water_times, 'set_water_times')
|
||||
|
||||
self.server.register_function(self.config.get_water_autostate, 'get_water_autostate')
|
||||
self.server.register_function(self.config.get_water_times, 'get_water_times')
|
||||
|
||||
self.server.register_function(self.control.set_water_state, 'set_water_state')
|
||||
self.server.register_function(self.control.clear_water_state, 'clear_water_state')
|
||||
|
||||
def set_water_times(self, ident: str, times):
|
||||
"""Set water times"""
|
||||
self.config.set_water_times(ident, times)
|
||||
self.control.reload_config()
|
||||
|
||||
def set_water_autostate(self, ident: str):
|
||||
"""Set water auto state"""
|
||||
self.config.set_water_autostate(ident)
|
||||
self.control.reload_config()
|
||||
|
||||
def clear_water_autostate(self, ident: str):
|
||||
"""Clear water auto state"""
|
||||
self.config.clear_water_autostate(ident)
|
||||
self.control.reload_config()
|
||||
|
||||
|
||||
def start(self):
|
||||
"""Start remote server"""
|
||||
self.log.info('Control-c to quit')
|
||||
self.control.start()
|
||||
self.server.serve_forever()
|
||||
|
||||
self.log.info("Shutting down...")
|
||||
self.control.stop()
|
||||
self.log.info("...done. Exiting...")
|
Loading…
Reference in New Issue
Block a user