// // Class processing user input // #include "User.h" #include using namespace std; // Branch table for command evaluation static const struct CL_Struct { const char *Name; void (User::*CommandPointer)(); unsigned int MinNumParameter; bool NeedCrate; const char *Parameters; const char *Help; } CommandList[] = {{"pixel", &User::cmd_hv, 2, true, " ", "Change bias of pixels"}, {"channel", &User::cmd_hv, 2, true, " ", "Change bias of channels of active crate"}, {"gs", &User::cmd_gs, 1, true, "[crate] ", "Global voltage set active crate"}, {"reset", &User::cmd_reset, 0, true, "", "Reset active crate"}, {"synch", &User::cmd_synch, 0, true, "", "Synchronize active crate"}, {"voltage", &User::cmd_status, 0, false, "[dac]", "Show voltage setpoints"}, {"current", &User::cmd_status, 0, false, "", "Show currents"}, {"status", &User::cmd_status, 0, false, "[R|I0]", "Show status information"}, {"calib", &User::cmd_calib, 1, true, "", "Calibrate current measurement (linear fit between V1 and V2)"}, {"mode", &User::cmd_mode, 1, true, "", "Set voltage stabilization mode (experimental)"}, {"load", &User::cmd_load, 1, true, "", "Load and set bias settings from file"}, {"save", &User::cmd_save, 1, true, "", "Save current bias settings to file"}, {"help", &User::cmd_help, 0, false, "", "Print help"}, {"exit", &User::cmd_exit, 0, false, "", "Exit program"}, {".", &User::cmd_shell, 1, false, "", "Execute shell command"}}; // // Constructor // User::User(string Board): EvidenceServer(SERVER_NAME) { vector Text; MainThread = pthread_self(); // DIM console service used in PrintMessage() ConsoleText = NULL; ConsoleOut = new DimService(SERVER_NAME"/ConsoleOut", (char *) ""); // Initialize current calibration constants R.resize(MAX_NUM_BOARDS*NUM_CHANNELS, numeric_limits::infinity()); I0.resize(MAX_NUM_BOARDS*NUM_CHANNELS, 0); // Get/initialize configuration data if (Board.empty()) Board = GetConfig("Boards", "dummy"); GetConfig("TimeOut"); GetConfig("VoltageLimit"); GetConfig("MinResetPeriod"); GetConfig("RampSpeed"); GetConfig("UpdatePeriod"); Text = Tokenize(GetConfig("DefaultVoltage", ""), " \t"); for (unsigned int i=0; iInitOK) PrintMessage("Synchronized and reset crate %s\n", Board.c_str()); else { Message(WARN, "Failed to synchronize crate %s", Board.c_str()); delete Dev; Dev = NULL; } // Create PixelMap instance (map from config server) DimRpcInfo RPC((char *) "ConfigRequest", (char *) ""); RPC.setData((char *) "Misc PixelMap"); PixMap = new PixelMap(std::string(RPC.getString(), RPC.getSize())); // Install DIM command (after all initialized) DIMCommand = new DimCommand((char *) SERVER_NAME"/Command", (char *) "C", this); // Create monitor thread and make accessible for sending signal if ((pthread_create(&Thread, NULL, (void * (*)(void *)) LaunchMonitor,(void *) this)) != 0) { Message(FATAL, "pthread_create() failed with Monitor thread"); } } // // Destructor // User::~User() { int Ret; // Wait for thread to quit (ignore error if thread did already exit) if ((Ret = pthread_cancel(Thread)) != 0) { if (Ret != ESRCH) Message(ERROR, "pthread_cancel() failed (%s)", strerror(Ret)); } if ((Ret = pthread_join(Thread, NULL)) != 0) Message(ERROR, "pthread_join() failed (%s)", strerror(Ret)); // Delete crate delete Dev; delete DIMCommand; delete PixMap; delete ConsoleOut; free(ConsoleText); } // // Process user input // void User::commandHandler() { // Build string safely string Command = string(getCommand()->getString(), getCommand()->getSize()); // Check if command is legal and ignore empty commands if (getCommand() != DIMCommand || Command.size() < 2) return; // Parse command into tokens Parameter = Tokenize(Command, " "); // Special handling of shell execution if (Command[0] == '.') { Parameter.clear(); Parameter.push_back("."); Parameter.push_back(Command.substr(1)); } // Search for command in command list for(unsigned int CmdNumber=0; CmdNumber*CommandList[CmdNumber].CommandPointer)(); return; } } PrintMessage("Unknown command '%s'\n", Parameter[0].c_str()); } // Print help void User::cmd_help() { for(unsigned int i=0; i are mandatory, in [] optional, | indicates mutual exclusive or.\n" "Ranges can be 'all', a single number or in the form 'a-b'.\n"); } // // Synchronize crate // void User::cmd_synch() { if (Dev->Synch()) PrintMessage("Synchronized crate\n"); else PrintMessage("Failed to synchronize crate\n"); } // // Set new bias voltage // void User::cmd_hv() { unsigned int Channel, Errors = 0; double Double; struct Range Chan, Pixel; map Voltages; vector Channels; // Loop over all parameters for (unsigned int n=1; n < Parameter.size()-1; n+=2) { // Convert voltage value and check format if (!ConvertToDouble(Parameter[n+1], &Double) && !Match(Parameter[n+1], "default") && !Match(Parameter[n+1], "info")) { PrintMessage("Error: Wrong number format for voltage setting\n"); continue; } // Extract affected channels for this argument pair Channels.clear(); // Pixel identification? if (Match(Parameter[0], "pixel")) { Pixel.Min = 0; Pixel.Max = 1439; if (!ConvertToRange(Parameter[n], Pixel)) { PrintMessage("Pixel ID out-of-range for parameter %d, skipping channel\n", n); continue; } for (int i=Pixel.Min; i<=Pixel.Max; i++) { Channel = PixMap->Pixel_to_HVboard(i)*NUM_CHANNELS + PixMap->Pixel_to_HVchannel(i); // Skip if pixel ID or corresponding channels not existing if (PixMap->Pixel_to_HVcrate(i) == 0 && ChannelPresent[Channel]) PrintMessage(" is not present in crate\n"); else PrintMessage("is on board %d, board channel %d\n", Channel/32, Channel%32); // Print pixel information vector List = PixMap->HV_to_Pixel(0, Channel/NUM_CHANNELS, Channel%NUM_CHANNELS); PrintMessage("\n Default voltage: %.2f Pixel IDs: ", Channel < DefaultVoltage.size() ? DefaultVoltage[Channel] : 0); for (unsigned int j=0; jGetVoltage(Channel), Dev->GetDAC(Channel), Dev->GetCurrent(Channel)); if (Dev->OC[Channel]) PrintMessage("(overcurrent)\n"); else PrintMessage("\n"); continue; } // Get current voltage on first change of a channel if (Voltages.count(Channel) == 0) Voltages[Channel] = Dev->GetVoltage(Channel); // Voltage change (number starts with + oder -) ignored if voltage is zero if (Parameter[n+1][0]=='+' || Parameter[n+1][0]=='-') if (Voltages[Channel] == 0) continue; // Should the default value be set? if (Match(Parameter[n+1], "default")) { if (Channel < DefaultVoltage.size()) Double = DefaultVoltage[Channel]; else Double = 0; } // Relative or absolute change? if (Parameter[n+1][0]=='+' || Parameter[n+1][0]=='-') Voltages[Channel] += Double; else Voltages[Channel] = Double; } // Channels } // Loop over command argument // Ramp voltages and update DIM services Errors += RampVoltages(Voltages); Dev->UpdateDIM(); // Error message only if not yet too many errors if (Errors > 0) { if (Dev->ErrorCount > MAX_ERR_COUNT) return; Message(ERROR, "%d errors occurred from SetChannels()", Errors); } } // // Load bias settings from file // void User::cmd_load() { char Buffer[MAX_COM_SIZE]; int Errors = 0; unsigned int Channel; double Value; FILE *File; map Voltages; // Open file if ((File=fopen(Parameter[1].c_str(), "r")) == NULL) { PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno)); return; } // Scan through file line by line while (fgets(Buffer, sizeof(Buffer), File) != NULL) if (Match(Dev->Name, Buffer)) { PrintMessage("Found bias settings for crate %s\n\r", Dev->Name); Voltages.clear(); Channel = 0; while (fscanf(File, "%lf", &Value)==1 && ChannelUpdateDIM(); if (ferror(File) != 0) { PrintMessage("Error reading DAC value from file, terminating. (%s)\n",strerror(errno)); return; } else PrintMessage("\nFinished updating board\n"); } // if() if (Errors != 0) PrintMessage("Warning: %d error(s) occurred\n", Errors); if (fclose(File) != 0) PrintMessage("Error: Could not close file '%s'\n", Parameter[1].c_str()); } // // Reset crate // void User::cmd_reset() { if (Dev->SystemReset()) PrintMessage("System reset of crate\n"); else PrintMessage("Error: Could not reset crate\n"); } // // Read channel // void User::cmd_gs() { double Voltage; if (!ConvertToDouble(Parameter[1], &Voltage)) { PrintMessage("Error: Wrong number format\n"); return; } if (!Dev->GlobalSet(Voltage)) { PrintMessage("Error: Could not global set crate\n"); } } // // Save bias settings of all boards // void User::cmd_save() { FILE *File; time_t Time = time(NULL); if ((File = fopen(Parameter[1].c_str(), "w")) == NULL) { PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno)); return; } fprintf(File,"********** Bias settings of %s **********\n\n", ctime(&Time)); fprintf(File, "%s\n\n", Dev->Name); for (unsigned int j=0; jGetVoltage(j)); fprintf(File, "\n"); if (fclose(File) != 0) { PrintMessage("Error: Could not close file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno)); } } // // Set operation mode // void User::cmd_mode() { if (Match(Parameter[1], "static")) Mode = mode_static; else { Mode = mode_dynamic; Dev->SetRefCurrent(); } } // // Calibrate current measurement // void User::cmd_calib() { map Voltages, Initial, Current[2]; int Errors, Num; double Volt[2]; // Clear current calibration values? if (Match(Parameter[1], "invalidate")) { PrintMessage("Current calibration invalidated\n"); R.assign(MAX_NUM_BOARDS*NUM_CHANNELS, numeric_limits::infinity()); I0.assign(MAX_NUM_BOARDS*NUM_CHANNELS, 0); return; } // Check number of parameters and convert if (Parameter.size() != 4) { PrintMessage("Error: Number of parameters incorrect\n"); return; } if (!ConvertToDouble(Parameter[1], &Volt[0]) || !ConvertToDouble(Parameter[2], &Volt[1])) { PrintMessage("Error: Wrong number format for voltages\n"); return; } if (!ConvertToInt(Parameter[3], &Num)) { PrintMessage("Error: Wrong number format for number of iterations\n"); return; } // Check if infinity can be represented for double if (!numeric_limits::has_infinity) { PrintMessage("Cannot perform calibration, double cannot represent infinity\n"); return; } // Save initial voltages for (unsigned int i=0; iGetVoltage(i); // Make current measurements at given voltages for (unsigned int Round=0; Round<2; Round++) { // Set voltage for (unsigned int i=0; iReadAll()) { PrintMessage("\nError from ReadAll()\n"); return; } for (unsigned int i=0; iGetCurrent(i)/Num; } } } // for() // Set initial voltages if ((Errors = RampVoltages(Initial)) != 0) { PrintMessage("%d errors occurred while ramping back to inital voltages\n", Errors); return; } else PrintMessage("Ramped all channels back to inital voltages\n"); // Calculate calibration constants (R in MOhm) for (unsigned int i=0; iName, Dev->WrapOK ? "ok":"error", Dev->WrapCount, Dev->ResetHit ? "yes" : "no", Dev->ErrorCount, Dev->Disabled ? "(DISABLED)":""); // Read all channels if (!Dev->ReadAll()) { PrintMessage("Could not update status, ReadAll() failed\n"); return; } // Voltage or current list if (Match(Parameter[0], "voltage")) { if (Parameter.size() == 1) M = M_V; else M = M_DAC; } else if (Match(Parameter[0], "current")) M = M_I; else if (Parameter.size() == 2) { if (Match(Parameter[1], "R")) M = M_R; if (Match(Parameter[1], "I0")) M = M_I0; } if (M == M_notset) return; if (M == M_V) PrintMessage("Channel voltages (in V)"); if (M == M_DAC) PrintMessage("Channel voltages (in DAC values)"); if (M == M_R) PrintMessage("Channel internal resistance (in kOhm)"); if (M == M_I0) PrintMessage("Channel current offset (in uA)"); if (M == M_I) PrintMessage("Channel currents (in uA)"); for (unsigned int j=0; jPresent[j]) PrintMessage(" -"); else if (M == M_V) PrintMessage("%#5.2f", Dev->GetVoltage(j)); else if (M == M_DAC) PrintMessage("%5d", Dev->GetDAC(j)); else if (M == M_R) PrintMessage("%#5.2f", R[j]*1000); else if (M == M_I0) PrintMessage("%#5.2f", I0[j]); else if (M == M_I) PrintMessage("%#5.1f", Dev->GetCurrent(j) - Dev->GetVoltage(j)/R[j] - I0[j]); // Print overcurrent marker PrintMessage("%s ", Dev->OC[j] ? "*":" "); } PrintMessage("\n"); } // // Exit program (Signal makes readline return and sets ExitRequest) // void User::cmd_exit() { pthread_kill(MainThread, SIGTERM); } // // Execute shell command // void User::cmd_shell() { if (system(Parameter[1].c_str()) == -1) PrintMessage("Error with system() call\n"); } // // Print message to screen and to DIM text service // void User::PrintMessage(const char *Format, ...) { static char Error[] = "vasprintf() failed in PrintMessage()"; char *Text; // Evaluate arguments va_list ArgumentPointer; va_start(ArgumentPointer, Format); if (vasprintf(&Text, Format, ArgumentPointer) == -1) Text = Error; va_end(ArgumentPointer); if (strlen(Text) == 0) return; // Print to console printf("%s", Text); fflush(stdout); if (Text[strlen(Text)-1] == '\n') rl_on_new_line(); // New prompt // Send to DIM text service ConsoleOut->updateService(Text); // Free old text if (ConsoleText != Error) free(ConsoleText); ConsoleText = Text; } // Ramp to new voltage with given maximum step unsigned int User::RampVoltages(map Voltages) { map Target; int Errors = 0; double V, Lim, MaxDiff = atof(GetConfig("RampSpeed").c_str()); while (!Voltages.empty() && Errors < MAX_ERR_COUNT) { // Voltage limit evaluate here in case of asynchronous update in config file Lim = atof(GetConfig("VoltageLimit").c_str()); // Remove channels already at target or at voltage limit for (map::iterator it = Voltages.begin(); it != Voltages.end(); ++it) { // Channel at target? if (fabs(Dev->GetVoltage(it->first)-it->second) < 0.001) Voltages.erase(it); // Channel at voltage limit? if (it->second > Lim && Dev->GetVoltage(it->first) == Lim) Voltages.erase(it); if (it->second < 0 && Dev->GetVoltage(it->first) == 0) Voltages.erase(it); } // Limit voltage changes to MaxDiff Target = Voltages; for (map::iterator it = Target.begin(); it != Target.end(); ++it) { V = Dev->GetVoltage(it->first); // Ramp up if ((V < it->second) && (V + MaxDiff < it->second)) it->second = V + MaxDiff; // Ramp down if ((V > it->second) && (V - MaxDiff > it->second)) it->second = V - MaxDiff; } // Set channels to next target and wait 10 ms if (!Dev->SetChannels(Target)) Errors++; usleep(10000); } return Errors; } // // Check status // void User::Monitor() { int Ret; // End if no crate available if (Dev == NULL) return; while (!ExitRequest) { // Wait (this is the only allowed cancelation point) if ((Ret=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) != 0) { Message(FATAL, "pthread_setcancelstate() failed (%s)", strerror(Ret)); } sleep(min(atoi(GetConfig("UpdatePeriod").c_str()), 1)); if ((Ret=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL)) != 0) { Message(FATAL, "pthread_setcancelstate() failed (%s)", strerror(Ret)); } // Check crate if (Dev->Disabled) continue; // Read all channels if (!Dev->ReadAll()) { Message(ERROR, "Monitor thread could not read status, ReadAll() failed\n"); continue; } // Check if crate push button was hit if (Dev->ResetHit) { Message(INFO, "Manual reset of crate, setting voltages to zero and issuing system reset"); if (!Dev->GlobalSet(0)) Message(ERROR, "Global set to zero voltage of crate failed"); if (!Dev->SystemReset()) Message(ERROR, "System reset of crate failed"); } // Check for overcurrent and set voltage to zero if occurred map Voltages; for (unsigned int j=0; jOC[j]) Voltages[j] = 0; if (!Voltages.empty()) { if (!Dev->SetChannels(Voltages)) Message(ERROR, "Error setting voltage to zero for channels with overcurrent"); Dev->UpdateDIM(); } if (Mode == mode_dynamic) Dev->AdaptVoltages(); } // while } // Call monitor loop inside class void User::LaunchMonitor(User *m) { m->Monitor(); } // // Check if two strings match (min 1 character must match) // bool User::Match(string str, const char *cmd) { return strncasecmp(str.c_str(),cmd,strlen(str.c_str())==0 ? 1:strlen(str.c_str())) ? false:true; } // // Conversion function from string to double or int // // Return false if conversion did not stop on whitespace or EOL character bool User::ConvertToDouble(string String, double *Result) { char *EndPointer; *Result = strtod(String.c_str(), &EndPointer); if(!isspace(*EndPointer) && *EndPointer!='\0') return false; return true; } bool User::ConvertToInt(string String, int *Result) { char *EndPointer; *Result = (int) strtol(String.c_str(), &EndPointer, 0); if(!isspace(*EndPointer) && *EndPointer!='\0') return false; return true; } // // Interprets a range // bool User::ConvertToRange(string String, struct User::Range &R) { int N=0, M=0; // Init to avoid compiler warning // Full range if (Match(String, "all")) return true; // Single number if (ConvertToInt(String, &N)) { if (N>= R.Min && N<=R.Max) { R.Max = R.Min = N; return true; } return false; } // Range a-b vector V = EvidenceServer::Tokenize(String, "-"); if (V.size()==2 && ConvertToInt(V[0], &N) && ConvertToInt(V[1], &M) && N>=R.Min && M<=R.Max) { R.Min = N; R.Max = M; return true; } return false; }