Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental support Rx #118

Merged
merged 31 commits into from
May 18, 2020
Merged

Experimental support Rx #118

merged 31 commits into from
May 18, 2020

Conversation

helto4real
Copy link
Collaborator

@helto4real helto4real commented May 9, 2020

Proposed change

** I know this is way too big PR to be a good one... **

Refactorings of where logic is put

In order to make application lifecycle easier, all eventlisteners and state handlers are tied to each app-instance. This makes it way more easy to disable individual apps in runtime plus manage lifecykle in the Rx stuff too.

Implement the Rx API

Implement the Rx interface. This is an alternative implementation to @jvert suggestions (thanks alot for this great idéa). This proposal was so different it was easier to implement it in own PR rather than change yours.. Hope that is ok :).

New BaseClass NetDaemonRxApp that will implement the Rx style. We do not want to mix with old style. This is event based and not Async/await. All calls post a message on a Channel and async code takes care of the async stuff.

Tests will be added. after feedback.
More API features will be added before merge.

Example of usage:

// Observable that get state changes 
StateChanges.Subscribe(t=> Log(t.New.State));

// Observable that get all state changes inkluding attributes 
StateAllChanges.Subscribe(t=> Log(t.New.State));

// IEnumerable<EntityState>
var allLights = States.Select(n => n.EntityId.StartsWith("light."));
// Gets single state
var state = State("light.my_light")?.State;

// No async, handled in background
CallService("light", "turn_on", new {entity_id = "light.my_light"});

// Entity now not fluent
// Action on single entity
Entity("light.my_light").TurnOn();
Entity("light.my_light").Toggle();
// Action on multiple entities
Entities("light.my_light", "light.my_light").Toggle();
// Or lambda
Entities(n => n.EntityId.StartsWith("light.").Toggle();

// Merging observables <3
Entities(
    "binary_sensor.tomas_rum_pir",
    "binary_sensor.vardagsrum_pir")
    .StateChanges
    .Subscribe(e =>
    {
        Log("{entity}: {state}({oldstate}, {lastchanged})", e.New.EntityId, e.New.State, e.Old.State, e.New.LastChanged);
    });

// Merging observables all changes including attributes <3
Entities(
    "binary_sensor.tomas_rum_pir",
    "binary_sensor.vardagsrum_pir")
    .StateAllChanges
    .Subscribe(e =>
    {
        Log("{entity}: {state}({oldstate}, {lastchanged})", e.New.EntityId, e.New.State, e.Old.State, e.New.LastChanged);
    });

// Set state
SetState("sensor.thesensor", "on", new {attributex="cool"});
// Set state selecting with Entity Selector
Entity("sensor.x", "sensor.y").SetState("on");

// Merging of entity results

// Schedulers
RunEvery(TimeSpan.FromMinutes(1)).Subscribe(....);

RunDaily("12:00:00").Subscribe(...);

// Events
EventChanges
     .Subscribe(f =>
      {
            Log("event: {domain}.{event} - {data}", f?.Domain??"none", f.Event, f?.Data);
       });

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (which adds functionality to an existing integration)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the [development checklist][dev-checklist]
  • The code compiles without warnings (code quality chek)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

@helto4real helto4real mentioned this pull request May 10, 2020
Closed
12 tasks
@danpowell88
Copy link

It's looking fairly nice and clean to me

@helto4real
Copy link
Collaborator Author

It's looking fairly nice and clean to me

Thank you for taking time to comment.

/// <inheritdoc/>
public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false)
public Task CallServiceAsync(string domain, string service, dynamic? data = null, bool waitForResponse = false)
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a little confusing to have a ...Async() method with a 'waitForResponse' argument. If the caller wants to wait, they can just call .Wait() on the returned Task, right? But there is no return code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the old Api that I am changing the names to standard Async postfix. Most functions that are async use that postfix. I want to make the very different from the new API

/// <summary>
/// Interface for Observable Events
/// </summary>
public interface IRxEvent : IObservable<RxEvent>
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer not having this empty interface, it just adds some extra indirection for anybody trying to use the API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm there was a reason I can not remember now... I will check that out

/// <summary>
/// Interface for scheduling
/// </summary>
public interface IRxSchedule
Copy link
Contributor

Choose a reason for hiding this comment

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

These are super useful! It doesn't say what the 'long' represents though - I assume a Windows file time? Might be a little more obvious if it was IObservable but I'm not sure how useful that is - you could just do DateTime.Now.

src/App/NetDaemon.App/Common/Reactive/RxEntity.cs Outdated Show resolved Hide resolved
src/App/NetDaemon.App/Common/Reactive/RxEntity.cs Outdated Show resolved Hide resolved
@helto4real helto4real merged commit 6aab278 into dev May 18, 2020
@helto4real helto4real deleted the netdaemon_rx_app branch May 18, 2020 10:19
Ikcelaks pushed a commit to Ikcelaks/netdaemon that referenced this pull request Dec 23, 2022
## Proposed change
The change implements the System.Reactive (Rx) style of using streams of changes for automations. 

## Refactorings of where logic is put
In order to make application lifecycle easier, all eventlisteners and state handlers are tied to each app-instance. This makes it way more easy to disable individual apps in runtime plus manage lifecykle in the Rx stuff too. 

## Implement the Rx API
Implement the Rx interface. This is an alternative implementation to @jvert suggestions (thanks alot for this great idéa). This proposal was so different it was easier to implement it in own PR rather than change yours.. Hope that is ok :). 

New BaseClass `NetDaemonRxApp` that will implement the Rx style. We do not want to mix with old style. This is event based and not Async/await. All calls post a message on a Channel and async code takes care of the async stuff. 

Tests will be added. after feedback.
More API features will be added before merge. 

Example of usage:

```c#
// Observable that get state changes 
StateChanges.Subscribe(t=> Log(t.New.State));

// Observable that get all state changes inkluding attributes 
StateAllChanges.Subscribe(t=> Log(t.New.State));

// IEnumerable<EntityState>
var allLights = States.Select(n => n.EntityId.StartsWith("light."));
// Gets single state
var state = State("light.my_light")?.State;

// No async, handled in background
CallService("light", "turn_on", new {entity_id = "light.my_light"});

// Entity now not fluent
// Action on single entity
Entity("light.my_light").TurnOn();
Entity("light.my_light").Toggle();
// Action on multiple entities
Entities("light.my_light", "light.my_light").Toggle();
// Or lambda
Entities(n => n.EntityId.StartsWith("light.").Toggle();

// Merging observables <3
Entities(
    "binary_sensor.tomas_rum_pir",
    "binary_sensor.vardagsrum_pir")
    .StateChanges
    .Subscribe(e =>
    {
        Log("{entity}: {state}({oldstate}, {lastchanged})", e.New.EntityId, e.New.State, e.Old.State, e.New.LastChanged);
    });

// Merging observables all changes including attributes <3
Entities(
    "binary_sensor.tomas_rum_pir",
    "binary_sensor.vardagsrum_pir")
    .StateAllChanges
    .Subscribe(e =>
    {
        Log("{entity}: {state}({oldstate}, {lastchanged})", e.New.EntityId, e.New.State, e.Old.State, e.New.LastChanged);
    });

// Set state
SetState("sensor.thesensor", "on", new {attributex="cool"});
// Set state selecting with Entity Selector
Entity("sensor.x", "sensor.y").SetState("on");

// Merging of entity results

// Schedulers
RunEvery(TimeSpan.FromMinutes(1)).Subscribe(....);

RunDaily("12:00:00").Subscribe(...);

// Events
EventChanges
     .Subscribe(f =>
      {
            Log("event: {domain}.{event} - {data}", f?.Domain??"none", f.Event, f?.Data);
       });


```
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.

Implement support for IObservable<T>
3 participants