2.3. Device Manager

[1]:
from device_manager import DeviceManager

device_manager = DeviceManager()
device_manager
[1]:
<device_manager.manager.DeviceManager at 0x1ca0c011d30>

The DeviceManager combines the functionality of some classes:

  1. To search for connected devices, it contains a DeviceScanner

  2. To store devices in the DeviceManager, it can be used just like a standard python dictionary

  3. To store the devices persistantly, the DeviceManager can be serialized in a JSON-file

2.3.1. Searching for devices

To search a device, you can use the functions find_by_address or find_by_device, that are working as their names say.

Additionally, the DeviceManager has a scanner-property that is an instance of DeviceScanner:

[2]:
device_manager.scanner
[2]:
<device_manager.scanner.DeviceScanner at 0x1ca0c0115b0>

This DeviceScanner can be used to search for devices by their unique identifiers and it can provide a list of all connected devices. The DeviceScanner is splitted into an USBDeviceScanner and a LANDeviceScanner. When the device_manager.scanner is used, it automatically uses both types of scanners to search for devices. But by using the __getitem__ operator, you can specify the device type you are seraching for.

[3]:
device_manager.scanner["lan"]
[3]:
<device_manager.scanner._win32.Win32LANDeviceScanner at 0x1ca0c199820>
[4]:
device_manager.scanner["usb"].list_devices()
[4]:
(USBDevice('USB\\VID_413C&PID_5534\\4&3FFD8F2A&0&21'),
 USBDevice('USB\\VID_413C&PID_2513\\6&1FFE1F1B&1&2'),
 USBDevice('USB\\VID_0B15&PID_3401\\0123456789ABCD'),
 USBDevice('USB\\VID_413C&PID_2134\\ABC1234'),
 USBDevice('USB\\VID_1130&PID_1620\\XYZWVUT975310'),
 USBDevice('USB\\VID_0781&PID_5581\\01171114'),
 USBDevice('USB\\VID_1BCF&PID_2B8D\\4C5DDB4'))
[5]:
device_manager.scanner["usb"].find_devices(serial="0123456789ABCD")
[5]:
(USBDevice('USB\\VID_0B15&PID_3401\\0123456789ABCD'),)

2.3.2. Using the DeviceManager as a dictionary

Standard dictionary functionality

The DeviceManager-class can be used as a standard python dictionary. For this purpose, the DeviceManager supports the functions set, get and remove, as well as the magic functions __getitem__, __setitem__ and __delitem__, for getting, setting and deleting items from the dictionary.

Storing devices into the DeviceManager

Using the set function or the __setitem__ operator, you can easily add devices to the device manager. To do this, you can define a name, which will be used to identify the device.

[6]:
from device_manager import USBDevice, LANDevice

test_usb_device = USBDevice()
test_usb_device.address = "path/to/my/new/usb/device"

# Add the device by using the add function
device_manager.set("my-usb-device", test_usb_device)

test_lan_device = LANDevice()
test_lan_device.address = "my.lan-device.com"

# Add the device by using the __setitem__ operator
device_manager["my-lan-device"] = test_lan_device

Now, both added keys can be found in device_manager:

[7]:
device_manager.keys()
[7]:
('my-usb-device', 'my-lan-device')

Beside keys, you can also use the functions values or items to get all stored devices or key-value-pairs of the DeviceManager – just as knwon from the standard python dictionary.

Getting a device stored in the DeviceManager

Getting a device from the DeviceManager works analogously to setting a device. So, getting an item is done with the get function or the __getitem__ operator.

[8]:
# Gets the device, which was stored as "my-lan-device", by using the get function
device_manager.get("my-lan-device")
[8]:
LANDevice('my.lan-device.com')
[9]:
# Gets the device, which was stored as "my-usb-device", by using the __getitem__ operator
device_manager["my-usb-device"]
[9]:
USBDevice('path/to/my/new/usb/device')

Checking the DeviceManager’s available keys

Before accessing a key of the DeviceManager-object, you can check if the key is known to the DeviceManager.

[10]:
# Check the length (number of keys) of device_manager
len(device_manager)
[10]:
2
[11]:
# Check if a device-name is already in device_manager
"my-usb-device" in device_manager
[11]:
True

Removing devices from the DeviceManager

To remove a device from the DeviceManager, you can use the __delitem__ operator or the remove function.

[12]:
# Deletes the device which was stored at "my-usb-device", by using the __delitem__ operator
del device_manager["my-usb-device"]
[13]:
# Deletes the device which was stored at "my-usb-device", by using the remove function
device_manager.remove("my-lan-device")

Now, after the previously added keys were removed, device_manager is empty again:

[14]:
device_manager.keys()
[14]:
()

To remove all items from the DeviceManager, you can also use the clear function:

[15]:
device_manager.clear()

Extended dictionary functionalities

Additionaly to the standard dictionary functionality, there are several functions that do not work with a standard python dictionary.

Multiple devices with the same key

The DeviceManager allows you to store multiple devices with the same key, but only if the devices have different types. This is very useful, if you need to store a device which supports different connection types.

The following examples shows a device that can be connected with ethernet (LAN) or USB:

[16]:
device_manager["multi-interface-device"] = test_usb_device
device_manager["multi-interface-device"] = test_lan_device

device_manager["multi-interface-device"]
[16]:
{<DeviceType.USB: 'usb'>: USBDevice('path/to/my/new/usb/device'),
 <DeviceType.LAN: 'lan'>: LANDevice('my.lan-device.com')}

Doing this, shows how the devices are actually stored inside the DeviceManager, because the DeviceManager uses two keys for storing a device:

  1. user-defined name

  2. type of the device

If the DeviceManager has multiple devices for a key, it will return a dictionary when requesting that devices. But if the DeviceManager only knwos a single device for the provided key, it returns only this device - not a dictionary. So, if you do not use this functionality, you will not notice it.

Accessing a specific device type

Due to the fact, that it is possible to store more than a single device for the same key, there is a possibility to access only the device you need. By using a tuple-key you can provide the required device-type to the second key:

[17]:
from device_manager import DeviceType

device_manager["multi-interface-device", DeviceType.USB]
[17]:
USBDevice('path/to/my/new/usb/device')

The second key does not need to be a DeviceType-object, you can also use a string or the device’s type or even a Device-object:

[18]:
device_manager["multi-interface-device", "usb"]
[18]:
USBDevice('path/to/my/new/usb/device')
[19]:
device_manager["multi-interface-device", USBDevice]
[19]:
USBDevice('path/to/my/new/usb/device')
[20]:
device_manager["multi-interface-device", test_usb_device]
[20]:
USBDevice('path/to/my/new/usb/device')

Deleting items works analogously to this:

[21]:
del device_manager["multi-interface-device", "usb"]
[22]:
# The ethernet device is still there
device_manager["multi-interface-device", "lan"]
[22]:
LANDevice('my.lan-device.com')

Now it is not required anymore to specify the DeviceType, because only the ethernet device is left for key “multi-interface-device”:

[23]:
# The ethernet device is the only device that is left
device_manager["multi-interface-device"]
[23]:
LANDevice('my.lan-device.com')

Automatical search for a device

If you want to add a device, but you do not have a Device-object, you can just pass the device’s address as string into the device_manager.

[24]:
device_manager["added-by-address", "usb"] = "USB\\VID_0B15&PID_3401\\0123456789ABCD"

print(device_manager["added-by-address"])
print("vendor id: ", device_manager["added-by-address"].vendor_id)
print("product id:", device_manager["added-by-address"].product_id)
print("serial:    ", device_manager["added-by-address"].serial)
USBDevice('USB\\VID_0B15&PID_3401\\0123456789ABCD')
vendor id:  2837
product id: 13313
serial:     0123456789ABCD

As you can see, the Device-object was automatically filled with its information. To add the device you can also leave out the type-specification. This results in a search for all available device types (currently usb and lan), which takes more time than the targeted search for one device type.

2.3.3. Serialization

Storing the device manager in a file

To persistantly save your devices, you can use the save function of the DeviceManager:

[25]:
with open("mydevices.json", "w") as file:
    device_manager.save(file, pretty=True)

# Show file contents
with open("mydevices.json", "r") as file:
    print(file.read())
{
    "multi-interface-device": {
        "lan": {
            "type": "lan",
            "address": "my.lan-device.com",
            "address_aliases": []
        }
    },
    "added-by-address": {
        "usb": {
            "type": "usb",
            "address": "USB\\VID_0B15&PID_3401\\0123456789ABCD",
            "address_aliases": [
                "USB\\VID_0B15&PID_3401&REV_0100",
                "USB\\VID_0B15&PID_3401",
            ],
            "vendor_id": 2837,
            "product_id": 13313,
            "revision_id": 256,
            "serial": "0123456789ABCD"
        }
    }
}

Loading the device manager from a file

To load data from the file, either use the load function or the context manager function load_device_manager, if you do not have a DeviceManager-object yet:

[26]:
from device_manager import load_device_manager

with load_device_manager("mydevices.json") as manager:
    devices = manager.items()

# This is doing the same:
manager = DeviceManager()
with open("mydevices.json", "r") as file:
    devices = manager.load(file)

devices
[26]:
(('multi-interface-device', {<DeviceType.LAN: 'lan'>: LANDevice(None)}),
 ('added-by-address',
  {<DeviceType.USB: 'usb'>: USBDevice('USB\\VID_0B15&PID_3401\\0123456789ABCD')}))

After loading data from a file, the DeviceManager automatically checks the addresses of the devices. That causes the LANDevice at “multi-interface-device” to have None as address.