Passing in a string still works and returns either `IotModule` or
`SmartModule` type when called on `IotDevice` or `SmartDevice`
respectively. When calling on `Device` will return `Module` type.
Passing in a module type is then typed to that module, i.e.:
```py
smartdev.get_module(FanModule) # type is FanModule
smartdev.get_module("FanModule") # type is SmartModule
```
Only thing this doesn't do is check that you can't pass an `IotModule`
to a `SmartDevice.get_module()`. However there is a runtime check which
will return null if the passed `ModuleType` is not a subclass of
`SmartModule`.
Many thanks to @cdce8p for helping with this.
Also updates CI pypy versions to be 3.9 and 3.10 which are the currently
[supported
versions](https://www.pypy.org/posts/2024/01/pypy-v7315-release.html).
Otherwise latest cryptography doesn't ship with pypy3.8 wheels and is
unable to build on windows.
Also updates the `codecov-action` to v4 which fixed some intermittent
uploading errors.