Wait for response from BLE module before proceeding with the rest of the code (HM-10)

Hello everyone, I am writing a simple app which I will use to test my arduino project.

I have the receiving/sending of strings ready to go on the arduino side, but in my app I am facing an issue regarding responses from the arduino. Here is the situation:

Data sent from the app must be in a specific format to receive an ACK response (everything is OK):
BP:<int>/<int>\n

Otherwise a RETRY response will be sent by the arduino.

No problem, I have that set up correctly on the arduino side, and verified it works.

However, in my when Btn_BP_Send.Click procedure after sending the data (which I can see from the serial monitor is received successfully) I need to wait for the response from the arduino (which I can also see is sending as it should). If an ACK string is received, all good proceed. If a RETRY string is received however, I want to do something else (e.g. notify the user that something went wrong and they should try sending again). Instead, I am receiving neither of these.

Here is my .aia file. Note: I have registered for receiving strings.

ThesisSimulationApp.aia (217.5 KB)

I initially tried adding a while loop after calling the send_data procedure, but that caused my app to crash.

I guess my question if how can I "wait" for a response from the arduino, and then according to the response continue with the code in the when Btn_BP_Send.Click procedure without blocking the rest of the code (so that strings can actually be received)?

Edit:

In case they are helpful, here are 2 functions which are directly related to reading data:

When data is received while in the READING state, check it with validate_message, and send the appropriate response.

states state_reading() {
	char input_buffer[64];
	uint8_t index = 0;
	static unsigned long last_high_time = 0;
	const unsigned long disconnect_threshold = 2000; // 2 seconds of LOW before triggering
	uint8_t bt_pin_state = digitalRead(BT_STATE);
	if (bt_pin_state == HIGH) {
		last_high_time = millis();
	}
	else {
		if ((millis() - last_high_time) > disconnect_threshold) {
			log_msg("WARN", "Bluetooth disconnected during READING");
			return DISCONNECTED;
		}
	}
	while (HM10_UART.available()) {
		log_msg("DEBUG", "Reading data");
		char incoming_byte = HM10_UART.read();
		char debug_char[2] = {incoming_byte, '\0'};
		log_msg("DEBUG", debug_char);
		if (incoming_byte == '\n') {
			input_buffer[index] = '\0';
			size_t len = strlen(input_buffer);
			if (len > 0 && input_buffer[len - 1] == '\r') {
				input_buffer[len - 1] = '\0';
				log_msg("DEBUG", "Stripped trailing \\r from input_buffer");
			}
			log_msg("DEBUG", "Received string: ");
			log_msg("DEBUG", input_buffer);
			if (validate_message(input_buffer)) {
				HM10_UART.println("ACK");
				log_msg("INFO", "Valid data received. ACK sent.");
				strncpy(g_received_data_buffer, input_buffer, sizeof(g_received_data_buffer));
				g_received_data_buffer[sizeof(g_received_data_buffer) - 1] = '\0'; // ensure that the last character is the null terminator no matter what
				return PROCESSING;
			}
			else {
				HM10_UART.println("RETRY");
				log_msg("INFO", "Invalid data received. Retry request sent.");
			}
			index = 0;
		}
		else if (index < sizeof(input_buffer) - 1) {
			input_buffer[index++] = incoming_byte;
		}
		else { // Buffer overflow
			index = 0;
			log_msg("WARN", "Input buffer overflow. Resetting.");
		}
	}
	return READING;
}

validate_message checks that the received data is in the correct format


bool validate_message(const char *msg) {
	if (strncmp(msg, "BP:", 3) == 0) {
		log_msg("DEBUG", "Received data is from a BP device.");
		const char* data = msg + 3;
		int systolic, diastolic, consumed = 0;
		if (sscanf(data, "%d/%d%n", &systolic, &diastolic, &consumed) == 2) {
			if (data[consumed] == '\0') {
				return true;
			}
		}
	}
	else if (strncmp(msg, "TEMP:", 5) == 0) {
		log_msg("DEBUG", "Received data is from a TEMP device.");
		const char* data = msg + 5;
		char* endptr;
		strtod(data, &endptr);
		if (endptr != data && *endptr == '\0') {
			return true;
		}
	}
	else if (strncmp(msg, "HR:", 3) == 0) {
		log_msg("DEBUG", "Received data is from a HR device.");
		const char* data = msg + 3;
		char* endptr;
		strtol(data, &endptr, 10);
		if (endptr != data && *endptr == '\0') {
			return true;
		}
	}
	return false;
}

Use a clock component instead of a while loop
Taifun

For BLE, instead of


use

(I am assuming the strings don't have extra text like \n attached, that would break the comparisons. In such a case, use the text CONTAINS test.

2 Likes

The responses from the arduino are simple strings HM10_UART.println("ACK");
(I was using println but changed it to print to avoid any \r\n shenanigans) and I tried both with the is in list? as well as the contains blocks but got the same unknown response back, even though in my main virtual screen's logs (what update_logs writes to) I see [RECEIVED] ACK

Interestingly I added another update_logs call at the end of when Btn_BP_Send.Click which prints out the value of global response_msg at that point, and it seems to be empty, followed by the [RECEIVED] ACK line. So I think while in the button click function, for some reason its not reading string back from the BLE module, or the click function finishes too fast and the ACK is set after it runs.

In my app's logs (which are just a label I keep adding to when I want to see something so I cant copy stuff from it) I have the following:

[DEBUG] BP:120/80
                        <-- This new line is not a mistake from when I was typing it all here, this also shows up in the logs I guess because of the \n character at the end of the data
[DEBUG] Current response contains: None <-- This is the initial value of response_msg and also what I reset it to at the end of Btn_BP_Send.Click
[RECEIVED] ACK <-- At this point the data has already been received on my arduino and the ACK has obviously been sent

Could you please elaborate? I've never used a clock component and I'm not sure how to use it to wait for a response while remaining in the when Btn_BP_Send.Click function. I'd like to try this as well.

I suggest squeezing this into your log line JOINs, to see the timing of events.

Using a local variable for the common log code would save you a dozen lines of code.

The Clock is in the Sensors Drawer of the Designer.

Also, don't expect to find responses in global variables within the same code that sends requests. That's what Data Received Events are for.

App Inventor is event oriented programming
You do not wait, instead you have different events, which occur and in these events you can do something

For example in the StringsReceived event you do something after strings have been received. Just use that event...

Taifun

I added the clock event to my update_logs procedure, this should be really helpful to track down issues, thanks!

I'll try to work with what Taifun said about App Inventor being event driven rather than the type of programming im used to (thanks by the way, that actually cleared up a lot for me) and add the notification to the StringsReceived event if a RETRY response is received rather than try to handle that in the event for the button click. I guess its ok for my button to just send the data it has and then hand all control away.

Hopefully I can figure this out, but if not I'll be back to this thread to pester everyone some more (thanks by the way :smiley: )