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