// ******************************************************************
//
//    File: App_SPDS100_SPP_Demo_Server.c
//    Date: 2025-07-29
//    Version: 1.00
//    Mod: SLS
//    Case: General
//	  Notes:
//		Requires reference to both BGAPI v3.3 documentation (https://docs.silabs.com/bluetooth/3.3/bluetooth-api-reference-bgapi-headers/)
//		and BGAPI v2.13 (https://www.silabs.com/documents/public/reference-manuals/bluetooth-api-reference.pdf)
//		BLEGetEvent and BLETxRxModuleCommand both depend on knowledge of these documents.
//
// 	  V1.00:
//    ------
//    - Initial release, based on App_SPPD100_SPP_Demo_Server.c, renamed to disambiguate
//		from client file
//
// ******************************************************************

#include "twn4.sys.h"
#include "apptools.h"

#define LOG_CHANNEL CHANNEL_USB


// Toggle this to enable/disable debugging
#define DEBUG_ENABLED 1 // set to 1-enable, 0-disable debug output

#if DEBUG_ENABLED
#define DEBUG_HEX(String, Data, ByteCnt)           \
	do                                             \
	{                                              \
		WriteString(LOG_CHANNEL, String);          \
		WriteBytesHex(LOG_CHANNEL, Data, ByteCnt); \
		WriteString(LOG_CHANNEL, "\r");            \
	} while (false)

#define DEBUG_STRING(String)              \
	do                                    \
	{                                     \
		WriteString(LOG_CHANNEL, String); \
		WriteString(LOG_CHANNEL, "\r");   \
	} while (false)

#define AppDEBUGUINT16HEX(String, UInt16)       \
	do                                       \
	{                                        \
		WriteString(LOG_CHANNEL, String);    \
		WriteUInt16Hex(LOG_CHANNEL, UInt16); \
		WriteString(LOG_CHANNEL, "\r");      \
	} while (false)

#else
#define DEBUG_HEX(String, Data, ByteCnt)
#define DEBUG_STRING(String)
#define AppDEBUGUINT16HEX(String, UInt16)

#endif


#define ADVERTISE_PACKETS 0
#define SCAN_PACKETS 1 		//Currently unused

#define MAXIDBYTES 10
#define MAXIDBITS (MAXIDBYTES * 8)

byte ID[MAXIDBYTES];
int IDBitCnt;
int TagType;

byte LastID[MAXIDBYTES];
int LastIDBitCnt;
int LastTagType;

const byte bgm220VersionString[] = {0x42, 0x54, 0x32, 0x2E, 0x30, 0x31, 0x45, 0x4C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00};
//const byte bgm1xxVersionString[] = {0x42, 0x54, 0x31, 0x2E, 0x30, 0x37, 0x45, 0x4C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00};


// ******************************************************************
// ****** Section containing configurable behaviour *****************
// ******************************************************************

// If necessary adjust default parameters
#ifndef __APP_CONFIG_H__
#define LFTAGTYPES 0
#define HFTAGTYPES 0
#define ICLASS_READMODE_VALUE ICLASS_READMODE_UID
#endif

byte rxBuffer[256];
int rxDataBytes;

const unsigned char AppManifest[] =
	{
		USB_KEYBOARDREPEATRATE, 1, 1,
		EXECUTE_APP, 1, EXECUTE_APP_ALWAYS,
		TLV_END};

// Adjust parameters as configured in AppBlaster
const byte TLVBytes[] = {ICLASS_READMODE, 1, ICLASS_READMODE_VALUE, TLV_END};
void ConfigSetParameters(void)
{
	SetParameters(TLVBytes, sizeof(TLVBytes));
}

// Setup tag types as configured in AppBlaster
void ConfigSetTagTypes(void)
{
	if (LFTAGTYPES || HFTAGTYPES)
		SetTagTypes(LFTAGTYPES, HFTAGTYPES);
}
// ****************************************************************************
// BLE Services and Characteristics
// ****************************************************************************

//Used to track hardware version and connection status
bool bgm220 = false;
bool connected = false;
byte connHandle;		

//Only necessary for BGM1xx
static int UUIDCharSPPHandle;
static int UUIDCharSP1Handle;

void OnStartup(void)
{
	// SetHostChannel(HOST_CHANNEL);

	// Adjust parameters
	ConfigSetParameters();
	// Set tag types
	ConfigSetTagTypes();

	// Init LEDs
	LEDInit(REDLED | GREENLED);
	// Turn on green LED
	LEDOn(GREENLED);
	// Turn off red LED
	LEDOff(REDLED);
	// Make some noise at startup at low volume
	SetVolume(30);
	BeepLow();
	BeepHigh();
	// Continue with maximum  volume
	SetVolume(100);

	// No transponder found up to now
	LastTagType = NOTAG;
}

//Can be changed, but the corresponding array - AdvertisingArray - must be edited in Client file
const byte UserData[] =
	{
		// Flags - See Bluetooth 4.0 Core Specification , Volume 3, Appendix C, 18.1.
		0x02, // Length of the Flags field - 2 bytes-
		0x01, // Type of the Flags field.
		0x06, // Flags field. Device in LE General discoverable mode

		// Manufacturer data
		0x08, // Length of the Manufacturer Data field - 6+1 bytes -
		0x09, // Name
		'T',  // Local name "TWN4BLE"
		'W',
		'N',
		'4',
		'B',
		'L',
		'E',

		// TX power field advertisement, iOS apps may need this
		0x02, // ad field length = 2 bytes
		0x0A, // ad field type = 0x0A [TX Power)
		0x03, // TX power in dBm

		0x03,
		0xFF, // Manufacturer Specific data
		0x52, // Company Identifier Code - ELATEC 0x0752
		0x07, // Company Identifier Code

		0x05,
		0x05, // Complete List of 32-bit Service Class UUIDs
		0x3A,
		0x4B,
		0x5C,
		0x6D};

const TBLEConfig DefaultBleConfig = {

	.ConnectTimeout = 10000,
	.Power = 50,
	.BondableMode = 0x81,
	.AdvInterval = 0x00a0,
	.ChannelMap = 0x07,
	.DiscoverMode = 0x04,
	.ConnectMode = 0x02,
	.SecurityFlags = 0x00,
	.IOCapabilities = 0x00,
	.Passkey = 0x00000000,
};
static TBLEConfig BleConfig;

void BLE_WaitForConnection(void){
	//Indicate waiting for connection
	LEDBlink(GREENLED, 500, 500);
	unsigned long mark = GetSysTicks();
    byte BLEDataBuffer[256];
    int BLEDataBufferBytes;
    while(!connected){
        if((GetSysTicks() - mark) > 1000){
            DEBUG_STRING( "Timeout waiting for Connection");
            return;
        }
        int event = BLEGetEvent(BLEDataBuffer, sizeof(BLEDataBuffer), &BLEDataBufferBytes);
        if(event != BLE_NO_MESSAGE){
            DEBUG_STRING(BLEGetMessageString(event));
            DEBUG_HEX( "Received Event Data", BLEDataBuffer, BLEDataBufferBytes);
        }
		if((event == BLE_EVENT_CONNECTION_OPENED)|(event == BLE_EVENT_LE_CONNECTION_OPENED)){
        	connected = true;
        	connHandle = BLEDataBuffer[8];	//Both events have handle in 8th byte
        }
    }
    bool notificationsEnabled = false;
    mark = GetSysTicks();
    while(!notificationsEnabled){
        if((GetSysTicks() - mark) > 2000){
        	mark = GetSysTicks();
            DEBUG_STRING( "Timeout waiting for Set Notifications");
        }
        int event = BLEGetEvent(BLEDataBuffer, sizeof(BLEDataBuffer), &BLEDataBufferBytes);
        if(event != BLE_NO_MESSAGE){
            DEBUG_STRING(BLEGetMessageString(event));
            DEBUG_HEX( "Received Event Data", BLEDataBuffer, BLEDataBufferBytes);
        }
        if(event == BLE_EVENT_GATT_SERVER_CHARACTERISTIC_STATUS){
            if((BLEDataBuffer[3] == 0x01)&&(BLEDataBuffer[4]) == 0x01){
                notificationsEnabled = true;
                //Inidicate connection established by stopping blink
                LEDOff(GREENLED);
                LEDOn(GREENLED);
                return;
            }
        }
    }
}

int main(void)
{
	StartTimer(5000);
	while (!TestTimer())
		; // Starting delay 3 sec
	DEBUG_STRING("GATT Server running");

	// Init BLE
	BleConfig = DefaultBleConfig;
	if (BLEPresetConfig(&BleConfig))
		DEBUG_STRING("BLEPresetConfig: Ok");
	else
		DEBUG_STRING("BLEPresetConfig: Failed");

	if (BLEInit(BLE_MODE_TRANSPARENT))
		DEBUG_STRING("BLEInit: Ok");
	else
		DEBUG_STRING("BLEInit: Failed");

	//Determine which BLE module is attached to reader
	byte HWVersion[16];
	byte BootString[12];
	if(!BLEGetVersion(HWVersion, BootString)){
		DEBUG_STRING("BLEGetVersion failed");
	}else{
		DEBUG_HEX("BLE HW Version", HWVersion, 16);
		DEBUG_HEX("BLE Boot String", BootString, 12);
	}
	//Check HW version of connected module
	//Depends on .gbl file name programmed on module
	//Can also check BootString[0], which should be '0x02'
	//for BGM1xx and '0x03' for BGM220
	if(CompBytes(HWVersion, bgm220VersionString, 16)){
		bgm220 = true;
	}else{
		DEBUG_STRING("Comp HWVersion and VersionString");
		DEBUG_STRING((const char*)HWVersion);
		DEBUG_STRING((const char*)bgm220VersionString);
	}

	//SPP Service only needs to be defined for BGM220
	TBLEGATTDBSERVICE BLE2Service = {
		.uuid.UUIDLength = 16,
		.uuid.UUID = {0x8e, 0xdf, 0xae, 0x3d, 0x9b, 0xcd, 0x0e, 0x88, 0x74, 0x42, 0x12, 0x41, 0x04, 0xc0, 0x44, 0x5a},
		.type = 0,	  // Primary service
		.property = 1 // Advertised Service
	};
	TBLEGATTDBCHARACTERISTIC BLEReadCharacteristic = {
		.uuid.UUIDLength = 16,
		.uuid.UUID = {0x52, 0xc7, 0x9e, 0x16, 0x9d, 0x48, 0x22, 0xaa, 0x43, 0x4c, 0x0a, 0x2f, 0xdf, 0x9e, 0xc2, 0x43},
		.property = SL_BT_GATTDB_CHARACTERISTIC_NOTIFY | SL_BT_GATTDB_CHARACTERISTIC_READ,
		.valueType = BLE_GATTDB_VALUETYPE_VARIABLELENGTH,
		.flag = 0,
		.security = 0,
		.maxValueLength = 256};

	TBLEGATTDBCHARACTERISTIC BLEWriteCharacteristic = {
		.uuid.UUIDLength = 16,
		.uuid.UUID = {0xb6, 0x6e, 0x2e, 0x51, 0x57, 0x4a, 0xef, 0xa2, 0x2b, 0x4a, 0xf0, 0xad, 0x9f, 0x33, 0x97, 0xa8},
		.property = SL_BT_GATTDB_CHARACTERISTIC_WRITE_NO_RESPONSE | SL_BT_GATTDB_CHARACTERISTIC_WRITE,
		.valueType = BLE_GATTDB_VALUETYPE_VARIABLELENGTH,
		.flag = 0,
		.security = 0,
		.maxValueLength = 256};
	if(bgm220){
		// Create GATT service and characteristic for BGM220
		// Created handles for service and characteristics are automatically added to the structures
		if (!BLEGATTDBStartSession())
		{
			DEBUG_STRING("Failed GattDBNewSession");
			return false;
		}
		if (!BLEGATTDBAddNewService(&BLE2Service))
		{
			DEBUG_STRING("Failed GattAddService");
			return false;
		}
	
		AppDEBUGUINT16HEX("Service Handle", BLE2Service.ServiceHandle);
	
		if (!BLEGATTDBStartStopService(&BLE2Service, true))
		{
			DEBUG_STRING("Failed GattDBStartService");
			return false;
		}
		if (!BLEGATTDBEndSession(true))
		{
			DEBUG_STRING("Failed GattDBEndSession");
			return false;
		}
		if (!BLEGATTDBStartSession())
		{
			DEBUG_STRING("Failed GattDBNewSession2");
			return false;
		}
		if (!BLEGATTDBAddNewCharacteristic(&BLEReadCharacteristic, &BLE2Service))
		{
			DEBUG_STRING("Failed GattDBAddChar");
			return false;
		}
	
		AppDEBUGUINT16HEX("Read Characteristic Handle", BLEReadCharacteristic.CharacteristicHandle);
	
		if (!BLEGATTDBStartStopCharacteristic(&BLEReadCharacteristic, true))
		{
			DEBUG_STRING("Failed GattDBStartChar");
			return false;
		}
		if (!BLEGATTDBEndSession(true))
		{
			DEBUG_STRING("Failed GattDBEndSession");
			return false;
		}
		if (!BLEGATTDBStartSession())
		{
			DEBUG_STRING("Failed GattDBNewSession2");
			return false;
		}
		if (!BLEGATTDBAddNewCharacteristic(&BLEWriteCharacteristic, &BLE2Service))
		{
			DEBUG_STRING("Failed GattDBAddChar");
			return false;
		}
	
		AppDEBUGUINT16HEX("Write Characteristic Handle", BLEWriteCharacteristic.CharacteristicHandle);
	
		if (!BLEGATTDBStartStopCharacteristic(&BLEWriteCharacteristic, true))
		{
			DEBUG_STRING("Failed GattDBStartChar");
			return false;
		}
		if (!BLEGATTDBEndSession(true))
		{
			DEBUG_STRING("Failed GattDBEndSession");
			return false;
		}
	}

	//Set Advertising Data, then Begin Advertising
	if (BLEPresetUserData(ADVERTISE_PACKETS, UserData, sizeof(UserData)))
		DEBUG_STRING("BLEPresetUserData: Ok");
	else
		DEBUG_STRING("BLEPresetUserData: Failed");

	// Advertising Interval can be changed, potentially lower if necessary
	if (!BLEStartAdvertisement(100, BLE_ADV_CONNECTABLE_SCANNABLE))
	{
		DEBUG_STRING("Failed StartAdvertisement");
		return false;
	}
	DEBUG_STRING("Advertisement Started");
	if(!bgm220){
		//BGM1xx need to retrieve static handles of read and write characteristics
		if(!BLEFindGattServerAttribute(BLEReadCharacteristic.uuid.UUID, BLEReadCharacteristic.uuid.UUIDLength, &UUIDCharSPPHandle)){
			DEBUG_STRING("Failed to find read characteristic handle");
		}else{
			DEBUG_STRING("FindGattServerAttribute succeeded");
			BLEReadCharacteristic.CharacteristicHandle = UUIDCharSPPHandle;
			AppDEBUGUINT16HEX("Read Characteristic Handle", BLEReadCharacteristic.CharacteristicHandle);
		}
		if(!BLEFindGattServerAttribute(BLEWriteCharacteristic.uuid.UUID, BLEWriteCharacteristic.uuid.UUIDLength, &UUIDCharSP1Handle)){
			DEBUG_STRING("Failed to find write characteristic handle");
		}else{
			BLEWriteCharacteristic.CharacteristicHandle = UUIDCharSP1Handle;
			AppDEBUGUINT16HEX("Write Characteristic Handle", BLEWriteCharacteristic.CharacteristicHandle);
		}	
	}

	OnStartup();

	while (true)
	{
		while(!connected){
			BLE_WaitForConnection();
		}
		byte eventData[128];
		int eventDataBytes;
		int currentEvent = BLEGetEvent(eventData, sizeof(eventData), &eventDataBytes);

		//Output info about ignored events
		if(currentEvent != BLE_NO_MESSAGE){
            DEBUG_STRING(BLEGetMessageString(currentEvent));
            DEBUG_HEX( "Received Event Data", eventData, eventDataBytes);
		}
		//Different event values returned by BGM1xx and BGM220, regardless of HW, only one type will be returned
		if((currentEvent == BLE_EVENT_CONNECTION_CLOSED) | (currentEvent == BLE_EVENT_LE_CONNECTION_CLOSED)){
			connected = false;
			if (!BLEStartAdvertisement(100, BLE_ADV_CONNECTABLE_SCANNABLE))
			{
				DEBUG_STRING("Failed StartAdvertisement");
			}
			while(!connected){
				BLE_WaitForConnection();
			}
		}
		//Check for data from client device
		if(currentEvent == BLE_EVENT_GATT_SERVER_ATTRIBUTE_VALUE){
			//Check that write characteristic is source of event
			if(eventData[1] == (byte)BLEWriteCharacteristic.CharacteristicHandle){
				//Bytes 6+ of event message are number of message bytes + message
				byte messageBytes = eventData[6];
				byte message[128];
				if(messageBytes>128){
					messageBytes = 128;
				}
				CopyBytes(message, &eventData[7], messageBytes);
				//Print message, assuming host device is connected and buffer is empty
				if(TestEmpty(GetHostChannel(), DIR_OUT)){
					HostWriteHex(message, messageBytes*8, messageBytes*2);
					HostWriteChar('\r');
				}
			}
		}

		// Search a transponder
		if (SearchTag(&TagType, &IDBitCnt, ID, sizeof(ID)))
		{
			// Is this transponder new to us?
			if (TagType != LastTagType || IDBitCnt != LastIDBitCnt || !CompBits(ID, 0, LastID, 0, MAXIDBITS))
			{
				// Save this as known ID, before modifying the ID for proper output format
				CopyBits(LastID, 0, ID, 0, MAXIDBITS);
				LastIDBitCnt = IDBitCnt;
				LastTagType = TagType;

				// Yes! Sound a beep
				BeepHigh();
				// Turn off the green LED
				LEDOff(GREENLED);
				// Let the red one blink, start with on-state
				LEDOn(REDLED);
				LEDBlink(REDLED, 500, 500);

				//Build a message that is just the ID
				int messageBytes = ((IDBitCnt + 7) / 8);
				byte BLEMessage[messageBytes];
				CopyBytes(BLEMessage, ID, messageBytes);
				DEBUG_HEX("ID", ID, ((IDBitCnt + 7) / 8));
				//Send a notification with the card data
				byte BGAPIPacket[128];
				byte rxData[128];
				int rxDataBytes;
				BGAPIPacket[0] = connHandle; 										//Connection number
				BGAPIPacket[1] = (byte)BLEReadCharacteristic.CharacteristicHandle;	//First byte of handle
				BGAPIPacket[2] = 0x00;												//Second byte of handle
				BGAPIPacket[3] = (byte)messageBytes;								//Number of data bytes
				CopyBytes(&BGAPIPacket[4], BLEMessage, messageBytes);
				if(!BLETxRxModuleCommand(CMD_GATT_SERVER_SEND_CHARACTERISTIC_NOTIFICATION , BGAPIPacket, messageBytes+4, rxData, &rxDataBytes, sizeof(rxData))){
					DEBUG_STRING("Failed sending notification");
				}
			}
			// Start a timeout of two seconds
			StartTimer(2000);
		}
		if (TestTimer())
		{
			LEDOn(GREENLED);
			LEDOff(REDLED);
			LastTagType = NOTAG;
		}
	}
}
