summaryrefslogtreecommitdiff
path: root/site_config.py
blob: eb7d8845639a6b81b73c22425c90fbc52eb8d5c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/env python3

# © Copyright 2021-2022, Scott Gasch

"""Location/site dependent data."""

import logging
import platform
from dataclasses import dataclass
from typing import Callable, Optional

# Note: this module is fairly early loaded.  Be aware of dependencies.
import config
from type.locations import Location

logger = logging.getLogger(__name__)

args = config.add_commandline_args(
    f'Global Site Config ({__file__})',
    'Args related to global site-specific configuration',
)
args.add_argument(
    '--site_config_override_location',
    default='NONE',
    const='NONE',
    nargs='?',
    choices=['HOUSE', 'CABIN', 'NONE'],
    help='Where are we, HOUSE, CABIN?  Overrides standard detection code.',
)


@dataclass
class SiteConfig(object):
    """The set of information specific to where the program is running."""

    location_name: str
    """Either "HOUSE" or "CABIN" depending on where we're running"""

    location: Location
    """Same as above but as an enum value instead of a string"""

    network: str
    """The local network specification, e.g. 192.168.0.0/24."""

    network_netmask: str
    """The netmask of the local network, e.g. 255.255.255.0."""

    network_router_ip: str
    """The IP address of the local router, e.g. 192.168.0.1."""

    presence_location: Location
    """Same as location, above."""

    is_anyone_present: Callable
    """Returns a callable which, when invoked, will tell you if it detects
    any person in your location by auditing network device MAC addresses."""

    arper_minimum_device_count: int
    """How many MAC addresses do we need to see for it to be considered a
    successful scan?"""

    arper_cache_file: str
    """The location of the persisted IP-MAC address mappings."""


def get_location_name() -> str:
    """
    Where are we?

    >>> location = get_location_name()
    >>> location == 'HOUSE' or location == 'CABIN'
    True

    """
    return get_config().location_name


def get_location() -> Location:
    """
    Returns location as an enum instead of a string.

    >>> from type.locations import Location
    >>> location = get_location()
    >>> location == Location.HOUSE or location == Location.CABIN
    True

    """
    return get_config().location


def _is_anyone_present_wrapper(location: Location):
    import base_presence

    p = base_presence.PresenceDetection()
    return p.is_anyone_in_location_now(location)


def other_location() -> str:
    """
    Returns the location where this program is _NOT_ running.

    >>> x = other_location()
    >>> x in set(['HOUSE', 'CABIN'])
    True

    >>> y = this_location()
    >>> x == y
    False

    """
    this = this_location()
    if this == 'HOUSE':
        return 'CABIN'
    elif this == 'CABIN':
        return 'HOUSE'
    else:
        raise Exception(f"{this} doesn't tell me where I'm running?!")


def this_location() -> str:
    """
    Returns the location where this program _IS_ running.

    >>> x = this_location()
    >>> x in set(['HOUSE', 'CABIN'])
    True

    """
    hostname = platform.node()
    if '.house' in hostname:
        location = 'HOUSE'
    elif '.cabin' in hostname:
        location = 'CABIN'
    elif '.local' in hostname:
        location = 'HOUSE'
    else:
        raise Exception(f"{hostname} doesn't help me know where I'm running?!")
    return location


def effective_location(location_override: Optional[str] = None) -> str:
    """Detects and returns a location taking into account two override
    mechanisms.

    >>> x = effective_location()
    >>> x in set(['HOUSE', 'CABIN'])
    True

    >>> effective_location('HOUSE')
    'HOUSE'

    """
    if location_override is None:
        try:
            location_override = config.config['site_config_override_location']
        except KeyError:
            location_override = None

    if location_override is None or location_override == 'NONE':
        location = this_location()
    else:
        logger.debug('site_config\'s location_override was set to: %s', location_override)
        location = location_override
    return location


def get_config(location_override: Optional[str] = None):
    """
    Get a configuration dataclass with information that is
    site-specific including the current running location.

    >>> cfg = get_config()
    >>> cfg.location_name == 'HOUSE' or cfg.location_name == 'CABIN'
    True

    """
    location = effective_location(location_override)
    if location == 'HOUSE':
        return SiteConfig(
            location_name='HOUSE',
            location=Location.HOUSE,
            network='10.0.0.0/24',
            network_netmask='255.255.255.0',
            network_router_ip='10.0.0.1',
            presence_location=Location.HOUSE,
            is_anyone_present=lambda x=Location.HOUSE: _is_anyone_present_wrapper(x),
            arper_minimum_device_count=50,
            arper_cache_file='/home/scott/cache/.arp_table_cache_house',
        )
    elif location == 'CABIN':
        return SiteConfig(
            location_name='CABIN',
            location=Location.CABIN,
            network='192.168.0.0/24',
            network_netmask='255.255.255.0',
            network_router_ip='192.168.0.1',
            presence_location=Location.CABIN,
            is_anyone_present=lambda x=Location.CABIN: _is_anyone_present_wrapper(x),
            arper_minimum_device_count=15,
            arper_cache_file='/home/scott/cache/.arp_table_cache_cabin',
        )
    else:
        raise Exception(f'Unknown site location: {location}')


if __name__ == '__main__':
    import doctest

    doctest.testmod()