2018-10-09 01:42:16 +02:00
|
|
|
"""
|
|
|
|
Doorlockd -- Binary Kitchen's smart door opener
|
|
|
|
|
2019-06-14 00:25:53 +02:00
|
|
|
Copyright (c) Binary Kitchen e.V., 2018-2019
|
2018-10-09 01:42:16 +02:00
|
|
|
|
|
|
|
Author:
|
|
|
|
Ralf Ramsauer <ralf@binary-kitchen.de>
|
|
|
|
|
|
|
|
This work is licensed under the terms of the GNU GPL, version 2. See
|
|
|
|
the LICENSE file in the top-level directory.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
details.
|
|
|
|
"""
|
|
|
|
|
2018-10-09 00:35:14 +02:00
|
|
|
import logging
|
|
|
|
|
2018-10-08 22:59:34 +02:00
|
|
|
from enum import Enum
|
|
|
|
from random import sample
|
2019-06-14 00:25:53 +02:00
|
|
|
from subprocess import run
|
2018-10-09 00:35:14 +02:00
|
|
|
from threading import Thread
|
|
|
|
from time import sleep
|
|
|
|
from os.path import join
|
2022-11-16 20:58:32 +01:00
|
|
|
from pydoorlock.AvrDoorlock import AvrDoorlockBackend
|
2022-11-16 21:09:26 +01:00
|
|
|
from pydoorlock.NukiBridge import NukiBridge
|
2022-11-16 20:58:32 +01:00
|
|
|
from pydoorlock.SimulationBackend import SimulationBackend
|
|
|
|
|
|
|
|
from .Config import Config
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
from .Door import DoorState
|
2019-06-14 21:09:03 +02:00
|
|
|
from .Protocol import Protocol
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
log = logging.getLogger()
|
2018-10-08 22:59:34 +02:00
|
|
|
|
|
|
|
# copied from sudo
|
|
|
|
eperm_insults = {
|
|
|
|
'Wrong! You cheating scum!',
|
|
|
|
'And you call yourself a Rocket Scientist!',
|
|
|
|
'No soap, honkie-lips.',
|
|
|
|
'Where did you learn to type?',
|
|
|
|
'Are you on drugs?',
|
|
|
|
'My pet ferret can type better than you!',
|
|
|
|
'You type like i drive.',
|
|
|
|
'Do you think like you type?',
|
|
|
|
'Your mind just hasn\'t been the same since the electro-shock, has it?',
|
|
|
|
'Maybe if you used more than just two fingers...',
|
|
|
|
'BOB says: You seem to have forgotten your passwd, enter another!',
|
|
|
|
'stty: unknown mode: doofus',
|
|
|
|
'I can\'t hear you -- I\'m using the scrambler.',
|
|
|
|
'The more you drive -- the dumber you get.',
|
|
|
|
'Listen, broccoli brains, I don\'t have time to listen to this trash.',
|
|
|
|
'I\'ve seen penguins that can type better than that.',
|
|
|
|
'Have you considered trying to match wits with a rutabaga?',
|
|
|
|
'You speak an infinite deal of nothing',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def choose_insult():
|
2018-10-09 01:03:35 +02:00
|
|
|
return sample(eperm_insults, 1)[0]
|
2018-10-08 22:59:34 +02:00
|
|
|
|
|
|
|
|
2019-06-14 00:25:53 +02:00
|
|
|
def run_background(cmd):
|
|
|
|
run('%s &' % cmd, shell=True)
|
|
|
|
|
|
|
|
|
2018-10-08 22:59:34 +02:00
|
|
|
class DoorlockResponse(Enum):
|
|
|
|
Success = 0
|
|
|
|
Perm = 1
|
|
|
|
AlreadyActive = 2
|
|
|
|
# don't break old apps, value 3 is reserved now
|
|
|
|
RESERVED = 3
|
|
|
|
Inval = 4
|
2022-11-16 20:58:32 +01:00
|
|
|
BackendError = 5
|
2018-10-08 22:59:34 +02:00
|
|
|
|
|
|
|
EmergencyUnlock = 10,
|
|
|
|
ButtonLock = 11,
|
|
|
|
ButtonUnlock = 12,
|
|
|
|
ButtonPresent = 13,
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
if self == DoorlockResponse.Success:
|
|
|
|
return 'Yo, passt.'
|
|
|
|
elif self == DoorlockResponse.Perm:
|
|
|
|
return choose_insult()
|
|
|
|
elif self == DoorlockResponse.AlreadyActive:
|
|
|
|
return 'Zustand bereits aktiv'
|
|
|
|
elif self == DoorlockResponse.Inval:
|
|
|
|
return 'Das was du willst geht nicht.'
|
|
|
|
elif self == DoorlockResponse.EmergencyUnlock:
|
|
|
|
return '!!! Emergency Unlock !!!'
|
|
|
|
elif self == DoorlockResponse.ButtonLock:
|
|
|
|
return 'Closed by button'
|
|
|
|
elif self == DoorlockResponse.ButtonUnlock:
|
|
|
|
return 'Opened by button'
|
|
|
|
elif self == DoorlockResponse.ButtonPresent:
|
|
|
|
return 'Present by button'
|
2022-11-16 20:58:32 +01:00
|
|
|
elif self == DoorlockResponse.BackendError:
|
|
|
|
return "Backend Error"
|
2018-10-08 22:59:34 +02:00
|
|
|
|
|
|
|
return 'Error'
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
class DoorHandler:
|
|
|
|
state = DoorState.Closed
|
|
|
|
do_close = False
|
|
|
|
|
|
|
|
wave_lock = 'lock.wav'
|
|
|
|
wave_lock_button = 'lock_button.wav'
|
|
|
|
|
|
|
|
wave_present = 'present.wav'
|
|
|
|
wave_present_button = 'present.wav'
|
|
|
|
|
|
|
|
wave_unlock = 'unlock.wav'
|
|
|
|
wave_unlock_button = 'unlock_button.wav'
|
|
|
|
|
|
|
|
wave_zonk = 'zonk.wav'
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
def __init__(self, cfg: Config, sounds_prefix, scripts_prefix):
|
2018-10-09 01:06:59 +02:00
|
|
|
self._callback = None
|
|
|
|
|
2018-10-09 00:35:14 +02:00
|
|
|
self.sounds = cfg.boolean('SOUNDS')
|
|
|
|
if self.sounds:
|
|
|
|
self.sounds_prefix = sounds_prefix
|
|
|
|
|
|
|
|
self.scripts_prefix = scripts_prefix
|
|
|
|
self.run_hooks = cfg.boolean('RUN_HOOKS')
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
backend_type = cfg.str("BACKEND_TYPE", "backend")
|
|
|
|
print(backend_type)
|
|
|
|
if not backend_type:
|
|
|
|
log.error("No backend configured")
|
|
|
|
raise RuntimeError()
|
|
|
|
|
|
|
|
if backend_type == "avr":
|
|
|
|
self.backend = AvrDoorlockBackend(self)
|
2022-11-16 21:09:26 +01:00
|
|
|
elif backend_type == "nuki":
|
|
|
|
self.backend = NukiBridge(cfg.str("NUKI_ENDPOINT", "backend"),
|
|
|
|
cfg.str("NUKI_APITOKEN", "backend"),
|
|
|
|
cfg.str("NUKI_DEVICE", "backend"))
|
2022-11-16 20:58:32 +01:00
|
|
|
elif backend_type == "simulation":
|
|
|
|
self.backend = SimulationBackend(self)
|
|
|
|
else:
|
|
|
|
log.error(f"Unknown backend {backend_type}")
|
|
|
|
raise RuntimeError
|
|
|
|
|
|
|
|
def state_changed(self, new_state):
|
|
|
|
if new_state == DoorState.Open:
|
|
|
|
self.run_hook('post_unlock')
|
|
|
|
elif new_state == DoorState.Present:
|
|
|
|
self.run_hook('post_present')
|
|
|
|
elif new_state == DoorState.Closed:
|
|
|
|
self.run_hook('post_lock')
|
|
|
|
else:
|
2018-10-09 00:35:14 +02:00
|
|
|
return
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
self.state = new_state
|
|
|
|
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
def open(self):
|
|
|
|
if self.state == DoorState.Open:
|
|
|
|
return DoorlockResponse.AlreadyActive
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
if self.backend.set_state(DoorState.Open):
|
|
|
|
self.state = DoorState.Open
|
|
|
|
self.run_hook('post_unlock')
|
|
|
|
return DoorlockResponse.Success
|
|
|
|
|
|
|
|
return DoorlockResponse.BackendError
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
def present(self):
|
|
|
|
if self.state == DoorState.Present:
|
|
|
|
return DoorlockResponse.AlreadyActive
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
if self.backend.set_state(DoorState.Present):
|
|
|
|
self.state = DoorState.Present
|
|
|
|
self.run_hook('post_present')
|
|
|
|
return DoorlockResponse.Success
|
|
|
|
|
|
|
|
return DoorlockResponse.BackendError
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.state == DoorState.Closed:
|
|
|
|
return DoorlockResponse.AlreadyActive
|
|
|
|
|
2022-11-16 20:58:32 +01:00
|
|
|
if self.backend.set_state(DoorState.Closed):
|
|
|
|
self.state = DoorState.Closed
|
|
|
|
self.run_hook('post_lock')
|
|
|
|
return DoorlockResponse.Success
|
|
|
|
|
|
|
|
return DoorlockResponse.BackendError
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
def request(self, state):
|
|
|
|
old_state = self.state
|
|
|
|
if state == DoorState.Closed:
|
|
|
|
err = self.close()
|
|
|
|
elif state == DoorState.Present:
|
|
|
|
err = self.present()
|
|
|
|
elif state == DoorState.Open:
|
|
|
|
err = self.open()
|
|
|
|
|
|
|
|
self.sound_helper(old_state, self.state, False)
|
2018-10-09 01:06:59 +02:00
|
|
|
self.invoke_callback(err)
|
2018-10-09 00:35:14 +02:00
|
|
|
return err
|
|
|
|
|
|
|
|
def sound_helper(self, old_state, new_state, button):
|
|
|
|
if not self.sounds:
|
|
|
|
return
|
|
|
|
|
|
|
|
# TBD: Emergency Unlock
|
|
|
|
# wave_emergency = 'emergency_unlock.wav'
|
|
|
|
|
|
|
|
if old_state == new_state:
|
|
|
|
filename = self.wave_zonk
|
|
|
|
elif button:
|
|
|
|
if new_state == DoorState.Open:
|
|
|
|
filename = self.wave_unlock_button
|
|
|
|
elif new_state == DoorState.Present:
|
|
|
|
filename = self.wave_present_button
|
|
|
|
elif new_state == DoorState.Closed:
|
|
|
|
filename = self.wave_lock_button
|
|
|
|
else:
|
|
|
|
if new_state == DoorState.Open:
|
|
|
|
filename = self.wave_unlock
|
|
|
|
elif new_state == DoorState.Present:
|
|
|
|
filename = self.wave_present
|
|
|
|
elif new_state == DoorState.Closed:
|
|
|
|
filename = self.wave_lock
|
|
|
|
|
2019-06-14 00:25:53 +02:00
|
|
|
run_background('aplay %s' % join(self.sounds_prefix, filename))
|
2018-10-09 00:35:14 +02:00
|
|
|
|
|
|
|
def run_hook(self, script):
|
|
|
|
if not self.run_hooks:
|
|
|
|
log.info('Hooks disabled: not starting %s' % script)
|
|
|
|
return
|
|
|
|
log.info('Starting hook %s' % script)
|
2019-06-14 00:25:53 +02:00
|
|
|
run_background(join(self.scripts_prefix, script))
|
2018-10-09 01:06:59 +02:00
|
|
|
|
|
|
|
def register_callback(self, callback):
|
|
|
|
self._callback = callback
|
|
|
|
|
|
|
|
def invoke_callback(self, val):
|
|
|
|
if self._callback:
|
|
|
|
self._callback(val)
|