void print_device( int index ) { tdInit(); int intId = tdGetDeviceId(index); char *name = tdGetName(intId); printf("%i\t%s\t", intId, name); tdReleaseString(name); int lastSentCommand = tdLastSentCommand(intId, SUPPORTED_METHODS); char *level = 0; switch(lastSentCommand) { case TELLSTICK_TURNON: printf("ON"); break; case TELLSTICK_TURNOFF: printf("OFF"); break; case TELLSTICK_DIM: level = tdLastSentValue(intId); printf("DIMMED:%s", level); tdReleaseString(level); break; default: printf("Unknown state"); } printf("\n"); }
int main(void) { char protocol[DATA_LENGTH], model[DATA_LENGTH]; int sensorId = 0, dataTypes = 0; char value[DATA_LENGTH]; char timeBuf[80]; time_t timestamp = 0; tdInit(); while(tdSensor(protocol, DATA_LENGTH, model, DATA_LENGTH, &sensorId, &dataTypes) == TELLSTICK_SUCCESS) { //Print the sensor printf("%s,\t%s,\t%i\n", protocol, model, sensorId); //Retrieve the values the sensor supports if (dataTypes & TELLSTICK_TEMPERATURE) { tdSensorValue(protocol, model, sensorId, TELLSTICK_TEMPERATURE, value, DATA_LENGTH, (int *)×tamp); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); printf("Temperature:\t%sº\t(%s)\n", value, timeBuf); } if (dataTypes & TELLSTICK_HUMIDITY) { tdSensorValue(protocol, model, sensorId, TELLSTICK_HUMIDITY, value, DATA_LENGTH, (int *)×tamp); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); printf("Humidity:\t%s%%\t(%s)\n", value, timeBuf); } printf("\n"); } tdClose(); return 0; }
int main(void) { tdInit(); Events ev; //Our own simple eventloop while(1) { sleep(100); } tdClose(); return 0; }
void CTellstick::Init() { tdInit(); tdRegisterDeviceEvent( reinterpret_cast<TDDeviceEvent>(&CTellstick::deviceEventCallback), this ); int intNumberOfDevices = tdGetNumberOfDevices(); for (int i = 0; i < intNumberOfDevices; i++) { int id = tdGetDeviceId(i); char *name = tdGetName(id); _log.Log(LOG_NORM, "Tellstick: %s method %d", name, tdMethods(id, TELLSTICK_TURNON | TELLSTICK_TURNOFF | TELLSTICK_DIM) & TELLSTICK_DIM); bool isDimmer = tdMethods(id, TELLSTICK_TURNON | TELLSTICK_TURNOFF | TELLSTICK_DIM) & TELLSTICK_DIM; AddSwitchIfNotExits(id, name, isDimmer); tdReleaseString(name); } }
int main(int argc, char *argv[]) { int res; char myname[256]; unsigned int addr, laddr; int iflag, ntd, slot = 0; /* printf("\n"); if(argc==2||argc==3) { strncpy(myname, argv[1], 255); printf("Use argument >%s< as bin file name\n",myname); if(argc==3) { slot = atoi(argv[2]); printf("Upgrade board at slot=%d only\n",slot); } else { slot = 0; printf("Upgrade all boards in crate\n"); } } else { printf("Usage: dsc2init <bin file> [slot]\n"); exit(0); } printf("\n"); */ /* Open the default VME windows */ vmeOpenDefaultWindows(); printf("\n"); ntd = 0; iflag = 1; tdInit((3<<19),0x80000,20,iflag); ntd = tdGetNtds(); /* actual number of TD boards found */ tdGStatus(0); exit(0); }
int main(void) { int callbackId = 0; tdInit(); //Register for callback callbackId = tdRegisterSensorEvent( (TDSensorEvent)&sensorEvent, 0 ); //Our own simple eventloop while(1) { sleep(100); } //Cleanup tdUnregisterCallback( callbackId ); tdClose(); return 0; }
main(int argc, char *argv[]) #endif { int stat; int BoardNumber; char *filename; int inputchar=10; unsigned int vme_addr=0; printf("\nTD firmware update via VME\n"); printf("----------------------------\n"); #ifdef VXWORKS programName = __FUNCTION__; vme_addr = arg_vmeAddr; filename = arg_filename; #else programName = argv[0]; if(argc<3) { printf(" ERROR: Must specify two arguments\n"); tdFirmwareUsage(); return(-1); } else { vme_addr = (unsigned int) strtoll(argv[1],NULL,16)&0xffffffff; filename = argv[2]; } vmeSetQuietFlag(1); stat = vmeOpenDefaultWindows(); if(stat != OK) goto CLOSE; #endif stat = tdInit(vme_addr,0,1,TD_INIT_SKIP_FIRMWARE_CHECK); if(stat != OK) { printf("\n"); printf("*** Failed to initialize TD ***\nThis may indicate (either):\n"); printf(" a) an incorrect VME Address provided\n"); printf(" b) new firmware must be loaded at provided VME address\n"); printf("\n"); printf("Proceed with the update with the provided VME address?\n"); REPEAT: printf(" (y/n): "); inputchar = getchar(); if((inputchar == 'n') || (inputchar == 'N')) { printf("--- Exiting without update ---\n"); goto CLOSE; } else if((inputchar == 'y') || (inputchar == 'Y')) { printf("--- Continuing update, assuming VME address is correct ---\n"); } else { goto REPEAT; } } /* Read out the board serial number first */ BoardSerialNumber = tdGetSerialNumber(0,NULL); printf(" Board Serial Number from PROM usercode is: 0x%08x (%d) \n", BoardSerialNumber, BoardSerialNumber&0xffff); /* Check the serial number and ask for input if necessary */ /* Force this program to only work for TD (not TI or TS) */ if (!((BoardSerialNumber&0xffff0000) == 0x7D000000)) { printf(" This TD has an invalid serial number (0x%08x)\n",BoardSerialNumber); printf (" Enter a new board number (0-4095), or -1 to quit: "); scanf("%d",&BoardNumber); if(BoardNumber == -1) { printf("--- Exiting without update ---\n"); goto CLOSE; } /* Add the TD board ID in the MSB */ BoardSerialNumber = 0x7D000000 | (BoardNumber&0xfff); printf(" The board serial number will be set to: 0x%08x (%d)\n",BoardSerialNumber, BoardSerialNumber&0xffff); } firmwareInfo = tdGetFirmwareVersion(0); if(firmwareInfo>0) { printf(" User ID: 0x%x \tFirmware (version - revision): 0x%X - 0x%03X\n", (firmwareInfo&0xFFFF0000)>>16, (firmwareInfo&0xF000)>>12, firmwareInfo&0xFFF); }
static void __download() { int ii, i1, i2, i3, id, slot; char filename[1024]; #ifdef POLLING_MODE rol->poll = 1; #else rol->poll = 0; #endif printf("\n>>>>>>>>>>>>>>> ROCID=%d, CLASSID=%d <<<<<<<<<<<<<<<<\n",rol->pid,rol->classid); printf("CONFFILE >%s<\n\n",rol->confFile); printf("LAST COMPILED: %s %s\n", __DATE__, __TIME__); printf("USRSTRING >%s<\n\n",rol->usrString); /**/ CTRIGINIT; /* initialize OS windows and TS board */ #ifdef VXWORKS CDOINIT(TSPRIMARY); #else CDOINIT(TSPRIMARY,TIR_SOURCE); #endif /************/ /* init daq */ daqInit(); DAQ_READ_CONF_FILE; /*************************************/ /* redefine TS settings if neseccary */ tsSetUserSyncResetReceive(1); /* TS 1-6 create physics trigger, no sync event pin, no trigger 2 */ vmeBusLock(); /*tsLoadTriggerTable();*/ /*tsSetTriggerWindow(7);TS*/ // (7+1)*4ns trigger it coincidence time to form trigger type vmeBusUnlock(); /*********************************************************/ /*********************************************************/ /* set wide pulse */ vmeBusLock(); /*sergey: WAS tsSetSyncDelayWidth(1,127,1);*/ /*worked for bit pattern latch tsSetSyncDelayWidth(0x54,127,1);*/ vmeBusUnlock(); usrVmeDmaSetConfig(2,5,1); /*A32,2eSST,267MB/s*/ /*usrVmeDmaSetConfig(2,5,0);*/ /*A32,2eSST,160MB/s*/ /*usrVmeDmaSetConfig(2,3,0);*/ /*A32,MBLT*/ tdcbuf = (unsigned int *)i2_from_rol1; /******************/ /* USER code here */ /* TD setup */ ntd = 0; tdInit((3<<19),0x80000,20,0); ntd = tdGetNtds(); /* actual number of TD boards found */ tdGSetBlockLevel(block_level); tdGSetBlockBufferLevel(buffer_level); //tdAddSlave(17,2); // TI Slave - Bottom Crate (payload) //tdAddSlave(17,5); // TI Slave - Bench (GTP) tdslotmask = 0; for(id=0; id<ntd; id++) { slot = tdSlot(id); tdslotmask |= (1<<slot); printf("=======================> tdslotmask=0x%08x\n",tdslotmask); } printf("TDSLOTMASK: tdslotmask=0x%08x (from library 0x%08x)\n",tdslotmask,tdSlotMask()); sprintf(filename,"%s/portnames_%s.txt",getenv("CLON_PARMS"),getenv("EXPID")); printf("loading portnames from file >%s<\n",filename); tdLoadPortNames(filename); /* tdGStatus(0); */ /*************************************** * SD SETUP ***************************************/ printf("SD init starts\n"); vmeBusLock(); printf("SD init 1\n"); sdInit(1); /* Initialize the SD library */ sdSetActiveVmeSlots(tdslotmask); /* Use the tdslotmask to configure the SD */ sdStatus(); vmeBusUnlock(); printf("SD init done\n"); /* if TDs are present, set busy from SD board */ if(ntd>0) { printf("Set BUSY from SWB for TDs\n"); vmeBusLock(); tsSetBusySource(TS_BUSY_SWB,0); vmeBusUnlock(); } /*sergey: following piece from tsConfig.c, doing it there not always propagate correct block_level to slaves; doing it again here seems helps, have to investigate */ tsSetInstantBlockLevelChange(1); /* enable immediate block level setting */ printf("trig1: setting block_level = %d\n",block_level); sleep(1); tsSetBlockLevel(block_level); sleep(1); tsSetInstantBlockLevelChange(0); /* disable immediate block level setting */ sprintf(rcname,"RC%02d",rol->pid); printf("rcname >%4.4s<\n",rcname); #ifdef SSIPC sprintf(ssname,"%s_%s",getenv("HOST"),rcname); printf("Smartsockets unique name >%s<\n",ssname); epics_msg_sender_init(getenv("EXPID"), ssname); /* SECOND ARG MUST BE UNIQUE !!! */ #endif logMsg("INFO: User Download Executed\n",1,2,3,4,5,6); }
int main(int argc, char *argv[]) { int port = 1883; int reconnect_delay = 1; char *host = "localhost"; char subscription[128]; int rawcallback; struct context *ctx = malloc(sizeof(struct context)+sizeof(default_relay_rules)); ctx->debug = 0; ctx->failures = 0; ctx->relay_rules=default_relay_rules; // TODO read relay rules from a configuration file ctx->num_relay_rules=sizeof(default_relay_rules)/sizeof(*default_relay_rules); int opt; ctx->sub_prefix = "telldus"; ctx->pub_prefix = ""; struct relay_rule *relay_rules = ctx->relay_rules; snprintf(subscription,sizeof(subscription)-1,"%s/#",ctx->sub_prefix); while ((opt = getopt(argc, argv, "vS:d:h:p:P:")) != -1) { switch (opt) { case 'v': ctx->debug++; break; case 'h': host=strdup(optarg); break; case 'S': ctx->sub_prefix=strdup(optarg); snprintf(subscription,sizeof(subscription)-1,"%s/#",ctx->sub_prefix); break; case 'P': ctx->pub_prefix=strdup(optarg); break; case 'p': port=atoi(optarg); break; case 'd': reconnect_delay=atoi(optarg); break; default: /* '?' */ fprintf(stderr, "Usage: %s [-v] " "[-h <host>] " "[-p <port>]\n\t" "[-S <subscription topic prefix>] " "[-P <publishing topic prefix>]\n\t" "[-d <reconnect after n seconds> ]\n\n" "\t%s connects to MQTT broker at %s:%d.\n\n" "\tIt subscribes messages for topic '%s'.\n" "\tWhen a 'turnon', 'turnoff' or 'bell' message is received at %s/<device>/method it will trigger\n" "\tthe corresponding operation on a Telldus device with the same name.\n", argv[0], argv[0], host, port, subscription, ctx->sub_prefix); fprintf(stderr, "\n\tIt listens for raw events from Telldus.\n"); int f; for(f=0; f<ctx->num_relay_rules; f++) { fprintf(stderr, "\tWhen it receives a raw event where "); int i = 0; const char *separator=""; while(ctx->relay_rules[f].filters[i].key && relay_rules[f].filters[i].value) { fprintf(stderr, "%sfield '%s' value is '%s'", separator, relay_rules[f].filters[i].key, relay_rules[f].filters[i].value); i++; separator = relay_rules[f].filters[i+1].key ? ", " : " and "; } separator=""; fprintf(stderr, "\n\t\tit publishes "); int j = 0; while(relay_rules[f].mqtt_template[j].topicformat && relay_rules[f].mqtt_template[j].messageformat) { fprintf(stderr, "%sa message '%s' on topic '%s%s'", separator, relay_rules[f].mqtt_template[j].messageformat, ctx->pub_prefix, relay_rules[f].mqtt_template[j].topicformat); j++; separator = relay_rules[f].mqtt_template[j+1].topicformat ? ", \n\t\t" : " and \n\t\t"; } fprintf(stderr, "\n"); } exit(EXIT_FAILURE); } } tdInit(); // TODO: move after mqtt connection has been established when unregistering and re-registering works ok rawcallback = tdRegisterRawDeviceEvent(raw_event,ctx); do { ctx->failures = 0; // TODO: synchronization for telldus threading char hostname[21]; char id[30]; memset(hostname, 0, sizeof(hostname)); gethostname(hostname, sizeof(hostname)-1); snprintf(id, sizeof(id)-1, "mosq_pub_%d_%s", getpid(), hostname); mosquitto_lib_init(); ctx->mosq = mosquitto_new(id, true, ctx); if(!ctx->mosq) { fprintf(stderr, "Error: Out of memory.\n"); return 1; } if(ctx->debug > 0) { mosquitto_log_callback_set(ctx->mosq, my_log_callback); } int rc; rc = mosquitto_connect(ctx->mosq, host, port, 30); if(rc) { if(ctx->debug > 0) { fprintf(stderr, "failed to connect %s:%d\n", host, port); } goto clean; } mosquitto_message_callback_set(ctx->mosq, my_message_callback); rc = mosquitto_subscribe(ctx->mosq, NULL, subscription, 0); if(rc) { if(ctx->debug > 0) { fprintf(stderr, "failed to subscribe %s\n", subscription); } goto clean; } do { rc = mosquitto_loop(ctx->mosq, 60, 1); } while(rc == MOSQ_ERR_SUCCESS && !ctx->failures); clean: mosquitto_destroy(ctx->mosq); mosquitto_lib_cleanup(); } while(reconnect_delay >= 0 && ( sleep(reconnect_delay), true)); tdUnregisterCallback(rawcallback); tdClose(); return 0; }
int main() { qDebug() << "WeekTimer"; tdInit(); SQLiteWrapper lite; // This list comes from /etc/tellstick.conf // that was created with TelldusCenter. // // The name given in TelldusCenter also becomes the // uniq part of the mqtt topic. QList<WeekTimer> *weekTimerList; weekTimerList = new QList<WeekTimer>; int intNumberOfDevices = tdGetNumberOfDevices(); for (int i = 0; i < intNumberOfDevices; i++) { int id = tdGetDeviceId( i ); char *nameTmp = tdGetName( id ); QString name = QString(nameTmp); int methods = tdMethods( id, (TELLSTICK_TURNON | TELLSTICK_TURNOFF | TELLSTICK_BELL ) ); if ( (methods & TELLSTICK_TURNON ) && (methods & TELLSTICK_TURNOFF) ) { WeekTimer wt(name); wt.setID(id); QString timerdata = lite.getWeekTimer(name); if(!timerdata.isEmpty()) { wt.addNewTimers(timerdata); } // lite.getForce(name) weekTimerList->append(wt); } if ( (methods & TELLSTICK_BELL) ) { /// @todo What to do with a door bell qDebug() << id << name << "is bell"; } qDebug() << id << name; tdReleaseString(nameTmp); } for (int z = 0; z < weekTimerList->size(); ++z) { WeekTimer wt = weekTimerList->at(z); qDebug() << "WeekTimer name:" << wt.getName() << "id:" << wt.getID(); } class MosqConnect *mqtt; int rc; mosqpp::lib_init(); mqtt = new MosqConnect( "FunTechHouse_WeekTimer_Nexa", "mosqhub", 1883, weekTimerList, &lite ); while(1) { //Check if there is new mess for me rc = mqtt->loop(); if(rc) { mqtt->reconnect(); } //Then every minute or so, //update the timer output if(UnixTime::get()%30==0) { //qDebug() << UnixTime::get(); QDate nowDate = QDate::currentDate(); int dow = nowDate.dayOfWeek(); QTime nowTime = QTime::currentTime(); int hour = nowTime.hour(); int min = nowTime.minute(); qDebug() << "Time:" << dow << hour << min; for (int z = 0; z < weekTimerList->size(); ++z) { WeekTimer wt = weekTimerList->at(z); //qDebug() << "Rum" << wt.getName() << wt.getID(); int id = wt.getID(); if(id != -1) { int methods = tdMethods( id, (TELLSTICK_TURNON | TELLSTICK_TURNOFF) ); if ( (methods & TELLSTICK_TURNON ) && (methods & TELLSTICK_TURNOFF) ) { /* //Check for remote ctrl action, has the output changes since last time? int last = tdLastSentCommand( id, methods); switch ( last ) { case TELLSTICK_TURNON : qDebug() << wt.getName() << "Last cmd was Turn ON"; break; case TELLSTICK_TURNOFF : qDebug() << wt.getName() << "Last cmd was Turn OFF"; break; default : break; } */ WeekTimerOut status = wt.isON(dow, hour, min); switch ( status ) { case WT_ON: { qDebug() << wt.getName() << "Turn ON at:" << dow << hour << min; tdTurnOn(id); sleep(1); } break; case WT_DISABLED: //Do nothing! qDebug() << wt.getName() << "Not active at:" << dow << hour << min; break; case WT_OFF: //Fall thru ok default : { qDebug() << wt.getName() << "Turn OFF at:" << dow << hour << min; tdTurnOff(id); sleep(1); } break; } } } } } } mosqpp::lib_cleanup(); return 0; }
int list_devices() { tdInit(); int intNum = tdGetNumberOfDevices(); if (intNum < 0) { char *errorString = tdGetErrorString(intNum); fprintf(stderr, "Error fetching devices: %s\n", errorString); tdReleaseString(errorString); return intNum; } printf("Number of devices: %i\n", intNum); int i = 0; while (i < intNum) { print_device( i ); i++; } char protocol[DATA_LENGTH], model[DATA_LENGTH]; int sensorId = 0, dataTypes = 0; int sensorStatus = tdSensor(protocol, DATA_LENGTH, model, DATA_LENGTH, &sensorId, &dataTypes); if(sensorStatus == 0){ printf("\n\nSENSORS:\n\n%-20s\t%-20s\t%-5s\t%-5s\t%-8s\t%-20s\t%-20s\t%-20s\n", "PROTOCOL", "MODEL", "ID", "TEMP", "HUMIDITY", "RAIN", "WIND", "LAST UPDATED"); } while(sensorStatus == 0){ char tempvalue[DATA_LENGTH]; tempvalue[0] = 0; char humidityvalue[DATA_LENGTH]; humidityvalue[0] = 0; char windvalue[DATA_LENGTH]; windvalue[0] = 0; char winddirectionvalue[DATA_LENGTH]; winddirectionvalue[0] = 0; char windaveragevalue[DATA_LENGTH]; windaveragevalue[0] = 0; char windgustvalue[DATA_LENGTH]; windgustvalue[0] = 0; char rainvalue[DATA_LENGTH]; rainvalue[0] = 0; char raintotvalue[DATA_LENGTH]; raintotvalue[0] = 0; char rainratevalue[DATA_LENGTH]; rainratevalue[0] = 0; char timeBuf[80]; timeBuf[0] = 0; time_t timestamp = 0; if (dataTypes & TELLSTICK_TEMPERATURE) { tdSensorValue(protocol, model, sensorId, TELLSTICK_TEMPERATURE, tempvalue, DATA_LENGTH, (int *)×tamp); strcat(tempvalue, DEGREE); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_HUMIDITY) { tdSensorValue(protocol, model, sensorId, TELLSTICK_HUMIDITY, humidityvalue, DATA_LENGTH, (int *)×tamp); strcat(humidityvalue, "%"); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_RAINRATE) { tdSensorValue(protocol, model, sensorId, TELLSTICK_RAINRATE, rainratevalue, DATA_LENGTH, (int *)×tamp); strcat(rainratevalue, " mm/h, "); strcat(rainvalue, rainratevalue); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_RAINTOTAL) { tdSensorValue(protocol, model, sensorId, TELLSTICK_RAINTOTAL, raintotvalue, DATA_LENGTH, (int *)×tamp); //TODO detta blir väl fel, kan väl hamna i andra ordningar, eller hur? strcat(raintotvalue, " mm"); strcat(rainvalue, raintotvalue); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_WINDDIRECTION) { tdSensorValue(protocol, model, sensorId, TELLSTICK_WINDDIRECTION, winddirectionvalue, DATA_LENGTH, (int *)×tamp); //TODO or use charToInteger in common? std::stringstream inputstream; inputstream << winddirectionvalue; int direction; inputstream >> direction; direction = direction / 22.5; std::string directionabbrev = "N"; switch (direction) { case 1: directionabbrev = "NNE"; break; case 2: directionabbrev = "NE"; break; case 3: directionabbrev = "ENE"; break; case 4: directionabbrev = "E"; break; case 5: directionabbrev = "ESE"; break; case 6: directionabbrev = "SE"; break; case 7: directionabbrev = "SSE"; break; case 8: directionabbrev = "S"; break; case 9: directionabbrev = "SSW"; break; case 10: directionabbrev = "SW"; break; case 11: directionabbrev = "WSW"; break; case 12: directionabbrev = "W"; break; case 13: directionabbrev = "WNW"; break; case 14: directionabbrev = "NW"; break; case 15: directionabbrev = "NNW"; break; } strcat(windvalue, directionabbrev.c_str()); strcat(windvalue, ", "); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_WINDAVERAGE) { tdSensorValue(protocol, model, sensorId, TELLSTICK_WINDAVERAGE, windaveragevalue, DATA_LENGTH, (int *)×tamp); strcat(windaveragevalue, " m/s ("); strcat(windvalue, windaveragevalue); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } if (dataTypes & TELLSTICK_WINDGUST) { tdSensorValue(protocol, model, sensorId, TELLSTICK_WINDGUST, windgustvalue, DATA_LENGTH, (int *)×tamp); strcat(windgustvalue, " m/s) "); strcat(windvalue, windgustvalue); strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", localtime(×tamp)); } printf("%-20s\t%-20s\t%-5i\t%-5s\t%-8s\t%-20s\t%-20s\t%-20s\n", protocol, model, sensorId, tempvalue, humidityvalue, rainvalue, windvalue, timeBuf); sensorStatus = tdSensor(protocol, DATA_LENGTH, model, DATA_LENGTH, &sensorId, &dataTypes); }