Skip to content

Commit 2930f6c

Browse files
committed
Implement basic and advanced shutters support
1 parent ca33527 commit 2930f6c

12 files changed

+924
-64
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ The following Legrand and BTicino products are partially or fully supported by O
7676
| MyHome Up | | 03598 | F454 | |
7777
| MyHome Up | | 03651 | F418U2 | |
7878
| MyHome Up | | 03847 | F411U1 | |
79-
| MyHome Up | | 03848 | F411U2 | Shutter mode is not yet supported |
79+
| MyHome Up | | 03848 | F411U2 | |
80+
| MyHome Up | Céliane | 67557 | | |
81+
| MyHome Up | Céliane | 67561 | | |
8082
| | | | | |
8183
| MyHome Play | Céliane | 67223 | | |
8284
| MyHome Play | | 88328 | 3578 | |

src/OpenNetty.Mqtt/OpenNettyMqttAttributes.cs

+10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ public static class OpenNettyMqttAttributes
4141
/// </summary>
4242
public const string Scenario = "scenario";
4343

44+
/// <summary>
45+
/// Shutter position.
46+
/// </summary>
47+
public const string ShutterPosition = "shutter_position";
48+
49+
/// <summary>
50+
/// Shutter state.
51+
/// </summary>
52+
public const string ShutterState = "shutter_state";
53+
4454
/// <summary>
4555
/// Smart meter indexes.
4656
/// </summary>

src/OpenNetty.Mqtt/OpenNettyMqttHostedService.cs

+40
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,32 @@ await _events.ProgressiveScenarioReported
202202
.Retry()
203203
.SubscribeAsync(static arguments => ValueTask.CompletedTask),
204204

205+
await _events.ShutterPositionReported
206+
.Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name))
207+
.Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.ShutterPosition, builder =>
208+
{
209+
builder.WithPayload(arguments.Position.ToString(CultureInfo.InvariantCulture));
210+
builder.WithRetainFlag();
211+
}))
212+
.Retry()
213+
.SubscribeAsync(static arguments => ValueTask.CompletedTask),
214+
215+
await _events.ShutterStateReported
216+
.Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name))
217+
.Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.ShutterState, builder =>
218+
{
219+
builder.WithPayload(arguments.State switch
220+
{
221+
OpenNettyModels.Automation.ShutterState.Stopped => "stopped",
222+
OpenNettyModels.Automation.ShutterState.Opening => "opening",
223+
_ => "closing"
224+
});
225+
226+
builder.WithRetainFlag();
227+
}))
228+
.Retry()
229+
.SubscribeAsync(static arguments => ValueTask.CompletedTask),
230+
205231
await _events.SmartMeterIndexesReported
206232
.Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name))
207233
.Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterIndexes, builder =>
@@ -257,6 +283,20 @@ await _events.SmartMeterRateTypeReported
257283
.Retry()
258284
.SubscribeAsync(static arguments => ValueTask.CompletedTask),
259285

286+
await _events.StopUpDownScenarioReported
287+
.Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name))
288+
.Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.Scenario, builder =>
289+
{
290+
builder.WithPayload(arguments.State switch
291+
{
292+
OpenNettyModels.Automation.ShutterState.Stopped => "STOP",
293+
OpenNettyModels.Automation.ShutterState.Opening => "OPEN",
294+
_ => "CLOSE"
295+
});
296+
}))
297+
.Retry()
298+
.SubscribeAsync(static arguments => ValueTask.CompletedTask),
299+
260300
await _events.SwitchStateReported
261301
.Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name))
262302
.Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SwitchState, builder =>

src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs

+86
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,59 @@ await _controller.SetPilotWireSetpointModeAsync(endpoint,
267267
await _controller.DispatchBasicScenarioAsync(endpoint);
268268
break;
269269

270+
case "down" when endpoint.HasCapability(OpenNettyCapabilities.StopUpDownScenario):
271+
await _controller.DispatchStopUpDownScenarioAsync(endpoint, OpenNettyModels.Automation.ShutterState.Closing);
272+
break;
273+
270274
case "on" when endpoint.HasCapability(OpenNettyCapabilities.OnOffScenario):
271275
await _controller.DispatchOnOffScenarioAsync(endpoint, OpenNettyModels.Lighting.SwitchState.On);
272276
break;
273277

274278
case "off" when endpoint.HasCapability(OpenNettyCapabilities.OnOffScenario):
275279
await _controller.DispatchOnOffScenarioAsync(endpoint, OpenNettyModels.Lighting.SwitchState.Off);
276280
break;
281+
282+
case "stop" when endpoint.HasCapability(OpenNettyCapabilities.StopUpDownScenario):
283+
await _controller.DispatchStopUpDownScenarioAsync(endpoint, OpenNettyModels.Automation.ShutterState.Stopped);
284+
break;
285+
286+
case "up" when endpoint.HasCapability(OpenNettyCapabilities.StopUpDownScenario):
287+
await _controller.DispatchStopUpDownScenarioAsync(endpoint, OpenNettyModels.Automation.ShutterState.Opening);
288+
break;
289+
}
290+
break;
291+
292+
case OpenNettyMqttAttributes.ShutterPosition when operation is OpenNettyMqttOperation.Get:
293+
_ = await _controller.EnumerateShutterPositionsAsync(endpoint).ToListAsync();
294+
break;
295+
296+
case OpenNettyMqttAttributes.ShutterPosition when operation is OpenNettyMqttOperation.Set:
297+
if (!ushort.TryParse(message.PayloadSegment, CultureInfo.InvariantCulture, out var position))
298+
{
299+
throw new InvalidDataException(SR.GetResourceString(SR.ID0075));
300+
}
301+
302+
await _controller.SetShutterPositionAsync(endpoint, position);
303+
break;
304+
305+
case OpenNettyMqttAttributes.ShutterState when operation is OpenNettyMqttOperation.Get:
306+
_ = await _controller.EnumerateShutterStatesAsync(endpoint).ToListAsync();
307+
break;
308+
309+
case OpenNettyMqttAttributes.ShutterState when operation is OpenNettyMqttOperation.Set:
310+
switch (message.ConvertPayloadToString()?.ToLowerInvariant())
311+
{
312+
case "close":
313+
await _controller.MoveShutterUpAsync(endpoint);
314+
break;
315+
316+
case "open":
317+
await _controller.MoveShutterDownAsync(endpoint);
318+
break;
319+
320+
case "stop":
321+
await _controller.StopShutterAsync(endpoint);
322+
break;
277323
}
278324
break;
279325

@@ -488,6 +534,46 @@ orderby name
488534
components.Add($"entity{components.Count.ToString(CultureInfo.InvariantCulture)}", component);
489535
break;
490536
}
537+
538+
case "Cover":
539+
case null when endpoint.HasCapability(OpenNettyCapabilities.BasicShutterControl) &&
540+
endpoint.HasCapability(OpenNettyCapabilities.BasicShutterState):
541+
{
542+
var component = new JsonObject
543+
{
544+
["platform"] = "cover",
545+
["unique_id"] = Base64Url.EncodeToString(SHA256.HashData(
546+
[
547+
..Encoding.UTF8.GetBytes(name),
548+
..Encoding.UTF8.GetBytes(endpoint.Address?.Value ?? string.Empty)
549+
])),
550+
["name"] = endpoint.Name,
551+
["device_class"] = endpoint.GetStringSetting(OpenNettySettings.HomeAssistantDeviceClass) ?? "shutter"
552+
};
553+
554+
if (endpoint.HasCapability(OpenNettyCapabilities.BasicShutterControl))
555+
{
556+
component["command_topic"] = $"{options.RootTopic}/{name}/{OpenNettyMqttAttributes.ShutterState}/set";
557+
}
558+
559+
if (endpoint.HasCapability(OpenNettyCapabilities.BasicShutterState))
560+
{
561+
component["state_topic"] = $"{options.RootTopic}/{name}/{OpenNettyMqttAttributes.ShutterState}";
562+
}
563+
564+
if (endpoint.HasCapability(OpenNettyCapabilities.AdvancedShutterControl))
565+
{
566+
component["set_position_topic"] = $"{options.RootTopic}/{name}/{OpenNettyMqttAttributes.ShutterPosition}/set";
567+
}
568+
569+
if (endpoint.HasCapability(OpenNettyCapabilities.AdvancedShutterState))
570+
{
571+
component["position_topic"] = $"{options.RootTopic}/{name}/{OpenNettyMqttAttributes.ShutterPosition}";
572+
}
573+
574+
components.Add($"entity{components.Count.ToString(CultureInfo.InvariantCulture)}", component);
575+
break;
576+
}
491577
}
492578
}
493579

src/OpenNetty/OpenNettyCapabilities.cs

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ public static class OpenNettyCapabilities
2121
/// </summary>
2222
public static readonly OpenNettyCapability AdvancedDimmingState = new("Advanced dimming state");
2323

24+
/// <summary>
25+
/// Advanced shutter control.
26+
/// </summary>
27+
public static readonly OpenNettyCapability AdvancedShutterControl = new("Advanced shutter control");
28+
29+
/// <summary>
30+
/// Advanced shutter state.
31+
/// </summary>
32+
public static readonly OpenNettyCapability AdvancedShutterState = new("Advanced shutter state");
33+
2434
/// <summary>
2535
/// Basic dimming control.
2636
/// </summary>
@@ -36,6 +46,16 @@ public static class OpenNettyCapabilities
3646
/// </summary>
3747
public static readonly OpenNettyCapability BasicScenario = new("Basic scenario");
3848

49+
/// <summary>
50+
/// Basic shutter control.
51+
/// </summary>
52+
public static readonly OpenNettyCapability BasicShutterControl = new("Basic shutter control");
53+
54+
/// <summary>
55+
/// Basic shutter state.
56+
/// </summary>
57+
public static readonly OpenNettyCapability BasicShutterState = new("Basic shutter state");
58+
3959
/// <summary>
4060
/// Battery.
4161
/// </summary>
@@ -126,6 +146,11 @@ public static class OpenNettyCapabilities
126146
/// </summary>
127147
public static readonly OpenNettyCapability SmartMeterInformation = new("Smart meter information");
128148

149+
/// <summary>
150+
/// Stop/up/down scenario.
151+
/// </summary>
152+
public static readonly OpenNettyCapability StopUpDownScenario = new("Stop/up/down scenario");
153+
129154
/// <summary>
130155
/// Timed scenario.
131156
/// </summary>

src/OpenNetty/OpenNettyCommands.cs

+21
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ public static class Lighting
8282
public static readonly OpenNettyCommand DimStop = new(OpenNettyCategories.Lighting, "38");
8383
}
8484

85+
/// <summary>
86+
/// Automation commands (WHO = 2).
87+
/// </summary>
88+
public static class Automation
89+
{
90+
/// <summary>
91+
/// Stop (WHAT = 0).
92+
/// </summary>
93+
public static readonly OpenNettyCommand Stop = new(OpenNettyCategories.Automation, "0");
94+
95+
/// <summary>
96+
/// Up (WHAT = 1).
97+
/// </summary>
98+
public static readonly OpenNettyCommand Up = new(OpenNettyCategories.Automation, "1");
99+
100+
/// <summary>
101+
/// Down (WHAT = 2).
102+
/// </summary>
103+
public static readonly OpenNettyCommand Down = new(OpenNettyCategories.Automation, "2");
104+
}
105+
85106
/// <summary>
86107
/// Temperature control commands (WHO = 4).
87108
/// </summary>

0 commit comments

Comments
 (0)