/******************************************************************************* * Control.cc Control process * T.Barnaby, BEAM Ltd, 2007-02-07 ******************************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SINGLE_DMA 1 ///< Only allow a single DMA accross the PCI bus at a time in getData CycleParams::CycleParams(BString cycleType, CycleParam& params){ this->cycleType = cycleType; this->params.append(params); } void ControlTimer::sigFunc(int sig, siginfo_t* sigInfo, void* data){ ControlTimer::timer->tick(); } ControlTimer* ControlTimer::timer; ControlTimer::ControlTimer(Control& control) : ocontrol(control){ timer = this; } void ControlTimer::tick(){ otick.post(); } void ControlTimer::sync(){ struct itimerval t; t.it_interval.tv_sec = 0; t.it_interval.tv_usec = 100000; t.it_value = t.it_interval; setitimer(ITIMER_REAL, &t, NULL); } void* ControlTimer::function(){ struct sigaction sigAction; sigset_t sigm; dprintf(DBG_THREADS, "ControlTimer Thread: %d\n", gettid()); sigemptyset(&sigm); sigaddset(&sigm, SIGALRM); sigprocmask(SIG_UNBLOCK, &sigm, 0); memset(&sigAction, 0, sizeof(sigAction)); sigAction.sa_sigaction = ControlTimer::sigFunc; sigAction.sa_flags = SA_SIGINFO; sigaction(SIGALRM, &sigAction, NULL); sync(); while(1){ otick.wait(); ocontrol.timer(); } return 0; } Control::Control(int dbFixed) : odebugFixed(dbFixed), otimer(*this), opuControlServer(*this, oboapServer, "puControl"), opuProcessServer(*this, oboapServer, "puProcess"){ onum = 0; oinitialised = 0; omaster = 0; osimulate = 0; osimulateTiming = 0; ocycleNumberNext = 0; oprocessingCycle = 0; oserverName = "localhost"; ocycleTick = 12; ostartTime = getTime(); ocycleProcessed = 0; } Control::~Control(){ } BString Control::getName(){ return BString("Module") + moduleNum() + ": "; } BError Control::mergeError(BError err){ if(err){ err.set(err.getErrorNo(), getName() + err.getString()); } return err; } BError Control::init(int number){ BError err; BString s; sched_param sp; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); if(number){ onum = number; } else { if(s = config.findValue("ModuleControllerNumber:")) onum = s.retInt(); } if(config.findValue("SimulateFpga:").retInt() == 1) osimulate = 1; else osimulate = 0; s = config.findValue("SimulateTiming:"); if(s != "") osimulateTiming = s.retInt(); else osimulateTiming = 0; if(s = config.findValue("TmsServer:")) oserverName = s; // Set default priority to real-time for all of these threads if(realTime){ // seteuid(0); sp.sched_priority = 25; if(sched_setscheduler(0, SCHED_RR, &sp)) fprintf(stderr, "Warning: unable to set as real-time process\n"); } // Initialise sub systems otimer.start(); opuControlServer.setName(BString("puControl-") + onum); opuProcessServer.setName(BString("puProcess-") + onum); if(err = oboapServer.init(oserverName, BoapServer::THREADED)){ return mergeError(err); } #ifdef ZAP if(realTime){ // Set priority back to normal sp.sched_priority = 0; if(sched_setscheduler(0, SCHED_OTHER, &sp)) fprintf(stderr, "Warning: unable to set as normal process\n"); seteuid(getuid()); } #endif err = initCmd(); return err; } BError Control::initCmd(){ BError err; BIter i; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); olock.lock(); oinitialised = 0; opupeEnginesLock.wrLock(); for(opupeEngines.start(i); !opupeEngines.isEnd(i); ){ delete opupeEngines[i]; opupeEngines.del(i); } opupeEnginesLock.unlock(); if(err = initPupeEngines()){ olock.unlock(); return err; } nprintf("%d Pupe Engines initialised\n", opupeEngines.number()); olock.unlock(); // Setup default parameters setNextCycle(0, "Beam3"); dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__); return err; } BError Control::setProcessPriority(BUInt32 priority){ BError err; struct sched_param sp; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); if(realTime){ sp.sched_priority = 26; if(sched_setscheduler(0, SCHED_RR, &sp)) fprintf(stderr, "Warning: unable to set as real-time process\n"); } return err; } BError Control::configure(ConfigInfo configInfo){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); oconfigInfoLock.lock(); oconfigInfo = configInfo; oconfigInfoLock.unlock(); oinitialised = 1; return err; } BError Control::test(BList& errors){ BError errRet; BError err; BIter i; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){ if(err = opupeEngines[i]->status()){ errors.append(BError(err.getErrorNo(), getName() + "Pupe" + opupeEngines[i]->getSlot() + ": Status: " + err.getErrorNo() + " " + err.getString())); } else { errors.append(BError(0, getName() + "Pupe" + opupeEngines[i]->getSlot() + ": Status: " + "Ok")); } } opupeEnginesLock.unlock(); return errRet; } BError Control::getStatus(BList& statusList){ BError err; BError e; int numPupe = 0; BIter i; BString s; int p; BList l; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); #ifdef ZAP for(opupeEngines.start(i), p = 1; !opupeEngines.isEnd(i); opupeEngines.next(i), p++){ e = opupeEngines[i]->status(); s = BString(e.getErrorNo()) + ",\"" + e.getString() + "\""; statusList.append(NameValue(BString("TmsPuServer") + onum + "_Pupe" + p + "_Error", s)); if(!e){ numPupe++; } } #else for(opupeEngines.start(i), p = 1; !opupeEngines.isEnd(i); opupeEngines.next(i), p++){ e = opupeEngines[i]->status(); if(!e){ numPupe++; } opupeEngines[i]->getStatusList(PuChannel(1,1,1), statusList); // statusList = statusList + l; } #endif opupeEnginesLock.unlock(); statusList.append(NameValue(BString("Module") + moduleNum() + "_Running", "1")); statusList.append(NameValue(BString("Module") + moduleNum() + "_NumberPupe", numPupe)); statusList.append(NameValue(BString("Module") + moduleNum() + "_CycleNumber", ocycleNumber)); statusList.append(NameValue(BString("Module") + moduleNum() + "_CycleType", ocycleType)); return err; } BError Control::getStatistics(BList& statsList){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); statsList.append(NameValue(BString("Module") + moduleNum() + "_UpTime", getTime() - ostartTime)); statsList.append(NameValue(BString("Module") + moduleNum() + "_CycleNumProcessed", ocycleProcessed)); return err; } BError Control::addEventServer(BString name){ BError err; dprintf(DBG_CMD, "%s: %s\n", __PRETTY_FUNCTION__, name.retStr()); oeventServers.append(name); return err; } BError Control::delEventServer(BString name){ BError err; BIter i; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); oeventServers.del(name); return err; } BError Control::setControlInfo(CycleParam params){ BError err; BIter i; BIter i1, i2; int found = 0; dprintf(DBG_CMD, "Control::setControlInfo: %s\n", params.cycleType.retStr()); ocycleParmsLock.lock(); // Check if there is already an entry for this cycleType for(ocycleParms.start(i1); !ocycleParms.isEnd(i1); ocycleParms.next(i1)){ if(ocycleParms[i1].cycleType == params.cycleType){ found = 1; break; } } if(found){ // See if there is an entry for this channel found = 0; for(ocycleParms[i1].params.start(i2); !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){ if(ocycleParms[i1].params[i2].channel == params.channel){ found = 1; break; } } if(found){ ocycleParms[i1].params[i2] = params; } else { if(params.channel == 0){ i = ocycleParms[i1].params.begin(); ocycleParms[i1].params.insert(i, params); } else { ocycleParms[i1].params.append(params); } } } else { ocycleParms.append(CycleParams(params.cycleType, params)); } #ifdef ZAP printf("CycleTypes:\n"); for(ocycleParms.start(i); !ocycleParms.isEnd(i); ocycleParms.next(i)){ printf("%s\n", ocycleParms[i].cycleType.retStr()); } #endif ocycleParmsLock.unlock(); return err; } BError Control::setNextCycle(UInt32 cycleNumber, BString cycleType){ BError err; BIter i; BIter i1, i2; PuChannel puChannel; int found = 0; int afterCycleStop = 0; double ts, te; static double tmax; dprintf(DBG_SETNEXTCYCLE, "%s %u %s\n", __PRETTY_FUNCTION__, cycleNumber, cycleType.retStr()); ts = getTime(); olock.lock(); if(!oprocessingCycle) afterCycleStop = 1; ocycleNumberNext = cycleNumber; ocycleTypeNext = cycleType; // Could improve this with a hashed name lookup ocycleParmsLock.lock(); for(ocycleParms.start(i1); !ocycleParms.isEnd(i1); ocycleParms.next(i1)){ dprintf(DBG_SETNEXTCYCLE, "Control::setNextCycle: CheckCycleType: %s - %s\n", ocycleParms[i1].cycleType.retStr(), cycleType.retStr()); if(ocycleParms[i1].cycleType == cycleType){ found = 1; for(ocycleParms[i1].params.start(i2); !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){ if(ocycleParms[i1].params[i2].channel == 0){ puChannel = PuChannel(0, 0, 0); opupeEnginesLock.rdLock(); for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){ opupeEngines[i]->setNextCycle(puChannel, cycleNumber, cycleType, ocycleParms[i1].params[i2]); } opupeEnginesLock.unlock(); } else { getPuPhysChannel(ocycleParms[i1].params[i2].channel, puChannel); opupeEnginesLock.rdLock(); if(puChannel.pupeNum > opupeEngines.number()){ err.set(ErrorParam, getName() + "No Pupe Engine Numbered: " + puChannel.pupeNum); opupeEnginesLock.unlock(); ocycleParmsLock.unlock(); olock.unlock(); return err; } opupeEngines[puChannel.pupeNum - 1]->setNextCycle(puChannel, cycleNumber, cycleType, ocycleParms[i1].params[i2]); opupeEnginesLock.unlock(); } } break; } } ocycleParmsLock.unlock(); olock.unlock(); if(!found) err.set(ErrorParam, BString("There are no Cycle Parameters for cycle type: ") + cycleType); if(!err && afterCycleStop){ if(oprocessingCycle){ err.set(ErrorCycleNumber, getName() + "The next cycle has already started"); } } dprintf(DBG_SETNEXTCYCLE, "Tick: %d\n", ocycleTick); // Synchronise Module controllers if in simulation mode if(osimulate) ocycleTick = 10; if(osimulateTiming & TimingSigCycleStart){ // Make sure timing simulation is in sync between multiple TmsPuServers when CYCLE_START is simulated otimer.sync(); if(ocycleTick == 9){ otimer.tick(); } else { ocycleTick = 10; } } te = getTime(); if((te - ts) > tmax){ tmax = te - ts; } dprintf(DBG_SETNEXTCYCLE, "Control::setNextCycle: Time: %f MaxTime: %f\n", te - ts, tmax); dprintf(DBG_SETNEXTCYCLE, "%s: End\n", __PRETTY_FUNCTION__); return err; } BError Control::getStatus(PuChannel puChannel, PuStatus& puStatus){ BError err; BIter i; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if(puChannel.pupeNum == 0){ for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){ opupeEngines[i]->getStatus(puChannel, puStatus); } } else { if(puChannel.pupeNum > opupeEngines.number()){ err.set(ErrorParam, getName() + "No Pupe Engine Numbered: " + opupeEngines.number()); } else { opupeEngines[puChannel.pupeNum - 1]->getStatus(puChannel, puStatus); } } opupeEnginesLock.unlock(); return err; } BError Control::getCycleInformation(UInt32 cycleNumber, CycleInformation& cycleInformation){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); err = mergeError(opupeEngines[0]->getCycleInformation(cycleNumber, cycleInformation)); opupeEnginesLock.unlock(); return err; } BError Control::getData(PuChannel puChannel, DataInfo dataInfo, Data& data){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); #if SINGLE_DMA opupeLock.lock(); #endif if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ #if SINGLE_DMA opupeLock.unlock(); #endif opupeEnginesLock.unlock(); return err.set(ErrorParam, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->getData(puChannel, dataInfo, data)); #if SINGLE_DMA opupeLock.unlock(); #endif opupeEnginesLock.unlock(); return err; } BError Control::requestData(PuChannel puChannel, DataInfo dataInfo){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); return err; } BError Control::setTestMode(PuChannel puChannel, UInt32 testOutput, UInt32 timingDisableMask){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTestMode(puChannel, testOutput, timingDisableMask)); opupeEnginesLock.unlock(); return err; } BError Control::setTimingSignals(PuChannel puChannel, UInt32 timingSignals){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTimingSignals(puChannel, timingSignals)); opupeEnginesLock.unlock(); return err; } BError Control::captureTestData(PuChannel puChannel, TestCaptureInfo captureInfo, BArray& data){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->captureTestData(puChannel, captureInfo, data)); opupeEnginesLock.unlock(); dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__); return err; } BError Control::setTestData(PuChannel puChannel, Int32 on, BArray data){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTestData(puChannel, on, data)); opupeEnginesLock.unlock(); dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__); return err; } BError Control::setPupeConfig(PuChannel puChannel, PupeConfig pupeConfig){ BError err; dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setPupeConfig(puChannel, pupeConfig)); osimulateTiming = pupeConfig.internalTimingMask; opupeEnginesLock.unlock(); return err; } BError Control::getPupeConfig(PuChannel puChannel, PupeConfig& pupeConfig){ BError err; dprintf(DBG_CMD, "Control::getPupeConfig: %d.%d.%d\n", puChannel.moduleNum, puChannel.pupeNum, puChannel.pupeChan); opupeEnginesLock.rdLock(); if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){ opupeEnginesLock.unlock(); return err.set(ErrorMisc, getName() + "PuChannel: PupeNum out of range"); } err = mergeError(opupeEngines[puChannel.pupeNum - 1]->getPupeConfig(puChannel, pupeConfig)); opupeEnginesLock.unlock(); return err; } void Control::run(){ dprintf(DBG_CMD, "Control::run\n"); oboapServer.run(); } void Control::timer(){ static uint32_t tick = 0; static int simEnable = 0; BError err; BIter i; // printf("Control::timer: %d\n", ocycleTick); if(simEnable){ if(++ocycleTick >= 12) ocycleTick = 0; //printf("CycleTick: %d\n", ocycleTick); if(osimulate){ opupeEnginesLock.rdLock(); if(ocycleTick == 0){ for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)) opupeEngines[i]->cycleStart(); } else if(ocycleTick == 4){ if(opupeEngines.number()) opupeEngines.front()->fpgaTriggerTimingInputs(TimingSigInjection); } else if(ocycleTick == 6){ if(opupeEngines.number()) opupeEngines.front()->fpgaTriggerTimingInputs(TimingSigHChange); } else if(ocycleTick == 10){ for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)) opupeEngines[i]->cycleStop(); } opupeEnginesLock.unlock(); } else { opupeEnginesLock.rdLock(); if((osimulateTiming & TimingSigCycleStart) && (ocycleTick == 0)){ if(opupeEngines.number()) opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigCycleStart); } else if((osimulateTiming & TimingSigInjection) && (ocycleTick == 4)){ if(opupeEngines.number()) opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigInjection); } else if((osimulateTiming & TimingSigHChange) && (ocycleTick == 6)){ if(opupeEngines.number()) opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigHChange); } else if((osimulateTiming & TimingSigCycleStop) && (ocycleTick == 10)){ if(opupeEngines.number()) opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigCycleStop); } opupeEnginesLock.unlock(); } } if(tick == 30){ simEnable = 1; } // Every 1 seconds do some house keeping if((tick % 10) == 9){ if(!oinitialised){ TmsControl tmsControl(BString("//") + oserverName + "/tmsControl"); if(err = tmsControl.puServerStarted(onum)){ wprintf("Unable to call puServerStarted on %s: %s\n", oserverName.retStr(), err.getString().retStr()); } } // Check ADC Pll Locks opupeEnginesLock.rdLock(); for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){ opupeEngines[i]->adcPllLockCheck(); } opupeEnginesLock.unlock(); } // Every 10 seconds do some house keeping if((tick % 100) == 99){ } tick++; } void Control::cycleStart(UInt32 cycleNumber){ BError err; dprintf(DBG_CYCLE, "Control::cycleStart\n"); olock.lock(); oprocessingCycle = 1; ocycleNumber = ocycleNumberNext; ocycleType = ocycleTypeNext; #ifdef ZAP // Make sure timing simulation is in sync with cycle start otimer.sync(); ocycleTick = 0; #endif olock.unlock(); if(onum == 1){ if(err = oeventServers.cycleStartEvent(cycleNumber)) wprintf(getName() + "Error send CycleStart event: " + err.getString() + "\n"); } } void Control::cycleError(UInt32 cycleNumber, BError error){ dprintf(DBG_CYCLE, "Control::cycleError\n"); olock.lock(); oeventServers.errorEvent(cycleNumber, error); eprintf(getName() + "Control::cycleError: Cycle: %d Error: %s\n", cycleNumber, error.getString().retStr()); olock.unlock(); } void Control::cycleStop(UInt32 cycleNumber){ dprintf(DBG_CYCLE, "Control::cycleStop\n"); olock.lock(); ocycleProcessed++; oprocessingCycle = 0; // printf("Control::cycleStop: %u CurrentCycleType: %s NextCycleType: %s\n", cycleNumber, ocycleType.retStr(), ocycleTypeNext.retStr()); olock.unlock(); if(onum == 1){ oeventServers.cycleStopEvent(cycleNumber); } } BError Control::initPupeEngines(){ BError err; BError e; int physical = 0; int slot = 0; int board; Pupe* pupe; BArray devs; int d = 0; BString str; BString s; int numBoards = 5; if((s = config.findValue("PupeNumber:")) != ""){ numBoards = config.findValue("PupeNumber:").retInt(); } if((s = config.findValue("PupeMaster:")) != ""){ omaster = config.findValue("PupeMaster:").retInt() - 1; } if(config.findValue("PupePhysicalOn:").retInt() == 1){ str = config.findValue("PupePhysicalDevices:"); while((s = str.pullToken(",")) != ""){ devs.resize(d + 1); devs[d] = s.retInt(); d++; } if(err = opupeGeog.scan(devs)) return err; physical = 1; } opupeEnginesLock.wrLock(); for(slot = 0; slot < numBoards; slot++){ if(!osimulate && physical) board = opupeGeog.getBoardId(slot); else board = slot; if(board < 0){ wprintf(getName() + "PupeEngine no board in slot: " + slot + "\n"); } pupe = new Pupe(*this, slot, board); opupeEngines.append(pupe); if(e = pupe->init()){ wprintf(getName() + "PupeEngine init error: " + e.getString() + "\n"); } if(omaster == slot) pupe->setMaster(1); else pupe->setMaster(0); } opupeEnginesLock.unlock(); return err; } BError Control::getPuChannel(PuChannel puPhysChannel, UInt32& puChannel){ BError err; unsigned int i; // Could use a mapping table to speed this up if necessary oconfigInfoLock.lock(); puChannel = 0; for(i = 0; i < oconfigInfo.puReferences.size(); i++){ if((puPhysChannel.moduleNum == oconfigInfo.puReferences[i].moduleNum) && (puPhysChannel.pupeNum == oconfigInfo.puReferences[i].pupeNum) && (puPhysChannel.pupeChan == oconfigInfo.puReferences[i].pupeChan)){ puChannel = i + 1; oconfigInfoLock.unlock(); return err; } } oconfigInfoLock.unlock(); return err.set(ErrorParam, getName() + "Physical PuChannel not configured"); } BError Control::getPuPhysChannel(UInt32 puChannel, PuChannel& puPhysChannel){ BError err; oconfigInfoLock.lock(); if((puChannel < 1) || (puChannel > oconfigInfo.puReferences.size())){ oconfigInfoLock.unlock(); return err.set(ErrorParam, getName() + "Physical PuChannel not configured"); } puPhysChannel = oconfigInfo.puReferences[puChannel - 1]; oconfigInfoLock.unlock(); return err; } int Control::moduleNum(){ return onum; }