diff --git a/src/band.c b/src/band.c
index 0728b0cc..a44a6024 100644
--- a/src/band.c
+++ b/src/band.c
@@ -28,7 +28,8 @@
 
 #include "ell/useful.h"
 
-#include "band.h"
+#include "src/band.h"
+#include "src/netdev.h"
 
 void band_free(struct band *band)
 {
@@ -125,14 +126,21 @@ int band_estimate_nonht_rate(const struct band *band,
 }
 
 /*
- * Base RSSI values for 20MHz (both HT and VHT) channel. These values can be
+ * Base RSSI values for 20MHz (HT, VHT and HE) channel. These values can be
  * used to calculate the minimum RSSI values for all other channel widths. HT
- * MCS indexes are grouped into ranges of 8 (per spatial stream) where VHT are
- * grouped in chunks of 10. This just means HT will not use the last two
- * index's of this array.
+ * MCS indexes are grouped into ranges of 8 (per spatial stream), VHT in groups
+ * of 10 and HE in groups of 12. This just means HT will not use the last four
+ * index's of this array, and VHT won't use the last two.
+ *
+ * Note: The values here are not based on anything from 802.11 but data
+ *       found elsewhere online (presumably from testing, we hope). The two
+ *       indexes for HE (MCS 11/12) are not based on any data, but just
+ *       increased by 3dB compared to the previous value. We consider this good
+ *       enough for its purpose to estimate the date rate for network/BSS
+ *       preference.
  */
-static const int32_t ht_vht_base_rssi[] = {
-	-82, -79, -77, -74, -70, -66, -65, -64, -59, -57
+static const int32_t ht_vht_he_base_rssi[] = {
+	-82, -79, -77, -74, -70, -66, -65, -64, -59, -57, -54, -51
 };
 
 /*
@@ -194,7 +202,7 @@ bool band_ofdm_rate(uint8_t index, enum ofdm_channel_width width,
 	uint64_t rate;
 	int32_t width_adjust = width * 3;
 
-	if (rssi < ht_vht_base_rssi[index] + width_adjust)
+	if (rssi < ht_vht_he_base_rssi[index] + width_adjust)
 		return false;
 
 	rate = ht_vht_rates[width][index];
@@ -495,6 +503,227 @@ try_vht80:
 	return -ENETUNREACH;
 }
 
+/*
+ * Data Rate for HE is much the same as HT/VHT but some additional MCS indexes
+ * were added. This mean rfactors, and nbpscs will contain two additional
+ * values:
+ *
+ * rfactors.extend([3/4, 5/6])
+ * nbpscs.extend([10, 10])
+ *
+ * The guard interval also differs:
+ *
+ * Tdft = 12.8us
+ * Tgi = 0.8, 1.6 or 2.3us
+ *
+ * The Nsd values for HE are:
+ *
+ * Nsd = [234, 468, 980, 1960]
+ *
+ * The formula is identical to HT/VHT:
+ *
+ * Nsd * Nbpscs * R * Nss / (Tdft + Tgi)
+ *
+ * Note: The table below assumes a 0.8us GI. There isn't any way to know what
+ *       GI will be used for an actual connection, so assume the best.
+ */
+static uint64_t he_rates[4][12] = {
+	[OFDM_CHANNEL_WIDTH_20MHZ] = {
+		8600000ULL, 17200000ULL, 25800000ULL, 34400000ULL,
+		51600000ULL, 68800000ULL, 77400000ULL, 86000000ULL,
+		103200000ULL, 114700000ULL, 129000000ULL, 143300000ULL,
+	},
+	[OFDM_CHANNEL_WIDTH_40MHZ] = {
+		17200000ULL, 34400000ULL, 51600000ULL, 68800000ULL,
+		103200000ULL, 137600000ULL, 154900000ULL, 172000000ULL,
+		206500000ULL, 229400000ULL, 258000000ULL, 286800000ULL,
+	},
+	[OFDM_CHANNEL_WIDTH_80MHZ] = {
+		36000000ULL, 72000000ULL, 108000000ULL, 144100000ULL,
+		216200000ULL, 288200000ULL, 324300000ULL, 360300000ULL,
+		432400000ULL, 480400000ULL, 540400000ULL, 600500000ULL,
+	},
+	[OFDM_CHANNEL_WIDTH_160MHZ] = {
+		72000000ULL, 144100000ULL, 216200000ULL, 288200000ULL,
+		432400000ULL, 576500000ULL, 648500000ULL, 720600000ULL,
+		864700000ULL, 960800000ULL, 1080900000ULL, 1201000000ULL,
+	},
+};
+
+static bool band_he_rate(uint8_t index, enum ofdm_channel_width width,
+			int32_t rssi, uint8_t nss, uint64_t *data_rate)
+{
+	uint64_t rate;
+	int32_t width_adjust;
+
+	width_adjust = width * 3;
+
+	if (rssi < ht_vht_he_base_rssi[index] + width_adjust)
+		return false;
+
+	rate = he_rates[width][index];
+
+	rate *= nss;
+
+	*data_rate = rate;
+	return true;
+}
+
+static bool find_rate_he(const uint8_t *rx_map, const uint8_t *tx_map,
+				enum ofdm_channel_width width, int32_t rssi,
+				uint64_t *out_data_rate)
+{
+	uint32_t nss;
+	uint32_t max_mcs;
+	int i;
+
+	if (!find_best_mcs_nss(rx_map, tx_map, 7, 9, 11,
+				&max_mcs, &nss))
+		return false;
+
+	for (i = max_mcs; i >= 0; i--)
+		if (band_he_rate(i, width, rssi, nss, out_data_rate))
+			return true;
+
+	return false;
+}
+
+/*
+ * HE data rate is calculated based on 802.11ax - Section 27.5
+ */
+int band_estimate_he_rx_rate(const struct band *band, const uint8_t *hec,
+				int32_t rssi, uint64_t *out_data_rate)
+{
+	enum ofdm_channel_width width = OFDM_CHANNEL_WIDTH_20MHZ;
+	enum ofdm_channel_width i;
+	const struct band_he_capabilities *he_cap = NULL;
+	const struct l_queue_entry *entry;
+	const uint8_t *rx_map;
+	const uint8_t *tx_map;
+	bool mcs80p80 = false;
+	uint64_t rate = 0;
+	uint64_t new_rate = 0;
+	uint8_t width_set;
+
+	if (!hec || !band->he_capabilities)
+		return -EBADMSG;
+
+	for (entry = l_queue_get_entries(band->he_capabilities);
+						entry; entry = entry->next) {
+		const struct band_he_capabilities *cap = entry->data;
+
+		/*
+		 * TODO: Station type is assumed here since it is the only
+		 *       consumer of these data rate estimation APIs. If this
+		 *       changes the iftype would need to be passed in.
+		 */
+		if (cap->iftypes & (1 << NETDEV_IFTYPE_STATION)) {
+			he_cap = cap;
+			break;
+		}
+	}
+
+	if (!he_cap)
+		return -ENOTSUP;
+
+	/* AND the width sets, giving the widths supported by both */
+	width_set = bit_field(he_cap->he_phy_capa[0], 1, 7) &
+				bit_field((hec + 8)[0], 1, 7);
+
+	/*
+	 * 802.11ax Table 9-322b
+	 */
+	switch (band->freq) {
+	case BAND_FREQ_2_4_GHZ:
+		/* B0 indicates support for 40MHz */
+		if (test_bit(&width_set, 0))
+			width = OFDM_CHANNEL_WIDTH_40MHZ;
+
+		break;
+	case BAND_FREQ_5_GHZ:
+	case BAND_FREQ_6_GHZ:
+		/* B1 indicates support for 40MHz and 80MHz, choose 80 */
+		if (test_bit(&width_set, 1))
+			width = OFDM_CHANNEL_WIDTH_80MHZ;
+
+		/* B2 indicates support for 160MHz */
+		if (test_bit(&width_set, 2)) {
+			if (width != OFDM_CHANNEL_WIDTH_80MHZ) {
+				l_warn("Support for 160MHz but not 80MHz!");
+				return -EBADMSG;
+			}
+
+			width = OFDM_CHANNEL_WIDTH_160MHZ;
+		}
+
+		/* B3 indicates support for 80+80MHz */
+		if (test_bit(&width_set, 3)) {
+			if (width != OFDM_CHANNEL_WIDTH_160MHZ) {
+				l_warn("Support for 80+80MHz but not 160MHz!");
+				return -EBADMSG;
+			}
+
+			mcs80p80 = true;
+		}
+
+		break;
+	}
+
+	/*
+	 * The HE-MCS maps are 19 bytes into the HE Capabilities IE, and
+	 * alternate RX/TX every 2 bytes. Start the TX map 19 + 2 bytes
+	 * into the MCS set. For each MCS set find the best data rate.
+	 *
+	 * Note: Since 'hec' is an extended IE index 0 is actually the length
+	 *       since wiphy offsets data - 2.
+	 */
+	rx_map = he_cap->he_mcs_set;
+	tx_map = hec + 21;
+
+	/* 80+80MHz MCS set */
+	if (mcs80p80) {
+		/* 21 + 8 - 1 (len) bytes into the IE, plus 2 for the MCS set */
+		if (hec[0] < 30)
+			return -EBADMSG;
+
+		if (find_rate_he(rx_map + 8, tx_map + 8,
+				OFDM_CHANNEL_WIDTH_160MHZ, rssi, &new_rate))
+			rate = new_rate;
+	}
+
+	/* 160MHz MCS set */
+	if (width == OFDM_CHANNEL_WIDTH_160MHZ) {
+		/* 21 + 4 - 1 (len) bytes into the IE, plus 2 for the MCS set */
+		if (hec[0] < 26)
+			return -EBADMSG;
+
+		if (find_rate_he(rx_map + 4, tx_map + 4,
+				OFDM_CHANNEL_WIDTH_160MHZ, rssi, &new_rate) &&
+				new_rate > rate)
+			rate = new_rate;
+	}
+
+	/*
+	 * <= 80MHz MCS set. Adjust width to this range (if needed) and check
+	 * every smaller width in case the rate is higher
+	 */
+	if (width > OFDM_CHANNEL_WIDTH_80MHZ)
+		width = OFDM_CHANNEL_WIDTH_80MHZ;
+
+	for (i = OFDM_CHANNEL_WIDTH_20MHZ; i <= width; i++) {
+		if (find_rate_he(rx_map, tx_map, i, rssi, &new_rate) &&
+					new_rate > rate)
+			rate = new_rate;
+	}
+
+	if (!rate)
+		return -EBADMSG;
+
+	*out_data_rate = rate;
+
+	return 0;
+}
+
 static int band_channel_info_get_bandwidth(const struct band_chandef *info)
 {
 	switch (info->channel_width) {
diff --git a/src/band.h b/src/band.h
index 1f1269c7..9b307a77 100644
--- a/src/band.h
+++ b/src/band.h
@@ -74,7 +74,8 @@ void band_free(struct band *band);
 bool band_ofdm_rate(uint8_t index, enum ofdm_channel_width width,
 			int32_t rssi, uint8_t nss, bool sgi,
 			uint64_t *data_rate);
-
+int band_estimate_he_rx_rate(const struct band *band, const uint8_t *hec,
+				int32_t rssi, uint64_t *out_dat_rate);
 int band_estimate_vht_rx_rate(const struct band *band,
 				const uint8_t *vhtc, const uint8_t *vhto,
 				const uint8_t *htc, const uint8_t *hto,
