Example #1
0
 def setup_modules(self):
     # Base config
     self.config = SimpleConfig()
     self.config.add_param('configured', False)
     self.config.add_param('hostname',
                           'wifiswitch_{:s}'.format(mac_last_digits()))
     # Setup remote logging
     self.rlogging = RemoteLogging(self.config)
     # MQTT
     self.mqtt = tinymqtt.MQTTClient('wifiswitch-{:s}'.format(
         mac_last_digits()),
                                     server='192.168.1.1',
                                     port=3883,
                                     config=self.config)
     # Modules
     self.status = StatusLed(self.config, machine.Pin(status_led_pin))
     # self.setupbtn = SetupButton(self.config, machine.Pin(setup_btn_pin))
     # WebServer
     self.web = tinyweb.webserver(debug=True)
     # Relays
     self.relays = []
     for num, pin in enumerate(relays_pins):
         self.relays.append(
             Relay(num + 1, machine.Pin(pin), self.config, self.web,
                   self.mqtt))
     # Binary Sensors (user GPIO - currently only GPIO)
     self.binary_sensors = []
     for num, pin in enumerate(binary_sensor_pins):
         pname = 'mqtt_binary_sensor{}_status'.format(num + 1)
         self.config.add_param(pname, 'binary_sensor{}'.format(num + 1))
         self.binary_sensors.append(
             BinarySensor(machine.Pin(pin), self.config, self.mqtt, pname))
Example #2
0
 def setup_modules(self):
     # Base config
     self.config = SimpleConfig()
     self.config.add_param('configured', False)
     self.config.add_param(
         'hostname',
         'neopixel_{:s}'.format(platform.utils.mac_last_digits()))
     # Setup remote logging
     self.rlogging = RemoteLogging(self.config)
     # MQTT
     self.mqtt = tinymqtt.MQTTClient('neopixelctrl-{:s}'.format(
         platform.utils.mac_last_digits()),
                                     config=self.config)
     # DNS
     self.dns = tinydns.Server(ttl=10)
     # Modules
     self.ambi = AmbientLightAnalogSensor(self.config, self.mqtt,
                                          machine.ADC(0))
     self.setupbtn = SetupButton(self.config, machine.Pin(setup_btn_pin))
     self.status = StatusLed(self.config, machine.Pin(status_led_pin))
     # WebServer
     self.web = tinyweb.webserver(max_concurrency=1, debug=True)
     # LED strip handler (+ associated web routes)
     self.neo = NeopixelStrip(machine.Pin(neopixel_pin), self.config,
                              self.web, self.mqtt, self.loop)
Example #3
0
    def testSaveLoad(self):
        self.cfg.add_param('blah0', default=0)
        self.cfg.add_param('blah300', default=300)
        self.cfg.add_param('blah_below_zero', default=-1)
        self.cfg.add_param('blah_max_int', default=2147483647)
        self.cfg.add_param('blah_str', default='12345')
        self.cfg.add_param('blah_str_zero', default='')
        self.cfg.add_param('blah3', default=True)
        self.cfg.add_param('blah33', default=False)
        self.cfg.add_param('cb', default=1)
        self.cfg.save()
        jstr1 = [x for x in self.cfg.get({})]
        data1 = ujson.loads(''.join(jstr1))

        # Create another config instance and load it from previously saved config.
        cfg2 = SimpleConfig(autosave=False)
        # Add the same parameter to ensure that param's callback will be triggered
        cfg2.add_param('cb', default=1, callback=self.cb1)
        cfg2.load()
        jstr2 = [x for x in cfg2.get({})]
        data2 = ujson.loads(''.join(jstr2))
        self.assertEqual(data1, data2)
        # ensure that callback triggered during load
        self.assertEqual(self.cb1_fired, 1)
Example #4
0
 def setUp(self):
     self.cb1_fired = 0
     self.cb2_fired = 0
     self.cfg = SimpleConfig(autosave=False)
Example #5
0
class ConfigTests(unittest.TestCase):

    def cb1(self):
        self.cb1_fired += 1

    def cb2(self):
        self.cb2_fired += 1

    def setUp(self):
        self.cb1_fired = 0
        self.cb2_fired = 0
        self.cfg = SimpleConfig(autosave=False)

    def assertParams(self, obj):
        jstr = [x for x in self.cfg.get({})]
        data = ujson.loads(''.join(jstr))
        self.assertEqual(data, obj)

    def testSanity(self):
        self.cfg.add_param('blah1', default=1)
        self.cfg.add_param('blah2', default='2')
        self.cfg.add_param('blah3', default=True)
        # Check default values
        self.assertEqual(self.cfg.blah1, 1)
        self.assertEqual(self.cfg.blah2, '2')
        self.assertEqual(self.cfg.blah3, True)
        # Update and check again
        self.cfg.update({'blah1': 11, 'blah2': '22', 'blah3': False})
        self.assertEqual(self.cfg.blah1, 11)
        self.assertEqual(self.cfg.blah2, '22')
        self.assertEqual(self.cfg.blah3, False)
        # There should be no callbacks
        self.assertEqual(self.cb1_fired, 0)
        self.assertEqual(self.cb2_fired, 0)
        # Add one more parameter with callback
        self.cfg.add_param('cb', default=100, callback=self.cb1)
        self.cfg.update({'cb': 200})
        # Ensure that only one parameter got changed
        self.assertEqual(self.cfg.blah1, 11)
        self.assertEqual(self.cfg.blah2, '22')
        self.assertEqual(self.cfg.blah3, False)
        self.assertEqual(self.cfg.cb, 200)
        # .. and callback was called
        self.assertEqual(self.cb1_fired, 1)
        # final check
        exp = {"blah2": "22", "cb": 200, "blah3": False, "blah1": 11}
        self.assertParams(exp)

    def testSaveLoad(self):
        self.cfg.add_param('blah0', default=0)
        self.cfg.add_param('blah300', default=300)
        self.cfg.add_param('blah_below_zero', default=-1)
        self.cfg.add_param('blah_max_int', default=2147483647)
        self.cfg.add_param('blah_str', default='12345')
        self.cfg.add_param('blah_str_zero', default='')
        self.cfg.add_param('blah3', default=True)
        self.cfg.add_param('blah33', default=False)
        self.cfg.add_param('cb', default=1)
        self.cfg.save()
        jstr1 = [x for x in self.cfg.get({})]
        data1 = ujson.loads(''.join(jstr1))

        # Create another config instance and load it from previously saved config.
        cfg2 = SimpleConfig(autosave=False)
        # Add the same parameter to ensure that param's callback will be triggered
        cfg2.add_param('cb', default=1, callback=self.cb1)
        cfg2.load()
        jstr2 = [x for x in cfg2.get({})]
        data2 = ujson.loads(''.join(jstr2))
        self.assertEqual(data1, data2)
        # ensure that callback triggered during load
        self.assertEqual(self.cb1_fired, 1)

    def testValueType(self):
        self.cfg.add_param('blah1', default=1)
        self.cfg.add_param('blah2', default='2')
        self.cfg.add_param('blah3', default=True)
        with self.assertRaises(ConfigError):
            self.cfg.update({'blah1': '1'})
        with self.assertRaises(ConfigError):
            self.cfg.update({'blah2': 1})
        with self.assertRaises(ConfigError):
            self.cfg.update({'blah3': '1'})

    def testGroups(self):
        self.cfg.add_param('c1', default=1, callback=self.cb1)
        self.cfg.add_param('g1', default=2, callback=self.cb2, group=5)
        self.cfg.add_param('g2', default=3, group=5)
        # Update non group parameter, then check for callback fired
        self.cfg.update({'c1': 11})
        self.assertEqual(self.cb1_fired, 1)
        self.assertEqual(self.cb2_fired, 0)
        # Update two parameters in the same group
        self.cfg.update({'g1': 22, 'g2': 33})
        self.assertEqual(self.cb1_fired, 1)
        self.assertEqual(self.cb2_fired, 1)
        # Update one parameters in the same group
        self.cfg.update({'g2': 44})
        self.assertEqual(self.cb1_fired, 1)
        self.assertEqual(self.cb2_fired, 2)
        # Ensure that config has right values
        exp = {"c1": 11, "g1": 22, "g2": 44}
        self.assertParams(exp)

    def testValidators(self):
        def validator(name, value):
            if name != 'key1':
                raise KeyError()
            if value != 'value1':
                raise ValueError()
        # Default values validation
        # Validator as function
        self.cfg.add_param('key1', default='value1', validator=validator)
        # Invalid values
        with self.assertRaises(KeyError):
            self.cfg.add_param('key3', default='value3', validator=validator)
        with self.assertRaises(ValueError):
            self.cfg.update({'key1': 'blahinvalid'})
Example #6
0
class App():
    def __init__(self, loop):
        self.loop = loop
        self.log = logging.Logger('app')
        self.setup_modules()
        self.setup_wifi()
        self.setup_routes()

    def setup_modules(self):
        # Base config
        self.config = SimpleConfig()
        self.config.add_param('configured', False)
        self.config.add_param(
            'hostname',
            'neopixel_{:s}'.format(platform.utils.mac_last_digits()))
        # Setup remote logging
        self.rlogging = RemoteLogging(self.config)
        # MQTT
        self.mqtt = tinymqtt.MQTTClient('neopixelctrl-{:s}'.format(
            platform.utils.mac_last_digits()),
                                        config=self.config)
        # DNS
        self.dns = tinydns.Server(ttl=10)
        # Modules
        self.ambi = AmbientLightAnalogSensor(self.config, self.mqtt,
                                             machine.ADC(0))
        self.setupbtn = SetupButton(self.config, machine.Pin(setup_btn_pin))
        self.status = StatusLed(self.config, machine.Pin(status_led_pin))
        # WebServer
        self.web = tinyweb.webserver(max_concurrency=1, debug=True)
        # LED strip handler (+ associated web routes)
        self.neo = NeopixelStrip(machine.Pin(neopixel_pin), self.config,
                                 self.web, self.mqtt, self.loop)

    def setup_wifi(self):
        # Setup AP parameters
        self.wifi = WifiSetup(self.config)
        ap_if = network.WLAN(network.AP_IF)
        ap_if.active(True)
        ap_if.config(essid=self.config.hostname,
                     authmode=network.AUTH_WPA_WPA2_PSK,
                     password=b'neopixel')
        ap_if.ifconfig(('192.168.168.1', '255.255.255.0', '192.168.168.1',
                        '192.168.168.1'))
        ap_if.active(False)
        # Captive portal
        platform.utils.captiveportal.enable(self.web, self.dns,
                                            '192.168.168.1')

    def setup_routes(self):
        @self.web.route('/')
        async def index(req, resp):
            await resp.send_file('web/index.html.gz',
                                 content_encoding='gzip',
                                 content_type='text/html')

        @self.web.route('/fonts/<fn>')
        async def fonts(req, resp, fn):
            await resp.send_file('web/fonts/{}.gz'.format(fn),
                                 content_encoding='gzip',
                                 content_type='font/{}'.format(
                                     fn.split(".")[-1]))

        @self.web.route('/restart')
        @self.web.route('/reset')
        async def page_restart(req, resp):
            machine.reset()

        # REST API pages
        self.web.add_resource(self.wifi, '/wifi')
        self.web.add_resource(self.config, '/config')
        self.web.add_resource(Status(self), '/status')

    def run(self):
        logging.basicConfig(level=logging.DEBUG)
        # Load configuration
        try:
            self.config.load()
        except Exception as e:
            # Don't care in case of config load failed.
            # E.g. it may happen on first boot
            pass

        wport = 80
        dport = 53
        if platform.utils.is_emulator():
            wport = 8080
            dport = 5335
        # Start services
        self.dns.run(host='0.0.0.0', port=dport, loop=self.loop)
        self.web.run(host='0.0.0.0',
                     port=wport,
                     loop_forever=False,
                     loop=self.loop)
        self.mqtt.run(self.loop)
        self.ambi.run(self.loop)
        self.setupbtn.run(self.loop)
        self.status.run(self.loop)

    def stop(self):
        if not platform.utils.is_emulator():
            return
        for s in [
                self.web, self.dns, self.mqtt, self.ambi, self.setupbtn,
                self.status
        ]:
            s.shutdown()
Example #7
0
 def setUp(self):
     self.cfg = SimpleConfig(autosave=False)
     self.wifi = WifiSetup(self.cfg)
Example #8
0
class WiFiTests(unittest.TestCase):
    def setUp(self):
        self.cfg = SimpleConfig(autosave=False)
        self.wifi = WifiSetup(self.cfg)

    def testDefaultConfig(self):
        """Sanity test - just to make sure that default config OK"""
        res = json.loads(self.wifi.get())
        exp = {
            "connected": 0,
            "mac": "b4-75-0e-88-ed-e4",
            "mode": "802.11n",
            "status": "Not Connected",
            "ip": "",
            "netmask": "",
            "gateway": "",
            "dns": ""
        }
        self.assertEqual(res, exp)

    def testChangeSSID(self):
        """WiFi must be activated when changing ssid."""
        # Set SSID
        self.cfg.update({'wifi_ssid': 'junk', 'wifi_password': '******'})
        # Ensure that wifi got connected
        res = json.loads(self.wifi.get())
        exp = {
            "connected": 1,
            "mac": "b4-75-0e-88-ed-e4",
            "mode": "802.11n",
            "status": "Connected",
            "ip": "127.0.0.1",
            "netmask": "255.255.255.0",
            "gateway": "127.0.0.1",
            "dns": "8.8.8.8"
        }
        self.assertEqual(res, exp)
        # ... and lets disconnect from AP
        self.cfg.update({'wifi_ssid': '', 'wifi_password': ''})
        res = json.loads(self.wifi.get())
        exp = {
            "connected": 0,
            "mac": "b4-75-0e-88-ed-e4",
            "mode": "802.11n",
            "status": "Not Connected",
            "ip": "",
            "netmask": "",
            "gateway": "",
            "dns": ""
        }

    def testChangeWiFiMode(self):
        # Change WiFi mode
        self.cfg.update({'wifi_mode': '802.11b'})
        # Ensure that wifi got connected
        res = json.loads(self.wifi.get())
        exp = {
            "connected": 0,
            "mac": "b4-75-0e-88-ed-e4",
            "mode": "802.11b",
            "status": "Not Connected",
            "ip": "",
            "netmask": "",
            "gateway": "",
            "dns": ""
        }
        self.assertEqual(res, exp)

    def testInvalidWiFiMode(self):
        with self.assertRaises(ValueError):
            self.cfg.update({'wifi_mode': 'junk'})
        with self.assertRaises(ValueError):
            self.cfg.update({'wifi_mode': ''})

    def testScanAPs(self):
        """Scan APs test - mostly to test output format and activate/deactivate interface"""
        ap0 = {
            'ssid': 'iot1982',
            'auth_raw': 0,
            'channel': 11,
            'rssi': -44,
            'mac': '00-71-c2-54-51-82',
            'quality': 100,
            'auth': 'Open'
        }
        res = json.loads(self.wifi.get(data={'scan': 1, 'max_entries': 2}))
        self.assertEqual(len(res['access-points']), 2)
        self.assertEqual(res['access-points'][0], ap0)
Example #9
0
class App():
    def __init__(self, loop):
        self.loop = loop
        self.log = logging.Logger('app')
        self.setup_modules()
        self.setup_wifi()
        self.setup_routes()

    def setup_modules(self):
        # Base config
        self.config = SimpleConfig()
        self.config.add_param('configured', False)
        self.config.add_param('hostname',
                              'wifiswitch_{:s}'.format(mac_last_digits()))
        # Setup remote logging
        self.rlogging = RemoteLogging(self.config)
        # MQTT
        self.mqtt = tinymqtt.MQTTClient('wifiswitch-{:s}'.format(
            mac_last_digits()),
                                        server='192.168.1.1',
                                        port=3883,
                                        config=self.config)
        # Modules
        self.status = StatusLed(self.config, machine.Pin(status_led_pin))
        # self.setupbtn = SetupButton(self.config, machine.Pin(setup_btn_pin))
        # WebServer
        self.web = tinyweb.webserver(debug=True)
        # Relays
        self.relays = []
        for num, pin in enumerate(relays_pins):
            self.relays.append(
                Relay(num + 1, machine.Pin(pin), self.config, self.web,
                      self.mqtt))
        # Binary Sensors (user GPIO - currently only GPIO)
        self.binary_sensors = []
        for num, pin in enumerate(binary_sensor_pins):
            pname = 'mqtt_binary_sensor{}_status'.format(num + 1)
            self.config.add_param(pname, 'binary_sensor{}'.format(num + 1))
            self.binary_sensors.append(
                BinarySensor(machine.Pin(pin), self.config, self.mqtt, pname))

    def setup_wifi(self):
        # Setup AP parameters
        self.wifi = WifiSetup(self.config)
        ap_if = network.WLAN(network.AP_IF)
        ap_if.active(True)
        ap_if.config(essid=self.config.hostname,
                     authmode=network.AUTH_WPA_WPA2_PSK,
                     password=b'wifiswitch')
        ap_if.ifconfig(('192.168.168.1', '255.255.255.0', '192.168.168.1',
                        '192.168.168.1'))
        ap_if.active(False)
        # Captive portal
        # platform.utils.captiveportal.enable(self.web, self.dns, '192.168.168.1')

    def setup_routes(self):
        @self.web.route('/restart')
        @self.web.route('/reset')
        async def page_restart(req, resp):
            machine.reset()

        # REST API pages
        self.web.add_resource(self.wifi, '/wifi')
        self.web.add_resource(self.config, '/config')
        self.web.add_resource(Status(self), '/status')

    def run(self):
        logging.basicConfig(level=logging.DEBUG)
        # Load configuration
        try:
            self.config.load()
        except Exception as e:
            self.log.warning('Config load failed: {}'.format(e))

        wport = 80
        # dport = 53
        if is_emulator():
            wport = 8080
            # dport = 5335
        # Start services
        # self.dns.run(host='0.0.0.0', port=dport, loop=self.loop)
        self.web.run(host='0.0.0.0',
                     port=wport,
                     loop_forever=False,
                     loop=self.loop)
        self.mqtt.run(self.loop)
        # self.setupbtn.run(self.loop)
        self.status.run(self.loop)
        for bs in self.binary_sensors:
            bs.run(self.loop)

    def stop(self):
        if not is_emulator():
            return
        for s in [self.web, self.mqtt, self.status]:
            s.shutdown()
Example #10
0
def main():
    # Some ports requires to allocate extra mem for exceptions
    if hasattr(micropython, 'alloc_emergency_exception_buf'):
        micropython.alloc_emergency_exception_buf(100)

    loop = asyncio.get_event_loop()
    logging.basicConfig(level=logging.DEBUG)

    # Base config
    config = SimpleConfig()
    config.add_param('configured', False)
    wsetup = WifiSetup(config)

    # MQTT
    mqtt = tinymqtt.MQTTClient('LEDcontroller-{}'.format(
        platform.utils.mac_last_digits()), config=config)

    # DNS
    dns = tinydns.Server(ttl=10)

    # WebServer
    web = tinyweb.webserver()

    # Enable REST API for config & wifi
    web.add_resource(config, '/config')
    web.add_resource(wsetup, '/wifi')

    # Create LED strip handler
    WhiteLedStrip(machine.Pin(green_pin), config, web, mqtt, loop)

    # Peripheral modules
    setupbtn = SetupButton(config, None)

    # Other web routes
    @web.route('/')
    async def index(req, resp):
        if config.configured:
            await resp.redirect('/dashboard')
        else:
            await resp.redirect('/setup')

    @web.route('/dashboard')
    async def page_dashboard(req, resp):
        await resp.send_file('dashboard_all.html.gz',
                             content_encoding='gzip',
                             content_type='text/html')

    @web.route('/setup')
    async def page_setup(req, resp):
        await resp.send_file('setup_all.html.gz',
                             content_encoding='gzip',
                             content_type='text/html')

    # Setup AP parameters
    ap_if = network.WLAN(network.AP_IF)
    essid = b'LedCtrl-%s' % platform.utils.mac_last_digits()
    ap_if.active(True)
    ap_if.config(essid=essid, authmode=network.AUTH_WPA_WPA2_PSK, password=b'ledledled')
    ap_if.ifconfig(('192.168.168.1', '255.255.255.0', '192.168.168.1', '192.168.168.1'))
    ap_if.active(False)
    # Captive portal
    platform.utils.captiveportal.enable(web, dns, '192.168.168.1')

    # Load configuration
    try:
        config.load()
    except Exception as e:
        log.warning('Config load failed: {}'.format(e))
        pass

    # Main loop
    try:
        wport = 80
        dport = 53
        if platform.utils.is_emulator():
            wport = 8080
            dport = 5335
        # Start services
        dns.run(host='0.0.0.0', port=dport, loop=loop)
        web.run(host='0.0.0.0', port=wport, loop_forever=False, loop=loop)
        mqtt.run(loop)
        setupbtn.run(loop)

        # Run main loop
        loop.run_forever()
    except KeyboardInterrupt as e:
        if platform.utils.is_emulator():
            for s in [web, dns, mqtt]:
                s.shutdown()
            loop.run_until_complete(shutdown_wait())
        else:
            raise
    except Exception as e:
        log.exc(e, "Unhandled exception")