<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Flight Sim Dev]]></title><description><![CDATA[Join a software developer / private pilot / maker / gamer on a journey building custom instruments for flight simulators.]]></description><link>https://www.theflightsimdev.com/</link><image><url>https://www.theflightsimdev.com/favicon.png</url><title>The Flight Sim Dev</title><link>https://www.theflightsimdev.com/</link></image><generator>Ghost 5.88</generator><lastBuildDate>Wed, 06 May 2026 11:28:05 GMT</lastBuildDate><atom:link href="https://www.theflightsimdev.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[19. Event Driven Keypad]]></title><description><![CDATA[<p>In the original <a href="https://www.theflightsimdev.com/3-a-simple-keypad/" rel="noreferrer">A Simple Keypad</a> post, we wrote some code to send keyboard events using the Teensy&apos;s built in Keyboard emulator libraries. While this works, the code as written pretty much acts as a momentary button works, by checking for a key press in the <code>loop()</code> function</p>]]></description><link>https://www.theflightsimdev.com/19-event-driven-keypad/</link><guid isPermaLink="false">6715755f0a4975c50243a358</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Sun, 20 Oct 2024 22:00:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1707960191718-bcff0264cdbf?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxrZXlwYWR8ZW58MHx8fHwxNzMxNjM1MTcyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1707960191718-bcff0264cdbf?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxrZXlwYWR8ZW58MHx8fHwxNzMxNjM1MTcyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="19. Event Driven Keypad"><p>In the original <a href="https://www.theflightsimdev.com/3-a-simple-keypad/" rel="noreferrer">A Simple Keypad</a> post, we wrote some code to send keyboard events using the Teensy&apos;s built in Keyboard emulator libraries. While this works, the code as written pretty much acts as a momentary button works, by checking for a key press in the <code>loop()</code> function and send a key press event, wait a bit, and send a key release event. But what if we want to use the keypad as an actual keyboard, where you can press and hold a key and it will initially type it once, and while hold it down, it will repeatedly keep sending the same key until you release it. Luckily, the <code>Keypad.h</code> library provides a mechanism for that via an event listener. </p><p>Here are the code changes:</p><pre><code class="language-arduino">#include &lt;Keypad.h&gt;

const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  { &apos;1&apos;, &apos;2&apos;, &apos;3&apos;, &apos;A&apos; },
  { &apos;4&apos;, &apos;5&apos;, &apos;6&apos;, &apos;B&apos; },
  { &apos;7&apos;, &apos;8&apos;, &apos;9&apos;, &apos;C&apos; },
  { &apos;*&apos;, &apos;0&apos;, &apos;#&apos;, &apos;D&apos; }
};

// pads keypad
// byte rowPins[ROWS] = { 12, 11, 10, 9 };  //connect to the row pinouts of the keypad
// byte colPins[COLS] = { 8, 7, 6, 5 };     //connect to the column pinouts of the keypad
// mechanical keypads
byte rowPins[ROWS] = { 8, 7, 6, 5 };     //connect to the row pinouts of the keypad
byte colPins[COLS] = { 12, 11, 10, 9 };  //connect to the column pinouts of the keypad

//Create an object of keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  keypad.addEventListener(keypadEvent);
}


void loop() {
  keypad.getKey();
}

void keypadEvent(KeypadEvent key) {
  switch (keypad.getState()) {
    case PRESSED:
    case HOLD:
      Keyboard.press(key);
      break;
    case RELEASED:
      Keyboard.release(key);
      break;
    case IDLE:
      break;
  }
}</code></pre><h2 id="breakdown">Breakdown</h2><p>The first noticeable change is that during the <code>setup()</code> function, we are adding an event listener called <code>keypadEvent</code>, which we later define in the code. Next, our <code>loop()</code> function now just calls <code>getKey()</code> but doesn&apos;t do anything with it. This allows the library to scan for key presses.</p><p>If we take a look at the <code>void keypadEvent(KeypadEvent key)</code> function, we see that now we can get the state of the keypad using the <code>keypad.getState()</code>. There are 4 predefined states, IDLE, PRESSED, HOLD and RELEASED. With these now we can send the corresponding keyboard events using the built-in <code>Keyboard</code> object, which contains a <code>press(key)</code> and <code>release(key)</code> functions that we can use.</p><h2 id="sending-multiple-keys">Sending Multiple Keys</h2><p>So now that we have this basic functionality in place, we can make some more fun stuff. Lets map the * and # keys to be CTRL+C and CTRL+V to do something different. We can change the event listener function to do something like this instead:</p><pre><code class="language-arduino">void keypadEvent(KeypadEvent key) {
  switch (keypad.getState()) {
    case PRESSED:
    case HOLD:
      pressKey(key);
      break;
    case RELEASED:
      releaseKey(key);
      break;
    case IDLE:
      break;
  }
}

void pressKey(KeypadEvent key) {
  switch (key) {
    case &apos;*&apos;:
      Keyboard.press(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.press(KEY_C);
      delay(25);
      break;
    case &apos;#&apos;:
      Keyboard.press(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.press(KEY_V);
      delay(25);
      break;
    default:
      Keyboard.press(key);
      break;
  }
}

void releaseKey(KeypadEvent key) {
  switch (key) {
    case &apos;*&apos;:
      Keyboard.release(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.release(KEY_C);
      delay(25);
      break;
    case &apos;#&apos;:
      Keyboard.release(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.release(KEY_V);
      delay(25);
      break;
    default:
      Keyboard.release(key);
      break;
  }
}</code></pre><p>Now depending on what key was pressed, we can either send the default Keyboard press and release events, or send a combination of them. And you are not just limited to send Keyboard events. The Teensy&apos;s also have built-in Mouse and Joystick emulation so you can technically send Mouse clicks, drags, etc. the same way you send the Keyboard events.</p><p>Here is an example on how I bound the # key to click on the screen, press the right ALT key down and send a mouse click. If you play Microsoft Flight Simulator you know this is what you need to click to pop-out the digital panels, and then use the keypad&apos;s D to maximize it.</p><pre><code class="language-arduino "> case &apos;#&apos;:
      Mouse.click();
      delay(25);
      Keyboard.press(KEY_RIGHT_ALT);
      delay(25);
      Mouse.set_buttons(1, 0, 0);
      break;

case &apos;D&apos;:
      Mouse.click();
      delay(25);
      Keyboard.press(KEY_RIGHT_ALT);
      delay(25);
      Keyboard.press(KEY_ENTER);
      break;</code></pre><h2 id="conclusion">Conclusion</h2><p>While the initial way of sending keyboard events discussed earlier works, it&apos;s limited if you for example wanted to do key combinations. I ended up changing my code to this way because I wanted to bind a key to ALT and hold it down while I clicked on my mouse, but eventually figured out that I could just bind a key and send all those events and don&apos;t need to even use my keyboard or mouse to do so.</p><p>Hope you found this updated code useful and see you on the next one!</p>]]></content:encoded></item><item><title><![CDATA[18. Debugging UDP Traffic]]></title><description><![CDATA[<p>This is a quick tutorial on how to debug UDP broadcast traffic sent from our code to ForeFlight. This method applies for any type of UDP traffic you want to monitor, but the fact that it is sent in plain text makes it even easier.</p><p></p><h2 id="linux">Linux</h2><p>If you happen to</p>]]></description><link>https://www.theflightsimdev.com/18-debugging-udp-traffic/</link><guid isPermaLink="false">66e88a32a285eee1e2b38727</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Tue, 17 Sep 2024 03:09:03 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1487058792275-0ad4aaf24ca7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDMwfHxoYWNrfGVufDB8fHx8MTcyNjUxNTg5NXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1487058792275-0ad4aaf24ca7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDMwfHxoYWNrfGVufDB8fHx8MTcyNjUxNTg5NXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="18. Debugging UDP Traffic"><p>This is a quick tutorial on how to debug UDP broadcast traffic sent from our code to ForeFlight. This method applies for any type of UDP traffic you want to monitor, but the fact that it is sent in plain text makes it even easier.</p><p></p><h2 id="linux">Linux</h2><p>If you happen to have a Linux box connected to the same network, you can use the built-in <em>nc</em> command to listen to a specific TCP or UDP port. For our ForeFlight data, we would do something like this:</p><pre><code class="language-bash">nc -u -l 49002 -k</code></pre><p>Where -u is for specifying UDP, -l to specify the port and the -k is to keep monitoring indefinitely (the default behavior stops printing the data after a few bytes are received).</p><p>You&apos;ll see all the traffic coming through like this:</p><pre><code class="language-log">XGPSHelloForeFlight,-69.4282,44.3234,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.4232,44.3284,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.4182,44.3334,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.4132,44.3384,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.4082,44.3434,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.4032,44.3484,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.3982,44.3534,3657.42,45.00,56.59
XGPSHelloForeFlight,-69.3932,44.3584,3657.42,45.00,56.59</code></pre><p></p><h2 id="microsoft-windows">Microsoft Windows</h2><p>For Windows, you can use a tool called <a href="https://www.wireshark.org/?ref=theflightsimdev.com" rel="noreferrer">Wireshark</a> and create a <em>udp filter</em>. Note that you might need to install the WinPcap if you don&apos;t already have it installed. The filter should look like this:</p><p><code>udp.port == 49002</code></p><p>You&apos;ll see output like this, which you can right click on any packet and click on <em>Follow-&gt;UDP Stream </em>to see it in a nicer way:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-38.png" class="kg-image" alt="18. Debugging UDP Traffic" loading="lazy" width="1084" height="581" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-38.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/image-38.png 1000w, https://www.theflightsimdev.com/content/images/2024/09/image-38.png 1084w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-39.png" class="kg-image" alt="18. Debugging UDP Traffic" loading="lazy" width="786" height="502" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-39.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-39.png 786w" sizes="(min-width: 720px) 720px"></figure><h2 id="mobile">Mobile</h2><p>There are many apps that can do something similar, both on iOS and Android, both free and paid so try out a few to see which one you like &#x1F604;</p>]]></content:encoded></item><item><title><![CDATA[17. Sending Microsoft Flight Simulator data to ForeFlight]]></title><description><![CDATA[<p>So let&apos;s do something different today. Now that we know how to read data from the sim and send it to our Arduino devices, let&apos;s use that same idea to send data to Electronic Flight Bags (EFB) apps like <a href="https://foreflight.com/?ref=theflightsimdev.com" rel="noreferrer">ForeFlight</a>, <a href="https://www.garmin.com/en-US/aviation/garminpilot/overview/?ref=theflightsimdev.com" rel="noreferrer">Garmin Pilot</a> and <a href="https://www.apps4av.com/overview.html?ref=theflightsimdev.com" rel="noreferrer">Avare</a>. </p><p>But what</p>]]></description><link>https://www.theflightsimdev.com/17-sending-microsoft-flight-simulator-data-to-foreflight/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5f8</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Mon, 16 Sep 2024 23:29:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1610497072993-b509c7b10d0e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDU3fHxmbGlnaHQlMjBwbGFufGVufDB8fHx8MTcyNjQ2MDE5M3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1610497072993-b509c7b10d0e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDU3fHxmbGlnaHQlMjBwbGFufGVufDB8fHx8MTcyNjQ2MDE5M3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="17. Sending Microsoft Flight Simulator data to ForeFlight"><p>So let&apos;s do something different today. Now that we know how to read data from the sim and send it to our Arduino devices, let&apos;s use that same idea to send data to Electronic Flight Bags (EFB) apps like <a href="https://foreflight.com/?ref=theflightsimdev.com" rel="noreferrer">ForeFlight</a>, <a href="https://www.garmin.com/en-US/aviation/garminpilot/overview/?ref=theflightsimdev.com" rel="noreferrer">Garmin Pilot</a> and <a href="https://www.apps4av.com/overview.html?ref=theflightsimdev.com" rel="noreferrer">Avare</a>. </p><p>But what is an EFB and what can I we use it for? An EFB is a digital device or mobile device application that pilots use to manage flight information, replacing the need of paper based materials they usually carry in their flight bags, like VFR Sectional and IFR charts, with the addition that they can be connected to the aircraft&apos;s GPS to read navigation and positional data and display it on the screen for flight planning and tracking in real time, with the additional benefit that you can record your flights and play them back later.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.theflightsimdev.com/content/images/2024/09/20240916_041338000_iOS.png" class="kg-image" alt="17. Sending Microsoft Flight Simulator data to ForeFlight" loading="lazy" width="2000" height="1390" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/20240916_041338000_iOS.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/20240916_041338000_iOS.png 1000w, https://www.theflightsimdev.com/content/images/size/w1600/2024/09/20240916_041338000_iOS.png 1600w, https://www.theflightsimdev.com/content/images/2024/09/20240916_041338000_iOS.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">ForeFlight&apos;s Track Logs</span></figcaption></figure><p>We want to be able to use an EFB together with our simulator the same way you would use it while flying on a real aircraft and connecting it to the aircraft&apos;s GPS, via Wi-Fi or Bluetooth, depending on the equipment your aircraft has. Although there are already a few Windows apps that do this, let&apos;s look at how it&apos;s done so you can add it to the app we built earlier since we already have some of the data we need and we can figure out the rest.</p><p>I will show an example code for sending the data to ForeFlight, since it&apos;s pretty straight forward. The code for supporting Avare (on Android) is similar, with the difference that it uses NMEA sentences instead of ForeFlight&apos;s proprietary ones. I haven&apos;t looked into how this would be implemented for other EFB&apos;s like Garmin Pilot, but I&apos;m guessing it might be a similar idea.</p><p></p><h2 id="foreflights-protocol">ForeFlight&apos;s Protocol</h2><p>We will be using the UDP protocol to send data to devices running ForeFlight that are connected to the same network our PC. Unlike TCP, UDP is connectionless so the packets are just <a href="https://en.wikipedia.org/wiki/Broadcasting_(networking)?ref=theflightsimdev.com" rel="noreferrer">broadcasted</a> over the network so any device on the network can receive the traffic. We can have multiple devices connected on the network and as long as they are listening to the same port, all of them will be able to read it.</p><p>If we look at the <a href="https://support.foreflight.com/hc/en-us/articles/204115005-Flight-Simulator-GPS-Integration-UDP-Protocol?ref=theflightsimdev.com" rel="noreferrer">ForeFlight GPS Integration documentation</a>, it listens on UDP port 49002 and supports 3 types of <em>sentences</em>, two for your own aircraft&apos;s information and the other for other traffic. They are all text based and the fields are comma separated.</p><h3 id="xgps">XGPS</h3><p>This is used for your own aircraft&apos;s position, altitude, track and ground speed. It expects<em> <strong>one sentence per second</strong></em>. The format is:</p><p><code>XGPSname,longitude,latitude,altitude,track,groundspeed</code></p><table>
<thead>
<tr>
<th>Property</th>
<th>Description</th>
<th>SimVar</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>your C# App name</td>
<td>This will show in ForeFlight under Devices</td>
</tr>
<tr>
<td>longitude</td>
<td>Aircraft&apos;s longitude coordinate</td>
<td>PLANE LONGITUDE</td>
</tr>
<tr>
<td>latitude</td>
<td>Aircraft&apos;s latitude coordinate</td>
<td>PLANE LATITUDE</td>
</tr>
<tr>
<td>altitude</td>
<td>Aircraft&apos;s MSL altitude in meters</td>
<td>INDICATED ALTITUDE</td>
</tr>
<tr>
<td>track</td>
<td>Aircraft&apos;s true north based track</td>
<td>GPS GROUND TRUE TRACK</td>
</tr>
<tr>
<td>groundspeed</td>
<td>Aircraft&apos;s ground speed in meters per second</td>
<td>GROUND VELOCITY</td>
</tr>
</tbody>
</table>
<p></p><h3 id="xatt">XATT</h3><p>This is used by ForeFlight&apos;s built-in Attitude Indicator. The sentences need to be sent <strong><em>between 4 to 10 times per second</em></strong>. The format is:</p><p><code>XATTname,heading,pitch,roll</code></p><table>
<thead>
<tr>
<th>Property</th>
<th>Description</th>
<th>SimVar</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>your C# App name</td>
<td>This will show in ForeFlight under Devices</td>
</tr>
<tr>
<td>heading</td>
<td>true heading</td>
<td>GPS GROUND TRUE HEADING</td>
</tr>
<tr>
<td>pitch</td>
<td>in degrees, up is positive</td>
<td>PLANE PITCH DEGREES</td>
</tr>
<tr>
<td>roll</td>
<td>in degrees, right is positive</td>
<td>PLANE BANK DEGREES</td>
</tr>
</tbody>
</table>
<p></p><h3 id="xtraffic">XTRAFFIC</h3><p>This is used for traffic aircraft&apos;s properties surrounding your own aircraft. It expects<strong><em> one sentence per second</em></strong>. The format is:</p><p><code>XTRAFFICname,id,lat,long,alt,vspeed,airborne,heading,speed,callsign</code></p><table>
<thead>
<tr>
<th>Property</th>
<th>Description</th>
<th>SimVar</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>your C# App name</td>
<td>This will show in ForeFlight under Devices</td>
</tr>
<tr>
<td>id</td>
<td>an integer ID</td>
<td>See Note</td>
</tr>
<tr>
<td>lat</td>
<td>aircraft&apos;s current location.</td>
<td>PLANE LATITUDE</td>
</tr>
<tr>
<td>ong</td>
<td>aircraft&apos;s current location.</td>
<td>PLANE LONGITUDE</td>
</tr>
<tr>
<td>alt</td>
<td>aircraft&apos;s altitude in feet.</td>
<td>PLANE ALTITUDE</td>
</tr>
<tr>
<td>vspeed</td>
<td>aircraft&apos;s vertical speed in feet per minute</td>
<td>VERTICAL SPEED</td>
</tr>
<tr>
<td>airborne</td>
<td>flag: 0 for surface, 1 for airborne</td>
<td>SIM ON GROUND</td>
</tr>
<tr>
<td>heading</td>
<td>aircraft&apos;s true heading in degrees</td>
<td>PLANE HEADING DEGREES MAGNETIC</td>
</tr>
<tr>
<td>speed</td>
<td>aircraft&apos;s speed in knots</td>
<td>AIRSPEED TRUE</td>
</tr>
<tr>
<td>callsign</td>
<td>aircraft&apos;s callsign as a string</td>
<td>ATC ID</td>
</tr>
</tbody>
</table>
<p><em>Note: The id will be given to us in the <code>OnRecvSimobjectDataBytype(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)</code> as <code>data.dwObjectId</code>. Normally your own aircraft&apos;s ID = 1, and the rest start around 300.</em></p><h2 id="the-coding-part">The Coding Part</h2><p>Let&apos;s write a simple <em>Console Application</em> to explain some concepts:</p><pre><code class="language-csharp">using System.Net;
using System.Net.Sockets;
using System.Text;

namespace HelloForeFlight
{
    internal class Program
    {
        private const string APP_NAME = &quot;HelloForeFlight&quot;;

        private const float FEET_TO_METERS = 3.281f;
        private const float KNOTS_TO_METERS_PER_SECOND = 0.51444f;

        private const string BROADCAST_ADDRESS = &quot;255.255.255.255&quot;;
        private const int BROADCAST_PORT = 49002;

        private static void Main(string[] args)
        {
            float altitude = 12000;         // in feet
            float groundspeed = 110;        // in knots
            float groundtrack = 45;
            float latitude = 42.4630f;
            float longitude = -71.2872f;

            // Define the broadcast endpoint (IP address and port)
            var endPoint = new IPEndPoint(IPAddress.Parse(BROADCAST_ADDRESS), BROADCAST_PORT);

            using (UdpClient udpClient = new UdpClient() { EnableBroadcast = true })
            {
                while (true)
                {
                    try
                    {
                        // Write the XGPS sentence.
                        // Since we are using altitude in feet and ground speed in knots, we need to convert them to meters and to meters per second respectively.
                        // If you pull them from the Sim as Meters or Meters per second (check MSFS SDK documentation), you don&apos;t have to convert them
                        var xgps = $&quot;XGPS{APP_NAME},{longitude:F4},{latitude:F4},{altitude / FEET_TO_METERS:F2},{groundtrack:F2},{groundspeed * KNOTS_TO_METERS_PER_SECOND:F2}\n&quot;;

                        // Convert the message to bytes
                        byte[] data = Encoding.UTF8.GetBytes(xgps);

                        // Send the UDP message to the broadcast address
                        udpClient.Send(data, data.Length, endPoint);
                        Console.WriteLine($&quot;Sent: {xgps}&quot;);

                        // Finally, let&apos;s modify the latitude and longited a little to see the plane move in ForeFlight
                        longitude += 0.005f;
                        latitude += 0.005f;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($&quot;Error: {ex.Message}&quot;);
                    }

                    Thread.Sleep(1000);
                }
            }
        }
    }
}</code></pre><p></p><h3 id="code-breakdown">Code Breakdown</h3><p>First, we&apos;ll create an <code>IPEndpoint</code> to specify the target broadcast address and port, and an <code>UdpClient</code> to send the data over UDP. Make sure you set <em>EnableBroadcast</em> to true after you create the client. We&apos;ll be using <code>255.255.255.255</code> as the broadcast address for simplicity.</p><pre><code class="language-csharp">var endPoint = new IPEndPoint(IPAddress.Parse(BROADCAST_ADDRESS), BROADCAST_PORT);

using (UdpClient udpClient = new UdpClient() { EnableBroadcast = true })</code></pre><blockquote>If you want to only broadcast to a specific network, you could do so by either specifying the broadcast IP address of the that network, i.e. <code>192.168.0.255</code>, and provide some sort of mechanism like a config file, a text box, etc. to specify, or even programmatically query the network devices on your PC and let the user select. A query could look something like this, which is what I use in my plugin:</blockquote><pre><code class="language-csharp">var nics = NetworkInterface.GetAllNetworkInterfaces().Where(n =&gt; n.NetworkInterfaceType == NetworkInterfaceType.Ethernet &amp;&amp; n.OperationalStatus == OperationalStatus.Up).ToList();
var addrinfos = nics.SelectMany(x =&gt; x.GetIPProperties().UnicastAddresses.Where(y =&gt; y.Address.AddressFamily == AddressFamily.InterNetwork)).ToList();</code></pre><p></p><p>Then, we will loop once per second and build our <code>XGPS</code> sentence based on the data we get from the Sim (or some hardcoded values for demo purposes). We have to remember that ForeFlight expects the <em>altitude</em> in meters and the <em>ground speed</em> in meters per second, so if you subscribed to receive the SimVar in <em>feet</em> as we did in previous posts, you&apos;ll have to convert the value to meters or subscribe to it in meters, but convert it to feet when we send it to the Arduino.</p><pre><code class="language-csharp"> simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;INDICATED ALTITUDE&quot;, &quot;feet&quot;, SIMCONNECT_DATATYPE.FLOAT32, 0, SimConnect.SIMCONNECT_UNUSED);</code></pre><p></p><p>We set the precision based on ForeFlight&apos;s documentation. I chose 4 decimals for latitude/longitude and 2 for the rest.</p><pre><code class="language-csharp">var xgps = $&quot;XGPS{APP_NAME},{longitude:F4},{latitude:F4},{altitude / FEET_TO_METERS:F2},{groundtrack:F2},{groundspeed * KNOTS_TO_METERS_PER_SECOND:F2}\n&quot;;</code></pre><p></p><p>We then convert our sentence to a byte array and send it using the <code>UdpClient</code> and <code>IPEndpoint</code> we previously created.</p><pre><code class="language-csharp">byte[] data = Encoding.UTF8.GetBytes(xgps);

udpClient.Send(data, data.Length, endPoint);</code></pre><p>And finally just to see some stuff happen in ForeFlight, we increase the latitude and longitude to see the plane moving in the northeast direction.</p><blockquote>The XATT sentence is pretty much built the same as the XGPS one is, so not going to cover it here.</blockquote><p></p><h3 id="traffic">Traffic</h3><p>Traffic is a bit different. You have to register an additional SimObject event handler and request traffic in a different way.</p><p>First, you want to create a struct for the Traffic data and an enum to register it:</p><pre><code class="language-csharp">[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TrafficSimVarData
{
    public bool IsGrounded;                         // SIM ON GROUND (bool)

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string CallSign;                         // ATC ID (string)

    public float AirSpeed;                          // AIRSPEED TRUE (knots)
    public float Altitude;                          // PLANE ALTITUDE (feet)
    public float Heading;                           // PLANE HEADING DEGREES TRUE (degrees)
    public float Latitude;                          // PLANE LATITUDE (degree)
    public float Longitude;                         // PLANE LONGITUDE (degree)
    public float VerticalSpeed;                     // VERTICAL SPEED (feet/minute)
}

internal enum RequestType
{
    PerFrameData,
    TrafficData,
}</code></pre><p></p><p>Then, you want to register a new event handler by type:</p><pre><code class="language-csharp">  // add this below where you registered your other handlers
  simConnect.OnRecvSimobjectDataBytype += new SimConnect.RecvSimobjectDataBytypeEventHandler(SimConnect_OnRecvSimobjectDataBytype);

...

// define your handler
public void RecvSimobjectDataBytypeEventHandler(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)
{
    try
    {
        var objectId = data.dwObjectID;
        var requestType = (RequestType)data.dwRequestID;
        // your aircraft&apos;s objectId is 1, and it seems that the rest start above 300 
        if (requestType == RequestType.TrafficData &amp;&amp; objectId &gt; 300)
        {
            var traffic = (TrafficSimVarData)data.dwData[0];
            // call your method to build and send the XTRAFFIC package here
            // SendTraffic(objectId, traffic);
        }
    }
}</code></pre><p></p><p>Last but not least, you might want to have a background worker thread to run once per second, to request the traffic data from the sim and sent it to ForeFlight like the documentation suggests.</p><pre><code class="language-csharp">simConnect?.RequestDataOnSimObjectType(RequestType.TrafficData, RequestType.TrafficData, radius, SIMCONNECT_SIMOBJECT_TYPE.AIRCRAFT);</code></pre><p></p><p>Note that when you register this, you&apos;ll specify the radius in meters around your aircraft and the <code>RecvSimobjectDataBytypeEventHandler()</code> method will be called once for any object within that radius, and it&apos;s unique object Id will be included in the response data as <em>data.dwObjectId</em>, which you will send as the <em>id</em> in the <em>XTRAFFIC</em> sentence that ForeFlight uses to keep track of each unique traffic aircraft.</p><p></p><h2 id="running-and-testing">Running and testing</h2><p>That&apos;s it, build and run the <em>Console Application</em> and open ForeFlight. First thing you want to verify is that ForeFlight is receiving data from your &quot;plugin&quot;. In ForeFlight, go to Devices and you should see your &quot;HelloForeFlight&quot; app connected. Now open the Maps tab and watch your plane move around.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/4C_t-1ncT0I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="17-sending-microsoft-flight-simulator-data-to-foreflight"></iframe></figure><p></p><h2 id="wrapping-up">Wrapping up</h2><p>That&apos;s it. With everything here you should be able to add register some new SimVars on your C# app and add the UdpClient broadcast logic along the other minor modifications to request traffic data and implement the other two sentences.</p><p>If you are on Android and cannot use ForeFlight, the code is very similar for Avare and the only difference is the sentences used are NMEA ones. Since Avare is open source, you can find the sentences they expect in their github repo here: <a href="https://github.com/apps4av/avare/tree/master/app/src/main/java/com/ds/avare/nmea?ref=theflightsimdev.com">https://github.com/apps4av/avare/tree/master/app/src/main/java/com/ds/avare/nmea</a></p><p>One thing I discovered is that you can actually send the <em>sentences</em> to ForeFlight faster than one per second, but be aware that if you broadcast too often, you can literally kill your network with all the traffic being generated... speaking from experience &#x1F605;</p><p>In the next post I&apos;ll show you how to use 3rd party tools for Windows, and Linux so you can see the broadcast traffic live in case you need to do some debugging, or even connect your mobile device to the aircraft&apos;s device and see the actual traffic being sent. See you on the next one!</p>]]></content:encoded></item><item><title><![CDATA[16. Sending events - Part 3]]></title><description><![CDATA[<p>In this final part of this <em>Sending Events </em>series, we&apos;ll put everything we learned so we can now send events from the device to the C# app, and then to Microsoft Flight Simulator. </p><p></p><h2 id="arduino-sketch-changes">Arduino Sketch Changes</h2><p>Lets insert the rotary encoder code from part 2 with a small</p>]]></description><link>https://www.theflightsimdev.com/16-sending-events-part-3/</link><guid isPermaLink="false">66db5da6cef6b140bcdda305</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Fri, 06 Sep 2024 20:17:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1668438602240-37d027c941d2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEwNXx8Y29ja3BpdHxlbnwwfHx8fDE3MjU2NTQ0MzR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1668438602240-37d027c941d2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEwNXx8Y29ja3BpdHxlbnwwfHx8fDE3MjU2NTQ0MzR8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="16. Sending events - Part 3"><p>In this final part of this <em>Sending Events </em>series, we&apos;ll put everything we learned so we can now send events from the device to the C# app, and then to Microsoft Flight Simulator. </p><p></p><h2 id="arduino-sketch-changes">Arduino Sketch Changes</h2><p>Lets insert the rotary encoder code from part 2 with a small modification so we can send a more specific <code>string</code> when the state changes:</p><pre><code class="language-arduino">#include &quot;SPI.h&quot;
#include &quot;ILI9341_t3.h&quot;

#define TFT_DC 9
#define TFT_CS 10
#define CHAR_WIDTH 6

#define CLK_PIN 14
#define DATA_PIN 15
#define BUTTON_PIN 16

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

float currentAltitude = 0;
float previousAltitude = -1;

float currentBarometer = 0;
float previousBarometer = -1;

int lastEncoderState;
int lastEncoderButtonState;

void setup() {
    pinMode(CLK_PIN, INPUT_PULLUP);
    pinMode(DATA_PIN, INPUT_PULLUP);
    pinMode(BUTTON_PIN, INPUT_PULLUP);

    lastEncoderState = digitalRead(CLK_PIN);

    // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it&apos;s the same rate as in the C# code
    Serial.begin(115200);

    tft.begin();
    tft.setRotation(1);
    tft.fillScreen(ILI9341_BLACK);

    // labels
    drawTextCentered(&quot;Altitude (feet)&quot;, 0, 2, ILI9341_WHITE);
    drawTextCentered(&quot;Barometer (inHg)&quot;, 120, 2, ILI9341_WHITE);

    // refresh values
    update();

    // wait for a serial connection before beginning
    while (!Serial) {}
}

void loop() {
    // check if we have at least 8 bytes. our full message should be 9 bytes,
    if (Serial.available() &gt; 8) {
        currentAltitude = readFloat();
        currentBarometer = readFloat();

        Serial.readStringUntil(&apos;\n&apos;);

        update();
    }

    // process encoder
    switch (readEncoder(CLK_PIN, DATA_PIN, lastEncoderState)) {
    case -1:
        Serial.println(&quot;ENCODER_DECREASED&quot;);
        break;
    case 1:
        Serial.println(&quot;ENCODER_INCREASED&quot;);
        break;
    }

    // check if the button was pressed
    if (readButton(BUTTON_PIN, lastEncoderButtonState)) {
        Serial.println(&quot;ENCODER_BUTTON_PRESSED&quot;);
    }

    // small delay to give it time for debouncing
    delay(1);
}

// util method to read 4 bytes from the serial port and return them as a float
float readFloat() {
    char buffer[4];
    Serial.readBytes(buffer, 4);

    float value;
    memcpy(&amp;value, buffer, sizeof(float));

    return value;
}

// util method to redraw the values
void update() {
    if (previousAltitude != currentAltitude) {
        tft.fillRoundRect(30, 40, 260, 50, 10, ILI9341_GREEN);
        tft.drawRoundRect(30, 40, 260, 50, 10, ILI9341_WHITE);
        drawTextCentered(String(currentAltitude), 50, 4, ILI9341_BLACK);

        previousAltitude = currentAltitude;
    }

    if (previousBarometer != currentBarometer) {
        tft.fillRoundRect(30, 160, 260, 50, 10, ILI9341_GREEN);
        tft.drawRoundRect(30, 160, 260, 50, 10, ILI9341_WHITE);
        drawTextCentered(String(currentBarometer), 170, 4, ILI9341_BLACK);

        previousBarometer = currentBarometer;
    }
}

// util method to draw centered
void drawTextCentered(String text, int y, int size, uint16_t color) {
    int offset = 160 - (text.length() * CHAR_WIDTH * size / 2);

    tft.setTextSize(size);
    tft.setCursor(offset, y);
    tft.setTextColor(color);
    tft.print(text);
}

// simple funciton that reads the state of a button connected to &apos;pin&apos;, with current and last states for determining if the button is being pressed or not.
bool readButton(uint8_t pin, int&amp; lastState) {
    int state = !digitalRead(pin);

    if (state != lastState) {
        lastState = state;
        if (state == 0) {
            return true;
        }
    }

    return false;
}

// In my encoder, and depends on how you wired it, this is the sequence.
// If yours is inverted, you can invert the CLK and DATA connections
// cw   00,01,11,10,00
// ccw  00,10,11,01,00
int readEncoder(int clkPin, int dataPin, int&amp; lastState) {
    int state = digitalRead(clkPin);

    if (state != lastState) {
        lastState = state;

        int dataState = digitalRead(dataPin);
        if (dataState != state) {
            return -1;
        }
        else {
            return 1;
        }
    }

    return 0;
}</code></pre><p>Notice that we are now sending <code>ENCODER_DECREASED</code>, <code>ENCODER_INCREASED</code> and <code>ENCODER_BUTTON_PRESSED</code>, but we really can send anything we like, 0,1,2, &apos;D&apos;,&apos;I&apos;,&apos;P&apos;, or even the original string messages as long as our C# code matches with what we sent. I opted for these human readable strings for demo and debugging purposes.</p><p></p><h2 id="wpf-application-changes">WPF Application Changes</h2><p>Now let&apos;s look at the <code>SerialPort_DataReceived(...)</code> method, which it&apos;s really the only thing we have to modify:</p><pre><code class="language-csharp">private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        // Read data from the serial port until NewLine is found and trim the newline character(s)
        var dataReceived = serialPort.ReadLine().Trim();

        switch (dataReceived)
        {
            case &quot;ENCODER_DECREASED&quot;:
                simconnect.TransmitClientEvent(
                    SimConnect.SIMCONNECT_OBJECT_ID_USER,
                    SimEventType.KOHLSMAN_DEC,
                    0,
                    GroupId.FLAG,
                    SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY
                );
                break;

            case &quot;ENCODER_INCREASED&quot;:
                simconnect.TransmitClientEvent(
                    SimConnect.SIMCONNECT_OBJECT_ID_USER,
                    SimEventType.KOHLSMAN_INC,
                    0,
                    GroupId.FLAG,
                    SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY
                );
                break;

            case &quot;ENCODER_BUTTON_PRESSED&quot;:
                simconnect.TransmitClientEvent_EX1(
                    SimConnect.SIMCONNECT_OBJECT_ID_USER,
                    SimEventType.KOHLSMAN_SET,
                    GroupId.FLAG,
                    SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY,
                    (uint)(STANDARD_PRESSURE * INHG_TO_MILLIBAR * 16), 0, 0, 0, 0
                );
                break;

            default:
                Debug.WriteLine($&quot;Unsupported message received: {dataReceived}&quot;);
                break;
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(&quot;Error while receiving data: &quot; + ex.Message);
    }
}</code></pre><p>Since we are sending a string, we can still use <code>serialPort.ReadLine()</code> to read a <code>string</code> from the port and put that in a <code>switch</code> statement for each one of the 3 <em>message types</em> that we are sending. We moved the code from the event handlers into each one of the corresponding <code>case</code> blocks and added a default for debugging purposes.</p><blockquote>At this point you can remove the additional 3 buttons we added in Part 1 for sending the events and remove the handlers, but that&apos;s optional. They both should still work independently.</blockquote><p></p><p>Build and upload the Sketch, build and run the WPF app and launch the Sim and click on connect. Rotate the encoder <em>clockwise</em> and <em>counterclockwise</em> and you&apos;ll see the <em>barometer </em>setting <em>increasing </em>and <em>decreasing</em>, in addition to the <em>altimeter</em> getting updated because of the <em>barometer setting</em> change.</p><p></p><h2 id="demo">Demo</h2><p>Here is a short video with a demo on how it should work. I&apos;m using a dual rotary encoder hence the extra wires, but like I mentioned in the previous post, it works the same. <em>And once again, ignore the red lines, it&apos;s my broken screen that I use for development purposes</em> &#x1F605;</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/C-KjqzKUl7I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="16-sending-events-part-3"></iframe></figure><h2 id="wrapping-up">Wrapping up</h2><p>In this 3 Part series about Sending Events, we learned how to use <code>SimConnect SDK</code> to send events to <code>Microsoft Flight Simulator</code>. We learned about the two methods available <code>TransmitClientEvent()</code> and <code>TransmitClientEvent_EX1()</code> and their differences. We learned about electromechanical rotational input devices like potentiometers and rotary encoders and we looked into rotary encoders deeper. Finally we combined the code we wrote in part 1 with what we learned about rotary encoders in part 2 so we could convert an <em>action </em>performed on a rotary encoder into an <em>event</em> in the sim.</p><p>If you want to have a look at the instruments I&apos;ve designed drawn in the same way explained in this series, have a look at <a href="https://www.theflightsimdev.com/my-flight-instruments/" rel="noreferrer">My Flight Instruments</a> where I put screenshots and a description of each one of the ones I&apos;ve designed.</p><p>Hope you found this series useful, and see you on the next one!</p>]]></content:encoded></item><item><title><![CDATA[15. Sending events - Part 2]]></title><description><![CDATA[<p>In Part 1, we learned how to send events from C# to <em>Microsoft Flight Simulator</em> using the <em>SimConnect SDK&apos;s</em> <code>EventIds</code>. We added two buttons in our WPF app to increase and decrease the barometer setting and resetting it to standard 29.92 with the press of another button.</p>]]></description><link>https://www.theflightsimdev.com/15-sending-events-part-2/</link><guid isPermaLink="false">66db2b81cef6b140bcdda24f</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Fri, 06 Sep 2024 17:26:34 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1510736661179-a0d68edb2f90?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGtub2JzfGVufDB8fHx8MTcyNTY0MzU2Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1510736661179-a0d68edb2f90?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGtub2JzfGVufDB8fHx8MTcyNTY0MzU2Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="15. Sending events - Part 2"><p>In Part 1, we learned how to send events from C# to <em>Microsoft Flight Simulator</em> using the <em>SimConnect SDK&apos;s</em> <code>EventIds</code>. We added two buttons in our WPF app to increase and decrease the barometer setting and resetting it to standard 29.92 with the press of another button. Now let&apos;s achieve the same but with a <em>knob</em> connected to the Teensy.</p><p></p><h2 id="types-of-knobs">Types of Knobs</h2><p>In electronics, two of the most common types of  electromechanical rotational input devices are <em>Potentiometers</em> and <em>Rotary Encoders. </em>To the naked eye, they both kinda look the same, but there is a big difference in terms of how they internally work and how to use them. In addition, both them come with an integrated <em>push button</em> which have two additional pins, <em>signal</em> and <em>ground,</em> to determine if the button is open or closed.</p><p></p><h3 id="potentiometers">Potentiometers </h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.theflightsimdev.com/content/images/2024/09/download-3.jpg" class="kg-image" alt="15. Sending events - Part 2" loading="lazy" width="259" height="194"><figcaption><span style="white-space: pre-wrap;">A Potentiometer</span></figcaption></figure><p>Potentiometers are a three-terminal resistor, that forms as an adjustable voltage divider. You can turn it <em>clockwise </em>and <em>counterclockwise</em>, and they have a <em>minimum</em> and a <em>maximum</em> limit of movement. Two of the pins are connected to <em>ground</em> and a <em>voltage signal</em>, and the 3rd pin is used for the output signal which is proportional to the potentiometer position. Since they have <em>limits</em>, they are typically used for things like volume control, brightness, etc. anything that has a predefined <em>minimum</em> and<em> maximum</em>.</p><p></p><h3 id="rotary-encoders">Rotary Encoders</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.theflightsimdev.com/content/images/2024/09/download-4.jpg" class="kg-image" alt="15. Sending events - Part 2" loading="lazy" width="245" height="205"><figcaption><span style="white-space: pre-wrap;">A Rotary Encoder</span></figcaption></figure><p>Rotary Encoders are electromechanical devices that convert the angular position or motion of a shaft into digital or analog signals. They don&apos;t have a minimum or maximum limit so you can rotate them on either direction infinitely. They also have three pins, one is used for your reference signal (ground or VCC), and the other two are for CLK and DATA signals. These two signals are phased, so depending on the rotation direction, you can determine if its rotating <em>clockwise</em> or <em>counterclockwise</em> based on the values of these two signals combined. That means, the output pattern could look something like <code>(0,0) -&gt; (0,1) -&gt; (1,1) -&gt; (1,0) -&gt; (0,0)</code> when rotating <em>clockwise</em>,  and <code>(0,0) -&gt; (1,0) -&gt; (1,1) -&gt; (0,1) -&gt; (0,0)</code> when rotation <em>counterclockwise</em>. They are typically used for adjusting parameters that you just need to infinitely increase and decrease without a limit.</p><p>There are even dual rotary encoder versions that have 3 extra pins for an outer ring encoder besides the shaft encoder, which packs much more functionality into a single device, and pretty much acts as if you are working with two independent rotary encoders.</p><p></p><h2 id="wiring">Wiring</h2><p>Wiring a rotary encoder can vary depending on what you got. Some come in little development boards with pins that come out so you can connect them on a breadboard, but those boards wire them to Vcc and the logic is inverted. I will be using a standalone encoder like the one in the picture above.</p><table>
<thead>
<tr>
<th>Teensy PIN</th>
<th>Rotary Encoder PIN</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>GND</td>
<td>GND</td>
<td>Ground</td>
</tr>
<tr>
<td>14</td>
<td>CLK</td>
<td>CLK Signal</td>
</tr>
<tr>
<td>15</td>
<td>DATA</td>
<td>DATA Signal</td>
</tr>
<tr>
<td>16</td>
<td>BUTTON</td>
<td>Button Signal</td>
</tr>
<tr>
<td>GND</td>
<td>BUTTON GND</td>
<td>Button Ground Signal</td>
</tr>
</tbody>
</table>
<blockquote>The signal pins are arbitrary, so use the ones that are convenient to you.</blockquote><p></p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/Rotary-Encoder-1.png" class="kg-image" alt="15. Sending events - Part 2" loading="lazy" width="734" height="606" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/Rotary-Encoder-1.png 600w, https://www.theflightsimdev.com/content/images/2024/09/Rotary-Encoder-1.png 734w" sizes="(min-width: 720px) 720px"></figure><h2 id="implementation">Implementation</h2><p>For my instruments, I decided to go with a <em>Rotary Encoder</em> so I can use it to adjust different parameters depending on what instrument I&apos;m building, and if I require min/max limits, it is something I can handle in code. I&apos;m actually using a <em>Dual Rotary Encoder</em>, but the principle is the same.</p><p>Let&apos;s look at some code:</p><pre><code class="language-arduino">// set this to the pins you are using on your device. if you notice that the increase/decrease is inverted, swap the CLOCK/DATA PINs
#define CLK_PIN 14
#define DATA_PIN 15
#define BUTTON_PIN 16

int lastEncoderState;

int lastEncoderButtonState;

void setup() {
  // I&apos;m wiring my encoder and buttons to ground, so my signals need to be pullup to use the internal resistance. If you were to wire to Vcc, you probably want to use INPUT instead.
  // this is also a good practice to have your circuit being HIGH at start so you know if you supply enough power to it, vs being low and then doing something that will drain more power than provided.
  pinMode(CLK_PIN, INPUT_PULLUP);
  pinMode(DATA_PIN, INPUT_PULLUP);
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // store the current state the encoder is when we turn on the arduino
  lastEncoderState = digitalRead(CLK_PIN);

  // open serial connection and wait
  Serial.begin(115200);
  while (!Serial) {}
}

void loop() {
  // read the encoder connected to CLK_PIN, DATA_PIN with last state, and depending on the return value, we determine if we moved clockwise or counterclockwise
  switch (readEncoder(CLK_PIN, DATA_PIN, lastEncoderState)) {
    case -1:
      Serial.println(&quot;Encoder Decreased&quot;);
      break;
    case 1:
      Serial.println(&quot;Encoder Increased&quot;);
      break;
  }

  // check if the button was pressed
  if (readButton(BUTTON_PIN, lastEncoderButtonState)) {
    Serial.println(&quot;Button Pressed&quot;);
  }

  // small delay to give it time for debouncing
  delay(1);
}

// simple funciton that reads the state of a button connected to &apos;pin&apos;, with current and last states for determining if the button is being pressed or not.
bool readButton(uint8_t pin, int&amp; lastState) {
  int state = !digitalRead(pin);

  if (state != lastState) {
    lastState = state;
    if (state == 0) {
      return true;
    }
  }

  return false;
}

// In my encoder, and depends on how you wired it, this is the sequence.
// If yours is inverted, you can invert the CLK and DATA connections
// cw   00,01,11,10,00
// ccw  00,10,11,01,00
int readEncoder(int clkPin, int dataPin, int&amp; lastState) {
  int state = digitalRead(clkPin);

  if (state != lastState) {
    lastState = state;

    int dataState = digitalRead(dataPin);
    if (dataState != state) {
      return -1;
    } else {
      return 1;
    }
  }

  return 0;
}</code></pre><p></p><p>If we build and run the sketch and open the Serial Monitor window, we can rotate the encoder <em>clockwise</em>, <em>counterclockwise</em> and press the button and see a similar output:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-31.png" class="kg-image" alt="15. Sending events - Part 2" loading="lazy" width="375" height="396"></figure><p></p><h2 id="breakdown">Breakdown</h2><p>First, we define our pins for CLK/DATA and BUTTON signals. Then we create some variables to store the current and previous states for the encoder and button.</p><p>In the <code>setup()</code> function, we set the 3 pins as <code>INPUT_PULLUP</code> since we are connecting the signal to ground. Then we store the last button state so we don&apos;t get a false button pressed message.</p><p>In our <code>loop()</code> function, we first call <code>readEncoder()</code> to check the state of the encoder. This function returns -1,1 for when the encoder is being decreased or increased, and 0 when it&apos;s static. The logic in this function is a bit tricky, but it is based on how our sequence works. Remember that the two signals are out of phase, that means for example, that when CLK is 0, DATA can be 0 or 1 before CLK can change to 1 and so on. This is true only if your reading speed is fast enough, because if you are turning the encoder way too fast, it can jump into the wrong state and you would &apos;read&apos; movement in the opposite direction, which is something I&apos;ve even seen on equipment out there... <em>no, I&apos;m not talking about my flight school&apos;s $100k RedBird FAA certified simulator that has this issue... nope</em> &#x1F643;</p><p>Similar, we call the <code>readButton()</code> function to determine if the button was pressed or released depending on the previous state we stored in <code>lastState</code>.</p><blockquote>Note that I made these two functions generic enough so they can be used to verify multiple encoders and buttons, hence I&apos;m passing the <em><code>lastState</code></em> argument by reference.</blockquote><p></p><h2 id="conclusion">Conclusion</h2><p>That wraps up Part 2 of this series. Now that we know how to use a <em>Rotary Encoder</em> with some pretty straight forward code, in Part 3 we&apos;ll apply these changes to the <code>HelloMSFS.ino</code> sketch from Part 1 to send an <em>increase, decrease, and button</em> events to the C# app, so we can then call corresponding <em>SimConnect SDK</em> methods to send that event to the sim.</p>]]></content:encoded></item><item><title><![CDATA[14. Sending events - Part 1]]></title><description><![CDATA[<p>So up to this point we were only pulling data from <code>Microsoft Flight Simulator 2020</code> and sending it down to the devices. But what about interacting back with the sim? There are two things that you can do with the <code>SimConnect SDK</code>: setting <em>some</em> <code>SimVars</code>, and sending <em><code>EventIds</code></em>. I clarify</p>]]></description><link>https://www.theflightsimdev.com/14-sending-events-part-1/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5f9</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Thu, 05 Sep 2024 20:30:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1517190525944-4edce215bc4a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE2fHxjb2NrcGl0fGVufDB8fHx8MTcyNTY1NDM3NXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1517190525944-4edce215bc4a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE2fHxjb2NrcGl0fGVufDB8fHx8MTcyNTY1NDM3NXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="14. Sending events - Part 1"><p>So up to this point we were only pulling data from <code>Microsoft Flight Simulator 2020</code> and sending it down to the devices. But what about interacting back with the sim? There are two things that you can do with the <code>SimConnect SDK</code>: setting <em>some</em> <code>SimVars</code>, and sending <em><code>EventIds</code></em>. I clarify that we can only set <em>some</em><code>SimVars</code> since a lot of them are read only, but you can check the <em>Simulation Variables</em> section in the SDK&apos;s documentation to see which ones are read/write. Here&apos;s an example:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-27.png" class="kg-image" alt="14. Sending events - Part 1" loading="lazy" width="1659" height="200" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-27.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/image-27.png 1000w, https://www.theflightsimdev.com/content/images/size/w1600/2024/09/image-27.png 1600w, https://www.theflightsimdev.com/content/images/2024/09/image-27.png 1659w" sizes="(min-width: 720px) 720px"></figure><p>We&apos;ll skip setting <code>SimVars</code> for now. Right now we just want to focus on sending <code>EventIds</code>.</p><p>The <code>SimConnect SDK</code> offers a way of communicating back with the sim via <em><code>EventIds</code></em>. A list of all the existing ones and their documentation can be found here: <a href="https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm?ref=theflightsimdev.com">https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm</a></p><p>Continuing to build our <em>Altimeter</em> instrument, let&apos;s create some events to change the barometer setting, the same way we would by turning a knob in an airplane&apos;s <em>Altimeter</em>. If we look at the <a href="https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Aircraft_Instrumentation_Events.htm?ref=theflightsimdev.com#KOHLSMAN_INC" rel="noreferrer">documentation</a>, we can see there are 3 events that we can use to increase, decrease and reset the barometer setting:</p><table>
<thead>
<tr>
<th>Event Name</th>
<th>Parameters</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>KOHLSMAN_INC</td>
<td>N/A</td>
<td>Increments altimeter setting.</td>
</tr>
<tr>
<td>KOHLSMAN_DEC</td>
<td>N/A</td>
<td>Decrements altimeter setting.</td>
</tr>
<tr>
<td>KOHLSMAN_SET</td>
<td>[0]: Value to set, [1]: Altimeter index</td>
<td>Sets altimeter setting (Millibars * 16).</td>
</tr>
</tbody>
</table>
<blockquote>Note: we really only need the INC and DEC to mimic the behavior of the Altimeter&apos;s knob in an airplane, but I&apos;ll use SET to reset to standard barometric pressure of 29.92 to explain the two different ways of sending the events based on the <code>EventId</code> properties.</blockquote><h2 id="code-changes">Code Changes</h2><p>Setting the events is very similar to how we setup the <code>Simvars</code>. We first need to create an <code>enum</code> with our events:</p><pre><code class="language-csharp">public enum SimEventType
{
  KOHLSMAN_INC,   // increments the altimeter setting
  KOHLSMAN_DEC,   // decrements the altimeter setting
  KOHLSMAN_SET,   // sets the altimeter setting to a specified value
}</code></pre><p>Next, we need to register our <code>EventIds</code><em> </em>in a similar way to when we registered our <code>SimVars</code>. Let&apos;s do this in the same <code>Simconnect_OnRecvOpen()</code> method we used before:</p><pre><code class="language-csharp">private void Simconnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
{
  // original simconnect.AddToDataDefinition(...)&apos;s here
  ...
  // register events
  simconnect.MapClientEventToSimEvent(SimEventType.KOHLSMAN_SET, &quot;KOHLSMAN_SET&quot;);
  simconnect.MapClientEventToSimEvent(SimEventType.KOHLSMAN_INC, &quot;KOHLSMAN_INC&quot;);
  simconnect.MapClientEventToSimEvent(SimEventType.KOHLSMAN_DEC, &quot;KOHLSMAN_DEC&quot;);
}</code></pre><p>Next let&apos;s modify the XAML UI a bit so we can add some buttons to send the events. Also, I added a text field for specifying the COM port so I can test with different devices without having to rebuild the code.</p><pre><code class="language-xml">&lt;Window x:Class=&quot;HelloMSFS.MainWindow&quot;
        xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
        xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
        xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
        xmlns:local=&quot;clr-namespace:HelloMSFS&quot;
        mc:Ignorable=&quot;d&quot;
        Title=&quot;Hello MSFS&quot; SizeToContent=&quot;WidthAndHeight&quot; Topmost=&quot;True&quot;&gt;
    &lt;Grid Margin=&quot;20&quot;&gt;
        &lt;Grid.ColumnDefinitions&gt;
            &lt;ColumnDefinition MinWidth=&quot;100&quot;/&gt;
            &lt;ColumnDefinition MinWidth=&quot;100&quot;/&gt;
        &lt;/Grid.ColumnDefinitions&gt;

        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
        &lt;/Grid.RowDefinitions&gt;

        &lt;Label x:Name=&quot;label0&quot; Content=&quot;Serial Port:&quot; Grid.Row=&quot;0&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot;/&gt;
        &lt;Label x:Name=&quot;label1&quot; Content=&quot;Altimeter:&quot; Grid.Row=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; /&gt;
        &lt;Label x:Name=&quot;label2&quot; Content=&quot;Barometer:&quot; Grid.Row=&quot;2&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot;/&gt;

        &lt;TextBox x:Name=&quot;serialPortTextBox&quot; TextWrapping=&quot;Wrap&quot; Text=&quot;COM5&quot; Grid.Row=&quot;0&quot; Grid.Column=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; IsReadOnly=&quot;True&quot;/&gt;
        &lt;TextBox x:Name=&quot;altimeterTextBox&quot; TextWrapping=&quot;Wrap&quot; Text=&quot;1000&quot; Grid.Row=&quot;1&quot; Grid.Column=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; IsReadOnly=&quot;True&quot;/&gt;
        &lt;TextBox x:Name=&quot;barometerTextBox&quot; TextWrapping=&quot;Wrap&quot; Text=&quot;29.92&quot; Grid.Row=&quot;2&quot; Grid.Column=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; IsReadOnly=&quot;True&quot;/&gt;

        &lt;Grid Grid.Row=&quot;3&quot; Grid.Column=&quot;1&quot;&gt;
            &lt;Grid.ColumnDefinitions&gt;
                &lt;ColumnDefinition /&gt;
                &lt;ColumnDefinition /&gt;
            &lt;/Grid.ColumnDefinitions&gt;
            &lt;Button x:Name=&quot;decreaseBaroButton&quot; Content=&quot;-&quot; Grid.Column=&quot;0&quot; Click=&quot;decreaseBaroButton_Click&quot; IsEnabled=&quot;False&quot;/&gt;
            &lt;Button x:Name=&quot;increaseBaroButton&quot; Content=&quot;+&quot; Grid.Column=&quot;1&quot; Click=&quot;increaseBaroButton_Click&quot; IsEnabled=&quot;False&quot;/&gt;
        &lt;/Grid&gt;
        &lt;Button x:Name=&quot;resetBaroButton&quot; Content=&quot;STD Baro&quot; Grid.Row=&quot;3&quot; Grid.Column=&quot;0&quot; Click=&quot;resetBaroButton_Click&quot; IsEnabled=&quot;False&quot;/&gt;

        &lt;Button x:Name=&quot;connectButton&quot; Content=&quot;Connect&quot; Grid.Row=&quot;5&quot; Click=&quot;connectButton_Click&quot; /&gt;
        &lt;Button x:Name=&quot;disconnectButton&quot; Content=&quot;Disconnect&quot; Grid.Row=&quot;5&quot; Grid.Column=&quot;1&quot; Click=&quot;disconnectButton_Click&quot; IsEnabled=&quot;False&quot;/&gt;
    &lt;/Grid&gt;
&lt;/Window&gt;
</code></pre><p>To use the new <em>serialPortTextBox</em>, change the <code>private void InitializeSerialPort()</code> to this:</p><pre><code class="language-csharp">private void InitializeSerialPort()
{
    serialPort = new SerialPort(serialPortTextBox.Text, 115200)
    {
        ReadTimeout = 1000,
        WriteTimeout = 1000,
        DtrEnable = true,
        RtsEnable = true,
        NewLine = Environment.NewLine,
    };

    // attach an event handler
    serialPort.DataReceived += SerialPort_DataReceived;

    serialPort.Open();
}</code></pre><p>For the button event handlers, this is the code behind:</p><pre><code class="language-csharp">private void decreaseBaroButton_Click(object sender, RoutedEventArgs e)
{
    simconnect.TransmitClientEvent(SimConnect.SIMCONNECT_OBJECT_ID_USER, SimEventType.KOHLSMAN_DEC, 0, GroupId.FLAG, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
}

private void increaseBaroButton_Click(object sender, RoutedEventArgs e)
{
    simconnect.TransmitClientEvent(SimConnect.SIMCONNECT_OBJECT_ID_USER, SimEventType.KOHLSMAN_INC, 0, GroupId.FLAG, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
}
        
private void resetBaroButton_Click(object sender, RoutedEventArgs e)
{
    simconnect.TransmitClientEvent_EX1(SimConnect.SIMCONNECT_OBJECT_ID_USER, SimEventType.KOHLSMAN_SET, GroupId.FLAG, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY, (uint)(29.92 * 33.864 * 16), 0, 0, 0, 0);
}</code></pre><p></p><h2 id="code-break-down">Code Break down</h2><p>Notice we have two different methods that send an <code>EventId</code>:</p><p><code>TransmitClientEvent(uint ObjectID, Enum EventID, uint dwData, Enum GroupID, SIMCONNECT_EVENT_FLAG Flags)</code></p><p><code>TransmitClientEvent_EX1(uint ObjectID, Enum EventID, Enum GroupID, SIMCONNECT_EVENT_FLAG Flags, uint dwData0, uint dwData1, uint dwData2, uint dwData3, uint dwData4)</code></p><p>The difference between the two is that <code>TransmitClientEvent</code> has a single <em>dwData </em>argument, while <code>TransmitClientEvent_EX1</code> is used for when the data is expected in an array, and <em>dwData0 </em>through <em>dwData4 </em>are the elements of that array.</p><p>If we look back at the documentation for <code>KOHLSMAN_SET</code>, it expects the first parameter <code>[0]: Value to set</code> and the second <code>[1]: Altimeter index</code>, which in this case would be <em>0</em> for the first <em>altimeter.</em> This would be used in cockpits that have multiple altimeters, like for pilot and copilot, or backup instruments, where you would send 0,1,2, etc. as <em>dwData1</em> in this case. </p><blockquote>Note: If we tried to use <code>TransmitClientEvent</code> for <code>KOHLSMAN_SET</code>, we would see that nothing happens, since we are not sending the data in the format it expects.</blockquote><p>In the <code>resetBaroButton_Click</code> we are using the standard aviation barometric pressure of 29.92 inHg, then we multiply by 33.864 to convert to <em>Millibars</em> and then we multiply by 16 as the sim expects it.</p><blockquote><em>Of course if we want to be efficient in our coding, we could some constants for this or write a method to convert from a given pressure in inHg to Millibars, but got lazy, no judging! </em>&#x1F604;</blockquote><p>The rest of the parameters are:</p><ol><li>The SimConnect &quot;User ID&quot; const used to represent the user&apos;s own aircraft in the sim. We also used this to subscribe to the <code>SimVars</code> earlier.</li><li>The <code>enum</code> for the <em>event </em>you want to send, that you previously registered with <code>MapClientEventToSimEvent(...)</code>.</li><li>The group id, set to 1 which maps to <code>SIMCONNECT_GROUP_PRIORITY_HIGHEST</code></li><li>A flag that provides additional options. Common ones are <code>SIMCONNECT_EVENT_FLAG.DEFAULT</code>, and <code>SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY</code></li></ol><p></p><h2 id="running-and-testing">Running and testing</h2><p>If we build and run our code, we get the updated UI:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-28.png" class="kg-image" alt="14. Sending events - Part 1" loading="lazy" width="517" height="408"></figure><p>We connect and again verify that the numbers match what the sim is showing. Pressing on the +/- buttons should increase and decrease the barometer setting both in game and in the C# app, along an altitude value corresponding to the new barometer setting. This is because we send the event to the game, and then the game sends the new <code>SimVars</code> values back to us so we update the text field with the newest values.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-29.png" class="kg-image" alt="14. Sending events - Part 1" loading="lazy" width="490" height="431"></figure><p>Now press on the <em>STD Baro</em> button, and you should see that it changed to <em>29.92</em> and the <em>altitude</em> was updated accordingly as well.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-30.png" class="kg-image" alt="14. Sending events - Part 1" loading="lazy" width="463" height="435"></figure><p></p><h2 id="conclusion">Conclusion</h2><p>Sending events to the sim is pretty straight forward. One of the biggest mistakes I&apos;ve done is calling <code>TransmitClientEvent</code> instead of <code>TransmitClientEvent_EX1</code> by missing that it was expecting an array in the documentation. Another challenge I&apos;ve encountered is that sometimes it expects the data in a very specific data format I have never worked with before, so I had to spend some time transforming something like a <code>float</code> value into some weird byte array of some obscure standard I have never heard of before. &#x1F923;</p><p>In Part 2 and Part 3 of this series I will introduce a rotary encoder so we can send decrease, increase and button push events from the Teensy to the C# app, and repurpose the <code>DataReceived</code> handler method to interpret what I&apos;m sending it and use that to trigger these same events.</p>]]></content:encoded></item><item><title><![CDATA[13. Drawing a digital Altimeter]]></title><description><![CDATA[<p>Now that we wired our Teensy to a TFT LCD ILI9341 display, lets go ahead and revisit our previous sketch that received data from <code>Microsoft Flight Simulator 2020</code> via our C# app. We&apos;ll add the <code>SPI.h</code> and <code>ILI9341_t3.h</code> libraries and <code>#defines</code> for the TFT and</p>]]></description><link>https://www.theflightsimdev.com/13-drawing-a-digital-altimeter/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5ef</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Wed, 04 Sep 2024 22:52:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1518228684816-9135c15ab4ea?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fGFsdGltZXRlcnxlbnwwfHx8fDE3MjU1MTA1NDN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1518228684816-9135c15ab4ea?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fGFsdGltZXRlcnxlbnwwfHx8fDE3MjU1MTA1NDN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="13. Drawing a digital Altimeter"><p>Now that we wired our Teensy to a TFT LCD ILI9341 display, lets go ahead and revisit our previous sketch that received data from <code>Microsoft Flight Simulator 2020</code> via our C# app. We&apos;ll add the <code>SPI.h</code> and <code>ILI9341_t3.h</code> libraries and <code>#defines</code> for the TFT and create an ILI9341_t3 instance <code>tft</code> to draw. We also added variables to store the current and previous values for both the <em>altitude</em> and <em>barometer</em> for refresh purposes.</p><p></p><h2 id="the-code">The Code</h2><pre><code class="language-arduino">#include &quot;SPI.h&quot;
#include &quot;ILI9341_t3.h&quot;

#define TFT_DC 9
#define TFT_CS 10
#define CHAR_WIDTH 6

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

float currentAltitude = 0;
float previousAltitude = -1;

float currentBarometer = 0;
float previousBarometer = -1;

void setup() {
  // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it&apos;s the same rate as in the C# code
  Serial.begin(115200);

  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);

  // labels
  drawTextCentered(&quot;Altitude (feet)&quot;, 0, 2, ILI9341_WHITE);
  drawTextCentered(&quot;Barometer (inHg)&quot;, 120, 2, ILI9341_WHITE);

  // refresh values
  update();

  // wait for a serial connection before beginning
  while(!Serial);
}

void loop() {
  // check if we have at least 8 bytes. our full message should be 9 bytes,
  if (Serial.available() &gt; 8) {
    currentAltitude = readFloat();
    currentBarometer = readFloat();

    Serial.readStringUntil(&apos;\n&apos;);

    update();
  }
}

// util method to read 4 bytes from the serial port and return them as a float
float readFloat() {
  char buffer[4];
  Serial.readBytes(buffer, 4);

  float value;
  memcpy(&amp;value, buffer, sizeof(float));

  return value;
}

// util method to redraw the values
void update() {
  if (previousAltitude != currentAltitude) {
    tft.fillRoundRect(30, 40, 260, 50, 10, ILI9341_GREEN);
    tft.drawRoundRect(30, 40, 260, 50, 10, ILI9341_WHITE);
    drawTextCentered(String(currentAltitude), 50, 4, ILI9341_BLACK);

    previousAltitude = currentAltitude;
  }

  if (previousBarometer != currentBarometer) {
    tft.fillRoundRect(30, 160, 260, 50, 10, ILI9341_GREEN);
    tft.drawRoundRect(30, 160, 260, 50, 10, ILI9341_WHITE);
    drawTextCentered(String(currentBarometer), 170, 4, ILI9341_BLACK);

    previousBarometer = currentBarometer;
  }
}

// util method to draw centered
void drawTextCentered(String text, int y, int size, uint16_t color) {
  int offset = 160 - (text.length() * CHAR_WIDTH * size / 2);

  tft.setTextSize(size);
  tft.setCursor(offset, y);
  tft.setTextColor(color);
  tft.print(text);
}</code></pre><p></p><h2 id="initialization">Initialization</h2><p>We initialize our <code>tft</code> in the <code>setup()</code> method like before and add some labels for the &quot;Altitude (feet)&quot; and &quot;Barometer (inHg)&quot;. Since these we will only update a portion of the screen to draw the actual values, we only need to draw these labels once during <code>setup()</code>. Finally, we call the <code>update()</code> function to draw the values on the screen and wait for the Serial port to be opened, so that we wait until our C# app connects and starts sending data.</p><p></p><h2 id="loop">Loop</h2><p>In our <code>loop()</code> function, we replaced the the code that sent the <em>altitude </em>and <em>barometer </em>values back to the C# app, in favor of the <code>update()</code> function. If we look at the <code>update()</code> function&apos;s body, we have two similar sections, for <em>altitude</em> and <em>barometer</em>. Let&apos;s examine the <em>altitude</em> section.</p><p>To reduce updates, we only redraw when the new <em>currentAltitude</em> value is different from <em>previousAltitude</em>, We draw 2 rectangles, a solid GREEN one for the background, and a WHITE one for the border. We call the same <code>drawTextCentered()</code> function we used to draw the labels and we finally store the new <em>currentAltitude</em> value as <em>previousAltitude </em>for the next <code>loop()</code> pass.</p><p></p><h2 id="centering-text">Centering Text</h2><p>The <code>void drawTextCentered(String text, int y, int size, uint16_t color)</code> function is a handy utility function I use on a lot of my projects. Since displaying text is based on the top-left corner of the first letter in the <code>String</code>, based on the <code>String</code>&apos;s length() we can calculate how much we need to offset the <em>x</em> coordinate so that the entire text is centered.</p><p>Since I called <code>tft.rotate(1)</code> in the <code>setup()</code> function, the resolution is 320 x 240. If we want to make the text centered, we start from <em>x=160</em>. We calculate half of the <code>String</code>&apos;s length(), times the text size, times the character&apos;s width. We then subtract that from <em>160</em> and we have a rough position to make it centered.</p><p>Let&apos;s say we have &quot;Hello World!&quot;. String length is 12. Font size is set to 4. Default font width is 6.</p><blockquote>offset_x = 160 - (12 * 4 * 6 / 2) = 160 - (144) = 16</blockquote><p></p><h2 id="build-and-run">Build and Run</h2><p>If we build and upload the Sketch into our Teensy, run <code>Microsoft Flight Simulator 20202</code> and load a flight, run the <code>Hello MSFS</code> app and click on <em>Connect</em>, we should see something like this:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/20240905_035940830_iOS.jpg" class="kg-image" alt="13. Drawing a digital Altimeter" loading="lazy" width="2000" height="1500" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/20240905_035940830_iOS.jpg 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/20240905_035940830_iOS.jpg 1000w, https://www.theflightsimdev.com/content/images/size/w1600/2024/09/20240905_035940830_iOS.jpg 1600w, https://www.theflightsimdev.com/content/images/2024/09/20240905_035940830_iOS.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><blockquote>Please ignore the red lines, this screen is damaged and it has those 3 rows of pixels permanently red, so it is the one I use for development ;-)</blockquote><p>Now fly around or change the barometer setting in your plane&apos;s Altimeter and see how these change. Immediately you will notice that the <em>altitude</em> value flickers a lot compared to the <em>barometer</em> value. This is because it is constantly changing and it needs to redraw this section a lot.</p><p></p><h2 id="wrapping-up">Wrapping up</h2><p>At this point I have provided all the building blocks needed to pull data from <code>Microsoft Flight Simulator 2020</code> using the <code>SimConnect SDK</code>, sending the data down to a device via a USB Serial connection, and draw an instrument based on the data on a TFT LCD display. Now you can start designing your own instruments that mimic the typical <em>steam gauge</em> instruments or even digital ones like the <em>Aspens</em> or <em>Garmin digital display</em> instruments.</p><p>The only thing we are missing that I&apos;ll be covering in the next post is how to send events from the device back to the sim. Later on, we&apos;ll talk about how we can use the <code>GFXCanvas16</code> to minimize the flickering by drawing to a <em>in-memory canvas</em> and updating only the necessary areas of the screen.</p>]]></content:encoded></item><item><title><![CDATA[12. Hello TFT LCD World]]></title><description><![CDATA[<p>In our previous post <a href="https://www.theflightsimdev.com/6-choosing-the-right-display/" rel="noreferrer">Choosing the right display</a> we looked at a few options to use as our main display. We ended up choosing a 240x320 ILI9341 TFT Screen that is available to buy at a lot of places, like Amazon, AliExpress, PJRC, Ebay, etc. It is driven by a</p>]]></description><link>https://www.theflightsimdev.com/12-hello-tft-lcd-world/</link><guid isPermaLink="false">66d8b470cef6b140bcdd9f0f</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Tue, 03 Sep 2024 22:51:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1564585447193-cd9a0cf42a36?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDc0fHxwaXhlbHN8ZW58MHx8fHwxNzI1NDc4MDczfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1564585447193-cd9a0cf42a36?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDc0fHxwaXhlbHN8ZW58MHx8fHwxNzI1NDc4MDczfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="12. Hello TFT LCD World"><p>In our previous post <a href="https://www.theflightsimdev.com/6-choosing-the-right-display/" rel="noreferrer">Choosing the right display</a> we looked at a few options to use as our main display. We ended up choosing a 240x320 ILI9341 TFT Screen that is available to buy at a lot of places, like Amazon, AliExpress, PJRC, Ebay, etc. It is driven by a ILI9341 chip that can handle 240x320 resolution, 18-bit color, and uses the <a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface?ref=theflightsimdev.com" rel="noreferrer">SPI (Serial Peripheral Interface)</a> for communications and uses only 4 signal pins, CS, SCLK, MOSI and MISO, which is perfect for interfacing with boards like the Arduino, ESP32 and Teensy, since they all support SPI communications natively.</p><p>Most of these displays also come with Touch capabilities from a XPT2046 touch controller, which require an additional library to control and 5 additional pins.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-10.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="800" height="800" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-10.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-10.png 800w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="wiring">Wiring</h2><p>One important fact about these displays. The signal lines can only handle 3.3V, so be careful what you connect them to that could damage your display. 5V should only be used as VCC, and if wiring the LED pin to 5V, put a 100 ohm resistor in place (or just wire to 3.3V)</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.theflightsimdev.com/content/images/2024/09/TFT-LCD-Wiring.drawio.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="1651" height="861" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/TFT-LCD-Wiring.drawio.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/TFT-LCD-Wiring.drawio.png 1000w, https://www.theflightsimdev.com/content/images/size/w1600/2024/09/TFT-LCD-Wiring.drawio.png 1600w, https://www.theflightsimdev.com/content/images/2024/09/TFT-LCD-Wiring.drawio.png 1651w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Wiring for Teensy 4.0</span></figcaption></figure><table>
<thead>
<tr>
<th>TFT Screen PIN</th>
<th>Teensy PIN</th>
<th>Description</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>VCC</td>
<td>VIN</td>
<td>Power Supply In</td>
<td>5V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
<td>Power Supply Ground</td>
<td></td>
</tr>
<tr>
<td>CS</td>
<td>10</td>
<td>Chip Select</td>
<td></td>
</tr>
<tr>
<td>RESET</td>
<td>3.3V</td>
<td>LCD Reset</td>
<td></td>
</tr>
<tr>
<td>DC</td>
<td>9</td>
<td>SPI Data/Command selection</td>
<td></td>
</tr>
<tr>
<td>SDI (MOSI)</td>
<td>11</td>
<td>Serial data master, out slave in</td>
<td></td>
</tr>
<tr>
<td>SCK</td>
<td>13</td>
<td>Clock Signal from master</td>
<td></td>
</tr>
<tr>
<td>LED</td>
<td>3.3V or VIN (5V)</td>
<td>Backlight</td>
<td>Use 100ohm resistor if using 5V</td>
</tr>
<tr>
<td>SDO (MISO)</td>
<td>12</td>
<td>Serial data master in, slave out</td>
<td></td>
</tr>
</tbody>
</table>
<p>I recommend using a breadboard for development before soldering all the cables without testing. This is how my development setup looks like:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/20240904_185632909_iOS.jpg" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="2000" height="2667" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/20240904_185632909_iOS.jpg 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/20240904_185632909_iOS.jpg 1000w, https://www.theflightsimdev.com/content/images/size/w1600/2024/09/20240904_185632909_iOS.jpg 1600w, https://www.theflightsimdev.com/content/images/2024/09/20240904_185632909_iOS.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="arduino-ide-setup">Arduino IDE Setup</h2><p>The Arduino IDE supports multiple boards besides Arduino. To get Teensy support in the Arduino IDE, open File-&gt;Preferences and in the Additional boards manager URLs add the following:</p><pre><code>https://www.pjrc.com/teensy/package_teensy_index.json</code></pre><p>Should look like something like this:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-16.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="810" height="517" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-16.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-16.png 810w" sizes="(min-width: 720px) 720px"></figure><blockquote>Note that this is Arduino IDE 2.x. If you want to, you can do the same in Arduino 1.x, but I recommend using 2.x.</blockquote><p>Once that you added the Teensy board manager URL, let&apos;s open the Boards Manager and  search for teensy. <code>Teensy (for Arduino IDE 2.0.4 or later)</code> should show up. Install the latest version.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-18.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="501" height="335"></figure><p>Now that our IDE is setup and our Teensy is connected to the computer, lets try out one of the examples. Go to File -&gt; Examples, under <code>Examples for Teensy 4.0</code> look for <code>ILI9341_t3</code> and open the <code>graphicstest</code> example:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-21.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="721" height="331" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-21.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-21.png 721w" sizes="(min-width: 720px) 720px"></figure><p>Select your Teensy in the boards drop down menu and Build and Upload the Sketch. Once finished, and if everything is wired properly, you&apos;ll see a message on the screen saying &quot;Waiting for Arduino Serial Monitor...&quot;. Open the Serial Monitor in the IDE and watch the magic happen!</p><p>While the graphics eye candy show is going, the Teensy will output some <em>stats for nerds </em>to the Serial Monitor window that look like this:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-24.png" class="kg-image" alt="12. Hello TFT LCD World" loading="lazy" width="467" height="452"></figure><blockquote>One thing to try out. If you have an Arduino UNO, hook it up to the LCD screen and run the Adafruit ILI9341 graphicstest example sketch so you can compare the numbers. The Teensy is so much faster, which is why I decided to go with this platform. </blockquote><p></p><h2 id="so-how-does-this-work">So how does this work?</h2><p>At the beginning of the Sketch, we can see the following:</p><pre><code class="language-arduino">#include &quot;SPI.h&quot;
#include &quot;ILI9341_t3.h&quot;
#include &quot;font_Arial.h&quot;

// For the Adafruit shield, these are the default.
#define TFT_DC  9
#define TFT_CS 10

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);</code></pre><p>These TFT devices have an ILI9341 driver that uses SPI for communications. The <code>ILI9341_t3.h</code> is a Teensy optimized version of the <code>ILI9341.h</code> library made by Adafruit, which works in combination of the <code>Adafruit_GFX.h</code> library for drawing graphics on a screen.</p><p>We also define where our Data/Communications and Chip Select pins are connected to and we create an ILI9341_t3 object called <code>tft</code> for drawing.</p><p>In the <code>setup()</code> function we see the following:</p><pre><code class="language-arduino">void setup() {
  tft.begin();
// Note: you can now set the SPI speed to any value
// the default value is 30Mhz, but most ILI9341 displays
// can handle at least 60Mhz and as much as 100Mhz
//  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println(&quot;Waiting for Arduino Serial Monitor...&quot;);</code></pre><p>We are initializing our <code>tft</code> screen with the <code>begin()</code> function, which takes care of the SPI initialization. Then we use <code>fillscreen()</code> to set the screen to black, we <code>setTextColor()</code> to yellow and <code>setTextSize()</code> to 2, which renders the default font at 10 x 16 pixels per character, instead of the default size of 1 which renders it at 6 x 8 pixels. We can also specify the screen orientation by calling <code>setRotation()</code> with values of 0,1,2,3 for 0, 90, 180 and 270 degrees. Note that the coordinate system is always (0,0) at the top left corner of the screen and depending on the orientation, can be either 240x320 or 320x240 at the bottom left corner of the screen.</p><p>To draw text, we first call the <code>setCursor(x,y)</code> to specify where we will begin writing text, and use <code>println()</code> to output text with the last color specified (yellow) and font size (2). All subsequent calls to <code>print()</code> or <code>println()</code> will also use the same color and size unless you change it to something else.</p><pre><code class="language-arduino">tft.setSize(2);
tft.setColor(ILI9341_RED);
tft.println(&quot;This text is RED and size 2&quot;);

tft.setSize(3);
tft.setColor(ILI9341_WHITE);
tft.println(&quot;This text is WHITE and size 3&quot;);</code></pre><p>Drawing lines can be done in two ways: you can use <code>drawLine(x1,y1,x2,y2,color)</code> to draw a line from point (x1,y1) to point (x2,y2) with the specified color, or you can use the <code>drawFastHLine(x,y,width,color)</code> and <code>drawFastVLine(x,y,height,color)</code> to draw a horizontal or vertical line starting from point (x,y) and with the specified width or height and color. These two take advantage of ILI9341 commands and are more efficient than using the regular <code>drawLine()</code> to draw horizontal and vertical lines.</p><p>The library also functions to either draw an empty shape or fill a solid shape. For example <code>drawCircle(x,y,radius,color)</code> will draw an empty circle with a center at point (x,y) of the specified radius and color, but <code>fillCircle(x,y,radius,color)</code> will draw a solid circle at point (x,y) of the specified radius and filled in its entirety of the specified color. Similar functions are available for triangles and rectangles, with the addition of rectangles have an additional round corner counterparts, for when you might want to draw something like a button.</p><p></p><h2 id="performance-considerations">Performance considerations</h2><p>A very important thing to know, there is no native logic for &quot;remembering&quot; what the drawn shapes are. These functions are the most basic forms of drawing, and they just draw on top of whatever is already on the screen. If for example you wanted to draw something like the blue sky and the sun, and later want to draw the black night sky and the moon, you first have to fill the entire screen with a blue color using <code>fillScreen()</code> which erases everything, then you would have to use <code>fillCircle()</code> to draw a &quot;sun&quot;. Then when you want to draw the night scene, you&apos;ll need to call <code>fillScreen()</code> again, this time with black, erasing the entire display, and then would have to call <code>fillCircle()</code> again to draw the &quot;moon&quot;.  This tends to be slow, especially whenever you call <code>fillScreen()</code>, so keep in mind this and only update the sections of the screen you need to update depending on what you are drawing. Like I mentioned before, there are other ways around this, like using a frame buffer, which I&apos;ll talk more about later.</p><p></p><h2 id="useful-links">Useful Links</h2><p>Both Adafruit libraries can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/adafruit/Adafruit-GFX-Library?ref=theflightsimdev.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - adafruit/Adafruit-GFX-Library: Adafruit GFX graphics core Arduino library, this is the &#x2018;core&#x2019; class that all our other graphics libraries derive from</div><div class="kg-bookmark-description">Adafruit GFX graphics core Arduino library, this is the &#x2018;core&#x2019; class that all our other graphics libraries derive from - adafruit/Adafruit-GFX-Library</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="12. Hello TFT LCD World"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">adafruit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/293a43b2ea52bf6340a2c173e9f5d96862ef6841db089136db0b09fdc9ac2869/adafruit/Adafruit-GFX-Library" alt="12. Hello TFT LCD World"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/adafruit/Adafruit_ILI9341?ref=theflightsimdev.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - adafruit/Adafruit_ILI9341: Library for Adafruit ILI9341 displays</div><div class="kg-bookmark-description">Library for Adafruit ILI9341 displays. Contribute to adafruit/Adafruit_ILI9341 development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="12. Hello TFT LCD World"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">adafruit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/cd6fb8d9ef4dabaeb84e3a035de2f9038fa4e6bd46829e6209f4d3547ec1f767/adafruit/Adafruit_ILI9341" alt="12. Hello TFT LCD World"></div></a></figure><p>They also have a really good tutorial on how to draw on these ILI9341 driven devices here, which I suggest you go through for more details on how to draw graphics on the screen using the library.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.adafruit.com/adafruit-gfx-graphics-library?ref=theflightsimdev.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Adafruit GFX Graphics Library</div><div class="kg-bookmark-description">The Adafruit_GFX library for Arduino provides a common syntax and set of graphics functions for all of our LCD and OLED displays.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://learn.adafruit.com/assets/adafruit_favicon-d675616eb86845a7dba9e0f82b02bab9dbc195053d6f2adc8741e42ca351d2fe.svg" alt="12. Hello TFT LCD World"><span class="kg-bookmark-author">Adafruit Learning System</span><span class="kg-bookmark-publisher">Phillip Burgess</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn-learn.adafruit.com/guides/images/000/000/071/medium800/char.png" alt="12. Hello TFT LCD World"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[11. Say hello to my little friend, Teensy!]]></title><description><![CDATA[<p></p><h2 id="the-problem">The problem</h2><p>Up until this point, I&apos;ve been sharing code examples for the Arduino Leonardo/Micro. Arduinos are an &quot;ok&quot; starter microcontroller board, but eventually you will realize that it doesn&apos;t have the specs needed for building something other than a keypad, game controller</p>]]></description><link>https://www.theflightsimdev.com/11-say-hello-to-my-little-friend-teensy/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5f1</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Mon, 02 Sep 2024 04:00:00 GMT</pubDate><media:content url="https://www.theflightsimdev.com/content/images/2024/09/Male_Headers_Soldered_Teensy.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://www.theflightsimdev.com/content/images/2024/09/Male_Headers_Soldered_Teensy.jpg" alt="11. Say hello to my little friend, Teensy!"><p></p><h2 id="the-problem">The problem</h2><p>Up until this point, I&apos;ve been sharing code examples for the Arduino Leonardo/Micro. Arduinos are an &quot;ok&quot; starter microcontroller board, but eventually you will realize that it doesn&apos;t have the specs needed for building something other than a keypad, game controller or a switch/buttons box. Their processor speed is only 16 MHz and they only have 2 kB of SRAM and, 32 kB of Flash memory. Even worse, the Leonardo/Micro that I&apos;ve been using for these input devices really only has 28 kB of Flash memory. </p><p>When drawing complex graphics on a screen, we typically make use of a frame buffer to do graphics operations in memory before writing to the screen so we can have a high refresh rate. This is because writing to the screen is slow and we only want to update pixels as needed. Without a frame buffer, you pretty much have to rely on very simple graphics and remembering what you draw in the last frame so you can erase it and draw the new one (remember 70s and  80s flickering polygon graphics?)</p><figure class="kg-card kg-image-card"><img src="https://images.squarespace-cdn.com/content/v1/51b3dc8ee4b051b96ceb10de/1399220994243-6NG830FT2NM3J55DT644/retroscifi03-star_wars.jpg" class="kg-image" alt="11. Say hello to my little friend, Teensy!" loading="lazy" width="1241" height="992"></figure><p></p><h2 id="screen-resolution">Screen Resolution</h2><p>Small TFT LCD displays that are used in these types of projects usually come in a resolution of 320x240 pixels. That is 76,800 pixels each consisting of 3 bytes for the RGB color, for a total of 230,400 bytes or 225 kB of required RAM. Even the Minima is near max capacity just for a frame buffer, which won&apos;t leave us much room for other variables we would need to create, especially used in complex mathematical operations to draw simple polygons on a screen.</p><p></p><h2 id="the-alternatives">The Alternatives</h2><p>After doing some research across other microcontroller boards similar to Arduino, I looked some other popular boards that also work with the Arduino IDE and most of the existing community developed libraries. Here are some of them:</p><p></p><ol><li><strong>Arduino R4 Minima</strong></li></ol><figure class="kg-card kg-image-card"><img src="https://store.arduino.cc/cdn/shop/products/ABX00080_00.default_915d4754-8188-471c-aeb8-b3967aba76e5_934x700.jpg?v=1701256404" class="kg-image" alt="11. Say hello to my little friend, Teensy!" loading="lazy" width="933" height="700"></figure><p>The newest series in the Arduino line is the Arduino UNO R4 Minima. It has a 48 MHz processor and 256 kB of Flash and 32 kB of RAM which could work for our code but with 225 kB already dedicated to the frame buffer, we can easily run out of memory (which I did during my testing) while trying to calculate and draw complex shapes on the screen.</p><p></p><ol start="2"><li><strong>ESP32</strong></li></ol><figure class="kg-card kg-image-card"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/ESP32-C3_RISC-V_NodeMCU_board.jpg/220px-ESP32-C3_RISC-V_NodeMCU_board.jpg" class="kg-image" alt="11. Say hello to my little friend, Teensy!" loading="lazy" width="220" height="349"></figure><p>The ESP32&apos;s are very popular and cheap. There are so many variants that honestly it was a bit overwhelming deciding what to buy for experimenting. I finally ended up buying a set of 3 ESP-WROOM-32 boards for around $15 on Amazon, which even have WiFi and Bluetooth on board. While a very interesting development board, and so many ideas come to mind with these capabilities, they only have around 520 kB RAM and 448 kB Flash. That&apos;s more than the newest Arduino R4, but after trying to compile some code, I realized I didn&apos;t have access to all the memory and after doing some digging, it&apos;s due to some partitioning that the ESP32 supports and the Default partitioning only gives you like 1/3 of the memory and I didn&apos;t bother looking into how to reclaim all that memory or the consequences of doing so.</p><p></p><ol start="3"><li><strong>Teensy</strong></li></ol><h3 id><a href="https://www.pjrc.com/?ref=theflightsimdev.com" rel="noreferrer"></a></h3><p></p><p>Another board I came across was the Teensy.  Similar in size to the Arduino Micro, this board was created by Paul Stoffregen (<a href="https://www.pjrc.com/?ref=theflightsimdev.com" rel="noreferrer">PJRC.COM</a>), who is a big contributor to a lot of the Arduino libraries used by the Arduino community. Teensy originally used an ATMEGA AVR 8-bit processor similar to the Arduinos but eventually he moved to using Cortex M 32-bit and 64-bit processors. Their newest boards the Teensy 4.0 and 4.1 rock a Cortex-M7 processor running at 600 MHz that can be overclocked to 912 MHz, both have 1024 kB of RAM and the 4.0 has ~2 MB of Flash Memory, while 4.1 has ~8 MB, which for complex graphic processing is perfect. Teensy 4.0 has 40 pins, and 4.1 has 55 and even comes with an Ethernet Chip.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/teensy41_4.jpg" class="kg-image" alt="11. Say hello to my little friend, Teensy!" loading="lazy" width="772" height="224" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/teensy41_4.jpg 600w, https://www.theflightsimdev.com/content/images/2024/09/teensy41_4.jpg 772w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="conclusion">Conclusion</h2><p>After messing around with the latest Arduinos, the ESP32&apos;s and a few versions of the Teensy, I decided to use Teensy4.0 and 4.1 for the Flight Instruments I&apos;m building, because of the small form factor, number of pins, processor speed and RAM/Flash memory size.</p>]]></content:encoded></item><item><title><![CDATA[10. Serial I/O - Part 3]]></title><description><![CDATA[<p>Let&apos;s think about what data types. Remember the <code>SimVar</code> structure we created in Part 1 of the series, we created a <code>struct</code> with two <code>floats</code> that represent the <em>Altitude</em> and <em>Barometric Setting</em> of an altimeter.</p><pre><code class="language-csharp">private struct SimVars
{
  public float Altitude;            // INDICATED ALTITUDE (feet)
  public float KohlsmanSettingHg;   // KOHLSMAN</code></pre>]]></description><link>https://www.theflightsimdev.com/10-serial-communications-part-3/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c601</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Sun, 01 Sep 2024 04:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1603732551658-5fabbafa84eb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGFyZHVpbm98ZW58MHx8fHwxNzI1MzM2NDUzfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1603732551658-5fabbafa84eb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGFyZHVpbm98ZW58MHx8fHwxNzI1MzM2NDUzfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="10. Serial I/O - Part 3"><p>Let&apos;s think about what data types. Remember the <code>SimVar</code> structure we created in Part 1 of the series, we created a <code>struct</code> with two <code>floats</code> that represent the <em>Altitude</em> and <em>Barometric Setting</em> of an altimeter.</p><pre><code class="language-csharp">private struct SimVars
{
  public float Altitude;            // INDICATED ALTITUDE (feet)
  public float KohlsmanSettingHg;   // KOHLSMAN SETTING HG (inHG)
}</code></pre><p>In memory, Arduino&apos;s <code>float</code> and <code>double</code> are both 4-bytes objects, but our PC is 64-bit and C#&apos;s <code>double</code> is a 8-bytes object. Fortunately, <code>float</code> is 32-bits so we can register our SimVars as <code>SIMCONNECT_DATATYPE.FLOAT32</code> type to get the values as <code>floats</code> and send them directly to Arduino.</p><p></p><h2 id="changing-the-c-code-to-send-the-data-in-bytes">Changing the C# Code to send the data in bytes</h2><p>Let&apos;s change the <code>private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)</code> method. Instead of calling <code>serialPort.WriteLine(...)</code> we will be converting the values to byte arrays and sending them using <code>serialPort.Write(byte[] buffer, int offset, int count)</code>.</p><pre><code class="language-csharp">// comment out so we don&apos;t send it as a string anymore
//serialPort.WriteLine($&quot;{simvars.Altitude},{simvars.KohlsmanSettingHg}&quot;);

// first, create a buffer to put the data
List&lt;byte&gt; buffer = new();

// then lets add the two simvars. We will use BitConverter to get the floats as a byte array
buffer.AddRange(BitConverter.GetBytes(simvars.Altitude));
buffer.AddRange(BitConverter.GetBytes(simvars.KohlsmanSettingHg));

// now, lets add our newline to specify the end of our message
buffer.Add((byte)&apos;\n&apos;);

// finally, lets write the message to the serial port
serialPort.Write(buffer.ToArray(), 0, buffer.Count);</code></pre><p>That&apos;s it. We created a byte buffer, we converted both <code>SimVars</code> to byte arrays using the utility class <code>BitConverter</code>, while adding them to our buffer. We added our <code>newline</code> to specify the end of our message and we wrote the bytes to the serial port. Our message size should always be 9 bytes, that is:</p><ul><li>4 bytes - for the <em>Altitude</em></li><li>4 bytes - for the <em>Barometer Setting</em></li><li>1 byte - for the <em>Newline Character </em>(&apos;\n&apos;)</li></ul><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-1-2.png" class="kg-image" alt="10. Serial I/O - Part 3" loading="lazy" width="739" height="125" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-1-2.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-1-2.png 739w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="changing-the-arduino-sketch-to-read-and-convert-serial-bytes">Changing the Arduino Sketch to read and convert Serial bytes</h2><p>To read bytes from the serial port, we will be using the <code>Serial.readBytes(char* buffer, int length)</code>. We will then use <code>memcpy(float* value, char* buffer, int size)</code> to literally copy the 4 bytes we have in the buffer into the object&apos;s memory. This will reconstruct the <code>float</code> value we sent from the C# app. Finally we will send a message back to see if all of this worked:</p><pre><code class="language-arduino">void setup() {
  // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it&apos;s the same rate as in the C# code
  Serial.begin(115200);
}

float altimeter;
float barometer;

void loop() {
  // check if we have at least 8 bytes. our full message should be 9 bytes,
  if (Serial.available() &gt; 8) {
    altimeter = readFloat();
    barometer = readFloat();

    Serial.readStringUntil(&apos;\n&apos;);

    // send the data back through the serial connection
    Serial.print(&quot;Altitude: &quot;);
    Serial.print(altimeter);
    Serial.print(&quot;, Barometer: &quot;);
    Serial.println(barometer);
  }
}

// util method to read 4 bytes from the serial port and return them as a float
float readFloat() {
  char buffer[4];
  Serial.readBytes(buffer, 4);

  float value;
  memcpy(&amp;value, buffer, sizeof(float));

  return value;
}
</code></pre><p>Lets upload the Sketch to the Arduino, and press on the Connect button in the <code>Hello MSFS</code> app. We can see that the data is being sent correctly and the new message format is being displayed with the correct <em>Altimeter</em> and <em>Barometer Setting</em> values:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-2-1.png" class="kg-image" alt="10. Serial I/O - Part 3" loading="lazy" width="799" height="283" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-2-1.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-2-1.png 799w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="wrapping-things-up">Wrapping things up</h2><p>In Part 1 we learned about the <code>Serial Port</code>, how to send and receive data from and to the PC using C# code and an Arduino Sketch in C++. We learned how to identify our Arduino devices using PID/VID, which can be used to maintain a list of connected devices and send the <code>SimVar</code> data to them, but for sake of simplicity, just hardcoded my COM port in the examples to give the reader the option of adding that logic with the WMI query snippet provided.</p><p>In Part 2 we extended the code we wrote that uses SimConnect SDK to subscribe to <code>SimVars</code> and send them to an Arduino device using the <code>Serial Port</code> object as a string, and we verified that the data was received correctly by sending the same string back and printing it in the Debug output window. </p><p>In Part 3 we modified the code again to send the data in their own native type by using <code>BitConverter</code> to convert the <code>floats</code> to byte arrays, build a byte buffer with both Altitude and Barometer Setting <code>SimVars</code> byte arrays and our <code>newline</code> terminator, and modified the Arduino Sketch to read the data from the <code>Serial</code> connection as bytes and convert them back to <code>float</code> types and rebuilt our message and send it back to the <code>Hello MSFS</code> app to visually verify that the data was received and converted correctly.</p><p>Now you have the building blocks on how to build your own instruments based on the <code>SimVar</code> data from Microsoft Flight Simulator 2020, and hopefully you can start building some kickass instruments and build up your sim cockpit!</p>]]></content:encoded></item><item><title><![CDATA[9. Serial I/O - Part 2]]></title><description><![CDATA[<p>Expanding the code we used to send data from C# to Arduino in Part 1 of this series, let&apos;s define a <code>SerialPort</code> object and initialize it.</p><pre><code class="language-csharp">public partial class MainWindow : Window
{
  ...
  
  private SerialPort serialPort;

  ...

  private void InitializeSerialPort()
  {
    serialPort = new SerialPort(&quot;COM8&quot;, 115200);
  }
}</code></pre><blockquote>Note: My Arduino is</blockquote>]]></description><link>https://www.theflightsimdev.com/9-serial-io-part-2/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c600</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Sat, 31 Aug 2024 14:42:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1682971829405-42b40b5f0895?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQwfHxhcmR1aW5vfGVufDB8fHx8MTcyNDkwMzk2N3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1682971829405-42b40b5f0895?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQwfHxhcmR1aW5vfGVufDB8fHx8MTcyNDkwMzk2N3ww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="9. Serial I/O - Part 2"><p>Expanding the code we used to send data from C# to Arduino in Part 1 of this series, let&apos;s define a <code>SerialPort</code> object and initialize it.</p><pre><code class="language-csharp">public partial class MainWindow : Window
{
  ...
  
  private SerialPort serialPort;

  ...

  private void InitializeSerialPort()
  {
    serialPort = new SerialPort(&quot;COM8&quot;, 115200);
  }
}</code></pre><blockquote>Note: My Arduino is on COM8, so update the port to match yours.</blockquote><p>Next, let&apos;s try to open and close the serial port connection when we press on the Connect/Disconnect buttons:</p><pre><code class="language-csharp"> // Connect button event handler
 private void connectButton_Click(object sender, RoutedEventArgs e)
 {
     if (Process.GetProcessesByName(MSFS_PROCESS_NAME).Any())
     {
         try
         {
           ...
           
            // open the COM port
            InitializeSerialPort();

            // start out DispatcherTimer to start polling from the game
            simConnectDispatcherTimer.Start();

            ...
          }
          catch (Exception ex)
          {
            ...
          }
      }
  }

  // Disconnect button event handler
  private void disconnectButton_Click(object sender, RoutedEventArgs e)
  {
    ...

    // if port is open, close it
    if (serialPort.IsOpen)
    {
        serialPort.Close();
    }
}</code></pre><p>Finally, let&apos;s expand the <code>Simconnect_OnRecvSimobjectData</code> handler to send data:</p><pre><code class="language-csharp"> // called whenever we receive a new set of SimVar data
private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
{
    switch ((RequestType)data.dwRequestID)
    {
        case RequestType.PerFrameData:
          simvars = (SimVars)data.dwData[0];

          // send data to serial port if it&apos;s open
          if (serialPort.IsOpen)
          {
              try
              {
                  serialPort.WriteLine($&quot;{simvars.Altitude},{simvars.KohlsmanSettingHg}&quot;);
              }
              catch (Exception ex)
              {
                  Debug.WriteLine($&quot;Exception sending data to Arduino: {ex.Message}&quot;);
              }
          }
      
          altimeterTextBox.Text = $&quot;{simvars.Altitude:0.00}&quot;;
          barometerTextBox.Text = $&quot;{simvars.KohlsmanSettingHg:0.00}&quot;;
      
          break;

        default:
            Debug.WriteLine($&quot;Unsupported Request Type: {data.dwRequestID}&quot;);
            break;
    }
}</code></pre><p>We can use the same Arduino Sketch we used before:</p><pre><code class="language-arduino">void setup() {
  // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it&apos;s the same rate as in the C# code
  Serial.begin(115200);
}

void loop() {
  if (Serial.available() &gt; 0) {
    // read all data until end of line
    String data = Serial.readStringUntil(&apos;\n&apos;);

    // send the data back through the serial connection
    Serial.println(data);
  }
}</code></pre><p></p><h2 id="arduino-serial-monitor">Arduino Serial Monitor</h2><p>If we compile and upload this Sketch to our Arduino before running the <code>Hello MSFS</code> app, we can open the <em>Tools&#x2013;&gt;Serial Monitor</em> window in the Arduino IDE, set the BAUD to 115200, and type anything and hit enter. The sketch should echo what we just we wrote back to us. Now, make sure MSFS is running and run the <code>Hello MSFS</code> app and click on connect. As soon as it connects to the sim and tries to send data to the Arduino, we&apos;ll see the following exception:</p><pre><code class="language-log">22:36:04:570	Exception thrown: &apos;System.UnauthorizedAccessException&apos; in System.IO.Ports.dll
22:36:04:570	Access to the path &apos;COM8&apos; is denied.</code></pre><p>So what happened? Since we still have the Arduino IDE Serial Monitor open, it already has COM8 opened and Windows won&apos;t let us open it a second time, so let&apos;s close the Serial Monitor window and try one more time. Click Disconnect and then Connect and now we don&apos;t get the exception but we don&apos;t have a way of knowing what was actually received on the Arduino. We are sending data back from the Arduino to the PC, but our app is not listening to data coming in from the COM8 port. </p><blockquote>Note: If we would try to open the Serial Monitor again while our app is running and connected, we will get into a similar error in the Arduino IDE since, it won&apos;t be able to open the port because our app already has it opened.</blockquote><p></p><h2 id="datareceived-event-handler">DataReceived Event Handler</h2><p>So how do we verify that the data received by the Arduino is correct? We also did this in the previous post. Let&apos;s add some code to subscribe to the <code>DataReceived</code> event via a custom handler. By modifying the <code>InitializeSerialPort</code> method we created earlier, we can define our custom event handler:</p><pre><code class="language-csharp">private void InitializeSerialPort()
{
    serialPort = new SerialPort(&quot;COM8&quot;, 115200)
    {
        ReadTimeout = 1000,
        WriteTimeout = 1000,
        DtrEnable = true,
        RtsEnable = true,
        NewLine = Environment.NewLine,
    };

    // attach an event handler
    serialPort.DataReceived += SerialPort_DataReceived;
}

private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        // Read data from the serial port until NewLine is found and trim the newline character(s)
        var dataReceived = serialPort.ReadLine().Trim();

        Debug.WriteLine($&quot;Received: {dataReceived}&quot;);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(&quot;Error while receiving data: &quot; + ex.Message);
    }
}</code></pre><p>Notice a few new extra parameters while creating the <code>SerialPort</code> object:</p><ol><li><em>ReadTimeout/WriteTimeout</em> &#x2013; setting these to one second. The default is -1 which blocks forever and we don&apos;t want that</li><li><em>DtrEnable/RtsEnable</em> &#x2013; Enabling Data Terminal Ready and Request To Send flags. For some reason the <code>DataReceived</code> Handler doesn&apos;t work without these on, and haven&apos;t found where it&apos;s documented that they are needed.</li><li><em>NewLine</em> &#x2013; We are setting this to &apos;\r\n&apos; on Windows to match Arduino&apos;s <code>Serial.print()</code> behavior. See the <a href="https://www.arduino.cc/reference/en/language/functions/communication/serial/println/?ref=theflightsimdev.com" rel="noreferrer">documentation</a> for more details.</li></ol><p>With all this in place, we can now run our app again. First make sure Arduino&apos;s Serial Monitor is not open, and Debug the app and click on Connect. This time we&apos;ll be both displaying the <code>SimVar</code> values we are getting from the sim in the UI and sending them to the Arduino device as a string. The string with our <code>SimVars</code> does the round robin trip back and the <code>DataReceived</code> handler will read the string and print it on the Debug window:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-9.png" class="kg-image" alt="9. Serial I/O - Part 2" loading="lazy" width="668" height="251" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-9.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-9.png 668w"></figure><p></p><h2 id="a-note-on-message-format">A note on message format</h2><p>One thing worth mentioning, for the sake of simplicity, our communications rely on sending the data as a string using the <code>newline</code> character to determine when a &quot;message&quot; ends. We send from the C# app using <code>serialPort.WriteLine()</code>, we read in the Arduino Sketch using <code>Serial.readStringUntil(&apos;\n&apos;)</code>, then we send it back using <code>Serial.println()</code> and read it in the C# app using <code>serialPort.ReadLine()</code>.  You really want to be able to send other data types besides string, since you don&apos;t want to have to do a lot of string parsing in Arduino when you can send <code>floats</code> and <code>ints</code> in byte format and convert them directly to their corresponding types on the Sketch. There are many existing standards and protocols used in Serial Communications that can do this, but they are out of scope for what I wanted to achieve here.</p><p></p><h2 id="conclusion">Conclusion</h2><p>In part 2 of this 3 part series, we modified the code we created on part 1 that received data from Microsoft Flight Simulator and send it down to an Arduino device connected to the same PC where the <code>Hello MSFS</code> app is running. We simplified the process by sending a <code>newline</code> delimited string message and we verified that the data was received by implementing the <code>DataReceived</code> handler to print out the same data we sent to the Arduino.</p><p>In the last part of this series, we&apos;ll modify the code to send the two <code>SimVars</code> as <code>floats</code> and then storing them on some variables in the Arduino Sketch.</p>]]></content:encoded></item><item><title><![CDATA[8. Serial I/O- Part 1]]></title><description><![CDATA[<p>Let&apos;s continue thinking about building our Altimeter device. Now that we have the <em>altimeter</em> and <em>barometer</em> data from the sim, lets see what we need to be able to write a sketch to send the two <code>SimVars</code> to an Arduino and display them somehow.</p><p></p><h2 id="the-obvious-questions">The obvious questions</h2><p>So.</p>]]></description><link>https://www.theflightsimdev.com/8-serial-io-part-1/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5ee</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Sat, 31 Aug 2024 02:35:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1631378297854-185cff6b0986?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEyfHxhcmR1aW5vfGVufDB8fHx8MTcyNDkwMzk1Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1631378297854-185cff6b0986?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEyfHxhcmR1aW5vfGVufDB8fHx8MTcyNDkwMzk1Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="8. Serial I/O- Part 1"><p>Let&apos;s continue thinking about building our Altimeter device. Now that we have the <em>altimeter</em> and <em>barometer</em> data from the sim, lets see what we need to be able to write a sketch to send the two <code>SimVars</code> to an Arduino and display them somehow.</p><p></p><h2 id="the-obvious-questions">The obvious questions</h2><p>So... how do we send data from Windows to an Arduino via USB? But more important, how do we know out of all the USB devices connected to the PC, which one&apos;s our Arduino? It&apos;s pretty simple and I already talked about in my post about <a href="https://www.theflightsimdev.com/2-arduino-joystick-emulation/" rel="noreferrer">Arduino Joystick Emulation</a>. We can use the device&apos;s PID/VID to identify them.</p><p></p><h2 id="what-is-a-pidvid">What is a PID/VID?</h2><p>The Product ID (or PID) and the Vendor ID (or VID) are unique identifiers used in USB devices to identify the manufacturer and their specific product. For Arduino devices, the VID is 2341, and the PID depends on the Arduino board. For example:</p><table>
<thead>
<tr>
<th>Product</th>
<th>PID</th>
<th>VID</th>
</tr>
</thead>
<tbody>
<tr>
<td>Arduino UNO</td>
<td>0043</td>
<td>2341</td>
</tr>
<tr>
<td>Arduino Leonardo</td>
<td>8036</td>
<td>2341</td>
</tr>
<tr>
<td>Arduino Micro</td>
<td>8037</td>
<td>2341</td>
</tr>
<tr>
<td>TEENSY</td>
<td>0483</td>
<td>16C0</td>
</tr>
</tbody>
</table>
<blockquote>Note: these are the PID/VID values defined in <code>C:\Program Files (x86)\Arduino\hardware\arduino\avr\boards.txt</code></blockquote><p></p><h2 id="how-do-we-query-usb-devices-in-net">How do we query USB devices in .NET?</h2><p>Unfortunately, there is no &quot;native&quot; .NET way of querying that data for USB devices so you have to use some other API like a <a href="https://en.wikipedia.org/wiki/Windows_Management_Instrumentation?ref=theflightsimdev.com" rel="noreferrer">Windows Management Instrumentation</a> (or WMI) query and parse the results. Here is an example code snippet with the general idea of how to do it:</p><pre><code class="language-csharp">using System.Management;

var searcher = new ManagementObjectSearcher(&quot;select DeviceID, PNPDeviceID, MaxBaudRate from win32_serialport&quot;);

var devices = searcher.Get().Cast&lt;ManagementBaseObject&gt;().ToList();

// print all devices
foreach (var d in devices)
{
  // this contains the COM port
  var deviceId = (string)d[&quot;DeviceID&quot;];

  // this contains the PID?VID
  var pnpDeviceId = (string)d[&quot;PNPDeviceID&quot;];

  // this contains the Max Baud Rate you can use to talk to the device
  var maxBaudRate = (uint)d[&quot;MaxBaudRate&quot;];

  Debug.WriteLine($&quot;DeviceID: {deviceId}, PNPDeviceId: {pnpDeviceId}, MaxBaudRate: {maxBaudRate}&quot;);
}

// you can write a lambda to filter by your supported devices based on the PNPDeviceID</code></pre><p>Once we have built a list of connected devices that we can talk to, we can now write some C# code to open a Serial connection to a device and send our data.</p><p></p><h2 id="about-serial-ports">About Serial Ports</h2><p>A <a href="https://en.wikipedia.org/wiki/Serial_port?ref=theflightsimdev.com" rel="noreferrer">serial port</a> is a communication interface where information is transmitted sequentially <em>one bit at a time.</em> This is the interface used historically for communications between computers and peripherals like modems, keyboards, mice, terminals, back in the day before high speed USB, Ethernet, etc. Devices that support serial port communications are typically associated with port name and their speed, plus other data integrity validation parameters like data/stop bits and parity.</p><p>Although all these parameters must match on both sides to be able to communicate properly, we are going to focus on two things, the port name and the speed. When connecting to an Arduino, these typically look like &quot;COM3&quot;, &quot;COM5&quot; for the port name, and values like 115200, 9600, 38400 for the speed.</p><p></p><h2 id="sending-data-from-c-using-serialport">Sending data from C# using SerialPort</h2><p>We can use the SerialPort class to send and receive data to/from an Arduino. We&apos;ll need two things, it&apos;s COM port and the speed. To find the COM port name manually, we can go to Device Manager and find our Arduino device and see what port it is using; alternatively we can open the Arduino IDE and find it there.</p><p>Let&apos;s look at some code:</p><pre><code class="language-csharp">// initialize the SerialObject, assuming our Arduino is on COM3 and we can send data at 115200. If not, try a lower setting like 9600 which is commonly seen in older Arduino example Sketches
var serialPort = new SerialPort(&quot;COM3&quot;, 115200);

// try to open the port
try
{
  serialPort.Open();

  // send some data
  serialPort.WriteLine(&quot;Hello Serial World!&quot;);
}
catch (Exception e)
{
  Debug.WriteLine(e.Message);
}
finally
{
  // close the serial port
  if (serialPort.IsOpen)
  {
    serialPort.Close();
  }
}</code></pre><p></p><h2 id="receiving-data-in-c-using-serialport">Receiving data in C# using SerialPort</h2><p>For receiving data, we need to define a <code>DataReceived</code> event handler which is triggered whenever data comes to the serial port. Example:</p><pre><code class="language-csharp">// initialize the SerialObject, assuming our Arduino is on COM3 and we can send data at 115200. If not, try a lower setting like 9600 which is commonly seen in older Arduino example Sketches
var serialPort = new SerialPort(&quot;COM3&quot;, 115200);

// attach an event handler
serialPort.DataReceived += SerialPort_DataReceived;

// try to open the port
try
{
  serialPort.Open();

  while(true)
  {
    Thread.Sleep(1000);
  }
}
catch (Exception e)
{
  Debug.WriteLine(e.Message);
}
finally
{
  // close the serial port
  if (serialPort.IsOpen)
  {
    serialPort.Close();
  }
}

private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  try
  {
    // Read data from the serial port
    var dataReceived = serialPort.ReadLine(); // Reads until a newline character is found
    // Alternatively, use serialPort.ReadExisting() to read all available data as a string
            
    Debug.WriteLine(dataReceived);
  }
  catch (Exception ex)
  {
    Debug.WriteLine(&quot;Error while receiving data: &quot; + ex.Message);
  }
}</code></pre><p></p><h2 id="sending-and-receiving-data-from-arduino">Sending and Receiving data from Arduino</h2><p>Arduino has a Serial class used serial port communications which is pretty straightforward to use. All Arduinos have at least one Serial port, but some can have Serial2 and Serial3 for additional I/O. Example:</p><pre><code class="language-arduino">void setup() {
  // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it&apos;s the same rate as in the C# code
  Serial.begin(115200);
}

void loop() {
  if (Serial.available() &gt; 0) {
    // read all data until end of line
    String data = Serial.readStringUntil(&apos;\n&apos;);

    Serial.print(&quot;Received: &quot;);
    Serial.println(data);
  }
}</code></pre><p></p><h2 id="conclusion">Conclusion</h2><p>.NET and Arduino both provide simple classes to communicate via serial port. In the examples above, we are sending and receiving simple string messages as a proof of concept, but we can easily expand these to send other data types. The catch is that we have to send and receive them in bytes and convert them to the corresponding types after receiving them.</p><p>Alternatively, there are existing standardized protocols that are supported in Arduino that we could use to implement more robust communications instead of simple raw byte communications, but with the added overhead that could impact performance, but it&apos;s worth looking into in the future.</p>]]></content:encoded></item><item><title><![CDATA[7. Thinking about interaction]]></title><description><![CDATA[<p>Something I have to start thinking about at this point is how will I interact with these screens. All the ones I purchased for experimenting have touch capabilities. Some are capacitive, some are resistive. Both have their differences, so should I go in that direction? On one hand they all</p>]]></description><link>https://www.theflightsimdev.com/7-thinking-about-interaction/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5ff</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Fri, 30 Aug 2024 02:17:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1509541206217-cde45c41aa6d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIyfHxjb250cm9sc3xlbnwwfHx8fDE3MjQ5MDQzMTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1509541206217-cde45c41aa6d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIyfHxjb250cm9sc3xlbnwwfHx8fDE3MjQ5MDQzMTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="7. Thinking about interaction"><p>Something I have to start thinking about at this point is how will I interact with these screens. All the ones I purchased for experimenting have touch capabilities. Some are capacitive, some are resistive. Both have their differences, so should I go in that direction? On one hand they all already come with it, but then there&apos;s about 4-5 more connections I need to solder on each screen for touch, and from the tests I&apos;ve done... it&apos;s a bit finicky.</p><p>But what exactly do I want to interact with and how? For starters if I&apos;m going to build a device that supports multiple instruments, I need a way to selecting which one I want to display. Is <em>touch</em> really a good choice for something like this? And, if we look at the instruments in a typical cockpit, most of them have knobs and buttons.</p><p></p><h2 id="alternatives">Alternatives</h2><ol><li>Touch:<ol><li>I&apos;ll be drawing a lot of stuff on tiny 3.2&quot; screens, so I don&apos;t have a lot of room to add buttons. I played around with the idea of having the screen divided into 4 sections so I could use those as buttons, but for things like increasing/decreasing a heading bug on a Heading Indicator, a barometric pressure on an Altimeter, or an OBS on a VOR, it&apos;s unpractical.</li><li>Pros: <ol><li>Already comes with the device</li></ol></li><li>Cons:<ol><li>Extra wiring</li><li>Finicky and unpractical code</li></ol></li></ol></li><li>Push Buttons<ol><li>I tried adding two buttons to each screen for going back and forth along the screens, but that was about it. I even 3D printed an oval shape cap for the 2 buttons that pretty much looked like the buttons on a DVD player for fwd/back that looked really good but I felt was only solving half of the problem.</li><li>Pros:<ol><li>They are pretty straightforward to add and implement</li><li>They are more reliable than touch on these type of devices</li></ol></li><li>Cons:<ol><li>Only solved half of the problem</li></ol></li></ol></li><li>Rotary Encoders<ol><li>I messed around with the idea of a rotary encoder or two. Usually they come with an integrated push button so between two knobs and two buttons I would have more than enough inputs for the instruments I want to design</li><li>Pros:<ol><li>With the rotary encoder, I could control different values that I could increment and decrement</li><li>I also had available 2 buttons to assign to anything I needed</li></ol></li><li>Cons:<ol><li>There was really not a lot of room to put two rotary encoders on these small scale screens I&apos;m designing, especially when everything is built and soldered by hand and with off shelf components.</li></ol></li></ol></li><li>Dual Rotary Encoder<ol><li>Pretty much the same as having two rotary encoders but in the space occupied by just one, but with the trade-off of only having one button instead of two.</li><li>Pros:<ol><li>Same as two rotary encoders plus one is easier to mount and solder than two.</li></ol></li><li>Cons:<ol><li>They are a bit more expensive than regular rotary encoders</li></ol></li></ol></li></ol><p></p><h2 id="the-choice">The Choice</h2><p>After a lot of experimentation and testing, I decided to go with the dual rotary encoders. The extra cost is worth the simplicity and reduced space. I went with this one from <a href="https://www.digikey.com/en/products/detail/bourns-inc/118-PEC11D-4120F-H0015-ND/15926291?utm_adgroup=&amp;utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=Pmax_Shopping_Boston%20Metro%20Category%20Awareness&amp;utm_term=&amp;utm_content=&amp;utm_id=go_cmp-20837509568_adg-_ad-__dev-c_ext-_prd-15926291_sig-CjwKCAjwlbu2BhA3EiwA3yXyuxBm393fEFc1ajtrA-84IfPuOqnfTFMyOghpApx_0mnvkKesLZSbrxoC5HIQAvD_BwE&amp;gad_source=1&amp;gclid=CjwKCAjwlbu2BhA3EiwA3yXyuxBm393fEFc1ajtrA-84IfPuOqnfTFMyOghpApx_0mnvkKesLZSbrxoC5HIQAvD_BwE" rel="noreferrer">Digikey</a> and did not disappoint.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-19.png" class="kg-image" alt="7. Thinking about interaction" loading="lazy" width="640" height="640" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-19.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-19.png 640w"></figure>]]></content:encoded></item><item><title><![CDATA[6. Choosing the right display]]></title><description><![CDATA[<p></p><h2 id="design-restriction">Design restriction</h2><p>Remember back when I talked about measuring the 3U bracket that comes with the Logitech Multi Panel display, and designing a 2U bracket for my Switch Box that would be compatible with Logitech&apos;s and the yokes out there? I also ended up designing a 1U version</p>]]></description><link>https://www.theflightsimdev.com/6-choosing-the-right-display/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5ed</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Thu, 29 Aug 2024 02:34:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1724120932030-d8210a77deed?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE3fHxxdWVzdGlvbnxlbnwwfHx8fDE3MjQ4ODkyMjF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1724120932030-d8210a77deed?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE3fHxxdWVzdGlvbnxlbnwwfHx8fDE3MjQ4ODkyMjF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="6. Choosing the right display"><p></p><h2 id="design-restriction">Design restriction</h2><p>Remember back when I talked about measuring the 3U bracket that comes with the Logitech Multi Panel display, and designing a 2U bracket for my Switch Box that would be compatible with Logitech&apos;s and the yokes out there? I also ended up designing a 1U version of it so whatever I choose to use for a display has to fit in that space.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.theflightsimdev.com/content/images/2024/09/1U-Bracket.png" class="kg-image" alt="6. Choosing the right display" loading="lazy" width="1000" height="563" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/1U-Bracket.png 600w, https://www.theflightsimdev.com/content/images/2024/09/1U-Bracket.png 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">1U Bracket (94mm x 87.25mm)</span></figcaption></figure><p>The screen I choose has to fit inside of the 94mm x 87.25mm 1U bracket. I started looking around to see what screens were typically used with Arduinos, and ended up finding that 2.8&quot; and 3.2&quot; TFT screens that are driven by the ILI9341 controller are widely used, and there is a wide variety of graphics libraries that support them. Some even have touch capabilities, if we wanted to go in that direction. These are a few I screens experimented with:</p><p></p><h2 id="adafruit-28-tft-touch-shield-for-arduino"><a href="https://www.adafruit.com/product/1651?ref=theflightsimdev.com" rel="noreferrer">Adafruit 2.8&quot; TFT Touch Shield for Arduino</a></h2><figure class="kg-card kg-image-card"><img src="https://cdn-shop.adafruit.com/970x728/1651-06.jpg" class="kg-image" alt="6. Choosing the right display" loading="lazy" width="970" height="728"></figure><ol><li>Pros:<ol><li>Comes in a Shield factor, so you just have to mount it on top of an Arduino</li><li>Already has libraries made by Adafruit for drawing and with tutorials on how to use them</li><li>Has capacitive touch</li><li>Has an SD Card slot</li><li>Works with 3.3V and 5V signals</li></ol></li><li>Cons:<ol><li>The Shield form factor limits you on what devices you can use it with.</li><li>Only supports Arduino Uno/Leonardo/Mega, unless you want to get creative and modify the library for your device</li><li>Costs about $25 each (I believe it was around $45 when I bought mine)</li><li>It&apos;s a bit too small and there&apos;s definitely room for a bigger screen.</li></ol></li></ol><p></p><h2 id="adafruit-32-tft-lcd-with-touchscreen"><a href="https://www.adafruit.com/product/1743?ref=theflightsimdev.com" rel="noreferrer">Adafruit 3.2&quot; TFT LCD with Touchscreen</a></h2><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/1743-07.jpg" class="kg-image" alt="6. Choosing the right display" loading="lazy" width="970" height="728" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/1743-07.jpg 600w, https://www.theflightsimdev.com/content/images/2024/09/1743-07.jpg 970w" sizes="(min-width: 720px) 720px"></figure><ol><li>Pros:<ol><li>Comes in a breakout board so you can solder cables directly to it</li><li>Already has libraries made by Adafruit for drawing and with tutorials on how to use them</li><li>Can be wired to use SPI or 8-bit mode, for even faster data speeds.</li><li>Has capacitive touch</li><li>Has an SD Card slot</li></ol></li><li>Cons:<ol><li>8-bit mode has very limited support</li><li>8-bit mode requires 8 lines</li><li>Costs around $35 each</li></ol></li></ol><p></p><h2 id="pjrc-color-320x240-tft-display-ili9341-controller-chip"><a href="https://www.pjrc.com/store/display_ili9341_touch.html?ref=theflightsimdev.com" rel="noreferrer">PJRC Color 320x240 TFT Display, ILI9341 Controller Chip</a></h2><figure class="kg-card kg-image-card"><img src="https://www.pjrc.com/store/display_ili9341_touch.jpg" class="kg-image" alt="6. Choosing the right display" loading="lazy" width="1024" height="580"></figure><ol><li>Pros:<ol><li>Comes in 2.8&quot; and 3.2&quot; sizes</li><li>Has a Teensy Optimized ILI9341 Library to control it</li><li>Costs about $16 for the 2.8&quot; and $18 for the 3.2&quot;, although you can find similar ones in Ali Express for half than, if you can wait the 3 weeks shipping</li></ol></li><li>Cons:<ol><li>Resistive Touch</li><li>Comes with an SD Card slot but it&apos;s not supported natively</li><li>Limited to 3.3V signals and 5V will fry it.</li><li>Comes with the pins pre-soldered so you have to de-solder them if you want to solder wires to it</li><li>You need a 100 ohm resistor for the LED connection from VIN</li></ol></li></ol><p></p><h2 id="aliexpress-32-320x240-tft"><a href="https://www.aliexpress.us/item/3256802818901781.html?spm=a2g0o.order_list.order_list_main.20.65821802CU4BGm&amp;gatewayAdapt=glo2usa&amp;ref=theflightsimdev.com" rel="noreferrer">AliExpress 3.2&quot; 320x240 TFT</a></h2><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/S18d6b8abf53449c0be5a41dfbcad70caT-1.webp" class="kg-image" alt="6. Choosing the right display" loading="lazy" width="800" height="800" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/S18d6b8abf53449c0be5a41dfbcad70caT-1.webp 600w, https://www.theflightsimdev.com/content/images/2024/09/S18d6b8abf53449c0be5a41dfbcad70caT-1.webp 800w" sizes="(min-width: 720px) 720px"></figure><ol><li>Pros:<ol><li>Pretty similar to PJRC&apos;s version</li><li>Works with the same PRJC Optimized ILI9341 library</li><li>Comes with touch or no touch</li><li>Has an SD Card slot</li><li>Doesn&apos;t require the 100 ohm resistor</li></ol></li><li>Cons:<ol><li>You wire the LED pin to 3.3V, although wiring to 5V with a 100 ohm like PJRC does work too but the screen too bright so you need a bigger one.</li><li>Comes with the pins pre-soldered so you have to de-solder them if you want to solder wires to it</li></ol></li></ol><p></p><h2 id="and-the-winner-is">And the winner is?</h2><p>After getting a few of these, I initially ended up going with the PJRC 320x240 TFT screen for my first batch of instruments, and eventually moved to the cheap ones from AliExpress. These do have a small difference in how you wire them, and the screen is brighter but besides that hardware difference, there is no difference when it comes to writing code for it.</p>]]></content:encoded></item><item><title><![CDATA[5. Reading SimVar data with SimConnect SDK]]></title><description><![CDATA[<h2 id="prerequisites-for-developing-with-the-msfs-sdk">Prerequisites for Developing with the MSFS SDK</h2><p>Before we start developing with the MSFS SDK, there are a few things you&#x2019;ll need to have installed:</p><ol><li><strong>Microsoft Flight Simulator 2020:</strong> Ensure you have a copy of MSFS 2020 installed on your machine.</li><li><strong>Enable Developer Mode:</strong> While not strictly necessary</li></ol>]]></description><link>https://www.theflightsimdev.com/5-reading-simvar-data-with-simconnect/</link><guid isPermaLink="false">66d793f30c89fa08c6b3c5ec</guid><dc:creator><![CDATA[Luis Chardon]]></dc:creator><pubDate>Wed, 28 Aug 2024 02:34:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1529078155058-5d716f45d604?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE3fHxkYXRhfGVufDB8fHx8MTcyNDkwNDI1Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="prerequisites-for-developing-with-the-msfs-sdk">Prerequisites for Developing with the MSFS SDK</h2><img src="https://images.unsplash.com/photo-1529078155058-5d716f45d604?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE3fHxkYXRhfGVufDB8fHx8MTcyNDkwNDI1Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="5. Reading SimVar data with SimConnect SDK"><p>Before we start developing with the MSFS SDK, there are a few things you&#x2019;ll need to have installed:</p><ol><li><strong>Microsoft Flight Simulator 2020:</strong> Ensure you have a copy of MSFS 2020 installed on your machine.</li><li><strong>Enable Developer Mode:</strong> While not strictly necessary for using the SDK, enabling Developer Mode within the simulator is recommended. This mode allows you to download the SDK directly from within the sim and provides access to additional tools that will be useful throughout the development process, which I&#x2019;ll cover below.</li></ol><blockquote>Note: when you install the SDK it goes under your <code>C:\MSFS SDK</code> folder, so keep that in mind if  you install it elsewhere.</blockquote><ol start="3"><li><strong>Microsoft Visual Studio 2022</strong>. You can download the free Community version <a href="https://visualstudio.microsoft.com/vs/community/?ref=theflightsimdev.com" rel="noopener">here</a>. During installation, make sure to select the <strong>.NET desktop development</strong> workload, as this will include everything you need to develop WPF applications. Once installed, you&#x2019;re ready to move on to the next step.</li></ol><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-17.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="1271" height="719" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-17.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/image-17.png 1000w, https://www.theflightsimdev.com/content/images/2024/09/image-17.png 1271w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="setting-up-your-project">Setting up your project</h2><p>Lets proceed by creating a new project. A <em>.NET WPF</em> Application is ideal for what I want to achieve. However, if you prefer, you can write a<em> Console Application</em> and most of the steps this tutorial still applies.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-4.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="1014" height="675" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-4.png 600w, https://www.theflightsimdev.com/content/images/size/w1000/2024/09/image-4.png 1000w, https://www.theflightsimdev.com/content/images/2024/09/image-4.png 1014w" sizes="(min-width: 720px) 720px"></figure><p>After completing the &quot;Create New Project&quot; wizard in Visual Studio, the first thing you should do is configure your project file. To do this:</p><ol><li>In the Solution Explorer, right-click on your project&#x2019;s name.</li><li>Select <strong>Edit Project File</strong>.</li></ol><p>This allows you to manually adjust the project settings, add dependencies, and make other configurations necessary for integrating the MSFS SDK.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-13.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="428" height="469"></figure><ol start="3"><li>To properly integrate the MSFS SDK, you need to reference the SimConnect library in your project. Add the following two <code>&lt;ItemGroup&gt;</code> entries in lines 12 through 24 to your project file to include <code>SimConnect.dll</code> for both build and runtime:</li></ol><figure class="kg-card kg-code-card"><pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;

    &lt;PropertyGroup&gt;
        &lt;OutputType&gt;WinExe&lt;/OutputType&gt;
        &lt;TargetFramework&gt;net6.0-windows7.0&lt;/TargetFramework&gt;
        &lt;Nullable&gt;enable&lt;/Nullable&gt;
        &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
        &lt;UseWPF&gt;true&lt;/UseWPF&gt;
    &lt;/PropertyGroup&gt;
    
    &lt;!-- Add this reference for build and intellisense --&gt;
    &lt;ItemGroup&gt;
        &lt;Reference Include=&quot;Microsoft.FlightSimulator.SimConnect&quot;&gt;
            &lt;HintPath&gt;C:\MSFS SDK\SimConnect SDK\lib\managed\Microsoft.FlightSimulator.SimConnect.dll&lt;/HintPath&gt;
        &lt;/Reference&gt;
    &lt;/ItemGroup&gt;
    
    &lt;!-- Add this so the dll gets copied to the project output directory for runtime --&gt;
    &lt;ItemGroup&gt;
        &lt;Content Include=&quot;C:\MSFS SDK\SimConnect SDK\lib\SimConnect.dll&quot;&gt;
            &lt;Link&gt;SimConnect.dll&lt;/Link&gt;
            &lt;CopyToOutputDirectory&gt;Always&lt;/CopyToOutputDirectory&gt;
        &lt;/Content&gt;
    &lt;/ItemGroup&gt;
&lt;/Project&gt;</code></pre><figcaption><p><span style="white-space: pre-wrap;">HelloMSFS.csproj</span></p></figcaption></figure><blockquote>Note: If you installed the SDK in a different location, update these paths accordingly.</blockquote><p></p><h2 id="build-an-altimeter-kinda">Build an Altimeter... kinda...</h2><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-14.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="421" height="429"></figure><p>Let&#x2019;s start by building a simple instrument: the Altimeter. For this, we need to read two values from the simulator: the indicated altitude and the barometric pressure setting. By consulting the <a href="https://docs.flightsimulator.com/html/Programming_Tools/SimVars/Simulation_Variables.htm?ref=theflightsimdev.com" rel="noreferrer">Simulation Variables documentation</a>, we can identify the specific SimVars that will provide these values:</p><table>
<thead>
<tr>
<th>SimVar</th>
<th>Descritpion</th>
<th>Units</th>
</tr>
</thead>
<tbody>
<tr>
<td>INDICATED ALTITUDE</td>
<td>The indicated altitude.</td>
<td>feet</td>
</tr>
<tr>
<td>KOHLSMAN SETTING HG</td>
<td>The value for the given altimeter index in inches of mercury.</td>
<td>Inches of Mercury, inHg</td>
</tr>
</tbody>
</table>
<p>Now, let&#x2019;s build a simple UI for our Altimeter. Open <code>MainWindow.xaml</code> and add the following XAML code to create a basic layout for displaying the altitude and barometric pressure:</p><figure class="kg-card kg-code-card"><pre><code class="language-xml">&lt;Window x:Class=&quot;HelloMSFS.MainWindow&quot;
        xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
        xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
        xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
        xmlns:local=&quot;clr-namespace:HelloMSFS&quot;
        mc:Ignorable=&quot;d&quot;
        Title=&quot;Hello MSFS&quot; SizeToContent=&quot;WidthAndHeight&quot; Topmost=&quot;True&quot;&gt;
    &lt;Grid Margin=&quot;20&quot;&gt;
        &lt;Grid.ColumnDefinitions&gt;
            &lt;ColumnDefinition MinWidth=&quot;100&quot;/&gt;
            &lt;ColumnDefinition MinWidth=&quot;100&quot;/&gt;
        &lt;/Grid.ColumnDefinitions&gt;

        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
            &lt;RowDefinition MinHeight=&quot;25&quot;/&gt;
        &lt;/Grid.RowDefinitions&gt;

        &lt;Label x:Name=&quot;label&quot; Content=&quot;Altimeter:&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; /&gt;
        &lt;Label x:Name=&quot;label1&quot; Content=&quot;Barometer:&quot; Grid.Row=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot;/&gt;
        
        &lt;TextBox x:Name=&quot;altimeterTextBox&quot; TextWrapping=&quot;Wrap&quot; Text=&quot;1000&quot; Grid.Column=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; IsReadOnly=&quot;True&quot;/&gt;
        &lt;TextBox x:Name=&quot;barometerTextBox&quot; TextWrapping=&quot;Wrap&quot; Text=&quot;29.92&quot; Grid.Row=&quot;1&quot; Grid.Column=&quot;1&quot; VerticalContentAlignment=&quot;Center&quot; Margin=&quot;5&quot; IsReadOnly=&quot;True&quot;/&gt;
        
        &lt;Button x:Name=&quot;connectButton&quot; Content=&quot;Connect&quot; Grid.Row=&quot;2&quot; Click=&quot;connectButton_Click&quot; /&gt;
        &lt;Button x:Name=&quot;disconnectButton&quot; Content=&quot;Disconnect&quot; Grid.Row=&quot;2&quot; Grid.Column=&quot;1&quot; Click=&quot;disconnectButton_Click&quot; IsEnabled=&quot;False&quot;/&gt;
    &lt;/Grid&gt;
&lt;/Window&gt;
</code></pre><figcaption><p><span style="white-space: pre-wrap;">MainWindow.xaml</span></p></figcaption></figure><p>This should create a simple text based altimeter like this:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-15.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="778" height="497" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-15.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-15.png 778w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="implementing-the-simconnect-code">Implementing the SimConnect Code</h2><p>With the UI in place, it&#x2019;s time to add the functionality. Open <code>MainWindow.xaml.cs</code> and include the following C# code to connect to the simulator and update the UI with the Altimeter data:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using Microsoft.FlightSimulator.SimConnect;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Threading;

namespace HelloMSFS
{
    /// &lt;summary&gt;
    /// Interaction logic for MainWindow.xaml
    /// &lt;/summary&gt;
    public partial class MainWindow : Window
    {
        private const uint WM_USER_SIMCONNECT = 0x0402;                 //
        private const string MSFS_PROCESS_NAME = &quot;FlightSimulator&quot;;     // this is MSFS&apos;s process name
        private const string PLUGIN_NAME = &quot;Hello MSFS&quot;;                // your plugin&apos;s name

        private const int SIMCONNECT_TIMER_INTERVAL_MS = 50;            // how often to poll data from the game
        private DispatcherTimer simConnectDispatcherTimer;              // the DispatcherTimer object used to poll data from the game

        private SimConnect simconnect;                                  // the SimConnect SDK object used to subscribe and interact with the game
        private SimVars simvars;                                        // the object we&apos;ll store our new data from the game

        public MainWindow()
        {
            InitializeComponent();

            InitializeTimers();
        }

        // this is the structure that will store the data received from the game. note that the number of properties here have to be exactly the same number
        // of AddToDataDefinition(...) calls we are registering, and the type has to match the SIMCONNECT_DATATYPE type.
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        private struct SimVars
        {
            public float Altitude;                         // INDICATED ALTITUDE (feet)
            public float KohlsmanSettingHg;                // KOHLSMAN SETTING HG (inHG)
        }

        // we need an enum to register the data structure above. note that if you want to store them in separate structures, you&apos;ll need to register with a separate enum value here.
        private enum RequestType
        {
            PerFrameData,
        }

        // initializing our DispatcherTimers
        private void InitializeTimers()
        {
            simConnectDispatcherTimer = new DispatcherTimer() { Interval = new TimeSpan(0, 0, 0, 0, SIMCONNECT_TIMER_INTERVAL_MS) };
            simConnectDispatcherTimer.Tick += SimConnectTimer_Tick;
        }

        // function that will be called by the DispatcherTimer to receive new data from the game
        private void SimConnectTimer_Tick(object? sender, EventArgs e)
        {
            try
            {
                simconnect?.ReceiveMessage();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($&quot;Exception in {nameof(SimConnectTimer_Tick)}: {ex.Message}&quot;);
            }
        }

        // called whenever we receive a new set of SimVar data
        private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
        {
            switch ((RequestType)data.dwRequestID)
            {
                case RequestType.PerFrameData:
                    simvars = (SimVars)data.dwData[0];

                    Debug.WriteLine($&quot;Received: Altitude: {simvars.Altitude}, Barometer: {simvars.KohlsmanSettingHg}&quot;);

                    altimeterTextBox.Text = $&quot;{simvars.Altitude:0.00}&quot;;
                    barometerTextBox.Text = $&quot;{simvars.KohlsmanSettingHg:0.00}&quot;;

                    break;

                default:
                    Debug.WriteLine($&quot;Unsupported Request Type: {data.dwRequestID}&quot;);
                    break;
            }
        }

        // called when the connection throws an exception so we can log it
        private void Simconnect_OnRecvException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data)
        {
            Debug.WriteLine($&quot;Exception received: {data}&quot;);
        }

        // called when the game exits
        private void Simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
        {
            simConnectDispatcherTimer.Stop();
        }

        // called when we connect to the game for the first time
        private void Simconnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
        {
            // the framework is flexible enough to let you specify the units. check the documentation, for example for altitude you could set this to &quot;feet&quot;, &quot;meters&quot;, etc.
            simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;INDICATED ALTITUDE&quot;, &quot;feet&quot;, SIMCONNECT_DATATYPE.FLOAT32, 0, SimConnect.SIMCONNECT_UNUSED);
            simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;KOHLSMAN SETTING HG&quot;, &quot;inHG&quot;, SIMCONNECT_DATATYPE.FLOAT32, 0, SimConnect.SIMCONNECT_UNUSED);

            // register our SimVar structure with the PerFrameData enum
            simconnect.RegisterDataDefineStruct&lt;SimVars&gt;(RequestType.PerFrameData);

            // request data from sim
            simconnect.RequestDataOnSimObject(RequestType.PerFrameData, RequestType.PerFrameData, SimConnect.SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD.SIM_FRAME, SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT, 0, 0, 0);
        }

        // Connect button event handler
        private void connectButton_Click(object sender, RoutedEventArgs e)
        {
            if (Process.GetProcessesByName(MSFS_PROCESS_NAME).Any())
            {
                try
                {
                    var handle = Process.GetCurrentProcess().MainWindowHandle;

                    simconnect = new SimConnect(PLUGIN_NAME, handle, WM_USER_SIMCONNECT, null, 0);

                    // register a callback for each handler:
                    // called when you connect to the sim for the first time
                    simconnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(Simconnect_OnRecvOpen);
                    // called when the sim exits
                    simconnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(Simconnect_OnRecvQuit);
                    // called when there&apos;s an exception when sending us the data
                    simconnect.OnRecvException += new SimConnect.RecvExceptionEventHandler(Simconnect_OnRecvException);
                    // called every time we are sent new simvar data
                    simconnect.OnRecvSimobjectData += new SimConnect.RecvSimobjectDataEventHandler(Simconnect_OnRecvSimobjectData);

                    // start out DispatcherTimer to start polling from the game
                    simConnectDispatcherTimer.Start();

                    // toggle the connect/disconnect buttons
                    connectButton.IsEnabled = false;
                    disconnectButton.IsEnabled = true;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($&quot;{ex.Message}&quot;);
                }
            }
        }

        // Disconnect button event handler
        private void disconnectButton_Click(object sender, RoutedEventArgs e)
        {
            // stop our DispatcherTimer
            simConnectDispatcherTimer.Stop();

            // toggle the connect/disconnect buttons
            connectButton.IsEnabled = true;
            disconnectButton.IsEnabled = false;
        }
    }
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">MainWindow.xaml.cs</span></p></figcaption></figure><p>Ok that looks like a lot, I swear I tried to keep it as simple as possible, but here is what the code is doing:</p><ol><li>Creating a <code>SimConnect</code> object that will be used to read data from the sim</li><li>Register the event handlers for when the <code>SimConnect</code> object sends the events we are interested on:<ol><li><em>OnRecvOpen </em>- handler for when we connect successfully to the sim</li><li><em>OnRecvQuit </em>- handler for when the sim is exiting</li><li><em>OnRecvSimobjectData </em>- handler for when the sim sends us a new data object with <code>SimVars</code></li><li><em>OnRecvException</em> - handler for when there is an exception</li></ol></li><li>Create one or more <code>structures</code> that will hold our <code>SimVars</code>, which the sim will return with the current values</li><li>Create an <code>enum</code> with values representing our different <code>structures</code></li><li>Register our two <code>SimVars</code> so they can be returned as our <code>structure</code> object.</li><li>Use a <code>DispatcherTimer</code> to run a background task to check for new data</li><li>Connect/Disconnect to/from a local MSFS 2020 instance</li><li>On new data received, update the UI with the latest altimeter values</li></ol><p></p><h2 id="about-data-types">About Data Types</h2><p>An important thing we need to keep in mind when designing your <code>struct</code> is what data types to use. Let&apos;s look at our struct:</p><pre><code class="language-csharp">[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct SimVars
{
  public float Altitude;
  public float KohlsmanSettingHg;
}</code></pre><p>Notice that in the <code>Simconnect_OnRecvSimobjectData</code> handler, we are doing an explicit casting of an <code>object</code> to our <code>SimVars</code> structure, </p><pre><code class="language-csharp">simvars = (SimVars)data.dwData[0]</code></pre><p>and by doing so, the data gets copied from the object heap into our structure sequentially. This works because of the <code>StructLayout(LayoutKind.Sequential)</code> attribute we added to our struct, since the structure is stored in memory sequentially as well.</p><p>We told <code>SimConnect</code> the order and how big that data is when we registered the <code>SimVars</code> via the <code>simconnect.AddToDataDefinition(...)</code> calls, and specifying the <code>SIMCONENCT_DATATYPE</code>.</p><pre><code class="language-csharp">simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;INDICATED ALTITUDE&quot;, &quot;feet&quot;, SIMCONNECT_DATATYPE.FLOAT32, 0, SimConnect.SIMCONNECT_UNUSED);

simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;KOHLSMAN SETTING HG&quot;, &quot;inHG&quot;, SIMCONNECT_DATATYPE.FLOAT32, 0, SimConnect.SIMCONNECT_UNUSED);</code></pre><p>That is, we are expecting to receive 4 bytes for the <code>altitude</code> and 4 bytes for the <code>barometer</code> in that order. This exactly matches our <code>SimVars</code> structure.</p><p>This sounds tedious and prone for errors, especially if you add/remove or move the properties in your structure and forget to do the corresponding update where you are registering your <code>SimVars</code>, so be mindful when updating your <code>struct</code>.</p><p>Here are some of the mappings I&apos;ve used in my projects:</p><table>
<thead>
<tr>
<th>C# type</th>
<th>SIMCONNECT_DATATYPE</th>
</tr>
</thead>
<tbody>
<tr>
<td>bool, int</td>
<td>INT32</td>
</tr>
<tr>
<td>long</td>
<td>INT64</td>
</tr>
<tr>
<td>float</td>
<td>FLOAT32</td>
</tr>
<tr>
<td>double</td>
<td>FLOAT64</td>
</tr>
<tr>
<td>string</td>
<td>STRING8, STRING32, STRING64, STRING128, STRING256</td>
</tr>
</tbody>
</table>
<blockquote>Note: when you are working with <code>strings</code>, you must add an extra annotation specifying the size of the <code>string</code> matching the <code>SIMCONNECT_DATATYPE</code> you registered with</blockquote><pre><code class="language-csharp">public struct GPS {
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
  public string GpsWpNextId;                      // GPS WP NEXT ID
}


public void RegisterMySimVars()
{
  simconnect.AddToDataDefinition(RequestType.PerFrameData, &quot;GPS WP NEXT ID&quot;, &quot;&quot;, SIMCONNECT_DATATYPE.STRING256, 0, SimConnect.SIMCONNECT_UNUSED);
  ...
}</code></pre><p></p><h2 id="running-and-connecting-to-the-sim">Running and Connecting to the Sim</h2><ul><li><strong>Start MSFS 2020:</strong>Open Microsoft Flight Simulator 2020.Navigate to the World Map.Select your favorite airport and click <strong>Fly</strong>.Wait for the simulator to finish loading.</li><li><strong>Debugging the App:</strong>In Visual Studio, start debugging your application.Once the app is running, click <strong>Connect</strong>.</li><li><strong>Verify Functionality:</strong>If everything is set up correctly, the app should connect to the simulator.You&#x2019;ll see the Altimeter and Barometer values updating in real-time on your UI.You can interact with the simulator: fly around, adjust the barometer knob, and observe the changes reflected in your application.Since we added some <code>Debug.WriteLine(...)</code>  statements, we can open the <em>Output</em> View in <em>Visual Studio</em> to see some output. Should look something like this:</li></ul><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-22.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="568" height="230"></figure><p></p><h2 id="developer-mode">Developer Mode</h2><p>If you haven&apos;t already done so, go to <em>General Options-&gt; Developers</em> and turn on DEVELOPER MODE. Save and go back to the sim.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-11-1.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="1000" height="560" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-11-1.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-11-1.png 1000w" sizes="(min-width: 720px) 720px"></figure><p>If we go to the top of the screen, you&apos;ll see the Developer menu shown. If it&apos;s not shown, move your mouse to the top of the screen and it should be visible. It has an autohide option, which you can turn off if you want. Click on the Tools tab and, on the <em>SimConnect Inspector </em>menu item.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-5.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="673" height="380" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-5.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-5.png 673w"></figure><p>Once the <em>SimConnect Inspector </em>window opens, click on the drop down menu and you&apos;ll see our new <code>Hello MSFS</code> application connected to the game. </p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-6.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="899" height="234" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-6.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-6.png 899w" sizes="(min-width: 720px) 720px"></figure><blockquote>Fun fact: if you are using other plugins or peripherals that use a plugin like Logitech&apos;s Instrument Panel, you&apos;ll see them here too.</blockquote><p>If we click on the <em>Frames</em> tab, we can see the frame where we registered our two <code>SimVars</code> and requested data sent to us.</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-7.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="897" height="262" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-7.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-7.png 897w" sizes="(min-width: 720px) 720px"></figure><p>We can also click on the Data Definitions tab and see our two <code>SimVars</code>:</p><figure class="kg-card kg-image-card"><img src="https://www.theflightsimdev.com/content/images/2024/09/image-23.png" class="kg-image" alt="5. Reading SimVar data with SimConnect SDK" loading="lazy" width="891" height="250" srcset="https://www.theflightsimdev.com/content/images/size/w600/2024/09/image-23.png 600w, https://www.theflightsimdev.com/content/images/2024/09/image-23.png 891w" sizes="(min-width: 720px) 720px"></figure><blockquote>Fun fact: if you select the other plugins, you can see what <code>SimVars</code> they have registered, which helps you figure out what to use for yours.</blockquote><p></p><h2 id="wrapping-things-up">Wrapping things up</h2><p>This tutorial has covered how to read data from Microsoft Flight Simulator 2020 using the SimConnect SDK. To summarize:</p><ol><li><strong>Reading Data:</strong><ul><li>Consult the documentation to find the SimVars you need.</li><li>Add the <code>SimVars</code> to your <code>struct</code> and register them using <code>AddToDataDefinition(...)</code>.</li></ul></li><li><strong>Data Order and Type Matching:</strong><ul><li>The order in which you register SimVars with <code>AddToDataDefinition(...)</code> must match the order of the properties in your data structure. This is crucial for accurate data retrieval.</li><li>Ensure that the data types of your structure properties align with the types specified in the SimConnect documentation to avoid mismatches.</li></ul></li></ol>]]></content:encoded></item></channel></rss>