Ajustes na barra pulverizadora

This commit is contained in:
Diego Freitas 2026-03-11 22:00:21 -03:00
parent 59c1326d0a
commit c4cf9a2742
16 changed files with 776 additions and 606 deletions

View File

@ -51,6 +51,9 @@ namespace OperationControl.Controls
public static readonly DependencyProperty LongitudeProperty = DependencyProperty.Register(nameof(Longitude), typeof(double), typeof(MapViewControl), new PropertyMetadata(0.0, OnViewPropertyChanged));
public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register(nameof(Scale), typeof(double), typeof(MapViewControl), new PropertyMetadata(5000.0, OnViewPropertyChanged));
public static readonly DependencyProperty MbTilesPathProperty = DependencyProperty.Register(nameof(MbTilesPath), typeof(string), typeof(MapViewControl), new PropertyMetadata(string.Empty, OnMbTilesPathChanged));
public string PathMapaSelecionado;
public double Latitude
{
get => (double)GetValue(LatitudeProperty);
@ -122,7 +125,10 @@ namespace OperationControl.Controls
public List<StreetMapDictionaryModel> RuasMapaCarregado;
public event EventHandler<StreetMapClickedEventArgs>? StreetMapClicked;
private bool ParametrizandoOperacao => !(VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Iniciada ?? false) && (VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Status ?? StatusOperacao.NaoIniciado) == StatusOperacao.Parametrizando;
private bool ParametrizandoOperacao =>
!(VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Iniciada ?? false) &&
(VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Status ?? StatusOperacao.NaoIniciado) == StatusOperacao.Parametrizando &&
((App)System.Windows.Application.Current).Shell.Dock?._vm?._viewOperacaoRight?._vm?.SecaoAtual == ViewModels.Views.Operacao.SecaoRightBar.Parametrizacao;
protected virtual void OnStreetMapClicked(string streetId)
{
StreetMapClicked?.Invoke(this, new StreetMapClickedEventArgs(streetId, RuasMapaCarregado.Where(x => x.Selected).Select(x => x.Id).ToList()));
@ -653,15 +659,15 @@ namespace OperationControl.Controls
if (dlg.ShowDialog() == true)
{
var path = dlg.FileName;
if (path.EndsWith(".shp", StringComparison.OrdinalIgnoreCase))
PathMapaSelecionado = dlg.FileName;
if (PathMapaSelecionado.EndsWith(".shp", StringComparison.OrdinalIgnoreCase))
{
var geojson = ConvertShpToGeoJson(path);
var geojson = ConvertShpToGeoJson(PathMapaSelecionado);
AddGeoJsonLayer(geojson);
}
else
{
var geojson = File.ReadAllText(path);
var geojson = File.ReadAllText(PathMapaSelecionado);
AddGeoJsonLayer(geojson);
}
}
@ -800,19 +806,21 @@ namespace OperationControl.Controls
}
public void LoadMapFile(string path)
public void LoadMapFile(string? path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path) || path == PathMapaSelecionado)
return;
if (path.EndsWith(".shp", StringComparison.OrdinalIgnoreCase))
PathMapaSelecionado = path;
if (PathMapaSelecionado.EndsWith(".shp", StringComparison.OrdinalIgnoreCase))
{
var geojson = ConvertShpToGeoJson(path);
var geojson = ConvertShpToGeoJson(PathMapaSelecionado);
AddGeoJsonLayer(geojson);
}
else
{
var geojson = File.ReadAllText(path);
var geojson = File.ReadAllText(PathMapaSelecionado);
AddGeoJsonLayer(geojson);
}
}

View File

@ -1,15 +1,15 @@
<UserControl x:Class="OperationControl.Controls.Pulverizador.BicoLequeIndicator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:OperationControl.Controls.Pulverizador"
mc:Ignorable="d"
d:DesignHeight="120" d:DesignWidth="120">
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
MinWidth="56"
MinHeight="80"
d:DesignWidth="80"
d:DesignHeight="88">
<UserControl.Resources>
<!-- pode reaproveitar as mesmas cores do outro control -->
<SolidColorBrush x:Key="LineBrush" Color="#FFB0BAC8"/>
<SolidColorBrush x:Key="SprayBrush" Color="#FF00A8E8"/>
<SolidColorBrush x:Key="CommandOnBrush" Color="#FFE74C3C"/>
<SolidColorBrush x:Key="CommandOffBrush" Color="#FFB0BAC8"/>
@ -17,33 +17,36 @@
<SolidColorBrush x:Key="TextPrimary" Color="#FFF5F7FA"/>
</UserControl.Resources>
<Grid Margin="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- dados -->
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<!-- desenho -->
</Grid.RowDefinitions>
<!-- Dados do bico (mock, depois a gente binda) -->
<UniformGrid Grid.Row="0" Rows="2" Columns="2" Height="30" Width="90" HorizontalAlignment="Center">
<!-- infos -->
<!-- infos -->
<StackPanel Grid.Row="0"
Orientation="Vertical"
VerticalAlignment="Top"
HorizontalAlignment="Center">
<TextBlock Text="{Binding Vazao, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F1} mL/s}" Foreground="{StaticResource TextSecondary}" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding TempoAtuado, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F1} s}" Foreground="{StaticResource TextSecondary}" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding AnguloControle, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F0}°}" Foreground="{StaticResource TextSecondary}" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Atuacoes, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0} x}" Foreground="{StaticResource TextSecondary}" FontSize="10" HorizontalAlignment="Center"/>
</UniformGrid>
<!-- Desenho do bico + leque -->
<Canvas x:Name="DrawCanvas" Grid.Row="1" Margin="0,2,0,0" Width="Auto" Height="80">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{Binding Atuacoes, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0}x}" Foreground="{StaticResource TextSecondary}" FontSize="10" Margin="0 0 5 0"/>
<TextBlock Text="{Binding TempoAtuado, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F1} s}" Foreground="{StaticResource TextSecondary}" FontSize="10" HorizontalAlignment="Center"/>
</StackPanel>
</StackPanel>
<!-- Solenoide -->
<Rectangle x:Name="SolenoidRect" Width="14" Height="14" Fill="{StaticResource TextPrimary}" Stroke="{StaticResource CommandOffBrush}" StrokeThickness="3" Canvas.Left="53" Canvas.Top="0"/>
<!-- desenho -->
<Grid Grid.Row="1">
<Canvas x:Name="DrawCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Polygon x:Name="SprayPolygon" Stroke="{StaticResource SprayBrush}" StrokeThickness="2" Fill="#2200A8E8" Visibility="Collapsed"/>
<!-- Linha central do jato -->
<Line x:Name="CenterLine" Stroke="{StaticResource SprayBrush}" StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Visibility="Collapsed"/>
<Line x:Name="CenterLine" Stroke="{StaticResource SprayBrush}" StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Visibility="Collapsed"/>
<!-- Leque (triângulo) -->
<Polygon x:Name="SprayPolygon" Stroke="{StaticResource SprayBrush}" StrokeThickness="2" Fill="#2200A8E8" Visibility="Collapsed"/>
</Canvas>
<Rectangle x:Name="SolenoidRect" Width="14" Height="14" RadiusX="2" RadiusY="2" Fill="{StaticResource TextPrimary}" Stroke="{StaticResource CommandOffBrush}" StrokeThickness="3"/>
</Canvas>
</Grid>
</Grid>
</UserControl>
</UserControl>

View File

@ -1,179 +1,166 @@
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using UserControl = System.Windows.Controls.UserControl;
using Brush = System.Windows.Media.Brush;
using Point = System.Windows.Point;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace OperationControl.Controls.Pulverizador
{
/// <summary>
/// Interaction logic for BicoControl.xaml
/// </summary>
public partial class BicoLequeIndicator : UserControl
{
public BicoLequeIndicator()
{
InitializeComponent();
Loaded += (s, e) => UpdateGeometry();
Loaded += (_, __) => UpdateGeometry();
SizeChanged += (_, __) => UpdateGeometry();
}
#region DependencyProperties básicas
// Comando enviado para a solenoide
public bool IsCommandOn
{
get => (bool)GetValue(IsCommandOnProperty);
set => SetValue(IsCommandOnProperty, value);
}
public static readonly DependencyProperty IsCommandOnProperty = DependencyProperty.Register(nameof(IsCommandOn), typeof(bool), typeof(BicoLequeIndicator), new PropertyMetadata(false, OnVisualPropertyChanged));
public static readonly DependencyProperty IsCommandOnProperty =
DependencyProperty.Register(nameof(IsCommandOn), typeof(bool), typeof(BicoLequeIndicator),
new PropertyMetadata(false, OnVisualPropertyChanged));
// Leitura de ligado (se está realmente pulverizando)
public bool IsSpraying
{
get => (bool)GetValue(IsSprayingProperty);
set => SetValue(IsSprayingProperty, value);
}
public static readonly DependencyProperty IsSprayingProperty = DependencyProperty.Register(nameof(IsSpraying), typeof(bool), typeof(BicoLequeIndicator), new PropertyMetadata(false, OnVisualPropertyChanged));
public static readonly DependencyProperty IsSprayingProperty =
DependencyProperty.Register(nameof(IsSpraying), typeof(bool), typeof(BicoLequeIndicator),
new PropertyMetadata(false, OnVisualPropertyChanged));
// Ângulo central do jato (0 = vertical; positivo gira p/ esquerda, negativo p/ direita)
public double AnguloControle
{
get => (double)GetValue(AnguloControleProperty);
set => SetValue(AnguloControleProperty, value);
}
public static readonly DependencyProperty AnguloControleProperty = DependencyProperty.Register(nameof(AnguloControle), typeof(double), typeof(BicoLequeIndicator), new PropertyMetadata(0.0, OnGeometryPropertyChanged));
public static readonly DependencyProperty AnguloControleProperty =
DependencyProperty.Register(nameof(AnguloControle), typeof(double), typeof(BicoLequeIndicator),
new PropertyMetadata(0.0, OnGeometryPropertyChanged));
// Abertura total do leque (em graus) ex: 110°
public double OpeningAngle
{
get => (double)GetValue(OpeningAngleProperty);
set => SetValue(OpeningAngleProperty, value);
}
public static readonly DependencyProperty OpeningAngleProperty = DependencyProperty.Register(nameof(OpeningAngle), typeof(double), typeof(BicoLequeIndicator), new PropertyMetadata(110.0, OnGeometryPropertyChanged));
public static readonly DependencyProperty OpeningAngleProperty =
DependencyProperty.Register(nameof(OpeningAngle), typeof(double), typeof(BicoLequeIndicator),
new PropertyMetadata(110.0, OnGeometryPropertyChanged));
// Vazao instantanea (mL/s)
public double Vazao
{
get => (double)GetValue(VazaoProperty);
set => SetValue(VazaoProperty, value);
}
public static readonly DependencyProperty VazaoProperty = DependencyProperty.Register(nameof(Vazao), typeof(double), typeof(BicoLequeIndicator), new PropertyMetadata(0.0, OnGeometryPropertyChanged));
public static readonly DependencyProperty VazaoProperty =
DependencyProperty.Register(nameof(Vazao), typeof(double), typeof(BicoLequeIndicator),
new PropertyMetadata(0.0));
// Tempo atuado (s)
public double TempoAtuado
{
get => (double)GetValue(TempoAtuadoProperty);
set => SetValue(TempoAtuadoProperty, value);
}
public static readonly DependencyProperty TempoAtuadoProperty = DependencyProperty.Register(nameof(TempoAtuado), typeof(double), typeof(BicoLequeIndicator), new PropertyMetadata(0.0, OnGeometryPropertyChanged));
public static readonly DependencyProperty TempoAtuadoProperty =
DependencyProperty.Register(nameof(TempoAtuado), typeof(double), typeof(BicoLequeIndicator),
new PropertyMetadata(0.0));
// Atuacoes
public int Atuacoes
{
get => (int)GetValue(AtuacoesProperty);
set => SetValue(AtuacoesProperty, value);
}
public static readonly DependencyProperty AtuacoesProperty = DependencyProperty.Register(nameof(Atuacoes), typeof(int), typeof(BicoLequeIndicator), new PropertyMetadata(0, OnGeometryPropertyChanged));
#endregion
public static readonly DependencyProperty AtuacoesProperty =
DependencyProperty.Register(nameof(Atuacoes), typeof(int), typeof(BicoLequeIndicator),
new PropertyMetadata(0));
private static void OnGeometryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (BicoLequeIndicator)d;
ctrl.UpdateGeometry();
if (d is BicoLequeIndicator ctrl)
ctrl.UpdateGeometry();
}
private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (BicoLequeIndicator)d;
ctrl.UpdateVisualState();
if (d is BicoLequeIndicator ctrl)
ctrl.UpdateVisualState();
}
/// <summary>
/// Atualiza cor da solenoide e visibilidade do leque.
/// </summary>
private void UpdateVisualState()
{
if (SolenoidRect == null || SprayPolygon == null)
if (SolenoidRect == null || SprayPolygon == null || CenterLine == null)
return;
var onBrush = (Brush)FindResource("CommandOnBrush");
var offBrush = (Brush)FindResource("CommandOffBrush");
SolenoidRect.Stroke = IsCommandOn ? onBrush : offBrush;
SprayPolygon.Visibility = IsSpraying && OpeningAngle > 0 ? Visibility.Visible : Visibility.Collapsed;
CenterLine.Visibility = IsSpraying ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>
/// Recalcula geometria do leque e da linha central a partir do ângulo central e abertura.
/// </summary>
private void UpdateGeometry()
{
if (DrawCanvas == null || SprayPolygon == null || CenterLine == null)
if (DrawCanvas == null || SprayPolygon == null || CenterLine == null || SolenoidRect == null)
return;
double width = DrawCanvas.ActualWidth;
double height = DrawCanvas.ActualHeight;
if (width <= 0) width = 120;
if (height <= 0) height = 80;
// Origem do bico (ponto da solenoide)
if (width <= 0) width = 60;
if (height <= 0) height = 40;
double originX = width / 2.0;
double originY = 10.0; // um pouco abaixo do topo
double originY = 8.0;
double length = height - 10.0; // comprimento do jato até próximo do solo
double length = Math.Max(16.0, height - 10.0);
// Metade da abertura em radianos
double halfAngleRad = OpeningAngle * 0.5 * Math.PI / 180.0;
// Deslocamento horizontal na base do leque
double dx = Math.Tan(halfAngleRad) * length;
// Pontos em sistema local (0,0 = bico; y para baixo)
Point apex = new Point(0, 0);
Point leftLocal = new Point(-dx, length);
Point rightLocal = new Point(dx, length);
Point centerLocal = new Point(0, length);
// Rotação pelo ângulo central (em radianos).
double theta = AnguloControle * Math.PI / 180.0;
double cos = Math.Cos(theta);
double sin = Math.Sin(theta);
Point Rot(Point p) => new Point(
Point Rotate(Point p) => new Point(
p.X * cos - p.Y * sin,
p.X * sin + p.Y * cos);
Point apexR = Rot(apex);
Point leftR = Rot(leftLocal);
Point rightR = Rot(rightLocal);
Point centerR = Rot(centerLocal);
Point apexR = Rotate(apex);
Point leftR = Rotate(leftLocal);
Point rightR = Rotate(rightLocal);
Point centerR = Rotate(centerLocal);
// Translada para as coordenadas do Canvas
apexR.Offset(originX, originY);
leftR.Offset(originX, originY);
rightR.Offset(originX, originY);
centerR.Offset(originX, originY);
// Atualiza Polygon do leque
var pts = new PointCollection { leftR, apexR, rightR };
SprayPolygon.Points = pts;
SprayPolygon.Points = new PointCollection { leftR, apexR, rightR };
// Atualiza linha central do jato
CenterLine.X1 = apexR.X;
CenterLine.Y1 = apexR.Y;
CenterLine.X2 = centerR.X;
CenterLine.Y2 = centerR.Y;
// Reposiciona a solenoide exatamente na origem
Canvas.SetLeft(SolenoidRect, originX - SolenoidRect.Width / 2.0);
Canvas.SetTop(SolenoidRect, originY - SolenoidRect.Height / 2.0);
UpdateVisualState();
}
}
@ -239,5 +226,4 @@ namespace OperationControl.Controls.Pulverizador
protected void OnPropertyChanged([CallerMemberName] string propName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}

View File

@ -1,101 +1,39 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace OperationControl.Controls.Pulverizador
{
class Converters
public class BarraTopConverter : IMultiValueConverter
{
}
public double MinAlturaCm { get; set; } = 50.0;
public double MaxAlturaCm { get; set; } = 80.0;
public double TopMinPx { get; set; } = 18.0;
public double TopMaxPx { get; set; } = 62.0;
public class AlturaBarraToMarginFisicaConverter : IValueConverter
{
public double GroundTop { get; set; } = 80.0; // Y do solo
public double Top80Px { get; set; } = 20.0; // Y da barra quando está a 80 cm
public double AlturaMaxCm { get; set; } = 80.0;
public double BarraOffset { get; set; } = 13.0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double alturaCm)
{
alturaCm = Math.Clamp(alturaCm, 0, AlturaMaxCm);
double distanciaPx = GroundTop - Top80Px; // px que representam 80 cm
double pxPorCm = distanciaPx / AlturaMaxCm; // regra de 3
double alturaPx = alturaCm * pxPorCm;
double yBarra = GroundTop - alturaPx; // Y da barra na GRID
double marginTop = yBarra - BarraOffset; // compensa o offset interno
return new Thickness(33, marginTop, 33, 0);
}
return new Thickness(33, 0, 33, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> System.Windows.Data.Binding.DoNothing;
}
public class BicoPositionFisicaConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 5)
return 0.0;
if (values.Length < 2)
return new Thickness(0);
if (values[0] is not double barraWidth ||
values[1] is not int index ||
values[2] is not int qtd ||
values[3] is not double distEntreBicosCm ||
values[4] is not double comprimentoBarraCm)
{
return 0.0;
}
if (values[0] is not double alturaCm || values[1] is not double areaHeight)
return new Thickness(0);
if (qtd <= 0 || barraWidth <= 0 || comprimentoBarraCm <= 0)
return 0.0;
if (MaxAlturaCm <= MinAlturaCm)
return new Thickness(0);
// garante valores válidos
distEntreBicosCm = Math.Max(0.0, distEntreBicosCm);
double extraOffset = 0.0;
if (parameter != null)
double.TryParse(parameter.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out extraOffset);
if (qtd == 1)
{
// 1 bico: centro da barra
return barraWidth / 2.0;
}
double h = Math.Max(MinAlturaCm, Math.Min(MaxAlturaCm, alturaCm));
double t = (h - MinAlturaCm) / (MaxAlturaCm - MinAlturaCm);
// span "ideal" em cm (do primeiro ao último bico)
double spanIdealCm = (qtd - 1) * distEntreBicosCm;
double maxTop = Math.Min(TopMaxPx, Math.Max(TopMinPx, areaHeight - 20));
double top = maxTop - (t * (maxTop - TopMinPx));
// Se o span ideal for maior que a barra física, comprimimos
double escala = 1.0;
if (spanIdealCm > comprimentoBarraCm && qtd > 1)
{
escala = comprimentoBarraCm / spanIdealCm;
}
double distEfetivaCm = distEntreBicosCm * escala;
double spanEfetivoCm = (qtd - 1) * distEfetivaCm;
// centraliza o conjunto de bicos na barra
double offsetCm = (comprimentoBarraCm - spanEfetivoCm) / 2.0;
// posição em cm do bico atual
double xCm = offsetCm + index * distEfetivaCm;
// regra de 3: cm → px
double pxPorCm = barraWidth / comprimentoBarraCm;
double xPx = xCm * pxPorCm;
return xPx;
return new Thickness(0, top + extraOffset, 0, 0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
@ -116,12 +54,11 @@ namespace OperationControl.Controls.Pulverizador
return 0.0;
}
// px por cm na faixa do solo (usando comprimento da barra como referência física)
double pxPorCm = totalWidth / comprimentoBarraCm;
double widthPx = larguraRuaCm * pxPorCm;
// não deixa a rua ficar maior que a área disponível
if (widthPx > totalWidth) widthPx = totalWidth;
if (widthPx > totalWidth)
widthPx = totalWidth;
return widthPx;
}
@ -134,7 +71,8 @@ namespace OperationControl.Controls.Pulverizador
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 4) return new Thickness(0);
if (values.Length < 4)
return new Thickness(0);
if (values[0] is not double totalWidth ||
values[1] is not double erroLateralCm ||
@ -150,19 +88,14 @@ namespace OperationControl.Controls.Pulverizador
double center = totalWidth / 2.0;
// erro > 0 = robô desviado pra direita → rua aparece indo pra esquerda
// erro > 0 = robô está para a direita, então a rua aparece deslocando para a esquerda
double roadCenter = center - erroLateralCm * pxPorCm;
double left = roadCenter - widthPx / 2.0;
// SEM travas aqui: pode ser negativo ou maior que totalWidth
// o Grid com ClipToBounds cuida de cortar o desenho
return new Thickness(left, 0, 0, 0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
}
}

View File

@ -1,192 +1,253 @@
<UserControl x:Class="OperationControl.Controls.PulverizadorIndicator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:OperationControl.Controls.Pulverizador"
mc:Ignorable="d" Width="458" Height="123">
<!-- ========= RECURSOS ========= -->
<UserControl.Resources>
<local:BicoPositionFisicaConverter x:Key="BicoPositionFisicaConverter" />
<local:AlturaBarraToMarginFisicaConverter x:Key="AlturaBarraToMarginFisicaConverter" GroundTop="80" Top80Px="7" AlturaMaxCm="80" />
<local:RuaWidthFisicaConverter x:Key="RuaWidthFisicaConverter" />
<local:RuaOffsetFisicaConverter x:Key="RuaOffsetFisicaConverter" />
xmlns:ctrl="clr-namespace:OperationControl.Controls"
mc:Ignorable="d"
MinWidth="420"
MinHeight="150"
d:DesignWidth="900"
d:DesignHeight="180">
<SolidColorBrush x:Key="BgBrush" Color="#FF101820"/>
<SolidColorBrush x:Key="CardBrush" Color="#FF1C2530"/>
<SolidColorBrush x:Key="LineBrush" Color="#FFB0BAC8"/>
<SolidColorBrush x:Key="AccentBrush" Color="#FF00A8E8"/>
<SolidColorBrush x:Key="CommandBrush" Color="#FFE74C3C"/>
<SolidColorBrush x:Key="SprayBrush" Color="#FF00A8E8"/>
<UserControl.Resources>
<local:BarraTopConverter x:Key="BarraTopConverter" MinAlturaCm="50" MaxAlturaCm="80" TopMinPx="18" TopMaxPx="62"/>
<local:RuaWidthFisicaConverter x:Key="RuaWidthFisicaConverter"/>
<local:RuaOffsetFisicaConverter x:Key="RuaOffsetFisicaConverter"/>
<SolidColorBrush x:Key="CardBrush" Color="#FF162437"/>
<SolidColorBrush x:Key="LineBrush" Color="#FFD4DBE5"/>
<SolidColorBrush x:Key="TextPrimary" Color="#FFF5F7FA"/>
<SolidColorBrush x:Key="TextSecondary" Color="#FFB0BAC8"/>
<Style x:Key="Card" TargetType="Border">
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Background" Value="{StaticResource CardBrush}"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Margin" Value="0"/>
</Style>
<SolidColorBrush x:Key="GroundBrush" Color="#AA8B0000"/>
<SolidColorBrush x:Key="CropBrush" Color="#5F6A36"/>
<Style x:Key="SectionTitle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextSecondary}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style>
<Style x:Key="InfoLabelStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextSecondary}"/>
<Setter Property="FontSize" Value="11"/>
</Style>
<Style x:Key="InfoValueStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextPrimary}"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="0,0,0,6"/>
</Style>
</UserControl.Resources>
<!-- ========= LAYOUT ========= -->
<Grid Background="Transparent" Margin="0" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<!-- Vista de trás -->
</Grid.RowDefinitions>
<Border Background="{StaticResource CardBrush}" CornerRadius="12" Padding="12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="72"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<!-- ===================== VISTA TRASEIRA / LEQUES ===================== -->
<Border Grid.Row="1" Style="{StaticResource Card}">
<Grid>
<!-- ===================== ESQUERDA ===================== -->
<Grid Grid.Column="0" Margin="0,0,10,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="18"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="" Style="{StaticResource SectionTitle}"/>
<TextBlock Grid.Row="0" Text="{Binding AlturaBarra, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}, StringFormat={}{0:F0} cm}" Foreground="{StaticResource TextPrimary}" FontSize="16" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid Grid.Row="1" Margin="10,0,10,-8">
<!-- Cota de altura à esquerda -->
<Canvas Width="50" HorizontalAlignment="Left">
<TextBlock Foreground="{StaticResource TextSecondary}" FontSize="10" Canvas.Top="-15" Canvas.Left="1" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding AlturaBarra, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F0} cm}" />
<Slider Minimum="50" Maximum="80" Value="{Binding AlturaBarra, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}" Orientation="Vertical" Height="58" VerticalAlignment="Top" IsDirectionReversed="False" HorizontalAlignment="Center" Canvas.Left="5" />
</Canvas>
<Grid Grid.Row="1" x:Name="AreaRegua" Margin="0,8,0,30">
<!-- Slider -->
<Slider Minimum="50"
Maximum="80"
Value="{Binding AlturaBarra, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Orientation="Vertical"
Width="30"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
IsDirectionReversed="False"
Margin="0,0,0,0"/>
<!-- Bicos e leques (traseira) -->
<Canvas x:Name="canvasPai">
<Canvas.Margin>
<Binding Path="AlturaBarra" RelativeSource="{RelativeSource AncestorType=UserControl}" Converter="{StaticResource AlturaBarraToMarginFisicaConverter}" />
</Canvas.Margin>
</Grid>
<!-- Barra -->
<Line x:Name="linhaBarra" X1="4" Y1="10" X2="250" Y2="10" Stroke="{StaticResource LineBrush}" StrokeThickness="4" StrokeStartLineCap="Round" StrokeEndLineCap="Round" HorizontalAlignment="Left" VerticalAlignment="Center" Height="20" Canvas.Left="50" Canvas.Top="5" Width="256" />
<Rectangle Grid.Row="2" Fill="Transparent"/>
</Grid>
<!-- Bicos dinâmicos -->
<ItemsControl ItemsSource="{Binding Bicos, RelativeSource={RelativeSource AncestorType=UserControl}}" AlternationCount="1000">
<!-- ===================== CENTRO ===================== -->
<Grid Grid.Column="1" Margin="0,0,10,0">
<Grid.RowDefinitions>
<RowDefinition Height="85"/>
<RowDefinition Height="33"/>
</Grid.RowDefinitions>
<!-- área dos bicos -->
<Grid Grid.Row="0" x:Name="AreaTecnica">
<Grid VerticalAlignment="Top">
<Grid.Margin>
<MultiBinding Converter="{StaticResource BarraTopConverter}" ConverterParameter="5">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="AlturaBarra"/>
<Binding ElementName="AreaTecnica" Path="ActualHeight"/>
</MultiBinding>
</Grid.Margin>
<!-- barra -->
<Border Height="5" Background="{StaticResource LineBrush}" CornerRadius="3" VerticalAlignment="Top"/>
<!-- bicos -->
<ItemsControl ItemsSource="{Binding Bicos, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}}" Margin="0,-35,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
<UniformGrid Rows="1" Columns="{Binding QtdBicos, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Define onde cada item fica no Canvas -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="-27" />
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource BicoPositionFisicaConverter}">
<!-- largura visível da barra em px -->
<Binding ElementName="linhaBarra" Path="ActualWidth" />
<!-- índice do bico (do modelo) -->
<Binding Path="Index" />
<!-- quantidade total de bicos -->
<Binding Path="QtdBicos" RelativeSource="{RelativeSource AncestorType=UserControl}" />
<!-- distância entre bicos em cm -->
<Binding Path="DistanciaEntreBicosCm" RelativeSource="{RelativeSource AncestorType=UserControl}" />
<!-- comprimento da barra em cm -->
<Binding Path="ComprimentoBarraCm" RelativeSource="{RelativeSource AncestorType=UserControl}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
</Style>
</ItemsControl.ItemContainerStyle>
<!-- Como desenhar cada bico -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:BicoModel}">
<local:BicoLequeIndicator AnguloControle="{Binding AnguloControle}" OpeningAngle="{Binding OpeningAngle}" IsCommandOn="{Binding IsCommandOn}" IsSpraying="{Binding IsSpraying}" Vazao="{Binding Vazao}" TempoAtuado="{Binding TempoAtuado}" Atuacoes="{Binding Atuacoes}" />
<local:BicoLequeIndicator HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Margin="2,0,2,0"
AnguloControle="{Binding AnguloControle}"
OpeningAngle="{Binding OpeningAngle}"
IsCommandOn="{Binding IsCommandOn}"
IsSpraying="{Binding IsSpraying}"
Vazao="{Binding Vazao}"
TempoAtuado="{Binding TempoAtuado}"
Atuacoes="{Binding Atuacoes}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
<!-- Bomba Pressurizadora -->
<Canvas Width="50" HorizontalAlignment="Right" Margin="0,0,0,0">
<!-- Título -->
<TextBlock Text="Bomba" FontSize="10" Canvas.Top="-30" Canvas.Left="10" TextAlignment="Right" Width="50">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource SectionTitle}">
<Setter Property="Foreground" Value="LightGray"/>
<Style.Triggers>
<!-- Quando a bomba estiver ligada -->
<DataTrigger Binding="{Binding IsBombaOn, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="True">
<Setter Property="Foreground" Value="#4CD964"/>
</DataTrigger>
<!-- Quando desligada -->
<DataTrigger Binding="{Binding IsBombaOn, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="False">
<Setter Property="Foreground" Value="#FFB0BAC8"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- Dados da bomba (pressão e tempo) -->
<StackPanel Orientation="Vertical" Canvas.Left="10" Canvas.Top="-18" TextBlock.TextAlignment="Right">
<!-- Linha 1: pressão -->
<TextBlock Text="Pressão" Foreground="{StaticResource TextSecondary}" FontSize="10"/>
<TextBlock Text="{Binding PressaoLinha, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F2} psi}" Foreground="{StaticResource TextPrimary}" FontSize="12" FontWeight="SemiBold" Margin="0,0,0,4"/>
<!-- Linha 2: tempo ligado -->
<TextBlock Text="Tempo" Foreground="{StaticResource TextSecondary}" FontSize="10"/>
<TextBlock Text="{Binding TempoAtuado, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F2} s}" Foreground="{StaticResource TextPrimary}" FontSize="12" FontWeight="SemiBold" Margin="0,0,0,4"/>
<!-- Linha 3: vazao -->
<TextBlock Text="Vazão" Foreground="{StaticResource TextSecondary}" FontSize="10"/>
<TextBlock Text="{Binding VazaoInstantanea, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0:F2} mL/s}" Foreground="{StaticResource TextPrimary}" FontSize="12" FontWeight="SemiBold"/>
</StackPanel>
</Canvas>
<!-- Solo + Rua -->
<Grid Height="13" VerticalAlignment="Top" Margin="-18,80,-18,0" ClipToBounds="True">
<!-- Cana (fundo) -->
<Border Background="#5F6A36" CornerRadius="0,0,8,8"/>
<!-- Rua (faixa vermelha que desliza) -->
<Border Height="13" HorizontalAlignment="Left" Background="#AA8B0000" CornerRadius="0,0,8,8">
<!-- Largura da rua em px -->
<Border.Width>
<MultiBinding Converter="{StaticResource RuaWidthFisicaConverter}">
<!-- largura em px da faixa do solo -->
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualWidth"/>
<!-- largura da rua em cm -->
<Binding Path="LarguraRuaCm"/>
<!-- comprimento físico representado (cm) -->
<Binding Path="ComprimentoBarraCm"/>
</MultiBinding>
</Border.Width>
<!-- Deslocamento lateral da rua -->
<Border.Margin>
<MultiBinding Converter="{StaticResource RuaOffsetFisicaConverter}">
<!-- largura em px -->
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualWidth"/>
<!-- erro lateral em cm (0 centro, - esquerda, + direita) -->
<Binding Path="ErroLateralCm"/>
<!-- largura rua cm -->
<Binding Path="LarguraRuaCm"/>
<!-- comprimento barra cm (escala px/cm) -->
<Binding Path="ComprimentoBarraCm"/>
</MultiBinding>
</Border.Margin>
</Border>
</Grid>
</Grid>
<!-- solo -->
<Grid Grid.Row="1" ClipToBounds="True" Margin="8,0,8,0">
<Border Background="{StaticResource CropBrush}" CornerRadius="0,0,8,8"/>
<Border HorizontalAlignment="Left" Background="{StaticResource GroundBrush}" CornerRadius="0,0,8,8">
<Border.Width>
<MultiBinding Converter="{StaticResource RuaWidthFisicaConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="LarguraRuaCm"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="ComprimentoBarraCm"/>
</MultiBinding>
</Border.Width>
<Border.Margin>
<MultiBinding Converter="{StaticResource RuaOffsetFisicaConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="ErroLateralCm"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="LarguraRuaCm"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}" Path="ComprimentoBarraCm"/>
</MultiBinding>
</Border.Margin>
</Border>
</Grid>
</Grid>
</Border>
</Grid>
</UserControl>
<!-- ===================== DIREITA ===================== -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="*"/>
<RowDefinition Height="28"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Bomba"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource SectionTitle}">
<Setter Property="Foreground" Value="{StaticResource TextSecondary}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsBombaOn, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}}" Value="True">
<Setter Property="Foreground" Value="#4CD964"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Grid Grid.Row="1"
VerticalAlignment="Center"
Margin="0,2,0,2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Pressão -->
<Grid Grid.Row="0" Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Pressão"
Style="{StaticResource InfoLabelStyle}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding PressaoLinha, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}, StringFormat={}{0:F1} psi}"
Foreground="{StaticResource TextPrimary}"
FontSize="13"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</Grid>
<!-- Tempo -->
<Grid Grid.Row="1" Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Tempo"
Style="{StaticResource InfoLabelStyle}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding TempoAtuado, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}, StringFormat={}{0:F1} s}"
Foreground="{StaticResource TextPrimary}"
FontSize="13"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</Grid>
<!-- Vazão -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Vazão"
Style="{StaticResource InfoLabelStyle}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding VazaoInstantanea, RelativeSource={RelativeSource AncestorType={x:Type ctrl:PulverizadorIndicator}}, StringFormat={}{0:F1} mL/s}"
Foreground="{StaticResource TextPrimary}"
FontSize="13"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</Grid>
</Grid>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@ -7,28 +7,22 @@ using UserControl = System.Windows.Controls.UserControl;
namespace OperationControl.Controls
{
/// <summary>
/// Interaction logic for PulverizadorIndicator.xaml
/// </summary>
public partial class PulverizadorIndicator : UserControl
{
public PulverizadorIndicator()
{
InitializeComponent();
RebuildBicos();
DataContext = this;
}
public double DistanciaEntreBicosCm
{
get => (double)GetValue(DistanciaEntreBicosCmProperty);
set => SetValue(DistanciaEntreBicosCmProperty, value);
}
public static readonly DependencyProperty DistanciaEntreBicosCmProperty = DependencyProperty.Register(nameof(DistanciaEntreBicosCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(50.0));
public static readonly DependencyProperty DistanciaEntreBicosCmProperty =
DependencyProperty.Register(nameof(DistanciaEntreBicosCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(50.0));
public double ComprimentoBarraCm
{
@ -36,7 +30,8 @@ namespace OperationControl.Controls
set => SetValue(ComprimentoBarraCmProperty, value);
}
public static readonly DependencyProperty ComprimentoBarraCmProperty = DependencyProperty.Register(nameof(ComprimentoBarraCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(103.7));
public static readonly DependencyProperty ComprimentoBarraCmProperty =
DependencyProperty.Register(nameof(ComprimentoBarraCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(103.7));
public double AlturaBarra
{
@ -44,7 +39,8 @@ namespace OperationControl.Controls
set => SetValue(AlturaBarraProperty, value);
}
public static readonly DependencyProperty AlturaBarraProperty = DependencyProperty.Register(nameof(AlturaBarra), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(50.0));
public static readonly DependencyProperty AlturaBarraProperty =
DependencyProperty.Register(nameof(AlturaBarra), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(50.0));
public bool IsBombaOn
{
@ -52,7 +48,8 @@ namespace OperationControl.Controls
set => SetValue(IsBombaOnProperty, value);
}
public static readonly DependencyProperty IsBombaOnProperty = DependencyProperty.Register(nameof(IsBombaOn), typeof(bool), typeof(PulverizadorIndicator), new PropertyMetadata(false));
public static readonly DependencyProperty IsBombaOnProperty =
DependencyProperty.Register(nameof(IsBombaOn), typeof(bool), typeof(PulverizadorIndicator), new PropertyMetadata(false));
public double PressaoLinha
{
@ -60,8 +57,8 @@ namespace OperationControl.Controls
set => SetValue(PressaoLinhaProperty, value);
}
public static readonly DependencyProperty PressaoLinhaProperty = DependencyProperty.Register(nameof(PressaoLinha), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public static readonly DependencyProperty PressaoLinhaProperty =
DependencyProperty.Register(nameof(PressaoLinha), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public double TempoAtuado
{
@ -69,7 +66,8 @@ namespace OperationControl.Controls
set => SetValue(TempoAtuadoProperty, value);
}
public static readonly DependencyProperty TempoAtuadoProperty = DependencyProperty.Register(nameof(TempoAtuado), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public static readonly DependencyProperty TempoAtuadoProperty =
DependencyProperty.Register(nameof(TempoAtuado), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public double VazaoInstantanea
{
@ -77,7 +75,8 @@ namespace OperationControl.Controls
set => SetValue(VazaoInstantaneaProperty, value);
}
public static readonly DependencyProperty VazaoInstantaneaProperty = DependencyProperty.Register(nameof(VazaoInstantanea), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public static readonly DependencyProperty VazaoInstantaneaProperty =
DependencyProperty.Register(nameof(VazaoInstantanea), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public int QtdBicos
{
@ -85,7 +84,9 @@ namespace OperationControl.Controls
set => SetValue(QtdBicosProperty, value);
}
public static readonly DependencyProperty QtdBicosProperty = DependencyProperty.Register(nameof(QtdBicos), typeof(int), typeof(PulverizadorIndicator), new PropertyMetadata(VariaveisEquipamento.QuantidadeBicosPulverizadores, OnQtdBicosChanged));
public static readonly DependencyProperty QtdBicosProperty =
DependencyProperty.Register(nameof(QtdBicos), typeof(int), typeof(PulverizadorIndicator),
new PropertyMetadata(VariaveisEquipamento.QuantidadeBicosPulverizadores, OnQtdBicosChanged));
public double LarguraRuaCm
{
@ -93,8 +94,8 @@ namespace OperationControl.Controls
set => SetValue(LarguraRuaCmProperty, value);
}
public static readonly DependencyProperty LarguraRuaCmProperty = DependencyProperty.Register(nameof(LarguraRuaCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(150.0));
public static readonly DependencyProperty LarguraRuaCmProperty =
DependencyProperty.Register(nameof(LarguraRuaCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(150.0));
public double ErroLateralCm
{
@ -102,22 +103,24 @@ namespace OperationControl.Controls
set => SetValue(ErroLateralCmProperty, value);
}
public static readonly DependencyProperty ErroLateralCmProperty = DependencyProperty.Register(nameof(ErroLateralCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public static readonly DependencyProperty ErroLateralCmProperty =
DependencyProperty.Register(nameof(ErroLateralCm), typeof(double), typeof(PulverizadorIndicator), new PropertyMetadata(0.0));
public ObservableCollection<BicoModel> Bicos { get; } = new();
private static void OnQtdBicosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (PulverizadorIndicator)d;
ctl.RebuildBicos();
if (d is PulverizadorIndicator ctl)
ctl.RebuildBicos();
}
private void RebuildBicos()
{
Bicos.Clear();
for (int i = 0; i < QtdBicos; i++)
int quantidade = QtdBicos < 0 ? 0 : QtdBicos;
for (int i = 0; i < quantidade; i++)
{
Bicos.Add(new BicoModel
{
@ -132,10 +135,5 @@ namespace OperationControl.Controls
});
}
}
}
}
}

View File

@ -8,6 +8,8 @@ namespace OperationControl.Models
{
public class AppShell
{
public static readonly bool Mock = true;
private readonly System.Timers.Timer tmrComunicacao = new(1000) { AutoReset = true };
private int tmrComunicacaoTicks = 0;
private bool tmrComunicacaoTicking = false;
@ -33,6 +35,93 @@ namespace OperationControl.Models
Main = new MainWindow();
Dock = new DockWindow();
Dock.Show();
if (Mock)
{
Task.Run(async () =>
{
await Task.Delay(1000);
VariaveisControleOperacao.AdicionarNovoRoverNaRede("01", "192.168.1.100");
VariaveisControleOperacao.RoversNaRede.FirstOrDefault().DadosLeitura = new AgroBase.Models.Operacoes.OperacaoParametrosDadosModel()
{
StatusRover = StatusModulo.Operante,
Operacao = new AgroBase.Models.Operacoes.OperacaoParametrosDadosOperacaoModel()
{
Status = StatusOperacao.Parametrizando
},
Atuador = new AgroBase.Models.Operacoes.OperacaoParametrosDadosAtuadorModel()
{
Bombas = new List<AgroBase.Models.Operacoes.OperacaoParametrosDadosBombaModel>()
{
new AgroBase.Models.Operacoes.OperacaoParametrosDadosBombaModel()
{
ComandoEstado = true,
LeituraEstado = true,
ID = "BOMBA",
ID_Num = 10,
Inicializado = true,
Potencia = 50,
TempoAtuado = 3000
}
},
Bicos = new List<AgroBase.Models.Operacoes.OperacaoParametrosDadosBicoModel>()
{
new AgroBase.Models.Operacoes.OperacaoParametrosDadosBicoModel()
{
AnguloAbertura = 30,
ComandoAngulo = 0,
ComandoEstado = true,
ID = "B01",
ID_Num = 1,
Inicializado = true,
LeituraAngulo = 0,
LeituraEstado = true,
Posicao = 1,
QtdAtuacoes = 5,
TempoAtuado = 300,
VazaoInstantaneaMLs = 15,
VazaoMediaMLs = 30,
VolumeVazadoML = 325
},
new AgroBase.Models.Operacoes.OperacaoParametrosDadosBicoModel()
{
AnguloAbertura = 30,
ComandoAngulo = 0,
ComandoEstado = true,
ID = "B02",
ID_Num = 2,
Inicializado = true,
LeituraAngulo = 0,
LeituraEstado = false,
Posicao = 1,
QtdAtuacoes = 5,
TempoAtuado = 300,
VazaoInstantaneaMLs = 15,
VazaoMediaMLs = 30,
VolumeVazadoML = 325
},
new AgroBase.Models.Operacoes.OperacaoParametrosDadosBicoModel()
{
AnguloAbertura = 30,
ComandoAngulo = 0,
ComandoEstado = false,
ID = "B03",
ID_Num = 3,
Inicializado = true,
LeituraAngulo = 0,
LeituraEstado = true,
Posicao = 2,
QtdAtuacoes = 5,
TempoAtuado = 300,
VazaoInstantaneaMLs = 15,
VazaoMediaMLs = 30,
VolumeVazadoML = 325
},
}
}
};
});
}
}
private void tmrComunicacao_Elapsed()

View File

@ -168,7 +168,7 @@ namespace OperationControl.Models
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
(((App)Application.Current).Shell.Dock?.DataContext as DockWindowViewModel)?.AtualizarDadosTela(rover);
((App)Application.Current).Shell.Dock?._vm?.AtualizarDadosTela(rover);
}));
}
catch (Exception ex)
@ -237,7 +237,7 @@ namespace OperationControl.Models
}
foreach (var rover in rovers_atual)
{
rover.Alive = (DateTime.Now - rover.UltimoContato).TotalSeconds < TempoRoverVivo;
rover.Alive = AppShell.Mock || (DateTime.Now - rover.UltimoContato).TotalSeconds < TempoRoverVivo;
if (!rover.Alive)
{
//((App)Application.Current).Shell.Main?.MAP?.markers?.UpdateMarkerInfo(rover.RoverId, status: AgroBase.Models.Enums.StatusOperacao.Erro);

View File

@ -221,8 +221,7 @@ namespace OperationControl.ViewModels
// string fix = GnssService.FixMode(); // "No Fix", "3D", "RTK Float", "RTK Fixed"
// double? hdop = GnssService.Hdop();
bool conectado = Variaveis.GpsService?.IsConnected ?? false; // <-- seu método real aqui
conectado = true;
bool conectado = AppShell.Mock || (Variaveis.GpsService?.IsConnected ?? false);
if (!conectado)
{

View File

@ -126,6 +126,26 @@ namespace OperationControl.ViewModels.Views.Operacao
SelecionarAlerta(alerta);
}
SeveridadeAlerta DeParaStatus(StatusModulo status, bool mandatorio)
{
switch (status)
{
case StatusModulo.Operante:
return SeveridadeAlerta.Info;
case StatusModulo.Alerta:
return SeveridadeAlerta.Warning;
case StatusModulo.Falha:
return mandatorio ? SeveridadeAlerta.Critical : SeveridadeAlerta.Error;
case StatusModulo.Conectado:
return mandatorio ? SeveridadeAlerta.Warning : SeveridadeAlerta.Info;
case StatusModulo.Desconectado:
return mandatorio ? SeveridadeAlerta.Critical : SeveridadeAlerta.Error;
default:
return SeveridadeAlerta.Info;
}
}
public void AtualizarListaAlertasRoverSelecionado(string rover_id)
{
var rover = VariaveisControleOperacao.RoversNaRede.FirstOrDefault(x => x.RoverId == rover_id);
@ -133,25 +153,6 @@ namespace OperationControl.ViewModels.Views.Operacao
var dados_leitura = rover?.DadosLeitura;
if (dados_leitura?.ModulosSaude == null) return;
SeveridadeAlerta DeParaStatus(StatusModulo status, bool mandatorio)
{
switch (status)
{
case StatusModulo.Operante:
return SeveridadeAlerta.Info;
case StatusModulo.Alerta:
return SeveridadeAlerta.Warning;
case StatusModulo.Falha:
return mandatorio ? SeveridadeAlerta.Critical : SeveridadeAlerta.Error;
case StatusModulo.Conectado:
return mandatorio ? SeveridadeAlerta.Warning : SeveridadeAlerta.Info;
case StatusModulo.Desconectado:
return mandatorio ? SeveridadeAlerta.Critical : SeveridadeAlerta.Error;
default:
return SeveridadeAlerta.Info;
}
}
foreach (var mod in dados_leitura.ModulosSaude)
{
if (mod.saude_individual.Any())
@ -181,6 +182,113 @@ namespace OperationControl.ViewModels.Views.Operacao
}
}
}
public void AtualizarListaAlertasTodosRovers()
{
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var alertasEsperados = new List<AlertaModel>();
foreach (var rover in VariaveisControleOperacao.RoversNaRede)
{
var dadosLeitura = rover?.DadosLeitura;
if (dadosLeitura?.ModulosSaude == null)
continue;
foreach (var mod in dadosLeitura.ModulosSaude)
{
if (mod.saude_individual != null && mod.saude_individual.Any())
{
foreach (var ind in mod.saude_individual)
{
if (ind.status == StatusModulo.Operante)
continue;
alertasEsperados.Add(new AlertaModel
{
Rover_ID = rover.RoverId,
Modulo = mod.modulo,
Mod_ID = ind.label,
Severidade = DeParaStatus(ind.status, ind.em_uso),
Mensagem = string.Join(", ", ind.motivos ?? new List<string>()),
Timestamp = DateTime.Now
});
}
}
else
{
if (mod.status == StatusModulo.Operante)
continue;
bool mandatorio = rover.ModulosMandatorios?.Any(x =>
x.Dispositivo == mod.modulo && x.Mandatorio) ?? false;
alertasEsperados.Add(new AlertaModel
{
Rover_ID = rover.RoverId,
Modulo = mod.modulo,
Mod_ID = null,
Severidade = DeParaStatus(mod.status, mandatorio),
Mensagem = string.Join(", ", mod.motivos ?? new List<string>()),
Timestamp = DateTime.Now
});
}
}
}
string GerarChave(string roverId, T_Code modulo, string? modId)
=> $"{roverId}|{modulo}|{modId ?? ""}";
var mapaEsperado = alertasEsperados.ToDictionary(
x => GerarChave(x.Rover_ID, x.Modulo, x.Mod_ID),
x => x);
var alertasAtuais = AlertasAtivos.ToList();
// Remove os alertas que não deveriam mais existir
foreach (var atual in alertasAtuais)
{
var chave = GerarChave(atual.Rover_ID, atual.Modulo, atual.Mod_ID);
if (!mapaEsperado.ContainsKey(chave))
{
AlertasAtivos.Remove(atual);
if (ToastAlerta == atual)
LimparToast();
}
}
// Adiciona ou atualiza os alertas esperados
foreach (var esperado in alertasEsperados)
{
var existente = AlertasAtivos.FirstOrDefault(x =>
x.Rover_ID == esperado.Rover_ID &&
x.Modulo == esperado.Modulo &&
x.Mod_ID == esperado.Mod_ID);
if (existente == null)
{
AlertasAtivos.Add(esperado);
MostrarToast(esperado);
}
else
{
bool mudou =
existente.Mensagem != esperado.Mensagem ||
existente.Severidade != esperado.Severidade;
existente.Mensagem = esperado.Mensagem;
existente.Severidade = esperado.Severidade;
if (mudou)
{
existente.Timestamp = DateTime.Now;
MostrarToast(existente);
}
}
}
}));
}
}
}

View File

@ -94,16 +94,19 @@ namespace OperationControl.ViewModels
public void AtualizarDados(GPSModel dados)
{
LatitudeTexto = dados.Latitude.ToString("F6");
LongitudeTexto = dados.Longitude.ToString("F6");
AltitudeTexto = $"{dados.Altitude:F1} m";
HeadingTexto = $"{dados.OrientacaoReal:F1}°";
FixTexto = dados.QualidadeFix.ToString();
PrecisaoTexto = $"{dados.PrecisaoCm:F2} cm";
SatelitesTexto = dados.NumeroSatelites.ToString();
IdadeCorrecaoTexto = $"{dados.IdadeCorrecao:F1} s";
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
LatitudeTexto = dados.Latitude.ToString("F6");
LongitudeTexto = dados.Longitude.ToString("F6");
AltitudeTexto = $"{dados.Altitude:F1} m";
HeadingTexto = $"{dados.OrientacaoReal:F1}°";
FixTexto = dados.QualidadeFix.ToString();
PrecisaoTexto = $"{dados.PrecisaoCm:F2} cm";
SatelitesTexto = dados.NumeroSatelites.ToString();
IdadeCorrecaoTexto = $"{dados.IdadeCorrecao:F1} s";
AtualizarCorFix(dados.QualidadeFix);
AtualizarCorFix(dados.QualidadeFix);
});
}
private void AtualizarCorFix(TiposCorrecaoGPS fix)

View File

@ -9,6 +9,7 @@ using AgroBase.Models.Operacoes;
using Newtonsoft.Json.Linq;
using OperationControl.ViewModels.Views.Operacao;
using OperationControl.Controls;
using static OpenTK.Graphics.OpenGL.GL;
namespace OperationControl.ViewModels
{
@ -136,7 +137,7 @@ namespace OperationControl.ViewModels
}
}
private bool _baseFixada;
private bool _baseFixada = AppShell.Mock;
public bool BaseFixada
{
get => _baseFixada;
@ -343,10 +344,8 @@ namespace OperationControl.ViewModels
CenterContent = _viewOperacaoCenter;
RightContent = _viewOperacaoRight;
BottomContent = _viewOperacaoBottom;
CriarMarcadorBase(_viewOperacaoCenter?.MapaOperacao);
if (!string.IsNullOrWhiteSpace(_ultimoMapaCarregado))
_viewOperacaoCenter?.MapaOperacao?.LoadMapFile(_ultimoMapaCarregado);
CriarMarcadorBase(_viewOperacaoCenter?.Mapa);
AtualizarDadosMapa(null);
break;
}
@ -371,25 +370,18 @@ namespace OperationControl.ViewModels
AtualizarProgressoFixacaoBase(bf.Progresso, bf.ProgressoStr);
if (bf.Progresso >= 100) FinalizarFixacaoBase(true, bf.ProgressoStr);
}
switch (CurrentStep)
MapViewControl? Mapa = CurrentStep == DockStep.PreparacaoMapa ? _viewPreparacaoMapa?.MapaPreparacao : CurrentStep == DockStep.ParametrizacaoRover ? _viewOperacaoCenter?.Mapa : null;
if (CurrentStep == DockStep.PreparacaoMapa)
{
case DockStep.PreparacaoMapa:
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
_viewPreparacaoMapaTop?._vm?.AtualizarDados(dados);
_viewPreparacaoMapa?.MapaPreparacao?.markers?.UpdateMarkerPosition(VariaveisControleOperacao.BaseMarkerID, lat: dados?.Latitude, lon: dados?.Longitude, heading: dados?.OrientacaoReal);
});
break;
case DockStep.ParametrizacaoRover:
_viewOperacaoCenter?.MapaOperacao?.markers?.UpdateMarkerPosition(VariaveisControleOperacao.BaseMarkerID, lat: dados?.Latitude, lon: dados?.Longitude, heading: dados?.OrientacaoReal);
break;
_viewPreparacaoMapaTop?._vm?.AtualizarDados(dados);
}
Mapa?.markers?.UpdateMarkerPosition(VariaveisControleOperacao.BaseMarkerID, lat: dados?.Latitude, lon: dados?.Longitude, heading: dados?.OrientacaoReal);
}
public void AtualizarDadosMapa(OperacaoParametrosModel dados)
public void AtualizarDadosMapa(OperacaoParametrosModel? dados)
{
_viewOperacaoCenter?.MapaOperacao?.CarregarDadosMapa(dados?.Mapa, dados?.RuasPercorrer);
_viewOperacaoCenter?.Mapa?.LoadMapFile(_ultimoMapaCarregado);
_viewOperacaoCenter?.Mapa?.CarregarDadosMapa(dados?.Mapa, dados?.RuasPercorrer);
}
#region TELA 2
@ -500,6 +492,131 @@ namespace OperationControl.ViewModels
#endregion
#region TELA 3
public void AtualizarListaRovers(List<OperacaoParametrosModel> rovers_atual)
{
var cards = rovers_atual.Select(x => new RoverCardModel()
{
EquipamentoId = x.IP,
NumeroSerie = x.RoverId,
Status = x.DadosLeitura?.StatusRover ?? AgroBase.Models.Enums.StatusModulo.Desconectado
}).ToArray();
_viewOperacaoRight?._vm?.AtualizarListaRovers(cards);
foreach (var rover in cards)
{
if (!_viewOperacaoCenter?.Mapa?.markers?.Added(rover.NumeroSerie) ?? false)
{
var roverDados = VariaveisControleOperacao.RoversNaRede.FirstOrDefault(x => x.RoverId == rover.NumeroSerie);
if (roverDados != null)
{
_viewOperacaoCenter?.Mapa?.markers?.AddMarker(
rover.NumeroSerie,
false,
lat: roverDados.DadosLeitura?.Gnss?.Latitude ?? 0,
lon: roverDados.DadosLeitura?.Gnss?.Longitude ?? 0,
heading: roverDados.DadosLeitura?.Gnss?.OrientacaoReal ?? 0
);
}
}
}
}
public void AtualizarDadosTela(OperacaoParametrosModel rover)
{
if (rover == null) return;
var obj = rover.DadosLeitura;
if (obj == null) return;
var simulacao = obj.Controle?.SimulacaoMPC?.Select(x => new double[] { x.longitude, x.latitude })?.ToList();
_viewOperacaoCenter?.Mapa?.markers.UpdateMarkerPosition(rover.RoverId, lat: obj.Gnss?.Latitude, lon: obj.Gnss?.Longitude, heading: obj.Gnss?.OrientacaoReal, predict: simulacao);
_viewOperacaoCenter?.Mapa?.markers.UpdateMarkerInfo(rover.RoverId, status: obj.Operacao?.Status ?? AgroBase.Models.Enums.StatusOperacao.NaoIniciado);
//_viewOperacaoBottom?._vm?.AtualizarListaAlertasRoverSelecionado(rover.RoverId);
_viewOperacaoBottom?._vm?.AtualizarListaAlertasTodosRovers();
if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId) return;
var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == AgroBase.Models.Enums.T_Code.Ipb);
double c = conexao?.saude ?? 0;
double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value<double>() ?? 0.0;
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double>() ?? 0.0;
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double>() ?? 0;
string comunicacao = $"{conexao_latencia:0.00} ms {bwTot:0.00} Mbps";
string comunicacao_extended = $"{conexao_latencia:0.00} ms {conexao_perda:F2}% {bwTot:0.00} Mbps";
_viewOperacaoLeft?._vm?.AtualizarRover(
rover?.Descricao ?? "",
obj.Controle?.PercentualVelocidadeSPKmh ?? 0,
obj.Controle?.Angulo ?? 0,
obj.Controle?.TipoMovimento ?? AgroBase.Models.Enums.TipoMovimentoDirecional.Diagnostico,
obj.Refrigeracao?.Temperatura ?? 0,
obj.Bateria?.PercentualBateria ?? 0,
obj.Atuador?.PercentualReservatorio ?? 0,
$"{c}%",
$"{obj.Gnss?.QualidadeFix ?? AgroBase.Models.Enums.TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm"
);
_viewOperacaoRight?._vm?.AtualizarResumo(r =>
{
r.Operacao = (obj.Operacao?.Liberada ?? false) ? "🔓 Liberada" : "🔒 Bloqueada";
r.Modo = obj.Operacao == null ? "-" : obj.Operacao.Modo.ToString();
r.Status = obj.Operacao == null ? "-" : $"{obj.Operacao.Status}" + (obj.Operacao.Status == AgroBase.Models.Enums.StatusOperacao.Aguardando ? $" ({obj.Operacao.TempoAguardandoSegs:0}s)" : "");
r.Carro = obj.Trajetoria == null ? "-" : obj.Trajetoria.StatusCarro.ToString();
r.Temperatura = $"{(obj.PerformanceNcp?.CPU_temp ?? 0):0.00} °C";
r.RuaAtual = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.CorredorAtualDistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.CorredorAtualDistanciaTotal:0.00} m " + $"({obj.Trajetoria.CorredorAtualIdx + 1})";
r.AreaTotal = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.DistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.DistanciaTotal:0.00} m";
r.Bateria = obj.Bateria == null ? "-" : $"{obj.Bateria.TensaoInstantanea:0.00} V " + $"({TimeSpan.FromMinutes(obj.Bateria.TempoEstimadoRestanteMinutos):dd\\.hh\\:mm\\:ss} " + $"{obj.Bateria.DistanciaEstimadaRestanteMetros:0.00} m)";
r.Herbicida = obj.Atuador == null ? "-" : $"{obj.Atuador.VolumeReservatorioL:0.00} L " + $"({TimeSpan.FromMinutes(obj.Atuador.TempoEstimadoRestanteMinutos):hh\\:mm\\:ss} " + $"{obj.Atuador.DistanciaEstimadaRestanteMetros:0.00} m)";
r.Infestacao = obj.Atuador == null ? "-" : $"{obj.Atuador.HerbicidaPorAtuacaoMl:0.00} mL/atuação";
r.Conexao = comunicacao == null ? "-" : $"{comunicacao}";
r.Duracao = obj.Operacao == null ? "00:00:00 / 00:00:00" : $"{TimeSpan.FromSeconds(obj.Operacao.TempoDecorridoSegs):hh\\:mm\\:ss} / " + $"{(string.IsNullOrWhiteSpace(obj.Trajetoria?.TempoEstimadoOperacao) ? "00:00:00" : obj.Trajetoria.TempoEstimadoOperacao)}";
// Opcionais para progress bars, caso queira evoluir o resumo
r.RuaAtualPercentual = obj.Trajetoria?.PercentualRuaAtual ?? 0;
r.AreaTotalPercentual = obj.Trajetoria?.PercentualOperacao ?? 0;
r.BateriaPercentual = obj.Bateria?.PercentualBateria ?? 0;
r.HerbicidaPercentual = obj.Atuador?.PercentualReservatorio ?? 0;
r.InfestacaoPercentual = obj.Atuador?.PercentualErvasTerreno ?? 0;
});
_viewOperacaoCenter?.Diagnostico?._vm?.AtualizarDados(rover);
_viewOperacaoCenter?.Diagnostico?.AtualizarGraficos();
var bomba = obj.Atuador?.Bombas?.FirstOrDefault();
var ATU = _viewOperacaoCenter.Monitoramento.Pulverizador;
ATU.ComprimentoBarraCm = AgroBase.Models.VariaveisEquipamento.ComprimentoBarraPulverizadoraCm;
ATU.DistanciaEntreBicosCm = AgroBase.Models.VariaveisEquipamento.DistanciaEntreBicosCm;
ATU.AlturaBarra = obj?.Controle?.AlturaBarra ?? AgroBase.Models.VariaveisEquipamento.AlturaBarraPulverizadoraCm;
ATU.IsBombaOn = bomba?.LeituraEstado ?? false;
ATU.PressaoLinha = obj?.Atuador?.PressaoLinhaPsi ?? 0;
ATU.VazaoInstantanea = obj?.Atuador?.VazaoInstantaneaMLs ?? 0;
ATU.TempoAtuado = (bomba?.TempoAtuado ?? 0) / 1000.0;
double erroLateral = ((obj?.Trajetoria?.DistanciaEsquerda ?? 0) - (obj?.Trajetoria?.DistanciaDireita ?? 0)) / 2.0 * 100.0;
ATU.LarguraRuaCm = 100.0;
ATU.ErroLateralCm = erroLateral;
if (ATU.QtdBicos != obj?.Atuador?.Bicos?.Count)
{
ATU.QtdBicos = obj?.Atuador?.Bicos?.Count ?? AgroBase.Models.VariaveisEquipamento.QuantidadeBicosPulverizadores;
}
foreach (var bico in ATU.Bicos)
{
var _bc = obj?.Atuador?.Bicos?.FirstOrDefault(x => (x.Posicao - 1) == bico.Index);
if (_bc == null) continue;
bico.OpeningAngle = _bc.AnguloAbertura;
bico.AnguloControle = _bc.ComandoAngulo;
bico.IsCommandOn = _bc.ComandoEstado;
bico.IsSpraying = _bc.LeituraEstado;
bico.Vazao = _bc.VazaoInstantaneaMLs;
bico.Atuacoes = _bc.QtdAtuacoes;
bico.TempoAtuado = _bc.TempoAtuado / 1000.0;
}
}
public void AtualizarTopOperacaoBase()
{
@ -558,117 +675,12 @@ namespace OperationControl.ViewModels
}
public void AtualizarListaRovers(List<OperacaoParametrosModel> rovers_atual)
{
var cards = rovers_atual.Select(x => new RoverCardModel()
{
EquipamentoId = x.IP,
NumeroSerie = x.RoverId,
Status = x.DadosLeitura?.StatusRover ?? AgroBase.Models.Enums.StatusModulo.Desconectado
}).ToArray();
_viewOperacaoRight?._vm?.AtualizarListaRovers(cards);
foreach (var rover in cards)
{
if (!_viewOperacaoCenter?.MapaOperacao?.markers?.Added(rover.NumeroSerie) ?? false)
{
if (rover.NumeroSerie == VariaveisControleOperacao.BaseMarkerID)
{
_viewOperacaoCenter?.MapaOperacao?.markers?.AddMarker(
rover.NumeroSerie,
true,
lat: Variaveis.GpsService?.UltimaLeitura?.Latitude ?? 0,
lon: Variaveis.GpsService?.UltimaLeitura?.Longitude ?? 0,
heading: Variaveis.GpsService?.UltimaLeitura?.OrientacaoReal ?? 0,
label: "Base"
);
}
else
{
var roverDados = VariaveisControleOperacao.RoversNaRede.FirstOrDefault(x => x.RoverId == rover.NumeroSerie);
if (roverDados != null)
{
_viewOperacaoCenter?.MapaOperacao?.markers?.AddMarker(
rover.NumeroSerie,
false,
lat: roverDados.DadosLeitura?.Gnss?.Latitude ?? 0,
lon: roverDados.DadosLeitura?.Gnss?.Longitude ?? 0,
heading: roverDados.DadosLeitura?.Gnss?.OrientacaoReal ?? 0
);
}
}
}
}
}
public void AtualizarDadosTela(OperacaoParametrosModel rover)
{
if (rover == null) return;
var obj = rover.DadosLeitura;
if (obj == null) return;
var simulacao = obj.Controle?.SimulacaoMPC?.Select(x => new double[] { x.longitude, x.latitude })?.ToList();
_viewOperacaoCenter?.MapaOperacao?.markers.UpdateMarkerPosition(rover.RoverId, lat: obj.Gnss?.Latitude, lon: obj.Gnss?.Longitude, heading: obj.Gnss?.OrientacaoReal, predict: simulacao);
_viewOperacaoCenter?.MapaOperacao?.markers.UpdateMarkerInfo(rover.RoverId, status: obj.Operacao?.Status ?? AgroBase.Models.Enums.StatusOperacao.NaoIniciado);
_viewOperacaoBottom?._vm?.AtualizarListaAlertasRoverSelecionado(rover.RoverId);
if (rover.RoverId == VariaveisControleOperacao.SelectedRoverId)
{
var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == AgroBase.Models.Enums.T_Code.Ipb);
double c = conexao?.saude ?? 0;
double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value<double>() ?? 0.0;
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double>() ?? 0.0;
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double>() ?? 0;
string comunicacao = $"{conexao_latencia:0.00} ms {bwTot:0.00} Mbps";
string comunicacao_extended = $"{conexao_latencia:0.00} ms {conexao_perda:F2}% {bwTot:0.00} Mbps";
_viewOperacaoLeft?._vm?.AtualizarRover(
rover?.Descricao ?? "",
obj.Controle?.PercentualVelocidadeSPKmh ?? 0,
obj.Controle?.Angulo ?? 0,
obj.Controle?.TipoMovimento ?? AgroBase.Models.Enums.TipoMovimentoDirecional.Diagnostico,
obj.Refrigeracao?.Temperatura ?? 0,
obj.Bateria?.PercentualBateria ?? 0,
obj.Atuador?.PercentualReservatorio ?? 0,
$"{c}%",
$"{obj.Gnss?.QualidadeFix ?? AgroBase.Models.Enums.TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm"
);
_viewOperacaoRight?._vm?.AtualizarResumo(r =>
{
r.Operacao = (obj.Operacao?.Liberada ?? false) ? "🔓 Liberada" : "🔒 Bloqueada";
r.Modo = obj.Operacao == null ? "-" : obj.Operacao.Modo.ToString();
r.Status = obj.Operacao == null ? "-" : $"{obj.Operacao.Status}" + (obj.Operacao.Status == AgroBase.Models.Enums.StatusOperacao.Aguardando ? $" ({obj.Operacao.TempoAguardandoSegs:0}s)" : "");
r.Carro = obj.Trajetoria == null ? "-" : obj.Trajetoria.StatusCarro.ToString();
r.Temperatura = $"{(obj.PerformanceNcp?.CPU_temp ?? 0):0.00} °C";
r.RuaAtual = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.CorredorAtualDistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.CorredorAtualDistanciaTotal:0.00} m " + $"({obj.Trajetoria.CorredorAtualIdx + 1})";
r.AreaTotal = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.DistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.DistanciaTotal:0.00} m";
r.Bateria = obj.Bateria == null ? "-" : $"{obj.Bateria.TensaoInstantanea:0.00} V " + $"({TimeSpan.FromMinutes(obj.Bateria.TempoEstimadoRestanteMinutos):dd\\.hh\\:mm\\:ss} " + $"{obj.Bateria.DistanciaEstimadaRestanteMetros:0.00} m)";
r.Herbicida = obj.Atuador == null ? "-" : $"{obj.Atuador.VolumeReservatorioL:0.00} L " + $"({TimeSpan.FromMinutes(obj.Atuador.TempoEstimadoRestanteMinutos):hh\\:mm\\:ss} " + $"{obj.Atuador.DistanciaEstimadaRestanteMetros:0.00} m)";
r.Infestacao = obj.Atuador == null ? "-" : $"{obj.Atuador.HerbicidaPorAtuacaoMl:0.00} mL/atuação";
r.Conexao = comunicacao == null ? "-" : $"{comunicacao}";
r.Duracao = obj.Operacao == null ? "00:00:00 / 00:00:00" : $"{TimeSpan.FromSeconds(obj.Operacao.TempoDecorridoSegs):hh\\:mm\\:ss} / " + $"{(string.IsNullOrWhiteSpace(obj.Trajetoria?.TempoEstimadoOperacao) ? "00:00:00" : obj.Trajetoria.TempoEstimadoOperacao)}";
// Opcionais para progress bars, caso queira evoluir o resumo
r.RuaAtualPercentual = obj.Trajetoria?.PercentualRuaAtual ?? 0;
r.AreaTotalPercentual = obj.Trajetoria?.PercentualOperacao ?? 0;
r.BateriaPercentual = obj.Bateria?.PercentualBateria ?? 0;
r.HerbicidaPercentual = obj.Atuador?.PercentualReservatorio ?? 0;
r.InfestacaoPercentual = obj.Atuador?.PercentualErvasTerreno ?? 0;
});
_viewOperacaoCenter?.Diagnostico?._vm?.AtualizarDados(rover);
_viewOperacaoCenter?.Diagnostico?.AtualizarGraficos();
}
}
private void OnRoverSelecionadoChanged(string roverId)
{
_viewOperacaoRight?._vm?.DestacarRoverNoMapa(roverId);
}
private void OnRoverSelecionadoChanged(RoverCardModel rover)
{
bool dados_base = rover == null;
@ -683,7 +695,7 @@ namespace OperationControl.ViewModels
VariaveisControleOperacao.RoverEmFoco?.RuasPercorrer?.Clear();
AtualizarDadosMapa(VariaveisControleOperacao.RoverEmFoco);
VariaveisControleOperacao.SelectedRoverId = VariaveisControleOperacao.BaseMarkerID;
_viewOperacaoCenter?.MapaOperacao?.markers?.SetMarkerFocused(VariaveisControleOperacao.SelectedRoverId, true);
_viewOperacaoCenter?.Mapa?.markers?.SetMarkerFocused(VariaveisControleOperacao.SelectedRoverId, true);
_viewOperacaoCenter?._vm?.SelecionarRoverLista(rover);
@ -693,7 +705,7 @@ namespace OperationControl.ViewModels
else
{
VariaveisControleOperacao.SelectedRoverId = rover.NumeroSerie.ToString();
_viewOperacaoCenter?.MapaOperacao?.markers?.SetMarkerFocused(VariaveisControleOperacao.SelectedRoverId, true);
_viewOperacaoCenter?.Mapa?.markers?.SetMarkerFocused(VariaveisControleOperacao.SelectedRoverId, true);
AtualizarDadosTela(VariaveisControleOperacao.RoverEmFoco);
VariaveisControleOperacao.RequisitarParametrosOperacao();
@ -703,12 +715,15 @@ namespace OperationControl.ViewModels
_viewOperacaoCenter?._vm?.SelecionarRoverLista(rover);
AtualizarDadosMapa(VariaveisControleOperacao.RoverEmFoco);
AtualizarTopOperacaoRover(VariaveisControleOperacao.SelectedRoverId, ModoOperacaoAtual == ModoOperacao.Manual);
}
}
private void OnSecaoSelecionadaChanged(SecaoRightBar secao)
{
_viewOperacaoCenter?._vm?.DefinirSecao(secao);
AtualizarDadosMapa(VariaveisControleOperacao.RoverEmFoco);
}
private void OnLerParametrosSolicitado()
@ -727,7 +742,7 @@ namespace OperationControl.ViewModels
var rover = VariaveisControleOperacao.RoverEmFoco;
VariaveisControleOperacao.EnviarParametrosOperacao(new OperacaoParametrosModel()
var novosParametros = new OperacaoParametrosModel()
{
Modo = parametros.Modo ?? AgroBase.Models.Enums.ModoOperacao.NaoDefinido,
Descricao = parametros.Descricao,
@ -740,9 +755,20 @@ namespace OperationControl.ViewModels
ModulosMandatorios = rover?.ModulosMandatorios,
ParametrosMandatorios = rover?.ParametrosMandatorios,
RuasPercorrer = _viewOperacaoCenter?.MapaOperacao?.RuasMapaCarregado?.Where(x => x.Selected)?.Select(x => x.Id)?.ToList(),
Mapa = _viewOperacaoCenter?.MapaOperacao?.CriarDadosMapa()
});
RuasPercorrer = _viewOperacaoCenter?.Mapa?.RuasMapaCarregado?.Where(x => x.Selected)?.Select(x => x.Id)?.ToList(),
Mapa = _viewOperacaoCenter?.Mapa?.CriarDadosMapa()
};
VariaveisControleOperacao.EnviarParametrosOperacao(novosParametros);
VariaveisControleOperacao.RoverEmFoco.Modo = novosParametros.Modo;
VariaveisControleOperacao.RoverEmFoco.Descricao = novosParametros.Descricao;
VariaveisControleOperacao.RoverEmFoco.QtdCamerasSolo = novosParametros.QtdCamerasSolo;
VariaveisControleOperacao.RoverEmFoco.QtdBicos = novosParametros.QtdBicos;
VariaveisControleOperacao.RoverEmFoco.CapacidadeReservatorio = novosParametros.CapacidadeReservatorio;
VariaveisControleOperacao.RoverEmFoco.Controle = novosParametros.Controle;
VariaveisControleOperacao.RoverEmFoco.RuasPercorrer = novosParametros.RuasPercorrer;
VariaveisControleOperacao.RoverEmFoco.Mapa = novosParametros.Mapa;
}

View File

@ -87,49 +87,23 @@
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="0,0,0,8">
<TextBlock Text="Câmera de ervas"
DockPanel.Dock="Left"
FontSize="12"
FontWeight="SemiBold"
Foreground="#E6E6E6"
VerticalAlignment="Center"/>
<TextBlock Text="Câmera de ervas" DockPanel.Dock="Left" FontSize="12" FontWeight="SemiBold" Foreground="#E6E6E6" VerticalAlignment="Center"/>
<ComboBox x:Name="cmbCameraTraseiraFrameTipo"
Width="130"
Height="26"
HorizontalAlignment="Right"
SelectionChanged="cmbCameraTraseiraFrameTipo_SelectionChanged"/>
<ComboBox x:Name="cmbCameraTraseiraFrameTipo" Width="130" Height="26" HorizontalAlignment="Right" SelectionChanged="cmbCameraTraseiraFrameTipo_SelectionChanged"/>
<TextBlock Text="10 fps • 193 kbps • 5,41% erva • 0,00% cana"
Margin="12,0,8,0"
Foreground="#AFAFAF"
FontSize="11"
VerticalAlignment="Center"
HorizontalAlignment="Right"/>
<TextBlock Text="10 fps • 193 kbps • 5,41% erva • 0,00% cana" Margin="12,0,8,0" Foreground="#AFAFAF" FontSize="11" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</DockPanel>
<Grid Grid.Row="1" Background="Black">
<TextBlock Text="SEM SINAL"
Foreground="Gray"
FontSize="18"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Text="SEM SINAL" Foreground="Gray" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image x:Name="imgVideoWeed"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="LowQuality"/>
<Image x:Name="imgVideoWeed" Stretch="Uniform" RenderOptions.BitmapScalingMode="LowQuality"/>
</Grid>
</Grid>
</Border>
<!-- MAPA -->
<Border Grid.Row="1" Grid.Column="0"
Margin="0,6,6,0"
Padding="10"
CornerRadius="10"
Background="#202020"
BorderBrush="#353535"
BorderThickness="1">
<Border Grid.Row="1" Grid.Column="0" Margin="0,6,6,0" Padding="10" CornerRadius="10" Background="#202020" BorderBrush="#353535" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
@ -137,12 +111,7 @@
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Mapa e posição do rover"
Margin="0,0,0,8"
FontSize="12"
FontWeight="SemiBold"
Foreground="#E6E6E6"/>
<TextBlock Grid.Row="0" Text="Mapa e posição do rover" Margin="0,0,0,8" FontSize="12" FontWeight="SemiBold" Foreground="#E6E6E6"/>
<controls:MapViewControl x:Name="MapaMonitoramento" MarkerClicked="MAP_MarkerClicked" StreetMapClicked="MAP_StreetMapClicked" Grid.Row="1" Latitude="-22.1726572492617" Longitude="-47.3952163870556" Scale="5000" />
</Grid>
@ -157,12 +126,12 @@
<!-- PULVERIZADOR -->
<Border Grid.Row="0"
Margin="0,0,0,6"
Padding="10"
CornerRadius="10"
Background="#202020"
BorderBrush="#353535"
BorderThickness="1">
Margin="0,0,0,6"
Padding="10"
CornerRadius="10"
Background="#202020"
BorderBrush="#353535"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
@ -171,27 +140,21 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Pulverizador"
Margin="0,0,0,8"
FontSize="12"
FontWeight="SemiBold"
Foreground="#E6E6E6"/>
Text="Pulverizador"
Margin="0,0,0,8"
FontSize="12"
FontWeight="SemiBold"
Foreground="#E6E6E6"/>
<Grid Grid.Row="1">
<controls:PulverizadorIndicator x:Name="ATU"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"/>
</Grid>
<controls:PulverizadorIndicator Grid.Row="1"
x:Name="areaPulverizador"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
</Border>
<!-- AÇÕES RÁPIDAS -->
<Border Grid.Row="1"
Padding="10"
CornerRadius="10"
Background="#202020"
BorderBrush="#353535"
BorderThickness="1">
<Border Grid.Row="1" Padding="10" CornerRadius="10" Background="#202020" BorderBrush="#353535" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
@ -199,12 +162,7 @@
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Ações rápidas"
Margin="0,0,0,8"
FontSize="12"
FontWeight="SemiBold"
Foreground="#E6E6E6"/>
<TextBlock Grid.Row="0" Text="Ações rápidas" Margin="0,0,0,8" FontSize="12" FontWeight="SemiBold" Foreground="#E6E6E6"/>
<UniformGrid Grid.Row="1" Rows="2" Columns="2" Margin="0,4,0,0">
<Button Content="Retorno à base" Margin="4"/>

View File

@ -14,6 +14,7 @@ namespace OperationControl.Views.Operacao
private TcpVideoReceiver videoFront;
private TcpVideoReceiver videoWeed;
public PulverizadorIndicator Pulverizador => areaPulverizador;
public MonitoramentoView()
{

View File

@ -20,7 +20,7 @@
<!-- MAPA -->
<Grid Visibility="{Binding ExibirMapa, Converter={StaticResource BoolToVisibilityConverter}}">
<Border Margin="12" Padding="0" CornerRadius="10" Background="#202020" BorderBrush="#353535" BorderThickness="1" ClipToBounds="True">
<controls:MapViewControl x:Name="MapaOperacao" MarkerClicked="MAP_MarkerClicked" StreetMapClicked="MAP_StreetMapClicked" Latitude="-22.1726572492617" Longitude="-47.3952163870556" Scale="5000" />
<controls:MapViewControl x:Name="areaMapa" MarkerClicked="MAP_MarkerClicked" StreetMapClicked="MAP_StreetMapClicked" Latitude="-22.1726572492617" Longitude="-47.3952163870556" Scale="5000" />
</Border>
</Grid>

View File

@ -1,9 +1,5 @@
using OperationControl.Controls;
using OperationControl.Models;
using OperationControl.ViewModels.Views.Operacao;
using OperationControl.ViewModels.Views.Operacao.Diagnostico;
using OperationControl.ViewModels.Views.Operacao.Monitoramento;
using System.Windows.Controls;
namespace OperationControl.Views.Operacao
{
@ -13,6 +9,7 @@ namespace OperationControl.Views.Operacao
public partial class OperacaoCenterView : System.Windows.Controls.UserControl
{
public OperacaoCenterViewModel _vm;
public MapViewControl Mapa => _vm?.ConteudoAtual == OperacaoCenterConteudo.Mapa ? areaMapa : _vm?.ConteudoAtual == OperacaoCenterConteudo.Monitoramento ? Monitoramento?.MapaMonitoramento : null;
public MonitoramentoView Monitoramento => areaMonitor;
public DiagnosticoView Diagnostico => areaDiagnostico;