Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
main.cpp
- Committer:
- tony63
- Date:
- 2019-04-12
- Revision:
- 3:8aef67673965
- Parent:
- 2:ea6275d1222f
File content as of revision 3:8aef67673965:
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Programa para establecer la comunicación con un módem Siemens A56 y un modulo FRDMKL25Z
// opera como rastreador satelital para Geolocalizacion.
// Por la UART (1) se conecta el MODEM y por la uart (2) el GPS (se lee en modo NEMEA)
// Este sistema genera una cadena de geolocalizacion para GoogleMaps con las coordenadas locales
// Si previamente se envia el mensaje (Coordenadas o coordenadas)
// El sistema ademas recibe ordenes de tipo mensaje GSM PDU para accionar cargas
// 1----Una supuesta valvula de combustible (On y Off.....on y off......)
// 2----Una cantonera para cerradura electrica (Pulso o pulso) (pulso de 7 segundos)
// Este sistema responde con un mensaje si el mensaje fue recibido (Mensaje Recibido)
//
// Adicionalmente este sistema mide un valor analogico en respuesta al mensaje..(Voltaje o voltaje)
// El sistema dispone de un jumper que permite operar el sistema con o sin GPS (jumper a tierra)
// El sistema detecta si el modem GSM esta bien conectado configurado y respondiendo correctamente
// El sistema detecta si el GPS emite cadenas NEMEA y señaliza con un led si es exitosa la conexion
// Presenta borrado automatico de SMS entrantes para evitar perdida de sincronismo en la deteccion
// de cadenas y prefijos.
// Este codigo compila sin problemas para un modulo chino bluepill STM32F103
// con las modificaciones adecuadas de puertos y leds
//+++++++++++++++++++++++++++++++ARCHIVOS INCLUIDOS*************************************************************************
#include "mbed.h"
#include "DebouncedIn.h"
#include "stdio.h"
#include "string.h"
#include "GPS.h"
Timer t;
//++++++++++++++++++++++++++++++++++++salidas y entradas digitales+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DigitalOut LedVerde(LED2);
DigitalOut LedRojo(LED1);
DigitalOut LedAzul(LED3);
DigitalOut valvula(PTC5);//salida de la valvula
DigitalOut puerta(PTC6);//salida de cerradura magnetica (en caso de usarlo en casa o desbloquear puertas de carro)
DigitalIn sin_gps(PTA13);
// Entrada análoga
AnalogIn v(PTB0);
float medi;
// Declaración de los puertos de la FRDM, Módem GSM y GPS.
Serial GSM(PTE0,PTE1); // Puertos del FRDM para el Módem.
Serial pc(USBTX,USBRX);
GPS gps(PTE22, PTE23); // Puerto del FDRM para el GPS.
// Declaración de variables
// Cadenas de caracteres con las que se va a trabajar.
char DE1[255];
char DS1[255];
char DE2[255];
char DS2[255];
char buffer[512];
char resp[6];
char tam[2];
char mensaje[100];
char gprsBuffer[30];
//Variables enteras y caracteres
int g=0;
int count;
int i, K, LENOUT1, LENIN1, LENOUT2, LENIN2, C;
int c=0;
char r[]="";
char msg[256];
char char1;
int ind;
float med;
char outmed[16], outmedn[16];
int ret = 1;
// Adquisición de números de teléfono, emisor - receptor
char tel[15];
// El GPS entregará al celular coordenadas expresadas en latitud y longitud
// según la ubicación que encuentre, por lo tanto se declaran estas variables.
float lo,la;
char clo[255], cla[255]; // Cadenas a capturar para latitud y longitud.
char la_lo[255], volt[255];
// Cadena de google maps
char http2[255];
char http[] = "http://maps.google.com/maps?q=";
char buf[100];
// Relleno de datos propio del protocolo de SMS.
char relle1[] = "0011000A91";
char relle2[] = "0000AA";
// Reverses a string 'str' of length 'len'
// driver program to test above funtion.
void reverse(char *str, int len)
{
int i=0, j=len-1, temp;
while (i<j)
{
temp = str[i];
str[i] = str[j];
str[j] = temp;
i++; j--;
}
}
// Converts a given integer x to string str[]. d is the number
// of digits required in output. If d is more than the number
// of digits in x, then 0s are added at the beginning.
int intToStr(int x, char str[], int d)
{
int i = 0;
while (x)
{
str[i++] = (x%10) + '0';
x = x/10;
}
// If number of digits required is more, then
// add 0s at the beginning
while (i < d)
str[i++] = '0';
reverse(str, i);
str[i] = '\0';
return i;
}
// Converts a floating point number to string.
void ftoa(float n, char *res, int afterpoint)
{
// Extract integer part
int ipart = (int)n;
// Extract floating part
float fpart = n - (float)ipart;
// convert integer part to string
int i = intToStr(ipart, res, 0);
// check for display option after point
if (afterpoint != 0)
{
res[i] = '.'; // add dot
// Get the value of fraction part upto given no.
// of points after dot. The third parameter is needed
// to handle cases like 233.007
float fp=10;
fpart =fpart * pow(fp,afterpoint);
intToStr((int)fpart, res + i + 1, afterpoint);
}
}
//++++++++++++++++++++++++++++++++++++libreria para vaciar el modem+++++++++++++++++++++++++++++++++++++++++++++++++++++++
void FlushGSM(void) {
char1 = 0;
while (GSM.readable()){
char1 = GSM.getc();
}
return;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void callback(){
// Note: you need to actually read from the serial to clear the RX interrupt
pc.printf("%c\n", GSM.getc());
}
//-----------------------------------------------------------------------------------------------------------------------------
// Esta funcion de abajo lee todo un bufer hasta encontrar CR o LF y el resto lo rellena de
// $, count es lo que va a leer. Lo leido lo mete en buffer que es una cadena previamente definida
// incorpora medida de tiempo si se demora mas de tres segundos retorna fracaso con -1
int readBuffer(char *buffer,int count){
int i=0;
t.start(); // start timer
while(1) {
while (GSM.readable()) {
char c = GSM.getc();
if (c == '\r' || c == '\n') c = '$';
buffer[i++] = c;
if(i > count)break;
}
if(i > count)break;
if(t.read() > 3) {
t.stop();
t.reset();
break;
}
}
wait(0.5);
while(GSM.readable()){ // display the other thing..
char c = GSM.getc();
}
return 0;
}
//--------------------------------------------------------------------------------------------------------------
// Esta función de abajo limpia o borra todo un "buffer" de tamaño "count",
// lo revisa elemento por elemento y le mete el caracter null que indica fin de cadena.
// No retorna nada.
void cleanBuffer(char *buffer, int count){
for(int i=0; i < count; i++) {
buffer[i] = '\0';
}
}
//--------------------------------------------------------------------------------------------------------------
// Esta función de abajo envia un comando parametrizado como cadena
// puede ser un comando tipo AT.
void sendCmd(char *cmd){
GSM.puts(cmd);
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo espera la respuesta de un comando que debe ser idéntica a la cadena "resp" y un tiempo "timeout",
// si todo sale bien retorna un cero que en la programacion hay que validar,
// si algo sale mal (no se parece o se demora mucho) retorna -1 que debera validarse con alguna expresion logica.
int waitForResp(char *resp, int timeout){
int len = strlen(resp);
int sum=0;
t.start();
while(1) {
if(GSM.readable()) {
char c = GSM.getc();
sum = (c==resp[sum]) ? sum+1 : 0;// esta linea de C# sum se incrementa o se hace cero segun c
if(sum == len)break; //ya acabo se sale
}
if(t.read() > timeout) { // time out chequea el tiempo minimo antes de salir perdiendo
t.stop();
t.reset();
return -1;
}
}
t.stop(); // stop timer antes de retornar
t.reset(); // clear timer
while(GSM.readable()) { // display the other thing..
char c = GSM.getc();
}
return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo es muy completa y útil, se encarga de enviar el comando y esperar la respuesta.
// Si todo sale bien retorna un cero (herencia de las funciones contenedoras) que en la programacion hay que validar
// con alguna expresion lógica.
int sendCmdAndWaitForResp(char *cmd, char *resp, int timeout){
sendCmd(cmd);
return waitForResp(resp,timeout);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo chequea que el módem este vivo, envia AT, le contesta con OK y espera 2 segundos.
int powerCheck(void){ // Este comando se manda para verificar si el módem esta vivo o conectado.
return sendCmdAndWaitForResp("AT\r\n", "OK", 2);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo chequea el estado de la sim card
// y si todo sale bien retorna un cero que en la programacion hay que validar
// con alguna expresión lógica.
int checkSIMStatus(void){
char gprsBuffer[30];
int count = 0;
cleanBuffer(gprsBuffer, 30);
while(count < 3){
sendCmd("AT+CPIN?\r\n");
readBuffer(gprsBuffer,30);
if((NULL != strstr(gprsBuffer,"+CPIN: READY"))){
break;
}
count++;
wait(1);
}
if(count == 3){
return -1;
}
return 0;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo chequea la calidad de la señal
// y si todo sale bien retorna con el valor de señal útil o un -1 si no es aceptable, en la programacion hay que validar
// con alguna expresión lógica.
int checkSignalStrength(void){
char gprsBuffer[100];
int index, count = 0;
cleanBuffer(gprsBuffer,100);
while(count < 3){
sendCmd("AT+CSQ\r\n");
readBuffer(gprsBuffer,25);
if(sscanf(gprsBuffer, "AT+CSQ$$$$+CSQ: %d", &index)>0) {
break;
}
count++;
wait(1);
}
if(count == 3){
return -1;
}
return index;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta funcion de abajo inicaliza el módem. Se compone de un grupo de subfunciones ya definidas previamente
// primero chequea que este vivo,
// segundo chequea el estado de la simcard,
// tercero chequea la intencidad de señal celular,
// cuarto aplica la configuracion
// y si todo sale bien retorna un cero que en la programacion hay que validar
// con alguna expresión lógica.
int init(){
if (0 != sendCmdAndWaitForResp("AT\r\n", "OK", 3)){
return -1;
}
if (0 != sendCmdAndWaitForResp("AT+CNMI=1,1\r\n", "OK", 3)){
return -1;
}
if (0 != sendCmdAndWaitForResp("AT+CMGF=0\r\n", "OK", 3)){
return -1;
}
if (0 != sendCmdAndWaitForResp("AT+CBST=7,0,1\r\n", "OK", 3)){ //velocidad fija a 9600, modem asincronico no transparente
return -1;
}
if (0 != sendCmdAndWaitForResp("ATE\r\n", "OK", 3)){ //se le quita el eco al modem GSM
return -1;
}
LedVerde=0;
return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta funcion de abajo intenta leer un mensaje de texto en formato PDU o HEX
// y si todo sale bien retorna un cero que en la programacion hay que validar
// con alguna expresión lógica.
int readSMSpdu(char *message, int index){
int i = 0;
char gprsBuffer[100];
char *p,*s;
GSM.printf("AT+CMGR=%d\r\n",index);
cleanBuffer(gprsBuffer,100);
readBuffer(gprsBuffer,100);
if(NULL == ( s = strstr(gprsBuffer,"+CMGR"))) {
return -1;
}
if(NULL != ( s = strstr(gprsBuffer,"+32"))) {
p = s + 6;
while((*p != '$')&&(i < 5)) {
message[i++] = *(p++);
}
message[i] = '\0';
}
return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función de abajo borra mensajes SMS del modem
// y si todo sale bien retorna un cero que en la programacion hay que validar
// con alguna expresion logica.
int deleteSMS(int index){
char cmd[32];
snprintf(cmd,sizeof(cmd),"AT+CMGD=%d\r\n",index);
sendCmd(cmd);
return 0;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Esta función devuelve la confirmacion del mensaje a quien lo envio.
int recibe_ok(){
pc.printf("AT+CMGS=27\n\r");
GSM.printf("AT+CMGS=27\n\r");
wait(1);
GSM.printf("0011000A91%s0000AA10CDB27B1E569741F2F2382D4E93DF",tel);
GSM.printf("\n\r");
pc.printf("0011000A91%s0000AA10CDB27B1E569741F2F2382D4E93DF\n\r",tel);
pc.printf("\n\r");//control+Z
wait(1);
return 0;
}
//*******************************************************************************************************************************
// Programas a ejecutar.
int main(){
//configuramos los puertos seriales
GSM.baud(9600);//configura los baudios de la FRDMKL25Z en 9600
GSM.format(8,Serial::None,1); //configura el formato de los datos de la UART
LedVerde = 1; // APAGO LOS LEDS
LedRojo = 1;
LedAzul = 1;
LedRojo = 0; // PRENDO EL LED ROJO
valvula=1; // se enciende la valvula por defecto
//****************** CONFIGURACIÓN DEL MODEM GSM (TELEFONO CELULAR SIEMENS A56i).
inicio1:
ret = init();
if(ret==0){ // esta bien conectado el modem GSM.........................................................................
LedRojo = 1; // apagar led rojo
LedVerde = 0; // Enciende LED Verde para confirmar la comunicación OK con el módem.
pc.printf("Modem configurado\n");//envia mensaje a la terminal........................................................
}
else{
wait(1);
goto inicio1; // se hace un reintento de conexion o el sistema no funcionara
}
//Luego se verifica GPS bien conectado (si esta habilitado por dip sw)y recibiendo satelites
if(!sin_gps){
pc.printf("SIN GPS\n\r");//confirma con teminal que no se habilito el gps
goto seguir9;// de inmediato funcionara sin gps
}
//*****************************************Test para un GPS bien instalado***********************************************
//******************************************esta bien el GPS*****************************************************************
pc.printf("CON GPS\n\r");
g=gps.sample();
if(g){
LedAzul=0;
pc.printf("GPS--OK\n\r");
}
seguir9:
//**********************inicia codigo ciclico**********************************************
//**********************inicia codigo ciclico**********************************************
//**********************inicia codigo ciclico**********************************************
//****************DESDE ACA SE LEE CUALQUIER MENSAJE SMS ENTRANTE**************************
while(1){
if (GSM.readable()){
readBuffer(buffer,110);
pc.printf("%s\r\n",buffer);
for(i=0; i<5; i++){
resp[i] = buffer[i];
}
pc.printf("%s\r\n", resp);
if(strcmp("$$+CM", resp) == 0){ //COMPARA resp con "+CMTI"
pc.printf("Llego MSG\r\n");
cleanBuffer(buffer,10);
GSM.printf("AT+CMGL=0\r\n"); // Envío comando para leer mensaje
pc.printf("AT+CMGL=0\r\n");
//GSM.printf("AT+CMGD=0\r\n"); // Envío comando para borrar el mensaje.
readBuffer(buffer,110);
pc.printf("%s\r\n",buffer);
// Lectura el teléfono emisor
for(i=0; i<10; i++){
tel[i] = buffer[i+40];
}
pc.printf("Telefono: %c%c%c%c%c%c%c%c%c%c\r\n", tel[1], tel[0], tel[3], tel[2], tel[5], tel[4], tel[7], tel[6], tel[9], tel[8]);
// Lectura del tamaño
for(i=0;i<2;i++){
tam[i] = buffer[i + 68];
}
pc.printf("%s-\r\n", tam);
// Lectura del mensaje
for(i=0;i<26;i++){
msg[i] = buffer[i+70]; // Lee un mensaje de 26 caracteres máximo desde la posición 70 del buffer.
}
pc.printf("%s-\r\n", msg);
// Decodificación del mensaje
// Comparar el mensaje
deleteSMS(1); // Se borran los mensajes por medio de una función
readBuffer(buffer, 110);
//*************************SEFGUNDO CASO ACTIVAR SALIDAS************************************ }
//.................activar valvula de combustible...............On..on.......................
if((strncmp("4F37", msg, 4) == 0) || (strncmp("6F37", msg, 4) == 0)){
recibe_ok();
valvula = 1; // Encender valvula.
}
// ...................................................... off y Off........................
if((strncmp("4FB319", msg, 6) == 0) || (strncmp("6FB319", msg, 6) == 0)){
recibe_ok();
valvula = 0; // apagar valvula.
}
//............................Envia un Pulso Para Puerta Pulso...D03A7BFE06 pulso.F03A7BFE06..............
if((strncmp("D03A7BFE06", msg, 10) == 0) || (strncmp("F03A7BFE06", msg, 10) == 0)){
recibe_ok();//CONFIRMACION DE RECEPCION REMOTA
puerta = 1; // Encender Puerta.
wait(7);//pulso de 7 segundos
puerta = 0;//apaga puerta
}
// COMPARA resp con "E3F75B4E2EBBC3E4F01C" que es "coordenadas", o "C3F75B4E2EBBC3E4F01C" que es "Coordenadas".
if((strncmp("E3F75B4E2EBBC3E4F01C", msg, 20) == 0) || (strncmp("C3F75B4E2EBBC3E4F01C", msg, 20) == 0)){
recibe_ok();
LedVerde = 1; // Encender LED azul.
LedAzul = 0;
wait(2);
if(gps.sample()){
lo = gps.longitude;
la = gps.latitude;
pc.printf("\nLongitud entera = %f, Latitud entera = %f\n", lo, la);
//wait(0.5);
//LONGITUD
sprintf (clo, "%f", lo);
pc.printf ("\nLongitud = %s\n",clo);
//wait(0.5);
// LATITUD
sprintf (cla, "%f", la);
pc.printf ( "\nLatitud = %s\n",cla);
//wait(0.5);
// Concatenando las cadenas de Latitud y Longitud
strcpy(la_lo,cla);
strcat(la_lo,",");
strcat(la_lo,clo);
pc.printf("\nLatitud, Longitud: %s\n",la_lo);
//Ahora se juntan las cadenas obtenidas y se agrega el protocolo de transferencia de hipertextos http
strcpy(DE1,http);
strcat(DE1,la_lo);
pc.printf("\nDireccion: %s\n",DE1);
pc.printf("\n");
LENIN1 = strlen(DE1);
//Conversión a octetos.
K = 0;
C = 0;
for (i = 0; i < LENIN1; i++){
DS1[i] = DE1[i + C] >> K | DE1[i + C + 1] << (7 - K);
if(DS1[i] == 0x00) {LENOUT1 = i; goto salir1;}
K++;
if (K == 7) {K = 0; C++;} // se chequea que ya se acabaron los bits en un ciclo de conversion.
}
salir1:
for (i = 0; i < LENIN1; i++){
pc.printf("%X", DE1[i]);
}
pc.printf(":\r\n");
for (i = 0; i < LENOUT1; i++){
pc.printf("%2X", DS1[i]&0x000000FF);
}
pc.printf("\r\nLENOUT GPS: %d, LENIN GPS: %2X\r\n", LENOUT1, LENIN1);
// Concatenación del mensaje en formato PDU y envío del mismo.
ind = 14 + LENOUT1 - 1;
GSM.printf("AT+CMGS=%d\r\n",ind);
pc.printf("AT+CMGS=%d\r\n",ind);
pc.printf(relle1);
GSM.printf(relle1);
for (i=0 ;i<=9; i++) {
pc.printf("%c",tel[i]);
GSM.printf("%c",tel[i]);
}
pc.printf(relle2);
GSM.printf(relle2);
pc.printf("%2X", LENIN1);
GSM.printf("%2X", LENIN1);
for (i = 0; i < LENOUT1; i++){
pc.printf("%02X", DS1[i]&0x000000FF);
GSM.printf("%02X", DS1[i]&0x000000FF);
}
wait(1);
GSM.putc((char)0x1A); // Ctrl - Z.
GSM.scanf("%s",buf); // Estado del mensaje (Envió o Error).
pc.printf(">%s\n",buf);
pc.printf("\n");
}
wait(2);
LedAzul = 1;
LedVerde = 0;
GSM.printf("AT+CMGD=0\r\n"); // Borra el mensaje actual (Posición "0").
//pc.printf("\n%s\n\n", "Borro mensaje");
}
// COMPARA resp con "F6379B1E569701" que es "voltaje", o "F6379B1E569701" que es "Voltaje".
if((strncmp("F6379B1E569701", msg, 14) == 0) || (strncmp("D6379B1E569701", msg, 14) == 0)){
//recibe_ok();
LedRojo = 0; // Encender LED amarillo.
LedVerde = 0;
LedAzul = 1;
wait(2);
med = v.read()*3.3;
medi = v.read();
pc.printf("\n%f\n", medi);
cleanBuffer(outmed, 16);
if (med < 1){ // Se convierte la Medida a caracter.
strcat(outmed, "0");
ftoa(med, outmedn, 5);
strcat(outmed, outmedn);
}
else{
ftoa(med, outmed, 5);
}
strcpy(DE2,"Voltaje: ");
strcat(DE2,outmed);
pc.printf("\n%s\n\n", DE2);
LENIN2 = strlen(DE2);
//Conversión a octetos.
K = 0;
C = 0;
for (i = 0; i < LENIN2; i++){
DS2[i] = DE2[i + C] >> K | DE2[i + C + 1] << (7 - K);
if(DS2[i] == 0x00) {LENOUT2 = i; goto salir2;}
K++;
if (K == 7) {K = 0; C++;} // se chequea que ya se acabaron los bits en un ciclo de conversion.
}
salir2:
for (i = 0; i < LENIN2; i++){
pc.printf("%X", DE2[i]);
}
pc.printf(":\r\n");
for (i = 0; i < LENOUT2; i++){
pc.printf("%2X", DS2[i]&0x000000FF);
}
pc.printf("\r\nLENOUT VOLTAJE: %d, LENIN VOLTAJE: %2X\r\n", LENOUT2, LENIN2);
// Concatenación del mensaje en formato PDU y envío del mismo.
ind = 14 + LENOUT2 - 1;
GSM.printf("AT+CMGS=%d\r\n",ind);
pc.printf("AT+CMGS=%d\r\n",ind);
pc.printf(relle1);
GSM.printf(relle1);
for (i=0; i <= 9; i++) {
pc.printf("%c",tel[i]);
GSM.printf("%c",tel[i]);
}
pc.printf(relle2);
GSM.printf(relle2);
pc.printf("%2X", LENIN2);
GSM.printf("%2X", LENIN2);
/*pc.printf("0F");
GSM.printf("0F");*/
for (i = 0; i < LENOUT2; i++){
pc.printf("%02X", DS2[i]&0x000000FF);
GSM.printf("%02X", DS2[i]&0x000000FF);
}
wait(1);
GSM.putc((char)0x1A); // Ctrl - Z
GSM.scanf("%s",buf); // Estado del mensaje (Envió o Error).
pc.printf(">%s\n\n",buf);
pc.printf("\n");
wait(2);
LedRojo=1;
LedVerde=0;
GSM.printf("AT+CMGD=0\r\n"); // Borra el mensaje actual (Posición "0").
//pc.printf("\n%s\n\n", "Borro mensaje");
}
}
}
}
}