///============================================================================
/// MKLiveView
/// Copyright © 2016 Steph
///
///This file is part of MKLiveView.
///
///MKLiveView is free software: you can redistribute it and/or modify
///it under the terms of the GNU General Public License as published by
///the Free Software Foundation, either version 3 of the License, or
///(at your option) any later version.
///
///MKLiveView is distributed in the hope that it will be useful,
///but WITHOUT ANY WARRANTY; without even the implied warranty of
///MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
///GNU General Public License for more details.
///
///You should have received a copy of the GNU General Public License
///along with cssRcon. If not, see <http://www.gnu.org/licenses/>.
///
///============================================================================
///Credits:
///Chootair (http://www.codeproject.com/script/Membership/View.aspx?mid=3941737)
///for his "C# Avionic Instrument Controls" (http://www.codeproject.com/Articles/27411/C-Avionic-Instrument-Controls)
///I used some of his code for displaying the compass
///
///Tom Pyke (http://tom.pycke.be)
///for his "Artifical horizon" (http://tom.pycke.be/mav/100/artificial-horizon)
///Great job!
///
/// and last but most of all to JOHN C. MACDONALD at Ira A. Fulton College of Engineering and Technology
/// for his MIKROKOPTER SERIAL CONTROL TUTORIAL (http://hdl.lib.byu.edu/1877/2747)
/// and the sourcode (http://hdl.lib.byu.edu/1877/2748)
/// By his work I finally managed to get the communication with the Mikrokopter controllers to run
/// Some of his code was used in this programm like the SimpelSerialPort class (with some changes)
/// and the FilghtControllerMessage class
///
///============================================================================
using System;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace MKLiveView
{
public partial class MainForm
: Form
{
[FlagsAttribute
]
enum NC_HWError0
: short
{
None
= 0,
SPI_RX
= 1,
COMPASS_RX
= 2,
FC_INCOMPATIBLE
= 4,
COMPASS_INCOMPATIBLE
= 8,
GPS_RX
= 16,
COMPASS_VALUE
= 32
};
[FlagsAttribute
]
enum FC_HWError0
: short
{
None
= 0,
GYRO_NICK
= 1,
GYRO_ROLL
= 2,
GYRO_YAW
= 4,
ACC_NICK
= 8,
ACC_ROLL
= 16,
ACC_TOP
= 32,
PRESSURE
= 64,
CAREFREE
= 128
};
[FlagsAttribute
]
enum FC_HWError1
: short
{
None
= 0,
I2C
= 1,
BL_MISSING
= 2,
SPI_RX
= 4,
PPM
= 8,
MIXER
= 16,
RC_VOLTAGE
= 32,
ACC_NOT_CAL
= 64,
RES3
= 128
};
public enum LogMsgType
{ Incoming, Outgoing, Normal, Warning, Error
};
// Various colors for logging info
private Color
[] LogMsgTypeColor
= { Color
.FromArgb(43,
145,
175), Color
.Green, Color
.Black, Color
.Orange, Color
.Red };
string[] sAnalogLabel
= new string[32];
string[] sAnalogData
= new string[32];
bool bReadContinously
= false;
bool check_HWError
= false;
bool _bCBInit
= true;
bool _init
= true;
bool _debugDataAutorefresh
= true;
bool _navCtrlDataAutorefresh
= true;
bool _blctrlDataAutorefresh
= true;
bool _OSDAutorefresh
= true;
int crcError
= 0;
int iLableIndex
= 0;
string filePath
= Directory
.GetCurrentDirectory();
string fileName
= "NCLabelTexts.txt";
int _iCtrlAct
= 0;
int _iLifeCounter
= 0;
int iOSDPage
= 0;
int iOSDMax
= 0;
/// <summary>
/// interval for sending debugdata (multiplied by 10ms)
/// </summary>
byte debugInterval
= 25; //(=> 250ms)
/// <summary>
/// interval for sending BL-CTRL status (multiplied by 10ms)
/// </summary>
byte blctrlInterval
= 45;
/// <summary>
/// interval for sending NAV-CTRL status (multiplied by 10ms)
/// </summary>
byte navctrlInterval
= 80;
/// <summary>
/// interval for sending OSD page update (multiplied by 10ms)
/// </summary>
byte OSDInterval
= 85;
/// <summary>
/// datatable for the debug data array - displayed on settings tabpage in datagridview
/// </summary>
DataTable dtAnalog
= new DataTable
();
public MainForm
()
{
InitializeComponent
();
_readIni
();
dtAnalog
.Columns.Add("ID");
dtAnalog
.Columns.Add("Value");
dataGridView1
.DataSource = dtAnalog
;
simpleSerialPort
.PortClosed += SimpleSerialPort_PortClosed
;
simpleSerialPort
.PortOpened += SimpleSerialPort_PortOpened
;
simpleSerialPort
.DataReceived += processMessage
;
chkbAutoBL
.Checked = _blctrlDataAutorefresh
;
chkbAutoDbg
.Checked = _debugDataAutorefresh
;
chkbAutoNav
.Checked = _navCtrlDataAutorefresh
;
chkbAutoOSD
.Checked = _OSDAutorefresh
;
labelTimingDebug
.Text = (debugInterval
* 10).ToString();
labelTimingBLCTRL
.Text = (blctrlInterval
* 10).ToString();
labelTimingNAV
.Text = (navctrlInterval
* 10).ToString();
labelTimingOSD
.Text = (OSDInterval
* 10).ToString();
tabControl1
.TabPages.Remove(tabPageTesting
);
}
#region events
private void MainForm_Shown
(object sender, EventArgs e
)
{
_loadLabelNames
();
_init
= false;
}
private void MainForm_FormClosed
(object sender, FormClosedEventArgs e
)
{
_writeIni
();
}
private void SimpleSerialPort_PortOpened
()
{
btnConn
.Invoke((Action
)(() => btnConn
.BackColor = Color
.FromArgb(192,
255,
192)));
btnConn
.Invoke((Action
)(() => btnConn
.Text = "close" + Environment
.NewLine + "serial port"));
_getVersion
();
Thread
.Sleep(100);
_OSDMenue
(0);
// _readCont(true);
}
private void SimpleSerialPort_PortClosed
()
{
btnConn
.Invoke((Action
)(() => btnConn
.BackColor = Color
.FromArgb(224,
224,
224)));
btnConn
.Invoke((Action
)(() => btnConn
.Text = "open" + Environment
.NewLine + "serial port"));
_readCont
(false);
}
/// <summary>
/// timer for refreshing subscription of subscribed data
/// query lifecounter for connection failure
/// </summary>
private void timer1_Tick
(object sender, EventArgs e
)
{
if(bReadContinously
)
{
if (_debugDataAutorefresh
) { _readDebugData
(true); Thread
.Sleep(10); }
if (_blctrlDataAutorefresh
&& _iCtrlAct
== 2) { _readBLCtrl
(true); Thread
.Sleep(10); }
if (_navCtrlDataAutorefresh
&& _iCtrlAct
== 2) { _readNavData
(true); Thread
.Sleep(10); }
check_HWError
= true;
_getVersion
();
Thread
.Sleep(10);
if (_OSDAutorefresh
) { _OSDMenueAutoRefresh
(); }
if (_iLifeCounter
> 0)
{
lblLifeCounter
.BackColor = Color
.FromArgb(0,
224,
0);
_iLifeCounter
= 0;
}
else
{
Log
(LogMsgType
.Error,
"No communication to NC/FC!");
lblLifeCounter
.BackColor = Color
.FromArgb(224,
0,
0);
}
}
}
private void cbOSD_SelectedIndexChanged
(object sender, EventArgs e
)
{
if (!_bCBInit
&& cbOSD
.SelectedIndex > -1)
_OSDMenue
(cbOSD
.SelectedIndex);
}
private void chkbAutoDbg_CheckedChanged
(object sender, EventArgs e
)
{
if(!_init
) _debugDataAutorefresh
= chkbAutoDbg
.Checked;
}
private void chkbAutoNav_CheckedChanged
(object sender, EventArgs e
)
{
if (!_init
) _navCtrlDataAutorefresh
= chkbAutoNav
.Checked;
}
private void chkbAutoBL_CheckedChanged
(object sender, EventArgs e
)
{
if (!_init
) _blctrlDataAutorefresh
= chkbAutoBL
.Checked;
}
private void chkbAutoOSD_CheckedChanged
(object sender, EventArgs e
)
{
if (!_init
) _OSDAutorefresh
= chkbAutoOSD
.Checked;
}
private void cbTimingDebug_SelectedIndexChanged
(object sender, EventArgs e
)
{
if (cbTimingDebug
.SelectedIndex > -1)
{
debugInterval
= (byte)(Convert
.ToInt16(cbTimingDebug
.SelectedItem) / 10);
labelTimingDebug
.Text = (debugInterval
* 10).ToString();
}
}
private void cbTimingNAV_SelectedIndexChanged
(object sender, EventArgs e
)
{
if (cbTimingNAV
.SelectedIndex > -1)
{
navctrlInterval
= (byte)(Convert
.ToInt16(cbTimingNAV
.SelectedItem) / 10);
labelTimingNAV
.Text = (navctrlInterval
* 10).ToString();
}
}
private void cbTimingBLCTRL_SelectedIndexChanged
(object sender, EventArgs e
)
{
if (cbTimingBLCTRL
.SelectedIndex > -1)
{
blctrlInterval
= (byte)(Convert
.ToInt16(cbTimingBLCTRL
.SelectedItem) / 10);
labelTimingBLCTRL
.Text = (blctrlInterval
* 10).ToString();
}
}
private void cbTimingOSD_SelectedIndexChanged
(object sender, EventArgs e
)
{
if (cbTimingOSD
.SelectedIndex > -1)
{
OSDInterval
= (byte)(Convert
.ToInt16(cbTimingOSD
.SelectedItem) / 10);
labelTimingOSD
.Text = (OSDInterval
* 10).ToString();
}
}
#endregion events
/// <summary> Log data to the terminal window. </summary>
/// <param name="msgtype"> The type of message to be written. </param>
/// <param name="msg"> The string containing the message to be shown. </param>
private void Log
(LogMsgType msgtype,
string msg
)
{
rtfTerminal
.Invoke(new EventHandler
(delegate
{
if (rtfTerminal
.Lines.Length >= 1000) //Wenn Terminal mehr als 1000 Zeilen hat
rtfTerminal
.Select(42,
(500 * 129)); //500 löschen
rtfTerminal
.Select(rtfTerminal
.Text.Length,
0);
rtfTerminal
.SelectedText = string.Empty;
rtfTerminal
.SelectionFont = new Font
(rtfTerminal
.SelectionFont, FontStyle
.Regular);
rtfTerminal
.SelectionColor = LogMsgTypeColor
[(int)msgtype
];
rtfTerminal
.AppendText(msg
+ Environment
.NewLine);
rtfTerminal
.ScrollToCaret();
}));
}
/// <summary> display the OSD text in 4 lines à 20 chars </summary>
/// <param name="msgtype"> The type of message to be written. </param>
/// <param name="msg"> The string containing the message to be shown. </param>
private void OSD
(LogMsgType msgtype,
string msg
)
{
rtfOSD
.Invoke(new EventHandler
(delegate
{
if (rtfOSD
.Lines.Length > 4)
rtfOSD
.Clear();
rtfOSD
.Select(rtfOSD
.Text.Length,
0);
rtfOSD
.SelectedText = string.Empty;
rtfOSD
.SelectionFont = new Font
(rtfOSD
.SelectionFont, FontStyle
.Regular);
rtfOSD
.SelectionColor = LogMsgTypeColor
[(int)msgtype
];
rtfOSD
.AppendText(msg
+ Environment
.NewLine);
if (rtfOSD
.Text.IndexOf("ERR") > 0)
{
rtfOSD
.Select(rtfOSD
.Text.IndexOf("ERR"),
40);
rtfOSD
.SelectionColor = LogMsgTypeColor
[(int)LogMsgType
.Error];
}
}));
}
private void ErrorLog
(LogMsgType msgtype,
string msg
)
{
rtfError
.Invoke(new EventHandler
(delegate
{
if (rtfError
.Lines.Length > 4)
rtfError
.Clear();
rtfError
.Focus();
rtfError
.Select(rtfError
.Text.Length,
0);
rtfError
.SelectedText = string.Empty;
rtfError
.SelectionFont = new Font
(rtfError
.SelectionFont, FontStyle
.Regular);
rtfError
.SelectionColor = LogMsgTypeColor
[(int)msgtype
];
rtfError
.AppendText(msg
+ Environment
.NewLine);
}));
}
#region functions
/// <summary> Processing the messages and displaying them in the according form controls </summary>
/// <param name="message"> message bytearray recieved by SimpleSerialPort class </param>
private void processMessage
(byte[] message
)
{
if (message
.Length > 0)
{
_iLifeCounter
++;
//Log(LogMsgType.Incoming, BitConverter.ToString(message));
//Log(LogMsgType.Incoming, message.Length.ToString());
string s
= new string(ASCIIEncoding
.ASCII.GetChars(message,
0, message
.Length));
char cmdID
;
byte adr
;
byte[] data
;
if (message
[0] != '#')
Log
(LogMsgType
.Normal, s
.Trim('\0',
'\n',
'\r'));
//Debug.Print(s);
else
{
FlightControllerMessage
.ParseMessage(message,
out cmdID,
out adr,
out data
);
if (adr
== 255) { crcError
++; }
else crcError
= 0;
lblCRCErr
.Invoke((Action
)(() => lblCRCErr
.Text = crcError
.ToString()));
if (adr
> 0 && adr
< 3 && adr
!= _iCtrlAct
) //adr < 3: temporary workaround cause when I've connected the FC alone it always switches between mk3mag & FC every second...???
{
_iCtrlAct
= adr
;
switch (adr
)
{
case 1:
lblCtrl
.Invoke((Action
)(() => lblCtrl
.Text = "FC"));
lblNCCtrl
.Invoke((Action
)(() => lblNCCtrl
.Text = "FC"));
_setFieldsNA
(); //display fields NA for FC
break;
case 2:
lblCtrl
.Invoke((Action
)(() => lblCtrl
.Text = "NC"));
lblNCCtrl
.Invoke((Action
)(() => lblNCCtrl
.Text = "NC"));
break;
case 3:
lblCtrl
.Invoke((Action
)(() => lblCtrl
.Text = "MK3MAG"));
break;
case 4:
lblCtrl
.Invoke((Action
)(() => lblCtrl
.Text = "BL-CTRL"));
break;
default:
lblCtrl
.Invoke((Action
)(() => lblCtrl
.Text = "...."));
break;
}
_loadLabelNames
();
}
// else
// Debug.Print("Address == 0?");
if (data
!= null && data
.Length > 0)
{
s
= new string(ASCIIEncoding
.ASCII.GetChars(data,
1, data
.Length - 1));
s
= s
.Trim('\0',
'\n');
switch (cmdID
)
{
case 'A':
if (iLableIndex
< 32)
{
sAnalogLabel
[iLableIndex
] = s
;
if (dtAnalog
.Rows.Count < 32)
dtAnalog
.Rows.Add(s,
"");
else
dtAnalog
.Rows[iLableIndex
].SetField(0, s
);
_getAnalogLabels
(iLableIndex
+ 1);
}
Debug
.Print(s
);
break;
case 'D':
if (data
.Length == 66)
{
int[] iAnalogData
= new int[32];
int index
= 0;
Int16 i16
= 0;
double dTemp
= 0;
for (int i
= 2; i
< 66; i
+= 2)
{
i16
= data
[i
+ 1];
i16
= (Int16
)(i16
<< 8);
iAnalogData
[index
] = data
[i
] + i16
;
sAnalogData
[index
] = (data
[i
] + i16
).ToString();
dtAnalog
.Rows[index
].SetField(1, sAnalogData
[index
]);
if (adr
== 2) //NC
{
switch (index
)
{
case 0: //pitch (German: nick)
artificialHorizon1
.Invoke((Action
)(() => artificialHorizon1
.pitch_angle = ((double)iAnalogData
[index
] / (double)10)));
lblNCPitch
.Invoke((Action
)(() => lblNCPitch
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0°")));
break;
case 1: //roll
artificialHorizon1
.Invoke((Action
)(() => artificialHorizon1
.roll_angle = ((double)iAnalogData
[index
] / (double)10)));
lblNCRoll
.Invoke((Action
)(() => lblNCRoll
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0°")));
break;
case 4: //altitude
lblNCAlt
.Invoke((Action
)(() => lblNCAlt
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 m")));
break;
case 7: //Voltage
lblNCVolt
.Invoke((Action
)(() => lblNCVolt
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 V")));
break;
case 8: // Current
lblNCCur
.Invoke((Action
)(() => lblNCCur
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 A")));
break;
case 10: //heading
lblNCCompass
.Invoke((Action
)(() => lblNCCompass
.Text = sAnalogData
[index
] + "°"));
headingIndicator1
.Invoke((Action
)(() => headingIndicator1
.SetHeadingIndicatorParameters(iAnalogData
[index
])));
break;
case 12: // SPI error
lblNCSPI
.Invoke((Action
)(() => lblNCSPI
.Text = sAnalogData
[index
]));
break;
case 14: //i2c error
lblNCI2C
.Invoke((Action
)(() => lblNCI2C
.Text = sAnalogData
[index
]));
break;
case 20: //Earthmagnet field
lblNCMF
.Invoke((Action
)(() => lblNCMF
.Text = sAnalogData
[index
] + "%"));
break;
case 21: //GroundSpeed
lblNCGSpeed
.Invoke((Action
)(() => lblNCGSpeed
.Text = ((double)iAnalogData
[index
] / (double)100).ToString("0.00 m/s")));
break;
case 28: //Distance East from saved home position -> calculate distance with distance N + height
dTemp
= Math
.Pow((double)iAnalogData
[index
],
2) + Math
.Pow((double)iAnalogData
[index
- 1],
2);
dTemp
= Math
.Sqrt(dTemp
)/ (double)10; //'flat' distance from HP with N/E
// lblNCDist.Invoke((Action)(() => lblNCDist.Text = dTemp.ToString("0.00")));
dTemp
= Math
.Pow(dTemp,
2) + Math
.Pow(((double)iAnalogData
[4] / (double)10),
2); //adding 'height' into calculation
dTemp
= Math
.Sqrt(dTemp
);
lblNCDist
.Invoke((Action
)(() => lblNCDist
.Text = dTemp
.ToString("0 m")));
break;
case 31: //Sats used
lblNCSat
.Invoke((Action
)(() => lblNCSat
.Text = sAnalogData
[index
]));
break;
}
}
if (adr
== 1) //FC
{
switch (index
)
{
case 0: //pitch (German: nick)
artificialHorizon1
.Invoke((Action
)(() => artificialHorizon1
.pitch_angle = ((double)iAnalogData
[index
] / (double)10)));
lblNCPitch
.Invoke((Action
)(() => lblNCPitch
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0°")));
break;
case 1: //roll
artificialHorizon1
.Invoke((Action
)(() => artificialHorizon1
.roll_angle = ((double)iAnalogData
[index
] / (double)10)));
lblNCRoll
.Invoke((Action
)(() => lblNCRoll
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0°")));
break;
case 5: //altitude
lblNCAlt
.Invoke((Action
)(() => lblNCAlt
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 m")));
break;
case 8: //heading
lblNCCompass
.Invoke((Action
)(() => lblNCCompass
.Text = sAnalogData
[index
] + "°"));
headingIndicator1
.Invoke((Action
)(() => headingIndicator1
.SetHeadingIndicatorParameters(iAnalogData
[index
])));
break;
case 9: //Voltage
lblNCVolt
.Invoke((Action
)(() => lblNCVolt
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 V")));
break;
case 10: //Receiver quality
lblNCRC
.Invoke((Action
)(() => lblNCRC
.Text = sAnalogData
[index
]));
break;
case 22: // Current
lblNCCur
.Invoke((Action
)(() => lblNCCur
.Text = ((double)iAnalogData
[index
] / (double)10).ToString("0.0 A")));
break;
case 23: //capacity used
lblNCCap
.Invoke((Action
)(() => lblNCCap
.Text = (iAnalogData
[index
]).ToString("0 mAh")));
break;
case 27: // SPI error
lblNCSPI
.Invoke((Action
)(() => lblNCSPI
.Text = sAnalogData
[index
]));
break;
case 28: //i2c error
lblNCI2C
.Invoke((Action
)(() => lblNCI2C
.Text = sAnalogData
[index
]));
break;
}
}
index
++;
}
}
else
Debug
.Print("wrong data-length (66): " + data
.Length.ToString());
break;
case 'V':
if (data
.Length == 12)
{
if (!check_HWError
)
{
string[] sVersionStruct
= new string[10] { "SWMajor: ",
"SWMinor: ",
"ProtoMajor: ",
"LabelTextCRC: ",
"SWPatch: ",
"HardwareError 1: ",
"HardwareError 2: ",
"HWMajor: ",
"BL_Firmware: ",
"Flags: " };
string sVersion
= "";
//sbyte[] signed = Array.ConvertAll(data, b => unchecked((sbyte)b));
Log
(LogMsgType
.Warning,
(adr
== 1 ? "FC-" : "NC-") + "Version: ");
sVersion
= "HW V" + (data
[7] / 10).ToString() + "." + (data
[7] % 10).ToString();
Log
(LogMsgType
.Incoming, sVersion
);
sVersion
= "SW V" + (data
[0]).ToString() + "." + (data
[1]).ToString() + ((char)(data
[4] + 'a')).ToString();
Log
(LogMsgType
.Incoming, sVersion
);
Log
(LogMsgType
.Incoming,
"BL-Firmware: V" + (data
[8] / 100).ToString() + "." + (data
[8] % 100).ToString());
}
if(data
[5] > 0) //error0
{
if(adr
== 1)
ErrorLog
(LogMsgType
.Error,
"FC - HW-Error " + data
[5].ToString() + ": " + ((FC_HWError0
)data
[5]).ToString());
if (adr
== 2)
ErrorLog
(LogMsgType
.Error,
"NC - HW-Error " + data
[5].ToString() + ": " + ((NC_HWError0
)data
[5]).ToString());
}
if (data
[6] > 0) //error1
{
if (adr
== 1)
ErrorLog
(LogMsgType
.Error,
"FC - HW-Error " + data
[6].ToString() + ": " + ((FC_HWError1
)data
[6]).ToString());
if (adr
== 2)
ErrorLog
(LogMsgType
.Error,
"NC - Unknown HW-ERROR: " + data
[6].ToString()); //@moment NC has only one error field
}
}
check_HWError
= false;
break;
case 'K'://BL-CTRL debug data from NC
if (data
.Length == 6 && data
[0] < 8)
{
Label lbCur
= new Label
(), lbTemp
= new Label
();
switch (data
[0])
{
case 0:
lbCur
= LBLNCM1Cur
;
lbTemp
= LBLNCM1Temp
;
break;
case 1:
lbCur
= LBLNCM2Cur
;
lbTemp
= LBLNCM2Temp
;
break;
case 2:
lbCur
= LBLNCM3Cur
;
lbTemp
= LBLNCM3Temp
;
break;
case 3:
lbCur
= LBLNCM4Cur
;
lbTemp
= LBLNCM4Temp
;
break;
case 4:
lbCur
= LBLNCM5Cur
;
lbTemp
= LBLNCM5Temp
;
break;
case 5:
lbCur
= LBLNCM6Cur
;
lbTemp
= LBLNCM6Temp
;
break;
case 6:
lbCur
= LBLNCM7Cur
;
lbTemp
= LBLNCM7Temp
;
break;
case 7:
lbCur
= LBLNCM8Cur
;
lbTemp
= LBLNCM8Temp
;
break;
}
if (lbCur
!= null)
lbCur
.Invoke((Action
)(() => lbCur
.Text = ((double)data
[1] / (double)10).ToString("0.0 A")));
if (lbTemp
!= null)
lbTemp
.Invoke((Action
)(() => lbTemp
.Text = data
[2].ToString("0 °C")));
}
break;
case 'O': //NC Data
int i_16,iVal
;
i_16
= data
[81];
i_16
= (Int16
)(i_16
<< 8);
iVal
= data
[80] + i_16
;
lblNCCap
.Invoke((Action
)(() => lblNCCap
.Text = iVal
.ToString() + " mAh")); //Capacity used
i_16
= data
[56];
i_16
= (Int16
)(i_16
<< 8);
iVal
= data
[55] + i_16
;
TimeSpan t
= TimeSpan
.FromSeconds(iVal
);
string Text
= t
.Hours.ToString("D2") + ":" + t
.Minutes.ToString("D2") + ":" + t
.Seconds.ToString("D2");
lblNCFlTime
.Invoke((Action
)(() => lblNCFlTime
.Text = Text
.ToString())); //Flying time
lblNCRC
.Invoke((Action
)(() => lblNCRC
.Text = data
[66].ToString())); //RC quality
lblNCErrNmbr
.Invoke((Action
)(() => lblNCErrNmbr
.Text = data
[69].ToString())); //NC Errornumber
if (data
[69] > 0)
_readNCError
();
break;
case 'E': //NC error-string
ErrorLog
(LogMsgType
.Error,
"NC Error: " + s
);
break;
case 'L':
if(data
.Length == 84)
{
string sMessage
= "";
iOSDPage
= data
[0];
iOSDMax
= data
[1];
if (cbOSD
.Items.Count != iOSDMax
) _initOSDCB
();
sMessage
= new string(ASCIIEncoding
.ASCII.GetChars(data,
2, data
.Length - 4));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(0,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(20,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(40,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(60,
20));
lblOSDPageNr
.Invoke((Action
)(()=>lblOSDPageNr
.Text = iOSDPage
.ToString("[0]")));
}
else
OSD
(LogMsgType
.Incoming,
"Wrong length: " + data
.Length + " (should be 84)");
break;
case 'H':
if(data
.Length == 81)
{
string sMessage
= "";
sMessage
= new string(ASCIIEncoding
.ASCII.GetChars(data,
0, data
.Length - 1));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(0,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(20,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(40,
20));
OSD
(LogMsgType
.Incoming, sMessage
.Substring(60,
20));
}
else
OSD
(LogMsgType
.Incoming,
"Wrong length: " + data
.Length + " (should be 81)");
break;
//default:
// Log(LogMsgType.Incoming, "cmd: " + cmdID.ToString());
// Log(LogMsgType.Incoming, BitConverter.ToString(data));
// break;
}
}
//else
//{
// Log(LogMsgType.Incoming, "cmd: " + cmdID.ToString());
// Log(LogMsgType.Incoming, BitConverter.ToString(data));
//}
}
}
}
/// <summary> send message to controller to request data
/// for detailed info see http://wiki.mikrokopter.de/en/SerialProtocol/
/// </summary>
/// <param name="CMDID"> the command ID </param>
/// <param name="address"> the address of the controller: 0-any, 1-FC, 2-NC </param>
private void _sendControllerMessage
(char CMDID,
byte address
)
{
if (simpleSerialPort
.Port.IsOpen)
{
Stream serialStream
= simpleSerialPort
.Port.BaseStream;
byte[] bytes
= FlightControllerMessage
.CreateMessage(CMDID, address
);
serialStream
.Write(bytes,
0, bytes
.Length);
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
/// <summary> send message to controller to request data
/// for detailed info see http://wiki.mikrokopter.de/en/SerialProtocol/
/// </summary>
/// <param name="CMDID"> the command ID </param>
/// <param name="address"> the address of the controller: 0-any, 1-FC, 2-NC </param>
/// <param name="data"> additional data for the request</param>
private void _sendControllerMessage
(char CMDID,
byte address,
byte[]data
)
{
if (simpleSerialPort
.Port.IsOpen)
{
Stream serialStream
= simpleSerialPort
.Port.BaseStream;
byte[] bytes
= FlightControllerMessage
.CreateMessage(CMDID, address,data
);
serialStream
.Write(bytes,
0, bytes
.Length);
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
/// <summary>
/// read the analog-label names for the actual controller
/// and load it into listbox
/// </summary>
void _loadLabelNames
()
{
if (_iCtrlAct
> 0 && _iCtrlAct
< 3)
{
switch (_iCtrlAct
)
{
case 1:
sAnalogLabel
= Properties
.Resources.FCLabelTexts.Split(new[] { Environment
.NewLine }, StringSplitOptions
.None);
break;
case 2:
sAnalogLabel
= Properties
.Resources.NCLabelTexts.Split(new[] { Environment
.NewLine }, StringSplitOptions
.None);
break;
}
for (int i
= 0; i
< 32; i
++)
{
if (dtAnalog
.Rows.Count < 32)
dtAnalog
.Rows.Add(sAnalogLabel
[i
],
"");
else
dtAnalog
.Rows[i
].SetField(0, sAnalogLabel
[i
]);
}
dataGridView1
.Invoke((Action
)(()=>dataGridView1
.Refresh()));
}
}
/// <summary>
/// no longer used...
/// read the analog-label textfile for the actual controller
/// </summary>
private void _loadLabelFile
()
{
switch (_iCtrlAct
)
{
case 1:
fileName
= "FCLabelTexts.txt";
break;
case 2:
fileName
= "NCLabelTexts.txt";
break;
//default:
// fileName = "NCLabelTexts.txt";
// break;
}
if (File
.Exists(filePath
+ "\\" + fileName
))
{
sAnalogLabel
.Initialize();
sAnalogLabel
= File
.ReadAllLines(filePath
+ "\\" + fileName
);
lbLabels
.Invoke((Action
)(() => lbLabels
.Items.Clear()));
lbLabels
.Invoke((Action
)(() => lbLabels
.Update()));
lbLabels
.Invoke((Action
)(() => lbLabels
.Items.AddRange(sAnalogLabel
)));
Console
.WriteLine("Names loaded from file");
lblFileName
.Invoke((Action
)(() => lblFileName
.Text = fileName
));
}
else
{
_readCont
(false);
Log
(LogMsgType
.Error,
"Label-file not found!");
Log
(LogMsgType
.Error,
"Please go to settings-tab and load the label texts from the copter control (FC & NC)");
Log
(LogMsgType
.Error,
"When done, you have to save the label texts with the 'save' button!");
}
}
/// <summary>
/// no longer used...
/// assign the analog-label names from the textfile to the datatable
///
/// </summary>
private void _assignLabelNames
()
{
if (lbLabels
.Items.Count == 32)
{
lbLabels
.Items.CopyTo(sAnalogLabel,
0);
for (int i
= 0; i
< 32; i
++)
{
if (dtAnalog
.Rows.Count < 32)
dtAnalog
.Rows.Add(sAnalogLabel
[i
],
"");
else
dtAnalog
.Rows[i
].SetField(0, sAnalogLabel
[i
]);
}
}
}
/// <summary>
/// get the version struct of actual controller
/// </summary>
/// <summary>
/// get the labeltexts for the analog values
/// </summary>
private void _getAnalogLabels
()
{
if (simpleSerialPort
.Port.IsOpen)
{
iLableIndex
= 0;
for (int i
= 0; i
< 32; i
++)
{
Stream serialStream
= simpleSerialPort
.Port.BaseStream;
byte[] bytes
= FlightControllerMessage
.CreateMessage('a',
0,
new byte[1] { (byte)i
});
serialStream
.Write(bytes,
0, bytes
.Length);
Thread
.Sleep(10);
}
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
/// <summary>
/// get the labeltext for a single label
/// </summary>
/// <param name="iIndex">index of the label</param>
private void _getAnalogLabels
(int iIndex
)
{
if (simpleSerialPort
.Port.IsOpen)
{
if (iIndex
< 32)
{
iLableIndex
= iIndex
;
_sendControllerMessage
('a',
0,
new byte[1] { (byte)iLableIndex
});
}
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
private void _getVersion
()
{
_sendControllerMessage
('v',
0);
}
/// <summary>
/// get FC version struct via NC
/// by sending '1' as data (not documented in wiki...)
/// returns HW error 255 (comment in uart1.c : tells the KopterTool that it is the FC-version)
/// </summary>
/// <param name="ctrl">controller number 1=FC</param>
private void _getVersion
(byte ctrl
)
{
_sendControllerMessage
('v',
0,
new byte[1] {ctrl
});
}
/// <summary>
/// Switch back to NC by sending the 'Magic Packet' 0x1B,0x1B,0x55,0xAA,0x00
/// </summary>
private void _switchToNC
()
{
if (simpleSerialPort
.Port.IsOpen)
{
Stream serialStream
= simpleSerialPort
.Port.BaseStream;
byte[] bytes
= new byte[5] { 0x1B,0x1B,0x55,0xAA,0x00
};
serialStream
.Write(bytes,
0, bytes
.Length);
Thread
.Sleep(100);
_getVersion
();
Thread
.Sleep(100);
_OSDMenue
(0);
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
/// <summary>
/// switch to FC
/// </summary>
private void _switchToFC
()
{
_sendControllerMessage
('u',
2,
new byte[1] { (byte)0 });
Thread
.Sleep(100);
_getVersion
();
Thread
.Sleep(100);
_OSDMenue
(0);
}
/// <summary>
/// send RESET signal to FC
/// </summary>
private void _resetCtrl
()
{
_sendControllerMessage
('R',
1);
}
/// <summary>
/// poll the debug data (4sec subscription)
/// </summary>
/// <param name="auto"> onetimequery(false) or autoupdate(true) with set timing interval </param>
private void _readDebugData
(bool auto
)
{
byte interval
= auto
? debugInterval
: (byte)0;
_sendControllerMessage
('d',
0,
new byte[1] { debugInterval
});
}
/// <summary>
/// poll the BL-CTRL status via NC (4sec subscription)
/// </summary>
/// <param name="auto"> onetimequery(false) or autoupdate(true) with set timing interval </param>
private void _readBLCtrl
(bool auto
)
{
byte interval
= auto
? blctrlInterval
: (byte)0;
_sendControllerMessage
('k',
0,
new byte[1] { interval
});
}
/// <summary>
/// poll the NC data struct (4sec subscription)
/// </summary>
/// <param name="auto"> onetimequery(false) or autoupdate(true) with set timing interval </param>
private void _readNavData
(bool auto
)
{
byte interval
= auto
? navctrlInterval
: (byte)0;
_sendControllerMessage
('o',
2,
new byte[1] { interval
});
}
/// <summary>
/// get the errortext for pending NC error
/// </summary>
private void _readNCError
()
{
_sendControllerMessage
('e',
2);
}
/// <summary>
/// start/stop continous polling of controller values
/// </summary>
/// <param name="b">start/stop switch</param>
void _readCont
(bool b
)
{
bReadContinously
= b
;
btnReadDebugCont
.Invoke((Action
)(() => btnReadDebugCont
.Text = bReadContinously
? "stop automatic" + Environment
.NewLine + "data refresh" : "start automatic" + Environment
.NewLine + "data refresh"));
btnReadDebugCont
.Invoke((Action
)(() => btnReadDebugCont
.BackColor = bReadContinously
? Color
.FromArgb(192,
255,
192) : Color
.FromArgb(224,
224,
224)));
if (bReadContinously
)
{
_readDebugData
(true);
if (_iCtrlAct
== 2) { Thread
.Sleep(10); _readBLCtrl
(true);}
if (_iCtrlAct
== 2) { Thread
.Sleep(10); _readNavData
(true);}
Thread
.Sleep(10);
_OSDMenueAutoRefresh
();
lblLifeCounter
.Invoke((Action
)(() => lblLifeCounter
.BackColor = Color
.FromArgb(0,
224,
0)));
}
else
lblLifeCounter
.Invoke((Action
)(() => lblLifeCounter
.BackColor = Color
.FromArgb(224,
224,
224)));
_iLifeCounter
= 0;
}
/// <summary>
/// set fieldtexts to "NA" when not available with FC
/// </summary>
void _setFieldsNA
()
{
Thread
.Sleep(100);
Label lbCur
= new Label
(), lbTemp
= new Label
();
for (int i
= 0; i
< 8; i
++)
{
//BL-Ctrl Temp & Current
switch (i
)
{
case 0:
lbCur
= LBLNCM1Cur
;
lbTemp
= LBLNCM1Temp
;
break;
case 1:
lbCur
= LBLNCM2Cur
;
lbTemp
= LBLNCM2Temp
;
break;
case 2:
lbCur
= LBLNCM3Cur
;
lbTemp
= LBLNCM3Temp
;
break;
case 3:
lbCur
= LBLNCM4Cur
;
lbTemp
= LBLNCM4Temp
;
break;
case 4:
lbCur
= LBLNCM5Cur
;
lbTemp
= LBLNCM5Temp
;
break;
case 5:
lbCur
= LBLNCM6Cur
;
lbTemp
= LBLNCM6Temp
;
break;
case 6:
lbCur
= LBLNCM7Cur
;
lbTemp
= LBLNCM7Temp
;
break;
case 7:
lbCur
= LBLNCM8Cur
;
lbTemp
= LBLNCM8Temp
;
break;
}
if (lbCur
!= null)
lbCur
.Invoke((Action
)(() => lbCur
.Text = "NA"));
if (lbTemp
!= null)
lbTemp
.Invoke((Action
)(() => lbTemp
.Text = "NA"));
}
lblNCFlTime
.Invoke((Action
)(() => lblNCFlTime
.Text = "NA")); //FlightTime
lblNCErrNmbr
.Invoke((Action
)(() => lblNCErrNmbr
.Text = "NA")); //NC ErrorNr
lblNCMF
.Invoke((Action
)(() => lblNCMF
.Text = "NA")); //earth magnet field
lblNCGSpeed
.Invoke((Action
)(() => lblNCGSpeed
.Text = "NA")); //GroundSpeed
lblNCDist
.Invoke((Action
)(() => lblNCDist
.Text = "NA")); //Distance to HP
lblNCSat
.Invoke((Action
)(() => lblNCSat
.Text = "NA")); //Sats used
}
/// <summary>
/// one time query of the OSD Menue with pagenumber
/// </summary>
/// <param name="iMenue">Menue page</param>
void _OSDMenue
(int iMenue
)
{
if (simpleSerialPort
.Port.IsOpen)
{
if (iMenue
> iOSDMax
)
iMenue
= 0;
Stream serialStream
= simpleSerialPort
.Port.BaseStream;
byte[] bytes
= FlightControllerMessage
.CreateMessage('l',
0,
new byte[1] { (byte)iMenue
});
serialStream
.Write(bytes,
0, bytes
.Length);
}
else
Log
(LogMsgType
.Error,
"NOT CONNECTED!");
}
/// <summary>
/// call the OSDMenue and start autorefresh
/// usually by sending a menuekey
/// a bit tricky - but by sending inverted value of 32 (32 = 0010 0000) you can start the OSD menue with autoupdate (abo) without switching the page with the keyvalues (0x1, 0x2)
/// therefore the value has to be negative (inverted) in order to distinguish from old (2 line) menuestyle
/// and must not have any bits of the menue keys 0x1 0x2 0x4 0x8 (0x10?) --> 0x20 = -33
/// </summary>
void _OSDMenueAutoRefresh
()
{
_sendControllerMessage
('h',
0,
new byte[2] { unchecked((byte)(-33)),OSDInterval
});
}
void _OSDMenueAutoRefresh
(byte key
)
{
_sendControllerMessage
('h',
0,
new byte[2] { unchecked((byte)~key
), OSDInterval
});
}
/// <summary>
/// initialize the OSD menue combobox
/// combox is filled by numbers from 0 to max pagenumber
/// </summary>
void _initOSDCB
()
{
_bCBInit
= true;
if(iOSDMax
== 0)
{
_OSDMenue
(0);
Thread
.Sleep(10);
}
cbOSD
.Invoke((Action
)(()=>cbOSD
.Items.Clear()));
for(int i
= 0; i
<= iOSDMax
;i
++)
{
cbOSD
.Invoke((Action
)(() => cbOSD
.Items.Add(i
)));
}
cbOSD
.Invoke((Action
)(() => cbOSD
.SelectedItem = iOSDPage
));
_bCBInit
= false;
}
void _readIni
()
{
if (!File
.Exists(filePath
+ "\\MKLiveViewSettings.ini"))
_writeIni
();
IniFile ini
= new IniFile
("MKLiveViewSettings.ini");
ini
.path = filePath
+ "\\MKLiveViewSettings.ini";
string sVal
= ini
.IniReadValue("default",
"AutorefreshDebugData");
_debugDataAutorefresh
= Convert
.ToBoolean(sVal
);
sVal
= ini
.IniReadValue("default",
"AutorefreshNavCtrlData");
_navCtrlDataAutorefresh
= Convert
.ToBoolean(sVal
);
sVal
= ini
.IniReadValue("default",
"AutorefreshBLCtrlData");
_blctrlDataAutorefresh
= Convert
.ToBoolean(sVal
);
sVal
= ini
.IniReadValue("default",
"AutorefreshOSDData");
_OSDAutorefresh
= Convert
.ToBoolean(sVal
);
sVal
= ini
.IniReadValue("default",
"IntervalDebugData");
debugInterval
= (byte)Convert
.ToInt16(sVal
);
sVal
= ini
.IniReadValue("default",
"IntervalNavCtrlData");
navctrlInterval
= (byte)Convert
.ToInt16(sVal
);
sVal
= ini
.IniReadValue("default",
"IntervalBLCtrlData");
blctrlInterval
= (byte)Convert
.ToInt16(sVal
);
sVal
= ini
.IniReadValue("default",
"IntervalOSDData");
OSDInterval
= (byte)Convert
.ToInt16(sVal
);
}
void _writeIni
()
{
IniFile ini
= new IniFile
("MKLiveViewSettings.ini");
ini
.path = filePath
+ "\\MKLiveViewSettings.ini";
ini
.IniWriteValue("default",
"AutorefreshDebugData", _debugDataAutorefresh
? "true":"false");
ini
.IniWriteValue("default",
"AutorefreshNavCtrlData", _navCtrlDataAutorefresh
? "true":"false");
ini
.IniWriteValue("default",
"AutorefreshBLCtrlData", _blctrlDataAutorefresh
? "true":"false");
ini
.IniWriteValue("default",
"AutorefreshOSDData", _OSDAutorefresh
? "true":"false");
ini
.IniWriteValue("default",
"IntervalDebugData", debugInterval
.ToString());
ini
.IniWriteValue("default",
"IntervalNavCtrlData", navctrlInterval
.ToString());
ini
.IniWriteValue("default",
"IntervalBLCtrlData", blctrlInterval
.ToString());
ini
.IniWriteValue("default",
"IntervalOSDData", OSDInterval
.ToString());
}
#endregion functions
#region buttons
private void buttonReset_Click
(object sender, EventArgs e
)
{
_resetCtrl
();
}
private void btnVersion_Click
(object sender, EventArgs e
)
{
_getVersion
();
}
private void btnAnalogLabels_Click
(object sender, EventArgs e
)
{
_getAnalogLabels
(0);
}
private void btnDbgData_Click
(object sender, EventArgs e
)
{
_readDebugData
(false); //onetime reading of debug data --> subscription lasts 4sec - this means you will receive data for 4 seconds
}
private void btnSaveLabels_Click
(object sender, EventArgs e
)
{
switch (_iCtrlAct
)
{
case 1:
fileName
= "FCLabelTexts.txt";
break;
case 2:
fileName
= "NCLabelTexts.txt";
break;
default:
fileName
= "NCLabelTexts.txt";
break;
}
if (sAnalogLabel
[0] != null)
{
File
.WriteAllLines(filePath
+ "\\" + fileName, sAnalogLabel
);
Console
.WriteLine("Names saved to file");
_loadLabelFile
();
}
else
Log
(LogMsgType
.Warning,
"there's no data -> read first from fc/nc!");
}
private void btnLoadLabels_Click
(object sender, EventArgs e
)
{
_assignLabelNames
();
}
private void btnReadLabelFile_Click
(object sender, EventArgs e
)
{
_loadLabelFile
();
}
private void btnSwitchFC_Click
(object sender, EventArgs e
)
{
_switchToFC
();
}
private void btnSwitchNC_Click
(object sender, EventArgs e
)
{
_switchToNC
();
}
private void btnReadDbgCont_Click
(object sender, EventArgs e
)
{
_readCont
(!bReadContinously
);
}
private void btnReadBLCtrl_Click
(object sender, EventArgs e
)
{
if (_iCtrlAct
== 2) _readBLCtrl
(false);
else Log
(LogMsgType
.Warning,
"only available when connected to NC");
}
private void btnGetNaviData_Click
(object sender, EventArgs e
)
{
if (_iCtrlAct
== 2) _readNavData
(false);
else Log
(LogMsgType
.Warning,
"only available when connected to NC");
}
private void btnConn_Click
(object sender, EventArgs e
)
{
simpleSerialPort
.Connect(!simpleSerialPort
.Port.IsOpen);
}
private void button3_Click
(object sender, EventArgs e
)
{
_getVersion
(1);
}
private void button4_Click
(object sender, EventArgs e
)
{
_getVersion
(2);
}
private void btnOSD_Click
(object sender, EventArgs e
)
{
if (iOSDPage
> iOSDMax
)
iOSDPage
= 0;
_OSDMenue
(iOSDPage
);
}
private void btnOSDForward_Click
(object sender, EventArgs e
)
{
iOSDPage
++;
if (iOSDPage
> iOSDMax
)
iOSDPage
= 0;
_OSDMenue
(iOSDPage
);
}
private void btnOSDBackward_Click
(object sender, EventArgs e
)
{
iOSDPage
--;
if (iOSDPage
< 0)
iOSDPage
= iOSDMax
;
_OSDMenue
(iOSDPage
);
}
private void btnOSDAuto_Click
(object sender, EventArgs e
)
{
_OSDMenueAutoRefresh
();
}
/// call the OSDMenue with Key 0x8
private void btnOSDLeave_Click
(object sender, EventArgs e
)
{
_OSDMenueAutoRefresh
(8);
}
/// call the OSDMenue with Key 0x4
private void btnOSDEnter_Click
(object sender, EventArgs e
)
{
_OSDMenueAutoRefresh
(4);
}
#endregion buttons
}
public class IniFile
{
public string path
;
[DllImport
("kernel32")]
private static extern long WritePrivateProfileString
(string section,
string key,
string val,
string filePath
);
[DllImport
("kernel32.dll", CharSet
= CharSet
.Auto)]
static extern uint GetPrivateProfileSectionNames
(IntPtr lpszReturnBuffer,
uint nSize,
string lpFileName
);
[DllImport
("kernel32")]
private static extern int GetPrivateProfileString
(string section,
string key,
string def, StringBuilder retVal,
int size,
string filePath
);
public IniFile
(string INIPath
)
{
path
= INIPath
;
}
public void IniWriteValue
(string Section,
string Key,
string Value)
{
WritePrivateProfileString
(Section, Key,
Value,
this.path);
}
public string IniReadValue
(string Section,
string Key
)
{
StringBuilder temp
= new StringBuilder
(255);
int i
= GetPrivateProfileString
(Section, Key,
"", temp,
255,
this.path);
return temp
.ToString();
}
//Ini_sections auslesen in String-Array
public string[] IniSectionNames
()
{
// uint MAX_BUFFER = 32767;
uint MAX_BUFFER
= 8388608;
IntPtr pReturnedString
= Marshal
.AllocCoTaskMem((int)MAX_BUFFER
);
uint bytesReturned
= GetPrivateProfileSectionNames
(pReturnedString, MAX_BUFFER,
this.path);
if (bytesReturned
== 0)
{
Marshal
.FreeCoTaskMem(pReturnedString
);
return null;
}
string local
= Marshal
.PtrToStringAuto(pReturnedString,
(int)bytesReturned
).ToString();
Marshal
.FreeCoTaskMem(pReturnedString
);
//use of Substring below removes terminating null for split
return local
.Substring(0, local
.Length - 1).Split('\0');
}
}
public static class ControlExtensions
{
/// <summary>
/// Execute a threadsafe operation, when accessing a control via another thread
/// action is a lamdaexpression
/// e.g. comboBox1.ExecuteThreadSafe(() => comboBox1.Enabled = true);
/// </summary>
/// <param name="control"> The control </param>
/// <param name="action"> The 'action' to perform </param>
public static void ExecuteThreadSafe
(this Control control, Action action
)
{
if (control
.InvokeRequired)
{
control
.BeginInvoke(action
); //"BeginInvoke" is an async call -> threadsafety error when called to many times successively -> then take "Invoke"
}
else
{
action
.Invoke();
}
}
}
}