PROJECTAdvanced

Build a WiFi Site Survey Tool with Heatmap Generation

Build a WiFi site survey tool in C#: read RSSI from the Native WiFi API, click a floor plan to sample, and render an IDW-interpolated coverage heatmap.

Dylan H.

Infrastructure Engineer

September 2, 2026
7 min read
6-8 hours
Build a WiFi Site Survey Tool with Heatmap Generation

Tools & Technologies

Visual Studio 2022.NET 8 SDKA WiFi adapter supporting the Native WiFi API

Why Build a Survey Tool

Commercial WiFi survey software starts around a thousand dollars per seat and assumes you are surveying a campus. For a warehouse, a clinic, or a two-storey office, you need far less: a floor plan, a way to record signal strength where you are standing, and a picture at the end that shows management where the dead zones are.

That is a weekend project. This guide builds one in C# — reading real RSSI from the Windows Native WiFi API, sampling by clicking a floor plan, and interpolating a coverage heatmap between the points you measured.

The finished reference implementation is WifiSurvey on GitHub.

Project Overview

What you'll build:

  • A floor-plan canvas with pan, zoom, and click-to-sample
  • Live RSSI, link quality, channel, frequency, and band capture
  • An IDW-interpolated heatmap that fills the space between samples
  • Saveable survey projects and CSV / image / HTML export

Time to complete: 6-8 hours

Prerequisites: Solid C#, comfort with P/Invoke, and a Windows 10/11 machine with a WiFi adapter. This one genuinely needs real hardware — you cannot fake a survey in a VM without a passthrough adapter.

Step 1: Talk to the Native WiFi API

.NET has no managed WiFi API. You call wlanapi.dll directly. Three functions do almost everything:

[DllImport("wlanapi.dll")]
private static extern uint WlanOpenHandle(
    uint dwClientVersion, IntPtr pReserved,
    out uint pdwNegotiatedVersion, out IntPtr phClientHandle);
 
[DllImport("wlanapi.dll")]
private static extern uint WlanEnumInterfaces(
    IntPtr hClientHandle, IntPtr pReserved, out IntPtr ppInterfaceList);
 
[DllImport("wlanapi.dll")]
private static extern uint WlanGetAvailableNetworkList(
    IntPtr hClientHandle, ref Guid pInterfaceGuid,
    uint dwFlags, IntPtr pReserved, out IntPtr ppAvailableNetworkList);

Every one of these allocates unmanaged memory you are responsible for. Wrap the handle in a SafeHandle and free every list with WlanFreeMemory, or a long survey will leak steadily as you sample.

public sealed class WlanClient : IDisposable
{
    private readonly IntPtr _handle;
 
    public WlanClient()
    {
        if (WlanOpenHandle(2, IntPtr.Zero, out _, out _handle) != 0)
            throw new InvalidOperationException("WlanOpenHandle failed");
    }
 
    public void Dispose() => WlanCloseHandle(_handle, IntPtr.Zero);
}

Step 2: Signal Quality Is Not dBm

This is the detail that trips up most first attempts. The Native WiFi API reports wlanSignalQuality as an integer from 0 to 100. That is not RSSI — it is a linear scale across a fixed dBm window, and Microsoft documents the mapping:

// wlanSignalQuality 0   => -100 dBm
// wlanSignalQuality 100 =>  -50 dBm
public static int QualityToDbm(uint signalQuality)
    => (int)(signalQuality / 2.0) - 100;

Two consequences worth internalising before you trust your own data:

  • Resolution is 0.5 dBm per quality point. You cannot distinguish -47 from -47.3 dBm; do not render a heatmap that implies you can.
  • The scale saturates. Anything stronger than -50 dBm reads as 100, and anything weaker than -100 dBm reads as 0. Standing next to the AP, every sample looks identical — which is fine, because that is not where coverage problems live.

Step 3: The Floor Plan Canvas

Load any bitmap as the backdrop and keep a transform between screen space and image space. Do not store sample positions in screen coordinates — the moment someone zooms, every point you recorded is wrong.

private float _zoom = 1.0f;
private PointF _pan = PointF.Empty;
 
private PointF ScreenToImage(Point screen) => new(
    (screen.X - _pan.X) / _zoom,
    (screen.Y - _pan.Y) / _zoom);
 
protected override void OnMouseClick(MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return;
    var imagePoint = ScreenToImage(e.Location);
    var reading = _wlan.SampleCurrent();
    _survey.Add(new Measurement(imagePoint, reading));
    Invalidate();
}

Store measurements in image coordinates, render them through the transform. Pan on right-drag, zoom on the mouse wheel around the cursor position.

Step 4: Interpolate the Heatmap

You have scattered points and need a continuous surface. Inverse Distance Weighting is the right tool here — simple, no fitting step, and it honours your measurements exactly.

Each output pixel is a weighted average of nearby samples, where weight falls off with the square of distance:

public static double Interpolate(PointF target, IReadOnlyList<Measurement> samples, int k = 8)
{
    const double Power = 2.0;
 
    var nearest = samples
        .Select(s => (s, dist: Distance(target, s.Position)))
        .OrderBy(t => t.dist)
        .Take(k)
        .ToList();
 
    // Standing exactly on a sample: return it, and avoid dividing by zero
    if (nearest[0].dist < 0.0001) return nearest[0].s.Rssi;
 
    double weightedSum = 0, weightTotal = 0;
    foreach (var (s, dist) in nearest)
    {
        double w = 1.0 / Math.Pow(dist, Power);
        weightedSum += w * s.Rssi;
        weightTotal += w;
    }
 
    return weightedSum / weightTotal;
}

Three practical notes:

  • Limit to the k nearest samples. Weighting every point against every pixel is O(pixels × samples) and will crawl on a large floor plan. Eight neighbours is plenty.
  • Guard the zero-distance case. Without that early return, a pixel landing exactly on a sample divides by zero.
  • IDW cannot see walls. It will happily interpolate strong signal straight through a lift shaft. Sample densely near anything that attenuates, and treat the space between rooms as an estimate rather than a measurement.

Step 5: Map Values to Colour

Interpolate on a green-to-red ramp so the picture reads instantly. Anchor the ends at values that mean something operationally rather than at your dataset's min and max — otherwise a survey of a uniformly good floor renders alarming red patches.

RangeQualityColour
-30 to -50 dBmExcellentGreen
-50 to -60 dBmGoodYellow-green
-60 to -70 dBmMarginalAmber
-70 to -90 dBmPoorRed

Anything weaker than -70 dBm is where voice and roaming start to suffer, so that is the boundary worth making visually obvious.

Render the heatmap to an off-screen bitmap once after each sample, then blit it under the markers. Recomputing interpolation inside OnPaint will make panning unusable.

Step 6: Export Something You Can Send

The survey is only useful if it leaves your laptop. Three formats cover nearly every request:

  • CSV — one row per sample: position, RSSI, quality, SSID, BSSID, channel, band, timestamp. This is what goes into a spreadsheet when someone wants to argue about coverage.
  • PNG — the composited floor plan plus heatmap plus legend. This is what goes into the report.
  • HTML — both of the above in one self-contained file you can email.

Keep the project file itself boring. Serialise the sample list and the floor-plan path to JSON under your own extension:

var json = JsonSerializer.Serialize(_survey, new JsonSerializerOptions
{
    WriteIndented = true
});
File.WriteAllText(path, json);

Embedding the floor-plan image as base64 makes the project file self-contained and much easier to hand to a colleague — at the cost of size. Worth it.

Surveying Well

The tool is the easy half. Getting data worth trusting:

  • Walk a grid, not a path. Sample every 5 to 10 metres in open space, tighter near walls and lifts.
  • Hold still and let it settle. Signal quality fluctuates; sample after a second of standing, not mid-stride.
  • Survey at working height. A reading taken at floor level is not what a laptop on a desk sees.
  • Repeat the worst areas at a different time of day. Interference from microwaves, neighbouring networks, and simple occupancy moves the numbers more than people expect.

Where to Take It Next

  • Multi-AP attribution — record BSSID per sample and colour by which AP is serving, which exposes roaming problems a pure signal map hides
  • Channel overlap — chart co-channel interference from the available network list
  • Predictive mode — place APs on the plan and model expected coverage before buying hardware
  • 5 GHz versus 6 GHz overlays — same walk, two surfaces, side by side

Reference Implementation

Complete source, including the WinForms canvas, the P/Invoke layer, and the export pipeline, is at CosmicBytez/WifiSurvey. MIT licensed.