implement more commands, use will messages to turn entities unavailable

pull/34/head
sleevezipper 4 years ago
parent 06b5373d6b
commit ebae183f3a

@ -127,7 +127,19 @@ This sensor spits out a random number every second. Useful for testing, maybe yo
## Commands
Commands can be used to trigger certain things on the client.
Commands can be used to trigger certain things on the client. For each command, a switch will be available in Home Assistant. Turning on the switch fires the command on the client and it will turn the switch off when it's done. Turning it off will cancel thje running command.
### ShutdownCommand
This command shuts down the computer immediately. It runs `shutdown /s`.
### RestartCommand
This command restarts the computer immediately. It runs `shutdown /r`.
### LogOffCommand
This command logs off the current user. It runs `shutdown /l`.
### CustomCommand
@ -135,4 +147,6 @@ This command allows you to run any Windows Commands. The command will be run in
|Command|Explanation|
|---|---|
|Rundll32.exe user32.dll,LockWorkStation|This locks the current session.|
|Rundll32.exe user32.dll,LockWorkStation|This locks the current session.|
|shutdown /s /t 300|Shuts the PC down after 5 minutes (300 seconds).|
|C:\path\to\your\batchfile.bat|Run the specified batch file.|

@ -20,7 +20,7 @@
<TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/>
<ContentControl IsVisible="{Binding ShowCommandInput}" Margin="0 20 0 10">Command</ContentControl>
<TextBox IsVisible="{Binding ShowCommandInput}" Text="{Binding Command}" Watermark="Rundll32.exe user32.dll,LockWorkStation" HorizontalAlignment="Left" MinWidth="300"/>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Test">Test</Button>
<Button IsVisible="{Binding ShowCommandInput}" Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Test">Test</Button>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button>
</StackPanel>
</Window>

@ -63,6 +63,21 @@ namespace UserInterface.Views
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#customcommand";
item.ShowCommandInput = true;
break;
case AvailableCommands.ShutdownCommand:
item.Description = "This command shuts down the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#shutdowncommand";
item.ShowCommandInput = false;
break;
case AvailableCommands.RestartCommand:
item.Description = "This command restarts the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#restartcommand";
item.ShowCommandInput = false;
break;
case AvailableCommands.LogOffCommand:
item.Description = "This command logs the current user off immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#logoffcommand";
item.ShowCommandInput = false;
break;
default:
item.Description = null;
item.MoreInfoLink = null;

@ -18,7 +18,7 @@
Width="1*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock IsVisible="{Binding !ConfiguredSensors.Count}">Add some commands by clicking the "Add" button. </TextBlock>
<TextBlock IsVisible="{Binding !ConfiguredCommands.Count}">Add some commands by clicking the "Add" button. </TextBlock>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Add">Add</Button>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Delete">Delete</Button>

@ -173,6 +173,15 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
AbstractCommand commandToCreate = null;
switch (commandType)
{
case AvailableCommands.ShutdownCommand:
commandToCreate = new ShutdownCommand(this._publisher, model.Name);
break;
case AvailableCommands.RestartCommand:
commandToCreate = new RestartCommand(this._publisher, model.Name);
break;
case AvailableCommands.LogOffCommand:
commandToCreate = new LogOffCommand(this._publisher, model.Name);
break;
case AvailableCommands.CustomCommand:
commandToCreate = new CustomCommand(this._publisher, model.Command, model.Name);
break;

@ -55,6 +55,9 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public enum AvailableCommands
{
CustomCommand
CustomCommand,
ShutdownCommand,
LogOffCommand,
RestartCommand,
}
}

@ -61,6 +61,7 @@ namespace hass_workstation_service.Communication
if (options != null)
{
options.WillMessage.Topic = $"homeassistant/sensor/{this.DeviceConfigModel.Name}/availability";
this._mqttClient.ConnectAsync(options);
this._mqttClientMessage = "Connecting...";
}
@ -72,7 +73,6 @@ namespace hass_workstation_service.Communication
this._mqttClient.UseConnectedHandler(e => {
this._mqttClientMessage = "All good";
});
this._mqttClient.UseApplicationMessageReceivedHandler(e => this.HandleMessageReceived(e.ApplicationMessage));
// configure what happens on disconnect
@ -93,7 +93,6 @@ namespace hass_workstation_service.Communication
_logger.LogError(ex, "Reconnecting failed");
}
}
});
}
@ -211,16 +210,17 @@ namespace hass_workstation_service.Communication
{
if (command.GetAutoDiscoveryConfig().Command_topic == applicationMessage.Topic)
{
command.Execute();
if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "ON")
{
command.TurnOn();
}
else if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "OFF")
{
command.TurnOff();
}
}
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {applicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(applicationMessage?.Payload)}");
Console.WriteLine($"+ QoS = {applicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {applicationMessage.Retain}");
Console.WriteLine();
}
}
}

@ -109,11 +109,7 @@ namespace hass_workstation_service.Communication
/// </summary>
/// <value></value>
public string Device_class { get; set; }
/// <summary>
/// (Optional) Defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. Defaults to 0 in hass.
/// </summary>
/// <value></value>
public int? Expire_after { get; set; }
/// <summary>
/// Sends update events even if the value hasnt changed. Useful if you want to have meaningful value graphs in history.
/// </summary>

@ -26,6 +26,7 @@ namespace hass_workstation_service.Data
public ICollection<AbstractSensor> ConfiguredSensors { get; private set; }
public ICollection<AbstractCommand> ConfiguredCommands { get; private set; }
public Action<IMqttClientOptions> MqqtConfigChangedHandler { get; set; }
private readonly DeviceConfigModel _deviceConfigModel;
private bool BrokerSettingsFileLocked { get; set; }
private bool SensorsSettingsFileLocked { get; set; }
@ -33,8 +34,9 @@ namespace hass_workstation_service.Data
private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
public ConfigurationService()
public ConfigurationService(DeviceConfigModel deviceConfigModel)
{
this._deviceConfigModel = deviceConfigModel;
if (!File.Exists(Path.Combine(path, "mqttbroker.json")))
{
File.Create(Path.Combine(path, "mqttbroker.json")).Close();
@ -156,6 +158,15 @@ namespace hass_workstation_service.Data
AbstractCommand command = null;
switch (configuredCommand.Type)
{
case "ShutdownCommand":
command = new ShutdownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "RestartCommand":
command = new RestartCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "LogOffCommand":
command = new LogOffCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "CustomCommand":
command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id);
break;
@ -184,6 +195,12 @@ namespace hass_workstation_service.Data
AllowUntrustedCertificates = true
})
.WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString())
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
.WithWillMessage(new MqttApplicationMessageBuilder()
.WithRetainFlag()
.WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability")
.WithPayload("offline")
.Build())
.Build();
return mqttClientOptions;
}
@ -315,12 +332,12 @@ namespace hass_workstation_service.Data
public async void DeleteConfiguredCommand(Guid id)
{
var sensorToRemove = this.ConfiguredCommands.FirstOrDefault(s => s.Id == id);
if (sensorToRemove != null)
var commandToRemove = this.ConfiguredCommands.FirstOrDefault(s => s.Id == id);
if (commandToRemove != null)
{
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredCommands.Remove(sensorToRemove);
WriteSensorSettingsAsync();
await commandToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredCommands.Remove(commandToRemove);
WriteCommandSettingsAsync();
}
else
{

@ -13,7 +13,7 @@ namespace hass_workstation_service.Domain.Commands
/// <summary>
/// The update interval in seconds. It checks state only if the interval has passed.
/// </summary>
public int UpdateInterval { get; protected set; }
public int UpdateInterval { get => 1; }
public DateTime? LastUpdated { get; protected set; }
public string PreviousPublishedState { get; protected set; }
public MqttPublisher Publisher { get; protected set; }
@ -74,6 +74,7 @@ namespace hass_workstation_service.Domain.Commands
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain, true);
}
public abstract void Execute();
public abstract void TurnOn();
public abstract void TurnOff();
}
}

@ -1,6 +1,8 @@
using hass_workstation_service.Communication;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -10,39 +12,64 @@ namespace hass_workstation_service.Domain.Commands
public class CustomCommand : AbstractCommand
{
public string Command { get; protected set; }
public string State { get; protected set; }
public Process Process { get; private set; }
public CustomCommand(MqttPublisher publisher, string command, string name = "Custom", Guid id = default(Guid)) : base(publisher, name ?? "Custom", id)
{
this.Command = command;
this.State = "OFF";
}
public override void Execute()
public override async void TurnOn()
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
this.State = "ON";
this.Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.CreateNoWindow = true;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/C {this.Command}";
process.StartInfo = startInfo;
process.Start();
this.Process.StartInfo = startInfo;
try
{
this.Process.Start();
}
catch (Exception e)
{
Log.Logger.Error($"Sensor {this.Name} failed", e);
this.State = "FAILED";
}
while (!this.Process.HasExited)
{
await Task.Delay(1000);
}
this.State = "OFF";
}
public override CommandDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return new CommandDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Device = this.Publisher.DeviceConfigModel,
Expire_after = 60
};
}
public override string GetState()
{
return "off";
return this.State;
}
public override void TurnOff()
{
this.Process.Kill();
}
}
}

@ -0,0 +1,17 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class LogOffCommand : CustomCommand
{
public LogOffCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /l", name ?? "LogOff", id)
{
this.State = "OFF";
}
}
}

@ -0,0 +1,17 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class RestartCommand : CustomCommand
{
public RestartCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /r", name ?? "Restart", id)
{
this.State = "OFF";
}
}
}

@ -0,0 +1,17 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class ShutdownCommand : CustomCommand
{
public ShutdownCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /s", name ?? "Shutdown", id)
{
this.State = "OFF";
}
}
}

@ -18,8 +18,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -27,8 +27,7 @@ namespace hass_workstation_service.Domain.Sensors
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:chart-areaspline",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -19,8 +19,7 @@ namespace hass_workstation_service.Domain.Sensors
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:speedometer",
Unit_of_measurement = "MHz",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
}

@ -21,8 +21,7 @@ namespace hass_workstation_service.Domain.Sensors
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -18,8 +18,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -22,8 +22,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -44,8 +44,7 @@ namespace hass_workstation_service.Domain.Sensors
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:memory",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
}

@ -30,8 +30,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:microphone",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -25,8 +25,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -44,8 +44,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:lock",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -18,8 +18,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:laptop",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -28,8 +28,7 @@ namespace hass_workstation_service.Domain.Sensors
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -32,8 +32,7 @@ namespace hass_workstation_service.Domain.Sensors
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:webcam",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Expire_after = 60
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -67,11 +67,10 @@ namespace hass_workstation_service
Log.CloseAndFlush();
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostContext, loggingBuilder) =>
loggingBuilder.AddSerilog(dispose: true))
.ConfigureServices((hostContext, services) =>

@ -44,7 +44,6 @@ namespace hass_workstation_service
List<AbstractSensor> sensors = _configurationService.ConfiguredSensors.ToList();
List<AbstractCommand> commands = _configurationService.ConfiguredCommands.ToList();
_mqttPublisher.AnnounceAvailability("sensor");
_mqttPublisher.AnnounceAvailability("switch");
foreach (AbstractSensor sensor in sensors)
{
sensor.PublishAutoDiscoveryConfigAsync();
@ -55,7 +54,6 @@ namespace hass_workstation_service
}
while (!stoppingToken.IsCancellationRequested)
{
sensors = _configurationService.ConfiguredSensors.ToList();
_logger.LogDebug("Worker running at: {time}", DateTimeOffset.Now);
foreach (AbstractSensor sensor in sensors)
@ -69,6 +67,18 @@ namespace hass_workstation_service
Log.Logger.Warning("Sensor failed: " + sensor.Name, ex);
}
}
foreach (AbstractCommand command in commands)
{
try
{
await command.PublishStateAsync();
}
catch (Exception ex)
{
Log.Logger.Warning("Command state failed: " + command.Name, ex);
}
}
// announce autodiscovery every 30 seconds
if (_mqttPublisher.LastConfigAnnounce < DateTime.UtcNow.AddSeconds(-30))
@ -82,7 +92,6 @@ namespace hass_workstation_service
command.PublishAutoDiscoveryConfigAsync();
}
_mqttPublisher.AnnounceAvailability("sensor");
_mqttPublisher.AnnounceAvailability("switch");
}
await Task.Delay(1000, stoppingToken);
}
@ -92,7 +101,6 @@ namespace hass_workstation_service
public override async Task StopAsync(CancellationToken stoppingToken)
{
_mqttPublisher.AnnounceAvailability("sensor", true);
_mqttPublisher.AnnounceAvailability("switch", true);
await _mqttPublisher.DisconnectAsync();
}

Loading…
Cancel
Save