#nullable enable using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text.Json; public class UdpSender : IDisposable { private readonly UdpClient client = new UdpClient(); private IPEndPoint endpoint; private const string DefaultIp = "192.168.1.50"; private const int DefaultPort = 12345; public UdpSender() { string exeDir = AppContext.BaseDirectory; string cfgPath = Path.Combine(exeDir, "config.json"); // Create default config if missing if (!File.Exists(cfgPath)) { var defaultCfg = new UdpConfig { esp32_ip = DefaultIp, esp32_port = DefaultPort }; string json = JsonSerializer.Serialize( defaultCfg, new JsonSerializerOptions { WriteIndented = true } ); File.WriteAllText(cfgPath, json); } // Load config var jsonText = File.ReadAllText(cfgPath); var cfg = JsonSerializer.Deserialize(jsonText) ?? throw new Exception("Invalid config.json"); endpoint = new IPEndPoint(IPAddress.Parse(cfg.esp32_ip), cfg.esp32_port); } public void SendFloats(float[] values) { try { string packet = string.Join(",", values); byte[] data = System.Text.Encoding.ASCII.GetBytes(packet); client.Send(data, data.Length, endpoint); } catch (SocketException ex) when ( ex.SocketErrorCode == SocketError.NetworkUnreachable || ex.SocketErrorCode == SocketError.HostUnreachable || ex.SocketErrorCode == SocketError.NetworkDown || ex.SocketErrorCode == SocketError.AddressNotAvailable) { // Network not ready (sleep, reconnecting, etc.) // Skip this tick silently. } catch (ObjectDisposedException) { // App is shutting down — ignore. } catch (Exception) { // Any other unexpected error — swallow to avoid crashing the tray app. } } public void Dispose() { client.Dispose(); } private class UdpConfig { public string esp32_ip { get; set; } = DefaultIp; public int esp32_port { get; set; } = DefaultPort; } }