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