Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@cdce8p
Copy link
Contributor

@cdce8p cdce8p commented May 2, 2018

During my improvement PR's I noticed that we assign may things to each accessory although they are only needed once (at the driver level). Especially for bridged accessories that is mostly unnecessary. This PR should be a first step in reversing this behavior.

Instead of assigning the accessory to the driver, I think that I would make much more sense to assign the driver to each accessory and later add the top level one to the driver.

To-Do

  • Remove accessory.set_sentinel
  • Use driver.run_sentinel, driver.aio_stop_event and driver.event_loop
  • Maybe: Assign keys, pincode, mac to driver instead.
  • Save loader in the driver.

Note: All these changes assume that we stick to the current version, where one driver only handles one top level accessory.

@ikalchev What do you think this?

@ikalchev
Copy link
Owner

ikalchev commented May 2, 2018

Few points:

  • I think that an Accessory should be able to exist without a driver
  • The load and persist methods of the driver allow users to overwrite as necessary. There were comments at one time when someone had a read-only file system on the raspberry - one option to make hap work on this is to overwrite the persist/load to do that to/from a remote store.
  • Keeping the "management" properties in the driver sounds good( and obvious, now that I think about it)!

@cdce8p
Copy link
Contributor Author

cdce8p commented May 2, 2018

The load and persist methods of the driver allow users to overwrite as necessary.

I reverted the changes to the encoder and persistence methods.

I think that an Accessory should be able to exist without a driver

Is there a reason for it? As far as I see it, accessories can't do anything useful without a driver. That is even less if you consider that the driver handles the event loop.

Keeping the "management" properties in the driver sounds good( and obvious, now that I think about it)!

Maybe we should outsouce them to a entirely new class, similar to the IID_Manager? However this shouldn't be included in this PR.

@codecov-io
Copy link

codecov-io commented May 3, 2018

Codecov Report

Merging #105 into dev will decrease coverage by 0.09%.
The diff coverage is 71.87%.

@@            Coverage Diff            @@
##              dev     #105     +/-   ##
=========================================
- Coverage   51.04%   50.95%   -0.1%     
=========================================
  Files          15       15             
  Lines        1340     1315     -25     
  Branches      140      135      -5     
=========================================
- Hits          684      670     -14     
+ Misses        642      633      -9     
+ Partials       14       12      -2
Impacted Files Coverage Δ
pyhap/characteristic.py 98.18% <100%> (ø) ⬆️
pyhap/loader.py 90% <100%> (ø) ⬆️
pyhap/service.py 94.87% <100%> (ø) ⬆️
pyhap/accessory.py 47.53% <53.84%> (+0.41%) ⬆️
pyhap/accessory_driver.py 54.79% <75%> (-1.49%) ⬇️

@ikalchev
Copy link
Owner

ikalchev commented May 7, 2018

Maybe we should outsouce them to a entirely new class?
Yes, maybe AccessoryState or something.

Is there a reason for it?
No reason, but why couple them?

@cdce8p
Copy link
Contributor Author

cdce8p commented May 7, 2018

No reason, but why couple them?

They are already. What reason is there for an accessory to exist without the driver. Currently it's just the other way round which complicates thing IMO. Passing the driver to each accessory allows us to shortcut some functions.


I will update this PR. That should make it clearer where the benefits are.

@ikalchev
Copy link
Owner

ikalchev commented May 8, 2018

What do you mean by "complicates things"? The way I see it is that there are three clear, separate things: (1) A representation of a service (2) the state of the service and (3) a thing which manipulates the state of the service as a result of interactions with the outside world.

Currently, the Accessory is both the representation of a service and its state and the driver is the thing that manipulates. What this change proposes is to move the state from the accessory to the driver, which is totally fine, although ideally we should separate it. However, by forcing the accessory to have a driver, we essentially need to have all three things created to get functionality even from only one of them. For example, to test the Accessory I have to have a driver. I don't see this is as bringing any benefits. Right now I can have and Accessory and a Driver separately, potentially reusing and substituting whatever I want individually.

@cdce8p
Copy link
Contributor Author

cdce8p commented May 8, 2018

Let me step back a few points.

The problem I see with the current setup is that we create the event loop within the driver (which is the right call IMO). As we move more and more methods to async. Eventually nearly all objects will need a reference to the loop to work.

To be honest I took this lesson from Home Assistant as well. Their we have the hass object as a starting point (and the event loop handler) and basically any object created has some kind of reference to it.


separate things

Their is nothing against that. The question that remains however is how much sense does it make to have a object that is totally separated, but can't do anything since it requires some setup (eg. set_driver and set_sentinel for the accessory)?

Tests

You're right their. However currently you need an accessory to test the top level driver. HA has a special function to handle this get_test_home_assistant(). It returns a hass object to use for tests.

Move state from accessory to driver

I still plan on doing this.


The way I see it the hierarchy for HAP-python looks / could look something like this:

-> AccessoryDriver
  -> AccessoryState (or a different name)
  -> Loader (should be cached here instead of the module)
  -> Encoder
  -> HAPServer
    -> ...
  -> Accessory
    -> IIDManager
    -> Service
      -> Characteristic

@ikalchev
Copy link
Owner

Will have another look when you resolve the conflicts, but I think we are mostly there.

cdce8p added 4 commits May 19, 2018 00:44
* Driver now passed to accessory as argument
* Top level accessory then added to driver with 'add_accessory'
@cdce8p
Copy link
Contributor Author

cdce8p commented May 18, 2018

@ikalchev I have updated this PR.
For a complete list of changes see: Changelog/unrelease

The main ideas some of which we already discussed:

  1. Assign driver to accessory during init
  2. Add acc to driver with driver.add_accessory()
  3. acc.set_driver() and acc.set_sentinel() were redundant and have therefore been removed.
  4. run_sentinel(now stop_event), aio_stop_event and loop are now accessible through the driver.

Additional changes:
5. Added default port to accessory_driver: 51234. That's the one you used in the tests.
6. The loader object is now stored inside the driver, instead of in the module. Hence get_loader(), get_serv_loader() and get_char_loader() can be removed entirely.
7. driver.init() now requires keyword arguments. That will make any future changes easier and we already have a breaking change for this method.
8. Rearranged internal parameter for characteristic and service (now sorted alphabetically).
9. Updated accessories and tests.

Since this PR is already quite big (even though about half of the changes are related to tests), I did not include the async helper methods we talked about. They will follow in another PR.

@cdce8p cdce8p changed the title WIP: Improvements 6 - Driver assignment Improvements 6 - Driver assignment May 18, 2018
Copy link
Owner

@ikalchev ikalchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. However, why do you want to remove the standalone default loaders, i.e. why do we assume that the user will be using these only inside the driver/acc? It is perfectly fine for the user to have a method get services which are then fed to the acc using add_service. Let's now burn down bridges for users and let them decide if they want a different workflow than ours. Also, if we don't remove the get_x_loader methods, this is one breaking change less.

pyhap/loader.py Outdated
_loader = None
logger = logging.getLogger(__name__)

def read_file(path):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually json_file_to_dict

Accessory(DRIVER, 'Test Accessory')


class TestAccessory:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing the classes? This prevents you from running tests only from a single test class. Is the performance issue really big - there only a handful of methods?

* Revert loader changes
* Use pytest.fixtures for driver
* Updated changelog
@cdce8p
Copy link
Contributor Author

cdce8p commented May 19, 2018

Loader changes

I've reverted them, so this PR can get merged. The original idea was just to reduce maintenance costs and avoid storing an object in a module, since this is kind of a bad practice for python.

json_file_to_dict

I changed it back as well. Only added @staticmethod to fix a pylint issue. Since this is a private method and the class is small, I don't think the method name is that important.

TestAccessory

You couldn't run them separately before either. Since TestBridge inherited from TestAccessory running this test module resulted in 9 executed tests, although it only had 7. Additionally the change to normal methods allows us to use pytest.fixtures more easily (like mock_driver).
Furthermore I added keywords to each test. That way you know can run them individually with pytest tests/test_accessory.py -k acc or pytest tests/test_accessory.py -k bridge.


The conftest.py file I've added is for pytest. It is used to store fixtures that should be accessible for every test. The MockDriver class can be extended in the future if necessary.

@ikalchev ikalchev merged commit 1b86917 into ikalchev:dev May 19, 2018
@cdce8p cdce8p deleted the impr-6-driver branch May 19, 2018 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants