int initKeithley(void){ /* * First we open a session to the VISA resource manager. We are * returned a handle to the resource manager session that we must * use to open sessions to specific instruments. */ status = viOpenDefaultRM (&defaultRM); if (status < VI_SUCCESS) { printf("Could not open a session to the VISA Resource Manager!\n"); exit (EXIT_FAILURE); return 1; } /* * Next we use the resource manager handle to open a session to a * GPIB instrument at address 2. A handle to this session is * returned in the handle inst. */ status = viOpen (defaultRM, "GPIB::2::INSTR", VI_NULL, VI_NULL, &inst); if (status < VI_SUCCESS) { printf("Could not open a session to the device simulator"); return 1; } /* Now we must enable the service request event so that VISA * will receive the events. Note: one of the parameters is * VI_QUEUE indicating that we want the events to be handled by * a synchronous event queue. The alternate mechanism for handling * events is to set up an asynchronous event handling function using * the VI_HNDLR option. The events go into a queue which by default * can hold 50 events. This maximum queue size can be changed with * an attribute but it must be called before the events are enabled. */ status = viEnableEvent (inst, VI_EVENT_SERVICE_REQ, VI_QUEUE, VI_NULL); if (status < VI_SUCCESS) { printf("The SRQ event could not be enabled\n"); return 1; } /* * Now the VISA write command is used to send a request to the * instrument to generate a sine wave and assert the SRQ line * when it is finished. Notice that this is specific to one * particular instrument. */ KeithWrite("*RST\n"); KeithOn(); KeithWrite(":SOUR:FUNC VOLT\n"); KeithWrite(":SOUR:VOLT:RANG 210\n"); KeithWrite(":SOUR:VOLT:AUTO 0\n"); KeithSetVoltage(0); //float val = KeithGetVoltage(); //printf("Here is the data: %f\n", val); return 0; }
void VisaEmitter::EIO_Open(GenericBaton* data) { char temp[QUERY_STRING_SIZE]; ViStatus status = -1; if (this->isConnected) { _snprintf(temp, sizeof(temp), "Already connected %s", session); ErrorCodeToString(temp, status, data->errorString); return; } status = viOpenDefaultRM(&defaultRM); if (status < VI_SUCCESS) { _snprintf(temp, sizeof(temp), "Opening RM"); ErrorCodeToString(temp, status, data->errorString); return; } status = viOpen(defaultRM, data->command, VI_NULL, this->timeoutMiliSeconds, &session); if (status < VI_SUCCESS) { _snprintf(temp, sizeof(temp), "Opening session %s", data->command); ErrorCodeToString(temp, status, data->errorString); return; } status = viSetAttribute(session, VI_ATTR_TMO_VALUE, this->timeoutMiliSeconds); if (status < VI_SUCCESS) { _snprintf(temp, sizeof(temp), "Setting timeout to %d", this->timeoutMiliSeconds); ErrorCodeToString(temp, status, data->errorString); return; } this->isConnected = true; // status = viSetAttribute(instr, VI_ATTR_SEND_END_EN, VI_TRUE); // terminate reads on a carriage return 0x0a 0x0d // LF (Line feed, '\n', 0x0A, 10 in decimal) // Carriage return, '\r', 0x0D, 13 in decimal // status = viSetAttribute(session, VI_ATTR_TERMCHAR, 0x0A); //status = viSetAttribute(session, VI_ATTR_TERMCHAR_EN, VI_TRUE); if (this->assertREN) { viGpibControlREN(session, VI_GPIB_REN_ASSERT); } if (this->enableSRQ) { m_async = uv_async_t(); m_async.data = this; uv_async_init(uv_default_loop(), &m_async, reinterpret_cast<uv_async_cb>(aCallback)); isAsyncInitialized = true; status = viInstallHandler(session, VI_EVENT_SERVICE_REQ, callback, this->uniqueSRQhandlerIdentification); if (status >= VI_SUCCESS) { status = viEnableEvent(session, VI_EVENT_SERVICE_REQ, VI_HNDLR, VI_NULL); } if (status < VI_SUCCESS) { _snprintf(temp, sizeof(temp), "Post AfterOpenSuccess session %s", data->command); ErrorCodeToString(temp, status, data->errorString); return; } this->installedSRQHanlder = true; } _snprintf_s(data->result, _countof(data->result), _TRUNCATE, "%d", session); }
// ================================================================================================ // FUNCTION : mexVISA // ------------------------------------------------------------------------------------------------ // Purpose : Implement basic VISA functions for a MATLAB toolbox // Parameters: passed from MATLAB mexFunction // Returns : {0=success, -1=failure} // ================================================================================================ int mexVISA(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { ViStatus Status; // returned status from called VISA function char *tcmd, cmd[256]; // passed command parameter // ---------------------- // Get the command string // ---------------------- if(nrhs<1) mexErrMsgTxt("A string argument must be given as the first parameter."); tcmd = ViMexUtil_GetStringValue(prhs[0],0); strncpy(cmd, tcmd, 256); mxFree(tcmd); cmd[255] = 0; // copied and free tcmd as it is not free'd below // ****************************************** // ***** ***** // ***** COMMAND LOOKUP-AND-EXECUTE ***** // ***** ***** // ****************************************** // -------------- // viOpen // -------------- if(!strcmp(cmd, "viOpen()")) { ViSession RMSession, Session = 0; ViString Resource; ViUInt32 Timeout; if( (nrhs!=3) && (nrhs!=4) ) mexErrMsgTxt("Incorrect number of input parameters (needs to be 3 or 4)"); if( nlhs != 2) mexErrMsgTxt("2 output terms were expected\n"); if(!mexVISA_GetViRM(&RMSession)) { Resource = ViMexUtil_GetStringValue(prhs[1], 0); Timeout = (ViUInt32)ViMexUtil_GetUIntValue(prhs[2]); if(ResourceChecking) { if(mexVISA_ResourceIsOpen(Resource, &Session)) { if(AllowSharedSessions) Status = VI_SUCCESS; else mexErrMsgTxt(" Resource is already open"); } } if(!Session) { Status = viOpen(RMSession, Resource, VI_NULL, Timeout, &Session); if(ResourceChecking && (Status == VI_SUCCESS)) { if(nrhs == 3) mexVISA_InsertSession(Session, Resource, "no stack trace available\n"); else { ViString StackTrace = ViMexUtil_GetStringValue(prhs[3], 0); mexVISA_InsertSession(Session, Resource, StackTrace); mxFree(StackTrace); } } } ViMexUtil_PutIntValue(Status, &plhs[0]); ViMexUtil_PutUIntValue((unsigned)Session, &plhs[1]); mxFree(Resource); return 0; } else mexErrMsgTxt("mexVISA: Call to viOpenDefaultRM prior to viOpen failed."); } // --------------- // viClose // --------------- if(!strcmp(cmd, "viClose()")) { ViSession Session; ViMexUtil_CheckParameters(nlhs,nrhs,1,2); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Status = viClose(Session); if(ResourceChecking && (Status == VI_SUCCESS)) mexVISA_RemoveSession(Session); ViMexUtil_PutIntValue((int)Status, &plhs[0]); return 0; } // --------------- // viWrite // --------------- if(!strcmp(cmd, "viWrite()")) { ViSession Session; ViUInt32 Count, Return_Count; ViBuf Buffer; char *Message; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Buffer = (ViBuf)ViMexUtil_GetStringValue(prhs[2], ((ViUInt32 *)&Count)); Message = (char *)mxCalloc(Count+2, sizeof(char)); if(!Message) mexErrMsgTxt("Memory allocation error."); sprintf(Message,"%s",Buffer); Status = viWrite(Session, Message, Count-1, &Return_Count); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutUIntValue(Return_Count, &plhs[1]); mxFree(Message); mxFree((char *)Buffer); return 0; } // --------------- // viWriteBinary // --------------- if(!strcmp(cmd, "viWriteBinary()")) { ViSession Session; ViUInt32 Count, Return_Count; ViBuf Buffer; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Buffer = (ViBuf)ViMexUtil_GetBinaryArray(prhs[2], ((ViUInt32 *)&Count)); Status = viWrite(Session, Buffer, Count, &Return_Count); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutUIntValue(Return_Count, &plhs[1]); mxFree((char *)Buffer); return 0; } // --------------- // viPrintf // --------------- if(!strcmp(cmd, "viPrintf()")) { ViSession Session; ViUInt32 Count; ViBuf Buffer; ViMexUtil_CheckParameters(nlhs,nrhs,1,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Buffer = (ViBuf)ViMexUtil_GetStringValue(prhs[2], ((ViUInt32 *)&Count)); Status = viPrintf(Session,"%s",Buffer); ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree((char *)Buffer); return 0; } // -------------- // viRead // -------------- if(!strcmp(cmd, "viRead()")) { ViSession Session; ViUInt32 Count, Return_Count; unsigned char *Buffer; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Count = (ViUInt32)ViMexUtil_GetUIntValue(prhs[2]); Buffer = (unsigned char *)mxCalloc(Count+1,sizeof(char)); if(!Buffer) mexErrMsgTxt("Memory Allocation Failed.\n"); Status = viRead(Session, Buffer, Count, &Return_Count); if(Return_Count < Count) Buffer[Return_Count-1] = '\0'; ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutString(Buffer, &plhs[1]); mxFree(Buffer); // removed 6/4/04 for debug return 0; } // -------------- // viReadBinary // -------------- if(!strcmp(cmd, "viReadBinary()")) { ViSession Session; ViUInt32 Count, Return_Count; unsigned char *Buffer; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Count = (ViUInt32)ViMexUtil_GetUIntValue(prhs[2]); Buffer = (unsigned char *)mxCalloc(Count+1,sizeof(char)); if(!Buffer) mexErrMsgTxt("Memory Allocation Failed.\n"); Status = viRead(Session, Buffer, Count, &Return_Count); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutBinaryArray(Return_Count, Buffer, &plhs[1]); mxFree(Buffer); return 0; } // ------- // viClear // ------- if(!strcmp(cmd, "viClear()")) { ViSession Session; ViMexUtil_CheckParameters(nlhs,nrhs,1,2); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Status = viClear(Session); ViMexUtil_PutIntValue((int)Status, &plhs[0]); return 0; } // --------- // viReadSTB // --------- if(!strcmp(cmd, "viReadSTB()")) { ViSession Session; ViUInt16 Status_Byte; ViMexUtil_CheckParameters(nlhs,nrhs,2,2); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Status = viReadSTB(Session, &Status_Byte); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutUIntValue((unsigned)Status_Byte, &plhs[1]); return 0; } // --------------- // viAssertTrigger // --------------- if(!strcmp(cmd, "viAssertTrigger()")) { ViSession Session; ViUInt16 Protocol; char *ProtocolString; ViMexUtil_CheckParameters(nlhs,nrhs,1,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); ProtocolString = ViMexUtil_GetStringValue(prhs[2], 0); if(!strcmp(ProtocolString, "VI_TRIG_PROT_DEFAULT")) Protocol = VI_TRIG_PROT_DEFAULT; else if(!strcmp(ProtocolString, "VI_TRIG_PROT_ON")) Protocol = VI_TRIG_PROT_ON; else if(!strcmp(ProtocolString, "VI_TRIG_PROT_OFF")) Protocol = VI_TRIG_PROT_OFF; else if(!strcmp(ProtocolString, "VI_TRIG_PROT_SYNC")) Protocol = VI_TRIG_PROT_SYNC; else { mxFree(ProtocolString); mexErrMsgTxt("Unrecognized protocol definition"); } Status = viAssertTrigger(Session, Protocol); ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree(ProtocolString); return 0; } // ------------ // viStatusDesc // ------------ if(!strcmp(cmd, "viStatusDesc()")) { ViSession Session; char *Description; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); Status = (ViStatus) ViMexUtil_GetIntValue(prhs[2]); Description = (char *)mxCalloc(256,sizeof(char)); Status = viStatusDesc(Session, Status, Description); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutString(Description, &plhs[1]); //mxFree(Description); return 0; } // -------------- // viGetAttribute // -------------- if(!strcmp(cmd, "viGetAttribute()")) { ViSession Session; ViAttr Attribute; char *AttrString; int Type; double dVal; char buffer[256]; ViMexUtil_CheckParameters(nlhs,nrhs,2,3); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); AttrString = ViMexUtil_GetStringValue(prhs[2], 0); if(mexVISA_Get_VI_ATTR(AttrString, &Attribute, &Type)) { mxFree(AttrString); mexErrMsgTxt("Unrecognized/unsupported VISA Attribute type."); } Status = viGetAttribute(Session, Attribute, ((void *)buffer)); ViMexUtil_PutIntValue((int)Status, &plhs[0]); if(Status >= 0) { switch(Type) { case TYPE_ViString : break; // handle below case TYPE_ViStatus : dVal = (double)(*((ViStatus *)buffer)); break; case TYPE_ViBoolean : dVal = (double)(*((ViBoolean *)buffer)); break; case TYPE_ViInt8 : dVal = (double)(*((ViInt8 *)buffer)); break; case TYPE_ViInt16 : dVal = (double)(*((ViInt16 *)buffer)); break; case TYPE_ViInt32 : dVal = (double)(*((ViInt32 *)buffer)); break; case TYPE_ViUInt8 : dVal = (double)(*((ViUInt8 *)buffer)); break; case TYPE_ViUInt16 : dVal = (double)(*((ViUInt16 *)buffer)); break; case TYPE_ViUInt32 : dVal = (double)(*((ViUInt32 *)buffer)); break; case TYPE_ViSession : dVal = (double)(*((ViSession *)buffer)); break; case TYPE_ViBusAddress: dVal = (double)(*((ViBusAddress*)buffer)); break; case TYPE_ViBusSize : dVal = (double)(*((ViBusSize *)buffer)); break; case TYPE_ViVersion : dVal = (double)(*((ViVersion *)buffer)); break; case TYPE_ViAccessMode: dVal = (double)(*((ViAccessMode*)buffer)); break; case TYPE_ViEventType : dVal = (double)(*((ViEventType *)buffer)); break; case TYPE_ViJobId : dVal = (double)(*((ViJobId *)buffer)); break; default: { mxFree(AttrString); mexErrMsgTxt("Unhandled case in mexVISA.c under viGetAttribute."); } } if(Type == TYPE_ViString) { ViMexUtil_PutString(buffer, &plhs[1]); } else ViMexUtil_PutRealValue(dVal, &plhs[1]); } else ViMexUtil_PutRealValue(-1.0, &plhs[1]); mxFree(AttrString); return 0; } // -------------- // viSetAttribute // -------------- if(!strcmp(cmd, "viSetAttribute()")) { ViSession Session; ViAttr Attribute; char *AttrString; double dVal; int Type; ViMexUtil_CheckParameters(nlhs,nrhs,1,4); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); AttrString = ViMexUtil_GetStringValue(prhs[2], 0); if(mexVISA_Get_VI_ATTR(AttrString, &Attribute, &Type)) { mxFree(AttrString); mexErrMsgTxt("Unrecognized/unsupported VISA Attribute type."); } if(Type != TYPE_ViString) { dVal = ViMexUtil_GetRealValue(prhs[3]); } switch(Type) { case TYPE_ViInt8: case TYPE_ViInt16: case TYPE_ViInt32: { ViAttrState attrState = (ViAttrState)((ViInt32)dVal); Status = viSetAttribute(Session, Attribute, attrState); } break; case TYPE_ViStatus: case TYPE_ViBoolean: case TYPE_ViUInt8: case TYPE_ViUInt16: case TYPE_ViUInt32: case TYPE_ViSession: case TYPE_ViBusAddress: case TYPE_ViBusSize: case TYPE_ViVersion: case TYPE_ViAccessMode: case TYPE_ViEventType: case TYPE_ViJobId: { ViAttrState attrState = (ViAttrState)((ViUInt32)dVal); Status = viSetAttribute(Session, Attribute, attrState); } break; case TYPE_ViString: { mxFree(AttrString); mexErrMsgTxt("Setting string attributes is not allowed."); } break; default: { mxFree(AttrString); mexErrMsgTxt("Unhandled case in mexVISA.c under viGetAttribute."); } } ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree(AttrString); return 0; } // ------------- // viEnableEvent // ------------- if(!strcmp(cmd, "viEnableEvent()")) { ViSession Session; ViEventType Event; char *EventString; ViUInt16 Mechanism; ViMexUtil_CheckParameters(nlhs,nrhs,1,4); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); EventString = ViMexUtil_GetStringValue(prhs[2], 0); Mechanism = (ViUInt16)ViMexUtil_GetIntValue(prhs[3]); if(Mechanism != 1) { mxFree(EventString); mexErrMsgTxt("EventMechanism must be VI_QUEUE = 1"); } if(mexVISA_Get_VI_EVENT(EventString, &Event)) { mxFree(EventString); mexErrMsgTxt("Unrecognized/unsupported VISA Event type."); } Status = viEnableEvent(Session, Event, Mechanism, VI_NULL); ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree(EventString); return 0; } // --------------------- // viDisableEvent // --------------------- if(!strcmp(cmd, "viDisableEvent()")) { ViSession Session; ViEventType Event; char *EventString; ViUInt16 Mechanism; ViMexUtil_CheckParameters(nlhs,nrhs,1,4); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); EventString = ViMexUtil_GetStringValue(prhs[2], 0); Mechanism = (ViUInt16)ViMexUtil_GetIntValue(prhs[3]); if(Mechanism != 1) { mxFree(EventString); mexErrMsgTxt("EventMechanism must be VI_QUEUE = 1"); } if(mexVISA_Get_VI_EVENT(EventString, &Event)) { mxFree(EventString); mexErrMsgTxt("Unrecognized/unsupported VISA Event type."); } Status = viDisableEvent(Session, Event, Mechanism); ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree(EventString); return 0; } // --------------- // viDiscardEvents // --------------- if(!strcmp(cmd, "viDiscardEvents()")) { ViSession Session; ViEventType Event; char *EventString; ViUInt16 Mechanism; ViMexUtil_CheckParameters(nlhs,nrhs,1,4); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); EventString = ViMexUtil_GetStringValue(prhs[2], 0); Mechanism = (ViUInt16)ViMexUtil_GetIntValue(prhs[3]); if(Mechanism != 1) { mxFree(EventString); mexErrMsgTxt("EventMechanism must be VI_QUEUE = 1"); } if(mexVISA_Get_VI_EVENT(EventString, &Event)) { mxFree(EventString); mexErrMsgTxt("Unrecognized/unsupported VISA Event type."); } Status = viDiscardEvents(Session, Event, Mechanism); ViMexUtil_PutIntValue((int)Status, &plhs[0]); mxFree(EventString); return 0; } // ------------- // viWaitOnEvent // ------------- if(!strcmp(cmd, "viWaitOnEvent()")) { ViSession Session; ViEventType Event, OutEventType; char *EventString; ViEvent EventHandle; ViUInt32 Timeout; ViMexUtil_CheckParameters(nlhs,nrhs,3,4); Session = (ViSession)ViMexUtil_GetUIntValue(prhs[1]); EventString = ViMexUtil_GetStringValue(prhs[2], 0); Timeout = (ViUInt32)ViMexUtil_GetUIntValue(prhs[3]); if(mexVISA_Get_VI_EVENT(EventString, &Event)) { mxFree(EventString); mexErrMsgTxt("Unrecognized/unsupported VISA Event type."); } Status = viWaitOnEvent(Session, Event, Timeout, &OutEventType, &EventHandle); ViMexUtil_PutIntValue((int)Status, &plhs[0]); ViMexUtil_PutUIntValue((unsigned)OutEventType, &plhs[1]); ViMexUtil_PutUIntValue((unsigned)EventHandle, &plhs[2]); mxFree(EventString); return 0; } // ****************************************************************** // ------------------- // No Command Found... // ------------------- return -1; }
int main (void) { /* * First we open a session to the VISA resource manager. We are * returned a handle to the resource manager session that we must * use to open sessions to specific instruments. */ status = viOpenDefaultRM (&defaultRM); if (status < VI_SUCCESS) { printf("Could not open a session to the VISA Resource Manager!\n"); exit (EXIT_FAILURE); } /* * Next we use the resource manager handle to open a session to a * GPIB instrument at device 2. A handle to this session is * returned in the handle inst. Please consult the NI-VISA User Manual * for the syntax for using other instruments. */ status = viOpen (defaultRM, "GPIB::2::INSTR", VI_NULL, VI_NULL, &inst); /* * Now we install the handler for asynchronous i/o completion events. * To install the handler, we must pass our instrument session, the type of * event to handle, the handler function name and a user handle * which acts as a handle to the handler function. */ status = viInstallHandler (inst, VI_EVENT_IO_COMPLETION, AsyncHandler, uhandle); /* Now we must actually enable the I/O completion event so that our * handler will see the events. Note, one of the parameters is * VI_HNDLR indicating that we want the events to be handled by * an asynchronous event handler. The alternate mechanism for handling * events is to queue them and read events off of the queue when * you want to check them in your program. */ status = viEnableEvent (inst, VI_EVENT_IO_COMPLETION, VI_HNDLR, VI_NULL); /* * Now the VISA write command is used to send a request to the * instrument to generate a sine wave. This demonstrates the * synchronous read operation that blocks the application until viRead() * returns. Note that the command syntax is instrument specific. */ /* * Here you specify which string you wish to send to your instrument. * The command listed below is device specific. You may have to change * command to accommodate your instrument. */ strcpy(stringinput,"SOUR:FUNC SIN; SENS: DATA?\n"); BytesToWrite = strlen(stringinput); status = viWrite (inst, (ViBuf)stringinput,BytesToWrite, &rcount); /* * Next the asynchronous read command is called to read back the * date from the instrument. Immediately after this is called * the program goes into a loop which will terminate * on an i/o completion event triggering the asynchronous callback. * Note that the asynchronous read command returns a job id that is * a handle to the asynchronous command. We can use this handle * to terminate the read if too much time has passed. */ status = viReadAsync (inst, data, 1024, &job); printf("\n\nHit enter to continue."); letter = getchar(); /* * If the asynchronous callback was called and the flag was set * we print out the returned data otherwise we terminate the * asynchronous job. */ if (stopflag == VI_TRUE) { /* RtCount was set in the callback */ printf ("Here is the data: %*s", RtCount, data); } else { status = viTerminate (inst, VI_NULL, job); printf("The asynchronous read did not complete.\n"); } printf ("\nHit enter to continue."); fflush(stdin); getchar(); /* * Now we close the instrument session and the resource manager * session to free up resources. */ status = viClose(inst); status = viClose(defaultRM); return 0; }