Browse Source

Integrated tnc_kiss and bluetooth spp code from bluetooth-spp-dev branch

curiousmuch 4 years ago
parent
commit
233680a0b1
2 changed files with 454 additions and 0 deletions
  1. 194 0
      main/bt_spp.c
  2. 260 0
      main/tnc_kiss.c

+ 194 - 0
main/bt_spp.c

@@ -0,0 +1,194 @@
+/*
+ * Project: Arrow
+ * Author: 	curiousmuch
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "esp_bt.h"
+#include "esp_bt_main.h"
+#include "esp_gap_bt_api.h"
+#include "esp_bt_device.h"
+#include "esp_spp_api.h"
+
+
+#include "tnc_kiss.c"
+
+// Why are these global? Should I move this to a .h settings file?
+#define DEVICE_NAME		"Arrow"
+#define SPP_TAG			"BT SPP"
+#define SPP_SERVER_NAME	"Arrow_TNC"
+
+static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;
+static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;//ESP_SPP_SEC_AUTHENTICATE;
+static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;
+
+// SPP Callback
+static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
+{
+	switch(event) {
+	case ESP_SPP_INIT_EVT:
+		// called when SPP initialize
+		ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_ESP");
+		esp_bt_dev_set_device_name(DEVICE_NAME);
+		esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+		esp_spp_start_srv(sec_mask, role_slave, 0, SPP_SERVER_NAME);
+		break;
+	case ESP_SPP_DISCOVERY_COMP_EVT:
+		// called with SPP discovery is complete
+		ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
+		break;
+	case ESP_SPP_OPEN_EVT:
+		// called when SPP client connection opens
+		ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
+		break;
+	case ESP_SPP_CLOSE_EVT:
+		// called when SPP client connection closes
+		ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
+		break;
+	case ESP_SPP_CL_INIT_EVT:
+		// called when SPP client initiated a connection
+		ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
+		break;
+	case ESP_SPP_DATA_IND_EVT:
+		// called when SPP connection receives data
+		ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT len=%d handle=%d", param->data_ind.len, param->data_ind.handle);
+		esp_log_buffer_hex("", param->data_ind.data, param->data_ind.len);
+		tnc_receive(param->data_ind.data, param->data_ind.len);
+		// send data to Arrow State-Machine
+		// esp_spp_write(param->data_ind.handle, param->data_ind.len, param->data_ind.data);
+		break;
+	case ESP_SPP_CONG_EVT:
+		// called when SPP connection congestion status changes
+		ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT");
+		break;
+	case ESP_SPP_WRITE_EVT:
+		// called when SPP write operation completes
+		ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT");
+		// remove the need to write from the write queue
+		break;
+	case ESP_SPP_SRV_OPEN_EVT:
+		// called when SPP server connection opens
+		ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
+		break;
+	default:
+		break;
+	}
+}
+
+void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
+{
+	switch (event) {
+	case ESP_BT_GAP_AUTH_CMPL_EVT:{
+		// called with authentication process complete
+		if(param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
+			ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name);
+			esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
+		} else {
+			ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
+		}
+		break;
+	}
+	case ESP_BT_GAP_PIN_REQ_EVT:{
+		// called when legacy pairing pin code requested
+		ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
+		if (param->pin_req.min_16_digit) {
+			ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
+			esp_bt_pin_code_t pin_code = {0};
+			esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
+		} else {
+			ESP_LOGI(SPP_TAG, "Input pin code: 1234");
+			esp_bt_pin_code_t pin_code;
+			pin_code[0] = '1';
+			pin_code[1] = '2';
+			pin_code[2] = '3';
+			pin_code[3] = '4';
+			esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
+		}
+		break;
+	}
+	case ESP_BT_GAP_CFM_REQ_EVT:
+		// called for simple pairing user confirmation
+		ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
+		esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
+		break;
+	case ESP_BT_GAP_KEY_NOTIF_EVT:
+		// called during simple pairing passkey notification
+		ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
+		break;
+	case ESP_BT_GAP_KEY_REQ_EVT:
+		// called during simple pairing passkey request
+		ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
+		break;
+	default:
+		ESP_LOGI(SPP_TAG, "event: %d", event);
+		break;
+	}
+	return;
+}
+
+
+void bt_spp_init(void)
+{
+    // Bluetooth Controller Initialization
+    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
+
+    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+    esp_err_t ret;
+    if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    // Bluetooth Stack Initialization
+    if ((ret = esp_bluedroid_init()) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    if ((ret = esp_bluedroid_enable()) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    // Set Bluetooth GAT Callback
+    if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    // Set Bluetooth SPP Callback
+    if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+    if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) {
+        ESP_LOGE(SPP_TAG, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret));
+        return;
+    }
+
+	esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
+	esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
+	esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
+
+   /*
+	* Set default parameters for Legacy Pairing
+	* Use variable pin, input pin code when pairing
+	*/
+   esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
+   esp_bt_pin_code_t pin_code;
+   esp_bt_gap_set_pin(pin_type, 0, pin_code);
+}

+ 260 - 0
main/tnc_kiss.c

@@ -0,0 +1,260 @@
+/*
+ * tnc_kiss.c
+ *
+ *  Created on: Feb 26, 2019
+ *      Author: curiousmuch
+ */
+
+// basic set of functions at this moment which decodes KISS
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/ringbuf.h"
+#include "esp_log.h"
+#include <stdio.h>
+
+// Logging Tag
+#define TNC_TAG "KISS TNC"
+
+// Frame Buffer
+#define FRAME_BUFFER_SIZE 512
+
+// KISS Specific Characters
+#define KISS_FEND 	0xC0
+#define KISS_FESC 	0xDB
+#define KISS_TFEND 	0xDC
+#define KISS_TFESC 	0xDD
+
+// KISS Commands
+// command Structure: [ 4 bits ][ 4 bits ]
+//				 	  [ TNC #  ][   cmd  ]
+#define KISS_DATAFRAME			0x00		// data to be send on HDLC channel
+#define KISS_CMD_TXDELAY		0x01		// keyup delay in 10ms units. default is 50ms.
+#define KISS_CMD_P				0x02
+#define KISS_CMD_SLOTTIME		0x03
+#define KISS_CMD_TXTAIL 		0x04
+#define KISS_CMD_FULLDUPLEX		0x05
+#define KISS_CMD_SETHARDWARE	0x06
+#define KISS_CMD_RETURN			0xFF
+
+ typedef struct {
+	uint8_t tx_delay;
+	uint8_t persistence;
+	uint8_t slot_time;
+	uint8_t tx_tail;
+	uint8_t full_duplex;
+	uint8_t tnc_number;
+//	uint8_t max_frame_size;
+} tnc_settings_t;
+
+typedef enum {
+	ESC_MODE = 0,
+	FRAME_ASS,
+	FRAME_END,
+} KISS_STATE_t;
+
+typedef struct {
+	uint8_t 	buf[FRAME_BUFFER_SIZE];
+	uint32_t 	max_len;
+	uint32_t 	index;
+} buffer_handle_t;
+
+buffer_handle_t buffer_handle;
+tnc_settings_t tnc_settings;
+
+void kiss_clear_buffer(void)
+{
+	ESP_LOGD(TNC_TAG, "TNC buffer cleared.");
+	buffer_handle.index = 0;
+}
+
+void kiss_append_buffer(uint8_t chr)
+{
+	ESP_LOGD(TNC_TAG, "Appending %x to TNC buffer.", chr);
+	// ERROR - Packet Buffer Overflow
+	if (buffer_handle.index >= buffer_handle.max_len)
+	{
+		ESP_LOGE(TNC_TAG, "TNC buffer overflow");
+		kiss_clear_buffer();
+	}
+	buffer_handle.buf[(buffer_handle.index)] = chr;
+	buffer_handle.index = buffer_handle.index + 1;
+}
+
+// Frame Format
+// [ Command Byte ][ Data ]
+void kiss_process_frame(void)
+{
+    uint8_t data_byte, tnc_number;
+    data_byte = buffer_handle.buf[0];
+    tnc_number = (data_byte >> 4) & 0x0F;
+
+    if (tnc_number != tnc_settings.tnc_number)					// ignore irrelevent TNC data
+    	return;
+
+	switch( data_byte ) {
+		case KISS_DATAFRAME: {
+			ESP_LOGI(TNC_TAG, "Received Data Frame - Length %d", (buffer_handle.index));
+			ESP_LOG_BUFFER_HEXDUMP(TNC_TAG, buffer_handle.buf, buffer_handle.index, ESP_LOG_INFO);
+			// unblock AX.25 code to process code
+			break;
+		}
+		case KISS_CMD_TXDELAY: {
+			ESP_LOGI(TNC_TAG, "Updated TX Delay");
+			tnc_settings.tx_delay = buffer_handle.buf[1];
+			break;
+		}
+		case KISS_CMD_P: {
+			ESP_LOGI(TNC_TAG, "Update Persistence");
+			tnc_settings.persistence = buffer_handle.buf[1];
+			break;
+		}
+		case KISS_CMD_SLOTTIME: {
+			ESP_LOGI(TNC_TAG, "Updated Slottime");
+			tnc_settings.slot_time = buffer_handle.buf[1];
+			break;
+		}
+		case KISS_CMD_TXTAIL: {
+			ESP_LOGI(TNC_TAG, "Updated TX Tail");
+			tnc_settings.tx_tail= buffer_handle.buf[1];
+			break;
+		}
+		case KISS_CMD_FULLDUPLEX: {
+			ESP_LOGI(TNC_TAG, "Updated Full/Half Duplex Setting");
+			tnc_settings.full_duplex = buffer_handle.buf[1];
+			break;
+		}
+//		case KISS_CMD_SETHARDWARE: {
+//			break;
+//		}
+//		case KISS_CMD_RETURN: {
+//			tnc_settings.tx_delay = buffer_handle.buff_ptr[1];
+//			break;
+//		}
+		default: {
+			// error
+			break;
+		}
+	}
+	kiss_clear_buffer();
+	return;
+}
+
+typedef struct {
+	uint8_t *data;
+	uint16_t len;
+} raw_kiss_frame_t;
+
+typedef struct {
+	uint8_t *data;
+	uint16_t len;
+} raw_ax25_frame_t;
+
+#define TNC_INPUT_QUEUE_LEN 1
+#define TNC_INPUT_QUEUE_ITEM_SIZE sizeof(raw_kiss_frame_t)
+#define TNC_OUTPUT_QUEUE_LEN 1
+#define TNC_INPUT_QUEUE_ITEM_SIZE sizeof(raw_ax25_frame_t)
+
+// TNC needs the ability to RX KISS frame, decode it, schedule it, and then send out via APRS or Arrow-Net
+// TNC needs the ability to switch to RX mode,
+
+void tnc_init(tnc_settings_t s)
+{
+	buffer_handle.max_len = FRAME_BUFFER_SIZE;			// TODO: make frame buffer dynamic
+//	tnc_settings.max_frame_size = FRAME_BUFFER_SIZE;
+
+	// TODO: Store / retrieve default TNC settings from flash?
+
+	// set default TNC settings
+	tnc_settings.tx_delay = s.tx_delay;
+	tnc_settings.persistence = s.persistence;
+	tnc_settings.slot_time = s.slot_time;
+	tnc_settings.tx_tail = s.tx_tail;
+	tnc_settings.full_duplex = s.full_duplex;
+	tnc_settings.tnc_number = s.tnc_number;
+
+	// setup TNC task with queue
+}
+
+// Decode KISS-TNC data from SPP Interface and combine until there is
+// is a valid packet. Pass valid packet / frame to AX.25 engine.
+// Data Flow:
+// (1) SPP interface hands over data array which is runs a state machine to decode the KISS Frame
+// (2) Decoded information is uploaded thrown into a buffer
+// (3) When frame is complete the state machine will copy or indicate to another task to dump the buffer
+// If the frame is not completed, the state machine will wait until more data is provided by the SPP interface.
+// If the frame is corrupted or the buffer fills, the frame will be dumped and the error must be logged.
+KISS_STATE_t kiss_state = FRAME_END;
+void tnc_receive(uint8_t* data, uint16_t len)
+{
+	uint32_t i;
+	uint8_t chr;
+	for (i=0; i<len; i++)
+	{
+		chr = data[i];
+		switch(kiss_state) {
+			case ESC_MODE: {
+				if (chr == KISS_TFEND)
+				{
+					kiss_append_buffer(KISS_FEND);		// append FEND to frame
+					kiss_state = FRAME_ASS;				// return to assembly
+				}
+				else if (chr == KISS_TFESC)
+				{
+					kiss_append_buffer(KISS_FESC);		// append FESC to frame
+					kiss_state = FRAME_ASS;				// return to assembly
+				}
+				else if (chr == KISS_FEND)
+				{
+					kiss_process_frame();				// process frame
+					kiss_state = FRAME_END;				// end frame assembly
+				}
+				else
+				{
+					// log error
+					kiss_append_buffer(chr);			// append chr to frame
+					kiss_state = FRAME_ASS;				// return to assembly
+				}
+				break;
+			}
+			case FRAME_END: {
+				if (chr != KISS_FEND)
+				{
+					kiss_append_buffer(chr);			// append chr to frame
+					kiss_state = FRAME_ASS;				// return to assembly
+				}
+				else
+				{
+					kiss_state = FRAME_END;				// stay in frame end
+				}
+				break;
+			}
+			case FRAME_ASS: {
+				if (chr == KISS_FESC)
+				{
+					kiss_state = ESC_MODE;
+				}
+				else if (chr == KISS_FEND)
+				{
+					kiss_process_frame();				// process frame
+					kiss_state = FRAME_END;				// end frame assembly
+				}
+				else
+				{
+					kiss_append_buffer(chr);			// append chr to frame
+					kiss_state = FRAME_ASS;				// continue frame assembly
+				}
+				break;
+			default: {
+				ESP_LOGE(TNC_TAG, "TNC FSM Error");
+			}
+			}
+		}
+	}
+}
+
+
+
+
+
+