diff --git a/EgwProxy.Shelly.Test/App.config b/EgwProxy.Shelly.Test/App.config
new file mode 100644
index 0000000..0b589db
--- /dev/null
+++ b/EgwProxy.Shelly.Test/App.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly.Test/EgwProxy.Shelly.Test.csproj b/EgwProxy.Shelly.Test/EgwProxy.Shelly.Test.csproj
new file mode 100644
index 0000000..dce3fb9
--- /dev/null
+++ b/EgwProxy.Shelly.Test/EgwProxy.Shelly.Test.csproj
@@ -0,0 +1,69 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {081AB7E7-4C36-49D7-9343-2C567F324B74}
+ Exe
+ EgwProxy.Shelly.Test
+ EgwProxy.Shelly.Test
+ v4.6.2
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+ {69b1068b-e53f-4e54-9a3d-1031f84889ba}
+ EgwProxy.Shelly
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly.Test/Program.cs b/EgwProxy.Shelly.Test/Program.cs
new file mode 100644
index 0000000..a087a9c
--- /dev/null
+++ b/EgwProxy.Shelly.Test/Program.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Configuration;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using EgwProxy.Shelly.Clients;
+using System.Net.Http;
+using EgwProxy.Shelly.Options;
+using System.Threading;
+
+namespace EgwProxy.Shelly.Test
+{
+ internal class Program
+ {
+
+ ///
+ /// Helper separatore dash
+ ///
+ private const string separator = "------------------------";
+
+ private static Stopwatch sw = new Stopwatch();
+ ///
+ /// legge conf in formato stringa
+ ///
+ ///
+ ///
+ protected static string ReadSetting(string key)
+ {
+ string answ = "";
+ try
+ {
+ answ = $"{ConfigurationManager.AppSettings[key]}" ?? "";
+ }
+ catch (Exception exc)
+ {
+ Console.Write("Eccezione in ReadSettings");
+ Console.Write(exc.Message);
+ }
+ return answ;
+ }
+
+ //// caffè
+ //private string shellyAddr = "10.74.81.72";
+ //APC
+ private string shellyAddr = "10.74.81.71";
+
+ ///
+ /// Programma principale
+ ///
+ ///
+ private static void Main(string[] args)
+ {
+ Console.WriteLine(separator);
+ Console.WriteLine("Test Shelly Client");
+ Console.WriteLine(separator);
+ Console.WriteLine();
+ string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
+ string BaseDirectory = System.IO.Path.GetDirectoryName(exePath);
+ string testFile = Path.Combine(BaseDirectory, "conf", ReadSetting("testFile"));
+ if (!string.IsNullOrEmpty(testFile))
+ {
+ Console.WriteLine(separator);
+ Console.WriteLine($"Mode json ({testFile})");
+ Console.WriteLine(separator);
+ Console.WriteLine();
+ if (File.Exists(testFile))
+ {
+ var rawData = File.ReadAllText(testFile);
+ if (!string.IsNullOrEmpty(rawData))
+ {
+ TestSetup testConf = new TestSetup();
+ try
+ {
+ testConf = JsonConvert.DeserializeObject(rawData);
+ }
+ catch
+ { }
+ // setup devAddr
+ Shelly1PmOptions options = new Shelly1PmOptions()
+ {
+ DefaultTimeout = TimeSpan.FromSeconds(testConf.tOutSec),
+ ServerUri = new Uri($"http://{testConf.devAddr}/rpc")
+ };
+ Shelly1PmClient shelly = new Shelly1PmClient(new HttpClient(), options);
+ // test chiamata completa
+ serverTest(shelly);
+ bool doRepeat = true;
+ while (doRepeat)
+ {
+ // eseguo per ogni step
+ foreach (var item in testConf.steps)
+ {
+ // ripeto per il num di volte richieste...
+ for (int i = 0; i < item.numRep; i++)
+ {
+ Console.WriteLine(separator);
+ Console.WriteLine($"Rep: {i + 1}");
+ Console.WriteLine(separator);
+ string esitoStep = "";
+ sw.Restart();
+ switch (item.action)
+ {
+ case stepType.getFullStatus:
+ var respFull = Task.Run(() => shelly.GetStatus(CancellationToken.None)).Result;
+ if (respFull.IsSuccess)
+ {
+ esitoStep = JsonConvert.SerializeObject(respFull.Value, Formatting.Indented);
+ }
+ else
+ {
+ esitoStep = "Errore in GetStatus";
+ }
+ break;
+ case stepType.getSwitchStatus:
+ var respSwitch = Task.Run(() => shelly.GetSwitchStatus(CancellationToken.None, 0)).Result;
+ if (respSwitch.IsSuccess)
+ {
+ esitoStep = JsonConvert.SerializeObject(respSwitch.Value, Formatting.Indented);
+ }
+ else
+ {
+ esitoStep = "Errore in GetSwitchStatus";
+ }
+ break;
+ default:
+ esitoStep = "Action not managed: skipping";
+ break;
+ }
+ sw.Stop();
+ Console.WriteLine(esitoStep);
+ Console.WriteLine(separator);
+ Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:N3}ms");
+ Console.WriteLine(separator);
+ Console.WriteLine();
+ Thread.Sleep(item.waitTime);
+ }
+ Console.WriteLine($"------ Done Step {item.id} ------");
+ }
+
+ Console.WriteLine("Do you want to repeat from the beginnning? esc to close");
+ ConsoleKeyInfo answ = Console.ReadKey();
+ doRepeat = answ.Key != ConsoleKey.Escape;
+ }
+ }
+ }
+ }
+ }
+
+ private static void serverTest(Shelly1PmClient shellyClient)
+ {
+ Console.WriteLine(separator);
+ // registro ed eseguo chiamata in modalità sincrona
+ sw.Restart();
+ var response = Task.Run(() => shellyClient.GetStatus(CancellationToken.None)).Result;
+ sw.Stop();
+ Console.WriteLine($"CallSuccess: {response.IsSuccess}");
+ if (response.Value != null)
+ {
+ Console.WriteLine(separator);
+ Console.WriteLine(JsonConvert.SerializeObject(response.Value, Formatting.Indented));
+ Console.WriteLine(separator);
+ Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:N3}ms");
+ Console.WriteLine(separator);
+ Console.WriteLine();
+ }
+ }
+ }
+}
diff --git a/EgwProxy.Shelly.Test/Properties/AssemblyInfo.cs b/EgwProxy.Shelly.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..eeaef9d
--- /dev/null
+++ b/EgwProxy.Shelly.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("EgwProxy.Shelly.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("EgwProxy.Shelly.Test")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("081ab7e7-4c36-49d7-9343-2c567f324b74")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/EgwProxy.Shelly.Test/TestSetup.cs b/EgwProxy.Shelly.Test/TestSetup.cs
new file mode 100644
index 0000000..b617b7c
--- /dev/null
+++ b/EgwProxy.Shelly.Test/TestSetup.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Test
+{
+ public class TestSetup
+ {
+ public string devAddr { get; set; } = "";
+ public int tOutSec{ get; set; } = 5;
+ public List steps { get; set; }
+ }
+
+ public class singleStep
+ {
+ public string id { get; set; } = "00";
+ public string description { get; set; } = "00";
+ public stepType action { get; set; } = stepType.none;
+
+ public int numRep { get; set; } = 1;
+
+ public int waitTime { get; set; } = 1000;
+
+ public List paramList { get; set; } = new List();
+ }
+
+ public enum stepType
+ {
+ none,
+ getFullStatus,
+ getSwitchStatus
+ }
+
+}
diff --git a/EgwProxy.Shelly.Test/conf/.placeholder b/EgwProxy.Shelly.Test/conf/.placeholder
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/EgwProxy.Shelly.Test/conf/.placeholder
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly.Test/conf/testSetup.json b/EgwProxy.Shelly.Test/conf/testSetup.json
new file mode 100644
index 0000000..f8be26c
--- /dev/null
+++ b/EgwProxy.Shelly.Test/conf/testSetup.json
@@ -0,0 +1,13 @@
+{
+ "devAddr": "10.74.81.71",
+ "steps": [
+ {
+ "id": "01",
+ "description": "Test SwitchStatus",
+ "action": "getSwitchStatus",
+ "numRep": 5,
+ "waitTime": 500,
+ "paramList": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/EgwProxy.Shelly.Test/packages.config b/EgwProxy.Shelly.Test/packages.config
new file mode 100644
index 0000000..6f243a9
--- /dev/null
+++ b/EgwProxy.Shelly.Test/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly/Clients/IShelly1.cs b/EgwProxy.Shelly/Clients/IShelly1.cs
new file mode 100644
index 0000000..516da82
--- /dev/null
+++ b/EgwProxy.Shelly/Clients/IShelly1.cs
@@ -0,0 +1,12 @@
+using EgwProxy.Shelly.DTO;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Clients
+{
+ public interface IShelly1
+ {
+ Task> GetStatus(CancellationToken cancellationToken, TimeSpan? timeout = null);
+ }
+}
diff --git a/EgwProxy.Shelly/Clients/IShelly1Pm.cs b/EgwProxy.Shelly/Clients/IShelly1Pm.cs
new file mode 100644
index 0000000..3913879
--- /dev/null
+++ b/EgwProxy.Shelly/Clients/IShelly1Pm.cs
@@ -0,0 +1,12 @@
+using EgwProxy.Shelly.DTO;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Clients
+{
+ public interface IShelly1Pm
+ {
+ Task> GetStatus(CancellationToken cancellationToken, TimeSpan? timeout = null);
+ }
+}
diff --git a/EgwProxy.Shelly/Clients/Shelly1Client.cs b/EgwProxy.Shelly/Clients/Shelly1Client.cs
new file mode 100644
index 0000000..598363c
--- /dev/null
+++ b/EgwProxy.Shelly/Clients/Shelly1Client.cs
@@ -0,0 +1,24 @@
+using EgwProxy.Shelly.DTO;
+using EgwProxy.Shelly.Options;
+using Flurl;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Clients
+{
+ public class Shelly1Client : ShellyClientBase, IShelly1
+ {
+ public Shelly1Client(HttpClient httpClient, Shelly1Options shelly1Options) : base(httpClient, shelly1Options)
+ {
+ }
+
+ public async Task> GetStatus(CancellationToken cancellationToken, TimeSpan? timeout = null)
+ {
+ var endpoint = ServerUri.AppendPathSegment("status");
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, endpoint);
+ return await ExecuteRequestAsync(requestMessage, cancellationToken, timeout);
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/Clients/Shelly1PmClient.cs b/EgwProxy.Shelly/Clients/Shelly1PmClient.cs
new file mode 100644
index 0000000..269c4e8
--- /dev/null
+++ b/EgwProxy.Shelly/Clients/Shelly1PmClient.cs
@@ -0,0 +1,31 @@
+using EgwProxy.Shelly.DTO;
+using EgwProxy.Shelly.Options;
+using Flurl;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Clients
+{
+ public class Shelly1PmClient : ShellyClientBase, IShelly1Pm
+ {
+ public Shelly1PmClient(HttpClient httpClient, Shelly1PmOptions shellyOptions) : base(httpClient, shellyOptions)
+ {
+ }
+
+ public async Task> GetStatus(CancellationToken cancellationToken, TimeSpan? timeout = null)
+ {
+ var endpoint = ServerUri.AppendPathSegment("Shelly.GetStatus");
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, endpoint);
+ return await ExecuteRequestAsync(requestMessage, cancellationToken, timeout);
+ }
+
+ public async Task> GetSwitchStatus(CancellationToken cancellationToken, int id, TimeSpan? timeout = null)
+ {
+ var endpoint = ServerUri.AppendPathSegment("Switch.GetStatus").AppendQueryParam("id",id);
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, endpoint);
+ return await ExecuteRequestAsync(requestMessage, cancellationToken, timeout);
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/Clients/ShellyClientBase.cs b/EgwProxy.Shelly/Clients/ShellyClientBase.cs
new file mode 100644
index 0000000..f1e56ae
--- /dev/null
+++ b/EgwProxy.Shelly/Clients/ShellyClientBase.cs
@@ -0,0 +1,84 @@
+using EgwProxy.Shelly.Options;
+using Newtonsoft.Json;
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Clients
+{
+ public abstract class ShellyClientBase
+ {
+ protected readonly HttpClient ShellyHttpClient;
+ private readonly IShellyCommonOptions _shellyCommonOptions;
+ protected readonly Uri ServerUri;
+
+ protected TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5);
+
+ public ShellyClientBase(HttpClient httpClient, IShellyCommonOptions shellyCommonOptions)
+ {
+ if (httpClient == null) throw new ArgumentNullException(nameof(httpClient));
+ if (shellyCommonOptions == null) throw new ArgumentNullException(nameof(shellyCommonOptions));
+
+
+ ShellyHttpClient = httpClient;
+ _shellyCommonOptions = shellyCommonOptions;
+ ServerUri = shellyCommonOptions.ServerUri;
+
+ if (shellyCommonOptions.DefaultTimeout.HasValue)
+ {
+ DefaultTimeout = shellyCommonOptions.DefaultTimeout.Value;
+ }
+ }
+
+ protected async Task> ExecuteRequestAsync(HttpRequestMessage httpRequestMessage,
+ CancellationToken cancellationToken, TimeSpan? timeout = null)
+ {
+ timeout = timeout ?? DefaultTimeout;
+
+ using (var timeoutTokenSource = new CancellationTokenSource(timeout.Value))
+ {
+ var authenticationString = $"{_shellyCommonOptions.UserName}:{_shellyCommonOptions.Password}";
+ var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString));
+
+ httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
+
+ var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, cancellationToken);
+ var response = await ShellyHttpClient.SendAsync(httpRequestMessage, linkedTokenSource.Token);
+
+ if (response.StatusCode == 0)
+ {
+ // Status code of 0 means timeout reached
+ return ShellyResult.TransientFailure("Device did not respond within timeout period");
+ }
+
+ if (response.StatusCode == HttpStatusCode.ServiceUnavailable)
+ {
+ return ShellyResult.TransientFailure("Device responded with ServiceUnavailable");
+ }
+
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ {
+ return ShellyResult.Success(default, "Device responded with NotFound");
+ }
+
+ if (response.StatusCode != HttpStatusCode.OK)
+ {
+ return ShellyResult.Failure($"Device responded with http status code {response.StatusCode}");
+ }
+
+ return await HandleOkResponse(response);
+ }
+ }
+
+ protected virtual async Task> HandleOkResponse(HttpResponseMessage response)
+ {
+ var readAsStringAsync = await response.Content.ReadAsStringAsync();
+ var shelly1Status = JsonConvert.DeserializeObject(readAsStringAsync);
+ return ShellyResult.Success(shelly1Status);
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/BaseServiceDto.cs b/EgwProxy.Shelly/DTO/BaseServiceDto.cs
new file mode 100644
index 0000000..bb026c7
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/BaseServiceDto.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO.Shelly1PM
+{
+ public class BaseServiceDto
+ {
+ ///
+ /// Connection State
+ ///
+ [JsonProperty("connected")]
+ public bool Connected { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/CloudDto.cs b/EgwProxy.Shelly/DTO/CloudDto.cs
new file mode 100644
index 0000000..85d285f
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/CloudDto.cs
@@ -0,0 +1,14 @@
+using EgwProxy.Shelly.DTO.Shelly1PM;
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ public class CloudDto :BaseServiceDto
+ {
+ ///
+ /// Define if service is enabled
+ ///
+ [JsonProperty("enabled")]
+ public bool Enabled { get; set; } = true;
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/Shelly1PM/EnergyDto.cs b/EgwProxy.Shelly/DTO/Shelly1PM/EnergyDto.cs
new file mode 100644
index 0000000..ecd64a0
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/Shelly1PM/EnergyDto.cs
@@ -0,0 +1,28 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO.Shelly1PM
+{
+ ///
+ /// Energy Info
+ ///
+ public class EnergyDto
+ {
+ ///
+ /// Total returned energy consumed in Watt-hours
+ ///
+ [JsonProperty("total")]
+ public double Total { get; set; }
+
+ ///
+ /// Returned energy in Milliwatt-hours for the last three complete minutes. The 0-th element indicates the counts accumulated during the minute preceding minute_ts. Present only if the device clock is synced.
+ ///
+ [JsonProperty("by_minute")]
+ public double[] LastByMinute { get; set; }
+
+ ///
+ /// Unix timestamp marking the start of the current minute (in UTC).
+ ///
+ [JsonProperty("minute_ts")]
+ public long TimeStamp { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/Shelly1PM/InputDto.cs b/EgwProxy.Shelly/DTO/Shelly1PM/InputDto.cs
new file mode 100644
index 0000000..2290d87
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/Shelly1PM/InputDto.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO.Shelly1PM
+{
+ ///
+ /// Input info
+ ///
+ public class InputDto
+ {
+ ///
+ /// Id of the Input component instance
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Current State(only for type switch, button) State of the input (null if the input instance is stateless, i.e. for type button)
+ ///
+ [JsonProperty("state")]
+ public bool State { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/Shelly1PM/SwitchDto.cs b/EgwProxy.Shelly/DTO/Shelly1PM/SwitchDto.cs
new file mode 100644
index 0000000..b230e3c
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/Shelly1PM/SwitchDto.cs
@@ -0,0 +1,67 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO.Shelly1PM
+{
+ public class SwitchDto
+ {
+ ///
+ /// Id of the Switch component instance
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; } = 0;
+
+ ///
+ /// Source of the last command, for example: init, WS_in, http, ...
+ ///
+ [JsonProperty("source")]
+ public string Source { get; set; } = "";
+
+ ///
+ /// Output status: rue if the output channel is currently on, false otherwise
+ ///
+ [JsonProperty("output")]
+ public bool Output { get; set; }
+
+ ///
+ /// Last measured instantaneous active power(in Watts) delivered to the attached load(shown if applicable)
+ ///
+ [JsonProperty("apower")]
+ public double Power { get; set; }
+
+ ///
+ /// Last measured voltage in Volts (shown if applicable)
+ ///
+ [JsonProperty("voltage")]
+ public double Voltage { get; set; }
+
+ ///
+ /// Last measured current in Amperes (shown if applicable)
+ ///
+ [JsonProperty("current")]
+ public double Current { get; set; }
+
+ ///
+ /// Information about the active energy counter (shown if applicable)
+ ///
+ [JsonProperty("aenergy")]
+ public EnergyDto ActEnergy { get; set; }
+
+ ///
+ /// Information about the returned active energy counter * (shown if applicable)
+ ///
+ [JsonProperty("ret_aenergy")]
+ public EnergyDto RetEnergy { get; set; }
+
+ ///
+ /// Information about the temperature (shown if applicable)
+ ///
+ [JsonProperty("temperature")]
+ public TemperatureDto Temperature { get; set; }
+
+ ///
+ /// Error conditions occurred. May contain overtemp, overpower, overvoltage, undervoltage, (shown if at least one error is present)
+ ///
+ [JsonProperty("errors")]
+ public string[] Errors { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/Shelly1PmStatusDto.cs b/EgwProxy.Shelly/DTO/Shelly1PmStatusDto.cs
new file mode 100644
index 0000000..9ae4b6a
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/Shelly1PmStatusDto.cs
@@ -0,0 +1,25 @@
+using EgwProxy.Shelly.DTO.Shelly1PM;
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ ///
+ /// Extended information for the Shelly1PM
+ /// https://shelly-api-docs.shelly.cloud/gen2/Devices/Gen3/Shelly1PMG3
+ ///
+ public class Shelly1PmStatusDto : ShellyGen2StatusDto
+ {
+ ///
+ /// Input channel
+ ///
+ [JsonProperty("input:0")]
+ public InputDto Input { get; set; }
+
+ ///
+ /// Switch 0 data
+ ///
+ [JsonProperty("switch:0")]
+ public SwitchDto Switch { get; set; }
+
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/ShellyGen2StatusDto.cs b/EgwProxy.Shelly/DTO/ShellyGen2StatusDto.cs
new file mode 100644
index 0000000..30bf2a1
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/ShellyGen2StatusDto.cs
@@ -0,0 +1,44 @@
+using EgwProxy.Shelly.DTO.Shelly1PM;
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ ///
+ /// Shelly Gen2 device status data
+ /// https://shelly-api-docs.shelly.cloud/gen2/#status
+ ///
+ public class ShellyGen2StatusDto
+ {
+
+ ///
+ /// WiFi data
+ ///
+ [JsonProperty("wifi")]
+ public WifiDto WiFiStatus { get; set; }
+
+ ///
+ /// Shelly Cloud state
+ ///
+ [JsonProperty("cloud")]
+ public CloudDto ShellyCloud { get; set; }
+
+ ///
+ /// MQTT queue state
+ ///
+ [JsonProperty("mqtt")]
+ public BaseServiceDto MQTT { get; set; }
+
+ ///
+ /// Sys Info data
+ ///
+ [JsonProperty("sys")]
+ public SysDto SysInfo { get; set; }
+
+ ///
+ /// WS state
+ ///
+ [JsonProperty("ws")]
+ public BaseServiceDto WebSocket { get; set; }
+
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/SysDto.cs b/EgwProxy.Shelly/DTO/SysDto.cs
new file mode 100644
index 0000000..75b4302
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/SysDto.cs
@@ -0,0 +1,101 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ ///
+ /// System Info
+ ///
+ public class SysDto
+ {
+ ///
+ /// Mac address of the device
+ ///
+ [JsonProperty("mac")]
+ public string Mac { get; set; } = "";
+
+ ///
+ /// True if restart is required, false otherwise
+ ///
+ [JsonProperty("restart_required")]
+ public bool RestartRequired { get; set; } = false;
+
+ ///
+ /// Current time in the format HH:MM (24-hour time format in the current timezone with leading zero). null when time is not synced from NTP server.
+ ///
+ [JsonProperty("time")]
+ public string Time { get; set; } = "";
+
+ ///
+ /// Unix timestamp (in UTC), null when time is not synced from NTP server.
+ ///
+ [JsonProperty("unixtime")]
+ public int UnixTime { get; set; } = 0;
+
+ ///
+ /// Time in seconds since last reboot
+ ///
+ [JsonProperty("uptime")]
+ public int Uptime { get; set; } = 0;
+
+ ///
+ /// Total size of the RAM in the system in Bytes
+ ///
+ [JsonProperty("ram_size")]
+ public int RamSize { get; set; } = 0;
+
+ ///
+ /// Size of the free RAM in the system in Bytes
+ ///
+ [JsonProperty("ram_free")]
+ public int RamFree { get; set; } = 0;
+
+ ///
+ /// Total size of the file system in Bytes
+ ///
+ [JsonProperty("fs_size")]
+ public int FSSize { get; set; } = 0;
+
+ ///
+ /// Size of the free file system in Bytes
+ ///
+ [JsonProperty("fs_free")]
+ public int FSFree { get; set; } = 0;
+
+ ///
+ /// Configuration revision number
+ ///
+ [JsonProperty("cfg_rev")]
+ public int CfgRev { get; set; } = 0;
+
+ ///
+ /// KVS (Key-Value Store) revision number
+ ///
+ [JsonProperty("kvs_rev")]
+ public int KvsRev { get; set; } = 0;
+
+ ///
+ /// Schedules revision number, present if schedules are enabled
+ ///
+ [JsonProperty("schedule_rev")]
+ public int ScheduleRev { get; set; } = 0;
+
+ ///
+ /// Webhooks revision number, present if webhooks are enabled
+ ///
+ [JsonProperty("webhook_rev")]
+ public int WebhookRev { get; set; } = 0;
+
+ ///
+ /// Webhooks revision number, present if webhooks are enabled
+ ///
+ [JsonProperty("available_updates")]
+ public UpdateDto AvailUpdates { get; set; }
+
+
+ ///
+ /// Information about reset type and cause
+ ///
+ [JsonProperty("reset_reason")]
+ public int ResetReason { get; set; } = 0;
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/TemperatureDto.cs b/EgwProxy.Shelly/DTO/TemperatureDto.cs
new file mode 100644
index 0000000..48401f8
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/TemperatureDto.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ public class TemperatureDto
+ {
+ ///
+ /// Temperature in Celsius (null if the temperature is out of the measurement range)
+ ///
+ [JsonProperty("tC")]
+ public double? Celsius { get; set; }
+
+ ///
+ /// Temperature in Fahrenheit (null if the temperature is out of the measurement range)
+ ///
+ [JsonProperty("tF")]
+ public double? Fahrenheit { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/UpdateDto.cs b/EgwProxy.Shelly/DTO/UpdateDto.cs
new file mode 100644
index 0000000..aa6066a
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/UpdateDto.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ ///
+ /// Info about sw update
+ ///
+ public class UpdateDto
+ {
+ ///
+ /// Shown only if beta update is available
+ ///
+ [JsonProperty("beta")]
+ public VersDto Beta { get; set; }
+
+ ///
+ /// Shown only if beta update is available
+ ///
+ [JsonProperty("stable")]
+ public VersDto Stable { get; set; }
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/VersDto.cs b/EgwProxy.Shelly/DTO/VersDto.cs
new file mode 100644
index 0000000..a93ff5f
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/VersDto.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ public class VersDto
+ {
+ ///
+ /// Version of the new firmware
+ ///
+ [JsonProperty("version")]
+ public string Version { get; set; } = "";
+ }
+}
diff --git a/EgwProxy.Shelly/DTO/WifiDto.cs b/EgwProxy.Shelly/DTO/WifiDto.cs
new file mode 100644
index 0000000..1ced5ca
--- /dev/null
+++ b/EgwProxy.Shelly/DTO/WifiDto.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json;
+
+namespace EgwProxy.Shelly.DTO
+{
+ ///
+ /// WiFi status info
+ ///
+ public class WifiDto
+ {
+ ///
+ /// Ip of the device in the network (null if disconnected)
+ ///
+ [JsonProperty("sta_ip")]
+ public string StaIp { get; set; } = "";
+
+ ///
+ /// Status of the connection. Range of values: disconnected, connecting, connected, got ip
+ ///
+ [JsonProperty("status")]
+ public string Status { get; set; } = "";
+
+ ///
+ /// SSID of the network (null in case of hidden network)
+ ///
+ [JsonProperty("ssid")]
+ public string SSID { get; set; } = "";
+
+ ///
+ /// Strength of the signal in dBms
+ ///
+ [JsonProperty("rssi")]
+ public int RSSI { get; set; } = 0;
+
+ ///
+ /// Number of clients connected to the access point. Present only when AP is enabled and range extender functionality is present and enabled.
+ ///
+ [JsonProperty("ap_client_count")]
+ public int NumClients { get; set; } = 0;
+ }
+}
diff --git a/EgwProxy.Shelly/EgwProxy.Shelly.csproj b/EgwProxy.Shelly/EgwProxy.Shelly.csproj
new file mode 100644
index 0000000..9b14bd4
--- /dev/null
+++ b/EgwProxy.Shelly/EgwProxy.Shelly.csproj
@@ -0,0 +1,119 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {69B1068B-E53F-4E54-9A3D-1031F84889BA}
+ Library
+ Properties
+ EgwProxy.Shelly
+ EgwProxy.Shelly
+ v4.6.2
+ 512
+ true
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Flurl.4.0.0\lib\net461\Flurl.dll
+
+
+ ..\packages\Flurl.Http.4.0.2\lib\net461\Flurl.Http.dll
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+ ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+
+
+
+ ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll
+
+
+
+ ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll
+
+
+ ..\packages\System.Text.Json.6.0.4\lib\net461\System.Text.Json.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly/Options/IShellyAuthOptions.cs b/EgwProxy.Shelly/Options/IShellyAuthOptions.cs
new file mode 100644
index 0000000..1ac3d3e
--- /dev/null
+++ b/EgwProxy.Shelly/Options/IShellyAuthOptions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Options
+{
+ ///
+ /// Common authentication options across all shelly devices
+ ///
+ public interface IShellyAuthOptions
+ {
+ string UserName { get; }
+ string Password { get; }
+ }
+}
diff --git a/EgwProxy.Shelly/Options/IShellyCommonOptions.cs b/EgwProxy.Shelly/Options/IShellyCommonOptions.cs
new file mode 100644
index 0000000..9fb112a
--- /dev/null
+++ b/EgwProxy.Shelly/Options/IShellyCommonOptions.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Options
+{
+ ///
+ /// Common options across all shelly devices
+ ///
+ public interface IShellyCommonOptions : IShellyAuthOptions
+ {
+ ///
+ /// URI for the Shelly device
+ ///
+ Uri ServerUri { get; }
+ ///
+ /// Default timeout for HTTP requests if a specific timeout is not supplied
+ ///
+ TimeSpan? DefaultTimeout { get; }
+ }
+}
diff --git a/EgwProxy.Shelly/Options/Shelly1Options.cs b/EgwProxy.Shelly/Options/Shelly1Options.cs
new file mode 100644
index 0000000..fe63756
--- /dev/null
+++ b/EgwProxy.Shelly/Options/Shelly1Options.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Options
+{
+ public class Shelly1Options : IShellyCommonOptions
+ {
+ public Uri ServerUri { get; set; }
+ public TimeSpan? DefaultTimeout { get; set; }
+ public string UserName { get; set; }
+ public string Password { get; set; }
+
+ public Shelly1Options()
+ {
+ DefaultTimeout = null;
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/Options/Shelly1PmOptions.cs b/EgwProxy.Shelly/Options/Shelly1PmOptions.cs
new file mode 100644
index 0000000..41c0141
--- /dev/null
+++ b/EgwProxy.Shelly/Options/Shelly1PmOptions.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly.Options
+{
+ public class Shelly1PmOptions : IShellyCommonOptions
+ {
+ public Uri ServerUri { get; set; }
+ public TimeSpan? DefaultTimeout { get; set; }
+ public string UserName { get; set; }
+ public string Password { get; set; }
+
+ public Shelly1PmOptions()
+ {
+ DefaultTimeout = null;
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/Properties/AssemblyInfo.cs b/EgwProxy.Shelly/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ef7c2f9
--- /dev/null
+++ b/EgwProxy.Shelly/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("EgwProxy.Shelly")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("EgwProxy.Shelly")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("69b1068b-e53f-4e54-9a3d-1031f84889ba")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/EgwProxy.Shelly/ShellyResult.cs b/EgwProxy.Shelly/ShellyResult.cs
new file mode 100644
index 0000000..91c543f
--- /dev/null
+++ b/EgwProxy.Shelly/ShellyResult.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EgwProxy.Shelly
+{
+ public class ShellyResult
+ {
+ public T Value
+ {
+ get
+ {
+ if (!_successChecked)
+ {
+ throw new InvalidOperationException("Cannot access value of result without checking success first");
+ }
+
+ return _value;
+ }
+ }
+
+ ///
+ /// Indicates the client request has completed successfully
+ ///
+ public bool IsSuccess
+ {
+ get
+ {
+ _successChecked = true;
+ return _success;
+ }
+ }
+
+ ///
+ /// Indicates the client request has failed
+ ///
+ public bool IsFailure => !IsSuccess;
+
+ ///
+ /// Indicates if the reason for failure is transient
+ ///
+ public bool IsTransient { get; private set; }
+
+ ///
+ /// Any message that accompanies this result
+ ///
+ public string Message { get; }
+
+ private T _value;
+ private bool _successChecked = false;
+ private bool _success = false;
+
+ private ShellyResult(T value, bool success, bool isTransient, string message = null)
+ {
+ _value = value;
+ _success = success;
+ IsTransient = isTransient;
+ Message = message;
+ }
+
+ public static ShellyResult Success(T value, string message = null)
+ {
+ return new ShellyResult(value, true, false, message);
+ }
+
+ public static ShellyResult Failure(string message = null)
+ {
+ return new ShellyResult(default, success: false, isTransient: false, message);
+ }
+
+ public static ShellyResult TransientFailure(string message = null)
+ {
+ return new ShellyResult(default, success: false, isTransient: true, message);
+ }
+ }
+}
diff --git a/EgwProxy.Shelly/app.config b/EgwProxy.Shelly/app.config
new file mode 100644
index 0000000..1696df6
--- /dev/null
+++ b/EgwProxy.Shelly/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly/docfx.json b/EgwProxy.Shelly/docfx.json
new file mode 100644
index 0000000..435e349
--- /dev/null
+++ b/EgwProxy.Shelly/docfx.json
@@ -0,0 +1,69 @@
+{
+ "metadata": [
+ {
+ "src": [
+ {
+ "files": [
+ "*.csproj",
+ "*.vbproj"
+ ],
+ "cwd": ".",
+ "exclude": [
+ "**/obj/**",
+ "**/bin/**",
+ "_site/**"
+ ]
+ }
+ ],
+ "dest": "obj/api"
+ }
+ ],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "api/**.yml"
+ ],
+ "cwd": "obj"
+ },
+ {
+ "files": [
+ "api/*.md",
+ "articles/**.md",
+ "toc.yml",
+ "*.md"
+ ],
+ "exclude": [
+ "obj/**",
+ "_site/**"
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "files": [
+ "images/**"
+ ],
+ "exclude": [
+ "obj/**",
+ "_site/**"
+ ]
+ }
+ ],
+ "overwrite": [
+ {
+ "files": [
+ "apidoc/**.md"
+ ],
+ "exclude": [
+ "obj/**",
+ "_site/**"
+ ]
+ }
+ ],
+ "dest": "_site",
+ "template": [
+ "default"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/EgwProxy.Shelly/index.md b/EgwProxy.Shelly/index.md
new file mode 100644
index 0000000..63cb151
--- /dev/null
+++ b/EgwProxy.Shelly/index.md
@@ -0,0 +1,19 @@
+# EgwProxy.Shelly Library
+
+Documentazione relativa alla libreria di interfaccia con sistemi Shelly x controllo domotico e del consumo energetico.
+
+Disponibile in forma di pacchetto nuget sul repo aziendale nexus.steamware.net: i pacchetti sono disponibili all'indirizzo
+
+https://nexus.steamware.net/#browse/browse:nuget-hosted
+
+Vedere la sezione Articles per maggiori informazioni sulle definizioni, l'impiego ed esempi.
+
+L'oggetot id base è **EgwProxy.Shelly.Clients.* **
+
+## Articles
+
+Per maggiori dettagli, definizioni e demo funzionamento si rimanda alla sezione Articles
+
+## Api
+
+Per ogni dettaglio e riferimento alla libreria si rimanda alla sezione Api Documentation
\ No newline at end of file
diff --git a/EgwProxy.Shelly/packages.config b/EgwProxy.Shelly/packages.config
new file mode 100644
index 0000000..45a386e
--- /dev/null
+++ b/EgwProxy.Shelly/packages.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EgwProxy.Shelly/toc.yml b/EgwProxy.Shelly/toc.yml
new file mode 100644
index 0000000..7eee9ff
--- /dev/null
+++ b/EgwProxy.Shelly/toc.yml
@@ -0,0 +1,6 @@
+
+- name: Articles
+ href: articles/
+- name: API Documentation
+ href: obj/api/
+ homepage: api/index.md