-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfrmSimulator.cs
More file actions
202 lines (174 loc) · 7.22 KB
/
Copy pathfrmSimulator.cs
File metadata and controls
202 lines (174 loc) · 7.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
namespace ModuleSimulator
{
/// <summary>
/// Simulates a YieldFlo hardware module — sends PK1 (sensor, 5 Hz) and
/// PK2 (temperature, 1 Hz) UDP packets to the YieldFlo PC app on port 30100.
///
/// Moisture and temperature sliders represent calibrated values.
/// Raw counts sent = value / default_scale so the PC app reads correctly
/// at default scale settings (moist_scale=0.001, temp_scale=0.0125).
/// </summary>
public partial class frmSimulator : Form
{
private const int PC_RECV_PORT = 30100;
private const int SIM_SEND_PORT = 30201;
// Default scales — must match Core.cs defaults so slider values display correctly
private const double DefaultMoistScale = 0.001; // %/count
private const double DefaultTempScale = 0.0125; // °C/count
private System.Windows.Forms.Timer _sendTimer;
private UdpClient _udp;
private IPEndPoint _target;
private double _simAngle = 0;
private int _pk2Ticks = 0; // PK2 sent every 10 ticks (1 Hz at 100 ms timer)
public frmSimulator()
{
InitializeComponent();
}
private static readonly string _posFile =
Path.Combine(Application.LocalUserAppDataPath, "simpos.txt");
private void frmSimulator_Load(object sender, EventArgs e)
{
RestorePosition();
try
{
_udp = new UdpClient(SIM_SEND_PORT);
_target = new IPEndPoint(IPAddress.Loopback, PC_RECV_PORT);
}
catch (Exception ex)
{
lblStatus.Text = "UDP Error: " + ex.Message;
return;
}
_sendTimer = new System.Windows.Forms.Timer { Interval = 100 }; // 10 Hz
_sendTimer.Tick += SendTimer_Tick;
_sendTimer.Start();
lblStatus.Text = "Sending to 127.0.0.1:" + PC_RECV_PORT;
}
private void RestorePosition()
{
try
{
if (!File.Exists(_posFile)) return;
var parts = File.ReadAllText(_posFile).Split(',');
if (parts.Length == 2 && int.TryParse(parts[0], out int x) && int.TryParse(parts[1], out int y))
{
var pt = new System.Drawing.Point(x, y);
foreach (Screen s in Screen.AllScreens)
if (s.WorkingArea.Contains(pt)) { Location = pt; return; }
}
}
catch { }
}
private void SavePosition()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_posFile));
File.WriteAllText(_posFile, $"{Location.X},{Location.Y}");
}
catch { }
}
private void chkSections_CheckedChanged(object sender, EventArgs e)
{
chkSections.ForeColor = chkSections.Checked
? System.Drawing.Color.DarkGreen
: System.Drawing.Color.Red;
}
private void SendTimer_Tick(object sender, EventArgs e)
{
_simAngle += 0.05;
_pk2Ticks++;
double yieldSlider = trkYield.Value / 100.0; // 0.0 – 1.0
double moistureSlider = trkMoisture.Value / 10.0; // 0.0 – 30.0 %
double tempSlider = trkTemperature.Value / 10.0; // -10.0 – 50.0 °C
double variation = trkVariation.Value / 100.0;
lblVariationSlider.Text = $"Variation: {trkVariation.Value}%";
// Sensor obstruction ratio
const double SimBaseline = 0.2;
double ratio = 0;
if (chkSections.Checked)
{
double flow = chkSineWave.Checked
? yieldSlider * (1.0 + variation * Math.Sin(_simAngle))
: yieldSlider;
ratio = SimBaseline + flow * (1.0 - SimBaseline);
}
// sensor_ratio: uint16, 0–1000 (ratio × 1000)
ushort sensorRatio = (ushort)Math.Max(0, Math.Min(1000, ratio * 1000));
// moisture_raw: raw ADS1115 count — back-calculated from % using default scale
ushort moistRaw = (ushort)Math.Max(0, Math.Min(65535, moistureSlider / DefaultMoistScale));
byte flags = 0x05; // bit0=SensorOK, bit2=MoistureOK (no RPM sensor in sim)
ushort rpm = 200; // fixed RPM-absent sentinel
SendPK1(sensorRatio, moistRaw, rpm, flags);
if (_pk2Ticks >= 10)
{
_pk2Ticks = 0;
// temp_raw: raw ADS1115 count — back-calculated from °C using default scale
short tempRaw = (short)Math.Max(-32768, Math.Min(32767, tempSlider / DefaultTempScale));
SendPK2(tempRaw);
}
// Update UI labels
lblSensor1.Text = $"S1: {ratio:F3}";
lblMoistureVal.Text = $"Mst: {moistureSlider:F1}%";
lblTempVal.Text = $"Tmp: {tempSlider:F1}°C";
}
private void SendPK1(ushort sensorRatio, ushort moistureRaw, ushort rpm, byte flags)
{
// PK1 — 11 bytes:
// [0-1] PGN 40001 LE
// [2] flags bit0=SensorOK, bit1=RPMPresent, bit2=MoistureOK
// [3-4] sensor_ratio uint16 LE (ratio × 1000)
// [5-6] moisture_raw uint16 LE (raw ADS1115 AIN0-AIN1 count)
// [7-8] module_rpm uint16 LE
// [9] noise_count uint8
// [10] CRC8
byte[] pkt = new byte[11];
pkt[0] = 0x41;
pkt[1] = 0x9C;
pkt[2] = flags;
pkt[3] = (byte)(sensorRatio & 0xFF);
pkt[4] = (byte)(sensorRatio >> 8);
pkt[5] = (byte)(moistureRaw & 0xFF);
pkt[6] = (byte)(moistureRaw >> 8);
pkt[7] = (byte)(rpm & 0xFF);
pkt[8] = (byte)(rpm >> 8);
pkt[9] = 0; // noise_count
int ck = 0;
for (int i = 0; i < 10; i++) ck += pkt[i];
pkt[10] = (byte)ck;
try { _udp.Send(pkt, pkt.Length, _target); } catch { }
}
private void SendPK2(short tempRaw)
{
// PK2 — 7 bytes:
// [0-1] PGN 40002 LE
// [2] flags bit0=TempOK, bit1=PaddleHzPresent
// [3-4] temp_raw int16 LE (raw ADS1115 AIN2 count)
// [5] paddle_hz uint8 (paddles/s)
// [6] CRC8
byte[] pkt = new byte[7];
pkt[0] = 0x42;
pkt[1] = 0x9C;
pkt[2] = 0x03; // TempOK + PaddleHzPresent
pkt[3] = (byte)(tempRaw & 0xFF);
pkt[4] = (byte)((tempRaw >> 8) & 0xFF);
pkt[5] = 7; // fixed simulated paddle rate
int ck = 0;
for (int i = 0; i < 6; i++) ck += pkt[i];
pkt[6] = (byte)ck;
try { _udp.Send(pkt, pkt.Length, _target); } catch { }
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
SavePosition();
_sendTimer?.Stop();
_udp?.Close();
base.OnFormClosed(e);
}
}
}