mirror of
https://github.com/susabolca/cc2640r2-etag.git
synced 2025-12-06 14:42:48 +08:00
fix: epd_server gatt
This commit is contained in:
@@ -28,15 +28,6 @@ OBDISP obd;
|
||||
|
||||
extern const uint8_t ucMirror[];
|
||||
|
||||
/*
|
||||
* <int.frac> format size (3.8) bits.
|
||||
* int for 0-3 voltage
|
||||
* frac each means 1/256 of ONE voltage
|
||||
*/
|
||||
#define INTFRAC_V(x) (x>>8)
|
||||
#define INTFRAC_mV(x) ((x&0xff)*1000/256)
|
||||
#define INTFRAC2MV(x) (INTFRAC_mV(x)+(INTFRAC_V(x)*1000))
|
||||
|
||||
// https://www.mdpi.com/2072-666X/12/5/578
|
||||
#define VSS (0b00)
|
||||
#define VH1 (0b01)
|
||||
@@ -110,9 +101,9 @@ static void EPD_2IN13_Lut(const unsigned char *lut)
|
||||
EPD_SSD_SendData(*(lut+158));
|
||||
}
|
||||
|
||||
static uint8_t EPD_2IN13_ReadTemp()
|
||||
static int8_t EPD_2IN13_ReadTemp()
|
||||
{
|
||||
uint8_t rc;
|
||||
int8_t rc;
|
||||
|
||||
// soft reset
|
||||
EPD_SSD_SendCommand(0x12);
|
||||
@@ -243,8 +234,7 @@ void EPD_SSD_Update(void)
|
||||
return;
|
||||
}
|
||||
last = now;
|
||||
// TBD: timezone +8
|
||||
now += (3600 * 8);
|
||||
now += utc_offset_mins * 60;
|
||||
struct tm *l = localtime(&now);
|
||||
|
||||
// wakeup EPD
|
||||
@@ -272,9 +262,9 @@ void EPD_SSD_Update(void)
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 0, 118, buf, 1);
|
||||
|
||||
// battery voltage
|
||||
uint32_t v = AONBatMonBatteryVoltageGet();
|
||||
uint8_t t = EPD_2IN13_ReadTemp();
|
||||
System_snprintf(buf, 32, "%3uc %u.%uv", t, INTFRAC_V(v), INTFRAC_mV(v)/100);
|
||||
uint16_t v = epd_battery;
|
||||
epd_temperature = EPD_2IN13_ReadTemp();
|
||||
System_snprintf(buf, 32, "%3uc %u.%uv", epd_temperature, INTFRAC_V(v), INTFRAC_mV(v)/100);
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 164, 118, buf, 1);
|
||||
|
||||
// time
|
||||
|
||||
@@ -118,9 +118,9 @@ static void EPD_2IN9_Lut(const unsigned char *lut)
|
||||
EPD_SSD_SendData(*(lut+232));
|
||||
}
|
||||
|
||||
static uint8_t EPD_2IN9_ReadTemp()
|
||||
static int8_t EPD_2IN9_ReadTemp()
|
||||
{
|
||||
uint8_t rc;
|
||||
int8_t rc;
|
||||
|
||||
EPD_SSD_SendCommand(0x12); // soft reset
|
||||
EPD_SSD_WaitBusy();
|
||||
@@ -257,8 +257,7 @@ void EPD_SSD_Update(void)
|
||||
return;
|
||||
}
|
||||
last = now;
|
||||
// TBD: timezone +8
|
||||
now += (3600 * 8);
|
||||
now += utc_offset_mins * 60;
|
||||
struct tm *l = localtime(&now);
|
||||
|
||||
// wakeup EPD
|
||||
@@ -274,9 +273,9 @@ void EPD_SSD_Update(void)
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 2, 128, buf, 1);
|
||||
|
||||
// battery voltage
|
||||
uint32_t v = AONBatMonBatteryVoltageGet();
|
||||
System_snprintf(buf, 32, "%u.%uv", INTFRAC_V(v), INTFRAC_mV(v)/100);
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 256, 128, buf, 1);
|
||||
uint8_t v = EPD_BATT_Percent();
|
||||
System_snprintf(buf, 32, "%3u%%", v);
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 250, 128, buf, 1);
|
||||
|
||||
// date
|
||||
const char *wstr[]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
@@ -284,8 +283,8 @@ void EPD_SSD_Update(void)
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_24, 2, 22, buf, 1);
|
||||
|
||||
// temp
|
||||
uint8_t t = EPD_2IN9_ReadTemp();
|
||||
System_snprintf(buf, 32, "%3uc", t);
|
||||
epd_temperature = EPD_2IN9_ReadTemp();
|
||||
System_snprintf(buf, 32, "%3uc", epd_temperature);
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_24, 240, 22, buf, 1);
|
||||
|
||||
// time
|
||||
@@ -303,25 +302,7 @@ void EPD_SSD_Update(void)
|
||||
EPD_2IN9_BWR(EPD_WIDTH, EPD_HEIGHT, 0, 0);
|
||||
if (!full_upd) EPD_2IN9_Lut(lut_full_bwr);
|
||||
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 0);
|
||||
|
||||
#if 0
|
||||
// red
|
||||
obdFill(&obd, 0, 0);
|
||||
|
||||
// date
|
||||
const char *wstr[]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
System_snprintf(buf, 32, "%u-%02u-%02u %s", 1900+l->tm_year, l->tm_mon+1, l->tm_mday, wstr[l->tm_wday]);
|
||||
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 0, 122, buf, 1);
|
||||
|
||||
// endian and invent
|
||||
for (int i=0; i<sizeof(epd_buffer); i++) {
|
||||
uint8_t c = epd_buffer[i];
|
||||
epd_buffer[i] = ucMirror[c];
|
||||
}
|
||||
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
|
||||
#else
|
||||
EPD_2IN9_WriteRam(NULL, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
|
||||
#endif
|
||||
|
||||
// show
|
||||
EPD_2IN9_Display(full_upd ? 0xf7 : 0xc7); // c7: by REG f7: by OTP b1: no display
|
||||
|
||||
@@ -28,6 +28,15 @@ uint8_t epd_buffer[EPD_BUF_MAX];
|
||||
// debug only
|
||||
int lut_size;
|
||||
|
||||
// battery voltage, in frac <3.8>
|
||||
uint16_t epd_battery;
|
||||
|
||||
// in degree celcius, read from EPD.
|
||||
int8_t epd_temperature;
|
||||
|
||||
// local time to UTC time offset, in minuts.
|
||||
int32_t utc_offset_mins = 8 * 60; // default is UTC+8
|
||||
|
||||
/*
|
||||
* Device APIs
|
||||
*/
|
||||
@@ -188,6 +197,33 @@ int EPD_SSD_LutDetect()
|
||||
return i;
|
||||
}
|
||||
|
||||
// 0-100, for CR2450, 3000mv lithum battery.
|
||||
uint8_t EPD_BATT_Percent(void)
|
||||
{
|
||||
// Convert to from V to mV to avoid fractions.
|
||||
// Fractional part is in the lower 8 bits thus converting is done as follows:
|
||||
// (1/256)/(1/1000) = 1000/256 = 125/32
|
||||
// This is done most effectively by multiplying by 125 and then shifting
|
||||
// 5 bits to the right.
|
||||
uint32_t v = INTFRAC2MV(epd_battery);
|
||||
|
||||
/* for cr2450, lithium battery,
|
||||
* 3000 (100%) - 2800 (60%)
|
||||
* 2800 ( 60%) - 2500 (20%)
|
||||
* 2500 ( 20%) - 2000 (0%)
|
||||
*/
|
||||
if (v >= 3000) {
|
||||
return 100;
|
||||
} else if (v > 2800) {
|
||||
return (v-2800)*40/(3000-2800) + 60;
|
||||
} else if (v > 2500) {
|
||||
return (v-2500)*40/(2800-2500) + 20;
|
||||
} else if (v > 2000) {
|
||||
return (v-2000)*20/(2500-2000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Select a EPD
|
||||
#if defined(EPD_2IN13_SSD1680)
|
||||
|
||||
@@ -216,5 +252,9 @@ void EPD_Init()
|
||||
|
||||
void EPD_Update()
|
||||
{
|
||||
// update battery level
|
||||
epd_battery = AONBatMonBatteryVoltageGet();
|
||||
|
||||
// update Display
|
||||
EPD_SSD_Update();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#ifndef _EPD_SPI_H_
|
||||
#define _EPD_SPI_H_
|
||||
#ifndef _EPD_DRIVER_H_
|
||||
#define _EPD_DRIVER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// epd device
|
||||
// select a EPD device
|
||||
//#define EPD_2IN13_SSD1680
|
||||
#define EPD_2IN9_SSD1680A
|
||||
|
||||
@@ -19,6 +19,21 @@
|
||||
#define EPD_BUF_MAX (300 * 128 / 8)
|
||||
extern uint8_t epd_buffer[EPD_BUF_MAX];
|
||||
|
||||
// local time to UTC offset, in +/- minutes.
|
||||
extern int32_t utc_offset_mins;
|
||||
|
||||
// public sensor values
|
||||
extern int8_t epd_temperature; // in degree celsius, +/-127
|
||||
extern uint16_t epd_battery; // in (3.8) frac
|
||||
/*
|
||||
* <int.frac> format size (3.8) bits.
|
||||
* int for 0-3 voltage
|
||||
* frac each means 1/256 of ONE voltage
|
||||
*/
|
||||
#define INTFRAC_V(x) (x>>8)
|
||||
#define INTFRAC_mV(x) ((x&0xff)*125/32)
|
||||
#define INTFRAC2MV(x) (INTFRAC_mV(x)+(INTFRAC_V(x)*1000))
|
||||
|
||||
// the driver public,
|
||||
void EPD_Init();
|
||||
void EPD_Update();
|
||||
@@ -29,6 +44,7 @@ uint8_t EPD_SSD_ReadData();
|
||||
void EPD_SSD_ReadBytes(uint8_t* buf, uint8_t len);
|
||||
bool EPD_SSD_IsBusy();
|
||||
void EPD_SSD_WaitBusy();
|
||||
uint8_t EPD_BATT_Percent();
|
||||
|
||||
// for epd_inch.c shoule implement,
|
||||
void EPD_SSD_Init();
|
||||
|
||||
@@ -3,39 +3,59 @@
|
||||
|
||||
//#include <xdc/runtime/Log.h> // Comment this in to use xdc.runtime.Log
|
||||
//#include <uartlog/UartLog.h> // Comment out if using xdc Log
|
||||
|
||||
#include <icall.h>
|
||||
#include "util.h"
|
||||
|
||||
/* This Header file contains all BLE API and icall structure definition */
|
||||
#include "icall_ble_api.h"
|
||||
|
||||
#include <ti/sysbios/hal/Seconds.h> // Seconds_set
|
||||
|
||||
#include "epd_driver.h" // epd_battery, epd_temperature
|
||||
#include "epd_service.h"
|
||||
|
||||
// EPD_Service Service UUID
|
||||
CONST uint8_t EpdServiceUUID[ATT_BT_UUID_SIZE] =
|
||||
{
|
||||
CONST uint8 EpdServiceUUID[ATT_BT_UUID_SIZE] = {
|
||||
LO_UINT16(EPD_SERVICE_SERV_UUID), HI_UINT16(EPD_SERVICE_SERV_UUID),
|
||||
};
|
||||
|
||||
// Unix Epoch UUID
|
||||
CONST uint8_t EpdEpochUUID[ATT_BT_UUID_SIZE] =
|
||||
{
|
||||
CONST uint8 EpdEpochUUID[ATT_BT_UUID_SIZE] = {
|
||||
LO_UINT16(EPD_EPOCH_UUID), HI_UINT16(EPD_EPOCH_UUID),
|
||||
};
|
||||
|
||||
CONST uint8 EpdUtcOffsetUUID[ATT_BT_UUID_SIZE] = {
|
||||
LO_UINT16(EPD_UTC_OFFSET_UUID), HI_UINT16(EPD_UTC_OFFSET_UUID),
|
||||
};
|
||||
|
||||
CONST uint8 EpdBattUUID[ATT_BT_UUID_SIZE] = {
|
||||
LO_UINT16(EPD_BATT_UUID), HI_UINT16(EPD_BATT_UUID),
|
||||
};
|
||||
|
||||
CONST uint8 EpdTempUUID[ATT_BT_UUID_SIZE] = {
|
||||
LO_UINT16(EPD_TEMP_UUID), HI_UINT16(EPD_TEMP_UUID),
|
||||
};
|
||||
|
||||
static EpdServiceCBs_t *pAppCBs = NULL;
|
||||
static gattCharCfg_t *EpdDataConfig;
|
||||
|
||||
// Service declaration
|
||||
static CONST gattAttrType_t EpdServiceDecl = { ATT_BT_UUID_SIZE, EpdServiceUUID };
|
||||
static uint8 EpochProps = GATT_PROP_READ | GATT_PROP_WRITE;
|
||||
static uint8 EpochVal[4] = {0};
|
||||
static uint8 EpochDesc[] = "Unix Epoch";
|
||||
|
||||
// Characteristic "Epoch" Properties (for declaration)
|
||||
static uint8_t EpochProps = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY;
|
||||
static uint8 UtcOffProps = GATT_PROP_READ | GATT_PROP_WRITE;
|
||||
static uint8 UtcOffDesc[] = "UTC Offset Mins";
|
||||
static int8 UtcOffVal[4] = {0};
|
||||
|
||||
// Characteristic "Epoch" Value variable
|
||||
static uint8_t EpochVal[EPD_EPOCH_LEN] = {0};
|
||||
static uint8 BattProps = GATT_PROP_READ;
|
||||
static uint8 BattDesc[] = "Battery mv";
|
||||
static uint8 BattVal[2] = {0};
|
||||
|
||||
// Length of data in characteristic "Epoch" Value variable, initialized to minimal size.
|
||||
static uint16_t EpochValLen = EPD_EPOCH_LEN_MIN;
|
||||
static uint8 TempProps = GATT_PROP_READ;
|
||||
static uint8 TempDesc[] = "Temperature";
|
||||
static int8 TempVal[1] = {0};
|
||||
|
||||
static gattAttribute_t EpdServiceAttrTbl[] =
|
||||
{
|
||||
@@ -46,6 +66,7 @@ static gattAttribute_t EpdServiceAttrTbl[] =
|
||||
0,
|
||||
(uint8_t *)&EpdServiceDecl
|
||||
},
|
||||
|
||||
// Epoch Characteristic Declaration
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, characterUUID },
|
||||
@@ -53,13 +74,60 @@ static gattAttribute_t EpdServiceAttrTbl[] =
|
||||
0,
|
||||
&EpochProps
|
||||
},
|
||||
// Epoch Characteristic Value
|
||||
// Epoch Characteristic Value
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, EpdEpochUUID },
|
||||
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
|
||||
0,
|
||||
EpochVal
|
||||
},
|
||||
|
||||
// Characteristic Declaration
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, EpdEpochUUID },
|
||||
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
|
||||
{ ATT_BT_UUID_SIZE, characterUUID },
|
||||
GATT_PERMIT_READ,
|
||||
0,
|
||||
EpochVal
|
||||
&UtcOffProps
|
||||
},
|
||||
// Characteristic Value
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, EpdUtcOffsetUUID },
|
||||
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
|
||||
0,
|
||||
UtcOffVal
|
||||
},
|
||||
|
||||
#if 1
|
||||
// Characteristic Declaration
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, characterUUID },
|
||||
GATT_PERMIT_READ,
|
||||
0,
|
||||
&BattProps
|
||||
},
|
||||
// Characteristic Value
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, EpdBattUUID },
|
||||
GATT_PERMIT_READ,
|
||||
0,
|
||||
BattVal
|
||||
},
|
||||
|
||||
// Characteristic Declaration
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, characterUUID },
|
||||
GATT_PERMIT_READ,
|
||||
0,
|
||||
&TempProps
|
||||
},
|
||||
// Characteristic Value
|
||||
{
|
||||
{ ATT_BT_UUID_SIZE, EpdTempUUID },
|
||||
GATT_PERMIT_READ,
|
||||
0,
|
||||
TempVal
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -86,9 +154,19 @@ CONST gattServiceCBs_t EpdServiceCBs =
|
||||
NULL // Authorization callback function pointer
|
||||
};
|
||||
|
||||
extern bStatus_t EPDService_AddService(uint8_t rspTaskId)
|
||||
bStatus_t EPDService_AddService(uint8_t rspTaskId)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
// Allocate Client Characteristic Configuration table
|
||||
EpdDataConfig = (gattCharCfg_t *)ICall_malloc(sizeof(gattCharCfg_t) * linkDBNumConns);
|
||||
if (EpdDataConfig == NULL) {
|
||||
return (bleMemAllocError);
|
||||
}
|
||||
|
||||
// Register with Link DB to receive link status change callback
|
||||
GATTServApp_InitCharCfg(INVALID_CONNHANDLE, EpdDataConfig);
|
||||
|
||||
// Register GATT attribute list and CBs with GATT Server App
|
||||
status = GATTServApp_RegisterService(EpdServiceAttrTbl,
|
||||
GATT_NUM_ATTRS(EpdServiceAttrTbl),
|
||||
@@ -110,23 +188,14 @@ bStatus_t EPDService_RegisterAppCBs(EpdServiceCBs_t *appCallbacks)
|
||||
bStatus_t EPDService_SetParameter(uint8_t param, uint16_t len, void *value)
|
||||
{
|
||||
bStatus_t ret = SUCCESS;
|
||||
uint8_t *pAttrVal;
|
||||
uint16_t *pValLen;
|
||||
uint16_t valMinLen;
|
||||
uint16_t valMaxLen;
|
||||
|
||||
switch (param) {
|
||||
case EPD_EPOCH_ID:
|
||||
pAttrVal = EpochVal;
|
||||
pValLen = &EpochValLen;
|
||||
valMinLen = EPD_EPOCH_LEN_MIN;
|
||||
valMaxLen = EPD_EPOCH_LEN;
|
||||
break;
|
||||
|
||||
default:
|
||||
return(INVALIDPARAMETER);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Check bounds, update value and send notification or indication if possible.
|
||||
if(len <= valMaxLen && len >= valMinLen) {
|
||||
memcpy(pAttrVal, value, len);
|
||||
@@ -134,7 +203,7 @@ bStatus_t EPDService_SetParameter(uint8_t param, uint16_t len, void *value)
|
||||
} else {
|
||||
ret = bleInvalidRange;
|
||||
}
|
||||
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -142,11 +211,12 @@ bStatus_t EPDService_GetParameter(uint8_t param, uint16_t *len, void *value)
|
||||
{
|
||||
bStatus_t ret = SUCCESS;
|
||||
switch (param) {
|
||||
#if 0
|
||||
case EPD_EPOCH_ID:
|
||||
*len = MIN(*len, EpochValLen);
|
||||
*len = 4;
|
||||
memcpy(value, EpochVal, *len);
|
||||
break;
|
||||
|
||||
#endif
|
||||
default:
|
||||
ret = INVALIDPARAMETER;
|
||||
break;
|
||||
@@ -154,6 +224,7 @@ bStatus_t EPDService_GetParameter(uint8_t param, uint16_t *len, void *value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static uint8_t EPDService_findCharParamId(gattAttribute_t *pAttr)
|
||||
{
|
||||
#if 0
|
||||
@@ -163,13 +234,38 @@ static uint8_t EPDService_findCharParamId(gattAttribute_t *pAttr)
|
||||
return (EPDService_findCharParamId(pAttr - 1)); // Assume the value attribute precedes CCCD and recurse
|
||||
} elif
|
||||
#endif
|
||||
// Is this attribute in "Epoch"?
|
||||
if (ATT_BT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, EpdEpochUUID, pAttr->type.len)) {
|
||||
return EPD_EPOCH_ID;
|
||||
|
||||
} else {
|
||||
return 0xFF; // Not found. Return invalid.
|
||||
if (ATT_BT_UUID_SIZE == pAttr->type.len) {
|
||||
uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
|
||||
switch (uuid) {
|
||||
case EPD_EPOCH_UUID:
|
||||
return EPD_EPOCH_ID;
|
||||
case EPD_UTC_OFFSET_UUID:
|
||||
return EPD_UTC_OFFSET_ID;
|
||||
case EPD_BATT_UUID:
|
||||
return EPD_BATT_ID;
|
||||
case EPD_TEMP_UUID:
|
||||
return EPD_TEMP_ID;
|
||||
}
|
||||
}
|
||||
return 0xFF; // Not found. Return invalid.
|
||||
}
|
||||
#endif
|
||||
|
||||
static bStatus_t utilExtractUuid16(gattAttribute_t *pAttr, uint16_t *pUuid)
|
||||
{
|
||||
bStatus_t status = SUCCESS;
|
||||
|
||||
if (pAttr->type.len == ATT_BT_UUID_SIZE) {
|
||||
// 16-bit UUID direct
|
||||
*pUuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
|
||||
} else if (pAttr->type.len == ATT_UUID_SIZE) {
|
||||
// 16-bit UUID extracted bytes 12 and 13
|
||||
*pUuid = BUILD_UINT16(pAttr->type.uuid[12], pAttr->type.uuid[13]);
|
||||
} else {
|
||||
*pUuid = 0xFFFF;
|
||||
status = FAILURE;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static bStatus_t EPDService_ReadAttrCB(uint16_t connHandle,
|
||||
@@ -180,28 +276,49 @@ static bStatus_t EPDService_ReadAttrCB(uint16_t connHandle,
|
||||
uint8_t method)
|
||||
{
|
||||
bStatus_t status = SUCCESS;
|
||||
uint16_t valueLen;
|
||||
uint8_t paramID = 0xFF;
|
||||
|
||||
// Find settings for the characteristic to be read.
|
||||
paramID = EPDService_findCharParamId(pAttr);
|
||||
switch(paramID) {
|
||||
case EPD_EPOCH_ID:
|
||||
valueLen = EpochValLen;
|
||||
if (offset > 0) {
|
||||
return ATT_ERR_ATTR_NOT_LONG;
|
||||
}
|
||||
|
||||
uint16_t uuid;
|
||||
if (utilExtractUuid16(pAttr, &uuid) == FAILURE) {
|
||||
return ATT_ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
switch(uuid) {
|
||||
case EPD_EPOCH_UUID: {
|
||||
uint32_t t = Seconds_get();
|
||||
*pLen = sizeof(t);
|
||||
memcpy(pValue, &t, *pLen);
|
||||
break;
|
||||
}
|
||||
|
||||
case EPD_UTC_OFFSET_UUID: {
|
||||
int32_t t = utc_offset_mins;
|
||||
*pLen = sizeof(t);
|
||||
memcpy(pValue, &t, *pLen);
|
||||
break;
|
||||
}
|
||||
|
||||
case EPD_BATT_UUID: {
|
||||
uint16_t v = INTFRAC2MV(epd_battery);
|
||||
*pLen = sizeof(v);
|
||||
memcpy(pValue, &v, *pLen);
|
||||
break;
|
||||
}
|
||||
|
||||
case EPD_TEMP_UUID: {
|
||||
uint8_t v = epd_temperature;
|
||||
*pLen = sizeof(v);
|
||||
memcpy(pValue, &v, *pLen);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return ATT_ERR_ATTR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Check bounds and return the value
|
||||
if(offset > valueLen) {
|
||||
status = ATT_ERR_INVALID_OFFSET;
|
||||
} else {
|
||||
*pLen = MIN(maxLen, valueLen - offset); // Transmit as much as possible
|
||||
memcpy(pValue, pAttr->pValue + offset, *pLen);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -212,60 +329,46 @@ static bStatus_t EPDService_WriteAttrCB(uint16_t connHandle,
|
||||
uint8_t method)
|
||||
{
|
||||
bStatus_t status = SUCCESS;
|
||||
uint8_t paramID = 0xFF;
|
||||
uint8_t changeParamID = 0xFF;
|
||||
uint16_t writeLenMin;
|
||||
uint16_t writeLenMax;
|
||||
uint16_t *pValueLenVar;
|
||||
uint16_t uuid;
|
||||
|
||||
// Find settings for the characteristic to be written.
|
||||
paramID = EPDService_findCharParamId(pAttr);
|
||||
switch(paramID) {
|
||||
case EPD_EPOCH_ID:
|
||||
writeLenMin = EPD_EPOCH_LEN_MIN;
|
||||
writeLenMax = EPD_EPOCH_LEN;
|
||||
pValueLenVar = &EpochValLen;
|
||||
if (utilExtractUuid16(pAttr, &uuid) == FAILURE) {
|
||||
return ATT_ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
/* Other considerations for Epoch can be inserted here */
|
||||
switch (uuid) {
|
||||
case EPD_EPOCH_UUID: {
|
||||
if (len == 4) {
|
||||
uint32_t t = *(uint32_t*)pValue;
|
||||
Seconds_set(t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EPD_UTC_OFFSET_UUID: {
|
||||
if (len == 4) {
|
||||
int32_t t = *(int32_t*)pValue;
|
||||
if ((t >= (-12*60)) && (t <= (12*60))) { // +/- 12 hrs
|
||||
utc_offset_mins = t;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GATT_CLIENT_CHAR_CFG_UUID:
|
||||
status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len, offset, GATT_CLIENT_CFG_NOTIFY);
|
||||
break;
|
||||
|
||||
default:
|
||||
return ATT_ERR_ATTR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Check whether the length is within bounds.
|
||||
if(offset >= writeLenMax) {
|
||||
status = ATT_ERR_INVALID_OFFSET;
|
||||
} else if(offset + len > writeLenMax) {
|
||||
status = ATT_ERR_INVALID_VALUE_SIZE;
|
||||
} else if(offset + len < writeLenMin &&
|
||||
(method == ATT_EXECUTE_WRITE_REQ || method == ATT_WRITE_REQ)) {
|
||||
// Refuse writes that are lower than minimum.
|
||||
// Note: Cannot determine if a Reliable Write (to several chars) is finished, so those will
|
||||
// only be refused if this attribute is the last in the queue (method is execute).
|
||||
// Otherwise, reliable writes are accepted and parsed piecemeal.
|
||||
status = ATT_ERR_INVALID_VALUE_SIZE;
|
||||
} else {
|
||||
// Copy pValue into the variable we point to from the attribute table.
|
||||
memcpy(pAttr->pValue + offset, pValue, len);
|
||||
|
||||
// Only notify application and update length if enough data is written.
|
||||
//
|
||||
// Note: If reliable writes are used (meaning several attributes are written to using ATT PrepareWrite),
|
||||
// the application will get a callback for every write with an offset + len larger than _LEN_MIN.
|
||||
// Note: For Long Writes (ATT Prepare + Execute towards only one attribute) only one callback will be issued,
|
||||
// because the write fragments are concatenated before being sent here.
|
||||
if(offset + len >= writeLenMin) {
|
||||
changeParamID = paramID;
|
||||
*pValueLenVar = offset + len; // Update data length.
|
||||
}
|
||||
}
|
||||
|
||||
// Let the application know something changed (if it did) by using the
|
||||
// callback it registered earlier (if it did).
|
||||
if(changeParamID != 0xFF) {
|
||||
if(pAppCBs && pAppCBs->pfnChangeCb) {
|
||||
pAppCBs->pfnChangeCb(connHandle, paramID, len + offset, pValue); // Call app function from stack task context.
|
||||
pAppCBs->pfnChangeCb(connHandle, changeParamID, len + offset, pValue); // Call app function from stack task context.
|
||||
}
|
||||
}
|
||||
return status;
|
||||
|
||||
@@ -4,13 +4,26 @@
|
||||
#include <bcomdef.h>
|
||||
|
||||
// Service UUID
|
||||
#define EPD_SERVICE_SERV_UUID 0x1140
|
||||
#define EPD_SERVICE_SERV_UUID 0xFFF0
|
||||
|
||||
// Unix Epoch (from 1970-1-1 00:00)
|
||||
#define EPD_EPOCH_ID 0
|
||||
#define EPD_EPOCH_UUID 0x1141
|
||||
#define EPD_EPOCH_LEN 4
|
||||
#define EPD_EPOCH_LEN_MIN 4
|
||||
//#define EPD_EPOCH_ID 0
|
||||
#define EPD_EPOCH_UUID 0xFFF1
|
||||
//#define EPD_EPOCH_LEN 4
|
||||
//#define EPD_EPOCH_LEN_MIN 4
|
||||
|
||||
// UTC local time offset
|
||||
//#define EPD_UTC_OFFSET_ID 1
|
||||
#define EPD_UTC_OFFSET_UUID 0xFFF2
|
||||
|
||||
// battery
|
||||
//#define EPD_BATT_ID 2
|
||||
#define EPD_BATT_UUID 0xFFF3
|
||||
|
||||
// temperature
|
||||
//#define EPD_TEMP_ID 3
|
||||
#define EPD_TEMP_UUID 0xFFF4
|
||||
|
||||
|
||||
// Callback when a characteristic value has changed
|
||||
typedef void (*EpdServiceChange_t)(uint16_t connHandle, uint8_t paramID,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <ti/devices/DeviceFamily.h>
|
||||
#include DeviceFamily_constructPath(driverlib/sys_ctrl.h)
|
||||
|
||||
#include <ti/sysbios/hal/Seconds.h> // Seconds_set
|
||||
//#include <ti/sysbios/hal/Seconds.h> // Seconds_set
|
||||
#include <xdc/runtime/System.h> // snprintf
|
||||
|
||||
// profiles
|
||||
@@ -37,18 +37,18 @@
|
||||
|
||||
// Minimum connection interval (units of 1.25ms, 80=100ms) for automatic
|
||||
// parameter update request
|
||||
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 16 // 80
|
||||
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 80
|
||||
|
||||
// Maximum connection interval (units of 1.25ms, 800=1000ms) for automatic
|
||||
// parameter update request
|
||||
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL 40 // 800
|
||||
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL 800
|
||||
|
||||
// Slave latency to use for automatic parameter update request
|
||||
#define DEFAULT_DESIRED_SLAVE_LATENCY 0
|
||||
|
||||
// Supervision timeout value (units of 10ms, 1000=10s) for automatic parameter
|
||||
// update request
|
||||
#define DEFAULT_DESIRED_CONN_TIMEOUT 100 //1000
|
||||
#define DEFAULT_DESIRED_CONN_TIMEOUT 1000
|
||||
|
||||
// After the connection is formed, the peripheral waits until the central
|
||||
// device asks for its preferred connection parameters
|
||||
@@ -118,9 +118,6 @@ static Queue_Handle appMsgQueue;
|
||||
Task_Struct sbpTask;
|
||||
Char sbpTaskStack[SBP_TASK_STACK_SIZE];
|
||||
|
||||
// BLE mac address.
|
||||
uint8_t mac_address[6];
|
||||
|
||||
// Scan response data (max size = 31 bytes)
|
||||
static uint8_t scanRspData[] =
|
||||
{
|
||||
@@ -129,6 +126,7 @@ static uint8_t scanRspData[] =
|
||||
GAP_ADTYPE_LOCAL_NAME_COMPLETE,
|
||||
'C','2','6','_','0','0','0','0','0','0',
|
||||
|
||||
#if 0
|
||||
// connection interval range
|
||||
0x05, // length of this data
|
||||
GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE,
|
||||
@@ -141,14 +139,14 @@ static uint8_t scanRspData[] =
|
||||
0x02, // length of this data
|
||||
GAP_ADTYPE_POWER_LEVEL,
|
||||
0 // 0dBm
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Advertisement data (max size = 31 bytes, though this is
|
||||
// best kept short to conserve power while advertising)
|
||||
static uint8_t advertData[] =
|
||||
{
|
||||
#if 0
|
||||
// Flags: this field sets the device to use general discoverable
|
||||
// mode (advertises indefinitely) instead of general
|
||||
// discoverable mode (advertise for 30 seconds at a time)
|
||||
@@ -162,10 +160,27 @@ static uint8_t advertData[] =
|
||||
GAP_ADTYPE_16BIT_MORE, // some of the UUID's, but not all
|
||||
LO_UINT16(EPD_SERVICE_SERV_UUID),
|
||||
HI_UINT16(EPD_SERVICE_SERV_UUID)
|
||||
|
||||
#else
|
||||
16, 0x16, // spec
|
||||
LO_UINT16(EPD_SERVICE_SERV_UUID), HI_UINT16(EPD_SERVICE_SERV_UUID),
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mac address
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // data
|
||||
0x07,
|
||||
|
||||
0x03, // length of this data
|
||||
GAP_ADTYPE_16BIT_MORE, // some of the UUID's, but not all
|
||||
LO_UINT16(EPD_SERVICE_SERV_UUID),
|
||||
HI_UINT16(EPD_SERVICE_SERV_UUID)
|
||||
#endif
|
||||
};
|
||||
|
||||
// GAP GATT Attributes
|
||||
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "CC2640R2_ETAG";
|
||||
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "C26_000000";
|
||||
|
||||
// BLE mac address.
|
||||
//uint8_t mac_address[6];
|
||||
uint8_t *mac_address = &advertData[4];
|
||||
|
||||
// Globals used for ATT Response retransmission
|
||||
static gattMsgEvent_t *pAttRsp = NULL;
|
||||
@@ -263,6 +278,7 @@ static void SimpleBLEPeripheral_init(void)
|
||||
char buf[8];
|
||||
System_snprintf(buf, 8, "%02x%02x%02x", mac_address[3], mac_address[4], mac_address[5]);
|
||||
memcpy(scanRspData+6, buf, 6);
|
||||
memcpy(attDeviceName+4, buf, 6);
|
||||
}
|
||||
|
||||
#ifdef USE_RCOSC
|
||||
@@ -357,17 +373,16 @@ static void SimpleBLEPeripheral_init(void)
|
||||
// initiate pairing
|
||||
uint8_t pairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;
|
||||
// Use authenticated pairing: require passcode.
|
||||
uint8_t mitm = TRUE;
|
||||
uint8_t mitm = 0; //TRUE;
|
||||
// This device only has display capabilities. Therefore, it will display the
|
||||
// passcode during pairing. However, since the default passcode is being
|
||||
// used, there is no need to display anything.
|
||||
uint8_t ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY;
|
||||
// Request bonding (storing long-term keys for re-encryption upon subsequent
|
||||
// connections without repairing)
|
||||
uint8_t bonding = TRUE;
|
||||
uint8_t bonding = 0; //TRUE;
|
||||
|
||||
GAPBondMgr_SetParameter(GAPBOND_DEFAULT_PASSCODE, sizeof(uint32_t),
|
||||
&passkey);
|
||||
GAPBondMgr_SetParameter(GAPBOND_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey);
|
||||
GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);
|
||||
GAPBondMgr_SetParameter(GAPBOND_MITM_PROTECTION, sizeof(uint8_t), &mitm);
|
||||
GAPBondMgr_SetParameter(GAPBOND_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);
|
||||
@@ -892,9 +907,11 @@ static void BLETask_EpdService_ValueChangeCB(uint16_t connHandle,
|
||||
uint16_t len,
|
||||
uint8_t *pValue)
|
||||
{
|
||||
#if 0
|
||||
if (len == 4) {
|
||||
Seconds_set(*((uint32_t*)pValue));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void BLETask_EpdService_CfgChangeCB(uint16_t connHandle,
|
||||
|
||||
@@ -94,3 +94,4 @@ void TaskEPD_taskFxn(UArg a0, UArg a1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
-mv7M3 -O4 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
|
||||
-DDeviceFamily_CC26X0R2
|
||||
-DBOARD_DISPLAY_USE_LCD=0
|
||||
-DBOARD_DISPLAY_USE_UART=1
|
||||
-DBOARD_DISPLAY_USE_UART=0
|
||||
-DBOARD_DISPLAY_USE_UART_ANSI=1
|
||||
-DCC2640R2_LAUNCHXL
|
||||
-DCC26XX
|
||||
@@ -105,7 +105,7 @@
|
||||
-mv7M3 -O4 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
|
||||
-DDeviceFamily_CC26X0R2
|
||||
-DBOARD_DISPLAY_USE_LCD=0
|
||||
-DBOARD_DISPLAY_USE_UART=1
|
||||
-DBOARD_DISPLAY_USE_UART=0
|
||||
-DBOARD_DISPLAY_USE_UART_ANSI=1
|
||||
-DCC2640R2_LAUNCHXL
|
||||
-DCC26XX
|
||||
@@ -173,14 +173,22 @@
|
||||
<pathVariable name="SRC_BLE_DIR" path="${SDK_ROOT}/source/ti/ble5stack" scope="project"/>
|
||||
|
||||
<!-- Application Folder -->
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd2in13.c" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd2in13.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd_2in9.c" openOnCreation="" excludeFromBuild="true" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd_2in13.c" openOnCreation="" excludeFromBuild="true" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd_driver.c" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/epd_driver.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/OneBitDisplay.c" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/OneBitDisplay.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/obd.inl" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font8.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font16.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font16zh.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font24.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font30.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font40.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font60.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font64.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/font80.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/task_ble.c" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/task_ble.h" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
<file path="EXAMPLE_BLE_ROOT/src/app/task_epd.c" openOnCreation="" excludeFromBuild="false" action="link" targetDirectory="Application" createVirtualFolders="true" applicableConfigurations="FlashROM, FlashROM_StackLibrary, FlashROM_StackLibrary_RCOSC"/>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
bleDevice = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ namePrefix: ['C26_'] }],
|
||||
optionalServices: [
|
||||
0x1140,
|
||||
0xfff0,
|
||||
],
|
||||
//acceptAllDevices: true
|
||||
});
|
||||
@@ -30,9 +30,9 @@
|
||||
info("Connect to " + bleDevice.name)
|
||||
gattServer = await bleDevice.gatt.connect();
|
||||
info('> Found GATT server');
|
||||
epdService = await gattServer.getPrimaryService(0x1140);
|
||||
epdService = await gattServer.getPrimaryService(0xfff0);
|
||||
info('> Found EPD service');
|
||||
epochCharacter = await epdService.getCharacteristic(0x1141);
|
||||
epochCharacter = await epdService.getCharacteristic(0xfff1);
|
||||
document.getElementById("btnConnect").innerHTML = 'Disconnect';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user