123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- #!/usr/bin/env python3
-
- import argparse
- import asyncio
- import logging
- from logging.handlers import WatchedFileHandler
-
- import websockets
-
- from .command import Command
- from .config import parse_config
- from .server import websocket_handler
- from .session import Session
-
- async def main(listen_addr:str, port:int):
- """ Create the websocket and handle new connection with websocket_handler
- """
- logging.warning('Listening on %s:%d' % (listen_addr, port))
- async with websockets.serve(websocket_handler, listen_addr, port):
- await asyncio.Future()
-
- def cli():
- """ Run CLI """
- proto_desc = '''
- Before entering the main loop, the listening server waits for 1st message
- containing a session ID. If an empty/dummy/invalid session ID is sent by the
- client, the server replies with a new, valid, session ID and enter the main loop.
- If a valid session ID is sent, the server reply the same session ID and enter
- the main loop, formated using SESSION:<SESSION_ID>.
-
-
- Main loop :
- -----------
-
- The server send the current time (ISO 8601 format) every second.
-
- Command results :
- -----------------
-
- The client can interact with the server sending one of the command listed using
- the -L flag.
-
- When a client send a command, the next message from the server will be a command
- result. Results are string formatted using <STATUS>:<DETAILS>. Status can be
- one of 'ERR' or 'OK', details are optionnale for OK statuses.
-
- Alarms rings :
- --------------
-
- When an alarm is ringing, after sending time, the server send messages formatted
- using ALRM:<NAME> where NAME is the name of a ringing alarm. If multiple alarms
- are ringing, multiple ALRM: messages are sent every seconds.
-
- Timezone informations :
- -----------------------
-
- Before the first time is sent and every time the timezone changes (inclucing
- summer/winter transitions), the clock. Timezones messages are formatted using
- TZN:<TZNAME>
-
- Messages types summary :
- ------------------------
- Session ID
- SESSION:<SESSION_ID>
- Clock tick
- ISO8601_Datetime
- Timezone name
- TZN:<TZNAME>
- Alarm rings
- ALRM:<JSON_ARRAY>
- Command results
- OK:<DETAILS>
- ERR:<REASON>
- '''
- parser = argparse.ArgumentParser(description='Run a simple clock websocket \
- server sending time every seconds', epilog=proto_desc,
- formatter_class=argparse.RawDescriptionHelpFormatter)
-
- parser.add_argument('-C', '--config', default=None,
- help="Indicate a config.ini file")
- parser.add_argument('-p', '--port', default=80, type=int,
- help='The TCP port to listen to')
- parser.add_argument('-l', '--listen-address', default='localhost', type=str,
- help='Bind to this address (localhost by default)')
- parser.add_argument('-L', '--list-commands', default=False,
- action='store_true',
- help='List available commands on the websocket and exit')
- parser.add_argument('-H', '--help-command', metavar='CMD',
- choices=Command.list()+['ALL'],
- help='Print a command help and exit')
- parser.add_argument('-v', '--verbose', action='count', default=0,
- help='Increase verbosity level')
- parser.add_argument('-F', '--file-session', action="store_true", default=False,
- help="Use persistent file session instead of RAM session")
- parser.add_argument('-S', '--session-dir', type=str, default=None,
- help="Store session in given directory (implies -F)")
- parser.add_argument('--logfile', type=str,
- help="Log to given file instead of stderr")
-
- args = parser.parse_args()
-
- if args.config:
- parse_config(args.config, args)
-
- loglvls = [logging.WARN, logging.INFO, logging.DEBUG]
- if args.verbose >= len(loglvls):
- lvl = loglvls[-1]
- else:
- lvl = loglvls[args.verbose]
- bconfig_kwargs = {'level':lvl,
- 'format':'%(asctime)s %(levelname)s:%(message)s'}
- if args.logfile:
- bconfig_kwargs['handlers'] = [WatchedFileHandler(args.logfile)]
- logging.basicConfig(**bconfig_kwargs)
-
- if args.file_session or args.session_dir:
- Session.set_file_handler(args.session_dir)
-
- all_cmd = Command.all()
- if args.list_commands:
- for cmd in all_cmd.values():
- usage = ':'.join(cmd.usage.split(':')[1:]).strip()
- print('# %s\n\t%s' % (cmd.description, usage))
- exit(0)
-
- if args.help_command:
- if args.help_command == 'ALL':
- print(('-'*16).join([cmd.help
- for cmd in all_cmd.values()]))
- exit(0)
- if args.help_command not in all_cmd:
- logging.critical('Unknown command %r' % args.help_command)
- exit(1)
- print(all_cmd[args.help_command].help)
- exit(0)
-
- asyncio.run(main(args.listen_address, args.port))
-
- if __name__ == '__main__':
- cli()
|