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_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 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 setUp(self): self.cb1_fired = 0 self.cb2_fired = 0 self.cfg = SimpleConfig(autosave=False)
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'})
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()
def setUp(self): self.cfg = SimpleConfig(autosave=False) self.wifi = WifiSetup(self.cfg)
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)
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()
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")