Repository: webdjoe/pyvesync (language: python)

Issue:
Readme & tox



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index ad326ec..e98d307 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,12 @@ htmlcov/
 *__pycache__
 cov_html/*
 cov_html/
+test/*
+/venv/
+.idea/
+src/tests/test1.py
+/src/tests/junit/
+/.tox/
+.tox/.tmp/
+.tox/dist/
+.tox/log/
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..449fc26
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,54 @@
+[MASTER]
+ignore=src/tests
+
+[BASIC]
+good-names=i,j,k,x,r,e,v,_,b,dt,d
+
+[MESSAGES CONTROL]
+# Reasons disabled:
+# format - handled by black
+# locally-disabled - it spams too much
+# duplicate-code - unavoidable
+# cyclic-import - doesn't test if both import on load
+# abstract-class-little-used - prevents from setting right foundation
+# unused-argument - generic callbacks and setup methods create a lot of warnings
+# global-statement - used for the on-demand requirement installation
+# redefined-variable-type - this is Python, we're duck typing!
+# too-many-* - are not enforced for the sake of readability
+# too-few-* - same as too-many-*
+# abstract-method - with intro of async there are always methods missing
+# inconsistent-return-statements - doesn't handle raise
+# not-an-iterable - https://github.com/PyCQA/pylint/issues/2311
+# unnecessary-pass - readability for functions which only contain pass
+
+disable=
+    format,
+    abstract-class-little-used,
+    abstract-method,
+    cyclic-import,
+    duplicate-code,
+    global-statement,
+    inconsistent-return-statements,
+    locally-disabled,
+    not-an-iterable,
+    redefined-variable-type,
+    too-few-public-methods,
+    too-many-arguments,
+    too-many-branches,
+    too-many-instance-attributes,
+    too-many-lines,
+    too-many-locals,
+    too-many-public-methods,
+    too-many-return-statements,
+    too-many-statements,
+    unnecessary-pass,
+    unused-argument,
+    useless-super-delegation
+
+[REPORTS]
+#reports=no
+
+[TYPECHECK]
+
+[FORMAT]
+expected-line-ending-format=LF
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 953f7c1..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: python
-python:
-  - "3.6"
-install:
-  - pip install -r requirements.txt
-  - pip install flake8
-  - pip install pytest
-script:
-  - python -m pytest -v
-  - flake8 . --exit-zero
diff --git a/README.md b/README.md
index f1135ec..b84064d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# pyvesync [![build status](https://img.shields.io/pypi/v/pyvesync.svg)](https://pypi.python.org/pypi/pyvesync) <!-- omit in toc -->
+# pyvesync [![build status](https://img.shields.io/pypi/v/pyvesync.svg)](https://pypi.python.org/pypi/pyvesync) [![Build Status](https://dev.azure.com/webdjoe/pyvesync/_apis/build/status/webdjoe.pyvesync?branchName=master)](https://dev.azure.com/webdjoe/pyvesync/_build/latest?definitionId=4&branchName=master) [![Open Source? Yes!](https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github)](https://github.com/Naereen/badges/) [![PyPI license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://pypi.python.org/pypi/ansicolortags/) <!-- omit in toc -->
 
 pyvesync is a library to manage VeSync compatible [smart home devices](#supported-devices)
 
@@ -26,7 +26,7 @@ pyvesync is a library to manage VeSync compatible [smart home devices](#supporte
     - [JSON Output for Bulbs](#json-output-for-bulbs)
     - [JSON Output for Air Purifier](#json-output-for-air-purifier)
 - [Notes](#notes)
-- [Integration with Home Assistant](#integration-with-home-assistant)
+- [Feature Requests](#feature-requests)
 
 ## Installation
 
@@ -56,7 +56,7 @@ To start with the module:
 ```python
 from pyvesync import VeSync
 
-manager = VeSync("EMAIL", "PASSWORD", time_zone=DEFAULT_TZ)
+manager = VeSync("EMAIL", "PASSWORD")
 manager.login()
 
 # Get/Update Devices from server - populate device lists
@@ -153,6 +153,10 @@ for s in manager.outlets:
 
 The rectangular smart switch model supports some additional functionality on top of the regular api call
 
+`VeSyncOutlet.nightlight_status` - Get the status of the nightlight
+
+`VeSyncOutlet.nightlight_brightness` - Get the brightness of the nightlight
+
 `VeSyncOutlet.turn_on_nightlight()` - Turn on the nightlight
 
 `VeSyncOutlet.turn_off_nightlight()` - Turn off the nightlight
@@ -169,7 +173,7 @@ The rectangular smart switch model supports some additional functionality on top
 
 `VeSyncFan.manual_mode()` - Change fan mode to manual with fan level 1
 
-`VeSyncFan.sleep_mode()` - Change fan mode to sleep  
+`VeSyncFan.sleep_mode()` - Change fan mode to sleep
 
 `VeSyncFan.change_fan_speed(speed)` - Change fan speed with level 1, 2 or 3
 
@@ -301,32 +305,31 @@ VesyncOutlet.energy['week']['total_energy']
 VesyncOutlet.energy['week']['data'] # which itself is a list of values
 ```
 
-## Integration with Home Assistant
-
-This library is integrated with Home Assistant and documentation can be found at <https://www.home-assistant.io/components/vesync/>. The library version included with Home Assistant may lag behind development compared to this repository so those wanting to use the latest version can do the following to integrate with HA.
+## Feature Requests
 
-1. Add a `custom_components` directory to your Home Assistant configuration directory
-2. Add a `vesync` directory as a directory within `custom_components`
-3. Add `switch.py` to the `vesync` directory
-4. Add `__init__.py` to the `vesync` directory
-5. Add `manifest.json` to the `vesync` directory
-6. Add the following config to your Home Assistant `configuration.yaml` file:
+If you would like new devices to be added, you will need to capture the packets from the app. The easiest way to do this is by using [Packet Capture for Android](https://play.google.com/store/apps/details?id=app.greyshirts.sslcapture&hl=en_US&gl=US). This works without rooting the device. If you do not have an android or are concerned with adding an app that uses a custom certificate to read the traffic, you can use an Android emulator such as [Nox](https://www.bignox.com/).
 
-```python
-vesync:
-  username: VESYNC_USERNAME
-  password: VESYNC_PASSWORD
-```
+When capturing packets make sure all packets are captured from the device list, along with all functions that the app contains. The captured packets are stored in text files, please do not capture with pcap format.
 
-1. Restart Home Assistant
+After you capture the packets, please redact the `accountid` and `token`. If you feel you must redact other keys, please do not delete them entirely. Replace letters with "A" and numbers with "1", leave all punctuation intact and maintain length. 
 
-The `custom_components` directory should include the following files:
+For example:
 
-```bash
-custom_components/vesync/__init__.py
-custom_components/vesync/switch.py
-custom_components/vesync/fan.py
-custom_components/vesync/manifest.json
+Before:
+```json
+{
+  'tk': 'abc123abc123==3rf',
+  'accountId': '123456789',
+  'cid': 'abcdef12-3gh-ij'
+}
 ```
+After:
+```json
+{
+  'tk': 'AAA111AAA111==1AA',
+  'accountId': '111111111',
+  'cid': 'AAAAAA11-1AA-AA'
+}
+``` 
 
-The version of the library defined in `manifest.json` should now get loaded within Home Assistant.
+All contributions are welcome, please run `tox` before submitting a PR to ensure code is valid.
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..1618279
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,65 @@
+# Python package
+# Create and test a Python package on multiple Python versions.
+# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/python
+
+trigger:
+  branches:
+    include:
+    - '*'
+    exclude:
+    - 'master'
+pr:
+  - '*'
+
+jobs:
+- job: 'Validation'
+  steps:
+  - task: UsePythonVersion@0
+    inputs:
+      versionSpec: '3.6'
+    displayName: 'Use Python 3.6'
+  - script: |
+      python -m pip install --upgrade pip
+      pip install -r requirements.txt
+    displayName: 'Install dependencies'
+  - script: |
+      pip install flake8 flake8-docstrings
+      flake8 src/pyvesync
+    displayName: 'Flake8'
+  - script: |
+      pip install pylint
+      pylint src/pyvesync
+    displayName: 'Pylint'
+  - script: |
+      pip install mypy
+      mypy src/pyvesync
+    displayName: 'MyPy'
+
+- job: 'pytest'
+  pool:
+    vmImage: 'ubuntu-latest'
+  strategy:
+    matrix:
+      Python36:
+        python.version: '3.6'
+      Python37:
+        python.version: '3.7'
+      Python38:
+        python.version: '3.8'
+
+  steps:
+  - task: UsePythonVersion@0
+    inputs:
+      versionSpec: '$(python.version)'
+    displayName: 'Use Python $(python.version)'
+
+  - script: |
+      set -e
+      python -m pip install --upgrade pip
+      pip install -r requirements.txt
+      pip install -U pytest pytest-azurepipelines pytest-sugar
+    displayName: 'Install dependencies'
+  - script: |
+      pytest --junitxml=reports/pytest.xml
+    displayName: 'Run Tests'
diff --git a/home_assistant/custom_components/__init__.py b/home_assistant/custom_components/__init__.py
deleted file mode 100644
index b67088b..0000000
--- a/home_assistant/custom_components/__init__.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""Etekcity VeSync integration."""
-import logging
-import voluptuous as vol
-from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_TIME_ZONE)
-from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers import discovery
-
-
-_LOGGER = logging.getLogger(__name__)
-
-DOMAIN = 'vesync'
-
-CONFIG_SCHEMA = vol.Schema({
-    DOMAIN: vol.Schema({
-        vol.Required(CONF_USERNAME): cv.string,
-        vol.Required(CONF_PASSWORD): cv.string,
-        vol.Optional(CONF_TIME_ZONE): cv.string,
-    }),
-}, extra=vol.ALLOW_EXTRA)
-
-
-def setup(hass, config):
-    """Set up the VeSync component."""
-    from pyvesync.vesync import VeSync
-
-    conf = config[DOMAIN]
-
-    manager = VeSync(conf.get(CONF_USERNAME), conf.get(CONF_PASSWORD),
-                     time_zone=conf.get(CONF_TIME_ZONE))
-
-    if not manager.login():
-        _LOGGER.error("Unable to login to VeSync")
-        return
-
-    manager.update()
-
-    hass.data[DOMAIN] = {
-        'manager': manager
-    }
-
-    discovery.load_platform(hass, 'switch', DOMAIN, {}, config)
-    discovery.load_platform(hass, 'fan', DOMAIN, {}, config)
-
-    return True
diff --git a/home_assistant/custom_components/fan.py b/home_assistant/custom_components/fan.py
deleted file mode 100644
index 11355f3..0000000
--- a/home_assistant/custom_components/fan.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""Support for Etekcity VeSync fans"""
-import logging
-from homeassistant.components.fan import (FanEntity, SUPPORT_SET_SPEED)
-
-from . import DOMAIN
-
-_LOGGER = logging.getLogger(__name__)
-
-FAN_SPEEDS = ["auto", "low", "medium", "high"]
-
-
-def setup_platform(hass, config, add_entities, discovery_info=None):
-    """Set up the VeSync fan platform."""
-
-    if discovery_info is None:
-        return
-
-    fans = []
-
-    manager = hass.data[DOMAIN]['manager']
-
-    if manager.fans is not None and manager.fans:
-        if len(manager.fans) == 1:
-            count_string = "fan"
-        else:
-            count_string = "fans"
-
-        for fan in manager.fans:
-            fans.append(VeSyncFanHA(fan))
-            _LOGGER.info("Added a VeSync fan named '%s'",
-                         fan.device_name)
-    else:
-        _LOGGER.info("No VeSync fans found")
-
-    add_entities(fans)
-
-
-class VeSyncFanHA(FanEntity):
-    """Representation of a VeSync fan."""
-
-    def __init__(self, fan):
-        """Initialize the VeSync fan device."""
-        self.smartfan = fan
-
-    @property
-    def supported_features(self):
-        """Flag supported features."""
-        return SUPPORT_SET_SPEED
-
-    @property
-    def is_on(self):
-        """Return True if device is on"""
-        return self.smartfan.device_status == "on"
-
-    @property
-    def speed(self):
-        """Return the current speed."""
-        if self.smartfan.mode == "auto":
-            return "auto"
-        if self.smartfan.mode == "manual":
-            current_level = self.smartfan.fan_level
-            if current_level is not None:
-                return FAN_SPEEDS[current_level]
-        return None
-
-    @property
-    def speed_list(self):
-        """Get the list of available speeds"""
-        return FAN_SPEEDS
-
-    @property
-    def unique_info(self):
-        """Return the ID of this fan."""
-        return self.smartfan.uuid
-
-    @property
-    def name(self):
-        """Return the name of the fan."""
-        return self.smartfan.device_name
-
-    @property
-    def device_state_attributes(self):
-        """Return the state attributes of the fan."""
-        attr = {}
-        attr['mode'] = self.smartfan.mode
-        attr['active_time'] = self.smartfan.active_time
-        attr['filter_life'] = self.smartfan.filter_life
-        attr['air_quality'] = self.smartfan.air_quality
-        attr['screen_status'] = self.smartfan.screen_status
-        return attr
-
-    def set_speed(self, speed):
-        if speed is None or speed == "auto":
-            self.smartfan.auto_mode()
-        else:
-            self.smartfan.manual_mode()
-            self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed))
-
-    def turn_on(self, speed, **kwargs):
-        self.smartfan.turn_on()
-        if speed is None or speed == "auto":
-            self.smartfan.auto_mode()
-        else:
-            self.smartfan.manual_mode()
-            self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed))
-
-    def turn_off(self, **kwargs):
-        self.smartfan.turn_off()
-
-    def update(self):
-        self.smartfan.update()
diff --git a/home_assistant/custom_components/manifest.json b/home_assistant/custom_components/manifest.json
deleted file mode 100644
index 8e24419..0000000
--- a/home_assistant/custom_components/manifest.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "domain": "vesync",
-  "name": "VeSync",
-  "documentation": "https://www.home-assistant.io/components/vesync",
-  "dependencies": [],
-  "codeowners": ["@markperdue", "@webdjoe"],
-  "requirements": ["pyvesync==1.1.0"]
-}
diff --git a/home_assistant/custom_components/switch.py b/home_assistant/custom_components/switch.py
deleted file mode 100644
index 841b2a4..0000000
--- a/home_assistant/custom_components/switch.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""Support for Etekcity VeSync switches."""
-import logging
-from homeassistant.components.switch import (SwitchDevice)
-
-from . import DOMAIN
-
-_LOGGER = logging.getLogger(__name__)
-
-ENERGY_UPDATE_INT = 21600
-
-
-def setup_platform(hass, config, add_entities, discovery_info=None):
-    """Set up the VeSync switch platform."""
-    if discovery_info is None:
-        return
-
-    switches = []
-
-    manager = hass.data[DOMAIN]['manager']
-
-    if manager.outlets is not None and manager.outlets:
-        if len(manager.outlets) == 1:
-            count_string = 'switch'
-        else:
-            count_string = 'switches'
-
-        _LOGGER.info("Discovered %d VeSync %s",
-                     len(manager.outlets), count_string)
-
-        if len(manager.outlets) > 1:
-            for switch in manager.outlets:
-                switch._energy_update_interval = ENERGY_UPDATE_INT
-                switches.append(VeSyncSwitchHA(switch))
-                _LOGGER.info("Added a VeSync switch named '%s'",
-                             switch.device_name)
-        else:
-            switches.append(VeSyncSwitchHA(manager.outlets))
-    else:
-        _LOGGER.info("No VeSync switches found")
-
-    add_entities(switches)
-
-
-class VeSyncSwitchHA(SwitchDevice):
-    """Representation of a VeSync switch."""
-
-    def __init__(self, plug):
-        """Initialize the VeSync switch device."""
-        self.smartplug = plug
-
-    @property
-    def unique_id(self):
-        """Return the ID of this switch."""
-        return self.smartplug.cid
-
-    @property
-    def name(self):
-        """Return the name of the switch."""
-        return self.smartplug.device_name
-
-    @property
-    def device_state_attributes(self):
-        """Return the state attributes of the device."""
-        attr = {}
-        attr['active_time'] = self.smartplug.active_time
-        attr['voltage'] = self.smartplug.voltage
-        attr['active_time'] = self.smartplug.active_time
-        attr['weekly_energy_total'] = self.smartplug.weekly_energy_total
-        attr['monthly_energy_total'] = self.smartplug.monthly_energy_total
-        attr['yearly_energy_total'] = self.smartplug.yearly_energy_total
-        return attr
-
-    @property
-    def current_power_w(self):
-        """Return the current power usage in W."""
-        return self.smartplug.power
-
-    @property
-    def today_energy_kwh(self):
-        """Return the today total energy usage in kWh."""
-        return self.smartplug.energy_today
-
-    @property
-    def available(self) -> bool:
-        """Return True if switch is available."""
-        return self.smartplug.connection_status == "online"
-
-    @property
-    def is_on(self):
-        """Return True if switch is on."""
-        return self.smartplug.device_status == "on"
-
-    def turn_on(self, **kwargs):
-        """Turn the switch on."""
-        self.smartplug.turn_on()
-
-    def turn_off(self, **kwargs):
-        """Turn the switch off."""
-        self.smartplug.turn_off()
-
-    def update(self):
-        """Handle data changes for node values."""
-        self.smartplug.update()
-        self.smartplug.update_energy()
diff --git a/requirements.txt b/requirements.txt
index 3cb65d1..1480256 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,1 @@
-requests==2.20.0
\ No newline at end of file
+requests>=2.20.0
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index ed8a958..b98a7ec 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,7 @@ universal = 1
 
 [metadata]
 license_file = LICENSE
+
+[tool:pytest]
+testpaths = src/tests
+norecursedirs = .git
\ No newline at end of file
diff --git a/setup.py b/setup.py
index d3d5873..2e77d93 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 """pyvesync setup script."""
 
-from setuptools import setup, find_packages
 from os import path
+from setuptools import setup, find_packages
 
 
 this_directory = path.abspath(path.dirname(__file__))
@@ -10,7 +10,7 @@
 
 setup(
     name='pyvesync',
-    version='1.2.1',
+    version='1.3.1',
     description='pyvesync is a library to manage Etekcity\
                  Devices and Levoit Air Purifier',
     long_description=long_description,
@@ -24,12 +24,12 @@
         'Intended Audience :: Developers',
         'Intended Audience :: End Users/Desktop',
         'Natural Language :: English',
-        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
     ],
     keywords=['iot', 'vesync'],
-    packages=find_packages('src',exclude=['tests','tests.*']),
-    package_dir={'': "src"},
+    packages=find_packages('src', exclude=['tests', 'tests.*']),
+    package_dir={'': 'src'},
     zip_safe=False,
     install_requires=['requests>=2.20.0'],
-    python_requires='>=3.5',
+    python_requires='>=3.6',
 )
diff --git a/src/pyvesync/__init__.py b/src/pyvesync/__init__.py
index d514817..bdfb16b 100755
--- a/src/pyvesync/__init__.py
+++ b/src/pyvesync/__init__.py
@@ -3,15 +3,18 @@
 # pylint: skip-file
 # flake8: noqa
 from .vesync import VeSync
-from .vesyncoutlet import (VeSyncOutlet10A, VeSyncOutlet15A, VeSyncOutlet7A,
-                           VeSyncOutdoorPlug)
-from .vesyncswitch import VeSyncWallSwitch
+from .vesyncoutlet import (
+    VeSyncOutlet10A,
+    VeSyncOutlet15A,
+    VeSyncOutlet7A,
+    VeSyncOutdoorPlug,
+)
+from .vesyncswitch import VeSyncWallSwitch, VeSyncDimmerSwitch
 from .vesyncfan import VeSyncAir131
 from .vesyncbulb import VeSyncBulbESL100
 import logging
 
 
 logging.basicConfig(
-    level=logging.INFO,
-    format='%(asctime)s - %(levelname)5s - %(message)s'
+    level=logging.INFO, format='%(asctime)s - %(levelname)5s - %(message)s'
 )
diff --git a/src/pyvesync/helpers.py b/src/pyvesync/helpers.py
index 15f62d7..7586d1b 100644
--- a/src/pyvesync/helpers.py
+++ b/src/pyvesync/helpers.py
@@ -24,7 +24,7 @@ class Helpers:
     """VeSync Helper Functions."""
 
     @staticmethod
-    def req_headers(manager):
+    def req_headers(manager) -> dict:
         """Build header for api requests."""
         headers = {
             'accept-language': 'en',
@@ -32,46 +32,38 @@ def req_headers(manager):
             'appVersion': APP_VERSION,
             'content-type': 'application/json',
             'tk': manager.token,
-            'tz': manager.time_zone
+            'tz': manager.time_zone,
         }
         return headers
 
     @staticmethod
-    def req_body_base(manager):
+    def req_body_base(manager) -> dict:
         """Return universal keys for body of api requests."""
-        return {
-            'timeZone': manager.time_zone,
-            'acceptLanguage': 'en'
-        }
+        return {'timeZone': manager.time_zone, 'acceptLanguage': 'en'}
 
     @staticmethod
-    def req_body_auth(manager):
+    def req_body_auth(manager) -> dict:
         """Keys for authenticating api requests."""
-        return {
-            'accountID': manager.account_id,
-            'token': manager.token
-        }
+        return {'accountID': manager.account_id, 'token': manager.token}
 
     @staticmethod
-    def req_body_details():
+    def req_body_details() -> dict:
         """Detail keys for api requests."""
         return {
             'appVersion': APP_VERSION,
             'phoneBrand': PHONE_BRAND,
             'phoneOS': PHONE_OS,
-            'traceId': str(int(time.time()))
+            'traceId': str(int(time.time())),
         }
 
     @classmethod
-    def req_body(cls, manager, type_):
+    def req_body(cls, manager, type_) -> dict:
         """Builder for body of api requests."""
         body = {}
 
         if type_ == 'login':
-            body = {
-                **cls.req_body_base(manager),
-                **cls.req_body_details()
-            }
+            body = {**cls.req_body_base(manager),
+                    **cls.req_body_details()}
             body['email'] = manager.username
             body['password'] = cls.hash_password(manager.password)
             body['devToken'] = ''
@@ -81,7 +73,7 @@ def req_body(cls, manager, type_):
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'devicedetail'
             body['mobileId'] = MOBILE_ID
@@ -95,15 +87,13 @@ def req_body(cls, manager, type_):
             body['pageNo'] = '1'
             body['pageSize'] = '50'
         elif type_ == 'devicestatus':
-            body = {
-                **cls.req_body_base(manager),
-                **cls.req_body_auth(manager)
-            }
+            body = {**cls.req_body_base(manager),
+                    **cls.req_body_auth(manager)}
         elif type_ == 'energy_week':
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'energyweek'
             body['mobileId'] = MOBILE_ID
@@ -111,7 +101,7 @@ def req_body(cls, manager, type_):
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'energymonth'
             body['mobileId'] = MOBILE_ID
@@ -119,7 +109,7 @@ def req_body(cls, manager, type_):
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'energyyear'
             body['mobileId'] = MOBILE_ID
@@ -127,35 +117,36 @@ def req_body(cls, manager, type_):
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'bypass'
         elif type_ == 'bypass_config':
             body = {
                 **cls.req_body_base(manager),
                 **cls.req_body_auth(manager),
-                **cls.req_body_details()
+                **cls.req_body_details(),
             }
             body['method'] = 'firmwareUpdateInfo'
 
         return body
 
     @staticmethod
-    def calculate_hex(hex_string):
+    def calculate_hex(hex_string) -> float:
         """Credit for conversion to itsnotlupus/vesync_wsproxy."""
         hex_conv = hex_string.split(':')
-        converted_hex = (int(hex_conv[0], 16) + int(hex_conv[1], 16))/8192
+        converted_hex = (int(hex_conv[0], 16) + int(hex_conv[1], 16)) / 8192
 
         return converted_hex
 
     @staticmethod
-    def hash_password(string):
+    def hash_password(string) -> str:
         """Encode password."""
         return hashlib.md5(string.encode('utf-8')).hexdigest()
 
     @staticmethod
-    def call_api(api: str, method: str,
-                 json: dict = None, headers: dict = None):
+    def call_api(
+        api: str, method: str, json: dict = None, headers: dict = None
+    ) -> tuple:
         """Make API calls by passing endpoint, header and body."""
         response = None
         status_code = None
@@ -164,22 +155,22 @@ def call_api(api: str, method: str,
             logger.debug("[%s] calling '%s' api", method, api)
             if method == 'get':
                 r = requests.get(
-                    API_BASE_URL + api, json=json,
-                    headers=headers, timeout=API_TIMEOUT
+                    API_BASE_URL + api, json=json, headers=headers,
+                    timeout=API_TIMEOUT
                 )
             elif method == 'post':
                 r = requests.post(
-                    API_BASE_URL + api, json=json,
-                    headers=headers, timeout=API_TIMEOUT
+                    API_BASE_URL + api, json=json, headers=headers,
+                    timeout=API_TIMEOUT
                 )
             elif method == 'put':
                 r = requests.put(
-                    API_BASE_URL + api, json=json,
-                    headers=headers, timeout=API_TIMEOUT
+                    API_BASE_URL + api, json=json, headers=headers,
+                    timeout=API_TIMEOUT
                 )
         except requests.exceptions.RequestException as e:
-            logger.waring(e)
-        except Exception as e:
+            logger.warning(e)
+        except Exception as e:  # pylint: disable=broad-except
             logger.warning(e)
         else:
             if r.status_code == 200:
@@ -187,13 +178,12 @@ def call_api(api: str, method: str,
                 response = r.json()
             else:
                 logger.debug('Unable to fetch %s%s', API_BASE_URL, api)
-        finally:
-            return (response, status_code)
+        return response, status_code
 
     @staticmethod
     def code_check(r: dict) -> bool:
         """Test if code == 0 for successful API call."""
-        if isinstance(r, dict) and r.get("code") == 0:
+        if isinstance(r, dict) and r.get('code') == 0:
             return True
         return False
 
@@ -207,7 +197,7 @@ def build_details_dict(r: dict) -> dict:
             'night_light_brightness': r.get('nightLightBrightness', None),
             'night_light_automode': r.get('nightLightAutomode', None),
             'power': r.get('power', 0),
-            'voltage': r.get('voltage', 0)
+            'voltage': r.get('voltage', 0),
         }
 
     @staticmethod
@@ -215,26 +205,26 @@ def build_energy_dict(r: dict) -> dict:
         """Build energy dictionary from API response."""
         return {
             'energy_consumption_of_today': r.get(
-                'energyConsumptionOfToday', 0),
+                    'energyConsumptionOfToday', 0),
             'cost_per_kwh': r.get('costPerKWH', 0),
             'max_energy': r.get('maxEnergy', 0),
             'total_energy': r.get('totalEnergy', 0),
             'currency': r.get('currency', 0),
-            'data': r.get('data', 0)
+            'data': r.get('data', 0),
         }
 
     @staticmethod
     def build_config_dict(r: dict) -> dict:
         """Build configuration dictionary from API response."""
-        if r.get("theshold") is not None:
+        if r.get('theshold') is not None:
             threshold = r.get('threshold')
         else:
             threshold = r.get('threshHold')
         return {
-            'current_firmware_version': r.get("currentFirmVersion"),
-            'latest_firmware_version': r.get("latestFirmVersion"),
-            'maxPower': r.get("maxPower"),
+            'current_firmware_version': r.get('currentFirmVersion'),
+            'latest_firmware_version': r.get('latestFirmVersion'),
+            'maxPower': r.get('maxPower'),
             'threshold': threshold,
-            'power_protection': r.get("powerProtectionStatus"),
-            'energy_saving_status': r.get("energySavingStatus")
+            'power_protection': r.get('powerProtectionStatus'),
+            'energy_saving_status': r.get('energySavingStatus'),
         }
diff --git a/src/pyvesync/vesync.py b/src/pyvesync/vesync.py
index 4749e8f..d33f393 100755
--- a/src/pyvesync/vesync.py
+++ b/src/pyvesync/vesync.py
@@ -1,44 +1,64 @@
 """VeSync API Device Libary."""
 
 import logging
-import time
 import re
+import time
 from itertools import chain
-from pyvesync.helpers import Helpers as helpers
-from pyvesync.vesyncoutlet import (VeSyncOutlet7A, VeSyncOutlet10A,
-                                   VeSyncOutlet15A, VeSyncOutdoorPlug)
-from pyvesync.vesyncswitch import VeSyncWallSwitch, VeSyncDimmerSwitch
-from pyvesync.vesyncfan import VeSyncAir131
+from collections import defaultdict
+from typing import List, Dict, DefaultDict, Union, Any
+
+from pyvesync.helpers import Helpers
 from pyvesync.vesyncbulb import VeSyncBulbESL100, VeSyncBulbESL100CW
+from pyvesync.vesyncfan import VeSyncAir131
+from pyvesync.vesyncoutlet import (
+    VeSyncOutlet7A,
+    VeSyncOutlet10A,
+    VeSyncOutlet15A,
+    VeSyncOutdoorPlug,
+)
+from pyvesync.vesyncswitch import VeSyncWallSwitch, VeSyncDimmerSwitch
 
 logger = logging.getLogger(__name__)
 
-API_RATE_LIMIT = 30
-DEFAULT_TZ = 'America/New_York'
-
-DEFAULT_ENER_UP_INT = 21600
-
-
-def get_device(device_type, config, manager):
-    """Return initilized device from API response."""
-    if device_type == 'wifi-switch-1.3':
-        return VeSyncOutlet7A(config, manager)
-    if device_type in ['ESW03-USA', 'ESW01-EU']:
-        return VeSyncOutlet10A(config, manager)
-    if device_type == 'ESW15-USA':
-        return VeSyncOutlet15A(config, manager)
-    if device_type in ['ESWL01', 'ESWL03']:
-        return VeSyncWallSwitch(config, manager)
-    if device_type == 'LV-PUR131S':
-        return VeSyncAir131(config, manager)
-    if device_type == 'ESO15-TB':
-        return VeSyncOutdoorPlug(config, manager)
-    if device_type == 'ESL100':
-        return VeSyncBulbESL100(config, manager)
-    if device_type == 'ESL100CW':
-        return VeSyncBulbESL100CW(config, manager)
-    if device_type == 'ESWD16':
-        return VeSyncDimmerSwitch(config, manager)
+API_RATE_LIMIT: int = 30
+DEFAULT_TZ: str = 'America/New_York'
+
+DEFAULT_ENER_UP_INT: int = 21600
+
+# Class dictionary based on device type
+
+_DEVICE_CLASS: Dict[str, Any] = {
+    'wifi-switch-1.3': VeSyncOutlet7A,
+    'ESW03-USA': VeSyncOutlet10A,
+    'ESW01-EU': VeSyncOutlet10A,
+    'ESW15-USA': VeSyncOutlet15A,
+    'ESWL01': VeSyncWallSwitch,
+    'ESWL03': VeSyncWallSwitch,
+    'LV-PUR131S': VeSyncAir131,
+    'ESO15-TB': VeSyncOutdoorPlug,
+    'ESL100': VeSyncBulbESL100,
+    'ESL100CW': VeSyncBulbESL100CW,
+    'ESWD16': VeSyncDimmerSwitch,
+}
+
+_DEVICE_TYPES_DICT: Dict[str, List[str]] = dict(
+    outlets=['wifi-switch-1.3', 'ESW03-USA',
+             'ESW01-EU', 'ESW15-USA', 'ESO15-TB'],
+    switches=['ESWL01', 'ESWL03', 'ESWD16'],
+    fans=['LV-PUR131S'],
+    bulbs=['ESL100', 'ESL100CW'],
+)
+
+_DEVICE_TYPES: DefaultDict[str, list] = defaultdict(list, _DEVICE_TYPES_DICT)
+
+
+def _device_builder(device_type: str,
+                    config: Dict[str, Union[str, int, float, None]],
+                    manager) -> Any:
+    """Build instantiated device objects from name."""
+    device_name = _DEVICE_CLASS.get(device_type)
+    if device_name:
+        return device_name(config, manager)
     logger.debug('Unknown device found - %s', device_type)
     return None
 
@@ -47,33 +67,39 @@ class VeSync:
     """VeSync API functions."""
 
     def __init__(self, username, password, time_zone=DEFAULT_TZ):
-        """Initilize VeSync class with username, password and time zone."""
+        """Initialize VeSync class with username, password and time zone."""
         self.username = username
         self.password = password
         self.token = None
         self.account_id = None
         self.devices = None
-        self.outlets = []
-        self.switches = []
-        self.fans = []
-        self.bulbs = []
         self.enabled = False
         self.update_interval = API_RATE_LIMIT
         self.last_update_ts = None
         self.in_process = False
         self._energy_update_interval = DEFAULT_ENER_UP_INT
         self._energy_check = True
+        self._dev_list = {}
+        self.outlets = []
+        self.switches = []
+        self.fans = []
+        self.bulbs = []
+        self.scales = []
+
+        for dt in _DEVICE_TYPES:
+            self._dev_list[dt] = getattr(self, dt)
 
         if isinstance(time_zone, str) and time_zone:
-            reg_test = r"[^a-zA-Z/_]"
+            reg_test = r'[^a-zA-Z/_]'
             if bool(re.search(reg_test, time_zone)):
                 self.time_zone = DEFAULT_TZ
-                logger.debug("Invalid characters in time zone - %s", time_zone)
+                logger.debug('Invalid characters in time zone - %s',
+                             time_zone)
             else:
                 self.time_zone = time_zone
         else:
             self.time_zone = DEFAULT_TZ
-            logger.debug("Time zone is not a string")
+            logger.debug('Time zone is not a string')
 
     @property
     def energy_update_interval(self) -> int:
@@ -81,13 +107,13 @@ def energy_update_interval(self) -> int:
         return self._energy_update_interval
 
     @energy_update_interval.setter
-    def energy_update_interval(self, new_energy_update):
+    def energy_update_interval(self, new_energy_update: int) -> None:
         """Set energy update interval in seconds."""
         if new_energy_update > 0:
             self._energy_update_interval = new_energy_update
 
     @staticmethod
-    def remove_dev_test(device, new_list):
+    def remove_dev_test(device, new_list: list) -> bool:
         """Test if device should be removed - False = Remove."""
         if isinstance(new_list, list) and device.cid:
             for item in new_list:
@@ -97,114 +123,109 @@ def remove_dev_test(device, new_list):
                         device_found = True
                         break
                 else:
-                    logger.error('No cid found in - %s', str(item))
+                    logger.debug('No cid found in - %s', str(item))
             if not device_found:
-                logger.debug("Device removed - %s - %s",
-                             device.device_name, device.device_type)
+                logger.debug(
+                    'Device removed - %s - %s',
+                    device.device_name, device.device_type
+                )
                 return False
         return True
 
-    def add_dev_test(self, new_dev):
+    def add_dev_test(self, new_dev: dict) -> bool:
         """Test if new device should be added - True = Add."""
         if 'cid' in new_dev:
-            devices = [self.outlets, self.bulbs, self.switches, self.fans]
-            was_found = False
-            for dev in chain(*devices):
-                if dev.cid == new_dev.get('cid') and\
-                        new_dev.get('subDeviceNo', 0) == dev.sub_device_no:
-                    was_found = True
-                    break
-            if not was_found:
-                logger.debug("Adding device - %s", new_dev)
-                return True
-        return False
-
-    def process_devices(self, devices) -> tuple:
-        """Call VSFactory to instantiate device classes."""
-        outlets = []
-        switches = []
-        fans = []
-        bulbs = []
+            for _, v in self._dev_list.items():
+                for dev in v:
+                    if (
+                        dev.cid == new_dev.get('cid')
+                        and new_dev.get('subDeviceNo', 0) == dev.sub_device_no
+                    ):
+                        return False
+        return True
 
-        outlet_types = [
-            'wifi-switch-1.3', 'ESW03-USA', 'ESW01-EU', 'ESW15-USA', 'ESO15-TB'
-        ]
-        switch_types = ['ESWL01', 'ESWL03', 'ESWD16']
-        fan_types = ['LV-PUR131S']
-        bulb_types = ['ESL100', 'ESL100CW']
+    def remove_old_devices(self, devices: list) -> bool:
+        """Remove devices not found in device list return."""
+        for k, v in self._dev_list.items():
+            before = len(v)
+            v[:] = [x for x in v if self.remove_dev_test(x, devices)]
+            after = len(v)
+            if before != after:
+                logger.debug('%s %s removed', str((before - after)), k)
+        return True
 
-        num_devices = len(self.outlets) + len(self.switches) + len(self.fans) \
-            + len(self.bulbs)
+    @staticmethod
+    def set_dev_id(devices: list) -> list:
+        """Correct devices without cid or uuid."""
+        dev_num = 0
+        dev_rem = []
+        for dev in devices:
+            if dev.get('cid') is None:
+                if dev.get('macID') is not None:
+                    dev['cid'] = dev['macID']
+                elif dev.get('uuid') is not None:
+                    dev['cid'] = dev['uuid']
+                else:
+                    dev_rem.append(dev_num)
+                    logger.warning('Device with no ID  - %s',
+                                   dev.get('deviceName'))
+            dev_num += 1
+            if dev_rem:
+                devices = [i for j, i in enumerate(
+                            devices) if j not in dev_rem]
+        return devices
+
+    def process_devices(self, dev_list: list) -> bool:
+        """Instantiate Device Objects."""
+        devices = VeSync.set_dev_id(dev_list)
+
+        num_devices = 0
+        for _, v in self._dev_list.items():
+            if isinstance(v, list):
+                num_devices += len(v)
+            else:
+                num_devices += 1
 
-        if not num_devices and devices:
-            logger.debug('New device list initialized')
-        elif not devices:
+        if not devices:
             logger.warning('No devices found in api return')
+            return False
+        if num_devices == 0:
+            logger.debug('New device list initialized')
         else:
-            self.outlets[:] = [x for x in self.outlets if self.remove_dev_test(
-                x, devices)]
-            for dev in self.outlets:
-                logger.debug('Outlets updated - %s', str(dev))
-
-            self.fans[:] = [x for x in self.fans if self.remove_dev_test(
-                x, devices)]
-            for dev in self.fans:
-                logger.debug('Fans Updated - %s', str(dev))
-
-            self.switches[:] = [x for x in self.switches if
-                                self.remove_dev_test(x, devices)]
-            for dev in self.switches:
-                logger.debug('Switches Updated - %s', str(dev))
+            self.remove_old_devices(devices)
 
-            self.bulbs[:] = [x for x in self.bulbs if self.remove_dev_test(
-                x, devices)]
-            for dev in self.bulbs:
-                logger.debug('Bulbs - %s', str(dev))
-
-            devices[:] = [x for x in devices if self.add_dev_test(x)]
+        devices[:] = [x for x in devices if self.add_dev_test(x)]
 
+        detail_keys = ['deviceType', 'deviceName', 'deviceStatus']
         for dev in devices:
-            detail_keys = ['deviceType', 'deviceName', 'deviceStatus']
-            if all(k in dev for k in detail_keys):
-                dev_type = dev['deviceType']
-                if dev_type in outlet_types:
-                    outlets.append(get_device(dev_type, dev, self))
-                elif dev_type in fan_types:
-                    fans.append(get_device(dev_type, dev, self))
-                elif dev_type in switch_types:
-                    switches.append(get_device(dev_type, dev, self))
-                elif dev_type in bulb_types:
-                    bulbs.append(get_device(dev_type, dev, self))
-                else:
-                    logger.warning('Unknown device %s', dev_type)
-            else:
-                logger.error('Details keys not found %s', str(dev))
-
-        return outlets, switches, fans, bulbs
+            if not all(k in dev for k in detail_keys):
+                logger.debug('Error adding device')
+                continue
+            dev_type = dev.get('deviceType')
+            for dt, v in self._dev_list.items():
+                if dev_type in _DEVICE_TYPES.get(dt, []):
+                    v.append(_device_builder(dev_type, dev, self))
+        return True
 
-    def get_devices(self) -> tuple:
+    def get_devices(self) -> bool:
         """Return tuple listing outlets, switches, and fans of devices."""
-        outlets = []
-        switches = []
-        fans = []
-        bulbs = []
         if not self.enabled:
-            return None
+            return False
 
         self.in_process = True
-
-        response, _ = helpers.call_api(
+        proc_return = False
+        response, _ = Helpers.call_api(
             '/cloud/v1/deviceManaged/devices',
             'post',
-            headers=helpers.req_headers(self),
-            json=helpers.req_body(self, 'devicelist')
+            headers=Helpers.req_headers(self),
+            json=Helpers.req_body(self, 'devicelist'),
         )
 
-        if response and helpers.code_check(response):
+        if response and Helpers.code_check(response):
             if 'result' in response and 'list' in response['result']:
                 device_list = response['result']['list']
-                outlets, switches, fans, bulbs = self.process_devices(
-                    device_list)
+
+                proc_return = self.process_devices(device_list)
             else:
                 logger.error('Device list in response not found')
         else:
@@ -212,7 +233,7 @@ def get_devices(self) -> tuple:
 
         self.in_process = False
 
-        return (outlets, switches, fans, bulbs)
+        return proc_return
 
     def login(self) -> bool:
         """Return True if log in request succeeds."""
@@ -225,13 +246,12 @@ def login(self) -> bool:
             logger.error('Password invalid')
             return False
 
-        response, _ = helpers.call_api(
-            '/cloud/v1/user/login',
-            'post',
-            json=helpers.req_body(self, 'login')
+        response, _ = Helpers.call_api(
+            '/cloud/v1/user/login', 'post',
+            json=Helpers.req_body(self, 'login')
         )
 
-        if helpers.code_check(response) and 'result' in response:
+        if Helpers.code_check(response) and 'result' in response:
             self.token = response.get('result').get('token')
             self.account_id = response.get('result').get('accountID')
             self.enabled = True
@@ -242,39 +262,37 @@ def login(self) -> bool:
 
     def device_time_check(self) -> bool:
         """Test if update interval has been exceeded."""
-        if self.last_update_ts is None or (
-                time.time() - self.last_update_ts) > self.update_interval:
+        if (
+            self.last_update_ts is None
+            or (time.time() - self.last_update_ts) > self.update_interval
+        ):
             return True
         return False
 
-    def update(self):
+    def update(self) -> None:
         """Fetch updated information about devices."""
-
         if self.device_time_check():
 
-            if not self.in_process and self.enabled:
-                outlets, switches, fans, bulbs = self.get_devices()
+            if not self.enabled:
+                logger.error('Not logged in to VeSync')
+                return
+            self.get_devices()
 
-                self.outlets.extend(outlets)
-                self.switches.extend(switches)
-                self.fans.extend(fans)
-                self.bulbs.extend(bulbs)
+            devices = list(self._dev_list.values())
 
-                devices = [self.outlets, self.bulbs, self.switches, self.fans]
+            for device in chain(*devices):
+                device.update()
 
-                [device.update() for device in chain(*devices)]
-
-                self.last_update_ts = time.time()
-            else:
-                logger.error('You are not logged in to VeSync')
+            self.last_update_ts = time.time()
 
-    def update_energy(self, bypass_check=False):
+    def update_energy(self, bypass_check=False) -> None:
         """Fetch updated energy information about devices."""
-        for outlet in self.outlets:
-            outlet.update_energy(bypass_check)
+        if self.outlets:
+            for outlet in self.outlets:
+                outlet.update_energy(bypass_check)
 
-    def update_all_devices(self):
+    def update_all_devices(self) -> None:
         """Run get_details() for each device."""
-        dev_list = [self.outlets, self.fans, self.bulbs, self.switches]
-        for dev in chain(*dev_list):
+        devices = list(self._dev_list.keys())
+        for dev in chain(*devices):
             dev.get_details()
diff --git a/src/pyvesync/vesyncbasedevice.py b/src/pyvesync/vesyncbasedevice.py
index b62186b..9789ef8 100644
--- a/src/pyvesync/vesyncbasedevice.py
+++ b/src/pyvesync/vesyncbasedevice.py
@@ -3,14 +3,15 @@
 import logging
 import collections
 import json
+
 logger = logging.getLogger(__name__)
 
 
-class VeSyncBaseDevice(object):
+class VeSyncBaseDevice:
     """Properties shared across all VeSync devices."""
 
     def __init__(self, details, manager):
-        """Initilize VeSync device base class."""
+        """Initialize VeSync device base class."""
         self.manager = manager
         if 'cid' in details and details['cid'] is not None:
             self.device_name = details.get('deviceName', None)
@@ -26,7 +27,8 @@ def __init__(self, details, manager):
             self.mode = details.get('mode', None)
             self.speed = details.get('speed', None)
             self.extension = details.get('extension', None)
-            self.current_firm_version = details.get('currentFirmVersion', None)
+            self.current_firm_version = details.get(
+                    'currentFirmVersion', None)
             self.sub_device_no = details.get('subDeviceNo', 0)
             self.config = {}
             if self.connection_status != 'online':
@@ -35,34 +37,37 @@ def __init__(self, details, manager):
                 self.device_status = details.get('deviceStatus', None)
 
         else:
-            logger.error('No cid found for ' + self.__class__.__name__)
+            logger.error('No cid found for %s', self.__class__.__name__)
 
     def __eq__(self, other):
         """Use device CID and subdevice number to test equality."""
-        if other.cid == self.cid and other.sub_device_no == self.sub_device_no:
-            return True
-        else:
-            return False
+        return bool(other.cid == self.cid
+                    and other.sub_device_no == self.sub_device_no)
 
     def __hash__(self):
         """Use CID and sub-device number to make device hash."""
         if isinstance(self.sub_device_no, int) and self.sub_device_no > 0:
             return hash(self.cid + str(self.sub_device_no))
-        else:
-            return hash(self.cid)
+        return hash(self.cid)
 
     def __str__(self):
         """Use device info for string represtation of class."""
-        return "Device Name: {}, Device Type: {},\
-            SubDevice No.: {} Status: {}".format(
-            self.device_name, self.device_type, self.sub_device_no,
-            self.device_status)
+        return 'Device Name: {}, Device Type: {},\
+            SubDevice No.: {} Status: {}'.format(
+            self.device_name, self.device_type,
+            self.sub_device_no, self.device_status
+        )
 
     def __repr__(self):
         """Representation of device details."""
-        return "DevClass: {}, Name:{}, Device No: {}, DevStatus: {}, CID: {}"\
-            .format(self.__class__.__name__, self.device_name,
-                    self.sub_device_no, self.device_status, self.cid)
+        return 'DevClass: {}, Name:{}, Device No: {},\
+            DevStatus: {}, CID: {}'.format(
+            self.__class__.__name__,
+            self.device_name,
+            self.sub_device_no,
+            self.device_status,
+            self.cid,
+        )
 
     @property
     def is_on(self) -> bool:
@@ -80,11 +85,10 @@ def firmware_update(self) -> bool:
             if cfv != lfv:
                 return True
         else:
-            logger.warning(
-                'Call device.get_config() to get firmware versions')
+            logger.debug('Call device.get_config() to get firmware versions')
         return False
 
-    def display(self):
+    def display(self) -> None:
         """Print formatted device info to stdout."""
         disp = [
             ('Device Name:', self.device_name),
@@ -99,16 +103,18 @@ def display(self):
             disp.append(('UUID: ', self.uuid))
         disp1 = collections.OrderedDict(disp)
         for k, v in disp1.items():
-            print("{:.<15} {:<15}".format(k, v))
+            print('{:.<15} {:<15}'.format(k, v))
 
-    def displayJSON(self):
+    def displayJSON(self) -> str:  # pylint: disable=invalid-name
         """JSON API for device details."""
-        return json.dumps({
-            'Device Name': self.device_name,
-            'Model': self.device_type,
-            'Subdevice No': str(self.sub_device_no),
-            'Status': self.device_status,
-            'Online': self.connection_status,
-            'Type': self.type,
-            'CID': self.cid
-            })
+        return json.dumps(
+            {
+                'Device Name': self.device_name,
+                'Model': self.device_type,
+                'Subdevice No': str(self.sub_device_no),
+                'Status': self.device_status,
+                'Online': self.connection_status,
+                'Type': self.type,
+                'CID': self.cid,
+            }
+        )
diff --git a/src/pyvesync/vesyncbulb.py b/src/pyvesync/vesyncbulb.py
index a59d0c2..4716830 100644
--- a/src/pyvesync/vesyncbulb.py
+++ b/src/pyvesync/vesyncbulb.py
@@ -3,6 +3,7 @@
 import logging
 import json
 from abc import ABCMeta, abstractmethod
+from collections import defaultdict
 
 from pyvesync.helpers import Helpers as helpers
 from pyvesync.vesyncbasedevice import VeSyncBaseDevice
@@ -10,10 +11,9 @@
 logger = logging.getLogger(__name__)
 
 # Possible features - dimmable, color_temp, rgb_shift
-feature_dict = {
-    'ESL100': ['dimmable'],
-    'ESL100CW': ['dimmable', 'color_temp']
-}
+feature_dict = defaultdict(
+    list, {'ESL100': ['dimmable'], 'ESL100CW': ['dimmable', 'color_temp']}
+)
 
 
 def pct_to_kelvin(pct, max_k=6500, min_k=2700):
@@ -29,100 +29,103 @@ class VeSyncBulb(VeSyncBaseDevice):
 
     def __init__(self, details, manager):
         """Initialize VeSync smart bulb base class."""
-        super(VeSyncBulb, self).__init__(details, manager)
-        self._brightness = None
-        self._color_temp = None
+        super().__init__(details, manager)
+        self._brightness = 0
+        self._color_temp = 0
 
     @property
-    def brightness(self):
+    def brightness(self) -> int:
         """Return brightness of vesync bulb."""
         if self.dimmable_feature and self._brightness is not None:
             return self._brightness
+        return 0
 
     @property
-    def color_temp_kelvin(self):
+    def color_temp_kelvin(self) -> int:
         """Return Color Temp of bulb if supported in Kelvin."""
         if self.color_temp_feature and self._color_temp is not None:
             return int(pct_to_kelvin(self._color_temp))
+        return 0
 
     @property
-    def color_temp_pct(self):
+    def color_temp_pct(self) -> int:
         """Return color temperature of bulb in percent."""
         if self.color_temp_feature and self._color_temp is not None:
             return int(self._color_temp)
+        return 0
 
     @property
-    def dimmable_feature(self):
+    def dimmable_feature(self) -> bool:
         """Return true if dimmable bulb."""
-        if 'dimmable' in feature_dict.get(self.device_type):
+        if 'dimmable' in feature_dict[self.device_type]:
             return True
         return False
 
     @property
-    def color_temp_feature(self):
+    def color_temp_feature(self) -> bool:
         """Return true in color temperature can be changed (tunable)."""
-        if 'color_temp' in feature_dict.get(self.device_type):
+        if 'color_temp' in feature_dict[self.device_type]:
             return True
         return False
 
     @property
-    def rgb_shift_feature(self):
+    def rgb_shift_feature(self) -> bool:
         """Return True if bulb supports changing color."""
-        if 'rgb_shift' in feature_dict.get(self.device_type):
+        if 'rgb_shift' in feature_dict[self.device_type]:
             return True
         return False
 
     @abstractmethod
-    def get_details(self):
+    def get_details(self) -> None:
         """Get vesync bulb details."""
 
     @abstractmethod
-    def toggle(self, status: int):
+    def toggle(self, status: str) -> bool:
         """Toggle vesync lightbulb."""
 
     @abstractmethod
-    def get_config(self):
+    def get_config(self) -> None:
         """Call api to get configuration details and firmware."""
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn on vesync bulbs."""
         if self.toggle('on'):
             self.device_status = 'on'
             return True
-        logger.warning("Error turning %s on", self.device_name)
+        logger.warning('Error turning %s on', self.device_name)
         return False
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn off vesync bulbs."""
         if self.toggle('off'):
             self.device_status = 'off'
             return True
-        logger.warning("Error turning %s off", self.device_name)
+        logger.warning('Error turning %s off', self.device_name)
         return False
 
-    def update(self):
+    def update(self) -> None:
         """Update bulb details."""
         self.get_details()
 
-    def display(self):
+    def display(self) -> None:
         """Return formatted bulb info to stdout."""
-        super(VeSyncBulb, self).display()
+        super().display()
         if self.connection_status == 'online':
             if self.dimmable_feature:
-                disp1 = [("Brightness: ", self.brightness, "%")]
+                disp1 = [('Brightness: ', self.brightness, '%')]
             for line in disp1:
-                print("{:.<17} {} {}".format(line[0], line[1], line[2]))
+                print('{:.<17} {} {}'.format(line[0], line[1], line[2]))
 
-    def displayJSON(self):
+    def displayJSON(self) -> str:
         """Return bulb device info in JSON format."""
         sup = super().displayJSON()
-        supVal = json.loads(sup)
+        sup_val = json.loads(sup)
         if self.connection_status == 'online':
             if self.dimmable_feature:
-                supVal.update({"Brightness": str(self.brightness)})
+                sup_val.update({'Brightness': str(self.brightness)})
             if self.color_temp_feature:
-                supVal.update({"Kelvin": str(self.color_temp_kelvin)})
-        return supVal
+                sup_val.update({'Kelvin': str(self.color_temp_kelvin)})
+        return sup_val
 
 
 class VeSyncBulbESL100(VeSyncBulb):
@@ -130,10 +133,10 @@ class VeSyncBulbESL100(VeSyncBulb):
 
     def __init__(self, details, manager):
         """Initialize Etekcity ESL100 Dimmable Bulb."""
-        super(VeSyncBulbESL100, self).__init__(details, manager)
+        super().__init__(details, manager)
         self.details = {}
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get details of dimmable bulb."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
@@ -141,8 +144,8 @@ def get_details(self):
             '/SmartBulb/v1/device/devicedetail',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body
-            )
+            json=body,
+        )
         if helpers.code_check(r):
             self.connection_status = r.get('connectionStatus')
             self.device_status = r.get('deviceStatus')
@@ -151,7 +154,7 @@ def get_details(self):
         else:
             logger.debug('Error getting %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get configuration of dimmable bulb."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
@@ -161,14 +164,15 @@ def get_config(self):
             '/SmartBulb/v1/device/configurations',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self.config = helpers.build_config_dict(r)
         else:
-            logger.warning("Error getting %s config info", self.device_name)
+            logger.warning('Error getting %s config info', self.device_name)
 
-    def toggle(self, status):
+    def toggle(self, status) -> bool:
         """Toggle dimmable bulb."""
         body = helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
@@ -177,20 +181,21 @@ def toggle(self, status):
             '/SmartBulb/v1/device/devicestatus',
             'put',
             headers=helpers.req_headers(self.manager),
-            json=body
-            )
+            json=body,
+        )
         if helpers.code_check(r):
             self.device_status = status
             return True
         return False
 
-    def set_brightness(self, brightness: int):
+    def set_brightness(self, brightness: int) -> bool:
         """Set brightness of dimmable bulb."""
         if not self.dimmable_feature:
             logger.debug('%s is not dimmable', self.device_name)
             return False
-        if isinstance(brightness, int) and (brightness <= 0 or
-                                            brightness > 100):
+        if isinstance(brightness, int) and (
+                    brightness <= 0 or brightness > 100):
+
             logger.warning('Invalid brightness')
             return False
 
@@ -202,7 +207,8 @@ def set_brightness(self, brightness: int):
             '/SmartBulb/v1/device/updateBrightness',
             'put',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self._brightness = brightness
@@ -219,7 +225,7 @@ def __init__(self, details, manager):
         """Initialize Etekcity Tunable white bulb."""
         super().__init__(details, manager)
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get details of tunable bulb."""
         body = helpers.req_body(self.manager, 'bypass')
         body['cid'] = self.cid
@@ -229,7 +235,8 @@ def get_details(self):
             '/cloud/v1/deviceManaged/bypass',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
         if r.get('code') == 0 and r.get('result').get('light') is not None:
             light = r.get('result').get('light')
             self.connection_status = 'online'
@@ -243,10 +250,14 @@ def get_details(self):
             self.connection_status = 'offline'
             self.device_status = 'off'
         else:
-            logger.warning('%s - Unknown return code - %d with message %s',
-                           self.device_name, r.get('code'), r.get('msg'))
+            logger.debug(
+                '%s - Unknown return code - %d with message %s',
+                self.device_name,
+                r.get('code'),
+                r.get('msg'),
+            )
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get configuration and firmware info of tunable bulb."""
         body = helpers.req_body(self.manager, 'bypass_config')
         body['uuid'] = self.uuid
@@ -255,14 +266,15 @@ def get_config(self):
             '/cloud/v1/deviceManaged/configurations',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self.config = helpers.build_config_dict(r)
         else:
-            logger.debug("Error getting %s config info", self.device_name)
+            logger.debug('Error getting %s config info', self.device_name)
 
-    def toggle(self, status):
+    def toggle(self, status) -> bool:
         """Toggle tunable bulb."""
         body = helpers.req_body(self.manager, 'bypass')
         body['cid'] = self.cid
@@ -272,7 +284,8 @@ def toggle(self, status):
             '/cloud/v1/deviceManaged/bypass',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
         if r.get('code') == 0:
             self.device_status = status
             return True
@@ -302,17 +315,16 @@ def set_brightness(self, brightness: int) -> bool:
             '/cloud/v1/deviceManaged/bypass',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self._brightness = brightness
             return True
-        else:
-            self.device_status = 'off'
-            self.connection_status = 'offline'
-            logger.debug('%s offline', self.device_name)
+        self.device_status = 'off'
+        self.connection_status = 'offline'
+        logger.debug('%s offline', self.device_name)
 
-        logger.debug('Error setting brightness for %s', self.device_name)
         return False
 
     def set_color_temp(self, color_temp: int) -> bool:
@@ -332,18 +344,22 @@ def set_color_temp(self, color_temp: int) -> bool:
             '/cloud/v1/deviceManaged/bypass',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if r.get('code') == -11300027:
             logger.debug('%s device offline', self.device_name)
             self.connection_status = 'offline'
             self.device_status = 'off'
             return False
-        elif r.get('code') == 0:
+        if r.get('code') == 0:
             self.device_status = 'on'
             self._color_temp = color_temp
             return True
-        else:
-            logger.warning('%s - Unknown return code - %d with message %s',
-                           self.device_name, r.get('code'), r.get('msg'))
+        logger.debug(
+            '%s - Unknown return code - %d with message %s',
+            self.device_name,
+            r.get('code'),
+            r.get('msg'),
+        )
         return False
diff --git a/src/pyvesync/vesyncfan.py b/src/pyvesync/vesyncfan.py
index 53a7451..20d9bb2 100644
--- a/src/pyvesync/vesyncfan.py
+++ b/src/pyvesync/vesyncfan.py
@@ -1,9 +1,10 @@
 """VeSync API for controling fans and purifiers."""
 
 import json
+import logging
 from pyvesync.vesyncbasedevice import VeSyncBaseDevice
 from pyvesync.helpers import Helpers as helpers
-import logging
+
 
 logger = logging.getLogger(__name__)
 
@@ -13,12 +14,12 @@ class VeSyncAir131(VeSyncBaseDevice):
 
     def __init__(self, details, manager):
         """Initilize air purifier class."""
-        super(VeSyncAir131, self).__init__(details, manager)
+        super().__init__(details, manager)
 
         self.details = {}
 
-    def get_details(self):
-        """Build details dictionary."""
+    def get_details(self) -> None:
+        """Build Air Purifier details dictionary."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
         head = helpers.req_headers(self.manager)
@@ -27,7 +28,7 @@ def get_details(self):
             '/131airPurifier/v1/device/deviceDetail',
             method='post',
             headers=head,
-            json=body
+            json=body,
         )
 
         if r is not None and helpers.code_check(r):
@@ -42,7 +43,7 @@ def get_details(self):
         else:
             logger.debug('Error getting %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get configuration info for air purifier."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
@@ -52,26 +53,27 @@ def get_config(self):
             '/131airpurifier/v1/device/configurations',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self.config = helpers.build_config_dict(r)
         else:
-            logger.warning("Unable to get config info for %s",
+            logger.warning('Unable to get config info for %s',
                            self.device_name)
 
     @property
-    def active_time(self):
+    def active_time(self) -> int:
         """Return total time active in minutes."""
         return self.details.get('active_time', 0)
 
     @property
-    def fan_level(self):
+    def fan_level(self) -> int:
         """Get current fan level (1-3)."""
         return self.details.get('level', 0)
 
     @property
-    def filter_life(self):
+    def filter_life(self) -> int:
         """Get percentage of filter life remaining."""
         try:
             return self.details['filter_life'].get('percent', 0)
@@ -79,16 +81,16 @@ def filter_life(self):
             return 0
 
     @property
-    def air_quality(self):
+    def air_quality(self) -> str:
         """Get Air Quality."""
         return self.details.get('air_quality', 'unknown')
 
     @property
-    def screen_status(self):
+    def screen_status(self) -> str:
         """Return Screen status (on/off)."""
         return self.details.get('screen_status', 'unknown')
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn Air Purifier on."""
         if self.device_status != 'on':
             body = helpers.req_body(self.manager, 'devicestatus')
@@ -97,20 +99,18 @@ def turn_on(self):
             head = helpers.req_headers(self.manager)
 
             r, _ = helpers.call_api(
-                '/131airPurifier/v1/device/deviceStatus',
-                'put',
-                json=body,
-                headers=head
+                '/131airPurifier/v1/device/deviceStatus', 'put',
+                json=body, headers=head
             )
 
             if r is not None and helpers.code_check(r):
                 self.device_status = 'on'
                 return True
-            else:
-                logger.warning('Error turning %s on', self.device_name)
-                return False
+            logger.warning('Error turning %s on', self.device_name)
+            return False
+        return False
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn Air Purifier Off."""
         if self.device_status == 'on':
             body = helpers.req_body(self.manager, 'devicestatus')
@@ -119,28 +119,26 @@ def turn_off(self):
             head = helpers.req_headers(self.manager)
 
             r, _ = helpers.call_api(
-                '/131airPurifier/v1/device/deviceStatus',
-                'put',
-                json=body,
-                headers=head
+                '/131airPurifier/v1/device/deviceStatus', 'put',
+                json=body, headers=head
             )
 
             if r is not None and helpers.code_check(r):
                 self.device_status = 'off'
                 return True
-            else:
-                logger.warning('Error turning %s off', self.device_name)
-                return False
+            logger.warning('Error turning %s off', self.device_name)
+            return False
+        return True
 
-    def auto_mode(self):
+    def auto_mode(self) -> bool:
         """Set mode to auto."""
         return self.mode_toggle('auto')
 
-    def manual_mode(self):
+    def manual_mode(self) -> bool:
         """Set mode to manual."""
         return self.mode_toggle('manual')
 
-    def sleep_mode(self):
+    def sleep_mode(self) -> bool:
         """Set sleep mode to on."""
         return self.mode_toggle('sleep')
 
@@ -151,17 +149,17 @@ def change_fan_speed(self, speed: int = None) -> bool:
         through speeds increasing by one.
         """
         if self.mode != 'manual':
-            logger.debug(
-                '{} not in manual mode, cannot change speed'.format(
-                    self.device_name))
+            logger.debug('%s not in manual mode, cannot change speed',
+                         self.device_name)
             return False
 
         try:
             level = self.details['level']
         except KeyError:
             logger.debug(
-                'Cannot change fan speed, no level set for {}'.format(
-                    self.device_name))
+                'Cannot change fan speed, no level set for %s',
+                self.device_name
+            )
             return False
 
         body = helpers.req_body(self.manager, 'devicestatus')
@@ -170,11 +168,11 @@ def change_fan_speed(self, speed: int = None) -> bool:
         if speed is not None:
             if speed == level:
                 return True
-            elif speed in [1, 2, 3]:
+            if speed in [1, 2, 3]:
                 body['level'] = speed
             else:
-                logger.debug(
-                    'Invalid fan speed for {}'.format(self.device_name))
+                logger.debug('Invalid fan speed for %s',
+                             format(self.device_name))
                 return False
         else:
             if (level + 1) > 3:
@@ -183,10 +181,8 @@ def change_fan_speed(self, speed: int = None) -> bool:
                 body['level'] = int(level + 1)
 
         r, _ = helpers.call_api(
-            '/131airPurifier/v1/device/updateSpeed',
-            'put',
-            json=body,
-            headers=head
+            '/131airPurifier/v1/device/updateSpeed', 'put',
+            json=body, headers=head
         )
 
         if r is not None and helpers.code_check(r):
@@ -201,50 +197,52 @@ def mode_toggle(self, mode: str) -> bool:
         body = helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         if mode != self.mode and mode in ['sleep', 'auto', 'manual']:
-            body["mode"] = mode
+            body['mode'] = mode
             if mode == 'manual':
                 body['level'] = 1
 
             r, _ = helpers.call_api(
-                '/131airPurifier/v1/device/updateMode',
-                'put',
-                json=body,
-                headers=head
+                '/131airPurifier/v1/device/updateMode', 'put',
+                json=body, headers=head
             )
 
             if r is not None and helpers.code_check(r):
                 self.mode = mode
                 return True
 
-        logger.warning("Error setting %s mode - %s", self.device_name, mode)
+        logger.warning('Error setting %s mode - %s', self.device_name, mode)
         return False
 
-    def update(self):
+    def update(self) -> None:
         """Run function to get device details."""
         self.get_details()
 
-    def display(self):
+    def display(self) -> None:
         """Return formatted device info to stdout."""
-        super(VeSyncAir131, self).display()
-        disp1 = [("Active Time : ", self.active_time, ' minutes'),
-                 ("Fan Level: ", self.fan_level, ""),
-                 ("Air Quality: ", self.air_quality, ""),
-                 ("Mode: ", self.mode, ""),
-                 ("Screen Status: ", self.screen_status, ""),
-                 ("Filter List: ", self.filter_life, " percent")]
+        super().display()
+        disp1 = [
+            ('Active Time : ', self.active_time, ' minutes'),
+            ('Fan Level: ', self.fan_level, ''),
+            ('Air Quality: ', self.air_quality, ''),
+            ('Mode: ', self.mode, ''),
+            ('Screen Status: ', self.screen_status, ''),
+            ('Filter List: ', self.filter_life, ' percent'),
+        ]
         for line in disp1:
-            print("{:.<15} {} {}".format(line[0], line[1], line[2]))
+            print('{:.<15} {} {}'.format(line[0], line[1], line[2]))
 
-    def displayJSON(self):
+    def displayJSON(self) -> str:
         """Return air purifier status and properties in JSON output."""
         sup = super().displayJSON()
-        supVal = json.loads(sup)
-        supVal.append({
-            "Active Time": str(self.active_time),
-            "Fan Level": self.fan_level,
-            "Air Quality": self.air_quality,
-            "Mode": self.mode,
-            "Screen Status": self.screen_status,
-            "Filter Life": str(self.filter_life)
-            })
-        return supVal
+        sup_val = json.loads(sup)
+        sup_val.append(
+            {
+                'Active Time': str(self.active_time),
+                'Fan Level': self.fan_level,
+                'Air Quality': self.air_quality,
+                'Mode': self.mode,
+                'Screen Status': self.screen_status,
+                'Filter Life': str(self.filter_life),
+            }
+        )
+        return sup_val
diff --git a/src/pyvesync/vesyncoutlet.py b/src/pyvesync/vesyncoutlet.py
index 657784b..6c46d58 100644
--- a/src/pyvesync/vesyncoutlet.py
+++ b/src/pyvesync/vesyncoutlet.py
@@ -5,7 +5,7 @@
 import json
 from abc import ABCMeta, abstractmethod
 
-from pyvesync.helpers import Helpers as helpers
+from pyvesync.helpers import Helpers
 from pyvesync.vesyncbasedevice import VeSyncBaseDevice
 
 logger = logging.getLogger(__name__)
@@ -18,7 +18,7 @@ class VeSyncOutlet(VeSyncBaseDevice):
 
     def __init__(self, details, manager):
         """Initilize VeSync Outlet base class."""
-        super(VeSyncOutlet, self).__init__(details, manager)
+        super().__init__(details, manager)
 
         self.details = {}
         self.energy = {}
@@ -30,31 +30,30 @@ def update_time_check(self) -> bool:
         """Test if energy update interval has been exceeded."""
         if self.update_energy_ts is None:
             return True
-        else:
-            if (time.time() - self.update_energy_ts) \
-                    > self._energy_update_interval:
-                return True
-            else:
-                return False
+
+        if ((time.time() - self.update_energy_ts)
+                > self._energy_update_interval):
+            return True
+        return False
 
     @abstractmethod
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Return True if device has beeeen turned on."""
 
     @abstractmethod
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Return True if device has beeeen turned off."""
 
     @abstractmethod
-    def get_details(self):
+    def get_details(self) -> None:
         """Build details dictionary."""
 
     @abstractmethod
-    def get_weekly_energy(self):
+    def get_weekly_energy(self) -> None:
         """Build weekly energy history dictionary."""
 
     @abstractmethod
-    def get_monthly_energy(self):
+    def get_monthly_energy(self) -> None:
         """Build Monthly Energy History Dictionary."""
 
     @abstractmethod
@@ -117,31 +116,36 @@ def yearly_energy_total(self) -> float:
 
     def display(self):
         """Return formatted device info to stdout."""
-        super(VeSyncOutlet, self).display()
-        disp1 = [("Active Time : ", self.active_time, ' minutes'),
-                 ("Energy: ", self.energy_today, " kWh"),
-                 ("Power: ", self.power, " Watts"),
-                 ("Voltage: ", self.voltage, " Volts"),
-                 ("Energy Week: ", self.weekly_energy_total, " kWh"),
-                 ("Energy Month: ", self.monthly_energy_total, " kWh"),
-                 ("Energy Year: ", self.yearly_energy_total, " kWh")]
+        super().display()
+        disp1 = [
+            ('Active Time : ', self.active_time, ' minutes'),
+            ('Energy: ', self.energy_today, ' kWh'),
+            ('Power: ', self.power, ' Watts'),
+            ('Voltage: ', self.voltage, ' Volts'),
+            ('Energy Week: ', self.weekly_energy_total, ' kWh'),
+            ('Energy Month: ', self.monthly_energy_total, ' kWh'),
+            ('Energy Year: ', self.yearly_energy_total, ' kWh'),
+        ]
         for line in disp1:
-            print("{:.<15} {} {}".format(line[0], line[1], line[2]))
+            print('{:.<15} {} {}'.format(line[0], line[1], line[2]))
 
     def displayJSON(self):
         """Return JSON details for outlet."""
         sup = super().displayJSON()
-        supVal = json.loads(sup)
-        supVal.update({
-            "Active Time": str(self.active_time),
-            "Energy": str(self.energy_today),
-            "Power": str(self.power),
-            "Voltage": str(self.voltage),
-            "Energy Week": str(self.weekly_energy_total),
-            "Energy Month": str(self.monthly_energy_total),
-            "Energy Year": str(self.yearly_energy_total)})
+        sup_val = json.loads(sup)
+        sup_val.update(
+            {
+                'Active Time': str(self.active_time),
+                'Energy': str(self.energy_today),
+                'Power': str(self.power),
+                'Voltage': str(self.voltage),
+                'Energy Week': str(self.weekly_energy_total),
+                'Energy Month': str(self.monthly_energy_total),
+                'Energy Year': str(self.yearly_energy_total),
+            }
+        )
 
-        return supVal
+        return sup_val
 
 
 class VeSyncOutlet7A(VeSyncOutlet):
@@ -149,18 +153,18 @@ class VeSyncOutlet7A(VeSyncOutlet):
 
     def __init__(self, details, manager):
         """Initilize Etekcity 7A round outlet class."""
-        super(VeSyncOutlet7A, self).__init__(details, manager)
-        self.det_keys = ['deviceStatus', 'activeTime', 'energy',
-                         'power', 'voltage']
-        self.energy_keys = ['energyConsumptionOfToday', 'maxEnergy',
-                            'totalEnergy']
+        super().__init__(details, manager)
+        self.det_keys = ['deviceStatus', 'activeTime',
+                         'energy', 'power', 'voltage']
+        self.energy_keys = ['energyConsumptionOfToday',
+                            'maxEnergy', 'totalEnergy']
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get 7A outlet details."""
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/v1/device/' + self.cid + '/detail',
             'get',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if r is not None and all(x in r for x in self.det_keys):
@@ -168,59 +172,59 @@ def get_details(self):
             self.details['active_time'] = r.get('activeTime', 0)
             self.details['energy'] = r.get('energy', 0)
             power = r.get('power', '0:0')
-            power = round(float(helpers.calculate_hex(power)), 2)
+            power = round(float(Helpers.calculate_hex(power)), 2)
             self.details['power'] = power
             voltage = r.get('voltage', '0:0')
-            voltage = round(float(helpers.calculate_hex(voltage)), 2)
+            voltage = round(float(Helpers.calculate_hex(voltage)), 2)
             self.details['voltage'] = voltage
         else:
             logger.debug('Unable to get %s details', self.device_name)
 
-    def get_weekly_energy(self):
+    def get_weekly_energy(self) -> None:
         """Get 7A outlet weekly energy info and buld weekly energy dict."""
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/v1/device/' + self.cid + '/energy/week',
             'get',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if r is not None and all(x in r for x in self.energy_keys):
-            self.energy['week'] = helpers.build_energy_dict(r)
+            self.energy['week'] = Helpers.build_energy_dict(r)
         else:
             logger.debug('Unable to get %s weekly data', self.device_name)
 
-    def get_monthly_energy(self):
+    def get_monthly_energy(self) -> None:
         """Get 7A outlet monthly energy info and buld monthly energy dict."""
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/v1/device/' + self.cid + '/energy/month',
             'get',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if r is not None and all(x in r for x in self.energy_keys):
-            self.energy['month'] = helpers.build_energy_dict(r)
+            self.energy['month'] = Helpers.build_energy_dict(r)
         else:
             logger.warning('Unable to get %s monthly data', self.device_name)
 
-    def get_yearly_energy(self):
+    def get_yearly_energy(self) -> None:
         """Get 7A outlet yearly energy info and build yearly energy dict."""
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/v1/device/' + self.cid + '/energy/year',
             'get',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if r is not None and all(x in r for x in self.energy_keys):
-            self.energy['year'] = helpers.build_energy_dict(r)
+            self.energy['year'] = Helpers.build_energy_dict(r)
         else:
             logger.debug('Unable to get %s yearly data', self.device_name)
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn 7A outlet on - return True if successful."""
-        _, status_code = helpers.call_api(
+        _, status_code = Helpers.call_api(
             '/v1/wifi-switch-1.3/' + self.cid + '/status/on',
             'put',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if status_code is not None and status_code == 200:
@@ -230,34 +234,33 @@ def turn_on(self):
         logger.warning('Error turning %s on', self.device_name)
         return False
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn 7A outlet off - return True if successful."""
-        _, status_code = helpers.call_api(
+        _, status_code = Helpers.call_api(
             '/v1/wifi-switch-1.3/' + self.cid + '/status/off',
             'put',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if status_code is not None and status_code == 200:
             self.device_status = 'off'
 
             return True
-        else:
-            logger.warning('Error turning %s off', self.device_name)
-            return False
+        logger.warning('Error turning %s off', self.device_name)
+        return False
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get 7A outlet configuration info."""
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/v1/device/' + self.cid + '/configurations',
             'get',
-            headers=helpers.req_headers(self.manager)
+            headers=Helpers.req_headers(self.manager),
         )
 
         if 'currentFirmVersion' in r:
-            self.config = helpers.build_config_dict(r)
+            self.config = Helpers.build_config_dict(r)
         else:
-            logger.debug("Error getting configuration info for %s",
+            logger.debug('Error getting configuration info for %s',
                          self.device_name)
 
 
@@ -266,130 +269,130 @@ class VeSyncOutlet10A(VeSyncOutlet):
 
     def __init__(self, details, manager):
         """Initialize 10A outlet class."""
-        super(VeSyncOutlet10A, self).__init__(details, manager)
+        super().__init__(details, manager)
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get 10A outlet details."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
 
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/10a/v1/device/devicedetail',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(r):
+        if Helpers.code_check(r):
             self.device_status = r.get('deviceStatus', self.device_status)
             self.connection_status = r.get('connectionStatus',
                                            self.connection_status)
-            self.details = helpers.build_details_dict(r)
+            self.details = Helpers.build_details_dict(r)
         else:
             logger.debug('Unable to get %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get 10A outlet configuration info."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
         body['uuid'] = self.uuid
 
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/10a/v1/device/configurations',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(r):
-            self.config = helpers.build_config_dict(r)
+        if Helpers.code_check(r):
+            self.config = Helpers.build_config_dict(r)
         else:
             logger.debug('Error getting %s config info', self.device_name)
 
-    def get_weekly_energy(self):
+    def get_weekly_energy(self) -> None:
         """Get 10A outlet weekly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_week')
+        body = Helpers.req_body(self.manager, 'energy_week')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/10a/v1/device/energyweek',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['week'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['week'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s weekly data', self.device_name)
 
-    def get_monthly_energy(self):
+    def get_monthly_energy(self) -> None:
         """Get 10A outlet monthly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_month')
+        body = Helpers.req_body(self.manager, 'energy_month')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/10a/v1/device/energymonth',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['month'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['month'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s monthly data', self.device_name)
 
-    def get_yearly_energy(self):
+    def get_yearly_energy(self) -> None:
         """Get 10A outlet yearly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_year')
+        body = Helpers.req_body(self.manager, 'energy_year')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/10a/v1/device/energyyear',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['year'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['year'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s yearly data', self.device_name)
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn 10A outlet on - return True if successful."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['status'] = 'on'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/10a/v1/device/devicestatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             self.device_status = 'on'
             return True
         logger.warning('Error turning %s on', self.device_name)
         return False
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn 10A outlet off - return True if successful."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['status'] = 'off'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/10a/v1/device/devicestatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             self.device_status = 'off'
             return True
         logger.warning('Error turning %s off', self.device_name)
@@ -401,174 +404,186 @@ class VeSyncOutlet15A(VeSyncOutlet):
 
     def __init__(self, details, manager):
         """Initialize 15A rectangular outlets."""
-        super(VeSyncOutlet15A, self).__init__(details, manager)
+        super().__init__(details, manager)
+        self.nightlight_status = 'off'
+        self.nightlight_brightness = 0
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get 15A outlet details."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
 
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/15a/v1/device/devicedetail',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        attr_list = ('deviceStatus', 'activeTime', 'energy', 'power',
-                     'voltage', 'nightLightStatus', 'nightLightAutomode',
-                     'nightLightBrightness')
+        attr_list = (
+            'deviceStatus',
+            'activeTime',
+            'energy',
+            'power',
+            'voltage',
+            'nightLightStatus',
+            'nightLightAutomode',
+            'nightLightBrightness',
+        )
 
-        if (helpers.code_check(r)
-                and all(k in r for k in attr_list)):
+        if Helpers.code_check(r) and all(k in r for k in attr_list):
 
             self.device_status = r.get('deviceStatus')
             self.connection_status = r.get('connectionStatus')
-            self.details = helpers.build_details_dict(r)
+            self.nightlight_status = r.get('nightLightStatus')
+            self.nightlight_brightness = r.get('nightLightBrightness')
+            self.details = Helpers.build_details_dict(r)
         else:
             logger.debug('Unable to get %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get 15A outlet configuration info."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
         body['uuid'] = self.uuid
 
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/15a/v1/device/configurations',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(r):
-            self.config = helpers.build_config_dict(r)
+        if Helpers.code_check(r):
+            self.config = Helpers.build_config_dict(r)
         else:
-            logger.debug("Unable to get %s config info", self.device_name)
+            logger.debug('Unable to get %s config info', self.device_name)
 
-    def get_weekly_energy(self):
+    def get_weekly_energy(self) -> None:
         """Get 15A outlet weekly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_week')
+        body = Helpers.req_body(self.manager, 'energy_week')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/energyweek',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['week'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['week'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s weekly data', self.device_name)
 
-    def get_monthly_energy(self):
+    def get_monthly_energy(self) -> None:
         """Get 15A outlet monthly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_month')
+        body = Helpers.req_body(self.manager, 'energy_month')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/energymonth',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['month'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['month'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s monthly data', self.device_name)
 
-    def get_yearly_energy(self):
+    def get_yearly_energy(self) -> None:
         """Get 15A outlet yearly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_year')
+        body = Helpers.req_body(self.manager, 'energy_year')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/energyyear',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
-            self.energy['year'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['year'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s yearly data', self.device_name)
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn 15A outlet on - return True if successful."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['status'] = 'on'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/devicestatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             self.device_status = 'on'
             return True
         logger.warning('Error turning %s on', self.device_name)
         return False
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn 15A outlet off - return True if successful."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['status'] = 'off'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/devicestatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             self.device_status = 'off'
             return True
 
         logger.warning('Error turning %s off', self.device_name)
         return False
 
-    def turn_on_nightlight(self):
+    def turn_on_nightlight(self) -> bool:
         """Turn on nightlight."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['mode'] = 'auto'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/nightlightstatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             return True
-        logger.debug("Error turning on %s nightlight", self.device_name)
+        logger.debug('Error turning on %s nightlight', self.device_name)
+        return False
 
-    def turn_off_nightlight(self):
+    def turn_off_nightlight(self) -> bool:
         """Turn Off Nightlight."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['mode'] = 'manual'
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/15a/v1/device/nightlightstatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             return True
-        logger.debug("Error turning off %s nightlight", self.device_name)
+        logger.debug('Error turning off %s nightlight', self.device_name)
+        return False
 
 
 class VeSyncOutdoorPlug(VeSyncOutlet):
@@ -576,129 +591,122 @@ class VeSyncOutdoorPlug(VeSyncOutlet):
 
     def __init__(self, details, manager):
         """Initialize Etekcity Outdoor Plug class."""
-        super(VeSyncOutdoorPlug, self).__init__(details, manager)
+        super().__init__(details, manager)
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get details for outdoor outlet."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/devicedetail',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body)
+            headers=Helpers.req_headers(self.manager),
+            json=body,
+        )
 
-        if helpers.code_check(r):
-            self.details = helpers.build_details_dict(r)
+        if Helpers.code_check(r):
+            self.details = Helpers.build_details_dict(r)
             self.connection_status = r.get('connectionStatus')
 
             dev_no = self.sub_device_no
             sub_device_list = r.get('subDevices')
             if sub_device_list and dev_no <= len(sub_device_list):
                 self.device_status = sub_device_list[(dev_no + -1)].get(
-                    'subDeviceStatus')
-        else:
-            logger.debug('Unable to get %s details', self.device_name)
+                    'subDeviceStatus'
+                )
+        logger.debug('Unable to get %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get configuration info for outdoor outlet."""
-        body = helpers.req_body(self.manager, 'devicedetail')
+        body = Helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
         body['uuid'] = self.uuid
 
-        r, _ = helpers.call_api(
+        r, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/configurations',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(r):
-            self.config = helpers.build_config_dict(r)
-        else:
-            logger.debug("Error getting %s config info", self.device_name)
+        if Helpers.code_check(r):
+            self.config = Helpers.build_config_dict(r)
+        logger.debug('Error getting %s config info', self.device_name)
 
-    def get_weekly_energy(self):
+    def get_weekly_energy(self) -> None:
         """Get outdoor outlet weekly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_week')
+        body = Helpers.req_body(self.manager, 'energy_week')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/energyweek',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
-            )
+            headers=Helpers.req_headers(self.manager),
+            json=body,
+        )
 
-        if helpers.code_check(response):
-            self.energy['week'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['week'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s weekly data', self.device_name)
 
-    def get_monthly_energy(self):
+    def get_monthly_energy(self) -> None:
         """Get outdoor outlet monthly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_month')
+        body = Helpers.req_body(self.manager, 'energy_month')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/energymonth',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
-            )
+            headers=Helpers.req_headers(self.manager),
+            json=body,
+        )
 
-        if helpers.code_check(response):
-            self.energy['month'] = helpers.build_energy_dict(response)
-        else:
-            logger.debug('Unable to get %s monthly data', self.device_name)
+        if Helpers.code_check(response):
+            self.energy['month'] = Helpers.build_energy_dict(response)
+        logger.debug('Unable to get %s monthly data', self.device_name)
 
-    def get_yearly_energy(self):
+    def get_yearly_energy(self) -> None:
         """Get outdoor outlet yearly energy info and populate energy dict."""
-        body = helpers.req_body(self.manager, 'energy_year')
+        body = Helpers.req_body(self.manager, 'energy_year')
         body['uuid'] = self.uuid
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/energyyear',
             'post',
-            headers=helpers.req_headers(self.manager),
-            json=body
-            )
+            headers=Helpers.req_headers(self.manager),
+            json=body,
+        )
 
-        if helpers.code_check(response):
-            self.energy['year'] = helpers.build_energy_dict(response)
+        if Helpers.code_check(response):
+            self.energy['year'] = Helpers.build_energy_dict(response)
         else:
             logger.debug('Unable to get %s yearly data', self.device_name)
 
-    def toggle(self, status):
+    def toggle(self, status) -> bool:
         """Toggle power for outdoor outlet."""
-        body = helpers.req_body(self.manager, 'devicestatus')
+        body = Helpers.req_body(self.manager, 'devicestatus')
         body['uuid'] = self.uuid
         body['status'] = status
         body['switchNo'] = self.sub_device_no
 
-        response, _ = helpers.call_api(
+        response, _ = Helpers.call_api(
             '/outdoorsocket15a/v1/device/devicestatus',
             'put',
-            headers=helpers.req_headers(self.manager),
-            json=body
+            headers=Helpers.req_headers(self.manager),
+            json=body,
         )
 
-        if helpers.code_check(response):
+        if Helpers.code_check(response):
             self.device_status = status
             return True
         logger.warning('Error turning %s %s', self.device_name, status)
         return False
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn outdoor outlet on and return True if successful."""
-        if self.toggle('on'):
-            return True
-        else:
-            return False
+        return bool(self.toggle('on'))
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn outdoor outlet off and return True if successful."""
-        if self.toggle('off'):
-            return True
-        else:
-            return False
+        return bool(self.toggle('off'))
diff --git a/src/pyvesync/vesyncswitch.py b/src/pyvesync/vesyncswitch.py
index f420215..7d5c5e9 100644
--- a/src/pyvesync/vesyncswitch.py
+++ b/src/pyvesync/vesyncswitch.py
@@ -3,15 +3,21 @@
 import logging
 import json
 from abc import ABCMeta, abstractmethod
+from collections import defaultdict
+from typing import DefaultDict, Dict, List
 
 from pyvesync.helpers import Helpers as helpers
 from pyvesync.vesyncbasedevice import VeSyncBaseDevice
 
 logger = logging.getLogger(__name__)
-feature_dict = {
-    'ESWL01': [],
-    'ESWD16': ['dimmable']
-}
+
+feature_list: Dict[str, List[str]] = dict(
+    ESWL01=[],
+    ESWD16=['dimmable'],
+    ESWL03=[]
+)
+
+feature_dict: DefaultDict[str, List[str]] = defaultdict(list, feature_list)
 
 
 class VeSyncSwitch(VeSyncBaseDevice):
@@ -24,35 +30,32 @@ def __init__(self, details, manager):
         super().__init__(details, manager)
         self.details = {}
 
-    def is_dimmable(self):
+    def is_dimmable(self) -> bool:
         """Return True if switch is dimmable."""
-        if 'dimmable' in feature_dict.get(self.device_type):
-            return True
-        else:
-            return False
+        return bool('dimmable' in feature_dict.get(self.device_type, []))
 
     @abstractmethod
-    def get_details(self):
+    def get_details(self) -> None:
         """Get Device Details."""
 
     @abstractmethod
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn Switch On."""
 
     @abstractmethod
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn switch off."""
 
     @abstractmethod
-    def get_config(self):
+    def get_config(self) -> None:
         """Get configuration and firmware deatils."""
 
     @property
-    def active_time(self):
+    def active_time(self) -> int:
         """Get active time of switch."""
         return self.details.get('active_time', 0)
 
-    def update(self):
+    def update(self) -> None:
         """Update device details."""
         self.get_details()
 
@@ -62,30 +65,28 @@ class VeSyncWallSwitch(VeSyncSwitch):
 
     def __init__(self, details, manager):
         """Initialize standard etekcity wall switch class."""
-        super(VeSyncWallSwitch, self).__init__(details, manager)
+        super().__init__(details, manager)
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get switch device details."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
         head = helpers.req_headers(self.manager)
 
         r, _ = helpers.call_api(
-            '/inwallswitch/v1/device/devicedetail',
-            'post',
-            headers=head,
-            json=body
+            '/inwallswitch/v1/device/devicedetail', 'post',
+            headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
             self.device_status = r.get('deviceStatus', self.device_status)
             self.details['active_time'] = r.get('activeTime', 0)
-            self.connection_status = r.get('connectionStatus',
-                                           self.connection_status)
+            self.connection_status = r.get(
+                'connectionStatus', self.connection_status)
         else:
             logger.debug('Error getting %s details', self.device_name)
 
-    def get_config(self):
+    def get_config(self) -> None:
         """Get switch device configuration info."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['method'] = 'configurations'
@@ -95,14 +96,16 @@ def get_config(self):
             '/inwallswitch/v1/device/configurations',
             'post',
             headers=helpers.req_headers(self.manager),
-            json=body)
+            json=body,
+        )
 
         if helpers.code_check(r):
             self.config = helpers.build_config_dict(r)
         else:
-            logger.warning("Unable to get %s config info", self.device_name)
+            logger.warning('Unable to get %s config info',
+                           self.device_name)
 
-    def turn_off(self):
+    def turn_off(self) -> bool:
         """Turn off switch device."""
         body = helpers.req_body(self.manager, 'devicestatus')
         body['status'] = 'off'
@@ -110,10 +113,8 @@ def turn_off(self):
         head = helpers.req_headers(self.manager)
 
         r, _ = helpers.call_api(
-            '/inwallswitch/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body
+            '/inwallswitch/v1/device/devicestatus', 'put',
+            headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
@@ -122,7 +123,7 @@ def turn_off(self):
         logger.warning('Error turning %s off', self.device_name)
         return False
 
-    def turn_on(self):
+    def turn_on(self) -> bool:
         """Turn on switch device."""
         body = helpers.req_body(self.manager, 'devicestatus')
         body['status'] = 'on'
@@ -130,10 +131,8 @@ def turn_on(self):
         head = helpers.req_headers(self.manager)
 
         r, _ = helpers.call_api(
-            '/inwallswitch/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body
+            '/inwallswitch/v1/device/devicestatus', 'put',
+            headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
@@ -149,28 +148,26 @@ class VeSyncDimmerSwitch(VeSyncSwitch):
     def __init__(self, details, manager):
         """Initilize dimmer switch class."""
         super().__init__(details, manager)
-        self._brightness = None
+        self._brightness = 'unknown'
         self._rgb_value = {'red': 0, 'blue': 0, 'green': 0}
-        self._rgb_status = None
-        self._indicator_light = None
+        self._rgb_status = 'unknown'
+        self._indicator_light = 'unknown'
 
-    def get_details(self):
+    def get_details(self) -> None:
         """Get dimmer switch details."""
         body = helpers.req_body(self.manager, 'devicedetail')
         body['uuid'] = self.uuid
         head = helpers.req_headers(self.manager)
 
         r, _ = helpers.call_api(
-            '/dimmer/v1/device/devicedetail',
-            'post',
-            headers=head,
-            json=body)
+            '/dimmer/v1/device/devicedetail', 'post', headers=head, json=body
+        )
 
         if r is not None and helpers.code_check(r):
             self.device_status = r.get('deviceStatus', self.device_status)
             self.details['active_time'] = r.get('activeTime', 0)
-            self.connection_status = r.get('connectionStatus',
-                                           self.connection_status)
+            self.connection_status = r.get(
+                'connectionStatus', self.connection_status)
             self._brightness = r.get('brightness')
             self._rgb_status = r.get('rgbStatus')
             self._rgb_value = r.get('rgbValue')
@@ -179,22 +176,22 @@ def get_details(self):
             logger.debug('Error getting %s details', self.device_name)
 
     @property
-    def brightness(self):
+    def brightness(self) -> float:
         """Return brightness in percent."""
         return self._brightness
 
     @property
-    def indicator_light_status(self):
+    def indicator_light_status(self) -> str:
         """Faceplate brightness light status."""
         return self._indicator_light
 
     @property
-    def rgb_light_status(self):
+    def rgb_light_status(self) -> str:
         """RGB Faceplate light status."""
         return self._rgb_status
 
     @property
-    def rgb_light_value(self):
+    def rgb_light_value(self) -> dict:
         """RGB Light Values."""
         return self._rgb_value
 
@@ -209,17 +206,16 @@ def switch_toggle(self, status: str) -> bool:
         head = helpers.req_headers(self.manager)
 
         r, _ = helpers.call_api(
-            '/dimmer/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body
+            '/dimmer/v1/device/devicestatus', 'put',
+            headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
             self.device_status = status
             return True
 
-        logger.warning('Error turning %s %s', self.device_name, status)
+        logger.warning('Error turning %s %s',
+                       self.device_name, status)
         return False
 
     def turn_on(self) -> bool:
@@ -242,9 +238,7 @@ def indicator_light_toggle(self, status: str) -> bool:
 
         r, _ = helpers.call_api(
             '/dimmer/v1/device/indicatorlightstatus',
-            'put',
-            headers=head,
-            json=body
+            'put', headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
@@ -263,8 +257,10 @@ def indicator_light_off(self) -> bool:
         """Turn indicator light off."""
         return self.indicator_light_toggle('off')
 
-    def rgb_color_status(self, status: str, red: int = None,
-                         blue: int = None, green: int = None) -> bool:
+    def rgb_color_status(
+            self, status: str,
+            red: int = None, blue: int = None, green: int = None
+    ) -> bool:
         """Set faceplate RGB color."""
         body = helpers.req_body(self.manager, 'devicestatus')
         body['status'] = status
@@ -274,10 +270,8 @@ def rgb_color_status(self, status: str, red: int = None,
             body['rgbValue'] = {'red': red, 'blue': blue, 'green': green}
 
         r, _ = helpers.call_api(
-            '/dimmer/v1/device/devicergbstatus',
-            'put',
-            headers=head,
-            json=body
+            '/dimmer/v1/device/devicergbstatus', 'put',
+            headers=head, json=body
         )
 
         if r is not None and helpers.code_check(r):
@@ -298,20 +292,20 @@ def rgb_color_on(self) -> bool:
 
     def rgb_color_set(self, red: int, green: int, blue: int) -> bool:
         """Set RGB color of faceplate."""
-        if (isinstance(red, int) and
-                isinstance(green, int) and
-                isinstance(blue, int)):
+        if isinstance(red, int) and isinstance(
+                green, int) and isinstance(blue, int):
             for color in [red, green, blue]:
                 if color < 0 or color > 255:
                     logger.warning('Invalid RGB value')
                     return False
 
-            return self.rgb_color_status('on', red, green, blue)
+            return bool(self.rgb_color_status('on', red, green, blue))
+        return False
 
-    def set_brightness(self, brightness: int):
+    def set_brightness(self, brightness: int) -> bool:
         """Set brightness of dimmer - 1 - 100."""
-        if isinstance(brightness, int) and \
-                (brightness > 0 or brightness <= 100):
+        if isinstance(brightness, int) and (
+                brightness > 0 or brightness <= 100):
 
             body = helpers.req_body(self.manager, 'devicestatus')
             body['brightness'] = brightness
@@ -319,28 +313,45 @@ def set_brightness(self, brightness: int):
             head = helpers.req_headers(self.manager)
 
             r, _ = helpers.call_api(
-                '/dimmer/v1/device/updatebrightness',
-                'put',
-                headers=head,
-                json=body)
+                '/dimmer/v1/device/updatebrightness', 'put',
+                headers=head, json=body
+            )
 
             if r is not None and helpers.code_check(r):
                 self._brightness = brightness
                 return True
-            else:
-                logger.warning('Error setting %s brightness', self.device_name)
+            logger.warning('Error setting %s brightness', self.device_name)
         else:
             logger.warning('Invalid brightness')
         return False
 
-    def displayJSON(self):
+    def displayJSON(self) -> str:
         """JSON API for dimmer switch."""
-        sup = super().displayJSON()
-        supVal = json.loads(sup)
-        if self.is_dimmable:
-            supVal.update({
-                "Indicator Light": str(self.active_time),
-                "Brightness": str(self._brightness),
-                "RGB Light": str(self._rgb_status)
-            })
-        return supVal
+        sup_val = json.loads(super().displayJSON())
+        if self.is_dimmable:  # pylint: disable=using-constant-test
+            sup_val.update(
+                {
+                    'Indicator Light': str(self.active_time),
+                    'Brightness': str(self._brightness),
+                    'RGB Light': str(self._rgb_status),
+                }
+            )
+        return sup_val
+
+    def get_config(self) -> None:
+        """Get dimmable switch device configuration info."""
+        body = helpers.req_body(self.manager, 'devicedetail')
+        body['method'] = 'configurations'
+        body['uuid'] = self.uuid
+
+        r, _ = helpers.call_api(
+            '/dimmer/v1/device/configurations',
+            'post',
+            headers=helpers.req_headers(self.manager),
+            json=body,
+        )
+
+        if helpers.code_check(r):
+            self.config = helpers.build_config_dict(r)
+        else:
+            logger.warning('Unable to get %s config info', self.device_name)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..7871501
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,25 @@
+[tox]
+envlist = py36, py37, py38, pylint, lint, mypy
+skip_missing_interpreters = True
+
+[testenv]
+basepython = {env:PYTHON3_PATH:python3}
+deps = pytest
+commands = pytest -q {posargs}
+
+[testenv:pylint]
+ignore_errors = True
+deps = pylint
+commands = pylint {posargs} src/pyvesync
+
+[testenv:lint]
+ignore_errors = True
+deps =
+    flake8
+    flake8-docstrings
+commands = flake8 src/pyvesync
+
+[testenv:mypy]
+ignore_errors = True
+deps = mypy
+commands = mypy src/pyvesync
\ No newline at end of file


Test patch:
diff --git a/src/tests/call_json.py b/src/tests/call_json.py
index b7b1ec8..6b8a823 100644
--- a/src/tests/call_json.py
+++ b/src/tests/call_json.py
@@ -59,234 +59,261 @@
     'appVersion': APP_VERSION,
     'content-type': 'application/json',
     'tk': SAMPLE_TOKEN,
-    'tz': DEFAULT_TZ
+    'tz': DEFAULT_TZ,
 }
 
 DEFAULT_BODY = {
-    "acceptLanguage": "en",
-    "accountID": SAMPLE_ACTID,
-    "appVersion": APP_VERSION,
-    "pageNo": 1,
-    "pageSize": 50,
-    "phoneBrand": PHONE_BRAND,
-    "phoneOS": PHONE_OS,
-    "timeZone": DEFAULT_TZ,
-    "token": SAMPLE_TOKEN,
-    "traceId": TRACE_ID
+    'acceptLanguage': 'en',
+    'accountID': SAMPLE_ACTID,
+    'appVersion': APP_VERSION,
+    'pageNo': 1,
+    'pageSize': 50,
+    'phoneBrand': PHONE_BRAND,
+    'phoneOS': PHONE_OS,
+    'timeZone': DEFAULT_TZ,
+    'token': SAMPLE_TOKEN,
+    'traceId': TRACE_ID,
 }
 
-ENERGY_HISTORY = ({
-    "code": 0,
-    "energyConsumptionOfToday": 1,
-    "costPerKWH": 1,
-    "maxEnergy": 1,
-    "totalEnergy": 1,
-    "data": [
-        1,
-        1,
-    ]
-}, 200)
-
-LOGIN_RET_BODY = ({
-    "traceId": TRACE_ID,
-    "msg": "",
-    "result": {
-        "accountID": SAMPLE_ACTID,
-        "avatarIcon": "",
-        "acceptLanguage": "en",
-        "gdprStatus": True,
-        "nickName": "nick",
-        "userType": "1",
-        "token": SAMPLE_TOKEN
+ENERGY_HISTORY = (
+    {
+        'code': 0,
+        'energyConsumptionOfToday': 1,
+        'costPerKWH': 1,
+        'maxEnergy': 1,
+        'totalEnergy': 1,
+        'data': [
+            1,
+            1,
+        ],
+    },
+    200,
+)
+
+LOGIN_RET_BODY = (
+    {
+        'traceId': TRACE_ID,
+        'msg': '',
+        'result': {
+            'accountID': SAMPLE_ACTID,
+            'avatarIcon': '',
+            'acceptLanguage': 'en',
+            'gdprStatus': True,
+            'nickName': 'nick',
+            'userType': '1',
+            'token': SAMPLE_TOKEN,
+        },
+        'code': 0,
     },
-    "code": 0
-}, 200)
+    200,
+)
 
 
 def login_call_body(email, password):
     json = {
-        "acceptLanguage": "en",
-        "appVersion": APP_VERSION,
-        "devToken": "",
-        "email": email,
-        "method": "login",
-        "password": password,
-        "phoneBrand": PHONE_BRAND,
-        "phoneOS": PHONE_OS,
-        "timeZone": DEFAULT_TZ,
-        "traceId": TRACE_ID,
-        "userType": "1"
+        'acceptLanguage': 'en',
+        'appVersion': APP_VERSION,
+        'devToken': '',
+        'email': email,
+        'method': 'login',
+        'password': password,
+        'phoneBrand': PHONE_BRAND,
+        'phoneOS': PHONE_OS,
+        'timeZone': DEFAULT_TZ,
+        'traceId': TRACE_ID,
+        'userType': '1',
     }
     return json
 
 
 LIST_CONF_7A = {
-    "deviceType": "wifi-switch-1.3",
-    "extension": None,
-    "macID": None,
-    "type": "wifi-switch",
-    "deviceName": "Name 7A Outlet",
-    "connectionType": "wifi",
-    "uuid": None,
-    "speed": None,
-    "deviceStatus": "on",
-    "mode": None,
-    "configModule": "7AOutlet",
-    "currentFirmVersion": "1.95",
-    "connectionStatus": "online",
-    "cid": "7A-CID"
+    'deviceType': 'wifi-switch-1.3',
+    'extension': None,
+    'macID': None,
+    'type': 'wifi-switch',
+    'deviceName': 'Name 7A Outlet',
+    'connectionType': 'wifi',
+    'uuid': None,
+    'speed': None,
+    'deviceStatus': 'on',
+    'mode': None,
+    'configModule': '7AOutlet',
+    'currentFirmVersion': '1.95',
+    'connectionStatus': 'online',
+    'cid': '7A-CID',
 }
 
 LIST_CONF_15A = {
-    "deviceType": "ESW15-USA",
-    "extension": None,
-    "macID": None,
-    "type": "wifi-switch",
-    "deviceName": "Name 15A Outlet",
-    "connectionType": "wifi",
-    "uuid": "UUID",
-    "speed": None,
-    "deviceStatus": "on",
-    "mode": None,
-    "configModule": "15AOutletNightlight",
-    "currentFirmVersion": None,
-    "connectionStatus": "online",
-    "cid": "15A-CID"
+    'deviceType': 'ESW15-USA',
+    'extension': None,
+    'macID': None,
+    'type': 'wifi-switch',
+    'deviceName': 'Name 15A Outlet',
+    'connectionType': 'wifi',
+    'uuid': 'UUID',
+    'speed': None,
+    'deviceStatus': 'on',
+    'mode': None,
+    'configModule': '15AOutletNightlight',
+    'currentFirmVersion': None,
+    'connectionStatus': 'online',
+    'cid': '15A-CID',
 }
 
 LIST_CONF_WS = {
-    "deviceType": "ESWL01",
-    "extension": None,
-    "macID": None,
-    "type": "Switches",
-    "deviceName": "Name Wall Switch",
-    "connectionType": "wifi",
-    "uuid": "UUID",
-    "speed": None,
-    "deviceStatus": "on",
-    "mode": None,
-    "configModule": "InwallswitchUS",
-    "currentFirmVersion": None,
-    "connectionStatus": "online",
-    "cid": "WS-CID"
+    'deviceType': 'ESWL01',
+    'extension': None,
+    'macID': None,
+    'type': 'Switches',
+    'deviceName': 'Name Wall Switch',
+    'connectionType': 'wifi',
+    'uuid': 'UUID',
+    'speed': None,
+    'deviceStatus': 'on',
+    'mode': None,
+    'configModule': 'InwallswitchUS',
+    'currentFirmVersion': None,
+    'connectionStatus': 'online',
+    'cid': 'WS-CID',
 }
 
 LIST_CONF_10AEU = {
-    "deviceType": "ESW01-EU",
-    "extension": None,
-    "macID": None,
-    "type": "wifi-switch",
-    "deviceName": "Name 10A Outlet",
-    "connectionType": "wifi",
-    "uuid": "UUID",
-    "speed": None,
-    "deviceStatus": "on",
-    "mode": None,
-    "configModule": "10AOutletEU",
-    "currentFirmVersion": None,
-    "connectionStatus": "online",
-    "cid": "10A-CID"
+    'deviceType': 'ESW01-EU',
+    'extension': None,
+    'macID': None,
+    'type': 'wifi-switch',
+    'deviceName': 'Name 10A Outlet',
+    'connectionType': 'wifi',
+    'uuid': 'UUID',
+    'speed': None,
+    'deviceStatus': 'on',
+    'mode': None,
+    'configModule': '10AOutletEU',
+    'currentFirmVersion': None,
+    'connectionStatus': 'online',
+    'cid': '10A-CID',
 }
 
 LIST_CONF_10AUS = {
-    "deviceType": "ESW03-USA",
-    "extension": None,
-    "macID": None,
-    "type": "wifi-switch",
-    "deviceName": "Name 10A Outlet",
-    "connectionType": "wifi",
-    "uuid": "UUID",
-    "speed": None,
-    "deviceStatus": "on",
-    "mode": None,
-    "configModule": "10AOutletUSA",
-    "currentFirmVersion": None,
-    "connectionStatus": "online",
-    "cid": "10A-CID"
+    'deviceType': 'ESW03-USA',
+    'extension': None,
+    'macID': None,
+    'type': 'wifi-switch',
+    'deviceName': 'Name 10A Outlet',
+    'connectionType': 'wifi',
+    'uuid': 'UUID',
+    'speed': None,
+    'deviceStatus': 'on',
+    'mode': None,
+    'configModule': '10AOutletUSA',
+    'currentFirmVersion': None,
+    'connectionStatus': 'online',
+    'cid': '10A-CID',
 }
 
 LIST_CONF_OUTDOOR_1 = {
-    "deviceRegion": "US",
-    "deviceName": "Outdoor Socket B",
-    "cid": "OUTDOOR-CID",
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "connectionType": "wifi",
-    "deviceType": "ESO15-TB",
-    "type": "wifi-switch",
-    "uuid": "UUID",
-    "configModule": "OutdoorSocket15A",
-    "macID": None,
-    "mode": None,
-    "speed": None,
-    "extension": None,
-    "currentFirmVersion": None,
-    "subDeviceNo": 1
+    'deviceRegion': 'US',
+    'deviceName': 'Outdoor Socket B',
+    'cid': 'OUTDOOR-CID',
+    'deviceStatus': 'on',
+    'connectionStatus': 'online',
+    'connectionType': 'wifi',
+    'deviceType': 'ESO15-TB',
+    'type': 'wifi-switch',
+    'uuid': 'UUID',
+    'configModule': 'OutdoorSocket15A',
+    'macID': None,
+    'mode': None,
+    'speed': None,
+    'extension': None,
+    'currentFirmVersion': None,
+    'subDeviceNo': 1,
 }
 
 LIST_CONF_OUTDOOR_2 = {
-    "deviceRegion": "US",
-    "deviceName": "Outdoor Socket B",
-    "cid": "OUTDOOR-CID",
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "connectionType": "wifi",
-    "deviceType": "ESO15-TB",
-    "type": "wifi-switch",
-    "uuid": "UUID",
-    "configModule": "OutdoorSocket15A",
-    "macID": None,
-    "mode": None,
-    "speed": None,
-    "extension": None,
-    "currentFirmVersion": None,
-    "subDeviceNo": 2
+    'deviceRegion': 'US',
+    'deviceName': 'Outdoor Socket B',
+    'cid': 'OUTDOOR-CID',
+    'deviceStatus': 'on',
+    'connectionStatus': 'online',
+    'connectionType': 'wifi',
+    'deviceType': 'ESO15-TB',
+    'type': 'wifi-switch',
+    'uuid': 'UUID',
+    'configModule': 'OutdoorSocket15A',
+    'macID': None,
+    'mode': None,
+    'speed': None,
+    'extension': None,
+    'currentFirmVersion': None,
+    'subDeviceNo': 2,
 }
 
 LIST_CONF_ESL100 = {
-    "deviceRegion": "US",
-    "deviceName": "Etekcity Soft White Bulb",
-    "cid": "ESL100-CID",
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "connectionType": "wifi",
-    "deviceType": "ESL100",
-    "type": "Wifi-light",
-    "uuid": "UUID",
-    "configModule": "WifiSmartBulb",
-    "macID": None,
-    "mode": None,
-    "speed": None,
-    "extension": None,
-    "currentFirmVersion": None,
-    "subDeviceNo": None
+    'deviceRegion': 'US',
+    'deviceName': 'Etekcity Soft White Bulb',
+    'cid': 'ESL100-CID',
+    'deviceStatus': 'on',
+    'connectionStatus': 'online',
+    'connectionType': 'wifi',
+    'deviceType': 'ESL100',
+    'type': 'Wifi-light',
+    'uuid': 'UUID',
+    'configModule': 'WifiSmartBulb',
+    'macID': None,
+    'mode': None,
+    'speed': None,
+    'extension': None,
+    'currentFirmVersion': None,
+    'subDeviceNo': None,
 }
 
 LIST_CONF_AIR = {
-    "deviceName": "Name Air Purifier",
-    "cid": "AIRPUR-CID",
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "connectionType": "wifi",
-    "deviceType": "LV-PUR131S",
-    "type": "wifi-air",
-    "uuid": "UUID",
-    "configModule": "AirPurifier131",
-    "macID": None,
-    "mode": "manual",
-    "speed": "low",
-    "extension": None,
-    "currentFirmVersion": None
+    'deviceName': 'Name Air Purifier',
+    'cid': 'AIRPUR-CID',
+    'deviceStatus': 'on',
+    'connectionStatus': 'online',
+    'connectionType': 'wifi',
+    'deviceType': 'LV-PUR131S',
+    'type': 'wifi-air',
+    'uuid': 'UUID',
+    'configModule': 'AirPurifier131',
+    'macID': None,
+    'mode': 'manual',
+    'speed': 'low',
+    'extension': None,
+    'currentFirmVersion': None,
 }
 
+LIST_CONF_DIMMER = {
+                "deviceRegion": "US",
+                "deviceName": "Etekcity Dimmer Switch",
+                "cid": "DIM-CID",
+                "deviceStatus": "on",
+                "connectionStatus": "online",
+                "connectionType": "wifi",
+                "deviceType": "ESWD16",
+                "type": "Switches",
+                "uuid": "DIM-UUID",
+                "configModule": "WifiWallDimmer"
+            }
+
 FULL_DEV_LIST = [
-    LIST_CONF_10AEU, LIST_CONF_10AUS, LIST_CONF_15A, LIST_CONF_7A,
-    LIST_CONF_AIR, LIST_CONF_WS, LIST_CONF_ESL100, LIST_CONF_OUTDOOR_1,
-    LIST_CONF_OUTDOOR_2
+    LIST_CONF_10AEU,
+    LIST_CONF_10AUS,
+    LIST_CONF_15A,
+    LIST_CONF_7A,
+    LIST_CONF_AIR,
+    LIST_CONF_WS,
+    LIST_CONF_ESL100,
+    LIST_CONF_OUTDOOR_1,
+    LIST_CONF_OUTDOOR_2,
+    LIST_CONF_DIMMER
 ]
 
 
 def get_devices_body():
+    """Build device body dictionary."""
     body = DEFAULT_BODY
     body['method'] = 'devices'
     return (body, 200)
@@ -304,16 +331,16 @@ def get_devices_body():
 
 DEVLIST_WS = ({'code': 0, 'result': {'list': [LIST_CONF_WS]}}, 200)
 
+DEVLIST_DIMMER = ({'code': 0, 'result': {'list': [LIST_CONF_DIMMER]}}, 200)
+
 DEVLIST_AIR = ({'code': 0, 'result': {'list': [LIST_CONF_AIR]}}, 200)
 
 DEVLIST_ESL100 = ({'code': 0, 'result': {'list': [LIST_CONF_ESL100]}}, 200)
 
-DEVLIST_OUTDOOR = ({
-    'code': 0,
-    'result': {
-        'list': [LIST_CONF_OUTDOOR_1, LIST_CONF_OUTDOOR_2]
-    }
-}, 200)
+DEVLIST_OUTDOOR = (
+    {'code': 0, 'result': {'list': [LIST_CONF_OUTDOOR_1, LIST_CONF_OUTDOOR_2]}},
+    200,
+)
 
 
 def get_details_body():
@@ -322,126 +349,149 @@ def get_details_body():
     return (body, 200)
 
 
-DETAILS_15A = ({
-    "code": 0,
-    "msg": None,
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "activeTime": 1,
-    "energy": 1,
-    "nightLightStatus": "on",
-    "nightLightBrightness": 50,
-    "nightLightAutomode": "manual",
-    "power": "1",
-    "voltage": "1"
-}, 200)
-
-DETAILS_7A = ({
-    "deviceStatus": "on",
-    "deviceImg": "",
-    "activeTime": 1,
-    "energy": 1,
-    "power": "1000:1000",
-    "voltage": "1000:1000"
-}, 200)
-
-DETAILS_WS = ({
-    "code": 0,
-    "msg": None,
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "activeTime": 1,
-    "power": "None",
-    "voltage": "None"
-}, 200)
-
-DETAILS_10A = ({
-    "code": 0,
-    "msg": None,
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "activeTime": 1,
-    "energy": 1,
-    "nightLightStatus": None,
-    "nightLightBrightness": None,
-    "nightLightAutomode": None,
-    "power": "1",
-    "voltage": "1"
-}, 200)
-
-DETAILS_OUTDOOR = ({
-    "code": 0,
-    "msg": None,
-    "connectionStatus": "online",
-    "activeTime": 1,
-    "energy": 1,
-    "power": "1",
-    "voltage": "1",
-    "deviceStatus": "on",
-    "deviceName": "Etekcity Outdoor Plug",
-    "subDevices": [{
-        "subDeviceNo": 1,
-        "defaultName": "Socket A",
-        "subDeviceName": "Outdoor Socket A",
-        "subDeviceStatus": "on",
-    }, {
-        "subDeviceNo": 2,
-        "defaultName": "Socket B",
-        "subDeviceName": "Outdoor Socket B",
-        "subDeviceStatus": "on",
-    }]
-}, 200)
-
-DETAILS_ESL100 = ({
-    "code": 0,
-    "msg": None,
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "name": "Etekcity Soft White Bulb",
-    "brightNess": "1",
-    "timer": None,
-    "away": None,
-    "schedule": None,
-    "ownerShip": "1",
-    "scheduleCount": 0
-}, 200)
-
-DETAILS_AIR = ({
-    "code": 0,
-    "msg": None,
-    "deviceStatus": "on",
-    "connectionStatus": "online",
-    "activeTime": 1,
-    "deviceImg": None,
-    "deviceName": "XXXXXXX",
-    "filterLife": {
-        "change": False,
-        "useHour": None,
-        "percent": 100
+DETAILS_15A = (
+    {
+        'code': 0,
+        'msg': None,
+        'deviceStatus': 'on',
+        'connectionStatus': 'online',
+        'activeTime': 1,
+        'energy': 1,
+        'nightLightStatus': 'on',
+        'nightLightBrightness': 50,
+        'nightLightAutomode': 'manual',
+        'power': '1',
+        'voltage': '1',
+    },
+    200,
+)
+
+DETAILS_7A = (
+    {
+        'deviceStatus': 'on',
+        'deviceImg': '',
+        'activeTime': 1,
+        'energy': 1,
+        'power': '1000:1000',
+        'voltage': '1000:1000',
+    },
+    200,
+)
+
+DETAILS_WS = (
+    {
+        'code': 0,
+        'msg': None,
+        'deviceStatus': 'on',
+        'connectionStatus': 'online',
+        'activeTime': 1,
+        'power': 'None',
+        'voltage': 'None',
+    },
+    200,
+)
+
+DETAILS_10A = (
+    {
+        'code': 0,
+        'msg': None,
+        'deviceStatus': 'on',
+        'connectionStatus': 'online',
+        'activeTime': 1,
+        'energy': 1,
+        'nightLightStatus': None,
+        'nightLightBrightness': None,
+        'nightLightAutomode': None,
+        'power': '1',
+        'voltage': '1',
+    },
+    200,
+)
+
+DETAILS_OUTDOOR = (
+    {
+        'code': 0,
+        'msg': None,
+        'connectionStatus': 'online',
+        'activeTime': 1,
+        'energy': 1,
+        'power': '1',
+        'voltage': '1',
+        'deviceStatus': 'on',
+        'deviceName': 'Etekcity Outdoor Plug',
+        'subDevices': [
+            {
+                'subDeviceNo': 1,
+                'defaultName': 'Socket A',
+                'subDeviceName': 'Outdoor Socket A',
+                'subDeviceStatus': 'on',
+            },
+            {
+                'subDeviceNo': 2,
+                'defaultName': 'Socket B',
+                'subDeviceName': 'Outdoor Socket B',
+                'subDeviceStatus': 'on',
+            },
+        ],
+    },
+    200,
+)
+
+DETAILS_ESL100 = (
+    {
+        'code': 0,
+        'msg': None,
+        'deviceStatus': 'on',
+        'connectionStatus': 'online',
+        'name': 'Etekcity Soft White Bulb',
+        'brightNess': '1',
+        'timer': None,
+        'away': None,
+        'schedule': None,
+        'ownerShip': '1',
+        'scheduleCount': 0,
+    },
+    200,
+)
+
+DETAILS_AIR = (
+    {
+        'code': 0,
+        'msg': None,
+        'deviceStatus': 'on',
+        'connectionStatus': 'online',
+        'activeTime': 1,
+        'deviceImg': None,
+        'deviceName': 'XXXXXXX',
+        'filterLife': {'change': False, 'useHour': None, 'percent': 100},
+        'airQuality': 'excellent',
+        'screenStatus': 'on',
+        'mode': 'manual',
+        'level': 1,
+        'schedule': None,
+        'timer': None,
+        'scheduleCount': 0,
+    },
+    200,
+)
+
+DETAILS_BADCODE = (
+    {
+        'code': 1,
+        'deviceImg': '',
+        'activeTime': 1,
+        'energy': 1,
+        'power': '1',
+        'voltage': '1',
     },
-    "airQuality": "excellent",
-    "screenStatus": "on",
-    "mode": "manual",
-    "level": 1,
-    "schedule": None,
-    "timer": None,
-    "scheduleCount": 0
-}, 200)
-
-DETAILS_BADCODE = ({
-    "code": 1,
-    "deviceImg": "",
-    "activeTime": 1,
-    "energy": 1,
-    "power": "1",
-    "voltage": "1"
-}, 200)
+    200,
+)
 
 STATUS_BODY = {
-    "accountID": SAMPLE_ACTID,
-    "token": SAMPLE_TOKEN,
-    "uuid": "UUID",
-    "timeZone": DEFAULT_TZ
+    'accountID': SAMPLE_ACTID,
+    'token': SAMPLE_TOKEN,
+    'uuid': 'UUID',
+    'timeZone': DEFAULT_TZ,
 }
 
 
diff --git a/src/tests/test_air_pur.py b/src/tests/test_air_pur.py
index 5d343b2..792c0b6 100644
--- a/src/tests/test_air_pur.py
+++ b/src/tests/test_air_pur.py
@@ -18,7 +18,7 @@
 BAD_LIST = call_json.DETAILS_BADCODE
 
 
-class TestVesyncAirPurifier(object):
+class TestVesyncAirPurifier:
     """Air purifier tests."""
 
     @pytest.fixture()
@@ -40,15 +40,15 @@ def api_mock(self, caplog):
     def test_airpur_conf(self, api_mock):
         """Tests that 15A Outlet is instantiated properly."""
         self.mock_api.return_value = CORRECT_LIST
-        fans = self.vesync_obj.get_devices()
-        fan = fans[2]
-        assert len(fan) == 1
-        fan = fan[0]
+        self.vesync_obj.get_devices()
+        fans = self.vesync_obj.fans
+        assert len(fans) == 1
+        fan = fans[0]
         assert isinstance(fan, VeSyncAir131)
-        assert fan.device_name == "Name Air Purifier"
-        assert fan.device_type == "LV-PUR131S"
-        assert fan.cid == "AIRPUR-CID"
-        assert fan.uuid == "UUID"
+        assert fan.device_name == 'Name Air Purifier'
+        assert fan.device_type == 'LV-PUR131S'
+        assert fan.cid == 'AIRPUR-CID'
+        assert fan.uuid == 'UUID'
 
     def test_airpur_details(self, api_mock):
         """Test 15A get_details()."""
@@ -57,7 +57,7 @@ def test_airpur_details(self, api_mock):
         fan.get_details()
         dev_details = fan.details
         assert fan.device_status == 'on'
-        assert type(dev_details) == dict
+        assert isinstance(dev_details, dict)
         assert dev_details['active_time'] == 1
         assert fan.filter_life == 100
         assert dev_details['screen_status'] == 'on'
@@ -77,7 +77,7 @@ def test_airpur_details_fail(self, caplog, api_mock):
 
     def test_airpur_onoff(self, caplog, api_mock):
         """Test Air Purifier Device On/Off Methods."""
-        self.mock_api.return_value = ({"code": 0}, 200)
+        self.mock_api.return_value = ({'code': 0}, 200)
         fan = VeSyncAir131(DEV_LIST_DETAIL, self.vesync_obj)
         head = helpers.req_headers(self.vesync_obj)
         body = helpers.req_body(self.vesync_obj, 'devicestatus')
@@ -86,8 +86,8 @@ def test_airpur_onoff(self, caplog, api_mock):
         body['uuid'] = fan.uuid
         on = fan.turn_on()
         self.mock_api.assert_called_with(
-            '/131airPurifier/v1/device/deviceStatus', 'put',
-            json=body, headers=head)
+            '/131airPurifier/v1/device/deviceStatus', 'put', json=body, headers=head
+        )
         call_args = self.mock_api.call_args_list[0][0]
         assert call_args[0] == '/131airPurifier/v1/device/deviceStatus'
         assert call_args[1] == 'put'
@@ -96,16 +96,16 @@ def test_airpur_onoff(self, caplog, api_mock):
         off = fan.turn_off()
         body['status'] = 'off'
         self.mock_api.assert_called_with(
-            '/131airPurifier/v1/device/deviceStatus', 'put',
-            json=body, headers=head)
+            '/131airPurifier/v1/device/deviceStatus', 'put', json=body, headers=head
+        )
         assert off
 
     def test_airpur_onoff_fail(self, api_mock):
         """Test Air Purifier On/Off Fail with Code>0."""
-        self.mock_api.return_value = ({"code": 1}, 400)
-        vswitch15a = VeSyncAir131(DEV_LIST_DETAIL, self.vesync_obj)
-        assert not vswitch15a.turn_on()
-        assert not vswitch15a.turn_off()
+        self.mock_api.return_value = ({'code': 1}, 400)
+        vsfan = VeSyncAir131(DEV_LIST_DETAIL, self.vesync_obj)
+        assert not vsfan.turn_on()
+        assert not vsfan.turn_off()
 
     def test_airpur_fanspeed(self, caplog, api_mock):
         """Test changing fan speed of."""
diff --git a/src/tests/test_vesync.py b/src/tests/test_vesync.py
index 6d6008b..83ccee2 100644
--- a/src/tests/test_vesync.py
+++ b/src/tests/test_vesync.py
@@ -11,6 +11,7 @@
 
 def mocked_req_post(*args, **kwargs):
     """Test call_api post request."""
+
     class MockResponse:
         """Mock response class."""
 
@@ -24,10 +25,11 @@ def __init__(self, json_data, status_code):
             '{"traceId": "", "msg": "", "result": {"accountID": "12346536, \
                 "avatarIcon": "", "acceptLanguage": "", "gdprStatus": true, \
                     "nickName": "mynickname", "userType": "1", \
-                        "token": "somevaluehere"}, "code": 0 }', 200)
-    elif args[
-            0] == 'https://smartapi.vesync.com/cloud/v1/deviceManaged/devices':
-        return MockResponse({"key2": "value2"}, 200)
+                        "token": "somevaluehere"}, "code": 0 }',
+            200,
+        )
+    elif args[0] == 'https://smartapi.vesync.com/cloud/v1/deviceManaged/devices':
+        return MockResponse({'key2': 'value2'}, 200)
 
     return MockResponse(None, 404)
 
@@ -75,10 +77,14 @@ def test_password(self):
 
     def test_hash_password(self):
         """Test password hash method."""
-        self.assertEqual(Helpers.hash_password(self.vesync_1.password),
-                         '5f4dcc3b5aa765d61d8327deb882cf99')
-        self.assertEqual(Helpers.hash_password(self.vesync_5.password),
-                         'd41d8cd98f00b204e9800998ecf8427e')
+        self.assertEqual(
+            Helpers.hash_password(self.vesync_1.password),
+            '5f4dcc3b5aa765d61d8327deb882cf99',
+        )
+        self.assertEqual(
+            Helpers.hash_password(self.vesync_5.password),
+            'd41d8cd98f00b204e9800998ecf8427e',
+        )
         with self.assertRaises(AttributeError):
             Helpers.hash_password(self.vesync_6.password)
 
@@ -101,17 +107,21 @@ def test_login(self):
         self.assertFalse(mock_vesync.login())
 
         with patch('pyvesync.helpers.Helpers.call_api') as mocked_post:
-            d = {"result": {"accountID": "12346536", "userType": "1",
-                 "token": "somevaluehere"}, "code": 0}
+            d = {
+                'result': {
+                    'accountID': '12346536',
+                    'userType': '1',
+                    'token': 'somevaluehere',
+                },
+                'code': 0,
+            }
             mocked_post.return_value = (d, 200)
 
             data = self.vesync_1.login()
             body = Helpers.req_body(self.vesync_1, 'login')
             body['email'] = self.vesync_1.username
             body['password'] = Helpers.hash_password(self.vesync_1.password)
-            mocked_post.assert_called_with('/cloud/v1/user/login',
-                                           'post',
-                                           json=body)
+            mocked_post.assert_called_with('/cloud/v1/user/login', 'post', json=body)
             self.assertTrue(data)
 
 
@@ -162,8 +172,7 @@ def test_api_bad_response(self, api_mock):
     def test_api_exception(self, api_mock, caplog):
         """Test call_api method exception handling."""
         caplog.set_level(logging.DEBUG)
-        api_mock.return_value.raiseError.side_effect = Mock(
-            side_effect=Exception)
+        api_mock.return_value.raiseError.side_effect = Mock(side_effect=Exception)
 
         Helpers.call_api('/call/location', method='get')
 
diff --git a/src/tests/test_vesync_10a.py b/src/tests/test_vesync_10a.py
index 568746d..b85d2c5 100644
--- a/src/tests/test_vesync_10a.py
+++ b/src/tests/test_vesync_10a.py
@@ -38,21 +38,22 @@ def api_mock(self, caplog):
         yield
         self.mock_api_call.stop()
 
-    @pytest.mark.parametrize('mock_return, devtype',
-                             [(CORRECT_10AEU_LIST, 'ESW01-EU'),
-                              (CORRECT_10AUS_LIST, 'ESW03-USA')])
+    @pytest.mark.parametrize(
+        'mock_return, devtype',
+        [(CORRECT_10AEU_LIST, 'ESW01-EU'), (CORRECT_10AUS_LIST, 'ESW03-USA')],
+    )
     def test_10a_conf(self, mock_return, devtype, api_mock):
         """Tests that 10A US & EU Outlet is instantiated properly."""
         self.mock_api.return_value = mock_return
-        outlets = self.vesync_obj.get_devices()
-        outlets = outlets[0]
+        self.vesync_obj.get_devices()
+        outlets = self.vesync_obj.outlets
         assert len(outlets) == 1
         outlet = outlets[0]
         assert isinstance(outlet, VeSyncOutlet10A)
-        assert outlet.device_name == "Name 10A Outlet"
+        assert outlet.device_name == 'Name 10A Outlet'
         assert outlet.device_type == devtype
-        assert outlet.cid == "10A-CID"
-        assert outlet.uuid == "UUID"
+        assert outlet.cid == '10A-CID'
+        assert outlet.uuid == 'UUID'
 
     def test_10a_details(self, api_mock):
         """Test 10A get_details()."""
@@ -79,7 +80,7 @@ def test_10a_details_fail(self, caplog, api_mock):
 
     def test_10a_onoff(self, caplog, api_mock):
         """Test 10A Device On/Off Methods."""
-        self.mock_api.return_value = ({"code": 0}, 200)
+        self.mock_api.return_value = ({'code': 0}, 200)
         out = VeSyncOutlet10A(DEV_LIST_DETAIL_EU, self.vesync_obj)
         head = helpers.req_headers(self.vesync_obj)
         body = helpers.req_body(self.vesync_obj, 'devicestatus')
@@ -87,22 +88,20 @@ def test_10a_onoff(self, caplog, api_mock):
         body['status'] = 'on'
         body['uuid'] = out.uuid
         on = out.turn_on()
-        self.mock_api.assert_called_with('/10a/v1/device/devicestatus',
-                                         'put',
-                                         headers=head,
-                                         json=body)
+        self.mock_api.assert_called_with(
+            '/10a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert on
         off = out.turn_off()
         body['status'] = 'off'
-        self.mock_api.assert_called_with('/10a/v1/device/devicestatus',
-                                         'put',
-                                         headers=head,
-                                         json=body)
+        self.mock_api.assert_called_with(
+            '/10a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert off
 
     def test_10a_onoff_fail(self, api_mock):
         """Test 10A On/Off Fail with Code>0."""
-        self.mock_api.return_value = ({"code": 1}, 400)
+        self.mock_api.return_value = ({'code': 1}, 400)
         out = VeSyncOutlet10A(DEV_LIST_DETAIL_US, self.vesync_obj)
         assert not out.turn_on()
         assert not out.turn_off()
@@ -114,11 +113,12 @@ def test_10a_weekly(self, api_mock):
         out.get_weekly_energy()
         body = helpers.req_body(self.vesync_obj, 'energy_week')
         body['uuid'] = out.uuid
-        self.mock_api.assert_called_with('/10a/v1/device/energyweek',
-                                         'post',
-                                         headers=helpers.req_headers(
-                                             self.vesync_obj),
-                                         json=body)
+        self.mock_api.assert_called_with(
+            '/10a/v1/device/energyweek',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = out.energy['week']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -134,11 +134,12 @@ def test_10a_monthly(self, api_mock):
         out.get_monthly_energy()
         body = helpers.req_body(self.vesync_obj, 'energy_month')
         body['uuid'] = out.uuid
-        self.mock_api.assert_called_with('/10a/v1/device/energymonth',
-                                         'post',
-                                         headers=helpers.req_headers(
-                                             self.vesync_obj),
-                                         json=body)
+        self.mock_api.assert_called_with(
+            '/10a/v1/device/energymonth',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = out.energy['month']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -154,11 +155,12 @@ def test_10a_yearly(self, api_mock):
         out.get_yearly_energy()
         body = helpers.req_body(self.vesync_obj, 'energy_year')
         body['uuid'] = out.uuid
-        self.mock_api.assert_called_with('/10a/v1/device/energyyear',
-                                         'post',
-                                         headers=helpers.req_headers(
-                                             self.vesync_obj),
-                                         json=body)
+        self.mock_api.assert_called_with(
+            '/10a/v1/device/energyyear',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = out.energy['year']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -169,7 +171,7 @@ def test_10a_yearly(self, api_mock):
 
     def test_history_fail(self, caplog, api_mock):
         """Test 15A energy failure."""
-        bad_history = {"code": 1}
+        bad_history = {'code': 1}
         self.mock_api.return_value = (bad_history, 200)
         out = VeSyncOutlet10A(DEV_LIST_DETAIL_US, self.vesync_obj)
         out.update_energy()
diff --git a/src/tests/test_vesync_15a.py b/src/tests/test_vesync_15a.py
index 80a4599..37334e8 100644
--- a/src/tests/test_vesync_15a.py
+++ b/src/tests/test_vesync_15a.py
@@ -35,15 +35,15 @@ def api_mock(self, caplog):
     def test_15aswitch_conf(self, api_mock):
         """Tests that 15A Outlet is instantiated properly"""
         self.mock_api.return_value = CORRECT_15A_LIST
-        outlets = self.vesync_obj.get_devices()
-        outlets = outlets[0]
+        self.vesync_obj.get_devices()
+        outlets = self.vesync_obj.outlets
         assert len(outlets) == 1
         vswitch15a = outlets[0]
         assert isinstance(vswitch15a, VeSyncOutlet15A)
-        assert vswitch15a.device_name == "Name 15A Outlet"
-        assert vswitch15a.device_type == "ESW15-USA"
-        assert vswitch15a.cid == "15A-CID"
-        assert vswitch15a.uuid == "UUID"
+        assert vswitch15a.device_name == 'Name 15A Outlet'
+        assert vswitch15a.device_type == 'ESW15-USA'
+        assert vswitch15a.cid == '15A-CID'
+        assert vswitch15a.uuid == 'UUID'
 
     def test_15a_details(self, api_mock):
         """Test 15A get_details() """
@@ -72,10 +72,7 @@ def test_15a_details_fail(self, caplog, api_mock):
 
     def test_15a_no_details(self, caplog, api_mock):
         """Test 15A details return with no details and code=0"""
-        bad_15a_details = {
-            "code": 0,
-            "deviceStatus": "on"
-        }
+        bad_15a_details = {'code': 0, 'deviceStatus': 'on'}
         self.mock_api.return_value = (bad_15a_details, 200)
         vswitch15a = VeSyncOutlet15A(DEV_LIST_DETAIL, self.vesync_obj)
         vswitch15a.get_details()
@@ -83,7 +80,7 @@ def test_15a_no_details(self, caplog, api_mock):
 
     def test_15a_onoff(self, caplog, api_mock):
         """Test 15A Device On/Off Methods"""
-        self.mock_api.return_value = ({"code": 0}, 200)
+        self.mock_api.return_value = ({'code': 0}, 200)
         vswitch15a = VeSyncOutlet15A(DEV_LIST_DETAIL, self.vesync_obj)
         head = helpers.req_headers(self.vesync_obj)
         body = helpers.req_body(self.vesync_obj, 'devicestatus')
@@ -92,17 +89,19 @@ def test_15a_onoff(self, caplog, api_mock):
         body['uuid'] = vswitch15a.uuid
         on = vswitch15a.turn_on()
         self.mock_api.assert_called_with(
-            '/15a/v1/device/devicestatus', 'put', headers=head, json=body)
+            '/15a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert on
         off = vswitch15a.turn_off()
         body['status'] = 'off'
         self.mock_api.assert_called_with(
-            '/15a/v1/device/devicestatus', 'put', headers=head, json=body)
+            '/15a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert off
 
     def test_15a_onoff_fail(self, api_mock):
         """Test 15A On/Off Fail with Code>0"""
-        self.mock_api.return_value = ({"code": 1}, 400)
+        self.mock_api.return_value = ({'code': 1}, 400)
         vswitch15a = VeSyncOutlet15A(DEV_LIST_DETAIL, self.vesync_obj)
         assert not vswitch15a.turn_on()
         assert not vswitch15a.turn_off()
@@ -115,8 +114,11 @@ def test_15a_weekly(self, api_mock):
         body = helpers.req_body(self.vesync_obj, 'energy_week')
         body['uuid'] = vswitch15a.uuid
         self.mock_api.assert_called_with(
-            '/15a/v1/device/energyweek', 'post',
-            headers=helpers.req_headers(self.vesync_obj), json=body)
+            '/15a/v1/device/energyweek',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = vswitch15a.energy['week']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -133,8 +135,11 @@ def test_15a_monthly(self, api_mock):
         body = helpers.req_body(self.vesync_obj, 'energy_month')
         body['uuid'] = vswitch15a.uuid
         self.mock_api.assert_called_with(
-            '/15a/v1/device/energymonth', 'post',
-            headers=helpers.req_headers(self.vesync_obj), json=body)
+            '/15a/v1/device/energymonth',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = vswitch15a.energy['month']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -151,8 +156,11 @@ def test_15a_yearly(self, api_mock):
         body = helpers.req_body(self.vesync_obj, 'energy_year')
         body['uuid'] = vswitch15a.uuid
         self.mock_api.assert_called_with(
-            '/15a/v1/device/energyyear', 'post',
-            headers=helpers.req_headers(self.vesync_obj), json=body)
+            '/15a/v1/device/energyyear',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = vswitch15a.energy['year']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -163,7 +171,7 @@ def test_15a_yearly(self, api_mock):
 
     def test_history_fail(self, caplog, api_mock):
         """Test 15A energy failure"""
-        bad_history = {"code": 1}
+        bad_history = {'code': 1}
         self.mock_api.return_value = (bad_history, 200)
         vswitch15a = VeSyncOutlet15A(DEV_LIST_DETAIL, self.vesync_obj)
         vswitch15a.update_energy()
diff --git a/src/tests/test_vesync_7aswitch.py b/src/tests/test_vesync_7aswitch.py
index 9a46669..f89c441 100644
--- a/src/tests/test_vesync_7aswitch.py
+++ b/src/tests/test_vesync_7aswitch.py
@@ -25,8 +25,7 @@ class TestVesync7ASwitch(object):
     @pytest.fixture()
     def api_mock(self, caplog):
         """Mock call_api() and initialize VeSync object."""
-        self.mock_api_call = patch.object(pyvesync.helpers.Helpers,
-                                          'call_api')
+        self.mock_api_call = patch.object(pyvesync.helpers.Helpers, 'call_api')
         self.mock_api = self.mock_api_call.start()
         self.mock_api.create_autospect()
         self.mock_api.return_value.ok = True
@@ -42,13 +41,14 @@ def api_mock(self, caplog):
     def test_7aswitch_conf(self, api_mock):
         """Test inizialization of 7A outlet."""
         self.mock_api.return_value = CORRECT_7A_LIST
-        devs = self.vesync_obj.get_devices()
-        assert len(devs) == 4
-        vswitch7a = devs[0][0]
+        self.vesync_obj.get_devices()
+        outlets = self.vesync_obj.outlets
+        assert len(outlets) == 1
+        vswitch7a = outlets[0]
         assert isinstance(vswitch7a, VeSyncOutlet7A)
-        assert vswitch7a.device_name == "Name 7A Outlet"
-        assert vswitch7a.device_type == "wifi-switch-1.3"
-        assert vswitch7a.cid == "7A-CID"
+        assert vswitch7a.device_name == 'Name 7A Outlet'
+        assert vswitch7a.device_type == 'wifi-switch-1.3'
+        assert vswitch7a.cid == '7A-CID'
         assert vswitch7a.is_on
 
     def test_7a_details(self, api_mock):
@@ -67,11 +67,11 @@ def test_7a_details(self, api_mock):
     def test_7a_no_devstatus(self, caplog, api_mock):
         """Test 7A outlet details response with no device status key."""
         bad_7a_details = {
-            "deviceImg": "",
-            "activeTime": 1,
-            "energy": 1,
-            "power": "1A:1A",
-            "voltage": "1A:1A"
+            'deviceImg': '',
+            'activeTime': 1,
+            'energy': 1,
+            'power': '1A:1A',
+            'voltage': '1A:1A',
         }
         self.mock_api.return_value = (bad_7a_details, 200)
         vswitch7a = VeSyncOutlet7A(DEV_LIST_DETAIL, self.vesync_obj)
@@ -81,9 +81,7 @@ def test_7a_no_devstatus(self, caplog, api_mock):
 
     def test_7a_no_details(self, caplog, api_mock):
         """Test 7A outlet details response with unknown keys."""
-        bad_7a_details = {
-            "wrongdetails": "on"
-        }
+        bad_7a_details = {'wrongdetails': 'on'}
         self.mock_api.return_value = (bad_7a_details, 200)
         vswitch7a = VeSyncOutlet7A(DEV_LIST_DETAIL, self.vesync_obj)
         vswitch7a.get_details()
@@ -91,18 +89,18 @@ def test_7a_no_details(self, caplog, api_mock):
 
     def test_7a_onoff(self, caplog, api_mock):
         """Test 7A outlet on/off methods."""
-        self.mock_api.return_value = ("response", 200)
+        self.mock_api.return_value = ('response', 200)
         vswitch7a = VeSyncOutlet7A(DEV_LIST_DETAIL, self.vesync_obj)
         on = vswitch7a.turn_on()
         head = helpers.req_headers(self.vesync_obj)
         self.mock_api.assert_called_with(
-            '/v1/wifi-switch-1.3/' + vswitch7a.cid + '/status/on', 'put',
-            headers=head)
+            '/v1/wifi-switch-1.3/' + vswitch7a.cid + '/status/on', 'put', headers=head
+        )
         assert on
         off = vswitch7a.turn_off()
         self.mock_api.assert_called_with(
-                '/v1/wifi-switch-1.3/' + vswitch7a.cid + '/status/off', 'put',
-                headers=head)
+            '/v1/wifi-switch-1.3/' + vswitch7a.cid + '/status/off', 'put', headers=head
+        )
         assert off
 
     def test_7a_onoff_fail(self, api_mock):
@@ -120,7 +118,8 @@ def test_7a_weekly(self, api_mock):
         self.mock_api.assert_called_with(
             '/v1/device/' + vswitch7a.cid + '/energy/week',
             'get',
-            headers=helpers.req_headers(self.vesync_obj))
+            headers=helpers.req_headers(self.vesync_obj),
+        )
         energy_dict = vswitch7a.energy['week']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -136,7 +135,8 @@ def test_7a_monthly(self, api_mock):
         self.mock_api.assert_called_with(
             '/v1/device/' + vswitch7a.cid + '/energy/month',
             'get',
-            headers=helpers.req_headers(self.vesync_obj))
+            headers=helpers.req_headers(self.vesync_obj),
+        )
         energy_dict = vswitch7a.energy['month']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -152,7 +152,8 @@ def test_7a_yearly(self, api_mock):
         self.mock_api.assert_called_with(
             '/v1/device/' + vswitch7a.cid + '/energy/year',
             'get',
-            headers=helpers.req_headers(self.vesync_obj))
+            headers=helpers.req_headers(self.vesync_obj),
+        )
         energy_dict = vswitch7a.energy['year']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -162,7 +163,7 @@ def test_7a_yearly(self, api_mock):
 
     def test_history_fail(self, caplog, api_mock):
         """Test handling of energy update failure."""
-        bad_history = {"code": 1}
+        bad_history = {'code': 1}
         self.mock_api.return_value = (bad_history, 200)
         vswitch7a = VeSyncOutlet7A(DEV_LIST_DETAIL, self.vesync_obj)
         vswitch7a.update_energy()
diff --git a/src/tests/test_vesync_bulbs.py b/src/tests/test_vesync_bulbs.py
index a924114..0eed139 100644
--- a/src/tests/test_vesync_bulbs.py
+++ b/src/tests/test_vesync_bulbs.py
@@ -35,15 +35,15 @@ def api_mock(self, caplog):
     def test_esl100_conf(self, api_mock):
         """Tests that Wall Switch is instantiated properly."""
         self.mock_api.return_value = DEV_LIST
-        devices = self.vesync_obj.get_devices()
-        bulbs = devices[3]
+        self.vesync_obj.get_devices()
+        bulbs = self.vesync_obj.bulbs
         assert len(bulbs) == 1
         bulb = bulbs[0]
         assert isinstance(bulb, VeSyncBulbESL100)
-        assert bulb.device_name == "Etekcity Soft White Bulb"
-        assert bulb.device_type == "ESL100"
-        assert bulb.cid == "ESL100-CID"
-        assert bulb.uuid == "UUID"
+        assert bulb.device_name == 'Etekcity Soft White Bulb'
+        assert bulb.device_type == 'ESL100'
+        assert bulb.cid == 'ESL100-CID'
+        assert bulb.uuid == 'UUID'
 
     def test_esl100_details(self, api_mock):
         """Test WS get_details()."""
diff --git a/src/tests/test_vesync_devices.py b/src/tests/test_vesync_devices.py
index e495177..075e39a 100644
--- a/src/tests/test_vesync_devices.py
+++ b/src/tests/test_vesync_devices.py
@@ -7,21 +7,26 @@
 import time
 import copy
 from itertools import chain
-from pyvesync import (VeSyncAir131, VeSyncBulbESL100, VeSyncOutdoorPlug,
-                      VeSyncOutlet10A, VeSyncOutlet15A, VeSyncOutlet7A,
-                      VeSyncWallSwitch)
+from pyvesync import (
+    VeSyncAir131,
+    VeSyncBulbESL100,
+    VeSyncOutdoorPlug,
+    VeSyncOutlet10A,
+    VeSyncOutlet15A,
+    VeSyncOutlet7A,
+    VeSyncWallSwitch,
+    VeSyncDimmerSwitch
+)
 from . import call_json as json_vals
 
 BAD_DEV_LIST = {
-    "result": {
-        "total": 5,
-        "pageNo": 1,
-        "pageSize": 50,
-        "list": [{
-            "NoConfigKeys": None
-        }]
+    'result': {
+        'total': 5,
+        'pageNo': 1,
+        'pageSize': 50,
+        'list': [{'NoConfigKeys': None}],
     },
-    "code": 0
+    'code': 0,
 }
 
 
@@ -48,7 +53,7 @@ def test_device_api(self, caplog, api_mock):
         """Tests to ensure call_api is being called correctly."""
         head = json_vals.DEFAULT_HEADER
         self.mock_api.return_value = ({'V': 2}, 200)
-        out, sw, fan, bulb = self.vesync_obj.get_devices()
+        self.vesync_obj.get_devices()
         call_list = self.mock_api.call_args_list
         call_p1 = call_list[0][0]
         call_p2 = call_list[0][1]
@@ -61,9 +66,11 @@ def test_device_api(self, caplog, api_mock):
     @patch('pyvesync.vesync.VeSyncOutlet15A')
     @patch('pyvesync.vesync.VeSyncOutlet10A')
     @patch('pyvesync.vesync.VeSyncWallSwitch')
+    @patch('pyvesync.vesync.VeSyncDimmerSwitch')
     @patch('pyvesync.vesync.VeSyncAir131')
-    def test_getdevs_vsfact(self, air_patch, ws_patch, out10a_patch,
-                            out15a_patch, out7a_patch, api_mock):
+    def test_getdevs_vsfact(
+        self, air_patch, wsdim_patch, ws_patch, out10a_patch, out15a_patch, out7a_patch, api_mock
+    ):
         """Test the get_devices, process_devices and VSFactory methods.
         Build list with device objects from details
         Test for all 6 known devices - 4 outlets, 2 switches, 1 fan.
@@ -73,56 +80,48 @@ def test_getdevs_vsfact(self, air_patch, ws_patch, out10a_patch,
 
         self.mock_api.return_value = device_list
 
-        proc_dev = self.vesync_obj.get_devices()
+        self.vesync_obj.get_devices()
 
-        assert out7a_patch.called
-        assert out10a_patch.called
-        assert out15a_patch.called
-        assert ws_patch.called
-        assert air_patch.called
-
-        assert len(proc_dev) == 4
-        outlets = proc_dev[0]
-        switches = proc_dev[1]
-        fans = proc_dev[2]
-        bulbs = proc_dev[3]
-        assert len(outlets) == 6
-        assert len(switches) == 1
-        assert len(fans) == 1
-        assert len(bulbs) == 1
+        assert len(self.vesync_obj.outlets) == 6
+        assert len(self.vesync_obj.switches) == 2
+        assert len(self.vesync_obj.fans) == 1
+        assert len(self.vesync_obj.bulbs) == 1
 
     def test_getdevs_code(self, caplog, api_mock):
         """Test get_devices with code > 0 returned."""
         device_list = ({'code': 1, 'msg': 'gibberish'}, 200)
 
         self.mock_api.return_value = device_list
-        dev_list = self.vesync_obj.get_devices()
+        self.vesync_obj.get_devices()
 
-        assert len(dev_list) == 4
         assert 'Error retrieving device list' in caplog.text
 
     def test_get_devices_resp_changes(self, caplog, api_mock):
         """Test if structure of device list response has changed."""
-        device_list = ({'code': 0,
-                        'NOTresult': {
-                            'NOTlist': [{
-                                'deviceType': 'wifi-switch-1.3',
-                                'type': 'wifi-switch',
-                                'cid': 'cid1'
-                            }]
+        device_list = (
+            {
+                'code': 0,
+                'NOTresult': {
+                    'NOTlist': [
+                        {
+                            'deviceType': 'wifi-switch-1.3',
+                            'type': 'wifi-switch',
+                            'cid': 'cid1',
                         }
-                        }, 200)
+                    ]
+                },
+            },
+            200,
+        )
         self.mock_api.return_value = device_list
-        devs = self.vesync_obj.get_devices()
-        assert len(devs) == 4
+        self.vesync_obj.get_devices()
         assert len(caplog.records) == 1
         assert 'Device list in response not found' in caplog.text
 
     def test_7a_bad_conf(self, caplog, api_mock):
         """Test bad device list response."""
         self.mock_api.return_value = (BAD_DEV_LIST, 200)
-        devs = self.vesync_obj.get_devices()
-        assert len(devs) == 4
+        self.vesync_obj.get_devices()
         assert len(caplog.records) == 2
 
     def test_7a_no_dev_list(self, caplog, api_mock):
@@ -133,30 +132,24 @@ def test_7a_no_dev_list(self, caplog, api_mock):
 
         assert len(caplog.records) == 1
 
-    def test_get_devices_deviceType_error(self, caplog, api_mock):
+    def test_get_devices_devicetype_error(self, caplog, api_mock):
         """Test result and list keys exist but deviceType not in list."""
-        device_list = ({'code': 0,
-                        'result': {
-                            'list': [{
-                                'type': 'wifi-switch',
-                                'cid': 'cid1'
-                            }]
-                        }
-                        }, 200)
+        device_list = (
+            {'code': 0, 'result': {'list': [{'type': 'wifi-switch', 'cid': 'cid1'}]}},
+            200,
+        )
         self.mock_api.return_value = device_list
-        devs = self.vesync_obj.get_devices()
-        assert len(devs) == 4
+        self.vesync_obj.get_devices()
         assert len(caplog.records) == 2
-        assert 'Details keys not found' in caplog.text
+        assert 'Error adding device' in caplog.text
 
     def test_unknown_device(self, caplog, api_mock):
         """Test unknown device type is handled correctly."""
         unknown_dev = json_vals.LIST_CONF_7A
 
-        unknown_dev['devType'] = "UNKNOWN-DEVTYPE"
+        unknown_dev['devType'] = 'UNKNOWN-DEVTYPE'
 
-        pyvesync.vesync.get_device(
-            'unkown_device', unknown_dev, self.vesync_obj)
+        pyvesync.vesync._device_builder('unknown_device', unknown_dev, self.vesync_obj)
 
         assert len(caplog.records) == 1
         assert 'Unknown' in caplog.text
@@ -169,115 +162,11 @@ def test_time_check(self, api_mock):
         time_check = self.vesync_obj.device_time_check()
         assert time_check is False
         self.vesync_obj.last_update_ts = (
-            time.time() - self.vesync_obj.update_interval - 1)
+            time.time() - self.vesync_obj.update_interval - 1
+        )
         time_check = self.vesync_obj.device_time_check()
         assert time_check is True
 
-    @patch('pyvesync.vesync.VeSyncOutlet7A', autospec=True)
-    @patch('pyvesync.vesync.VeSyncOutlet15A', autospec=True)
-    @patch('pyvesync.vesync.VeSyncOutlet10A', autospec=True)
-    @patch('pyvesync.vesync.VeSyncOutdoorPlug', autospec=True)
-    @patch('pyvesync.vesync.VeSyncBulbESL100', autospec=True)
-    @patch('pyvesync.vesync.VeSyncWallSwitch', autospec=True)
-    @patch('pyvesync.vesync.VeSyncAir131', autospec=True)
-    def test_resolve_updates(self, air_patch, ws_patch, esl100_patch,
-                             outdoor_patch, out10a_patch, out15a_patch,
-                             out7a_patch, caplog, api_mock):
-        """Test process_devices() with all devices.
-        Creates vesync object with all devices and returns
-        device list with new set of all devices.
-        """
-        outlet_10a = out10a_patch.return_value
-        outlet_10a.cid = '10A-CID1'
-        outlet_10a.device_type = 'ESW10-EU'
-        outlet_10a.device_name = '10A Removed'
-
-        outlet_15a = out15a_patch.return_value
-        outlet_15a.cid = '15A-CID1'
-        outlet_15a.device_type = 'ESW15-USA'
-        outlet_15a.device_name = '15A Removed'
-
-        outlet_7a = out7a_patch.return_value
-        outlet_7a.cid = '7A-CID1'
-        outlet_7a.device_type = 'wifi-switch-1.3'
-        outlet_7a.device_name = '7A Removed'
-
-        outlet_outdoor = outdoor_patch.return_value
-        outlet_outdoor.cid = 'OUTDOOR-CID1'
-        outlet_outdoor.device_type = 'ESO15-TB'
-        outlet_outdoor.device_name = 'Outdoor Removed'
-
-        bulb_esl100 = esl100_patch.return_value
-        bulb_esl100.cid = 'BULB-CID1'
-        bulb_esl100.device_type = 'ESL100'
-        bulb_esl100.device_name = 'Bulb Removed'
-
-        switch = ws_patch.return_value
-        switch.cid = 'WS-CID2'
-        switch.device_name = 'Switch Removed'
-        switch.device_type = 'ESWL01'
-
-        air = air_patch.return_value
-        air.cid = 'AirCID2'
-        air.device_type = 'LV-PUR131S'
-        air.device_name = 'fan Removed'
-
-        out10a_attr = MagicMock()
-        out10a_attr.device_type = 'ESW01-EU'
-        out10a_attr.device_name = '10A Added'
-        out10a_patch.return_value = out10a_attr
-
-        out15a_attr = MagicMock()
-        out15a_attr.device_type = 'ESW15A-USA'
-        out15a_attr.device_name = '15A Added'
-        out15a_patch.return_value = out15a_attr
-
-        out7a_attr = MagicMock()
-        out7a_attr.device_type = 'wifi-switch-1.3'
-        out7a_attr.device_name = '7A Added'
-        out7a_patch.return_value = out7a_attr
-
-        air_attr = MagicMock()
-        air_attr.device_type = 'LV-PUR131S'
-        air_attr.device_name = 'Air Added'
-        air_patch.return_value = air_attr
-
-        ws_attr = MagicMock()
-        ws_attr.device_name = 'Switch Added'
-        ws_attr.device_type = 'ESWL01'
-        ws_patch.return_value = ws_attr
-
-        outdoor_attr = MagicMock()
-        outdoor_attr.device_name = 'Outdoor added'
-        outdoor_attr.device_type = 'ESO15-TB'
-        outdoor_patch.return_value = outdoor_attr
-
-        esl100_attr = MagicMock()
-        esl100_attr.device_name = 'ESL100 Bulb Added'
-        esl100_attr.device_type = 'ESL100'
-        esl100_patch.return_value = esl100_attr
-
-        json_ret = json_vals.FULL_DEV_LIST
-
-        self.vesync_obj.outlets = [outlet_10a, outlet_15a,
-                                   outlet_7a, outlet_outdoor]
-        self.vesync_obj.switches = [switch]
-        self.vesync_obj.fans = [air]
-        self.vesync_obj.bulbs = [bulb_esl100]
-
-        outlets, switches, fans, bulbs = self.vesync_obj.process_devices(
-            json_ret)
-
-        assert len(self.vesync_obj.outlets) == 0
-        assert len(self.vesync_obj.switches) == 0
-        assert len(self.vesync_obj.fans) == 0
-        assert len(self.vesync_obj.bulbs) == 0
-
-        assert len(outlets) == 6
-        assert len(switches) == 1
-        assert len(fans) == 1
-        assert len(bulbs) == 1
-
     @patch('pyvesync.vesync.VeSyncOutlet7A', autospec=True)
     def test_remove_device(self, outlet_patch, caplog, api_mock):
         """Test remove device test."""
@@ -289,16 +178,16 @@ def test_remove_device(self, outlet_patch, caplog, api_mock):
         outlet_test.device_name = '7A Device'
 
         new_list = [device]
-
-        device_exists = pyvesync.vesync.VeSync.remove_dev_test(
-            outlet_test, new_list)
+        self.vesync_obj.outlets = [outlet_test]
+        device_exists = pyvesync.vesync.VeSync.remove_old_devices(
+            self.vesync_obj, new_list
+        )
 
         assert device_exists
 
         del device['cid']
 
-        device_exists = pyvesync.vesync.VeSync.remove_dev_test(
-            outlet_test, new_list)
+        device_exists = pyvesync.vesync.VeSync.remove_dev_test(outlet_test, new_list)
 
         assert device_exists is False
 
@@ -309,8 +198,9 @@ def test_remove_device(self, outlet_patch, caplog, api_mock):
     def test_add_dev_test(self, outdoor_patch, caplog, api_mock):
         """Test add_device_test to return if device found in existing conf."""
         outdoor_inst = pyvesync.VeSyncOutdoorPlug(
-            json_vals.LIST_CONF_OUTDOOR_2, self.vesync_obj)
-        self.vesync_obj.oulets = [outdoor_inst]
+            json_vals.LIST_CONF_OUTDOOR_2, self.vesync_obj
+        )
+        self.vesync_obj.outlets = [outdoor_inst]
 
         add_test = self.vesync_obj.add_dev_test(json_vals.LIST_CONF_OUTDOOR_1)
 
@@ -319,24 +209,102 @@ def test_add_dev_test(self, outdoor_patch, caplog, api_mock):
     def test_display_func(self, caplog, api_mock):
         """Test display function outputs text."""
         self.vesync_obj.outlets.append(
-            VeSyncOutdoorPlug(json_vals.LIST_CONF_OUTDOOR_1, self.vesync_obj))
+            VeSyncOutdoorPlug(json_vals.LIST_CONF_OUTDOOR_1, self.vesync_obj)
+        )
         self.vesync_obj.outlets.append(
-            VeSyncOutlet10A(json_vals.LIST_CONF_10AUS, self.vesync_obj))
+            VeSyncOutlet10A(json_vals.LIST_CONF_10AUS, self.vesync_obj)
+        )
         self.vesync_obj.outlets.append(
-            VeSyncOutlet15A(json_vals.LIST_CONF_15A, self.vesync_obj))
+            VeSyncOutlet15A(json_vals.LIST_CONF_15A, self.vesync_obj)
+        )
         self.vesync_obj.outlets.append(
-            VeSyncOutlet7A(json_vals.LIST_CONF_7A, self.vesync_obj))
+            VeSyncOutlet7A(json_vals.LIST_CONF_7A, self.vesync_obj)
+        )
         self.vesync_obj.switches.append(
-            VeSyncWallSwitch(json_vals.LIST_CONF_WS, self.vesync_obj))
+            VeSyncWallSwitch(json_vals.LIST_CONF_WS, self.vesync_obj)
+        )
         self.vesync_obj.fans.append(
-            VeSyncAir131(json_vals.LIST_CONF_AIR, self.vesync_obj))
+            VeSyncAir131(json_vals.LIST_CONF_AIR, self.vesync_obj)
+        )
         self.vesync_obj.bulbs.append(
-            VeSyncBulbESL100(json_vals.LIST_CONF_ESL100, self.vesync_obj))
+            VeSyncBulbESL100(json_vals.LIST_CONF_ESL100, self.vesync_obj)
+        )
 
-        dev_list = [self.vesync_obj.outlets, self.vesync_obj.switches,
-                    self.vesync_obj.fans, self.vesync_obj.bulbs]
+        dev_list = [
+            self.vesync_obj.outlets,
+            self.vesync_obj.switches,
+            self.vesync_obj.fans,
+            self.vesync_obj.bulbs,
+        ]
 
         for device in chain(*dev_list):
             device.display()
 
         assert len(caplog.records) == 0
+
+    @patch('pyvesync.vesync.VeSyncOutlet7A', autospec=True)
+    @patch('pyvesync.vesync.VeSyncOutlet15A', autospec=True)
+    @patch('pyvesync.vesync.VeSyncOutlet10A', autospec=True)
+    @patch('pyvesync.vesync.VeSyncOutdoorPlug', autospec=True)
+    @patch('pyvesync.vesync.VeSyncBulbESL100', autospec=True)
+    @patch('pyvesync.vesync.VeSyncWallSwitch', autospec=True)
+    @patch('pyvesync.vesync.VeSyncAir131', autospec=True)
+    def test_resolve_updates(
+        self,
+        air_patch,
+        ws_patch,
+        esl100_patch,
+        outdoor_patch,
+        out10a_patch,
+        out15a_patch,
+        out7a_patch,
+        caplog,
+        api_mock,
+    ):
+        """Test process_devices() with all devices.
+        Creates vesync object with all devices and returns
+        device list with new set of all devices.
+        """
+        out10a_patch.cid = '10A-CID1'
+        out10a_patch.device_type = 'ESW10-EU'
+        out10a_patch.device_name = '10A Removed'
+
+        out15a_patch.cid = '15A-CID1'
+        out15a_patch.device_type = 'ESW15-USA'
+        out15a_patch.device_name = '15A Removed'
+
+        out7a_patch.cid = '7A-CID1'
+        out7a_patch.device_type = 'wifi-switch-1.3'
+        out7a_patch.device_name = '7A Removed'
+
+        outdoor_patch.cid = 'OUTDOOR-CID1'
+        outdoor_patch.device_type = 'ESO15-TB'
+        outdoor_patch.device_name = 'Outdoor Removed'
+
+        esl100_patch.cid = 'BULB-CID1'
+        esl100_patch.device_type = 'ESL100'
+        esl100_patch.device_name = 'Bulb Removed'
+
+        ws_patch.cid = 'WS-CID2'
+        ws_patch.device_name = 'Switch Removed'
+        ws_patch.device_type = 'ESWL01'
+
+        air_patch.cid = 'AirCID2'
+        air_patch.device_type = 'LV-PUR131S'
+        air_patch.device_name = 'fan Removed'
+
+        json_ret = json_vals.FULL_DEV_LIST
+
+        self.vesync_obj.outlets.extend(
+            [out7a_patch, out10a_patch, outdoor_patch]
+        )
+        self.vesync_obj.switches.extend([ws_patch])
+        self.vesync_obj.fans.extend([air_patch])
+        self.vesync_obj.bulbs.extend([esl100_patch])
+
+        self.vesync_obj.process_devices(json_ret)
+
+        assert len(self.vesync_obj.outlets) == 6
+        assert len(self.vesync_obj.switches) == 2
+        assert len(self.vesync_obj.fans) == 1
+        assert len(self.vesync_obj.bulbs) == 1
diff --git a/src/tests/test_vesync_login.py b/src/tests/test_vesync_login.py
index a890243..bed747d 100644
--- a/src/tests/test_vesync_login.py
+++ b/src/tests/test_vesync_login.py
@@ -7,12 +7,14 @@
 from pyvesync.vesync import VeSync
 from pyvesync.helpers import Helpers as helpers
 
-login_test_vals = [('sam@mail.com', 'pass', 'America/New_York', 'full corret'),
-                   ('sam@mail.com', 'pass', 'invalidtz!', 'invalid tz'),
-                   ('sam@mail.com', 'pass', None, 'none tz'),
-                   ('sam@mail.com', 'pass', '', 'empty tz'),
-                   ('sam@mail.com', None, None, 'none tz pass'),
-                   ('sam@mail.com', '', '', 'empty pass')]
+login_test_vals = [
+    ('sam@mail.com', 'pass', 'America/New_York', 'full corret'),
+    ('sam@mail.com', 'pass', 'invalidtz!', 'invalid tz'),
+    ('sam@mail.com', 'pass', None, 'none tz'),
+    ('sam@mail.com', 'pass', '', 'empty tz'),
+    ('sam@mail.com', None, None, 'none tz pass'),
+    ('sam@mail.com', '', '', 'empty pass'),
+]
 
 
 @pytest.mark.parametrize('email, password, timezone, testid', login_test_vals)
@@ -61,28 +63,21 @@ def test_bad_login(self, api_mock, email, password, testid):
         assert vesync_obj.login() is False
         if testid == 'correct':
             jd = helpers.req_body(vesync_obj, 'login')
-            self.mock_api.assert_called_with('/cloud/v1/user/login',
-                                             'post',
-                                             json=jd)
+            self.mock_api.assert_called_with('/cloud/v1/user/login', 'post', json=jd)
         else:
             assert not self.mock_api.called
 
     def test_good_login(self, api_mock):
         """Test successful login."""
-        full_return = ({
-            'code': 0,
-            'result': {
-                'accountID': 'sam_actid',
-                'token': 'sam_token'
-            }
-        }, 200)
+        full_return = (
+            {'code': 0, 'result': {'accountID': 'sam_actid', 'token': 'sam_token'}},
+            200,
+        )
         self.mock_api.return_value = full_return
         vesync_obj = VeSync('sam@mail.com', 'pass')
         assert vesync_obj.login() is True
         jd = helpers.req_body(vesync_obj, 'login')
-        self.mock_api.assert_called_with('/cloud/v1/user/login',
-                                         'post',
-                                         json=jd)
+        self.mock_api.assert_called_with('/cloud/v1/user/login', 'post', json=jd)
         assert vesync_obj.token == 'sam_token'
         assert vesync_obj.account_id == 'sam_actid'
 
@@ -103,6 +98,7 @@ def test_login(mock_api, email, password, testid):
             'https://smartapi.vesync.com/cloud/v1/user/login',
             headers=None,
             json=jd,
-            timeout=5)
+            timeout=5,
+        )
     else:
         assert not mock_api.called
diff --git a/src/tests/test_vesync_outdoor.py b/src/tests/test_vesync_outdoor.py
index dfa18ee..62799fb 100644
--- a/src/tests/test_vesync_outdoor.py
+++ b/src/tests/test_vesync_outdoor.py
@@ -42,8 +42,8 @@ def api_mock(self, caplog):
     def test_outdoor_conf(self, api_mock):
         """Tests outdoor outlet is instantiated properly."""
         self.mock_api.return_value = CORRECT_OUTDOOR_LIST
-        outlets = self.vesync_obj.get_devices()
-        outlets = outlets[0]
+        self.vesync_obj.get_devices()
+        outlets = self.vesync_obj.outlets
         assert len(outlets) == 2
         outdoor_outlet = outlets[0]
         assert isinstance(outdoor_outlet, VeSyncOutdoorPlug)
@@ -70,7 +70,7 @@ def test_outdoor_details_fail(self, caplog, api_mock):
 
     def test_outdoor_outlet_onoff(self, caplog, api_mock):
         """Test Outdoor Outlet Device On/Off Methods."""
-        self.mock_api.return_value = ({"code": 0}, 200)
+        self.mock_api.return_value = ({'code': 0}, 200)
         outdoor_outlet = VeSyncOutdoorPlug(DEV_LIST_DETAIL, self.vesync_obj)
         head = helpers.req_headers(self.vesync_obj)
         body = helpers.req_body(self.vesync_obj, 'devicestatus')
@@ -80,25 +80,19 @@ def test_outdoor_outlet_onoff(self, caplog, api_mock):
         body['switchNo'] = outdoor_outlet.sub_device_no
         on = outdoor_outlet.turn_on()
         self.mock_api.assert_called_with(
-            '/outdoorsocket15a/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body
-            )
+            '/outdoorsocket15a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert on
         off = outdoor_outlet.turn_off()
         body['status'] = 'off'
         self.mock_api.assert_called_with(
-            '/outdoorsocket15a/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body
-            )
+            '/outdoorsocket15a/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert off
 
     def test_outdoor_outlet_onoff_fail(self, api_mock):
         """Test outdoor outlet On/Off Fail with Code>0."""
-        self.mock_api.return_value = ({"code": 1}, 400)
+        self.mock_api.return_value = ({'code': 1}, 400)
         outdoor_outlet = VeSyncOutdoorPlug(DEV_LIST_DETAIL, self.vesync_obj)
         assert not outdoor_outlet.turn_on()
         assert not outdoor_outlet.turn_off()
@@ -111,8 +105,11 @@ def test_outdoor_outlet_weekly(self, api_mock):
         body = helpers.req_body(self.vesync_obj, 'energy_week')
         body['uuid'] = outdoor_outlet.uuid
         self.mock_api.assert_called_with(
-            '/outdoorsocket15a/v1/device/energyweek', 'post',
-            headers=helpers.req_headers(self.vesync_obj), json=body)
+            '/outdoorsocket15a/v1/device/energyweek',
+            'post',
+            headers=helpers.req_headers(self.vesync_obj),
+            json=body,
+        )
         energy_dict = outdoor_outlet.energy['week']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -132,7 +129,8 @@ def test_outdoor_outlet_monthly(self, api_mock):
             '/outdoorsocket15a/v1/device/energymonth',
             'post',
             headers=helpers.req_headers(self.vesync_obj),
-            json=body)
+            json=body,
+        )
         energy_dict = outdoor_outlet.energy['month']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -152,7 +150,8 @@ def test_outdoor_outlet_yearly(self, api_mock):
             '/outdoorsocket15a/v1/device/energyyear',
             'post',
             headers=helpers.req_headers(self.vesync_obj),
-            json=body)
+            json=body,
+        )
         energy_dict = outdoor_outlet.energy['year']
         assert energy_dict['energy_consumption_of_today'] == 1
         assert energy_dict['cost_per_kwh'] == 1
@@ -163,7 +162,7 @@ def test_outdoor_outlet_yearly(self, api_mock):
 
     def test_history_fail(self, caplog, api_mock):
         """Test outdoor outlet energy failure."""
-        bad_history = {"code": 1}
+        bad_history = {'code': 1}
         self.mock_api.return_value = (bad_history, 200)
         outdoor_outlet = VeSyncOutdoorPlug(DEV_LIST_DETAIL, self.vesync_obj)
         outdoor_outlet.update_energy()
diff --git a/src/tests/test_wall_switch.py b/src/tests/test_wall_switch.py
index e05b381..c8c1ef4 100644
--- a/src/tests/test_wall_switch.py
+++ b/src/tests/test_wall_switch.py
@@ -35,15 +35,15 @@ def api_mock(self, caplog):
     def test_ws_conf(self, api_mock):
         """Tests that Wall Switch is instantiated properly"""
         self.mock_api.return_value = CORRECT_WS_LIST
-        devices = self.vesync_obj.get_devices()
-        switch = devices[1]
+        self.vesync_obj.get_devices()
+        switch = self.vesync_obj.switches
         assert len(switch) == 1
         wswitch = switch[0]
         assert isinstance(wswitch, VeSyncWallSwitch)
-        assert wswitch.device_name == "Name Wall Switch"
-        assert wswitch.device_type == "ESWL01"
-        assert wswitch.cid == "WS-CID"
-        assert wswitch.uuid == "UUID"
+        assert wswitch.device_name == 'Name Wall Switch'
+        assert wswitch.device_type == 'ESWL01'
+        assert wswitch.cid == 'WS-CID'
+        assert wswitch.uuid == 'UUID'
 
     def test_ws_details(self, api_mock):
         """Test WS get_details() """
@@ -66,7 +66,7 @@ def test_ws_details_fail(self, caplog, api_mock):
 
     def test_ws_onoff(self, caplog, api_mock):
         """Test 15A Device On/Off Methods"""
-        self.mock_api.return_value = ({"code": 0}, 200)
+        self.mock_api.return_value = ({'code': 0}, 200)
         wswitch = VeSyncWallSwitch(DEV_LIST_DETAIL, self.vesync_obj)
         head = helpers.req_headers(self.vesync_obj)
         body = helpers.req_body(self.vesync_obj, 'devicestatus')
@@ -75,23 +75,19 @@ def test_ws_onoff(self, caplog, api_mock):
         body['uuid'] = wswitch.uuid
         on = wswitch.turn_on()
         self.mock_api.assert_called_with(
-            '/inwallswitch/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body)
+            '/inwallswitch/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert on
         off = wswitch.turn_off()
         body['status'] = 'off'
         self.mock_api.assert_called_with(
-            '/inwallswitch/v1/device/devicestatus',
-            'put',
-            headers=head,
-            json=body)
+            '/inwallswitch/v1/device/devicestatus', 'put', headers=head, json=body
+        )
         assert off
 
     def test_ws_onoff_fail(self, api_mock):
         """Test ws On/Off Fail with Code>0"""
-        self.mock_api.return_value = ({"code": 1}, 400)
+        self.mock_api.return_value = ({'code': 1}, 400)
         vswitch15a = VeSyncWallSwitch(DEV_LIST_DETAIL, self.vesync_obj)
         assert not vswitch15a.turn_on()
         assert not vswitch15a.turn_off()