|
| 1 | +#!/usr/bin/python3 |
| 2 | + |
1 | 3 | """
|
2 |
| -this file contains configuration values for n1mm_view. |
3 |
| -This should be the only thing you would need to customize. |
| 4 | +this file is a rewrite of config.py to implement a configParser to keep the config in n1mm_view.ini |
4 | 5 | """
|
5 |
| -import datetime |
| 6 | + |
| 7 | +__author__ = 'Tom Schaefer NY4I' |
| 8 | +__copyright__ = 'Copyright 2024 Thomas M. Schaefer' |
| 9 | +__license__ = 'Simplified BSD' |
| 10 | + |
| 11 | + |
| 12 | +import configparser |
6 | 13 | import logging
|
| 14 | +import datetime |
| 15 | +import os |
| 16 | +import time |
7 | 17 |
|
8 |
| -""" name of database file """ |
9 |
| -DATABASE_FILENAME = 'n1mm_view.db' |
10 |
| -""" Name of the event/contest """ |
11 |
| -EVENT_NAME = 'N4N Field Day' |
12 |
| -""" start time of the event/contest in YYYY-MM-DD hh:mm:ss format """ |
13 |
| - |
14 |
| -EVENT_START_TIME = datetime.datetime.strptime('2024-06-22 18:00:00', '%Y-%m-%d %H:%M:%S') |
15 |
| -""" end time of the event/contest """ |
16 |
| -EVENT_END_TIME = datetime.datetime.strptime('2024-06-23 17:59:59', '%Y-%m-%d %H:%M:%S') |
17 |
| -""" port number used by N1MM+ for UDP broadcasts This matches the port you set in N1MM Configurator UDP logging """ |
18 |
| -N1MM_BROADCAST_PORT = 12060 |
19 |
| -""" |
20 |
| -broadcast IP address, used by log re-player. |
21 |
| -This could be the IP of the N1MM master, or just the last address in the network segment |
22 |
| -""" |
23 |
| -N1MM_BROADCAST_ADDRESS = '192.168.1.255' |
24 |
| -""" n1mm+ log file name used by replayer """ |
25 |
| -N1MM_LOG_FILE_NAME = 'FD2024-N4N.s3db' |
26 |
| -""" QTH here is the location of your event. We mark this location with a red dot when we generate the map views.""" |
27 |
| -""" QTH Latitude """ |
28 |
| -QTH_LATITUDE = 34.0109629 |
29 |
| -""" QTH Longitude """ |
30 |
| -QTH_LONGITUDE = -84.4616047 |
31 |
| -""" number of seconds before automatic display change to the next screen """ |
32 |
| -DISPLAY_DWELL_TIME = 6 |
33 |
| -""" |
34 |
| -number of seconds before automatic info recalculation from database. Too low makes the Pi work harder. |
35 |
| -Too high makes a lag in viewing your results. |
36 |
| -""" |
37 |
| -DATA_DWELL_TIME = 60 |
38 |
| -""" log level for apps -- one of logging.WARN, logging.INFO, logging.DEBUG """ |
39 |
| -LOG_LEVEL = logging.DEBUG |
40 |
| -# |
41 |
| -"""images directory, or None if not writing image files""" |
42 |
| -IMAGE_DIR = None # '/mnt/ramdisk/n1mm_view/html' |
43 |
| - |
44 |
| -""" set HEADLESS True to not open graphics window. This is for using only the Apache option.""" |
45 |
| -HEADLESS = False |
46 |
| - |
47 |
| -# This should match the directory above we want to send from the directory to which we write. |
48 |
| -POST_FILE_COMMAND = 'rsync -avz /mnt/ramdisk/n1mm_view/html/* sparc:www/n1mm_view/html' |
49 |
| - |
50 |
| -""" Font Sizes """ |
51 |
| -# If font seems too big, try 60 for VIEW_FONT and 100 for BIGGER_FONT |
52 |
| -VIEW_FONT = 64 |
53 |
| -BIGGER_FONT = 180 |
| 18 | +# Note LOG_FORMATS is here rather than constants.py to avoid a circular import |
| 19 | +LOG_FORMAT = '%(asctime)s.%(msecs)03d %(levelname)-8s [%(module)s::%(funcName)s] %(message)s' |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +BASE_CONFIG_NAME = 'n1mm_view.ini' |
| 24 | +CONFIG_NAMES = [ os.path.dirname(__file__) + '/' + BASE_CONFIG_NAME |
| 25 | + ,os.path.expanduser('~/' + BASE_CONFIG_NAME) |
| 26 | + ,os.path.expanduser('~/.config/' + BASE_CONFIG_NAME) |
| 27 | + ] |
| 28 | + |
| 29 | +# Setup logging. This is the first occurrence so this is the only place basicConfig is called |
| 30 | +logging.basicConfig( format=LOG_FORMAT, datefmt='%Y-%m-%d %H:%M:%S' |
| 31 | + ,level=logging.DEBUG # Set to DEBUG so we get all until we grab the value from the config file. THis allows config.py to log before we read the log level |
| 32 | + ) |
| 33 | +logging.Formatter.converter = time.gmtime |
| 34 | +class Singleton(type): |
| 35 | + def __init__(self, name, bases, mmbs): |
| 36 | + super(Singleton, self).__init__(name, bases, mmbs) |
| 37 | + self._instance = super(Singleton, self).__call__() |
| 38 | + |
| 39 | + def __call__(self, *args, **kw): |
| 40 | + return self._instance |
| 41 | + |
| 42 | +class Config(metaclass = Singleton): |
| 43 | + |
| 44 | + def __init__(self, *args, **kw): |
| 45 | + |
| 46 | + cfg = configparser.ConfigParser() |
| 47 | + # Find and read ini file |
| 48 | + readCFGName = cfg.read(CONFIG_NAMES) |
| 49 | + # Check if there was just one config file found or none at all - Error in both cases so exit |
| 50 | + n = len(readCFGName) # Number of config files found |
| 51 | + if n > 1: |
| 52 | + print ('ConfigParser found more than one config file named %s' % (BASE_CONFIG_NAME)) |
| 53 | + for s in readCFGName: |
| 54 | + print (' Found %s' % (s)) |
| 55 | + print ('Please use ONLY ONE file named %s in one of the following locations:' % (BASE_CONFIG_NAME)) |
| 56 | + for s in CONFIG_NAMES: |
| 57 | + print (' %s' % (s)) |
| 58 | + exit () |
| 59 | + elif n == 0: |
| 60 | + print ('ConfigParser cannot find a config file named %s' % (BASE_CONFIG_NAME)) |
| 61 | + print ('Please create ONLY ONE config file named %s in one of the following locations:' % (BASE_CONFIG_NAME)) |
| 62 | + for s in CONFIG_NAMES: |
| 63 | + print (' %s' % (s)) |
| 64 | + exit () |
| 65 | + |
| 66 | + |
| 67 | + |
| 68 | + # Get logging level set first for subsequent logging... |
| 69 | + self.LOG_LEVEL = cfg.get('GLOBAL','LOG_LEVEL',fallback='ERROR') |
| 70 | + logging.info('Setting log level to %s' % (self.LOG_LEVEL)) |
| 71 | + |
| 72 | + # Note that basicConfig is called again since n1mm_view uses the class methods in logging. |
| 73 | + # While there is a setLevel to dynamically set the level, it is not a class function. |
| 74 | + # So you have to call basicConfig again with the force parameter True to override the existing one. |
| 75 | + # If rather than call the class function, logging was instantiated as logger (accessible to all) then it could just use setLevel, but that is a bigger refactor. |
| 76 | + |
| 77 | + logging.basicConfig( format=LOG_FORMAT, datefmt='%Y-%m-%d %H:%M:%S' |
| 78 | + ,level=self.LOG_LEVEL |
| 79 | + ,force = True |
| 80 | + ) |
| 81 | + |
| 82 | + logging.info ('Reading config file @ %s' % (readCFGName)) |
| 83 | + |
| 84 | + self.DATABASE_FILENAME = cfg.get('GLOBAL','DATABASE_FILENAME',fallback='n1mm_view.db') |
| 85 | + logging.info ('Using database file %s' % (self.DATABASE_FILENAME)) |
| 86 | + |
| 87 | + self.LOGO_FILENAME = cfg.get('GLOBAL','LOGO_FILENAME',fallback='logo.png') |
| 88 | + if not os.path.exists(self.LOGO_FILENAME): |
| 89 | + logging.error('Logo file %s does not exist' % (self.LOGO_FILENAME)) |
| 90 | + else: |
| 91 | + logging.info ('Using logo file %s' % (self.LOGO_FILENAME)) |
| 92 | + |
| 93 | + self.EVENT_NAME = cfg.get('EVENT INFO','NAME') |
| 94 | + |
| 95 | + dt = cfg.get('EVENT INFO','START_TIME') |
| 96 | + try: |
| 97 | + self.EVENT_START_TIME = datetime.datetime.strptime(dt, '%Y-%m-%d %H:%M:%S') |
| 98 | + except (ValueError, TypeError): |
| 99 | + logging.exception('*** INVALID START_TIME *** Value for START_TIME (%s) is not valid' % (dt)) |
| 100 | + exit() |
| 101 | + |
| 102 | + dt = cfg.get('EVENT INFO','END_TIME') |
| 103 | + try: |
| 104 | + self.EVENT_END_TIME = datetime.datetime.strptime(dt, '%Y-%m-%d %H:%M:%S') |
| 105 | + except (ValueError, TypeError): |
| 106 | + logging.exception('*** INVALID END_TIME *** Value for END_TIME (%s) is not valid' % (dt)) |
| 107 | + exit() |
| 108 | + |
| 109 | + self.N1MM_BROADCAST_PORT = cfg.getint('N1MM INFO','BROADCAST_PORT',fallback=12060) |
| 110 | + logging.info ('LIstening on UDP port %d' % (self.N1MM_BROADCAST_PORT)) |
| 111 | + self.N1MM_BROADCAST_ADDRESS = cfg.get('N1MM INFO','BROADCAST_ADDRESS') |
| 112 | + self.N1MM_LOG_FILE_NAME = cfg.get('N1MM INFO','LOG_FILE_NAME') |
| 113 | + |
| 114 | + self.QTH_LATITUDE = cfg.getfloat('EVENT INFO','QTH_LATITUDE') |
| 115 | + self.QTH_LONGITUDE = cfg.getfloat('EVENT INFO','QTH_LONGITUDE') |
| 116 | + self.DISPLAY_DWELL_TIME = cfg.getint('GLOBAL','DISPLAY_DWELL_TIME',fallback=6) |
| 117 | + self.DATA_DWELL_TIME = cfg.getint('GLOBAL','DATA_DWELL_TIME',fallback=60) |
| 118 | + self.HEADLESS_DWELL_TIME = cfg.getint('GLOBAL','HEADLESS_DWELL_TIME',fallback=180) |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + self.IMAGE_DIR = cfg.get('HEADLESS INFO','IMAGE_DIR',fallback='/mnt/ramdisk/n1mm_view/html') |
| 123 | + self.HEADLESS = cfg.getboolean('HEADLESS INFO','HEADLESS',fallback = False) #False |
| 124 | + self.POST_FILE_COMMAND = cfg.get('HEADLESS INFO','POST_FILE_COMMAND', fallback=None) |
| 125 | + self.VIEW_FONT = cfg.getint('FONT INFO','VIEW_FONT',fallback=64) |
| 126 | + self.BIGGER_FONT = cfg.getint('FONT INFO','BIGGER_FONT',fallback=180) |
0 commit comments