Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff6919e54f | |||
| 9e8d1ee61b | |||
| 46cb938ea2 | |||
| b144629939 | |||
| 0cf9b3a566 | |||
| 1072853fa5 | |||
| e47f9ef5b7 | |||
| d932207db0 | |||
| b225953dbe | |||
| 929b49a05c | |||
| 6ca735b574 | |||
| 216daeac33 | |||
| 7467d5ba81 | |||
| 76bbefd729 | |||
| 29329ee303 | |||
| c6888550b6 | |||
| d8c7a9e2c5 | |||
| f67d070fc3 | |||
| 86593eff22 | |||
| a5e75e7e14 | |||
| 368f0b99df | |||
| b4bf7aad20 | |||
| ab85e78f4c | |||
| 0f91751087 | |||
| e3cc6392a7 | |||
| a1e1d43a6f | |||
| bf8ddcb738 | |||
| 9e78307541 | |||
| 25891c6c9d | |||
| 54ecf641f5 | |||
| 0396b66606 | |||
| f9adda14b8 | |||
| 198a6ddebc | |||
| bc4615761c | |||
| 684393f85d | |||
| 5dc9007ad2 | |||
| 8be57fb3df | |||
| c86e649b4a | |||
| 1fbcaacd63 | |||
| 9cc08ff10b | |||
| c96e8fc6ce | |||
| e64979e42f | |||
| 35e468e0b0 | |||
| 46f914fe4d | |||
| fa8608b44b | |||
| 73855e246c | |||
| ed635c5ec2 | |||
| 6d7a9969cd | |||
| b02114f0f9 | |||
| f03de89815 | |||
| a1944a1203 | |||
| aa20f9d466 | |||
| 18b9a97b91 | |||
| ea67a8069f | |||
| d81bf5d8ec | |||
| d7d60f30af | |||
| 59c146c45f | |||
| 1c49b5150a | |||
| 6b7ee73597 | |||
| 4eff8e32d8 | |||
| 233523ca4d | |||
| faa54aca0e | |||
| 58fbaf4e35 | |||
| 268e04a710 | |||
| 7e7109d609 | |||
| 9af2bf392d |
93
PlantBox.Broker/Broker.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class Broker
|
||||
{
|
||||
public PlantBoxesManager PlantBoxesManager { get; }
|
||||
public ClientManager ClientManager { get; }
|
||||
public ServerManager ServerManager { get; }
|
||||
|
||||
public bool IsRunning { get; set; } = true;
|
||||
|
||||
private Timer _autoSaveTimer;
|
||||
|
||||
public Broker(string[] args)
|
||||
{
|
||||
Console.WriteLine("Initializing Broker...");
|
||||
|
||||
PlantBoxesManager = new PlantBoxesManager();
|
||||
|
||||
PlantBoxesManager.Load();
|
||||
PlantBoxesManager.UpdatePlantsState();
|
||||
|
||||
ClientManager = new ClientManager(this);
|
||||
ServerManager = new ServerManager(this);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(() => ClientManager.Start());
|
||||
Task.Run(() => ServerManager.Start());
|
||||
|
||||
// Auto-Save
|
||||
_autoSaveTimer = new Timer(OnSave, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
|
||||
|
||||
string input;
|
||||
do
|
||||
{
|
||||
input = Console.ReadLine().ToLowerInvariant();
|
||||
|
||||
string[] split = input.Split(" ");
|
||||
input = split[0];
|
||||
string[] args = split.Skip(1).ToArray();
|
||||
|
||||
ExeCommand(input.ToLower(), args);
|
||||
} while (input != "exit" && input != "stop" && input != "quit");
|
||||
|
||||
Console.WriteLine("Stopping Broker...");
|
||||
|
||||
IsRunning = false;
|
||||
_autoSaveTimer.Dispose();
|
||||
PlantBoxesManager.Save();
|
||||
}
|
||||
|
||||
private void OnSave(object state)
|
||||
{
|
||||
PlantBoxesManager.Save();
|
||||
}
|
||||
|
||||
private void ExeCommand(string command, string[] arguments)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "save":
|
||||
PlantBoxesManager.Save();
|
||||
break;
|
||||
case "clear":
|
||||
Console.WriteLine($"Clearing {arguments[0]}...");
|
||||
if (ulong.TryParse(arguments[0], out ulong id) && PlantBoxesManager[id] != null)
|
||||
{
|
||||
PlantBoxesManager.ClearPlantBox(PlantBoxesManager[id]);
|
||||
Console.WriteLine($"{id} cleared");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Invalid id");
|
||||
}
|
||||
break;
|
||||
case "list":
|
||||
foreach (var plant in PlantBoxesManager)
|
||||
{
|
||||
Console.WriteLine(plant);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
PlantBox.Broker/CaptorsValue.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class CaptorsValue
|
||||
{
|
||||
public double Humidity { get; set; }
|
||||
public double Luminosity { get; set; }
|
||||
public double Temperature { get; set; }
|
||||
|
||||
public CaptorsValue()
|
||||
{
|
||||
|
||||
}
|
||||
public CaptorsValue(double humidity, double luminosity, double temperature)
|
||||
{
|
||||
Humidity = humidity;
|
||||
Luminosity = luminosity;
|
||||
Temperature = temperature;
|
||||
}
|
||||
public CaptorsValue((double humidity, double luminosity, double temperature) tuple) : this(tuple.humidity, tuple.luminosity, tuple.temperature)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
145
PlantBox.Broker/ClientManager.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class ClientManager : TcpManager
|
||||
{
|
||||
public ClientManager(Broker broker) : base(broker)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string LogPrefix => "Client";
|
||||
protected override int ListeningPort => Connection.TCP_CLIENT_PORT;
|
||||
|
||||
protected override void CaptorsCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
CaptorsRequest captorsRequest = new CaptorsRequest().Deserialize(packet.Arguments);
|
||||
ulong id = packet.ID;
|
||||
PlantBox plantBox = Broker.PlantBoxesManager[id];
|
||||
|
||||
CaptorsResponse response;
|
||||
|
||||
if (plantBox == null)
|
||||
{
|
||||
response = new CaptorsResponse(0, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = new CaptorsResponse(plantBox.Humidity.Value, plantBox.Luminosity.Value, plantBox.Temperature.Value, plantBox.TankLevel);
|
||||
}
|
||||
|
||||
commandStream.Send(response.ToCommandPacket(id));
|
||||
}
|
||||
|
||||
protected override void HistoricCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
double[] FillArray(double[] values, int length)
|
||||
{
|
||||
// Check if need
|
||||
if (values.Length >= length)
|
||||
{
|
||||
return values.Reverse().Take(length).Reverse().ToArray();
|
||||
}
|
||||
|
||||
var array = new double[length];
|
||||
|
||||
if (values.Length > 0)
|
||||
{
|
||||
int startIndex = length - values.Length;
|
||||
|
||||
values.CopyTo(array, startIndex);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
HistoricRequest historicRequest = new HistoricRequest().Deserialize(packet.Arguments);
|
||||
ulong id = packet.ID;
|
||||
PlantBox plantBox = Broker.PlantBoxesManager[id];
|
||||
int count = historicRequest.Number;
|
||||
|
||||
HistoricResponse response;
|
||||
|
||||
if (plantBox == null)
|
||||
{
|
||||
response = new HistoricResponse(TimeSpan.MinValue, Array.Empty<double>(), Array.Empty<double>(), Array.Empty<double>());
|
||||
}
|
||||
else
|
||||
{
|
||||
HistoricManager historic = plantBox.HistoricManager;
|
||||
|
||||
IReadOnlyList<CaptorsValue> captorsValues;
|
||||
switch (historicRequest.Interval)
|
||||
{
|
||||
case HistoricInterval.Minutely:
|
||||
captorsValues = historic.MinutesHistoric;
|
||||
break;
|
||||
case HistoricInterval.Hourly:
|
||||
captorsValues = historic.HoursHistoric;
|
||||
break;
|
||||
case HistoricInterval.Daily:
|
||||
captorsValues = historic.DaysHistoric;
|
||||
break;
|
||||
case HistoricInterval.Weekly:
|
||||
captorsValues = historic.WeeksHistoric;
|
||||
break;
|
||||
case HistoricInterval.Monthly:
|
||||
captorsValues = historic.MonthsHistoric;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("How did you just got here? Even 『 』can't");
|
||||
}
|
||||
|
||||
response = new HistoricResponse
|
||||
(
|
||||
DateTime.Now - plantBox.LastMeasureDate,
|
||||
FillArray(captorsValues.Select(x => x.Humidity).ToArray(), count),
|
||||
FillArray(captorsValues.Select(x => x.Luminosity).ToArray(), count),
|
||||
FillArray(captorsValues.Select(x => x.Temperature).ToArray(), count)
|
||||
);
|
||||
}
|
||||
|
||||
commandStream.Send(response.ToCommandPacket(id));
|
||||
}
|
||||
|
||||
protected override void InfoCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
InfoRequest infoRequest = new InfoRequest().Deserialize(packet.Arguments);
|
||||
ulong id = packet.ID;
|
||||
PlantBox plantBox = Broker.PlantBoxesManager[id];
|
||||
|
||||
InfoResponse response;
|
||||
|
||||
if (plantBox == null)
|
||||
{
|
||||
response = new InfoResponse("", default, default, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
plantBox.UpdateState();
|
||||
|
||||
response = new InfoResponse
|
||||
(
|
||||
plantBox.Name, plantBox.Type, plantBox.State,
|
||||
plantBox.Humidity.Min, plantBox.Humidity.Max,
|
||||
plantBox.Luminosity.Min, plantBox.Luminosity.Max,
|
||||
plantBox.Temperature.Min, plantBox.Temperature.Max
|
||||
);
|
||||
}
|
||||
commandStream.Send(response.ToCommandPacket(id));
|
||||
}
|
||||
|
||||
protected override void PingCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
var ping = new PingCommand().Deserialize(packet.Arguments);
|
||||
|
||||
commandStream.Send(ping.ToCommandPacket(packet.ID));
|
||||
}
|
||||
}
|
||||
}
|
||||
14
PlantBox.Broker/Extensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static IEnumerable<T> TakeFromEnd<T>(this List<T> list, int count)
|
||||
{
|
||||
return list.GetRange(list.Count - count, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
PlantBox.Broker/HistoricManager.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class HistoricManager
|
||||
{
|
||||
[JsonProperty(PropertyName = nameof(MinutesHistoric))]
|
||||
private List<CaptorsValue> _minutesHistoric;
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CaptorsValue> MinutesHistoric => _minutesHistoric.AsReadOnly();
|
||||
|
||||
[JsonProperty(PropertyName = nameof(HoursHistoric))]
|
||||
private List<CaptorsValue> _hoursHistoric;
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CaptorsValue> HoursHistoric => _hoursHistoric.AsReadOnly();
|
||||
|
||||
[JsonProperty(PropertyName = nameof(DaysHistoric))]
|
||||
private List<CaptorsValue> _daysHistoric;
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CaptorsValue> DaysHistoric => _daysHistoric.AsReadOnly();
|
||||
|
||||
[JsonProperty(PropertyName = nameof(WeeksHistoric))]
|
||||
private List<CaptorsValue> _weeksHistoric;
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CaptorsValue> WeeksHistoric => _weeksHistoric.AsReadOnly();
|
||||
|
||||
[JsonProperty(PropertyName = nameof(MonthsHistoric))]
|
||||
private List<CaptorsValue> _monthsHistoric;
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<CaptorsValue> MonthsHistoric => _monthsHistoric.AsReadOnly();
|
||||
|
||||
public HistoricManager()
|
||||
{
|
||||
_minutesHistoric = new List<CaptorsValue>();
|
||||
_hoursHistoric = new List<CaptorsValue>();
|
||||
_daysHistoric = new List<CaptorsValue>();
|
||||
_weeksHistoric = new List<CaptorsValue>();
|
||||
_monthsHistoric = new List<CaptorsValue>();
|
||||
}
|
||||
public HistoricManager(List<CaptorsValue> minutesHistoric, List<CaptorsValue> hoursHistoric, List<CaptorsValue> daysHistoric, List<CaptorsValue> weeksHistoric, List<CaptorsValue> monthsHistoric)
|
||||
{
|
||||
_minutesHistoric = minutesHistoric;
|
||||
_hoursHistoric = hoursHistoric;
|
||||
_daysHistoric = daysHistoric;
|
||||
_weeksHistoric = weeksHistoric;
|
||||
_monthsHistoric = monthsHistoric;
|
||||
}
|
||||
|
||||
public void Add(CaptorsValue captorsValue)
|
||||
{
|
||||
_minutesHistoric.Add(captorsValue);
|
||||
|
||||
if (_minutesHistoric.Count % 12 == 0)
|
||||
{
|
||||
_hoursHistoric.Add(new CaptorsValue(GetAverage(_minutesHistoric.TakeFromEnd(12))));
|
||||
|
||||
if (_hoursHistoric.Count % 24 == 0)
|
||||
{
|
||||
_daysHistoric.Add(new CaptorsValue(GetAverage(_hoursHistoric.TakeFromEnd(24))));
|
||||
|
||||
if (_daysHistoric.Count % 7 == 0)
|
||||
{
|
||||
_weeksHistoric.Add(new CaptorsValue(GetAverage(_daysHistoric.TakeFromEnd(7))));
|
||||
|
||||
if (_daysHistoric.Count % 31 == 0)
|
||||
{
|
||||
_monthsHistoric.Add(new CaptorsValue(GetAverage(_daysHistoric.TakeFromEnd(31))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_minutesHistoric.Clear();
|
||||
_hoursHistoric.Clear();
|
||||
_daysHistoric.Clear();
|
||||
_weeksHistoric.Clear();
|
||||
_monthsHistoric.Clear();
|
||||
}
|
||||
|
||||
private (double humidity, double luminosity, double temperature) GetAverage(IEnumerable<CaptorsValue> captorsValues)
|
||||
{
|
||||
return
|
||||
(
|
||||
captorsValues.Average(x => x.Humidity),
|
||||
captorsValues.Average(x => x.Luminosity),
|
||||
captorsValues.Average(x => x.Temperature)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,10 @@
|
||||
<Description>Broker of the PlantBox project</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlantBox.Shared\PlantBox.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
63
PlantBox.Broker/PlantBox.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class PlantBox
|
||||
{
|
||||
// General Info
|
||||
public ulong ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public PlantType Type { get; set; }
|
||||
public PlantState State { get; set; }
|
||||
|
||||
// Captors
|
||||
public DateTime LastMeasureDate { get; set; }
|
||||
public double TankLevel { get; set; }
|
||||
public CaptorValue Humidity { get; set; }
|
||||
public CaptorValue Luminosity { get; set; }
|
||||
public CaptorValue Temperature { get; set; }
|
||||
|
||||
// Historic
|
||||
public HistoricManager HistoricManager { get; set; }
|
||||
|
||||
// State conditions
|
||||
public void UpdateState()
|
||||
{
|
||||
bool IsDay()
|
||||
{
|
||||
double hour = DateTime.Now.Hour;
|
||||
|
||||
return hour > 8 && hour < 20;
|
||||
}
|
||||
|
||||
if (Humidity.Value < Humidity.Min || Humidity.Value > Humidity.Max)
|
||||
{
|
||||
State = PlantState.Warning;
|
||||
}
|
||||
if (IsDay() && Luminosity.Value < Luminosity.Min || Luminosity.Value > Luminosity.Max)
|
||||
{
|
||||
State = PlantState.Warning;
|
||||
}
|
||||
if (Temperature.Value < Temperature.Min || Temperature.Value > Temperature.Max)
|
||||
{
|
||||
State = PlantState.Warning;
|
||||
}
|
||||
if (TankLevel < 20)
|
||||
{
|
||||
State = PlantState.Warning;
|
||||
}
|
||||
if ((DateTime.Now - LastMeasureDate).TotalMinutes >= 7)
|
||||
{
|
||||
State = PlantState.Bad;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ID}:\n Name: {Name}\n Type: {Type}\n State: {State}";
|
||||
}
|
||||
}
|
||||
}
|
||||
87
PlantBox.Broker/PlantBoxesManager.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class PlantBoxesManager : IEnumerable<PlantBox>
|
||||
{
|
||||
public string FilePath => Path.Combine(Environment.CurrentDirectory, FileName);
|
||||
public string FileName => "storage.json";
|
||||
|
||||
private Dictionary<ulong, PlantBox> _plantBoxes;
|
||||
|
||||
public PlantBoxesManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public PlantBox this[ulong id]
|
||||
{
|
||||
get => _plantBoxes.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public PlantBox GetPlantBox(ulong id) => this[id];
|
||||
|
||||
public void Add(PlantBox plantBox)
|
||||
{
|
||||
ulong id = plantBox.ID;
|
||||
if (_plantBoxes.ContainsKey(id))
|
||||
{
|
||||
_plantBoxes[id] = plantBox;
|
||||
}
|
||||
else
|
||||
{
|
||||
_plantBoxes.Add(id, plantBox);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePlantsState()
|
||||
{
|
||||
foreach (PlantBox plantBox in _plantBoxes.Values)
|
||||
{
|
||||
plantBox.UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
Console.WriteLine("Loading storage...");
|
||||
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
_plantBoxes = JsonConvert.DeserializeObject<Dictionary<ulong, PlantBox>>(File.ReadAllText(FilePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
_plantBoxes = new Dictionary<ulong, PlantBox>();
|
||||
}
|
||||
|
||||
Console.WriteLine("Storage loaded");
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Console.WriteLine("Saving storage...");
|
||||
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(_plantBoxes));
|
||||
|
||||
Console.WriteLine("Storage saved");
|
||||
}
|
||||
|
||||
public void ClearPlantBox(PlantBox plantBox)
|
||||
{
|
||||
if (plantBox != null)
|
||||
{
|
||||
plantBox.HistoricManager.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<PlantBox> GetEnumerator() => _plantBoxes.Values.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
using System;
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static Broker Broker { get; private set; }
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
Broker = new Broker(args);
|
||||
Broker.Start();
|
||||
|
||||
Console.WriteLine("Broker stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
PlantBox.Broker/ServerManager.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
class ServerManager : TcpManager
|
||||
{
|
||||
public ServerManager(Broker broker) : base(broker)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string LogPrefix => "Server";
|
||||
protected override int ListeningPort => Connection.TCP_SERVER_PORT;
|
||||
|
||||
protected override void InfoCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
InfoResponse infoResponse = new InfoResponse().Deserialize(packet.Arguments);
|
||||
ulong id = packet.ID;
|
||||
PlantBox plantBox = Broker.PlantBoxesManager[id];
|
||||
|
||||
if (plantBox == null)
|
||||
{
|
||||
plantBox = new PlantBox
|
||||
{
|
||||
HistoricManager = new HistoricManager()
|
||||
};
|
||||
}
|
||||
plantBox.ID = id;
|
||||
plantBox.Name = infoResponse.Name;
|
||||
plantBox.Type = infoResponse.Type;
|
||||
plantBox.State = infoResponse.State;
|
||||
plantBox.Humidity = new CaptorValue(infoResponse.HumidityMin, infoResponse.HumidityMax, plantBox.Humidity?.Value ?? 0);
|
||||
plantBox.Luminosity = new CaptorValue(infoResponse.LuminosityMin, infoResponse.LuminosityMax, plantBox.Luminosity?.Value ?? 0);
|
||||
plantBox.Temperature = new CaptorValue(infoResponse.TemperatureMin, infoResponse.TemperatureMax, plantBox.Temperature?.Value ?? 0);
|
||||
|
||||
plantBox.UpdateState();
|
||||
|
||||
Broker.PlantBoxesManager.Add(plantBox);
|
||||
}
|
||||
|
||||
protected override void CaptorsCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
CaptorsResponse captorsResponse = new CaptorsResponse().Deserialize(packet.Arguments);
|
||||
ulong id = packet.ID;
|
||||
PlantBox plantBox = Broker.PlantBoxesManager[id];
|
||||
|
||||
if (plantBox == null)
|
||||
{
|
||||
Log($"Received captors info from non-registered PlantBox ({id}), ignoring it");
|
||||
return;
|
||||
}
|
||||
|
||||
plantBox.LastMeasureDate = DateTime.Now;
|
||||
plantBox.Humidity.Value = captorsResponse.Humidity;
|
||||
plantBox.Luminosity.Value = captorsResponse.Luminosity;
|
||||
plantBox.Temperature.Value = captorsResponse.Temperature;
|
||||
plantBox.TankLevel = captorsResponse.Tank;
|
||||
|
||||
plantBox.UpdateState();
|
||||
|
||||
plantBox.HistoricManager.Add(new CaptorsValue(plantBox.Humidity.Value, plantBox.Luminosity.Value, plantBox.Temperature.Value));
|
||||
}
|
||||
|
||||
protected override void HistoricCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void PingCommand(CommandStream commandStream, CommandPacket packet)
|
||||
{
|
||||
var ping = new PingCommand().Deserialize(packet.Arguments);
|
||||
|
||||
commandStream.Send(ping.ToCommandPacket(packet.ID));
|
||||
}
|
||||
}
|
||||
}
|
||||
101
PlantBox.Broker/TcpManager.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Broker
|
||||
{
|
||||
abstract class TcpManager
|
||||
{
|
||||
public Broker Broker { get; }
|
||||
|
||||
protected abstract string LogPrefix { get; }
|
||||
protected abstract int ListeningPort { get; }
|
||||
|
||||
private TcpListener _listener;
|
||||
|
||||
public TcpManager(Broker broker)
|
||||
{
|
||||
Log("Initializing...");
|
||||
|
||||
Broker = broker;
|
||||
_listener = new TcpListener(IPAddress.Any, ListeningPort);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Log("Starting...");
|
||||
|
||||
_listener.Start();
|
||||
|
||||
Log("Started");
|
||||
|
||||
TcpLoop();
|
||||
}
|
||||
|
||||
private void TcpLoop()
|
||||
{
|
||||
while (Broker.IsRunning)
|
||||
{
|
||||
Log("Waiting client...");
|
||||
|
||||
var client = _listener.AcceptTcpClient();
|
||||
|
||||
Log($"Found client: {client.Client.RemoteEndPoint}");
|
||||
|
||||
ClientLoop(client);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientLoop(TcpClient client)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var commandStream = new CommandStream(client.GetStream()))
|
||||
{
|
||||
while (client.Connected)
|
||||
{
|
||||
var packet = commandStream.Receive();
|
||||
Log($"Received command from {client.Client.RemoteEndPoint}");
|
||||
Log(packet.ToString());
|
||||
|
||||
switch (packet.Command)
|
||||
{
|
||||
case Command.Captors:
|
||||
CaptorsCommand(commandStream, packet);
|
||||
break;
|
||||
case Command.Historic:
|
||||
HistoricCommand(commandStream, packet);
|
||||
break;
|
||||
case Command.Info:
|
||||
InfoCommand(commandStream, packet);
|
||||
break;
|
||||
case Command.Ping:
|
||||
PingCommand(commandStream, packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Client disconnected: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
protected void Log(string message)
|
||||
{
|
||||
Console.WriteLine($"[{LogPrefix}]({DateTime.Now:HH:mm:ss}) {message}");
|
||||
}
|
||||
|
||||
protected abstract void CaptorsCommand(CommandStream commandStream, CommandPacket packet);
|
||||
protected abstract void HistoricCommand(CommandStream commandStream, CommandPacket packet);
|
||||
protected abstract void InfoCommand(CommandStream commandStream, CommandPacket packet);
|
||||
protected abstract void PingCommand(CommandStream commandStream, CommandPacket packet);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,9 @@
|
||||
<AotAssemblies>false</AotAssemblies>
|
||||
<EnableLLVM>false</EnableLLVM>
|
||||
<BundleAssemblies>false</BundleAssemblies>
|
||||
<EmbedAssembliesIntoApk>false</EmbedAssembliesIntoApk>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<MandroidI18n />
|
||||
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -44,6 +46,10 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AndroidManagedSymbols>true</AndroidManagedSymbols>
|
||||
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
|
||||
<AotAssemblies>false</AotAssemblies>
|
||||
<EnableLLVM>false</EnableLLVM>
|
||||
<BundleAssemblies>false</BundleAssemblies>
|
||||
<MandroidI18n />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.Android" />
|
||||
@@ -53,12 +59,12 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.4.0.1008975" />
|
||||
<PackageReference Include="Xamarin.Android.Support.Design" Version="27.0.2.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="27.0.2.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v4" Version="27.0.2.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="27.0.2.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="27.0.2.1" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
|
||||
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainActivity.cs" />
|
||||
@@ -93,7 +99,6 @@
|
||||
<Folder Include="Resources\drawable-xhdpi\" />
|
||||
<Folder Include="Resources\drawable-xxhdpi\" />
|
||||
<Folder Include="Resources\drawable-xxxhdpi\" />
|
||||
<Folder Include="Resources\drawable\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlantBox.Client\PlantBox.Client.csproj">
|
||||
@@ -126,5 +131,8 @@
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\Add.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -2,4 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="fr.ilyx.PlantBox.Client.Android" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
|
||||
<application android:label="PlantBox" android:icon="@mipmap/ic_launcher"></application>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
|
After Width: | Height: | Size: 211 B |
BIN
PlantBox.Client/PlantBox.Client.UWP/Add.png
Normal file
|
After Width: | Height: | Size: 211 B |
@@ -104,6 +104,7 @@
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Add.png" />
|
||||
<Content Include="Assets\Icon\LargeTile.scale-100.png" />
|
||||
<Content Include="Assets\Icon\LargeTile.scale-125.png" />
|
||||
<Content Include="Assets\Icon\LargeTile.scale-150.png" />
|
||||
@@ -189,8 +190,8 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.4.0.1008975" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.1.5" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.8" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlantBox.Client\PlantBox.Client.csproj">
|
||||
|
||||
@@ -148,6 +148,9 @@
|
||||
<Reference Include="Xamarin.iOS" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Acr.UserDialogs">
|
||||
<Version>7.0.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.4.0.1008975" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PlantBox.Client.ViewModels"
|
||||
x:Class="PlantBox.Client.App">
|
||||
<Application.Resources>
|
||||
|
||||
<viewmodels:SettingsViewModel x:Key="Settings" />
|
||||
<Style TargetType="ListView" >
|
||||
<Setter Property="Margin" Value="{OnPlatform UWP='10, 2'}" />
|
||||
</Style>
|
||||
<Style TargetType="TableView" >
|
||||
<Setter Property="Margin" Value="{OnPlatform UWP='10, 0'}" />
|
||||
</Style>
|
||||
<Color x:Key="HumidityColor">#2196f3</Color>
|
||||
<Color x:Key="HumidityColorSecondary">#90caf9</Color>
|
||||
<Color x:Key="LuminosityColor">#ffc107</Color>
|
||||
<Color x:Key="LuminosityColorSecondary">#ffe082</Color>
|
||||
<Color x:Key="TemperatureColor">#FF5722</Color>
|
||||
<Color x:Key="TemperatureColorSecondary">#ffab91</Color>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,4 +1,6 @@
|
||||
using PlantBox.Client.Forms;
|
||||
using PlantBox.Client.Resources;
|
||||
using PlantBox.Client.ViewModels;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
@@ -8,11 +10,23 @@ namespace PlantBox.Client
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public static SettingsViewModel Settings { get; private set; }
|
||||
public static MainPage MasterPage { get; private set; }
|
||||
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new MainPage();
|
||||
// Load settings
|
||||
Settings = (SettingsViewModel)Resources["Settings"];
|
||||
|
||||
if (Settings.Language != null)
|
||||
{
|
||||
Locale.Culture = Settings.Language;
|
||||
}
|
||||
|
||||
MasterPage = new MainPage();
|
||||
MainPage = MasterPage;
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
@@ -23,6 +37,7 @@ namespace PlantBox.Client
|
||||
protected override void OnSleep()
|
||||
{
|
||||
// Handle when your app sleeps
|
||||
Settings.Save();
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using PlantBox.Client.Models;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace PlantBox.Client.Converters
|
||||
{
|
||||
class CaptorValueToDoubleConverter : IValueConverter
|
||||
{
|
||||
const double HUMIDITY_UPPER_BOUND = 100.0;
|
||||
const double HUMIDITY_LOWER_BOUND = 0.0;
|
||||
const double LUMINOSITY_UPPER_BOUND = 1200.0;
|
||||
const double LUMINOSITY_LOWER_BOUND = 0.0;
|
||||
const double TEMPERATURE_UPPER_BOUND = 50.0;
|
||||
const double TEMPERATURE_LOWER_BOUND = -20.0;
|
||||
const double TANK_UPPER_BOUND = 100.0;
|
||||
const double TANK_LOWER_BOUND = 0.0;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double d && parameter is CaptorType type)
|
||||
{
|
||||
double upperBound;
|
||||
double lowerBound;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CaptorType.Humidity:
|
||||
upperBound = HUMIDITY_UPPER_BOUND;
|
||||
lowerBound = HUMIDITY_LOWER_BOUND;
|
||||
break;
|
||||
case CaptorType.Luminosity:
|
||||
upperBound = LUMINOSITY_UPPER_BOUND;
|
||||
lowerBound = LUMINOSITY_LOWER_BOUND;
|
||||
break;
|
||||
case CaptorType.Temperature:
|
||||
upperBound = TEMPERATURE_UPPER_BOUND;
|
||||
lowerBound = TEMPERATURE_LOWER_BOUND;
|
||||
break;
|
||||
case CaptorType.Tank:
|
||||
upperBound = TANK_UPPER_BOUND;
|
||||
lowerBound = TANK_LOWER_BOUND;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("How did you just got here?");
|
||||
}
|
||||
|
||||
return (d - lowerBound) / (upperBound - lowerBound);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid source or argument");
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Converters
|
||||
{
|
||||
class JsonCultureInfoConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(CultureInfo) ? true : false;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var c = new CultureInfo((string)reader.Value);
|
||||
return c;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var culture = (CultureInfo)value;
|
||||
|
||||
writer.WriteValue(culture.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace PlantBox.Client.Extensions
|
||||
{
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static string GetHexString(this Color color)
|
||||
{
|
||||
var red = (int)(color.R * 255);
|
||||
var green = (int)(color.G * 255);
|
||||
var blue = (int)(color.B * 255);
|
||||
var alpha = (int)(color.A * 255);
|
||||
var hex = $"#{alpha:X2}{red:X2}{green:X2}{blue:X2}";
|
||||
|
||||
return hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace PlantBox.Client.Extensions
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.WriteLine($"[ImageResourceExtension](Error): File '{name}' does not exist");
|
||||
#else
|
||||
throw new ArgumentException($"File '{Path}' does not exist");
|
||||
throw new ArgumentException($"File '{path}' does not exist");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace PlantBox.Client.Extensions
|
||||
|
||||
public object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return _resourceManager.GetString(Name) ?? $"#{Name}";
|
||||
return _resourceManager.GetString(Name, Locale.Culture) ?? $"#{Name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Extensions
|
||||
{
|
||||
// Source: https://stackoverflow.com/a/14285561
|
||||
public static class TimeSpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Multiplies a timespan by an integer value
|
||||
/// </summary>
|
||||
public static TimeSpan Multiply(this TimeSpan multiplicand, int multiplier)
|
||||
{
|
||||
return TimeSpan.FromTicks(multiplicand.Ticks * multiplier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies a timespan by a double value
|
||||
/// </summary>
|
||||
public static TimeSpan Multiply(this TimeSpan multiplicand, double multiplier)
|
||||
{
|
||||
return TimeSpan.FromTicks((long)(multiplicand.Ticks * multiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
xmlns:models="clr-namespace:PlantBox.Client.Models"
|
||||
x:Class="PlantBox.Client.Forms.AboutPage"
|
||||
Padding="{OnPlatform UWP=10}">
|
||||
x:Class="PlantBox.Client.Forms.AboutPage">
|
||||
<ContentPage.Content>
|
||||
<ListView x:Name="listView"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand"
|
||||
SelectionMode="None"
|
||||
IsGroupingEnabled="True"
|
||||
GroupDisplayBinding="{Binding Title}"
|
||||
|
||||
@@ -28,7 +28,13 @@ namespace PlantBox.Client.Forms
|
||||
},
|
||||
new AboutPageItemGroup(Locale.Libraries)
|
||||
{
|
||||
new AboutPageItem("Microcharts", "https://github.com/aloisdeniel/Microcharts", true)
|
||||
new AboutPageItem("Microcharts", "https://github.com/aloisdeniel/Microcharts", true),
|
||||
new AboutPageItem("Newtonsoft.Json", "https://github.com/JamesNK/Newtonsoft.Json", true)
|
||||
},
|
||||
new AboutPageItemGroup(Locale.Media)
|
||||
{
|
||||
new AboutPageItem("Android Material Icons", "https://material.io/tools/icons/", true),
|
||||
new AboutPageItem("Android Asset Studio", "https://romannurik.github.io/AndroidAssetStudio/", true)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PlantBox.Client.Forms.Controls.StatusBar.StatusBar">
|
||||
<ContentView.Content>
|
||||
<StackLayout HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<Label x:Name="label"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
VerticalOptions="Start"
|
||||
FontSize="Medium" />
|
||||
<AbsoluteLayout FlexLayout.Grow="1"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<BoxView x:Name="BackgroundBar"
|
||||
AbsoluteLayout.LayoutBounds="0, 1, 1, 1"
|
||||
AbsoluteLayout.LayoutFlags="All" />
|
||||
<BoxView x:Name="ForegroundBar"
|
||||
AbsoluteLayout.LayoutBounds="0, 1, 1, .5"
|
||||
AbsoluteLayout.LayoutFlags="All" />
|
||||
<BoxView x:Name="UpperBoundBar"
|
||||
AbsoluteLayout.LayoutBounds="0, 0.2, 1, 5" />
|
||||
<BoxView x:Name="LowerBoundBar"
|
||||
AbsoluteLayout.LayoutBounds="0, 0.8, 1, 5" />
|
||||
</AbsoluteLayout>
|
||||
<Image x:Name="image"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
VerticalOptions="End"
|
||||
Margin="2"/>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
||||
133
PlantBox.Client/PlantBox.Client/Forms/Controls/StatusBar.xaml.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace PlantBox.Client.Forms.Controls.StatusBar
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class StatusBar : ContentView
|
||||
{
|
||||
// Progress
|
||||
public static readonly BindableProperty ProgressProperty = BindableProperty.Create(nameof(Progress), typeof(double), typeof(StatusBar), 0.5);
|
||||
public double Progress
|
||||
{
|
||||
get => (double)GetValue(ProgressProperty);
|
||||
set => SetValue(ProgressProperty, value);
|
||||
}
|
||||
// HintColor
|
||||
public static readonly BindableProperty HintColorProperty = BindableProperty.Create(nameof(HintColor), typeof(Color), typeof(StatusBar));
|
||||
public Color HintColor
|
||||
{
|
||||
get => (Color)GetValue(HintColorProperty);
|
||||
set => SetValue(HintColorProperty, value);
|
||||
}
|
||||
// ForegroundColor
|
||||
public static readonly BindableProperty ForegroundColorProperty = BindableProperty.Create(nameof(ForegroundColor), typeof(Color), typeof(StatusBar));
|
||||
public Color ForegroundColor
|
||||
{
|
||||
get => (Color)GetValue(ForegroundColorProperty);
|
||||
set => SetValue(ForegroundColorProperty, value);
|
||||
}
|
||||
// Image
|
||||
public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(ImageSource), typeof(StatusBar));
|
||||
public ImageSource Image
|
||||
{
|
||||
get => (ImageSource)GetValue(ImageProperty);
|
||||
set => SetValue(ImageProperty, value);
|
||||
}
|
||||
// Label
|
||||
public static readonly BindableProperty LabelProperty = BindableProperty.Create(nameof(Label), typeof(string), typeof(StatusBar));
|
||||
public string Label
|
||||
{
|
||||
get => (string)GetValue(LabelProperty);
|
||||
set => SetValue(LabelProperty, value);
|
||||
}
|
||||
// BoundHeight
|
||||
public static readonly BindableProperty BoundHeightProperty = BindableProperty.Create(nameof(BoundHeight), typeof(double), typeof(StatusBar));
|
||||
public double BoundHeight
|
||||
{
|
||||
get => (double)GetValue(BoundHeightProperty);
|
||||
set => SetValue(BoundHeightProperty, value);
|
||||
}
|
||||
// BoundColor
|
||||
public static readonly BindableProperty BoundColorProperty = BindableProperty.Create(nameof(BoundColor), typeof(Color), typeof(StatusBar));
|
||||
public Color BoundColor
|
||||
{
|
||||
get => (Color)GetValue(BoundColorProperty);
|
||||
set => SetValue(BoundColorProperty, value);
|
||||
}
|
||||
// LowerBound
|
||||
public static readonly BindableProperty LowerBoundProperty = BindableProperty.Create(nameof(LowerBound), typeof(double), typeof(StatusBar), 0.2);
|
||||
public double LowerBound
|
||||
{
|
||||
get => (double)GetValue(LowerBoundProperty);
|
||||
set => SetValue(LowerBoundProperty, value);
|
||||
}
|
||||
// UpperBound
|
||||
public static readonly BindableProperty UpperBoundProperty = BindableProperty.Create(nameof(UpperBound), typeof(double), typeof(StatusBar), 0.8);
|
||||
public double UpperBound
|
||||
{
|
||||
get => (double)GetValue(UpperBoundProperty);
|
||||
set => SetValue(UpperBoundProperty, value);
|
||||
}
|
||||
|
||||
public StatusBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
AbsoluteLayout.SetLayoutFlags(UpperBoundBar, AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.WidthProportional);
|
||||
AbsoluteLayout.SetLayoutFlags(LowerBoundBar, AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.WidthProportional);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged([CallerMemberName]string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
|
||||
if (propertyName == ProgressProperty.PropertyName)
|
||||
{
|
||||
new Animation
|
||||
(
|
||||
x => AbsoluteLayout.SetLayoutBounds(ForegroundBar, new Rectangle(0, 1, 1, x)),
|
||||
AbsoluteLayout.GetLayoutBounds(ForegroundBar).Height,
|
||||
Progress,
|
||||
Easing.CubicInOut
|
||||
).Commit(this, "Scale", 16, 1000);
|
||||
}
|
||||
if (propertyName == HintColorProperty.PropertyName)
|
||||
{
|
||||
BackgroundBar.BackgroundColor = HintColor;
|
||||
}
|
||||
if (propertyName == ForegroundColorProperty.PropertyName)
|
||||
{
|
||||
ForegroundBar.BackgroundColor = ForegroundColor;
|
||||
label.TextColor = ForegroundColor;
|
||||
}
|
||||
if (propertyName == ImageProperty.PropertyName)
|
||||
{
|
||||
image.Source = Image;
|
||||
}
|
||||
if (propertyName == LabelProperty.PropertyName)
|
||||
{
|
||||
label.Text = Label;
|
||||
}
|
||||
if (propertyName == BoundHeightProperty.PropertyName || propertyName == LowerBoundProperty.PropertyName || propertyName == UpperBoundProperty.PropertyName)
|
||||
{
|
||||
// Y axis is reversed
|
||||
double realBoundHeight = BoundHeight / BackgroundBar.Height;
|
||||
AbsoluteLayout.SetLayoutBounds(LowerBoundBar, new Rectangle(0, 1 - LowerBound, 1, BoundHeight));
|
||||
AbsoluteLayout.SetLayoutBounds(UpperBoundBar, new Rectangle(0, 1 - UpperBound, 1, BoundHeight));
|
||||
}
|
||||
if (propertyName == BoundColorProperty.PropertyName)
|
||||
{
|
||||
LowerBoundBar.BackgroundColor = BoundColor;
|
||||
UpperBoundBar.BackgroundColor = BoundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
PlantBox.Client/PlantBox.Client/Forms/HomeItem.xaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
x:Class="PlantBox.Client.Forms.HomeItem">
|
||||
<ContentView.Content>
|
||||
<Frame CornerRadius="5"
|
||||
Padding="5"
|
||||
BorderColor="{OnPlatform UWP='Gray', Android='Gray'}"
|
||||
HasShadow="True"
|
||||
BackgroundColor="{OnPlatform UWP='#F0F0F0'}">
|
||||
<StackLayout HorizontalOptions="Fill"
|
||||
VerticalOptions="Fill"
|
||||
Orientation="Horizontal">
|
||||
<Image x:Name="imageType"
|
||||
Source="{extensions:ImageResource Logo_Gray.png}"
|
||||
WidthRequest="50"
|
||||
Margin="2, 0, 0, 0"/>
|
||||
<BoxView VerticalOptions="Fill"
|
||||
WidthRequest="3"
|
||||
Margin="0, 2"
|
||||
CornerRadius="5"
|
||||
BackgroundColor="Gray" />
|
||||
<StackLayout Orientation="Vertical">
|
||||
<Label x:Name="labelName"
|
||||
VerticalOptions="StartAndExpand"
|
||||
TextColor="#666666"
|
||||
Style="{DynamicResource TitleStyle}" />
|
||||
<Label x:Name="labelType"
|
||||
VerticalOptions="StartAndExpand"
|
||||
TextColor="Gray"
|
||||
Style="{DynamicResource SubtitleStyle}" />
|
||||
</StackLayout>
|
||||
<BoxView x:Name="boxState"
|
||||
VerticalOptions="Fill"
|
||||
HorizontalOptions="EndAndExpand"
|
||||
WidthRequest="5"
|
||||
CornerRadius="5" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
||||
88
PlantBox.Client/PlantBox.Client/Forms/HomeItem.xaml.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using PlantBox.Client.Extensions;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace PlantBox.Client.Forms
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class HomeItem : ContentView
|
||||
{
|
||||
// Colors
|
||||
public static readonly Color BadColor = Color.FromHex("#FA3838");
|
||||
public static readonly Color GoodColor = Color.FromHex("#8ED959");
|
||||
public static readonly Color WarningColor = Color.FromHex("#FFC533");
|
||||
|
||||
// Name
|
||||
public static readonly BindableProperty PlantNameProperty = BindableProperty.Create(nameof(PlantName), typeof(string), typeof(HomeItem));
|
||||
public string PlantName
|
||||
{
|
||||
get => (string)GetValue(PlantNameProperty);
|
||||
set => SetValue(PlantNameProperty, value);
|
||||
}
|
||||
|
||||
// PlantType
|
||||
public static readonly BindableProperty PlantTypeProperty = BindableProperty.Create(nameof(PlantType), typeof(PlantType), typeof(HomeItem));
|
||||
public PlantType PlantType
|
||||
{
|
||||
get => (PlantType)GetValue(PlantTypeProperty);
|
||||
set => SetValue(PlantTypeProperty, value);
|
||||
}
|
||||
|
||||
// PlantState
|
||||
public static readonly BindableProperty PlantStateProperty = BindableProperty.Create(nameof(PlantState), typeof(PlantState), typeof(HomeItem));
|
||||
public PlantState PlantState
|
||||
{
|
||||
get => (PlantState)GetValue(PlantStateProperty);
|
||||
set => SetValue(PlantStateProperty, value);
|
||||
}
|
||||
|
||||
public HomeItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
|
||||
if (propertyName == PlantNameProperty.PropertyName)
|
||||
{
|
||||
labelName.Text = PlantName;
|
||||
}
|
||||
if (propertyName == PlantTypeProperty.PropertyName && PlantType != PlantType.Default)
|
||||
{
|
||||
labelType.Text = PlantType.ToString();
|
||||
|
||||
// Icon
|
||||
imageType.Source = ImageResourceExtension.GetImage($"Type.{PlantType}.png");
|
||||
}
|
||||
if (propertyName == PlantStateProperty.PropertyName && PlantState != PlantState.Default)
|
||||
{
|
||||
Color color;
|
||||
|
||||
switch(PlantState)
|
||||
{
|
||||
case PlantState.Bad:
|
||||
color = BadColor;
|
||||
break;
|
||||
case PlantState.Warning:
|
||||
color = WarningColor;
|
||||
break;
|
||||
default:
|
||||
color = GoodColor;
|
||||
break;
|
||||
}
|
||||
|
||||
boxState.BackgroundColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,43 @@
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
xmlns:forms="clr-namespace:PlantBox.Client.Forms"
|
||||
xmlns:viewmodels="clr-namespace:PlantBox.Client.ViewModels"
|
||||
x:Class="PlantBox.Client.Forms.HomePage">
|
||||
<ContentPage.BindingContext>
|
||||
<viewmodels:HomeViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="{extensions:Locale HomePageTitle}"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="CenterAndExpand" />
|
||||
<Image x:Name="image"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
WidthRequest="50"
|
||||
Source="{extensions:ImageResource Info.png}"/>
|
||||
</StackLayout>
|
||||
<AbsoluteLayout>
|
||||
<!--List-->
|
||||
<ListView x:Name="List"
|
||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||
AbsoluteLayout.LayoutFlags="All"
|
||||
ItemsSource="{Binding Plants}"
|
||||
SelectionMode="None"
|
||||
RowHeight="{OnPlatform Android=85}"
|
||||
SeparatorVisibility="None"
|
||||
IsPullToRefreshEnabled="True"
|
||||
Refreshing="ListView_Refreshing"
|
||||
ItemTapped="ListView_ItemTapped">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ViewCell>
|
||||
<forms:HomeItem Margin="5"
|
||||
PlantName="{Binding Name}"
|
||||
PlantType="{Binding Type}"
|
||||
PlantState="{Binding State}" />
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<!--Floating Button-->
|
||||
<Button AbsoluteLayout.LayoutBounds="0.9, 0.94, 64, 64"
|
||||
AbsoluteLayout.LayoutFlags="PositionProportional"
|
||||
BackgroundColor="Accent"
|
||||
CornerRadius="100"
|
||||
Image="Add.png"
|
||||
Clicked="Button_Clicked" />
|
||||
</AbsoluteLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
@@ -1,8 +1,18 @@
|
||||
using System;
|
||||
using PlantBox.Client.Extensions;
|
||||
using PlantBox.Client.Forms.Plant;
|
||||
using PlantBox.Client.Models;
|
||||
using PlantBox.Client.Resources;
|
||||
using PlantBox.Client.Util;
|
||||
using PlantBox.Client.ViewModels;
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
@@ -17,5 +27,80 @@ namespace PlantBox.Client.Forms
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
|
||||
{
|
||||
var plant = (PlantInfo)e.Item;
|
||||
|
||||
// Prepare view model
|
||||
var viewModel = new PlantViewModel(plant);
|
||||
|
||||
await Navigation.PushAsync(new PlantPage(viewModel)
|
||||
{
|
||||
Title = plant.Name
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
App.MasterPage.IsPresented = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void Button_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
string input = await FormsDialog.InputBox(Navigation, Locale.AddPlantBox, Locale.EnterValidID);
|
||||
|
||||
if (ulong.TryParse(input, out ulong id) && await IsValidId(id))
|
||||
{
|
||||
App.Settings.IDs.Add(id);
|
||||
|
||||
await App.Settings.SaveAsync();
|
||||
await Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(Locale.Error, Locale.InvalidID, Locale.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsValidId(ulong id)
|
||||
{
|
||||
string plantName = "";
|
||||
|
||||
try
|
||||
{
|
||||
using (var client = new TcpClient(App.Settings.BrokerIP, Connection.TCP_CLIENT_PORT))
|
||||
using (var commandStream = new CommandStream(client.GetStream()))
|
||||
{
|
||||
await commandStream.SendAsync(new InfoRequest().ToCommandPacket(id));
|
||||
|
||||
(_, var info) = await commandStream.ReceiveAsync<InfoResponse>();
|
||||
|
||||
plantName = info.Name;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return plantName != "";
|
||||
}
|
||||
|
||||
private async void ListView_Refreshing(object sender, EventArgs e)
|
||||
{
|
||||
await Refresh();
|
||||
|
||||
List.EndRefresh();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
var homeViewModel = (HomeViewModel)BindingContext;
|
||||
|
||||
await Task.Run(() => homeViewModel.Plants = homeViewModel.LoadPlants(App.Settings.IDs));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,15 +30,18 @@ namespace PlantBox.Client.Forms
|
||||
if (e.Item is MenuPageItem item)
|
||||
{
|
||||
IsPresented = false;
|
||||
var page = item.PageCreator();
|
||||
page.Title = item.Title;
|
||||
var page = item.Page;
|
||||
|
||||
if (Detail.Navigation.NavigationStack.Count > 1)
|
||||
if (Detail.Navigation.NavigationStack.Count > 1 || page == null)
|
||||
{
|
||||
await Detail.Navigation.PopToRootAsync();
|
||||
}
|
||||
|
||||
await Detail.Navigation.PushAsync(page);
|
||||
if (page != null)
|
||||
{
|
||||
page.Title = item.Title;
|
||||
await Detail.Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
<DataTemplate>
|
||||
<ViewCell>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="15, 0, 0, 0">
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="15, 0, 0, 0">
|
||||
<Image HeightRequest="32"
|
||||
WidthRequest="32"
|
||||
Source="{Binding Image}" />
|
||||
WidthRequest="32"
|
||||
Source="{Binding Image}" />
|
||||
<Label HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="5, 0, 0, 0"
|
||||
Style="{DynamicResource SubtitleStyle}"
|
||||
Text="{Binding Title}" />
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="5, 0, 0, 0"
|
||||
Style="{DynamicResource SubtitleStyle}"
|
||||
Text="{Binding Title}" />
|
||||
</StackLayout>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -22,8 +22,9 @@ namespace PlantBox.Client.Forms
|
||||
|
||||
items.ItemsSource = new MenuPageItem[]
|
||||
{
|
||||
new MenuPageItem(Locale.Settings, ImageResourceExtension.GetImage("Settings.png"), typeof(SettingsPage)),
|
||||
new MenuPageItem(Locale.About, ImageResourceExtension.GetImage("Info.png"), typeof(AboutPage))
|
||||
new MenuPageItem(Locale.HomePageTitle, ImageResourceExtension.GetImage("Home.png"), null),
|
||||
new MenuPageItem(Locale.Settings, ImageResourceExtension.GetImage("Settings.png"), new SettingsPage()),
|
||||
new MenuPageItem(Locale.About, ImageResourceExtension.GetImage("Info.png"), new AboutPage())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
63
PlantBox.Client/PlantBox.Client/Forms/Plant/CaptorsPage.xaml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:converters="clr-namespace:PlantBox.Client.Converters"
|
||||
xmlns:statusbar="clr-namespace:PlantBox.Client.Forms.Controls.StatusBar"
|
||||
xmlns:models="clr-namespace:PlantBox.Client.Models"
|
||||
xmlns:viewmodels="clr-namespace:PlantBox.Client.ViewModels"
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
x:Class="PlantBox.Client.Forms.Plant.CaptorsPage">
|
||||
<ContentPage.Resources>
|
||||
<FlexBasis x:Key="BarWidth">17%</FlexBasis>
|
||||
<converters:CaptorValueToDoubleConverter x:Key="CaptorConverter" />
|
||||
</ContentPage.Resources>
|
||||
<ContentPage.Content>
|
||||
<FlexLayout Direction="Row"
|
||||
JustifyContent="SpaceAround"
|
||||
VerticalOptions="FillAndExpand"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
Margin="20">
|
||||
<statusbar:StatusBar Progress="{Binding HumidityCaptor.Value, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Humidity}}"
|
||||
Label="{Binding HumidityCaptor.Value, StringFormat='\{0:F1} %'}"
|
||||
ForegroundColor="{StaticResource HumidityColor}"
|
||||
HintColor="{StaticResource HumidityColorSecondary}"
|
||||
BoundColor="DarkSlateGray"
|
||||
BoundHeight="2"
|
||||
LowerBound="{Binding HumidityCaptor.Min, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Humidity}}"
|
||||
UpperBound="{Binding HumidityCaptor.Max, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Humidity}}"
|
||||
Image="{extensions:ImageResource Humidity_Icon.png}"
|
||||
FlexLayout.Basis="{StaticResource BarWidth}" />
|
||||
<statusbar:StatusBar Progress="{Binding LuminosityCaptor.Value, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Luminosity}}"
|
||||
Label="{Binding LuminosityCaptor.Value, StringFormat='\{0:F0} lux'}"
|
||||
ForegroundColor="{StaticResource LuminosityColor}"
|
||||
HintColor="{StaticResource LuminosityColorSecondary}"
|
||||
BoundColor="DarkSlateGray"
|
||||
BoundHeight="2"
|
||||
LowerBound="{Binding LuminosityCaptor.Min, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Luminosity}}"
|
||||
UpperBound="{Binding LuminosityCaptor.Max, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Luminosity}}"
|
||||
Image="{extensions:ImageResource Luminosity_Icon.png}"
|
||||
FlexLayout.Basis="{StaticResource BarWidth}" />
|
||||
<statusbar:StatusBar Progress="{Binding TemperatureCaptor.Value, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Temperature}}"
|
||||
Label="{Binding TemperatureCaptor.Value, StringFormat='\{0:F1} °C'}"
|
||||
ForegroundColor="{StaticResource TemperatureColor}"
|
||||
HintColor="{StaticResource TemperatureColorSecondary}"
|
||||
BoundColor="DarkSlateGray"
|
||||
BoundHeight="2"
|
||||
LowerBound="{Binding TemperatureCaptor.Min, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Temperature}}"
|
||||
UpperBound="{Binding TemperatureCaptor.Max, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Temperature}}"
|
||||
Image="{extensions:ImageResource Temperature_Icon.png}"
|
||||
FlexLayout.Basis="{StaticResource BarWidth}" />
|
||||
<statusbar:StatusBar Progress="{Binding TankValue, Converter={x:StaticResource CaptorConverter}, ConverterParameter={x:Static models:CaptorType.Tank}}"
|
||||
Label="{Binding TankValue, StringFormat='\{0:F0} %'}"
|
||||
ForegroundColor="{StaticResource HumidityColor}"
|
||||
HintColor="{StaticResource HumidityColorSecondary}"
|
||||
BoundColor="DarkSlateGray"
|
||||
BoundHeight="2"
|
||||
LowerBound="-1"
|
||||
UpperBound="-1"
|
||||
Image="{extensions:ImageResource Tank_Icon.png}"
|
||||
FlexLayout.Basis="{StaticResource BarWidth}" />
|
||||
|
||||
</FlexLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace PlantBox.Client.Forms.Plant
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class CaptorsPage : ContentPage
|
||||
{
|
||||
public CaptorsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
xmlns:forms="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
|
||||
x:Class="PlantBox.Client.Forms.Plant.HistoricPage">
|
||||
<ContentPage.Content>
|
||||
<StackLayout Padding="5">
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Label HorizontalOptions="Start"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Text="{extensions:Locale Interval}" />
|
||||
<Picker x:Name="Picker"
|
||||
HorizontalOptions="EndAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
WidthRequest="200"
|
||||
SelectedItem="{Binding HistoricDuration, Mode=OneWayToSource}"
|
||||
SelectedIndexChanged="Picker_SelectedIndexChanged" />
|
||||
</StackLayout>
|
||||
<ScrollView>
|
||||
<StackLayout x:Name="layout">
|
||||
<BindableLayout.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Frame HorizontalOptions="FillAndExpand"
|
||||
HeightRequest="200"
|
||||
Padding="5">
|
||||
<StackLayout>
|
||||
<Label Text="{Binding Name}" />
|
||||
<forms:ChartView Chart="{Binding Chart}"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</DataTemplate>
|
||||
</BindableLayout.ItemTemplate>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
215
PlantBox.Client/PlantBox.Client/Forms/Plant/HistoricPage.xaml.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using PlantBox.Client.Models;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
using Microcharts;
|
||||
using Entry = Microcharts.Entry;
|
||||
using PlantBox.Client.Extensions;
|
||||
using PlantBox.Client.ViewModels;
|
||||
using PlantBox.Client.Resources;
|
||||
|
||||
namespace PlantBox.Client.Forms.Plant
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class HistoricPage : ContentPage
|
||||
{
|
||||
private readonly SKColor _humidityColor;
|
||||
private readonly SKColor _humidityColorSecondary;
|
||||
private readonly SKColor _luminosityColor;
|
||||
private readonly SKColor _luminosityColorSecondary;
|
||||
private readonly SKColor _temperatureColor;
|
||||
private readonly SKColor _temperatureColorSecondary;
|
||||
|
||||
public HistoricPage(PlantViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
|
||||
// Colors
|
||||
_humidityColor = SKColor.Parse(((Color)Application.Current.Resources["HumidityColor"]).GetHexString());
|
||||
_humidityColorSecondary = SKColor.Parse(((Color)Application.Current.Resources["HumidityColorSecondary"]).GetHexString());
|
||||
|
||||
_luminosityColor = SKColor.Parse(((Color)App.Current.Resources["LuminosityColor"]).GetHexString());
|
||||
_luminosityColorSecondary = SKColor.Parse(((Color)Application.Current.Resources["LuminosityColorSecondary"]).GetHexString());
|
||||
|
||||
_temperatureColor = SKColor.Parse(((Color)Application.Current.Resources["TemperatureColor"]).GetHexString());
|
||||
_temperatureColorSecondary = SKColor.Parse(((Color)Application.Current.Resources["TemperatureColorSecondary"]).GetHexString());
|
||||
|
||||
// Picker
|
||||
var durations = new List<Duration>()
|
||||
{
|
||||
new Duration(1, Interval.Hourly),
|
||||
new Duration(1, Interval.Daily),
|
||||
new Duration(2, Interval.Daily),
|
||||
new Duration(1, Interval.Weekly),
|
||||
new Duration(2, Interval.Monthly),
|
||||
new Duration(3, Interval.Monthly),
|
||||
new Duration(6, Interval.Monthly),
|
||||
new Duration(1, Interval.Yearly),
|
||||
new Duration(2, Interval.Yearly),
|
||||
new Duration(3, Interval.Yearly)
|
||||
};
|
||||
|
||||
Picker.ItemsSource = durations;
|
||||
Picker.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
DisplayCharts();
|
||||
}
|
||||
|
||||
private void DisplayCharts()
|
||||
{
|
||||
var charts = new NamedChart[]
|
||||
{
|
||||
new NamedChart(CreateChart(CaptorType.Humidity, _humidityColor, _humidityColorSecondary), Locale.Humidity),
|
||||
new NamedChart(CreateChart(CaptorType.Luminosity, _luminosityColor, _luminosityColorSecondary), Locale.Luminosity),
|
||||
new NamedChart(CreateChart(CaptorType.Temperature, _temperatureColor, _temperatureColorSecondary), Locale.Temperature)
|
||||
};
|
||||
|
||||
BindableLayout.SetItemsSource(layout, charts);
|
||||
}
|
||||
|
||||
private Chart CreateChart(CaptorType captorType, SKColor begin, SKColor end)
|
||||
{
|
||||
var chart = new LineChart()
|
||||
{
|
||||
LabelTextSize = 14,
|
||||
Margin = 10,
|
||||
LineAreaAlpha = 50,
|
||||
LineMode = LineMode.Straight
|
||||
};
|
||||
var entries = CreateEntries(captorType, begin, end);
|
||||
|
||||
chart.Entries = entries;
|
||||
return chart;
|
||||
}
|
||||
|
||||
private Entry[] CreateEntries(CaptorType captorType, SKColor begin, SKColor end)
|
||||
{
|
||||
PlantViewModel viewModel = (PlantViewModel)BindingContext;
|
||||
Historic historic = viewModel.Historic;
|
||||
HistoricEntries historicEntries;
|
||||
uint multiplier = viewModel.HistoricDuration.Multiplier;
|
||||
uint count;
|
||||
float step;
|
||||
string labelFormat;
|
||||
string valueFormat;
|
||||
|
||||
switch (viewModel.HistoricDuration.Interval)
|
||||
{
|
||||
case Interval.Hourly:
|
||||
historicEntries = historic.MinutelyHistoric;
|
||||
count = 12;
|
||||
step = 2;
|
||||
labelFormat = "HH:mm";
|
||||
break;
|
||||
case Interval.Daily:
|
||||
historicEntries = historic.HourlyHistoric;
|
||||
count = 24;
|
||||
step = 3;
|
||||
labelFormat = "HH:mm";
|
||||
break;
|
||||
case Interval.Weekly:
|
||||
historicEntries = historic.DailyHistoric;
|
||||
count = 7;
|
||||
step = 1;
|
||||
labelFormat = "ddd";
|
||||
break;
|
||||
case Interval.Monthly:
|
||||
historicEntries = historic.DailyHistoric;
|
||||
count = 31;
|
||||
step = 4;
|
||||
labelFormat = "dd/MM";
|
||||
break;
|
||||
case Interval.Yearly:
|
||||
historicEntries = historic.MonthlyHistoric;
|
||||
count = 12;
|
||||
step = multiplier == 1 ? 1 : 1.5f;
|
||||
labelFormat = multiplier == 1 ? "MMM" : "MM/yy";
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
count *= multiplier;
|
||||
step *= multiplier;
|
||||
|
||||
HistoricEntry[] values;
|
||||
|
||||
switch (captorType)
|
||||
{
|
||||
case CaptorType.Humidity:
|
||||
values = historicEntries.Humidities;
|
||||
valueFormat = "{0:F1} %";
|
||||
break;
|
||||
case CaptorType.Luminosity:
|
||||
values = historicEntries.Luminosities;
|
||||
valueFormat = "{0:F0} lux";
|
||||
break;
|
||||
case CaptorType.Temperature:
|
||||
values = historicEntries.Temperatures;
|
||||
valueFormat = "{0:F1} °C";
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Take values
|
||||
values = values.Reverse().Take((int)count).ToArray();
|
||||
Entry[] entries = new Entry[values.Length / (int)step];
|
||||
SKColor[] colors = CreateGradient(begin, end, values.Length / (int)step).Reverse().ToArray();
|
||||
|
||||
int index = 0;
|
||||
for (uint i = 0; index < entries.Length; i += (uint)step)
|
||||
{
|
||||
var entry = values[i];
|
||||
double value = entry.Value != 0 ? entry.Value : 0.0001;
|
||||
|
||||
entries[index] = new Entry((float)value)
|
||||
{
|
||||
Label = entry.Date.ToString(labelFormat),
|
||||
ValueLabel = string.Format(valueFormat, value),
|
||||
Color = colors[index]
|
||||
};
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return entries.Reverse().ToArray();
|
||||
}
|
||||
|
||||
private SKColor[] CreateGradient(SKColor begin, SKColor end, int count)
|
||||
{
|
||||
var colors = new SKColor[count];
|
||||
|
||||
byte currentRed = begin.Red;
|
||||
byte deltaRed = (byte)((end.Red - begin.Red) / count);
|
||||
|
||||
byte currentGreen = begin.Green;
|
||||
byte deltaGreen = (byte)((end.Green - begin.Green) / count);
|
||||
|
||||
byte currentBlue = begin.Blue;
|
||||
byte deltaBlue = (byte)((end.Blue - begin.Blue) / count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
colors[i] = new SKColor(currentRed, currentGreen, currentBlue);
|
||||
|
||||
currentRed += deltaRed;
|
||||
currentGreen += deltaGreen;
|
||||
currentBlue += deltaBlue;
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PlantBox.Client.Forms.Plant.PlantPage" />
|
||||
@@ -0,0 +1,33 @@
|
||||
using PlantBox.Client.Models;
|
||||
using PlantBox.Client.Resources;
|
||||
using PlantBox.Client.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace PlantBox.Client.Forms.Plant
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class PlantPage : TabbedPage
|
||||
{
|
||||
public PlantPage(PlantViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Children.Add(new CaptorsPage()
|
||||
{
|
||||
BindingContext = viewModel,
|
||||
Title = Locale.Informations
|
||||
});
|
||||
Children.Add(new HistoricPage(viewModel)
|
||||
{
|
||||
Title = Locale.Historic
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PlantBox.Client.Forms.Settings.LanguageSelectorPage">
|
||||
<ContentPage.Content>
|
||||
<ListView x:Name="listView"
|
||||
SelectionMode="None"
|
||||
ItemTapped="ListView_ItemTapped">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextCell Text="{Binding NativeName}"
|
||||
Detail="{Binding Name}"
|
||||
TextColor="Accent" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,45 @@
|
||||
using PlantBox.Client.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace PlantBox.Client.Forms.Settings
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class LanguageSelectorPage : ContentPage
|
||||
{
|
||||
private CultureInfo[] _supportedCultures = new CultureInfo[]
|
||||
{
|
||||
new CultureInfo("en-US"),
|
||||
new CultureInfo("fr-FR")
|
||||
};
|
||||
|
||||
public LanguageSelectorPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
listView.ItemsSource = _supportedCultures;
|
||||
}
|
||||
|
||||
private async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
|
||||
{
|
||||
if (e.Item is CultureInfo culture)
|
||||
{
|
||||
App.Settings.Language = culture;
|
||||
Locale.Culture = culture;
|
||||
|
||||
await App.Settings.SaveAsync();
|
||||
|
||||
(Application.Current as App).MainPage = new MainPage();
|
||||
//await DisplayAlert(Locale.LanguageChangedTitle, Locale.LanguageChangedMessage, "Ok");
|
||||
//await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PlantBox.Client.Forms.Settings.SettingsPage">
|
||||
xmlns:extensions="clr-namespace:PlantBox.Client.Extensions"
|
||||
x:Class="PlantBox.Client.Forms.Settings.SettingsPage"
|
||||
BindingContext="{StaticResource Settings}">
|
||||
<ContentPage.Resources>
|
||||
<Style TargetType="TextCell">
|
||||
<Setter x:Name="TextCellStyle" Property="TextColor" Value="Green" />
|
||||
</Style>
|
||||
</ContentPage.Resources>
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="Welcome to Xamarin.Forms!"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
</StackLayout>
|
||||
<TableView x:Name="table" Intent="Settings">
|
||||
<TableRoot>
|
||||
<TableSection Title="{extensions:Locale General}">
|
||||
<TextCell Text="{extensions:Locale LanguageSetting}"
|
||||
Detail="{extensions:Locale LanguageSettingDetail}"
|
||||
Tapped="Language_Tapped" />
|
||||
</TableSection>
|
||||
<TableSection Title="{extensions:Locale Notifications}">
|
||||
<SwitchCell Text="{extensions:Locale NotificationsDisabled}"
|
||||
On="{Binding NotificationsDisabled}" />
|
||||
<SwitchCell Text="{extensions:Locale NotificationsMuted}"
|
||||
On="{Binding NotificationsMuted}" />
|
||||
</TableSection>
|
||||
<TableSection Title="{extensions:Locale Server}">
|
||||
<EntryCell Label="IP: "
|
||||
LabelColor="Accent"
|
||||
Placeholder="ip"
|
||||
Text="{Binding BrokerIP}"/>
|
||||
</TableSection>
|
||||
</TableRoot>
|
||||
</TableView>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using PlantBox.Client.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -12,9 +14,38 @@ namespace PlantBox.Client.Forms.Settings
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SettingsPage : ContentPage
|
||||
{
|
||||
public SettingsPage ()
|
||||
public SettingsPage()
|
||||
{
|
||||
InitializeComponent ();
|
||||
InitializeComponent();
|
||||
|
||||
// Save on close
|
||||
Disappearing += async (s, e) => await App.Settings.SaveAsync();
|
||||
|
||||
// Can't apply style to cells, so...
|
||||
ApplyStyle();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyStyle()
|
||||
{
|
||||
foreach (var cell in table.Root.SelectMany(x => x))
|
||||
{
|
||||
if (cell is TextCell textCell)
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
textCell.TextColor = Color.Accent;
|
||||
}
|
||||
}
|
||||
else if (cell is SwitchCell switchCell)
|
||||
{
|
||||
switchCell.Tapped += (s, e) => (s as SwitchCell).On = !(s as SwitchCell).On;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Language_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PushAsync(new LanguageSelectorPage());
|
||||
}
|
||||
}
|
||||
}
|
||||
14
PlantBox.Client/PlantBox.Client/Models/CaptorType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
enum CaptorType
|
||||
{
|
||||
Humidity,
|
||||
Luminosity,
|
||||
Tank,
|
||||
Temperature
|
||||
}
|
||||
}
|
||||
43
PlantBox.Client/PlantBox.Client/Models/Duration.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using PlantBox.Client.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class Duration
|
||||
{
|
||||
public uint Multiplier { get; }
|
||||
public Interval Interval { get; }
|
||||
|
||||
public Duration(uint multiplier, Interval interval)
|
||||
{
|
||||
Multiplier = multiplier;
|
||||
Interval = interval;
|
||||
}
|
||||
|
||||
private string IntervalToString(Interval interval)
|
||||
{
|
||||
switch (interval)
|
||||
{
|
||||
case Interval.Hourly:
|
||||
return Locale.Hour;
|
||||
case Interval.Daily:
|
||||
return Locale.Day;
|
||||
case Interval.Weekly:
|
||||
return Locale.Week;
|
||||
case Interval.Monthly:
|
||||
return Locale.Month;
|
||||
case Interval.Yearly:
|
||||
return Locale.Year;
|
||||
default:
|
||||
return "Invalid interval";
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Multiplier} {IntervalToString(Interval)}{(Multiplier > 1 && !IntervalToString(Interval).EndsWith("s") ? "s" : "")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
28
PlantBox.Client/PlantBox.Client/Models/Historic.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class Historic
|
||||
{
|
||||
public HistoricEntries MinutelyHistoric { get; }
|
||||
public HistoricEntries HourlyHistoric { get; }
|
||||
public HistoricEntries DailyHistoric { get; }
|
||||
public HistoricEntries MonthlyHistoric { get; }
|
||||
|
||||
public Historic
|
||||
(
|
||||
HistoricEntries minutelyHistoric,
|
||||
HistoricEntries hourlyHistoric,
|
||||
HistoricEntries dailyHistoric,
|
||||
HistoricEntries monthlyHistoric
|
||||
)
|
||||
{
|
||||
MinutelyHistoric = minutelyHistoric;
|
||||
HourlyHistoric = hourlyHistoric;
|
||||
DailyHistoric = dailyHistoric;
|
||||
MonthlyHistoric = monthlyHistoric;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
PlantBox.Client/PlantBox.Client/Models/HistoricEntries.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using PlantBox.Client.Extensions;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class HistoricEntries
|
||||
{
|
||||
public DateTime LastTime { get; }
|
||||
public TimeSpan TimeInterval { get; }
|
||||
|
||||
public HistoricEntry[] Humidities { get; }
|
||||
public HistoricEntry[] Luminosities { get; }
|
||||
public HistoricEntry[] Temperatures { get; }
|
||||
|
||||
public DateTime EndTime => LastTime - new TimeSpan(TimeInterval.Ticks * Count);
|
||||
public int Count => Humidities.Length;
|
||||
|
||||
public HistoricEntries(DateTime lastTime, TimeSpan timeInterval, double[] humidities, double[] luminosities, double[] temperatures)
|
||||
{
|
||||
LastTime = lastTime;
|
||||
TimeInterval = timeInterval;
|
||||
Humidities = ValuesToEntries(humidities);
|
||||
Luminosities = ValuesToEntries(luminosities);
|
||||
Temperatures = ValuesToEntries(temperatures);
|
||||
}
|
||||
public HistoricEntries(HistoricResponse response, TimeSpan time) : this(DateTime.Now - response.Time, time, response.Humidities, response.Luminosities, response.Temperatures)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private HistoricEntry[] ValuesToEntries(double[] values)
|
||||
{
|
||||
HistoricEntry[] entries = new HistoricEntry[values.Length];
|
||||
var oldestDate = LastTime - TimeInterval.Multiply(values.Length - 1);
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
entries[i] = new HistoricEntry(oldestDate + TimeInterval.Multiply(i), values[i]);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
PlantBox.Client/PlantBox.Client/Models/HistoricEntry.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class HistoricEntry
|
||||
{
|
||||
public DateTime Date { get; }
|
||||
public double Value { get; }
|
||||
|
||||
public HistoricEntry(DateTime date, double value)
|
||||
{
|
||||
Date = date;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
PlantBox.Client/PlantBox.Client/Models/Interval.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public enum Interval
|
||||
{
|
||||
Hourly,
|
||||
Daily,
|
||||
Weekly,
|
||||
Monthly,
|
||||
Yearly
|
||||
}
|
||||
}
|
||||
@@ -11,41 +11,15 @@ namespace PlantBox.Client.Models
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private ImageSource _image;
|
||||
public ImageSource Image
|
||||
{
|
||||
get => _image;
|
||||
set
|
||||
{
|
||||
if (value != _image)
|
||||
{
|
||||
_image = value;
|
||||
OnPropertyChanged(nameof(Image));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ImageSource Image { get; }
|
||||
public string Title { get; }
|
||||
public Page Page { get; }
|
||||
|
||||
private string _title;
|
||||
public string Title
|
||||
public MenuPageItem(string title, ImageSource image, Page targetPage)
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
if (value != _title)
|
||||
{
|
||||
_title = value;
|
||||
OnPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<Page> PageCreator { get; }
|
||||
|
||||
public MenuPageItem(string title, ImageSource image, Type targetPage)
|
||||
{
|
||||
_image = image;
|
||||
_title = title;
|
||||
PageCreator = () => (Page)Activator.CreateInstance(targetPage);
|
||||
Image = image;
|
||||
Title = title;
|
||||
Page = targetPage;
|
||||
}
|
||||
|
||||
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
|
||||
19
PlantBox.Client/PlantBox.Client/Models/NamedChart.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microcharts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class NamedChart
|
||||
{
|
||||
public Chart Chart { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public NamedChart(Chart chart, string name)
|
||||
{
|
||||
Chart = chart;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
PlantBox.Client/PlantBox.Client/Models/PlantInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
public class PlantInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
public ulong ID { get; }
|
||||
public PlantType Type { get; }
|
||||
public PlantState State { get; }
|
||||
public double HumidityMin { get; }
|
||||
public double HumidityMax { get; }
|
||||
public double LuminosityMin { get; }
|
||||
public double LuminosityMax { get; }
|
||||
public double TemperatureMin { get; }
|
||||
public double TemperatureMax { get; }
|
||||
|
||||
public PlantInfo(string name, ulong id, PlantType type, PlantState state, double humidityMin, double humidityMax, double luminosityMin, double luminosityMax, double temperatureMin, double temperatureMax)
|
||||
{
|
||||
Name = name;
|
||||
ID = id;
|
||||
Type = type;
|
||||
State = state;
|
||||
HumidityMin = humidityMin;
|
||||
HumidityMax = humidityMax;
|
||||
LuminosityMin = luminosityMin;
|
||||
LuminosityMax = luminosityMax;
|
||||
TemperatureMin = temperatureMin;
|
||||
TemperatureMax = temperatureMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
PlantBox.Client/PlantBox.Client/Models/Settings.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Newtonsoft.Json;
|
||||
using PlantBox.Client.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Client.Models
|
||||
{
|
||||
class Settings
|
||||
{
|
||||
[JsonConverter(typeof(JsonCultureInfoConverter))]
|
||||
public CultureInfo Language { get; set; } = CultureInfo.CurrentCulture;
|
||||
|
||||
public bool NotificationsDisabled { get; set; } = false;
|
||||
public bool NotificationsMuted { get; set; } = false;
|
||||
|
||||
public string BrokerIP { get; set; } = "";
|
||||
|
||||
public List<ulong> IDs { get; set; } = new List<ulong>();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Authors>Ilyx</Authors>
|
||||
<Version>0.1.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@@ -11,23 +12,40 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\Images\Add.png" />
|
||||
<None Remove="Resources\Images\Banner.png" />
|
||||
<None Remove="Resources\Images\Help.png" />
|
||||
<None Remove="Resources\Images\Home.png" />
|
||||
<None Remove="Resources\Images\Humidity_Icon.png" />
|
||||
<None Remove="Resources\Images\Info.png" />
|
||||
<None Remove="Resources\Images\Logo_Gray.png" />
|
||||
<None Remove="Resources\Images\Luminosity_Icon.png" />
|
||||
<None Remove="Resources\Images\Settings.png" />
|
||||
<None Remove="Resources\Images\Tank_Icon.png" />
|
||||
<None Remove="Resources\Images\Temperature_Icon.png" />
|
||||
<None Remove="Resources\Images\Type\Ficus.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Images\Add.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Banner.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Help.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Home.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Humidity_Icon.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Info.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Logo_Gray.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Luminosity_Icon.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Settings.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Tank_Icon.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Temperature_Icon.png" />
|
||||
<EmbeddedResource Include="Resources\Images\Type\Ficus.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.4.0.1008975" />
|
||||
<PackageReference Include="Microcharts" Version="0.7.1" />
|
||||
<PackageReference Include="Microcharts.Forms" Version="0.7.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -38,6 +56,9 @@
|
||||
<EmbeddedResource Update="Forms\AboutPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Controls\StatusBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\HomePage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
@@ -47,6 +68,21 @@
|
||||
<EmbeddedResource Update="Forms\MenuPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\HomeItem.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Plant\CaptorsPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Plant\HistoricPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Plant\PlantPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Settings\LanguageSelectorPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Forms\Settings\SettingsPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
@@ -57,10 +93,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ViewModels\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Forms\Settings\LanguageSelectorPage.xaml.cs">
|
||||
<DependentUpon>LanguageSelectorPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Resources\Locale.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
|
||||
BIN
PlantBox.Client/PlantBox.Client/Resources/Images/Add.png
Normal file
|
After Width: | Height: | Size: 213 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 670 B |
BIN
PlantBox.Client/PlantBox.Client/Resources/Images/Home.png
Normal file
|
After Width: | Height: | Size: 408 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 395 B After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 602 B |
BIN
PlantBox.Client/PlantBox.Client/Resources/Images/Tank_Icon.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
BIN
PlantBox.Client/PlantBox.Client/Resources/Images/Type/Ficus.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -19,7 +19,7 @@ namespace PlantBox.Client.Resources {
|
||||
// à l'aide d'un outil, tel que ResGen ou Visual Studio.
|
||||
// Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
|
||||
// avec l'option /str ou régénérez votre projet VS.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Locale {
|
||||
@@ -69,6 +69,15 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Add PlantBox.
|
||||
/// </summary>
|
||||
internal static string AddPlantBox {
|
||||
get {
|
||||
return ResourceManager.GetString("AddPlantBox", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Author.
|
||||
/// </summary>
|
||||
@@ -78,6 +87,69 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Cancel.
|
||||
/// </summary>
|
||||
internal static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Captors.
|
||||
/// </summary>
|
||||
internal static string Captors {
|
||||
get {
|
||||
return ResourceManager.GetString("Captors", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Day.
|
||||
/// </summary>
|
||||
internal static string Day {
|
||||
get {
|
||||
return ResourceManager.GetString("Day", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Enter a valid ID:.
|
||||
/// </summary>
|
||||
internal static string EnterValidID {
|
||||
get {
|
||||
return ResourceManager.GetString("EnterValidID", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Error.
|
||||
/// </summary>
|
||||
internal static string Error {
|
||||
get {
|
||||
return ResourceManager.GetString("Error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à General.
|
||||
/// </summary>
|
||||
internal static string General {
|
||||
get {
|
||||
return ResourceManager.GetString("General", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Historic.
|
||||
/// </summary>
|
||||
internal static string Historic {
|
||||
get {
|
||||
return ResourceManager.GetString("Historic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Home.
|
||||
/// </summary>
|
||||
@@ -87,6 +159,24 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Hour.
|
||||
/// </summary>
|
||||
internal static string Hour {
|
||||
get {
|
||||
return ResourceManager.GetString("Hour", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Humidity.
|
||||
/// </summary>
|
||||
internal static string Humidity {
|
||||
get {
|
||||
return ResourceManager.GetString("Humidity", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Informations.
|
||||
/// </summary>
|
||||
@@ -96,6 +186,60 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Interval:.
|
||||
/// </summary>
|
||||
internal static string Interval {
|
||||
get {
|
||||
return ResourceManager.GetString("Interval", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Invalid id.
|
||||
/// </summary>
|
||||
internal static string InvalidID {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidID", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Changes will take effect at next start.
|
||||
/// </summary>
|
||||
internal static string LanguageChangedMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("LanguageChangedMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Language changed.
|
||||
/// </summary>
|
||||
internal static string LanguageChangedTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("LanguageChangedTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Language.
|
||||
/// </summary>
|
||||
internal static string LanguageSetting {
|
||||
get {
|
||||
return ResourceManager.GetString("LanguageSetting", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Change the language.
|
||||
/// </summary>
|
||||
internal static string LanguageSettingDetail {
|
||||
get {
|
||||
return ResourceManager.GetString("LanguageSettingDetail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Libraries.
|
||||
/// </summary>
|
||||
@@ -105,6 +249,78 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Luminosity.
|
||||
/// </summary>
|
||||
internal static string Luminosity {
|
||||
get {
|
||||
return ResourceManager.GetString("Luminosity", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Media.
|
||||
/// </summary>
|
||||
internal static string Media {
|
||||
get {
|
||||
return ResourceManager.GetString("Media", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Month.
|
||||
/// </summary>
|
||||
internal static string Month {
|
||||
get {
|
||||
return ResourceManager.GetString("Month", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Notifications.
|
||||
/// </summary>
|
||||
internal static string Notifications {
|
||||
get {
|
||||
return ResourceManager.GetString("Notifications", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Desactivate notifications.
|
||||
/// </summary>
|
||||
internal static string NotificationsDisabled {
|
||||
get {
|
||||
return ResourceManager.GetString("NotificationsDisabled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Mute notifications.
|
||||
/// </summary>
|
||||
internal static string NotificationsMuted {
|
||||
get {
|
||||
return ResourceManager.GetString("NotificationsMuted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à OK.
|
||||
/// </summary>
|
||||
internal static string OK {
|
||||
get {
|
||||
return ResourceManager.GetString("OK", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Server.
|
||||
/// </summary>
|
||||
internal static string Server {
|
||||
get {
|
||||
return ResourceManager.GetString("Server", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Settings.
|
||||
/// </summary>
|
||||
@@ -114,6 +330,15 @@ namespace PlantBox.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Temperature.
|
||||
/// </summary>
|
||||
internal static string Temperature {
|
||||
get {
|
||||
return ResourceManager.GetString("Temperature", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Version.
|
||||
/// </summary>
|
||||
@@ -122,5 +347,23 @@ namespace PlantBox.Client.Resources {
|
||||
return ResourceManager.GetString("Version", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Week.
|
||||
/// </summary>
|
||||
internal static string Week {
|
||||
get {
|
||||
return ResourceManager.GetString("Week", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recherche une chaîne localisée semblable à Year.
|
||||
/// </summary>
|
||||
internal static string Year {
|
||||
get {
|
||||
return ResourceManager.GetString("Year", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,22 +120,103 @@
|
||||
<data name="About" xml:space="preserve">
|
||||
<value>À Propos</value>
|
||||
</data>
|
||||
<data name="AddPlantBox" xml:space="preserve">
|
||||
<value>Ajouter un PlantBox</value>
|
||||
</data>
|
||||
<data name="Author" xml:space="preserve">
|
||||
<value>Auteur</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Annuler</value>
|
||||
</data>
|
||||
<data name="Captors" xml:space="preserve">
|
||||
<value>Capteurs</value>
|
||||
</data>
|
||||
<data name="Day" xml:space="preserve">
|
||||
<value>Jour</value>
|
||||
</data>
|
||||
<data name="EnterValidID" xml:space="preserve">
|
||||
<value>Entrer une id valide:</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Erreur</value>
|
||||
</data>
|
||||
<data name="General" xml:space="preserve">
|
||||
<value>Général</value>
|
||||
</data>
|
||||
<data name="Historic" xml:space="preserve">
|
||||
<value>Historique</value>
|
||||
</data>
|
||||
<data name="HomePageTitle" xml:space="preserve">
|
||||
<value>Accueil</value>
|
||||
</data>
|
||||
<data name="Hour" xml:space="preserve">
|
||||
<value>Heure</value>
|
||||
</data>
|
||||
<data name="Humidity" xml:space="preserve">
|
||||
<value>Humidité</value>
|
||||
</data>
|
||||
<data name="Informations" xml:space="preserve">
|
||||
<value>Informations</value>
|
||||
</data>
|
||||
<data name="Interval" xml:space="preserve">
|
||||
<value>Intervalle:</value>
|
||||
</data>
|
||||
<data name="InvalidID" xml:space="preserve">
|
||||
<value>Id invalide</value>
|
||||
</data>
|
||||
<data name="LanguageChangedMessage" xml:space="preserve">
|
||||
<value>Les changements prendront effet au prochain lancement</value>
|
||||
</data>
|
||||
<data name="LanguageChangedTitle" xml:space="preserve">
|
||||
<value>Langue modifiée</value>
|
||||
</data>
|
||||
<data name="LanguageSetting" xml:space="preserve">
|
||||
<value>Langue</value>
|
||||
</data>
|
||||
<data name="LanguageSettingDetail" xml:space="preserve">
|
||||
<value>Modifie la langue</value>
|
||||
</data>
|
||||
<data name="Libraries" xml:space="preserve">
|
||||
<value>Bibliothèques</value>
|
||||
</data>
|
||||
<data name="Luminosity" xml:space="preserve">
|
||||
<value>Luminosité</value>
|
||||
</data>
|
||||
<data name="Media" xml:space="preserve">
|
||||
<value>Media</value>
|
||||
</data>
|
||||
<data name="Month" xml:space="preserve">
|
||||
<value>Mois</value>
|
||||
</data>
|
||||
<data name="Notifications" xml:space="preserve">
|
||||
<value>Notifications</value>
|
||||
</data>
|
||||
<data name="NotificationsDisabled" xml:space="preserve">
|
||||
<value>Désactiver les notifications</value>
|
||||
</data>
|
||||
<data name="NotificationsMuted" xml:space="preserve">
|
||||
<value>Rendre muet les notifications</value>
|
||||
</data>
|
||||
<data name="OK" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="Server" xml:space="preserve">
|
||||
<value>Serveur</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Options</value>
|
||||
<value>Préférences</value>
|
||||
</data>
|
||||
<data name="Temperature" xml:space="preserve">
|
||||
<value>Température</value>
|
||||
</data>
|
||||
<data name="Version" xml:space="preserve">
|
||||
<value>Version</value>
|
||||
</data>
|
||||
<data name="Week" xml:space="preserve">
|
||||
<value>Semaine</value>
|
||||
</data>
|
||||
<data name="Year" xml:space="preserve">
|
||||
<value>Année</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -120,22 +120,103 @@
|
||||
<data name="About" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="AddPlantBox" xml:space="preserve">
|
||||
<value>Add PlantBox</value>
|
||||
</data>
|
||||
<data name="Author" xml:space="preserve">
|
||||
<value>Author</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Captors" xml:space="preserve">
|
||||
<value>Captors</value>
|
||||
</data>
|
||||
<data name="Day" xml:space="preserve">
|
||||
<value>Day</value>
|
||||
</data>
|
||||
<data name="EnterValidID" xml:space="preserve">
|
||||
<value>Enter a valid ID:</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="General" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Historic" xml:space="preserve">
|
||||
<value>Historic</value>
|
||||
</data>
|
||||
<data name="HomePageTitle" xml:space="preserve">
|
||||
<value>Home</value>
|
||||
</data>
|
||||
<data name="Hour" xml:space="preserve">
|
||||
<value>Hour</value>
|
||||
</data>
|
||||
<data name="Humidity" xml:space="preserve">
|
||||
<value>Humidity</value>
|
||||
</data>
|
||||
<data name="Informations" xml:space="preserve">
|
||||
<value>Informations</value>
|
||||
</data>
|
||||
<data name="Interval" xml:space="preserve">
|
||||
<value>Interval:</value>
|
||||
</data>
|
||||
<data name="InvalidID" xml:space="preserve">
|
||||
<value>Invalid id</value>
|
||||
</data>
|
||||
<data name="LanguageChangedMessage" xml:space="preserve">
|
||||
<value>Changes will take effect at next start</value>
|
||||
</data>
|
||||
<data name="LanguageChangedTitle" xml:space="preserve">
|
||||
<value>Language changed</value>
|
||||
</data>
|
||||
<data name="LanguageSetting" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="LanguageSettingDetail" xml:space="preserve">
|
||||
<value>Change the language</value>
|
||||
</data>
|
||||
<data name="Libraries" xml:space="preserve">
|
||||
<value>Libraries</value>
|
||||
</data>
|
||||
<data name="Luminosity" xml:space="preserve">
|
||||
<value>Luminosity</value>
|
||||
</data>
|
||||
<data name="Media" xml:space="preserve">
|
||||
<value>Media</value>
|
||||
</data>
|
||||
<data name="Month" xml:space="preserve">
|
||||
<value>Month</value>
|
||||
</data>
|
||||
<data name="Notifications" xml:space="preserve">
|
||||
<value>Notifications</value>
|
||||
</data>
|
||||
<data name="NotificationsDisabled" xml:space="preserve">
|
||||
<value>Desactivate notifications</value>
|
||||
</data>
|
||||
<data name="NotificationsMuted" xml:space="preserve">
|
||||
<value>Mute notifications</value>
|
||||
</data>
|
||||
<data name="OK" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="Server" xml:space="preserve">
|
||||
<value>Server</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Temperature" xml:space="preserve">
|
||||
<value>Temperature</value>
|
||||
</data>
|
||||
<data name="Version" xml:space="preserve">
|
||||
<value>Version</value>
|
||||
</data>
|
||||
<data name="Week" xml:space="preserve">
|
||||
<value>Week</value>
|
||||
</data>
|
||||
<data name="Year" xml:space="preserve">
|
||||
<value>Year</value>
|
||||
</data>
|
||||
</root>
|
||||
77
PlantBox.Client/PlantBox.Client/Util/FormsDialog.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using PlantBox.Client.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace PlantBox.Client.Util
|
||||
{
|
||||
class FormsDialog
|
||||
{
|
||||
public static Task<string> InputBox(INavigation navigation, string title, string description)
|
||||
{
|
||||
// wait in this proc, until user did his input
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
|
||||
var lblTitle = new Label { Text = title, HorizontalOptions = LayoutOptions.Center, FontAttributes = FontAttributes.Bold };
|
||||
var lblMessage = new Label { Text = description };
|
||||
var txtInput = new Entry { Text = "" };
|
||||
|
||||
var btnOk = new Button
|
||||
{
|
||||
Text = Locale.OK,
|
||||
WidthRequest = 100,
|
||||
BackgroundColor = Color.FromRgb(0.8, 0.8, 0.8),
|
||||
};
|
||||
btnOk.Clicked += async (s, e) =>
|
||||
{
|
||||
// close page
|
||||
var result = txtInput.Text;
|
||||
await navigation.PopModalAsync();
|
||||
// pass result
|
||||
tcs.SetResult(result);
|
||||
};
|
||||
|
||||
var btnCancel = new Button
|
||||
{
|
||||
Text = Locale.Cancel,
|
||||
WidthRequest = 100,
|
||||
BackgroundColor = Color.FromRgb(0.8, 0.8, 0.8)
|
||||
};
|
||||
btnCancel.Clicked += async (s, e) =>
|
||||
{
|
||||
// close page
|
||||
await navigation.PopModalAsync();
|
||||
// pass empty result
|
||||
tcs.SetResult(null);
|
||||
};
|
||||
|
||||
var slButtons = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
Children = { btnOk, btnCancel },
|
||||
};
|
||||
|
||||
var layout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(0, 40, 0, 0),
|
||||
VerticalOptions = LayoutOptions.StartAndExpand,
|
||||
HorizontalOptions = LayoutOptions.CenterAndExpand,
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Children = { lblTitle, lblMessage, txtInput, slButtons },
|
||||
};
|
||||
|
||||
// create and show page
|
||||
var page = new ContentPage();
|
||||
page.Content = layout;
|
||||
navigation.PushModalAsync(page);
|
||||
// open keyboard
|
||||
txtInput.Focus();
|
||||
|
||||
// code is waiting her, until result is passed with tcs.SetResult() in btn-Clicked
|
||||
// then proc returns the result
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
PlantBox.Client/PlantBox.Client/ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using PlantBox.Client.Models;
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Client.ViewModels
|
||||
{
|
||||
class HomeViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private ObservableCollection<PlantInfo> _plants;
|
||||
public ObservableCollection<PlantInfo> Plants
|
||||
{
|
||||
get => _plants;
|
||||
set
|
||||
{
|
||||
if (value != _plants)
|
||||
{
|
||||
_plants = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HomeViewModel()
|
||||
{
|
||||
Plants = LoadPlants(App.Settings.IDs);
|
||||
}
|
||||
|
||||
public ObservableCollection<PlantInfo> LoadPlants(IEnumerable<ulong> ids)
|
||||
{
|
||||
if (App.Settings.BrokerIP == "")
|
||||
{
|
||||
return new ObservableCollection<PlantInfo>();
|
||||
}
|
||||
|
||||
IEnumerable<PlantInfo> infos = GetPlantInfos(ids);
|
||||
|
||||
return new ObservableCollection<PlantInfo>(infos);
|
||||
}
|
||||
|
||||
private IEnumerable<PlantInfo> GetPlantInfos(IEnumerable<ulong> ids)
|
||||
{
|
||||
using (var client = new TcpClient(App.Settings.BrokerIP, Connection.TCP_CLIENT_PORT))
|
||||
using (var stream = new CommandStream(client.GetStream()))
|
||||
{
|
||||
|
||||
foreach (ulong id in ids)
|
||||
{
|
||||
stream.Send(new InfoRequest().ToCommandPacket(id));
|
||||
(_, var responseInfo) = stream.Receive<InfoResponse>();
|
||||
|
||||
yield return new PlantInfo
|
||||
(
|
||||
responseInfo.Name,
|
||||
id,
|
||||
responseInfo.Type,
|
||||
responseInfo.State,
|
||||
responseInfo.HumidityMin,
|
||||
responseInfo.HumidityMax,
|
||||
responseInfo.LuminosityMin,
|
||||
responseInfo.LuminosityMax,
|
||||
responseInfo.TemperatureMin,
|
||||
responseInfo.TemperatureMax
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
181
PlantBox.Client/PlantBox.Client/ViewModels/PlantViewModel.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using PlantBox.Client.Models;
|
||||
using PlantBox.Shared.Communication;
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Client.ViewModels
|
||||
{
|
||||
public class PlantViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public PlantViewModel(PlantInfo plant)
|
||||
{
|
||||
PlantInfo = plant;
|
||||
|
||||
LoadValues();
|
||||
}
|
||||
|
||||
private Historic _historic;
|
||||
public Historic Historic
|
||||
{
|
||||
get => _historic;
|
||||
set
|
||||
{
|
||||
if (value != _historic)
|
||||
{
|
||||
_historic = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PlantInfo _plantInfo;
|
||||
public PlantInfo PlantInfo
|
||||
{
|
||||
get => _plantInfo;
|
||||
set
|
||||
{
|
||||
if (value != _plantInfo)
|
||||
{
|
||||
_plantInfo = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CaptorValue _humidityCaptor;
|
||||
public CaptorValue HumidityCaptor
|
||||
{
|
||||
get => _humidityCaptor;
|
||||
set
|
||||
{
|
||||
if (value != _humidityCaptor)
|
||||
{
|
||||
_humidityCaptor = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CaptorValue _luminosityCaptor;
|
||||
public CaptorValue LuminosityCaptor
|
||||
{
|
||||
get => _luminosityCaptor;
|
||||
set
|
||||
{
|
||||
if (value != _luminosityCaptor)
|
||||
{
|
||||
_luminosityCaptor = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CaptorValue _temperatureCaptor;
|
||||
public CaptorValue TemperatureCaptor
|
||||
{
|
||||
get => _temperatureCaptor;
|
||||
set
|
||||
{
|
||||
if (value != _temperatureCaptor)
|
||||
{
|
||||
_temperatureCaptor = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _tankValue;
|
||||
public double TankValue
|
||||
{
|
||||
get => _tankValue;
|
||||
set
|
||||
{
|
||||
if (value != _tankValue)
|
||||
{
|
||||
_tankValue = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Duration _historicDuration;
|
||||
public Duration HistoricDuration
|
||||
{
|
||||
get => _historicDuration;
|
||||
set
|
||||
{
|
||||
if (value != _historicDuration)
|
||||
{
|
||||
_historicDuration = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadValues()
|
||||
{
|
||||
CaptorValue humidityCaptor;
|
||||
CaptorValue luminosityCaptor;
|
||||
CaptorValue temperatureCaptor;
|
||||
double tankValue;
|
||||
HistoricResponse minutelyHistoric;
|
||||
HistoricResponse hourlyHistoric;
|
||||
HistoricResponse dailyHistoric;
|
||||
HistoricResponse monthlyHistoric;
|
||||
|
||||
using (var client = new TcpClient(App.Settings.BrokerIP, Connection.TCP_CLIENT_PORT))
|
||||
using (var stream = new CommandStream(client.GetStream()))
|
||||
{
|
||||
// Captors info
|
||||
stream.Send(new CaptorsRequest().ToCommandPacket(PlantInfo.ID));
|
||||
(_, var captorsReponse) = stream.Receive<CaptorsResponse>();
|
||||
|
||||
humidityCaptor = new CaptorValue(PlantInfo.HumidityMin, PlantInfo.HumidityMax, captorsReponse.Humidity);
|
||||
luminosityCaptor = new CaptorValue(PlantInfo.LuminosityMin, PlantInfo.LuminosityMax, captorsReponse.Luminosity);
|
||||
temperatureCaptor = new CaptorValue(PlantInfo.TemperatureMin, PlantInfo.TemperatureMax, captorsReponse.Temperature);
|
||||
tankValue = captorsReponse.Tank;
|
||||
|
||||
// Historic
|
||||
stream.Send(new HistoricRequest(HistoricInterval.Minutely, 12).ToCommandPacket(PlantInfo.ID));
|
||||
(_, minutelyHistoric) = stream.Receive<HistoricResponse>();
|
||||
|
||||
stream.Send(new HistoricRequest(HistoricInterval.Hourly, 48).ToCommandPacket(PlantInfo.ID));
|
||||
(_, hourlyHistoric) = stream.Receive<HistoricResponse>();
|
||||
|
||||
stream.Send(new HistoricRequest(HistoricInterval.Daily, 186).ToCommandPacket(PlantInfo.ID));
|
||||
(_, dailyHistoric) = stream.Receive<HistoricResponse>();
|
||||
|
||||
stream.Send(new HistoricRequest(HistoricInterval.Monthly, 36).ToCommandPacket(PlantInfo.ID));
|
||||
(_, monthlyHistoric) = stream.Receive<HistoricResponse>();
|
||||
}
|
||||
|
||||
HumidityCaptor = humidityCaptor;
|
||||
LuminosityCaptor = luminosityCaptor;
|
||||
TemperatureCaptor = temperatureCaptor;
|
||||
TankValue = tankValue;
|
||||
|
||||
var historic = new Historic
|
||||
(
|
||||
new HistoricEntries(minutelyHistoric, TimeSpan.FromMinutes(5)),
|
||||
new HistoricEntries(hourlyHistoric, TimeSpan.FromHours(1)),
|
||||
new HistoricEntries(dailyHistoric, TimeSpan.FromDays(1)),
|
||||
new HistoricEntries(monthlyHistoric, TimeSpan.FromDays(31))
|
||||
);
|
||||
Historic = historic;
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
120
PlantBox.Client/PlantBox.Client/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using Newtonsoft.Json;
|
||||
using PlantBox.Client.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Client.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public const string FileName = "Settings.json";
|
||||
public string FilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), FileName);
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
// Settings
|
||||
public CultureInfo Language
|
||||
{
|
||||
get => _settings.Language;
|
||||
set
|
||||
{
|
||||
if (value != _settings.Language)
|
||||
{
|
||||
_settings.Language = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool NotificationsDisabled
|
||||
{
|
||||
get => _settings.NotificationsDisabled;
|
||||
set
|
||||
{
|
||||
if (value != _settings.NotificationsDisabled)
|
||||
{
|
||||
_settings.NotificationsDisabled = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool NotificationsMuted
|
||||
{
|
||||
get => _settings.NotificationsMuted;
|
||||
set
|
||||
{
|
||||
if (value != _settings.NotificationsMuted)
|
||||
{
|
||||
_settings.NotificationsMuted = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string BrokerIP
|
||||
{
|
||||
get => _settings.BrokerIP;
|
||||
set
|
||||
{
|
||||
if (value != _settings.BrokerIP)
|
||||
{
|
||||
_settings.BrokerIP = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ulong> IDs
|
||||
{
|
||||
get => _settings.IDs;
|
||||
set
|
||||
{
|
||||
if (value != _settings.IDs)
|
||||
{
|
||||
_settings.IDs = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
_settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(FilePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings = new Settings();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
using (var file = new StreamWriter(FilePath, false))
|
||||
{
|
||||
file.WriteLine(JsonConvert.SerializeObject(_settings));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveAsync()
|
||||
{
|
||||
using (var file = new StreamWriter(FilePath, false))
|
||||
{
|
||||
await file.WriteLineAsync(JsonConvert.SerializeObject(_settings));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
18
PlantBox.Shared/Communication/Command.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// All valid commands as described in the Wiki > Commands
|
||||
/// </summary>
|
||||
public enum Command
|
||||
{
|
||||
Captors,
|
||||
Historic,
|
||||
Info,
|
||||
Invalid,
|
||||
Ping
|
||||
}
|
||||
}
|
||||
60
PlantBox.Shared/Communication/CommandPacket.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a Packet as described in the Wiki > Protocol
|
||||
/// </summary>
|
||||
public class CommandPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// A valid packet command, see the Wiki > Commands
|
||||
/// </summary>
|
||||
public Command Command { get; }
|
||||
/// <summary>
|
||||
/// A valid id
|
||||
/// </summary>
|
||||
public ulong ID { get; }
|
||||
/// <summary>
|
||||
/// Any arguments, see the Wiki page of a command for more infos
|
||||
/// </summary>
|
||||
public string[] Arguments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="CommandPacket"/>
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="arguments"></param>
|
||||
public CommandPacket(Command command, ulong id, string[] arguments)
|
||||
{
|
||||
Command = command;
|
||||
ID = id;
|
||||
Arguments = arguments ?? Array.Empty<string>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Create a <see cref="CommandPacket"/>
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="arguments"></param>
|
||||
public CommandPacket(Command command, params string[] arguments)
|
||||
{
|
||||
Command = command;
|
||||
ID = ulong.Parse(arguments[0]);
|
||||
Arguments = arguments != null && arguments.Length > 1 ? arguments.Skip(1).ToArray() : Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a <see cref="CommandPacket"/> to a valid text representation of a packet, see Wiki > Protocol
|
||||
/// </summary>
|
||||
/// <returns>A valid packet text representation, ready to be sent</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Command.ToString().ToUpperInvariant()}\n{ID}{(Arguments.Length > 0 ? $";{string.Join(";", Arguments)}" : string.Empty)}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
181
PlantBox.Shared/Communication/CommandStream.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using PlantBox.Shared.Communication.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlantBox.Shared.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrap a <see cref="NetworkStream"/> to send <see cref="CommandPacket"/> easily, see Wiki > Protocol
|
||||
/// </summary>
|
||||
public class CommandStream : IDisposable
|
||||
{
|
||||
private NetworkStream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CommandStream"/>
|
||||
/// </summary>
|
||||
/// <param name="networkStream">A connected and valid <see cref="NetworkStream"/></param>
|
||||
public CommandStream(NetworkStream networkStream)
|
||||
{
|
||||
_stream = networkStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release resources associated with the underlying stream
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="CommandPacket"/> from the stream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CommandPacket Receive()
|
||||
{
|
||||
// Length
|
||||
byte[] buffer = new byte[4];
|
||||
_stream.Read(buffer, 0, buffer.Length);
|
||||
|
||||
uint length = BitConverter.ToUInt32(buffer, 0);
|
||||
|
||||
// Data
|
||||
buffer = new byte[length];
|
||||
int position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
int bytesToRead = Math.Min(Connection.BUFFER_SIZE, (int)length - position);
|
||||
|
||||
int bytesRead = _stream.Read(buffer, position, bytesToRead);
|
||||
|
||||
position += bytesRead;
|
||||
}
|
||||
|
||||
string[] packet = Encoding.UTF8.GetString(buffer).Split('\n');
|
||||
|
||||
if (!Enum.TryParse(packet[0], true, out Command command))
|
||||
{
|
||||
command = Command.Invalid;
|
||||
}
|
||||
|
||||
return new CommandPacket(command, packet[1].Split(';'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="CommandPacket"/> from the stream and deserialize data to a <see cref="CommandSerializable{T}"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A valid <see cref="CommandSerializable{T}"/></typeparam>
|
||||
/// <returns></returns>
|
||||
public (CommandPacket, T) Receive<T>() where T : CommandSerializable<T>, new()
|
||||
{
|
||||
var packet = Receive();
|
||||
|
||||
return (packet, new T().Deserialize(packet.Arguments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="CommandPacket"/> from the stream asynchronously
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<CommandPacket> ReceiveAsync()
|
||||
{
|
||||
// Length
|
||||
byte[] buffer = new byte[4];
|
||||
await _stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
|
||||
uint length = BitConverter.ToUInt32(buffer, 0);
|
||||
|
||||
// Data
|
||||
buffer = new byte[length];
|
||||
int position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
int bytesToRead = Math.Min(Connection.BUFFER_SIZE, (int)length - position);
|
||||
|
||||
int bytesRead = await _stream.ReadAsync(buffer, position, bytesToRead);
|
||||
|
||||
position += bytesRead;
|
||||
}
|
||||
|
||||
string[] packet = Encoding.UTF8.GetString(buffer).Split('\n');
|
||||
|
||||
if (!Enum.TryParse(packet[0], true, out Command command))
|
||||
{
|
||||
command = Command.Invalid;
|
||||
}
|
||||
|
||||
return new CommandPacket(command, packet[1].Split(';'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="CommandPacket"/> from the stream and deserialize data to a <see cref="CommandSerializable{T}"/> asynchronously
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A valid <see cref="CommandSerializable{T}"/></typeparam>
|
||||
/// <returns></returns>
|
||||
public async Task<(CommandPacket, T)> ReceiveAsync<T>() where T : CommandSerializable<T>, new()
|
||||
{
|
||||
var packet = await ReceiveAsync();
|
||||
|
||||
return (packet, new T().Deserialize(packet.Arguments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a <see cref="CommandPacket"/> in the stream
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
public void Send(CommandPacket command)
|
||||
{
|
||||
string packet = command.ToString();
|
||||
byte[] data = Encoding.UTF8.GetBytes(packet);
|
||||
uint length = (uint)data.Length;
|
||||
|
||||
// Length
|
||||
_stream.Write(BitConverter.GetBytes(length), 0, 4);
|
||||
|
||||
// Data
|
||||
int position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
int bytesToWrite = Math.Min(Connection.BUFFER_SIZE, (int)length - position);
|
||||
|
||||
_stream.Write(data, position, bytesToWrite);
|
||||
|
||||
position += bytesToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a <see cref="CommandPacket"/> in the stream asynchronously
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendAsync(CommandPacket command)
|
||||
{
|
||||
string packet = command.ToString();
|
||||
byte[] data = Encoding.UTF8.GetBytes(packet);
|
||||
uint length = (uint)data.Length;
|
||||
|
||||
// Length
|
||||
await _stream.WriteAsync(BitConverter.GetBytes(length), 0, 4);
|
||||
|
||||
// Data
|
||||
int position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
int bytesToWrite = Math.Min(Connection.BUFFER_SIZE, (int)length - position);
|
||||
|
||||
await _stream.WriteAsync(data, position, bytesToWrite);
|
||||
|
||||
position += bytesToWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
PlantBox.Shared/Communication/Commands/CaptorValue.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using PlantBox.Shared.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class CaptorValue
|
||||
{
|
||||
public const char ValueSeparator = '/';
|
||||
|
||||
public double Min { get; set; }
|
||||
public double Max { get; set; }
|
||||
public double Value { get; set; }
|
||||
|
||||
public CaptorValue()
|
||||
{
|
||||
|
||||
}
|
||||
public CaptorValue(string argument)
|
||||
{
|
||||
string[] arguments = argument.Split(ValueSeparator);
|
||||
|
||||
Min = arguments[0].ToDouble();
|
||||
Max = arguments[1].ToDouble();
|
||||
Value = arguments[2].ToDouble();
|
||||
}
|
||||
public CaptorValue(double min, double max, double value)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string ToArgument()
|
||||
{
|
||||
return $"{Min}{ValueSeparator}{Max}{ValueSeparator}{Value}";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToArgument();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
PlantBox.Shared/Communication/Commands/CaptorsRequest.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class CaptorsRequest : CommandSerializable<CaptorsRequest>
|
||||
{
|
||||
public override Command Command => Command.Captors;
|
||||
|
||||
public override CaptorsRequest Deserialize(string[] arguments)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
61
PlantBox.Shared/Communication/Commands/CaptorsResponse.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using PlantBox.Shared.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class CaptorsResponse : CommandSerializable<CaptorsResponse>
|
||||
{
|
||||
public override Command Command => Command.Captors;
|
||||
|
||||
public double Humidity { get; set; }
|
||||
public double Luminosity { get; set; }
|
||||
public double Temperature { get; set; }
|
||||
public double Tank { get; set; }
|
||||
|
||||
public CaptorsResponse()
|
||||
{
|
||||
|
||||
}
|
||||
public CaptorsResponse(double humidity, double luminosity, double temperature, double tank)
|
||||
{
|
||||
Humidity = humidity;
|
||||
Luminosity = luminosity;
|
||||
Temperature = temperature;
|
||||
Tank = tank;
|
||||
}
|
||||
|
||||
public override CaptorsResponse Deserialize(string[] arguments)
|
||||
{
|
||||
if (arguments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(arguments));
|
||||
}
|
||||
if (arguments.Length < 4)
|
||||
{
|
||||
throw new ArgumentException($"Excepted 4 arguments, got {arguments.Length}");
|
||||
}
|
||||
|
||||
Humidity = arguments[0].ToDouble();
|
||||
Luminosity = arguments[1].ToDouble();
|
||||
Temperature = arguments[2].ToDouble();
|
||||
Tank = arguments[3].ToDouble();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Humidity.ToArgument(),
|
||||
Luminosity.ToArgument(),
|
||||
Temperature.ToArgument(),
|
||||
Tank.ToArgument()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A class serializable to a <see cref="CommandPacket"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class CommandSerializable<T> where T : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Default <see cref="Communication.Command"/>
|
||||
/// </summary>
|
||||
public abstract Command Command { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialize all the values in a string <see cref="Array"/>,
|
||||
/// should contains everything to deserialize
|
||||
/// </summary>
|
||||
/// <returns>Valid arguments for a <see cref="CommandPacket"/></returns>
|
||||
public abstract string[] Serialize();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of the class from a string <see cref="Array"/>
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments from the <see cref="CommandSerializable{T}.Serialize"/> method</param>
|
||||
/// <returns><see cref="T"/> initilized accordingly</returns>
|
||||
public abstract T Deserialize(string[] arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a <see cref="CommandSerializable{T}"/> to a <see cref="CommandPacket"/>, using <see cref="Command"/>
|
||||
/// </summary>
|
||||
/// <param name="id">A valid id</param>
|
||||
/// <returns></returns>
|
||||
public CommandPacket ToCommandPacket(ulong id)
|
||||
{
|
||||
return new CommandPacket(Command, id, Serialize());
|
||||
}
|
||||
/// <summary>
|
||||
/// Convert a <see cref="CommandSerializable{T}"/> to a <see cref="CommandPacket"/>
|
||||
/// </summary>
|
||||
/// <param name="command">Command type</param>
|
||||
/// <param name="id">A valid id</param>
|
||||
/// <returns></returns>
|
||||
public CommandPacket ToCommandPacket(Command command, ulong id)
|
||||
{
|
||||
return new CommandPacket(command, id, Serialize());
|
||||
}
|
||||
}
|
||||
}
|
||||
15
PlantBox.Shared/Communication/Commands/HistoricInterval.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public enum HistoricInterval
|
||||
{
|
||||
Monthly,
|
||||
Weekly,
|
||||
Daily,
|
||||
Hourly,
|
||||
Minutely
|
||||
}
|
||||
}
|
||||
52
PlantBox.Shared/Communication/Commands/HistoricRequest.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using PlantBox.Shared.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class HistoricRequest : CommandSerializable<HistoricRequest>
|
||||
{
|
||||
public override Command Command => Command.Historic;
|
||||
|
||||
public HistoricInterval Interval { get; set; }
|
||||
public int Number { get; set; }
|
||||
|
||||
public HistoricRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public HistoricRequest(HistoricInterval interval, int number)
|
||||
{
|
||||
Interval = interval;
|
||||
Number = number;
|
||||
}
|
||||
|
||||
public override HistoricRequest Deserialize(string[] arguments)
|
||||
{
|
||||
if (arguments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(arguments));
|
||||
}
|
||||
if (arguments.Length < 2)
|
||||
{
|
||||
throw new ArgumentException($"Excepted 2 arguments, got {arguments.Length}");
|
||||
}
|
||||
|
||||
Interval = arguments[0].ToEnumValue<HistoricInterval>();
|
||||
Number = arguments[1].ToInt();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Interval.ToString(),
|
||||
Number.ToArgument()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
73
PlantBox.Shared/Communication/Commands/HistoricResponse.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using PlantBox.Shared.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class HistoricResponse : CommandSerializable<HistoricResponse>
|
||||
{
|
||||
public const char ValueSeparator = '/';
|
||||
|
||||
public override Command Command => Command.Historic;
|
||||
|
||||
public TimeSpan Time { get; set; }
|
||||
public double[] Humidities { get; set; }
|
||||
public double[] Luminosities { get; set; }
|
||||
public double[] Temperatures { get; set; }
|
||||
|
||||
public HistoricResponse()
|
||||
{
|
||||
|
||||
}
|
||||
public HistoricResponse(TimeSpan time, double[] humidities, double[] luminosities, double[] temperatures)
|
||||
{
|
||||
Time = time;
|
||||
Humidities = humidities;
|
||||
Luminosities = luminosities;
|
||||
Temperatures = temperatures;
|
||||
}
|
||||
|
||||
public override HistoricResponse Deserialize(string[] arguments)
|
||||
{
|
||||
double[] GetArray(string argument)
|
||||
{
|
||||
return argument.Split(ValueSeparator).Select(x => x.ToDouble()).ToArray();
|
||||
}
|
||||
|
||||
if (arguments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(arguments));
|
||||
}
|
||||
if (arguments.Length < 4)
|
||||
{
|
||||
throw new ArgumentException($"Excepted 4 arguments, got {arguments.Length}");
|
||||
}
|
||||
|
||||
Time = TimeSpan.FromSeconds(arguments[0].ToDouble());
|
||||
Humidities = GetArray(arguments[1]);
|
||||
Luminosities = GetArray(arguments[2]);
|
||||
Temperatures = GetArray(arguments[3]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
string Join(double[] values)
|
||||
{
|
||||
return string.Join(ValueSeparator.ToString(), values.Select(x => x.ToArgument()));
|
||||
}
|
||||
|
||||
return new[]
|
||||
{
|
||||
Time.TotalSeconds.ToArgument(),
|
||||
Join(Humidities),
|
||||
Join(Luminosities),
|
||||
Join(Temperatures)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
21
PlantBox.Shared/Communication/Commands/InfoRequest.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class InfoRequest : CommandSerializable<InfoRequest>
|
||||
{
|
||||
public override Command Command => Command.Info;
|
||||
|
||||
public override InfoRequest Deserialize(string[] arguments)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
PlantBox.Shared/Communication/Commands/InfoResponse.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using PlantBox.Shared.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class InfoResponse : CommandSerializable<InfoResponse>
|
||||
{
|
||||
public override Command Command => Command.Info;
|
||||
|
||||
public string Name { get; set; }
|
||||
public PlantType Type { get; set; }
|
||||
public PlantState State { get; set; }
|
||||
public double HumidityMin { get; set; }
|
||||
public double HumidityMax { get; set; }
|
||||
public double LuminosityMin { get; set; }
|
||||
public double LuminosityMax { get; set; }
|
||||
public double TemperatureMin { get; set; }
|
||||
public double TemperatureMax { get; set; }
|
||||
|
||||
public InfoResponse()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public InfoResponse(string name, PlantType type, PlantState state, double humidityMin, double humidityMax, double luminosityMin, double luminosityMax, double temperatureMin, double temperatureMax)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
State = state;
|
||||
HumidityMin = humidityMin;
|
||||
HumidityMax = humidityMax;
|
||||
LuminosityMin = luminosityMin;
|
||||
LuminosityMax = luminosityMax;
|
||||
TemperatureMin = temperatureMin;
|
||||
TemperatureMax = temperatureMax;
|
||||
}
|
||||
|
||||
public override InfoResponse Deserialize(string[] arguments)
|
||||
{
|
||||
if (arguments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(arguments));
|
||||
}
|
||||
if (arguments.Length < 6)
|
||||
{
|
||||
throw new ArgumentException($"Excepted 6 arguments, got {arguments.Length}");
|
||||
}
|
||||
|
||||
Name = arguments[0];
|
||||
Type = arguments[1].ToEnumValue<PlantType>();
|
||||
State = arguments[2].ToEnumValue<PlantState>();
|
||||
(HumidityMin, HumidityMax) = arguments[3].Split('/').Select(x => x.ToDouble()).ToArray().ToTuple();
|
||||
(LuminosityMin, LuminosityMax) = arguments[4].Split('/').Select(x => x.ToDouble()).ToArray().ToTuple();
|
||||
(TemperatureMin, TemperatureMax) = arguments[5].Split('/').Select(x => x.ToDouble()).ToArray().ToTuple();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Name,
|
||||
Type.ToString(),
|
||||
State.ToString(),
|
||||
$"{HumidityMin}/{HumidityMax}",
|
||||
$"{LuminosityMin}/{LuminosityMax}",
|
||||
$"{TemperatureMin}/{TemperatureMax}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
46
PlantBox.Shared/Communication/Commands/PingCommand.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public class PingCommand : CommandSerializable<PingCommand>
|
||||
{
|
||||
public override Command Command => Command.Ping;
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public PingCommand()
|
||||
{
|
||||
|
||||
}
|
||||
public PingCommand(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public override PingCommand Deserialize(string[] arguments)
|
||||
{
|
||||
if (arguments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(arguments));
|
||||
}
|
||||
if (arguments.Length < 1)
|
||||
{
|
||||
throw new ArgumentException($"Excepted at least 1 argument, got {arguments.Length}");
|
||||
}
|
||||
|
||||
Message = arguments[0];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string[] Serialize()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
PlantBox.Shared/Communication/Commands/PlantState.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public enum PlantState
|
||||
{
|
||||
Default,
|
||||
Bad,
|
||||
Warning,
|
||||
Good
|
||||
}
|
||||
}
|
||||
15
PlantBox.Shared/Communication/Commands/PlantType.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Communication.Commands
|
||||
{
|
||||
public enum PlantType
|
||||
{
|
||||
Default,
|
||||
Bamboo,
|
||||
Bonsai,
|
||||
Cactus,
|
||||
Ficus
|
||||
}
|
||||
}
|
||||
42
PlantBox.Shared/Communication/Connection.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace PlantBox.Shared.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all informations used to communicate
|
||||
/// </summary>
|
||||
public static class Connection
|
||||
{
|
||||
// UDP
|
||||
/// <summary>
|
||||
/// Port used by broadcast discovery
|
||||
/// </summary>
|
||||
public const int UDP_PORT = 1401;
|
||||
/// <summary>
|
||||
/// Request sent for a discovery
|
||||
/// </summary>
|
||||
public static readonly byte[] UDP_REQUEST = { 102, 210, 48, 255 };
|
||||
/// <summary>
|
||||
/// Reply sent for a discovery
|
||||
/// </summary>
|
||||
public static readonly byte[] UDP_REPLY = { 102, 210, 48, 0 };
|
||||
|
||||
// TCP
|
||||
/// <summary>
|
||||
/// Port used by the broker to accept servers
|
||||
/// </summary>
|
||||
public const int TCP_SERVER_PORT = 1402;
|
||||
/// <summary>
|
||||
/// Port used by the broker to accept clients
|
||||
/// </summary>
|
||||
public const int TCP_CLIENT_PORT = 1403;
|
||||
|
||||
/// <summary>
|
||||
/// Bytes to read at a time in a <see cref="NetworkStream"/>
|
||||
/// </summary>
|
||||
public const int BUFFER_SIZE = 4096;
|
||||
}
|
||||
}
|
||||
42
PlantBox.Shared/Extensions/CommandSerializeExtensions.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PlantBox.Shared.Extensions
|
||||
{
|
||||
public static class CommandSerializeExtensions
|
||||
{
|
||||
// Array extensions
|
||||
public static (T first, T second) ToTuple<T>(this T[] array)
|
||||
{
|
||||
return (array[0], array[1]);
|
||||
}
|
||||
|
||||
// String conversion
|
||||
public static T ToEnumValue<T>(this string argument)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), argument, true);
|
||||
}
|
||||
public static int ToInt(this string argument)
|
||||
{
|
||||
return int.Parse(argument, CultureInfo.InvariantCulture);
|
||||
}
|
||||
public static double ToDouble(this string argument)
|
||||
{
|
||||
return double.Parse(argument, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Double conversion
|
||||
public static string ToArgument(this double argument)
|
||||
{
|
||||
return argument.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Int conversion
|
||||
public static string ToArgument(this int argument)
|
||||
{
|
||||
return argument.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
PlantBox.sln
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.421
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28803.202
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlantBox.Shared", "PlantBox.Shared\PlantBox.Shared.csproj", "{9E73B18F-8D48-43BC-974D-D1697E1085A1}"
|
||||
EndProject
|
||||
@@ -296,9 +296,9 @@ Global
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x64.Build.0 = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.Build.0 = Debug|x86
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.Build.0 = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Debug|x86.Deploy.0 = Debug|x64
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{33FE30F1-5344-4565-8456-24322CF17093}.Release|ARM.Build.0 = Release|ARM
|
||||
|
||||