diff --git a/src/core/cntlr_map.c b/src/core/cntlr_map.c
index 153ac51f6f7c3945533d4f381193fde4d836f4d6..9c1c2224d165fb9e2b0d4e8d987704361ed24e58 100644
--- a/src/core/cntlr_map.c
+++ b/src/core/cntlr_map.c
@@ -132,8 +132,11 @@ static bool ap_caps_info_from_tlv(struct controller *c,
 				struct tlv_ap_ht_cap *p = (struct tlv_ap_ht_cap *)tlv;
 
 				trace("\tradio_id: " MACFMT "\n", MAC2STR(p->radio_id));
-				trace("\tmax_tx_streams_supported: %02x\n", p->max_tx_streams_supported);
-				trace("\tmax_rx_streams_supported: %02x\n", p->max_rx_streams_supported);
+				trace("\tmax_tx_streams_supported: %d\n", p->max_tx_streams_supported);
+				trace("\tmax_tx_streams_supported: ");
+				print_bits(p->max_tx_streams_supported, 2, 2);
+				trace("\tmax_rx_streams_supported: ");
+				print_bits(p->max_rx_streams_supported, 2, 2);
 				trace("\tgi_20_support: %s\n", (p->gi_20_support ? "true" : "false"));
 				trace("\tgi_40_support: %s\n", (p->gi_40_support ? "true" : "false"));
 				trace("\tht_40_support: %s\n", (p->ht_40_support ? "true" : "false"));
@@ -144,10 +147,14 @@ static bool ap_caps_info_from_tlv(struct controller *c,
 				struct tlv_ap_vht_cap *p = (struct tlv_ap_vht_cap *)tlv;
 
 				trace("\tradio_id: " MACFMT "\n", MAC2STR(p->radio_id));
-				trace("\tvht_tx_mcs_supported: %04x\n", p->vht_tx_mcs_supported);
-				trace("\tvht_rx_mcs_supported: %04x\n", p->vht_rx_mcs_supported);
-				trace("\tmax_tx_streams_supported: %02x\n", p->max_tx_streams_supported);
-				trace("\tmax_rx_streams_supported: %02x\n", p->max_rx_streams_supported);
+				trace("\tvht_tx_mcs_supported: ");
+				print_bits(p->vht_tx_mcs_supported, 16, 2);
+				trace("\tvht_rx_mcs_supported: ");
+				print_bits(p->vht_rx_mcs_supported, 16, 2);
+				trace("\tmax_tx_streams_supported: ");
+				print_bits(p->max_tx_streams_supported, 3, 3);
+				trace("\tmax_rx_streams_supported: ");
+				print_bits(p->max_rx_streams_supported, 3, 3);
 				trace("\tgi_80_support: %s\n", (p->gi_80_support ? "true" : "false"));
 				trace("\tgi_160_support: %s\n", (p->gi_160_support ? "true" : "false"));
 				trace("\tvht160_support: %s\n", (p->vht_160_support ? "true" : "false"));
@@ -194,9 +201,65 @@ int handle_oper_channel_report(void *cntlr, struct cmdu_cstruct *cmdu)
 	return 0;
 }
 
+static bool sta_caps_info_from_tlv(struct controller *c,
+					struct cmdu_cstruct *cmdu_data)
+{
+	int i, j, k;
+	int radio_index, bss_index;
+	uint8_t *tlv = NULL;
+
+	for (i = 0; i < cmdu_data->num_tlvs; i++) {
+		tlv = cmdu_data->tlvs[i];
+		trace("CMDU type: %s\n", map_stringify_tlv_type(*tlv));
+		switch (*tlv) {
+		case MAP_TLV_CLIENT_INFO:
+			{
+				struct tlv_client_info *p = (struct tlv_client_info *)tlv;
+
+				trace("\tbssid: " MACFMT "\n", MAC2STR(p->bssid));
+				trace("\tclient_addr: " MACFMT "\n", MAC2STR(p->client_addr));
+				break;
+			}
+		case MAP_TLV_CLIENT_CAPABILITY_REPORT:
+			{
+				struct tlv_client_cap_report *p = (struct tlv_client_cap_report *)tlv;
+				char *frame;
+				int offset = 1 + 2 + 1;
+
+				frame = calloc(1, (2 * (p->tlv_len - offset)) + 1);
+				if (!frame)
+					continue;
+
+				btostr(p->frame_body + offset, p->tlv_len - offset, frame);
+
+				trace("\tresult_code: 0x%02x\n", p->result_code);
+				trace("\tframe: 0x%s\n", frame);
+				break;
+			}
+		case MAP_TLV_ERROR_CODE:
+			{
+				struct tlv_error_code *p = (struct tlv_error_code *)tlv;
+
+				trace("\treason_code: 0x%02x\n", p->reason_code);
+				if (p->reason_code == 0x02)
+					trace("\tclient_addr: " MACFMT "\n", MAC2STR(p->addr));
+
+				break;
+			}
+		default:
+			fprintf(stdout, "unknown TLV in CMDU:|%s|", map_stringify_cmdu_type(cmdu_data->message_type));
+			break;
+		}
+		trace("\n");
+	}
+
+	return 0;
+}
+
 int handle_sta_caps_report(void *cntlr, struct cmdu_cstruct *cmdu)
 {
 	trace("received sta caps report!\n");
+	sta_caps_info_from_tlv(cntlr, cmdu);
 	return 0;
 }
 
diff --git a/src/core/cntlr_ubus.c b/src/core/cntlr_ubus.c
index 176119cd447af0d73ef8700a8652489c795add59..a4ce4d03cbba7bbee95c2901fff807bb021ead21 100644
--- a/src/core/cntlr_ubus.c
+++ b/src/core/cntlr_ubus.c
@@ -36,16 +36,29 @@
 
 enum {
 	AP_POLICY_AGENT,
-	//AP_POLICY_BSSID,
+	//AP_POLICY_BSSID, /* TODO: filter on cntlr side based on bssid */
 	__AP_POLICY_MAX,
 };
 
 static const struct blobmsg_policy ap_caps_policy_params[__AP_POLICY_MAX] = {
-	[AP_POLICY_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING }, // agent = ieee1905 AL macaddress of an agent, agent = "ff:ff:ff:ff:ff:ff" means all agents
-	//[AP_POLICY_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING }
+	[AP_POLICY_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
+	//[AP_POLICY_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING } /* TODO: filter on cntlr side based on bssid */
 };
 
 
+enum {
+	STA_POLICY_AGENT,
+	STA_POLICY_STA,
+	STA_POLICY_BSSID,
+	__STA_POLICY_MAX,
+};
+
+static const struct blobmsg_policy sta_caps_policy_params[__STA_POLICY_MAX] = {
+	[STA_POLICY_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
+	[STA_POLICY_STA] = { .name = "sta", .type = BLOBMSG_TYPE_STRING },
+	[STA_POLICY_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING }
+};
+
 static void send_cmdu_cb(struct ubus_request *req,
 				int type, struct blob_attr *msg)
 {
@@ -110,8 +123,8 @@ static int send_cmdu(struct controller *c,
 	hwaddr_ntoa(cmdu_data->origin, dst_addr);
 	blobmsg_add_string(&b, "dst_macaddr", dst_addr);
 
-	fprintf(stdout, "|%s:%d|cmdu:%s|dst:%s|\n", __func__, __LINE__,
-			map_stringify_cmdu_type(cmdu_data->message_type), dst_addr);
+	fprintf(stdout, "|%s:%d|cmdu:%s|dst:%s|num_tlvs:%d|\n", __func__, __LINE__,
+			map_stringify_cmdu_type(cmdu_data->message_type), dst_addr, cmdu_data->num_tlvs);
 
 	if (cmdu_data->num_tlvs > 0) {
 		for (i = 0; i < cmdu_data->num_tlvs; i++) {
@@ -293,7 +306,7 @@ static int cntlr_ap_caps(struct ubus_context *ctx, struct ubus_object *obj,
 {
 	struct blob_attr *tb[__AP_POLICY_MAX];
 	struct controller *c = container_of(obj, struct controller, obj);
-	char mac[18] = {0};
+	char agent[18] = {0};
 	struct cmdu_cstruct *cmdu_data;
 	uint32_t total_bss = 0;
 	uint32_t k = 0;
@@ -309,9 +322,8 @@ static int cntlr_ap_caps(struct ubus_context *ctx, struct ubus_object *obj,
 
 
 	if (tb[AP_POLICY_AGENT]) {
-		strncpy(mac, blobmsg_data(tb[AP_POLICY_AGENT]), sizeof(mac) - 1);
-		printf("mac = %s\n", mac);
-		if (!hwaddr_aton(mac, cmdu_data->origin))
+		strncpy(agent, blobmsg_data(tb[AP_POLICY_AGENT]), sizeof(agent) - 1);
+		if (!hwaddr_aton(agent, cmdu_data->origin))
 			return UBUS_STATUS_UNKNOWN_ERROR;
 	}
 
@@ -327,15 +339,87 @@ static int cntlr_ap_caps(struct ubus_context *ctx, struct ubus_object *obj,
 	return 0;
 }
 
+static int cntlr_sta_caps(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct blob_attr *tb[__STA_POLICY_MAX];
+	uint8_t hw_bssid[6] = {0}, hw_sta[6] = {0};
+	struct tlv_client_info *p;
+	struct controller *c = container_of(obj, struct controller, obj);
+	char sta[18] = {0}, agent[18] = {0}, bssid[18] = {0};
+	struct cmdu_cstruct *cmdu_data;
+
+	blobmsg_parse(sta_caps_policy_params, __STA_POLICY_MAX, tb, blob_data(msg), blob_len(msg));
+
+	if (!tb[STA_POLICY_STA] || !tb[STA_POLICY_BSSID]) {
+		fprintf(stderr, "STA Capability Query: must provide STA and BSSID\n");
+		return UBUS_STATUS_INVALID_ARGUMENT;
+	}
+
+	strncpy(sta, blobmsg_data(tb[STA_POLICY_STA]), sizeof(sta) - 1);
+	strncpy(bssid, blobmsg_data(tb[STA_POLICY_BSSID]), sizeof(bssid) - 1);
+
+	cmdu_data = (struct cmdu_cstruct *)calloc(1, sizeof(struct cmdu_cstruct));
+	if (!cmdu_data) {
+		fprintf(stderr, "failed to malloc cmdu\n");
+		return UBUS_STATUS_UNKNOWN_ERROR;
+	}
+
+	if (tb[AP_POLICY_AGENT]) {
+		strncpy(agent, blobmsg_data(tb[AP_POLICY_AGENT]), sizeof(agent) - 1);
+		if (!hwaddr_aton(agent, cmdu_data->origin))
+			return UBUS_STATUS_UNKNOWN_ERROR;
+	}
+
+	// TODO: ff:ff:ff:ff:ff:ff = send to all agents
+
+	cmdu_data->message_type = CMDU_CLIENT_CAPABILITY_QUERY;
+	cmdu_data->message_id = 1;
+
+	p = calloc(1, sizeof(struct tlv_client_info));
+	if (!p) {
+		fprintf(stderr, "failed to malloc cmdu\n");
+		return UBUS_STATUS_UNKNOWN_ERROR;
+	}
+
+	if (!hwaddr_aton(sta, hw_sta)) {
+			fprintf(stderr, "STA Capability Query: provide STA address in " \
+				"format 11:22:33...\n");
+		return UBUS_STATUS_INVALID_ARGUMENT;
+	}
+
+	if (!hwaddr_aton(bssid, hw_bssid)) {
+			fprintf(stderr, "STA Capability Query: provide BSSID address in " \
+					"format 11:22:33...\n");
+		return UBUS_STATUS_INVALID_ARGUMENT;
+	}
+
+	p->tlv_type = MAP_TLV_CLIENT_INFO;
+	memcpy(p->bssid, hw_bssid, 6);
+	memcpy(p->client_addr, hw_sta, 6);
+
+	cmdu_data->num_tlvs = 1;
+	cmdu_data->tlvs = (uint8_t **)calloc(cmdu_data->num_tlvs, sizeof(uint8_t *));
+
+	if (cmdu_data->tlvs)
+			cmdu_data->tlvs[0] = (uint8_t *)p;
+
+	send_cmdu(c, cmdu_data);
+
+	return 0;
+}
+
 int cntlr_publish_object(struct controller *c, const char *objname)
 {
 	struct ubus_object *obj;
 	struct ubus_object_type *obj_type;
 	struct ubus_method *obj_methods;
-	struct ubus_method m[3] = {
+	struct ubus_method m[4] = {
 		UBUS_METHOD_NOARG("status", cntlr_status),
 		UBUS_METHOD_NOARG("req_cap", cntlr_req_cap),
 		UBUS_METHOD("ap_caps", cntlr_ap_caps, ap_caps_policy_params),
+		UBUS_METHOD("sta_caps", cntlr_sta_caps, sta_caps_policy_params),
 	};
 	int num_methods = ARRAY_SIZE(m);
 	int ret;
diff --git a/src/utils/utils.h b/src/utils/utils.h
index 465032f3b6d109489149d96a404048d7cd9f827b..200bf64cea785e4f09c8684b14b75bfeb6dd20b2 100644
--- a/src/utils/utils.h
+++ b/src/utils/utils.h
@@ -28,6 +28,16 @@
 #define MAC2STR(_m)	(_m)[0], (_m)[1], (_m)[2], (_m)[3], (_m)[4], (_m)[5]
 #endif
 
+#define print_bits(x, len, s) \
+do { \
+        unsigned long long a__ = (x); \
+        size_t bits__ = sizeof(x) * len; \
+        while(bits__--) { \
+			putchar(a__ & (1 << bits__) ? '1' : '0'); \
+			if (!(bits__ % s)) putchar(' '); \
+		} \
+        putchar('\n'); \
+} while(0)
 
 bool match_oui0(unsigned char *oui, unsigned char *hwaddr, int ouis);
 unsigned char *hwaddr_aton(const char *macstr, unsigned char *mac);