Initial commit
This commit is contained in:
commit
9593d77e10
|
@ -0,0 +1,3 @@
|
||||||
|
__pycache__/
|
||||||
|
*.swp
|
||||||
|
ground-control.cfg
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
|
@ -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()
|
|
@ -0,0 +1,8 @@
|
||||||
|
[systems]
|
||||||
|
garage=localhost
|
||||||
|
marshaller=localhost
|
||||||
|
|
||||||
|
[ntfy]
|
||||||
|
host=ntfy.sh
|
||||||
|
topic=topic
|
||||||
|
auth=auth
|
|
@ -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
|
|
@ -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