diff --git a/PlantBox.Broker/Broker.cs b/PlantBox.Broker/Broker.cs new file mode 100644 index 0000000..f0187c5 --- /dev/null +++ b/PlantBox.Broker/Broker.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; +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; } + + public Broker(string[] args) + { + Console.WriteLine("Initializing Broker..."); + + PlantBoxesManager = new PlantBoxesManager(); + + PlantBoxesManager.Load(); + + ClientManager = new ClientManager(this); + ServerManager = new ServerManager(this); + } + + public void Start() + { + Task.Run(() => ClientManager.Start()); + Task.Run(() => ServerManager.Start()); + + string input; + do + { + input = Console.ReadLine().ToLowerInvariant(); + + if (input == "save") + { + PlantBoxesManager.Save(); + } + } while (input != "exit" && input != "stop" && input != "quit"); + + Console.WriteLine("Stopping Broker..."); + + PlantBoxesManager.Save(); + } + } +} diff --git a/PlantBox.Broker/CaptorsValue.cs b/PlantBox.Broker/CaptorsValue.cs new file mode 100644 index 0000000..754e299 --- /dev/null +++ b/PlantBox.Broker/CaptorsValue.cs @@ -0,0 +1,24 @@ +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(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) + { + + } + } +} diff --git a/PlantBox.Broker/ClientManager.cs b/PlantBox.Broker/ClientManager.cs new file mode 100644 index 0000000..2373656 --- /dev/null +++ b/PlantBox.Broker/ClientManager.cs @@ -0,0 +1,92 @@ +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]; + + if (plantBox == null) + { + throw new InvalidOperationException($"This PlantBox (${id}) does not exist"); + } + + var 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) + { + HistoricRequest historicRequest = new HistoricRequest().Deserialize(packet.Arguments); + ulong id = packet.ID; + PlantBox plantBox = Broker.PlantBoxesManager[id]; + + if (plantBox == null) + { + throw new InvalidOperationException($"This PlantBox (${id}) does not exist"); + } + + HistoricManager historic = plantBox.HistoricManager; + + IReadOnlyList 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"); + } + + var response = new HistoricResponse + ( + DateTime.Now - plantBox.LastMeasureDate, + captorsValues.Select(x => x.Humidity).ToArray(), + captorsValues.Select(x => x.Luminosity).ToArray(), + captorsValues.Select(x => x.Temperature).ToArray() + ); + commandStream.Send(response.ToCommandPacket(id)); + } + + protected override void InfoCommand(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)); + } + } +} diff --git a/PlantBox.Broker/Extensions.cs b/PlantBox.Broker/Extensions.cs new file mode 100644 index 0000000..b67ce4a --- /dev/null +++ b/PlantBox.Broker/Extensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PlantBox.Broker +{ + public static class Extensions + { + public static IEnumerable TakeFromEnd(this List list, int count) + { + return list.GetRange(list.Count - count, count); + } + } +} diff --git a/PlantBox.Broker/HistoricManager.cs b/PlantBox.Broker/HistoricManager.cs new file mode 100644 index 0000000..36b287a --- /dev/null +++ b/PlantBox.Broker/HistoricManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PlantBox.Broker +{ + class HistoricManager + { + private List _minutesHistoric; + public IReadOnlyList MinutesHistoric => _minutesHistoric.AsReadOnly(); + + private List _hoursHistoric; + public IReadOnlyList HoursHistoric => _hoursHistoric.AsReadOnly(); + + private List _daysHistoric; + public IReadOnlyList DaysHistoric => _daysHistoric.AsReadOnly(); + + private List _weeksHistoric; + public IReadOnlyList WeeksHistoric => _weeksHistoric.AsReadOnly(); + + private List _monthsHistoric; + public IReadOnlyList MonthsHistoric => _monthsHistoric.AsReadOnly(); + + public HistoricManager() + { + _minutesHistoric = new List(); + _hoursHistoric = new List(); + _daysHistoric = new List(); + _weeksHistoric = new List(); + _monthsHistoric = new List(); + } + public HistoricManager(List minutesHistoric, List hoursHistoric, List daysHistoric, List weeksHistoric, List 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)))); + } + } + + private (double humidity, double luminosity, double temperature) GetAverage(IEnumerable captorsValues) + { + return + ( + captorsValues.Average(x => x.Humidity), + captorsValues.Average(x => x.Luminosity), + captorsValues.Average(x => x.Temperature) + ); + } + } +} diff --git a/PlantBox.Broker/PlantBox.Broker.csproj b/PlantBox.Broker/PlantBox.Broker.csproj index c3266b3..7518923 100644 --- a/PlantBox.Broker/PlantBox.Broker.csproj +++ b/PlantBox.Broker/PlantBox.Broker.csproj @@ -7,6 +7,10 @@ Broker of the PlantBox project + + + + diff --git a/PlantBox.Broker/PlantBox.cs b/PlantBox.Broker/PlantBox.cs new file mode 100644 index 0000000..9ea1474 --- /dev/null +++ b/PlantBox.Broker/PlantBox.cs @@ -0,0 +1,26 @@ +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; } + 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; } + } +} diff --git a/PlantBox.Broker/PlantBoxesManager.cs b/PlantBox.Broker/PlantBoxesManager.cs new file mode 100644 index 0000000..7156a74 --- /dev/null +++ b/PlantBox.Broker/PlantBoxesManager.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace PlantBox.Broker +{ + class PlantBoxesManager + { + public string FilePath => Path.Combine(Environment.CurrentDirectory, FileName); + public string FileName => "storage.json"; + + private Dictionary _plantBoxes; + + public PlantBoxesManager() + { + + } + + public PlantBox this[ulong id] + { + get => _plantBoxes.GetValueOrDefault(id); + } + + public PlantBox GetPlantBox(ulong id) => this[id]; + + public void Load() + { + Console.WriteLine("Loading storage..."); + + if (File.Exists(FilePath)) + { + _plantBoxes = JsonConvert.DeserializeObject>(File.ReadAllText(FilePath)); + } + else + { + _plantBoxes = new Dictionary(); + } + + Console.WriteLine("Storage loaded"); + } + + public void Save() + { + Console.WriteLine("Saving storage..."); + + File.WriteAllText(FilePath, JsonConvert.SerializeObject(_plantBoxes)); + + Console.WriteLine("Storage saved"); + } + } +} diff --git a/PlantBox.Broker/Program.cs b/PlantBox.Broker/Program.cs index 988578b..70b8d6a 100644 --- a/PlantBox.Broker/Program.cs +++ b/PlantBox.Broker/Program.cs @@ -8,30 +8,14 @@ namespace PlantBox.Broker { class Program { + public static Broker Broker { get; private set; } + static void Main(string[] args) { - Console.WriteLine("Hello World!"); - var listener = new TcpListener(IPAddress.Any, Connection.TCP_PORT); - listener.Start(); + Broker = new Broker(args); + Broker.Start(); - while (true) - { - try - { - var client = listener.AcceptTcpClient(); - var stream = new CommandStream(client.GetStream()); - - (var command, var ping) = stream.Receive(); - Console.WriteLine(command); - Console.WriteLine($"Ping: {ping.Message}"); - - stream.Send(new PingCommand(ping.Message).ToCommandPacket(command.ID)); - } - catch (Exception) - { - - } - } + Console.WriteLine("Broker stopped"); } } } diff --git a/PlantBox.Broker/ServerManager.cs b/PlantBox.Broker/ServerManager.cs new file mode 100644 index 0000000..0abcd3c --- /dev/null +++ b/PlantBox.Broker/ServerManager.cs @@ -0,0 +1,77 @@ +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.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); + } + + 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.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)); + } + } +} diff --git a/PlantBox.Broker/TcpManager.cs b/PlantBox.Broker/TcpManager.cs new file mode 100644 index 0000000..360995d --- /dev/null +++ b/PlantBox.Broker/TcpManager.cs @@ -0,0 +1,98 @@ +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) + { + var commandStream = new CommandStream(client.GetStream()); + + try + { + 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; + } + } + } + catch (Exception ex) + { + Log($"Client disconnected: {ex.Message}"); + } + } + + protected void Log(string message) + { + Console.WriteLine($"[{LogPrefix}] {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); + } +} diff --git a/PlantBox.Shared/Communication/Commands/HistoricResponse.cs b/PlantBox.Shared/Communication/Commands/HistoricResponse.cs index 07b3039..b3bbcf3 100644 --- a/PlantBox.Shared/Communication/Commands/HistoricResponse.cs +++ b/PlantBox.Shared/Communication/Commands/HistoricResponse.cs @@ -46,7 +46,7 @@ namespace PlantBox.Shared.Communication.Commands throw new ArgumentException($"Excepted 4 arguments, got {arguments.Length}"); } - Time = TimeSpan.FromMinutes(arguments[0].ToDouble()); + Time = TimeSpan.FromSeconds(arguments[0].ToDouble()); Humidities = GetArray(arguments[1]); Luminosities = GetArray(arguments[2]); Temperatures = GetArray(arguments[3]); @@ -63,7 +63,7 @@ namespace PlantBox.Shared.Communication.Commands return new[] { - Time.TotalMinutes.ToArgument(), + Time.TotalSeconds.ToArgument(), Join(Humidities), Join(Luminosities), Join(Temperatures)