Initial commit
This commit is contained in:
		
						commit
						9593d77e10
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.swp
 | 
			
		||||
ground-control.cfg
 | 
			
		||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2023 Mike Cifelli
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										42
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
# ground-control
 | 
			
		||||
 | 
			
		||||
A Raspberry Pi project for coordinating actions between systems.
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
```
 | 
			
		||||
cp ground-control.sample.cfg ground-control.cfg
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
```
 | 
			
		||||
sudo ./install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Output
 | 
			
		||||
By default output will be in `/var/log/syslog`.
 | 
			
		||||
A separate log file can be used by creating `/etc/rsyslog.d/30-ground-control.conf` containing:
 | 
			
		||||
```
 | 
			
		||||
if $programname == 'ground-control' then /var/log/ground-control.log
 | 
			
		||||
& stop
 | 
			
		||||
```
 | 
			
		||||
and then restart the rsyslog service:
 | 
			
		||||
```
 | 
			
		||||
sudo systemctl restart rsyslog
 | 
			
		||||
```
 | 
			
		||||
This log file can be rotated by creating `/etc/logrotate.d/ground-control` containing:
 | 
			
		||||
```
 | 
			
		||||
/var/log/ground-control.log
 | 
			
		||||
{
 | 
			
		||||
        rotate 14
 | 
			
		||||
        daily
 | 
			
		||||
        create
 | 
			
		||||
        missingok
 | 
			
		||||
        notifempty
 | 
			
		||||
        compress
 | 
			
		||||
        delaycompress
 | 
			
		||||
        postrotate
 | 
			
		||||
                /usr/lib/rsyslog/rsyslog-rotate
 | 
			
		||||
        endscript
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										91
									
								
								ground-control.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								ground-control.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
			
		||||
import json
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from configparser import ConfigParser
 | 
			
		||||
from signal import signal
 | 
			
		||||
from signal import SIGTERM
 | 
			
		||||
from time import sleep
 | 
			
		||||
 | 
			
		||||
config = ConfigParser()
 | 
			
		||||
config.read('ground-control.cfg')
 | 
			
		||||
 | 
			
		||||
garage_host = config['systems'].get('garage')
 | 
			
		||||
marshaller_host = config['systems'].get('marshaller')
 | 
			
		||||
 | 
			
		||||
ntfy = config['ntfy'].get('host')
 | 
			
		||||
topic = config['ntfy'].get('topic')
 | 
			
		||||
auth = config['ntfy'].get('auth')
 | 
			
		||||
 | 
			
		||||
ntfy_uri = f'https://{ntfy}/{topic}/json?auth={auth}'
 | 
			
		||||
garage_uri = f'https://{garage_host}'
 | 
			
		||||
marshaller_uri = f'https://{marshaller_host}/on'
 | 
			
		||||
 | 
			
		||||
is_east_door_previously_open = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HaltException(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    signal(SIGTERM, raise_halt_exception)
 | 
			
		||||
 | 
			
		||||
    isRunning = True
 | 
			
		||||
    isFirstRun = True
 | 
			
		||||
 | 
			
		||||
    while isRunning:
 | 
			
		||||
        try:
 | 
			
		||||
            if isFirstRun:
 | 
			
		||||
                isFirstRun = False
 | 
			
		||||
            else:
 | 
			
		||||
                print('waiting to restart main loop', flush=True)
 | 
			
		||||
                sleep(15)
 | 
			
		||||
 | 
			
		||||
            print('listening for events', flush=True)
 | 
			
		||||
            listen_for_notifications()
 | 
			
		||||
        except HaltException:
 | 
			
		||||
            isRunning = False
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(e, flush=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def listen_for_notifications():
 | 
			
		||||
    with requests.get(ntfy_uri, stream=True, timeout=60) as response:
 | 
			
		||||
        for line in response.iter_lines():
 | 
			
		||||
            if line:
 | 
			
		||||
                data = json.loads(line.decode('utf-8'))
 | 
			
		||||
 | 
			
		||||
                if data['event'] == 'message' and is_east_door_newly_open():
 | 
			
		||||
                    activate_marshaller()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_east_door_newly_open():
 | 
			
		||||
    global is_east_door_previously_open
 | 
			
		||||
 | 
			
		||||
    is_door_open_now = is_east_door_currently_open()
 | 
			
		||||
 | 
			
		||||
    if is_door_open_now != is_east_door_previously_open:
 | 
			
		||||
        is_east_door_previously_open = is_door_open_now
 | 
			
		||||
 | 
			
		||||
        return is_door_open_now
 | 
			
		||||
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_east_door_currently_open():
 | 
			
		||||
    return requests.get(garage_uri, timeout=6).json()['east-door'] == 'opened'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def activate_marshaller():
 | 
			
		||||
    print('activating marshaller', flush=True)
 | 
			
		||||
    requests.get(marshaller_uri, timeout=1)
 | 
			
		||||
    requests.get(marshaller_uri, timeout=1)
 | 
			
		||||
    requests.get(marshaller_uri, timeout=1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def raise_halt_exception(signum, frame):
 | 
			
		||||
    raise HaltException()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										8
									
								
								ground-control.sample.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ground-control.sample.cfg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
[systems]
 | 
			
		||||
garage=localhost
 | 
			
		||||
marshaller=localhost
 | 
			
		||||
 | 
			
		||||
[ntfy]
 | 
			
		||||
host=ntfy.sh
 | 
			
		||||
topic=topic
 | 
			
		||||
auth=auth
 | 
			
		||||
							
								
								
									
										14
									
								
								ground-control.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ground-control.service
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
[Unit]
 | 
			
		||||
Description=Ground Control
 | 
			
		||||
After=multi.user.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
WorkingDirectory=$workingDirectory
 | 
			
		||||
ExecStart=$execStart
 | 
			
		||||
Restart=on-failure
 | 
			
		||||
SyslogIdentifier=ground-control
 | 
			
		||||
User=$user
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
							
								
								
									
										36
									
								
								install
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										36
									
								
								install
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
#! /usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from string import Template
 | 
			
		||||
from subprocess import check_call
 | 
			
		||||
from subprocess import check_output
 | 
			
		||||
 | 
			
		||||
EXEC       = 'ground-control.py'
 | 
			
		||||
SERVICE    = 'ground-control.service'
 | 
			
		||||
SYSTEM_DIR = '/etc/systemd/system'
 | 
			
		||||
 | 
			
		||||
CURRENT_DIR      = os.path.dirname(os.path.realpath(__file__))
 | 
			
		||||
SERVICE_TEMPLATE = os.path.join(CURRENT_DIR, SERVICE)
 | 
			
		||||
SERVICE_FILE     = os.path.join(SYSTEM_DIR, SERVICE)
 | 
			
		||||
PYTHON           = sys.executable
 | 
			
		||||
EXEC_START       = f'{PYTHON} {EXEC}'
 | 
			
		||||
USER             = check_output(['logname']).decode('utf-8').strip()
 | 
			
		||||
 | 
			
		||||
with open(SERVICE_TEMPLATE) as f:
 | 
			
		||||
    serviceTemplate = Template(f.read())
 | 
			
		||||
 | 
			
		||||
serviceFile = serviceTemplate.substitute(
 | 
			
		||||
    workingDirectory=CURRENT_DIR,
 | 
			
		||||
    execStart=EXEC_START,
 | 
			
		||||
    user=USER
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
with open(SERVICE_FILE, 'w') as f:
 | 
			
		||||
    f.write(serviceFile)
 | 
			
		||||
 | 
			
		||||
check_call(['systemctl', 'daemon-reload'])
 | 
			
		||||
check_call(['systemctl', 'enable', '--no-pager', SERVICE])
 | 
			
		||||
check_call(['systemctl', 'restart', '--no-pager', SERVICE])
 | 
			
		||||
check_call(['systemctl', 'status', '--no-pager', SERVICE])
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user