Explorar o código

ability to skip cmd of write batch

vad7 %!s(int64=2) %!d(string=hai) anos
pai
achega
51d9d97edc
Modificáronse 4 ficheiros con 102 adicións e 53 borrados
  1. BIN=BIN
      Pics/descript.png
  2. 13 9
      README.md
  3. 9 8
      descript.txt
  4. 80 36
      nrf24batch.c

BIN=BIN
Pics/descript.png


+ 13 - 9
README.md

@@ -1,25 +1,29 @@
 # nRF24-Batch
-Flipper Zero application for nRF24L01 external board. Sends batch commands. (IN DEVELOP...)
+Flipper Zero application for nRF24L01 external board. Sends batch commands.
 
 <b>Приложение для Flipper Zero, предназначено для отправки пакетных команд на удаленные устройства, использующие радио nRF24L01.</b><br><br>
-Можно использовать для настройки или чтения данных с удаленного устройства. На удаленной стороне для команды чтения требуется поддержка.<br>
+Можно использовать для настройки или чтения данных с удаленного устройства. На удаленной стороне требуется поддержка, ссылка на код для микроконтроллера AVR ниже.<br>
+<br>
 Сначала выбирается файл настройки с описанием команд.  
 Затем стрелками влево или вправо выбирается нужный режим - Пакетное чтение (Read Batch), Чтение по одной команде (Read cmd), Пакетная запись (Write Batch).  
 <br><br>
 Есть два вида команд: запрос-ответ и запись.<br>
-Запрос-ответ - отправка пакета, переключение на прием и отображение на экране, что получили.<br>
-Запись - фактически отправка пакетов подряд с нужными данными.<br>
+Запрос-ответ (Read) - отправка пакета, переключение на прием и отображение на экране, что получили.<br>
+Запись (Write) - фактически отправка пакетов подряд с нужными данными.<br>
 <br><br>
 Формат пакета для отправки (payload) задается в виде размера полей структуры в байтах, например, так "Payload struct: 2,1,1", что означает структуру из 3 полей: 2 байта, 1 байт, 1 байт.<br>
-Полученный в ответ пакет состоит из одного значения, размерность по умолчанию 1 байт (int8), при необходимости, задается числом после '*' после имени команды.<br><br>
-Перед отправкой пакета, он заполняется сначала шаблону по умолчанию "R default" для запроса чтения, "W default" - для записи.<br>
-Можно использовать константы по их имени, они задаются в формате "имя=число", число либо десятичное или шестнадцатеричное с префиксом 0x.<br>
+Полученный в ответ пакет (такой же длины как и отправленный) состоит из одного значения (остаток пакета не используется), размерность по умолчанию 1 байт (int8), при необходимости, она задается числом после '*' после имени команды.<br><br>
+Перед отправкой пакета, он заполняется сначала по шаблону по умолчанию "R default" для запроса чтения, "W default" - для записи.<br>
+Поля разделены - ','. Для "W default" есть специальный маркер 'n', говорящий какое поле будет заполняться значением после '=' из команды "WBatch".<br>
+Можно использовать константы по их имени, они задаются в файле в формате "имя=число", число либо десятичное или шестнадцатеричное с префиксом 0x.<br>
 Затем берутся заполненные значения полей из самой команды ("R:" или "W:").<br>
-Если в конце строки с командой чтения символ '#', считанное значение будет показано в шестнадцатеричном виде.<br><br>
-Пакет состоит из списка имен команд, перечисленных через ";".<br><br>
+Если в конце строки с командой чтения символ '#', то считанное значение будет показано в шестнадцатеричном виде.<br><br>
+Пакеты чтения "RBatch:" и записи "WBatch:" состоят из списка имен команд "R:" или "W:", соответственно, перечисленных через ";".<br><br>
 Отправка пакета для записи - длительно нажать Ok в списке и подтвердить.<br>
 Перед пакетом команд для записи отправляется пакет 'Write start', если эта строка присутствует в файле настроек.<br><br>
 Значение команды для записи можно редактировать - Ok на списке команд, стрелки - +/- и переход по цифрам, завершить - Назад, вставка цифры - Ok, удаление цифры - длительный Ok.<br>
+Из списка команд в пакетах чтения и записи команды можно убирать - для этого нужно долго нажать на кнопку влево и подтвердить.<br>
+Это нужно, например, чтобы убирать те команды в сохраненном пакете для чтения, для которых нет команды записи.<br>
 <br>
 Пример файл [CO2_mini](https://raw.githubusercontent.com/vad7/nRF24-Batch/main/Distr/nrf24batch/CO2_mini.txt)<br>
 Для устройства на Attiny44A, которое отправляет данные с датчика CO2 на контроллеры, управляющие вентиляцией или проветриватели: https://github.com/vad7/CO2-mini

+ 9 - 8
descript.txt

@@ -1,9 +1,10 @@
 Info: CO2 sensor mini
+Address: C8C8CF   <- nRF24 address (3..5 bytes)
 Rate: 1	          <- nRF24 data rate (0 - 250Kbps, 1 - 1Mbps, 2 - 2Mbps)
 Ch: 121           <- nRF24 channel (0..125)
 CRC: 2            <- nRF24 CRC size
 DPL: 0            <- nRF24 dynamic payload en/dis
-Address: C8C8CF   <- nRF24 address (3..5 bytes)
+RETR: 0x0F        <- nRF24 SETUP_RETR register (address 04)
 Resend: 3         <- resend attempts on error
 Delay_ms: 30      <- delay between cmd and resend
 
@@ -14,21 +15,21 @@ R default: ,EEPROM,0xC1     <- default for read
 W default: n,,0x81          <- default for write, 'n' - means field value position
 Write start: 0,0,0x8F       <- if exist in the file - packet before write a batch
 
-R: ID*=,ID                  <- Read cmd, '*' - means string like device ID, result in bytes = { 0, 0, 3, 0xC1 }
+R: ID*=,ID                  <- Read cmd, '*' - means string like device ID, payload result in bytes = { 0, 0, 3, 0xC1 }
 R: OSCCAL=0x51,RAM          <- Read cmd, result in bytes = { 0x51, 0, 1, 0xC1 }
-R: OSCCAL_EMEM=0#           <- Read cmd, result in bytes = { 0, 0, 0, 0xC1 }, in the end of line - '#', returned value in hexadecimal format
+R: OSCCAL_EMEM=0#           <- Read cmd, result in bytes = { 0, 0, 0, 0xC1 }, in the end of line '#' - returned value in hexadecimal format
 R: CO2=0x67,RAM,0xC2        <- Read cmd, result in bytes = { 0x67, 0, 1, 0xC2 }
 
 R: CO2 threshold*2=5,,0xC2  <- Read cmd, '*2' - means received field with 2 bytes size (int16), result in bytes = { 5, 0, 0, 0xC2 }
-W: CO2 threshold=,5,0x82    <- Write cmd, send pkt = { <from WBatch 1 byte>, <from WBatch 2 byte>, 5, 0x82 }
+W: CO2 threshold=,5,0x82    <- Write cmd, send pkt = { <from WBatch first byte of value>, <from WBatch second byte of value>, 5, 0x82 }
 
-R: FanLSB[10]=i:9           <- Read cmd, '[n]' - means array with 'n' elements, 'i:n' - means array index field that will be incemented after next cmd
+R: FanLSB[10]=i:9           <- Read cmd, '[10]' - means array with 10 elements, 'i:9' - means array index field (9) that will be incemented after each send
                                result in bytes = 1 pkt: { 9, 0, 0, 0xC1 }, 2 pkt: { 10, 0, 0, 0xC1 }, 3 pkt: { 10, 0, 0, 0xC1 },... total 10 packets
 W: FanLSB=,i:9              <- Write cmd, 'i' - index field. Send packets: { n & 0xFF, n >> 8, 9, 0x81 }, { n & 0xFF, n >> 8, 10, 0x81 },...  
-W: RxAddr=,1
-W: Reset=,RESET,0xC1
+W: RxAddr=,1                <- Write cmd, payload = { n, 0, 1, 0x81 }
+W: Reset=,RESET,0xC1        <- Send payload = { 0, 0, 4, 0xC1 }
 
 RBatch: Settings: ID;OSCCAL;RxAddr;Ch;nRF RETR;CO2 threshold;CO2 correct;FanLSB   <- Read Batch, 8 packets will be sent and received
 
-WBatch: Default: RxAddr=0xCF;CO2 threshold=1000;Reset  <- Write Batch, Send: { 0,0,0,0x8F }, { 0xCF, 0, 1, 0x81 }, { 0xE8, 0x03, 5, 0x82 }, { 0, 0, 4, 0x81 }
+WBatch: Default: RxAddr=0xCF;CO2 threshold=1000;Reset  <- Write Batch, Send: { 0,0,0,0x8F }, { 0xCF, 0, 1, 0x81 }, { 0xE8, 0x03, 5, 0x82 }, { 0, 0, 4, 0xC1 }
 WBatch: Fans: FanLSB={0xC2,0xC3,0}                     <- Write Batch, Send: { 0,0,0,0x8F }, { 0xC2, 0, 9, 0x81 }, { 0xC3, 0, 10, 0x81 }

+ 80 - 36
nrf24batch.c

@@ -43,6 +43,7 @@ const char AskQuestion_Save[] = "SAVE BATCH?";
 #define Settings_i 'i'
 #define Settings_n 'n'
 #define VAR_EMPTY ((int32_t)0x80000000)
+#define FONT_5x7_SCREEN_WIDTH 25
 
 nRF24Batch* APP;
 uint8_t what_doing = 0; // 0 - setup, 1 - cmd list, 2 - view send cmd
@@ -68,10 +69,10 @@ bool cmd_array_hex;
 uint8_t save_settings = 0;
 uint16_t view_cmd[3] = {0, 0, 0}; // ReadBatch, Read, WriteBatch
 uint8_t view_x = 0;
-char Info[32] = "";
 char screen_buf[64];
-char file_name[32];
-char ERR_STR[32];
+char Info[FONT_5x7_SCREEN_WIDTH] = "";
+char file_name[FONT_5x7_SCREEN_WIDTH];
+char ERR_STR[FONT_5x7_SCREEN_WIDTH];
 uint8_t ERR = 0;
 uint8_t NRF_rate; 	// 0 - 250Kbps, 1 - 1Mbps, 2 - 2Mbps
 uint8_t NRF_channel;// 0..125
@@ -93,8 +94,8 @@ uint8_t payload_receive[32];
 uint8_t payload_struct[32]; 	// sizeof(1..4) in bytes of each field, example: 2,1,1
 uint8_t payload_fields = 0;
 uint8_t payload_size = 0;		// bytes
-uint16_t view_Batch = 0;		// view pos
-uint16_t view_WriteBatch = 0;	// view pos
+uint16_t view_Batch = 0;		// view pos in Batch or inside WriteBatch (Log[view_Batch])
+uint16_t view_WriteBatch = 0;	// view pos of WriteBatch list
 uint32_t delay_between_pkt = 10;// ms
 uint8_t Edit = 0;
 char   *Edit_pos;
@@ -120,6 +121,7 @@ uint16_t WriteBatch_cmd_curr = 0;	// == _Total - finish
 enum {
 	ask_write_batch = 1,
 	ask_save_batch,
+	ask_skip_cmd,
 	ask_return,
 	ask_exit
 };
@@ -130,6 +132,7 @@ static bool ask_fill_screen_buf(void)
 {
 	if(ask_question == ask_write_batch) strcpy(screen_buf, "RUN WRITE BATCH?");
 	else if(ask_question == ask_save_batch) strcpy(screen_buf, "SAVE AS WRITE BATCH?");
+	else if(ask_question == ask_skip_cmd) strcpy(screen_buf, "SKIP CMD?");
 	else if(ask_question == ask_return) strcpy(screen_buf, "RETURN?");
 	else if(ask_question == ask_exit) strcpy(screen_buf, "EXIT?");
 	else return false;
@@ -198,7 +201,7 @@ static void add_to_str_hex_bytes(char *out, uint8_t *arr, int bytes)
 void free_Log()
 {
 	if(Log_Total) {
-		for(uint16_t i = 0; i < Log_Total; i++) furi_string_free(Log[i]);
+		for(uint16_t i = 0; i < Log_Total; i++) if(Log[i]) furi_string_free(Log[i]);
 		Log_Total = 0;
 	}
 	if(Log) {
@@ -442,8 +445,9 @@ bool fill_payload(char *p, uint8_t *idx_i, int32_t var_n)
 			b = subs_constant(p, end ? (uint8_t)(end - p) : strlen(p));
 			if(b == VAR_EMPTY) {
 				ERR = 1;
+				memset(ERR_STR, 0, sizeof(ERR_STR));
 				strcpy(ERR_STR, "No ");
-				strcat(ERR_STR, p);
+				strncpy(ERR_STR + strlen(ERR_STR), p, sizeof(ERR_STR) - 4);
 				FURI_LOG_D(TAG, "Constant not found: %s", p);
 				return false;
 			}
@@ -487,7 +491,7 @@ bool Run_Read_cmd(FuriString *cmd)
 		return false;
 	}
 	FuriString *fs = furi_string_alloc();
-	furi_string_set_strn(fs, (char*)furi_string_get_cstr(cmd), (*(p-2)=='*' ? p-2 : p) - (char*)furi_string_get_cstr(cmd)); // skip *n
+	furi_string_set_strn(fs, (char*)furi_string_get_cstr(cmd), p - (char*)furi_string_get_cstr(cmd));
 	furi_string_cat_str(fs, ": ");
 	bool hexval;
 	if((hexval = *(p + strlen(p) - 1) == '#')) furi_string_cat_str(fs, "0x"); // value in Hex format
@@ -523,7 +527,11 @@ bool Run_ReadBatch_cmd(FuriString *cmd)
 	char *p;
 	if(cmd) {
 		p = strchr((char*)furi_string_get_cstr(cmd), ':');
-		if(p == NULL) return false;
+		if(p == NULL) {
+			ERR = 5;
+			strcpy(ERR_STR, "WRONG FORMAT");
+			return false;
+		}
 		p += 2;
 		ReadBatch_cmd_curr = NULL;
 		free_Log();
@@ -543,13 +551,20 @@ bool Run_ReadBatch_cmd(FuriString *cmd)
 			char c = *((char*)furi_string_get_cstr(fs) + len);
 			if(c != '=' && c != '*' && c != '[') continue;
 			if(end) ReadBatch_cmd_curr = end + 1; else ReadBatch_cmd_curr = (char*)0xFFFFFFFF;
-			Run_Read_cmd(fs);
+			if(!Run_Read_cmd(fs)) break;
 			return true;
 		}
 	}
-	ERR = 4;
-	strcpy(ERR_STR, "Not found");
-	FURI_LOG_D(TAG, "CMD %s: %s", ERR_STR, p == NULL ? "" : p);
+	if(NRF_ERROR) return false;
+	if(ERR == 0) {
+		ERR = 4;
+		strcpy(ERR_STR, "NOT FOUND");
+		FuriString *fs = furi_string_alloc();
+		furi_string_set_strn(fs, p, len);
+		Log[Log_Total++] = fs;
+		FURI_LOG_D(TAG, "CMD %s: %s", ERR_STR, p);
+	}
+	view_Batch = Log_Total ? Log_Total - 1 : 0;
 	return false;
 }
 
@@ -839,7 +854,6 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
 	furi_message_queue_put(event_queue, &event, FuriWaitForever);
 }
 
-#define FONT_5x7_SCREEN_WIDTH 25
 void render_display_list(Canvas* const canvas, FuriString ***fsa, char delim, uint16_t view_pos, uint16_t max_i)
 {
 	uint16_t page = view_pos & ~7;
@@ -865,6 +879,16 @@ void render_display_list(Canvas* const canvas, FuriString ***fsa, char delim, ui
 	}
 }
 
+void display_remove_asterisk(char *fsp, uint8_t vx)
+{
+	char *p2 = strchr(fsp, '*');
+	if(p2) { // remove '*' or '*n'
+		int pos = p2 - fsp;
+		if((pos -= vx) < 0) pos = 0;
+		memmove(screen_buf + pos, screen_buf + pos + (*(p2 + 1) == ':' ? 1 : 2), FONT_5x7_SCREEN_WIDTH + 1);
+	}
+}
+
 static void render_callback(Canvas* const canvas, void* ctx) {
 	const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
 	if(plugin_state == NULL) return;
@@ -905,20 +929,22 @@ static void render_callback(Canvas* const canvas, void* ctx) {
 	} else { // what_doing == 2
 		if(rw_type == rwt_read_cmd) {	// Read command
 			canvas_set_font(canvas, FontSecondary); // 8x10 font, 6 lines
-			if(!ask_fill_screen_buf()) strcpy(screen_buf, "Read cmd:");
+			if(!ask_fill_screen_buf()) strcpy(screen_buf, "Read cmd: ");
 			if(NRF_ERROR) strcat(screen_buf, "nRF24 ERROR!");
 			else if(ERR) {
-				snprintf(screen_buf + strlen(screen_buf), 16, " Error %d", ERR);
+				snprintf(screen_buf + strlen(screen_buf), FONT_5x7_SCREEN_WIDTH, "ERROR %d", ERR);
 				canvas_draw_str(canvas, 0, 60, ERR_STR);
-			} else if(send_status == sst_sending) strcat(screen_buf, " sending");
-			else if(send_status == sst_receiving) strcat(screen_buf, " receiving");
-			else if(send_status == sst_error) strcat(screen_buf, " NO ACK!");
-			else if(send_status == sst_timeout) strcat(screen_buf, " TIMEOUT!");
-			else if(send_status == sst_ok) strcat(screen_buf, " Ok");
+			} else if(send_status == sst_sending) strcat(screen_buf, "sending");
+			else if(send_status == sst_receiving) strcat(screen_buf, "receiving");
+			else if(send_status == sst_error) strcat(screen_buf, "NO ACK!");
+			else if(send_status == sst_timeout) strcat(screen_buf, "TIMEOUT!");
+			else if(send_status == sst_ok) strcat(screen_buf, "OK");
 			canvas_draw_str(canvas, 0, 10, screen_buf);
 			if(Log_Total) {
 				char *p = (char*)furi_string_get_cstr(Log[Log_Total - 1]);
-				strncpy(screen_buf, p + MIN(view_x, strlen(p)), 30);
+				uint8_t vx = MIN(view_x, strlen(p));
+				strncpy(screen_buf, p + vx, 30);
+				display_remove_asterisk(p, vx);
 				canvas_draw_str(canvas, 0, 15 + 10, screen_buf);
 			}
 
@@ -928,12 +954,12 @@ static void render_callback(Canvas* const canvas, void* ctx) {
 				strcpy(screen_buf, rw_type == rwt_read_batch ? "Read Batch: " : what_doing == 1 ? "Write Batch: " : "Write: ");
 				if(rw_type == rwt_read_batch || send_status != sst_none) {
 					if(NRF_ERROR) strcat(screen_buf, "nRF24 ERROR!");
-					else if(ERR) snprintf(screen_buf + strlen(screen_buf), FONT_5x7_SCREEN_WIDTH - 7, "Error %d", ERR); 
+					else if(ERR) snprintf(screen_buf, sizeof(screen_buf), "ERROR %s", ERR_STR); 
 					else if(send_status == sst_error) strcat(screen_buf, "NO ACK!");
 					else if(send_status == sst_timeout) strcat(screen_buf, "TIMEOUT!");
 					else if(send_status == sst_ok && ((rw_type == rwt_read_batch && (uint32_t)ReadBatch_cmd_curr == 0xFFFFFFFF) 
 						|| (rw_type == rwt_write_batch && WriteBatch_cmd_curr == Log_Total)))
-						strcat(screen_buf, "Ok");
+						strcat(screen_buf, "OK");
 					else strcat(screen_buf, "working");
 				} else if(rw_type == rwt_write_batch) {
 					char *p = (char*)furi_string_get_cstr(WriteBatch_cmd[view_cmd[rwt_write_batch]]);
@@ -978,7 +1004,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
 					}
 					strncpy(screen_buf, p + vx, FONT_5x7_SCREEN_WIDTH);
 					screen_buf[FONT_5x7_SCREEN_WIDTH] = '\0';
-					//if(ERR && page + i == Log_Total - 1) strcat(screen_buf, ERR_STR);
+					display_remove_asterisk(p, vx);
 					canvas_draw_str(canvas, 6, y, screen_buf);
 				}
 			}
@@ -1119,7 +1145,7 @@ int32_t nrf24batch_app(void* p) {
 				case InputKeyLeft:
 					if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) {
 						if(ask_question) {
-							ask_question_answer ^= 1;
+							if(event.input.type == InputTypeShort) ask_question_answer ^= 1;
 						} else if(what_doing == 0) {
 						} else if(what_doing == 1) {
 							if(event.input.type == InputTypeShort) {
@@ -1132,6 +1158,12 @@ int32_t nrf24batch_app(void* p) {
 								else if(*(Edit_pos - 1) == ',') Edit_pos -= 2;
 							} else if(view_x) view_x--;
 						}
+					} else if(event.input.type == InputTypeLong) {
+						if(!ask_question && view_x == 0 && what_doing == 2 && (rw_type == rwt_write_batch || rw_type == rwt_read_batch) 
+								&& Log_Total && Log[view_Batch] != NULL) {
+							ask_question = ask_skip_cmd;
+							ask_question_answer = 1;
+						}
 					}
 					break;
 				case InputKeyRight:
@@ -1166,6 +1198,13 @@ int32_t nrf24batch_app(void* p) {
 									WriteBatch_cmd_curr = 0;
 									Run_WriteBatch_cmd();
 									what_doing = 2;
+								} else if(ask_question == ask_skip_cmd) {
+									if(rw_type == rwt_write_batch || rw_type == rwt_read_batch) {
+										furi_string_free(Log[view_Batch]);
+										if(view_Batch < Log_Total - 1) memmove(&Log[view_Batch], &Log[view_Batch + 1], sizeof(Log) * (Log_Total - view_Batch - 1));
+										else view_Batch--;
+										Log_Total--;
+									}
 								} else if(ask_question == ask_exit) {
 									processing = false;
 								} else if(ask_question == ask_return) {
@@ -1249,23 +1288,28 @@ int32_t nrf24batch_app(void* p) {
 							}
 						}
 					} else if(event.input.type == InputTypeLong) {
-					 	if(rw_type == rwt_write_batch && what_doing == 2 && WriteBatch_cmd_Total) {
-							if(Edit) { // delete
-								FuriString *fs = Log[view_Batch];
-								if(is_digit(Edit_pos + 1, Edit_hex) || is_digit(Edit_pos - 1, Edit_hex)) { 
-									memmove(Edit_pos, Edit_pos + 1, strlen(Edit_pos));
-									furi_string_left(fs, furi_string_size(fs) - 1);
+						if(what_doing == 2 && Log_Total) {
+							if(rw_type == rwt_write_batch) {
+								if(Edit) { // delete
+									FuriString *fs = Log[view_Batch];
+									if(is_digit(Edit_pos + 1, Edit_hex) || is_digit(Edit_pos - 1, Edit_hex)) { 
+										memmove(Edit_pos, Edit_pos + 1, strlen(Edit_pos));
+										furi_string_left(fs, furi_string_size(fs) - 1);
+									}
+								} else {
+									ask_question = ask_write_batch;
+									ask_question_answer = 0;
 								}
-							} else {
-								ask_question = ask_write_batch;
+							} else if(rw_type == rwt_read_batch) {
+								ask_question = ask_save_batch;
 								ask_question_answer = 0;
 							}
-					 	}
+						}
 					}
 					break;
 				case InputKeyBack:
 					if(event.input.type == InputTypeLong) {
-						if(what_doing == 2 && (Edited || rw_type == rwt_read_batch)) {
+						if(what_doing == 2 && Edited) {
 							if(!ask_question) ask_question_answer = 1;
 							ask_question = ask_exit;
 						} else processing = false;