buzz twice to give users more time
[saartuer.git] / tyshell
diff --git a/tyshell b/tyshell
index 0b9b94361fde70972e65c48a897716e7ee8f9316..b535f2b9eef3d47b4938aa964c8e0041b8528ebb 100755 (executable)
--- a/tyshell
+++ b/tyshell
@@ -5,6 +5,10 @@ import shlex
 import sys
 import subprocess
 import socket
 import sys
 import subprocess
 import socket
+import pwd
+import grp
+import traceback
+from collections import namedtuple
 
 tuerSock = "/run/tuer.sock"
 
 
 tuerSock = "/run/tuer.sock"
 
@@ -16,11 +20,14 @@ except IOError:
     pass
 import atexit
 atexit.register(readline.write_history_file, histfile)
     pass
 import atexit
 atexit.register(readline.write_history_file, histfile)
-atexit.register(print, "Bye")
 
 # available commands
 def helpcmd(c):
 
 # available commands
 def helpcmd(c):
-       print("Available commands: %s" % ", ".join(sorted(longcommands.keys())))
+       if (len(c) > 1):
+               print(commands.get(c[1],(None,'Can\'t find help for command %s'%(c[1]))).helpstring)
+       else:
+               print("Available commands: %s" % ", ".join(sorted(commands.keys())))
+               print("Use 'help command' to get more information on the command 'command'")
 
 def extcmd(cmd):
        def run(c):
 
 def extcmd(cmd):
        def run(c):
@@ -31,60 +38,65 @@ def extcmd(cmd):
 
 def sendcmd(addr, cmd):
        def run(c):
 
 def sendcmd(addr, cmd):
        def run(c):
-               print("Running %s..." % (cmd))
+               print("206 Sending command %s..." % (cmd))
                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                s.connect(addr)
                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                s.connect(addr)
+               s.settimeout(60.0)
                s.send(cmd.encode())
                s.send(cmd.encode())
-               data = s.recv(4)
+               while True:
+                       data = s.recv(256)
+                       if not len(data): break
+                       print(data.decode('utf-8'))
                s.close()
                s.close()
-               print("...done")
-               if data != b'1':
-                       print("Received unexpected answer %s" % str(data))
        return run
 
 def exitcmd(c):
        return run
 
 def exitcmd(c):
-       sys.exit(0)
+       print("Bye")
+       return True
+
+def whocmd(c):
+       for n in grp.getgrnam("tuer").gr_mem:
+               p = pwd.getpwnam(n)
+               print (p.pw_name, " - ", p.pw_gecos)
 
 
-commands = {
-       'exit': exitcmd,
-       'help': helpcmd,
-       'open': sendcmd(tuerSock, 'open'),
-       'close': sendcmd(tuerSock, 'close'),
-       'buzz': sendcmd(tuerSock, 'buzz'),
-}
+def alias (cmds, aliases):
+       for newname, oldname in aliases.items():
+               cmds[newname] = cmds[oldname]
+       return cmds
 
 
-# command convenience shortcuts
-def filterCommonPrefix (strings, length):
-       # ignores duplicates in the string list "strings"
-       toremove=[]
-       for a in strings:
-               for b in strings:
-                       if a != b:
-                               if a[:length] == b[:length]:
-                                       toremove.append(a)
-                                       toremove.append(b)
-       ret = list(strings) # copy
-       for x in toremove:
+def prompt_sure(f,msg):
+       def run(c):
                try:
                try:
-                       ret.remove(x)
-               except ValueError:
-                       pass
-       return ret
+                       command = input("%s Are you sure? (yes/no) > " % msg)
+               except EOFError:
+                       print()
+                       return
+               if command[0] == 'y':
+                       return f(c)
+       return run
 
 
-def shortcutify (dic):
-       maxlen = 0
-       for x in dic.keys():
-               if len(x) > maxlen:
-                       maxlen = len(x)
-       for i in range(maxlen):
-               shortable = filterCommonPrefix (dic.keys(), i)
-               for x in shortable:
-                       dic[x[:i]] = dic[x]
-       return dic # only for convenience, as dic is passed by reference
+CmdEntry = namedtuple('CmdEntry','function helpstring')
 
 
-longcommands = commands.copy()
-shortcutify (commands)
+commands = alias({
+       'exit': CmdEntry(exitcmd, 'Quits this shell'),
+       'help': CmdEntry(helpcmd, 'Helps you getting to know the available commands'),
+       'unlock': CmdEntry(sendcmd(tuerSock, 'unlock'), 'Will try to unlock the apartment door'),
+       'lock': CmdEntry(sendcmd(tuerSock, 'lock'), 'If in fallback mode, try to lock the apartment door. If not in fallback mode, you must use the switch near the door.'),
+       'buzz': CmdEntry(sendcmd(tuerSock, 'buzz'), 'Will buzz the buzzer for the street door'),
+       'who': CmdEntry(whocmd, 'Shows the list of people, who are allowed to control this system'),
+       'fallback_mode_on': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_on'),'WARNING: This action will be reported to the admins. Use this only in case of Sphinx hardware failure when you need to ignore erroneous sensor input!'), 'Sets the system in a state where it is less dependent on sensoric input. Use it only when sensors are broken.'),
+       'fallback_mode_off': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_off'),'WARNING: This action will be reported to the admins. Use this only if you have fixed the sensors of the Sphinx or activated fallback mode by accident!'), 'Resets the system to the default state. Use this when you have just repaired the sensors of the Sphinx.'),
+       'status': CmdEntry(sendcmd(tuerSock, 'status'), 'Shows internal state and sensor information.'),
+},{
+       # aliases
+       'open': 'unlock',
+})
 
 
+def complete_command(cmd):
+       '''returns a list of commands (as strings) starting with cmd'''
+       return list(filter(lambda x: x.startswith(cmd), commands.keys()))
+readline.set_completer(lambda cmd, num: (complete_command(cmd)+[None])[num]) # wrap complete_command for readline's weird completer API
+readline.parse_and_bind("tab: complete") # run completion on tab
 
 # input loop
 print("Welcome to tyshell. Use help to see what you can do.")
 
 # input loop
 print("Welcome to tyshell. Use help to see what you can do.")
@@ -96,12 +108,23 @@ while True:
                break
        command = shlex.split(command)
        if not len(command): continue
                break
        command = shlex.split(command)
        if not len(command): continue
-       # execute command
-       if command[0] in commands:
+       # find suiting commands
+       if command[0] in commands: # needed in case a complete command is a prefix of another one
+               cmdoptions = [command[0]]
+       else:
+               cmdoptions = complete_command(command[0])
+       # check how many we found
+       if len(cmdoptions) == 0: # no commands fit prefix
+               print("Command %s not found. Use help." % command[0])
+       elif len(cmdoptions) == 1: # exactly one command fits (prefix)
                try:
                try:
-                       commands[command[0]](command)
+                       res = commands[cmdoptions[0]].function(command)
+                       if res: break
                except Exception as e:
                        print("Error while executing %s: %s" % (command[0], str(e)))
                except Exception as e:
                        print("Error while executing %s: %s" % (command[0], str(e)))
-       else:
-               print("Command %s not found. Use help." % command[0])
+                       #print(traceback.format_exc())
+       else: # multiple commands fit the prefix
+               print("Ambiguous command prefix, please choose one of the following:")
+               print("\t", " ".join(cmdoptions))
+               # TODO: put current "command[0]" into the shell for the next command, but such that it is deletable with backspace