12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247 |
- @** Support for Devices Using the DATAQ SDK.
-
- \noindent Support for hardware from DATAQ Instruments is currently provided
- through the DATAQ SDK. This means that this support is currently only available
- on Microsoft Windows. The first planned supported device is the DI-145 with
- support for the DI-148U planned later. The devices are sufficiently similar
- that adding support for other devices from this manufacturer should be easy,
- but I do not have hardware samples to use for testing with other devices. The
- DI-145 additionally has a documented serial protocol which should make the
- hardware usable without the DATAQ SDK both on Microsoft Windows and on other
- platforms for which there is a suitable serial driver.
-
- Originally the classes were surrounded with conditional compilation directives
- but moc failed to generate the appropriate meta-objects on Windows when this
- was done. The |DataqSdkDevice| and |DataqSdkDeviceImplementation| classes will
- truly only work on Microsoft Windows at this time. Attempts to use it elsewhere
- will not end well.
-
- @<Class declarations@>=
- class DataqSdkDeviceImplementation;
- class DataqSdkDevice : public QObject
- {
- Q_OBJECT
- DataqSdkDeviceImplementation *imp;
- private slots:
- void threadFinished();
- public:
- DataqSdkDevice(QString device);
- ~DataqSdkDevice();
- Channel* newChannel(Units::Unit scale);
- Q_INVOKABLE void setClockRate(double Hz);
- Q_INVOKABLE void start();
- static QStringList detectPorts();
- static QStringList detectHardware(); // Friendly names
- };
-
- @ The |DataqSdkDevice| class has as a private member an instance of a class
- called |DataqSdkDeviceImplementation|. The two classes together create and run
- a new thread of execution. This thread spends most of its time blocking while
- waiting for a new measurement to become available. When a new measurement is
- available, that measurement is passed to the appropriate channel which in turn
- passes it to any interested object.
-
- Note that subclassing |QThread| in this way is no longer considered best
- practice. This particular code architecture is based on code written when this
- was considered the right thing to do, but it would be good to rewrite this to
- not subclass |QThread| now that this is no longer required.
-
- @<Class declarations@>=
- class DataqSdkDeviceImplementation : public QThread
- {
- Q_OBJECT
- public:
- DataqSdkDeviceImplementation();
- ~DataqSdkDeviceImplementation();
- void run();
- @<DATAQ SDK library function pointers@>@;
- @<DataqSdkDeviceImplementation member data@>@;
- public slots:
- void measure();
- private:
- qint16 *buffer;
- };
-
- @ While the |DAQ| class for communicating with National Instruments devices
- uses a single function pointer type, increased variety of function signatures
- in the DATAQ SDK makes using several types a better option. This also
- eliminates the need for explicit casts on the arguments.
-
- @<DATAQ SDK library function pointers@>=
- typedef struct di_inlist_struct {
- unsigned short chan;
- unsigned short diff;
- unsigned short gain;
- unsigned short unipolar;
- unsigned short dig_out_enable;
- unsigned short dig_out;
- unsigned short ave;
- unsigned short counter;
- } DI_INLIST_STRUCT;
-
- typedef int (PASCAL *FPDIOPEN)(unsigned);
- typedef int (PASCAL *FPDICLOSE)(void);
- typedef double (PASCAL *FPDISAMPLERATE)(double, long*, long*);
- typedef double (PASCAL *FPDIMAXIMUMRATE)(double);
- typedef int (PASCAL *FPDILISTLENGTH)(unsigned, unsigned);
- typedef int (PASCAL *FPDIINLIST)(di_inlist_struct*);
- typedef int* (PASCAL *FPDIBUFFERALLOC)(unsigned, unsigned);
- typedef int (PASCAL *FPDISTARTSCAN)(void);
- typedef unsigned (PASCAL *FPDISTATUSREAD)(short*, unsigned);
- typedef unsigned (PASCAL *FPDIBUFFERSTATUS)(unsigned);
- typedef int (PASCAL *FPDIBURSTRATE)(unsigned);
- typedef int (PASCAL *FPDISTOPSCAN)(void);
-
- FPDIOPEN di_open;
- FPDICLOSE di_close;
- FPDISAMPLERATE di_sample_rate;
- FPDIMAXIMUMRATE di_maximum_rate;
- FPDILISTLENGTH di_list_length;
- FPDIINLIST di_inlist;
- FPDIBUFFERALLOC di_buffer_alloc;
- FPDISTARTSCAN di_start_scan;
- FPDISTATUSREAD di_status_read;
- FPDIBUFFERSTATUS di_buffer_status;
- FPDIBURSTRATE di_burst_rate;
- FPDISTOPSCAN di_stop_scan;
-
- @ The |PASCAL| macro is defined in the {\tt windef.h} header file which will
- need to be included. This modifies the mechanics of the function call. A
- feature of the C language which C++ inherits is the ability to create variadic
- functions. To facilitate this, when one function calls another, the function
- making that call is responsible for cleaning up the stack. The function being
- called has no reliable way of knowing how many and what type of arguments have
- been passed if it is a variadic function, but this can be determined in the
- calling function at compile time. This is effectively a compiler implementation
- detail which is unimportant to the vast majority of application code. Use of
- the |PASCAL| macro informs the compiler that the function being called will
- clean up the stack itself. This precludes the use of variadic functions, but
- results in a smaller executable. The choice of name for that macro is
- unfortunate as arguments are placed on the stack in the order opposite of
- calling conventions of the Pascal programming language, but these are
- unimportant details so long as the resulting program works.
-
- @<Header files to include@>=
- #ifdef Q_OS_WIN32
- #include <windef.h>
- #else
- #define PASCAL
- #endif
-
- @ |DataqSdkDeviceImplementation| maintains information about the device and the
- channels the measurements are sent to.
-
- @<DataqSdkDeviceImplementation member data@>=
- bool isOpen;
- double sampleRate;
- long oversample;
- long burstDivisor;
- QString device;
- unsigned deviceNumber;
- QVector<Channel*> channelMap;
-
- int error;
- int channels;
- bool ready;
- QLibrary *driver;
- QVector<Units::Unit> unitMap;
- int *input_buffer;
- QTimer *eventClock;
- QMultiMap<int, double> smoother;
-
- @ Most of the interesting work associated with the |DataqSdkDevice| class is
- handled in the |measure()| method of |DataqSdkDeviceImplementation|. This
- method will block until a measurement is available. Once |buffer| is filled by
- |di_status_read()| that function returns and new |Measurement| objects are
- created based on the information in the buffer. These measurements are sent to
- |Channel| objects tracked by |channelMap|.
-
- The buffered values are presented in terms of ADC counts. Before using these
- values to convert to a voltage measurement, the two least significant binary
- digits of the count are set to 0 to improve measurement accuracy as recommended
- in the DATAQ SDK reference documentation.
-
- One of the use cases for this class is using the data port provided on some
- roasters from Diedrich Manufacturing. In this case there are three channels
- that are used: one provides a 0-10V signal that maps to temperature
- measurements of 32 to 1832 degrees F, one provides a signal in the same range
- requiring distinguishing among three values for air flow settings, and one is
- intended to show a percentage for the fuel setting. After experimenting with
- the most direct approach, there are limitations of the hardware that complicate
- matters for the channel representing bean temperature. The hardware is
- providing a 14 bit value representing a signal in the range of +/-10V so as a
- practical matter we only have 13 bits for temperature values. There is a desire
- to present measurements with at least one digit after the decimal point,
- meaning that we require 18,000 distinct values despite likely only ever seeing
- values in the lower third of that range. A 13 bit value only allows 8,192
- distinct values to be represented. The result of this is that stable signals
- between representable values are coded in an inconsistent fashion which can be
- seen as displayed measurements varying erratically. The usual solution to this
- problem is to collect many measurements quickly and average them, which is a
- reasonable thing to do with the sample rates available on DATAQ hardware.
- Examining measurements at a higher sample rate unfortunately reveals a periodic
- structure to the measurement error which averaging alone is not adequate to
- solve. The quality of the measurements can be improved somewhat by removing the
- extreme values from each set of measurements prior to averaging, however this
- does not fully address the lower frequency error sources. Further improvements
- can be made by maintaining a multimap of recent ADC count values to averaged
- voltage values and producing results that take this slightly longer term data
- into account. This is essential for obtaining a sufficiently stable low
- temperature calibration value and introduces minimal additional measurement
- latency during a roast.
-
- At present smoothing is applied to the first data channel and no others. It
- should be possible to enable or disable adaptive smoothing for all channels
- independently to better handle different hardware configurations.
-
- @<DataqSdkDevice implementation@>=
- void DataqSdkDeviceImplementation::measure()
- {
- unsigned count = channels * 40;
- di_status_read(buffer, count);
- QTime time = QTime::currentTime();
- for(unsigned int i = 0; i < count; i++)
- {
- buffer[i] = buffer[i] & 0xFFFC;
- }
- QList<int> countList;
- for(unsigned int i = 0; i < (unsigned)channels; i++)
- {
- QList<double> channelBuffer;
- for(unsigned int j = 0; j < 40; j++)
- {
- channelBuffer << ((double)buffer[i+(channels*j)] * 10.0) / 32768.0;
- if(i == 0)
- {
- countList << buffer[i+(channels*j)];
- }
- }
- double value = 0.0;
- for(unsigned int j = 0; j < 40; j++)
- {
- value += channelBuffer[j];
- }
- value /= 40.0;
- if(i == 0)
- {
- QList<double> smoothingList;
- smoothingList << value;
- QList<int> smoothingKeys = smoother.uniqueKeys();
- for(int j = 0; j < smoothingKeys.size(); j++)
- {
- if(countList.contains(smoothingKeys[j]))
- {
- QList<double> keyValues = smoother.values(smoothingKeys[j]);
- for(int k = 0; k < keyValues.size(); k++)
- {
- smoothingList << keyValues[k];
- }
- }
- else
- {
- smoother.remove(smoothingKeys[j]);
- }
- }
- qSort(countList);
- int lastCount = 0;
- for(int j = 0; j < countList.size(); j++)
- {
- if(j == 0 || countList[j] != lastCount)
- {
- smoother.insert(countList[j], value);
- lastCount = countList[j];
- }
- }
- value = 0.0;
- for(int j = 0; j < smoothingList.size(); j++)
- {
- value += smoothingList[j];
- }
- value /= smoothingList.size();
- }
- Measurement measure(value, time, unitMap[i]);
- channelMap[i]->input(measure);
- }
- }
-
- @ It was noted that |di_status_read()| blocks until it is able to fill the
- |buffer| passed to it. To prevent this behavior from having adverse effects on
- the rest of the program, |measure()| is called from a loop running in its own
- thread of execution. When the thread is started, it begins its execution from
- the |run()| method of |DataqSdkDeviceImplementation| which overrides the
- |run()| method of |QThread|.
-
- The while loop is controlled by |ready| which is set to |false| when there is
- an error in collecting a measurement or when there is a desire to stop logging.
- It could also be set to |false| for reconfiguration events.
-
- All device initialization happens in this method.
-
- Note that while the equivalent method when communicating with National
- Instruments hardware sets a time critical thread priority in an attempt to cut
- down on the variation in time between recorded measurements, that is a really
- bad idea when using the DATAQ SDK. The result was that the main thread never
- got enough time to report measurements and responsiveness throughout the entire
- system became barely usable to the point that it was difficult to kill the
- process. If anybody reading this can provide some insight into why setting the
- thread priority is fine with interacting with either DAQmx or DAQmxBase but not
- when interacting with the DATAQ SDK, I would like to read such an explanation.
-
- @<DataqSdkDevice implementation@>=
- void DataqSdkDeviceImplementation::run()
- {
- if(!ready)
- {
- error = 9; // Device data not available
- return;
- }
- driver = new QLibrary(device);
- if(!driver->load())
- {
- error = 1; // Failed to load driver.
- qDebug() << "Failed to load driver: " << device;
- return;
- }
- di_open = (FPDIOPEN)driver->resolve("di_open");
- di_close = (FPDICLOSE)driver->resolve("di_close");
- di_sample_rate = (FPDISAMPLERATE)driver->resolve("di_sample_rate");
- di_maximum_rate = (FPDIMAXIMUMRATE)driver->resolve("di_maximum_rate");
- di_list_length = (FPDILISTLENGTH)driver->resolve("di_list_length");
- di_inlist = (FPDIINLIST)driver->resolve("di_inlist");
- di_buffer_alloc = (FPDIBUFFERALLOC)driver->resolve("di_buffer_alloc");
- di_start_scan = (FPDISTARTSCAN)driver->resolve("di_start_scan");
- di_status_read = (FPDISTATUSREAD)driver->resolve("di_status_read");
- di_buffer_status = (FPDIBUFFERSTATUS)driver->resolve("di_buffer_status");
- di_burst_rate = (FPDIBURSTRATE)driver->resolve("di_burst_rate");
- di_stop_scan = (FPDISTOPSCAN)driver->resolve("di_stop_scan");
- if((!di_open) || (!di_close) || (!di_sample_rate) || (!di_maximum_rate) ||
- (!di_list_length) || (!di_inlist) || (!di_buffer_alloc) ||
- (!di_start_scan) || (!di_status_read) || (!di_buffer_status) ||
- (!di_burst_rate) || (!di_stop_scan))
- {
- error = 2; // Failed to link required symbol
- return;
- }
- error = di_open(deviceNumber);
- if(error)
- {
- di_close();
- error = di_open(deviceNumber);
- if(error)
- {
- error = 3; // Failed to open device
- di_close();
- return;
- }
- }
- isOpen = true;
- di_maximum_rate(240.0);
- sampleRate = di_sample_rate(sampleRate * channels * 40, &oversample,
- &burstDivisor);
- buffer = new qint16[(int)sampleRate];
- di_inlist_struct inlist[16] = {{0, 0, 0, 0, 0, 0, 0, 0}};
- for(unsigned short i = 0; i < channels; i++)
- {
- inlist[i].chan = i;
- inlist[i].gain = 0;
- inlist[i].ave = 1;
- inlist[i].counter = (oversample - 1);
- }
- error = di_list_length(channels, 0);
- if(error)
- {
- error = 4; // List length error
- return;
- }
- error = di_inlist(inlist);
- if(error)
- {
- error = 5; // Inlist error
- return;
- }
- input_buffer = di_buffer_alloc(0, 4096);
- if(input_buffer == NULL)
- {
- error = 6; // Failed to allocate buffer
- return;
- }
- error = di_start_scan();
- if(error)
- {
- error = 7; // Failed to start scanning
- return;
- }
- while(ready)
- {
- measure();
- }
- }
-
- @ When the loop exits, |DataqSdkDeviceImplementation| emits a finished signal
- to indicate that the thread is no longer running. This could be due to normal
- conditions or there could be a problem that should be reported. That signal is
- connected to a function that checks for error conditions and reports them if
- needed.
-
- @<DataqSdkDevice implementation@>=
- void DataqSdkDevice::threadFinished()
- {
- if(imp->error)
- {
- @<Display DATAQ SDK Error@>@;
- }
- }
-
- @ The DATAQ SDK does not have a single method for reporting errors. Instead,
- any method that can return an error code has its return value checked and
- |error| is set to a value that allows the source of the problem to be
- determined. At present, error handling is very poor.
-
- @<Display DATAQ SDK Error@>=
- imp->ready = false;
- QMessageBox warning;
- warning.setStandardButtons(QMessageBox::Cancel);
- warning.setIcon(QMessageBox::Warning);
- warning.setText(QString(tr("Error: %1")).arg(imp->error));
- warning.setInformativeText(tr("An error occurred"));
- warning.setWindowTitle(QString(PROGRAM_NAME));
- warning.exec();
-
- @ Starting the thread is very simple. Device initialization happens in the new
- thread which then begins taking measurements. The call to |imp->start()| starts
- the new thread and passes control of that thread to |imp->run()|. The main
- thread of execution returns without waiting for the new thread to do anything.
- When the thread is finished, the |finished()| signal is emitted which we have
- connected to |threadFinished()|.
-
- @<DataqSdkDevice implementation@>=
- void DataqSdkDevice::start()
- {
- connect(imp, SIGNAL(finished()), this, SLOT(threadFinished()));
- imp->start();
- }
-
- @ When configuring Typica to use a device supported through the DATAQ SDK it is
- useful to have a way to report the ports where supported hardware has been
- detected. This is also used for automatic detection.
-
- @<DataqSdkDevice implementation@>=
- QStringList DataqSdkDevice::detectHardware()
- {
- QSettings deviceLookup("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\usbser\\Enum",
- QSettings::NativeFormat);
- QStringList keys = deviceLookup.childKeys();
- QStringList devices;
- for(int i = 0; i < keys.size(); i++)
- {
- QString value = deviceLookup.value(keys.at(i)).toString();
- if(value.startsWith("USB\\VID_0683&PID_1450\\"))
- {
- devices.append(value.split("\\").at(2));
- }
- }
- QStringList portList;
- foreach(QString device, devices)
- {
- QString deviceKey = QString("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_0683&PID_1450\\%1").arg(device);
- QSettings deviceEntry(deviceKey, QSettings::NativeFormat);
- portList.append(deviceEntry.value("FriendlyName").toString());
- }
- return portList;
- }
-
- QStringList DataqSdkDevice::detectPorts()
- {
- QStringList friendlyNames = detectHardware();
- QStringList portList;
- foreach(QString name, friendlyNames)
- {
- name.remove(0, name.indexOf("COM"));
- name.chop(1);
- portList.append(name);
- }
- return portList;
- }
-
- @ Setting up the device begins by constructing a new |DataqSdkDevice| object.
- The constructor takes as its argument a string which identifies the device. For
- legacy reasons this currently accepts device names such as |"Dev1"| and looks
- up currently connected devices to determine which serial port should be used.
- Now that it is preferred to configure devices graphically this is not a good
- way to do this. This should be changed before release.
-
- @<DataqSdkDevice implementation@>=
- DataqSdkDevice::DataqSdkDevice(QString device) : imp(new DataqSdkDeviceImplementation)
- {
- bool usesAuto = false;
- int autoIndex = device.toInt(&usesAuto);
- QString finalizedPort;
- if(usesAuto)
- {
- QStringList portList = detectPorts();
- if(autoIndex > 0 && autoIndex <= portList.size())
- {
- finalizedPort = portList.at(autoIndex - 1);
- }
- else
- {
- imp->error = 8; // Failed to find device.
- qDebug() << "Failed to detect port.";
- }
- }
- else
- {
- finalizedPort = device;
- }
- int rstart = finalizedPort.indexOf("COM");
- finalizedPort.remove(0, rstart + 3);
- bool chopFinished = false;
- int finalizedPortNumber = 0;
- while(finalizedPort.size() > 0 && !chopFinished)
- {
- finalizedPortNumber = finalizedPort.toInt(&chopFinished);
- if(!chopFinished)
- {
- finalizedPort.chop(1);
- }
- }
- if(finalizedPortNumber < 10)
- {
- imp->device = QString("DI10%1NT.DLL").arg(finalizedPort);
- }
- else
- {
- imp->device = QString("DI1%1NT.DLL").arg(finalizedPort);
- }
- imp->deviceNumber = 0x12C02D00;
- imp->deviceNumber += finalizedPortNumber;
- imp->ready = true;
- }
-
- @ Once the |DataqSdkDevice| is created, one or more channels can be added.
-
- @<DataqSdkDevice implementation@>=
- Channel* DataqSdkDevice::newChannel(Units::Unit scale)
- {
- Channel *retval = NULL;
- if(imp->ready)
- {
- retval = new Channel();
- imp->channelMap[imp->channels] = retval;
- imp->unitMap[imp->channels] = scale;
- imp->channels++;
- }
- return retval;
- }
-
- @ Once the channels are created, it is necessary to set the clock rate of the
- device. The DATAQ SDK will set the clock rate to be whichever value is closest
- to the specified value that is supported by the hardware. Note that when
- measuring multiple channels the device clock rate should be the desired sample
- rate per channel multiplied by the number of channels.
-
- The amount of time between measurements may vary slightly. Tests have shown
- that while most measurements come within 1ms of the expected time, some
- measurements do not come in within 100ms of the expected time.
-
- @<DataqSdkDevice implementation@>=
- void DataqSdkDevice::setClockRate(double Hz)
- {
- imp->sampleRate = Hz;
- }
-
- @ The destructor instructs the measurement thread to stop, waits for it to
- finish, and resets the device. If this is not done, an error would be issued
- the next time a program attempted to use the device.
-
- @<DataqSdkDevice implementation@>=
- DataqSdkDevice::~DataqSdkDevice()
- {
- if(imp->ready)
- {
- imp->ready = false;
- }
- imp->wait(ULONG_MAX);
- delete imp;
- }
-
- @ The constructor and destructor in |DataqSdkDeviceImplementation| currently
- limit the number of channels to 4. As additional devices are supported this
- restriction should be lifted.
-
- Very little is needed from the constructor. The destructor is responsible for
- closing the device and unloading the device driver.
-
- @<DataqSdkDevice implementation@>=
- DataqSdkDeviceImplementation::DataqSdkDeviceImplementation() : QThread(NULL),
- channelMap(4), error(0), channels(0), ready(false), unitMap(4)
- {
- /* Nothing needs to be done here. */
- }
-
- DataqSdkDeviceImplementation::~DataqSdkDeviceImplementation()
- {
- if(isOpen)
- {
- di_stop_scan();
- di_close();
- }
- if(driver->isLoaded())
- {
- driver->unload();
- }
- }
-
- @ This is exposed to the scripting engine in the usual way.
-
- @<Function prototypes for scripting@>=
- QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine);
- QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine);
- void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine);
-
- @ These functions are made known to the scripting engine.
-
- @<Set up the scripting engine@>=
- constructor = engine->newFunction(constructDataqSdkDevice);
- value = engine->newQMetaObject(&DataqSdkDevice::staticMetaObject, constructor);
- engine->globalObject().setProperty("DataqSdkDevice", value);
-
- @ When creating a new device we make sure that it is owned by the script
- engine. This is necessary to ensure that the destructor is called before \pn{}
- exits. Just as the constructor requires an argument that specifies the device
- name, the constructor available from a script also requires this argument.
-
- @<Functions for scripting@>=
- QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine)
- {
- QScriptValue object;
- if(context->argumentCount() == 1)
- {
- object = engine->newQObject(new DataqSdkDevice(argument<QString>(0, context)),
- QScriptEngine::ScriptOwnership);
- setDataqSdkDeviceProperties(object, engine);
- }
- else
- {
- context->throwError("Incorrect number of arguments passed to "
- "DataqSdkDevice. The constructor takes one string "
- "as an argument specifying a device name. "
- "Example: Dev1");
- }
- return object;
- }
-
- @ As |DataqSdkDevice| inherits |QObject| we add the |newChannel()| property
- after adding any |QObject| properties.
-
- @<Functions for scripting@>=
- void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine)
- {
- setQObjectProperties(value, engine);
- value.setProperty("newChannel", engine->newFunction(DataqSdkDevice_newChannel));
- }
-
- @ The |newChannel()| wrapper requires one argument to specify the measurement
- unit that will eventually be produced from that channel.
-
- @<Functions for scripting@>=
- QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine)
- {
- DataqSdkDevice *self = getself<DataqSdkDevice *>(context);
- QScriptValue object;
- if(self)
- {
- object = engine->newQObject(self->newChannel((Units::Unit)argument<int>(0, context)));
- setChannelProperties(object, engine);
- }
- return object;
- }
-
- @ In order to configure supported devices within Typica, a set of configuration
- controls is required. First there is the base device configuration widget.
-
- @<Class declarations@>=
- class DataqSdkDeviceConfWidget : public BasicDeviceConfigurationWidget
- {
- Q_OBJECT
- public:
- Q_INVOKABLE DataqSdkDeviceConfWidget(DeviceTreeModel *model,
- const QModelIndex &index);
- private slots:
- void updateAutoSelect(bool automatic);
- void updateDeviceNumber(int deviceNumber);
- void updatePort(QString portId);
- void addChannel();
- private:
- QStackedWidget *deviceIdStack;
- };
-
- @ The constructor sets up the interface for updating device configuration
- settings.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- DataqSdkDeviceConfWidget::DataqSdkDeviceConfWidget(DeviceTreeModel *model,
- const QModelIndex &index)
- : BasicDeviceConfigurationWidget(model, index),
- deviceIdStack(new QStackedWidget)
- {
- QVBoxLayout *layout = new QVBoxLayout;
- QCheckBox *autoDetect = new QCheckBox("Automatically select device");
- layout->addWidget(autoDetect);
- QWidget *autoLayerWidget = new QWidget;
- QHBoxLayout *autoLayerLayout = new QHBoxLayout;
- QLabel *autoLabel = new QLabel(tr("Device number"));
- QSpinBox *autoNumber = new QSpinBox;
- autoNumber->setMinimum(1);
- autoNumber->setMaximum(99);
- autoLayerLayout->addWidget(autoLabel);
- autoLayerLayout->addWidget(autoNumber);
- autoLayerWidget->setLayout(autoLayerLayout);
- QWidget *fixedLayerWidget = new QWidget;
- QHBoxLayout *fixedLayerLayout = new QHBoxLayout;
- QLabel *fixedLabel = new QLabel(tr("Device port"));
- QComboBox *portSelection = new QComboBox;
- portSelection->setEditable(true);
- portSelection->addItems(DataqSdkDevice::detectHardware());
- fixedLayerLayout->addWidget(fixedLabel);
- fixedLayerLayout->addWidget(portSelection);
- fixedLayerWidget->setLayout(fixedLayerLayout);
- deviceIdStack->addWidget(autoLayerWidget);
- deviceIdStack->addWidget(fixedLayerWidget);
- layout->addWidget(deviceIdStack);
- QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
- layout->addWidget(addChannelButton);
- @<Get device configuration data for current node@>@;
- for(int i = 0; i < configData.size(); i++)
- {
- node = configData.at(i).toElement();
- if(node.attribute("name") == "autoSelect")
- {
- autoDetect->setChecked(node.attribute("value") == "true" ? true : false);
- }
- else if(node.attribute("name") == "deviceNumber")
- {
- autoNumber->setValue(node.attribute("value").toInt());
- }
- else if(node.attribute("name") == "port")
- {
- int index = portSelection->findText(node.attribute("value"));
- if(index > -1)
- {
- portSelection->setCurrentIndex(index);
- }
- else
- {
- portSelection->setEditText(node.attribute("value"));
- }
- }
- }
- updateAutoSelect(autoDetect->isChecked());
- updateDeviceNumber(autoNumber->value());
- updatePort(portSelection->currentText());
- connect(autoDetect, SIGNAL(toggled(bool)), this, SLOT(updateAutoSelect(bool)));
- connect(autoNumber, SIGNAL(valueChanged(int)), this, SLOT(updateDeviceNumber(int)));
- connect(portSelection, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
- connect(addChannelButton, SIGNAL(clicked()), this, SLOT(addChannel()));
- setLayout(layout);
- }
-
- @ In addition to setting a value in the device configuration, the choice to
- automatically select devices also requires changing which controls in the
- configuration widget are presently available. It is recommended that automatic
- device selection is only used in cases where there is a single device supported
- by the DATAQ SDK present and it will always be the first detected device
- regardless of the current virtual COM port number. In cases where multiple
- devices must be connected, it is recommended to always plug devices into the
- same port and specify the port for each device explicitly.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkDeviceConfWidget::updateAutoSelect(bool automatic)
- {
- if(automatic)
- {
- updateAttribute("autoSelect", "true");
- deviceIdStack->setCurrentIndex(0);
- }
- else
- {
- updateAttribute("autoSelect", "false");
- deviceIdStack->setCurrentIndex(1);
- }
- }
-
- @ Other update methods only need to set a new current value.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkDeviceConfWidget::updateDeviceNumber(int deviceNumber)
- {
- updateAttribute("deviceNumber", QString("%1").arg(deviceNumber));
- }
-
- void DataqSdkDeviceConfWidget::updatePort(QString portId)
- {
- updateAttribute("port", portId);
- }
-
- @ The Add Channel button creates a new configuration node.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkDeviceConfWidget::addChannel()
- {
- insertChildNode(tr("Channel"), "dataqsdkchannel");
- }
-
- @ Channel configuration requires a slightly more complex configuration than
- it does on other devices. As these devices can be used for both temperature and
- non-temperature measurements the channel requires both the column name for the
- measurement series and the unit that the measurements will eventually be
- transformed into. The output of each channel will likely need to be run through
- a |LinearCalibrator| so the lower and upper values for both the measured and
- the mapped ranges are set here. It is also necessary to know if that interval
- is open or closed, if adaptive smoothing should be enabled on that channel, and
- how much precision the measurements should be presented with. In addition to
- the controls for setting these values, there should be a panel that assists in
- determining appropriate values by connecting to the device, collecting
- measurements on the channel, and showing how those measurements are presented
- with the current settings.
-
- @<Class declarations@>=
- class DataqSdkChannelConfWidget : public BasicDeviceConfigurationWidget
- {
- Q_OBJECT
- public:
- Q_INVOKABLE DataqSdkChannelConfWidget(DeviceTreeModel *model,
- const QModelIndex &index);
- private slots:
- void updateUnits(const QString &unit);
- void updateColumnName(const QString &value);
- void updateMeasuredLower(const QString &value);
- void updateMeasuredUpper(const QString &value);
- void updateMappedLower(const QString &value);
- void updateMappedUpper(const QString &value);
- void updateClosedInterval(bool closed);
- void updateSmoothingEnabled(bool enabled);
- void updateSensitivity(const QString &value);
- void startCalibration();
- void stopCalibration();
- void resetCalibration();
- void updateInput(Measurement measure);
- void updateOutput(Measurement measure);
- void updateHidden(bool hidden);
- private:
- QPushButton *startButton;
- QPushButton *resetButton;
- QPushButton *stopButton;
- @<DATAQ SDK device settings@>@;
- DataqSdkDevice *calibrationDevice;
- LinearCalibrator *calibrator;
- QLineEdit *currentMeasurement;
- QLineEdit *minimumMeasurement;
- QLineEdit *maximumMeasurement;
- QLineEdit *averageMeasurement;
- QLineEdit *currentMapped;
- QLineEdit *minimumMapped;
- QLineEdit *maximumMapped;
- QLineEdit *averageMapped;
- int rmCount;
- int cmCount;
- double rmin;
- double rmax;
- double rmean;
- double cmin;
- double cmax;
- double cmean;
- };
-
- @ Private members that hold minimum and maximum aggregate data for channel
- calibration will be initialized to the maximum and minimum values available for
- the |double| type respectively. This guarantees that the first measurement will
- overwrite these values. This is done with |std::numeric_limits| so we require a
- header to be included to gain access to this.
-
- @<Header files to include@>=
- #include <limits>
-
- @ The constructor sets up the interface. Calibration settings line edits need
- to have numeric validators added.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
- const QModelIndex &index)
- : BasicDeviceConfigurationWidget(model, index),
- startButton(new QPushButton(tr("Start"))),
- resetButton(new QPushButton(tr("Reset"))),
- stopButton(new QPushButton(tr("Stop"))),
- calibrator(new LinearCalibrator),
- currentMeasurement(new QLineEdit), minimumMeasurement(new QLineEdit),
- maximumMeasurement(new QLineEdit), averageMeasurement(new QLineEdit),
- currentMapped(new QLineEdit), minimumMapped(new QLineEdit),
- maximumMapped(new QLineEdit), averageMapped(new QLineEdit),
- rmCount(0), cmCount(0),
- rmin(std::numeric_limits<double>::max()),
- rmax(std::numeric_limits<double>::min()), rmean(0),
- cmin(std::numeric_limits<double>::max()),
- cmax(std::numeric_limits<double>::min()), cmean(0)
- {
- @<Find DATAQ SDK device settings from parent node@>@;
- resetButton->setEnabled(false);
- stopButton->setEnabled(false);
- connect(startButton, SIGNAL(clicked()), this, SLOT(startCalibration()));
- connect(resetButton, SIGNAL(clicked()), this, SLOT(resetCalibration()));
- connect(stopButton, SIGNAL(clicked()), this, SLOT(stopCalibration()));
- QVBoxLayout *layout = new QVBoxLayout;
- QFormLayout *topLayout = new QFormLayout;
- QLineEdit *columnEdit = new QLineEdit;
- topLayout->addRow(tr("Column name"), columnEdit);
- QComboBox *unitSelector = new QComboBox;
- unitSelector->addItem(tr("Temperature"));
- unitSelector->addItem(tr("Control"));
- topLayout->addRow(tr("Measurement type"), unitSelector);
- QCheckBox *smoothingBox = new QCheckBox(tr("Enable smoothing"));
- topLayout->addRow(smoothingBox);
- layout->addLayout(topLayout);
- QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
- topLayout->addRow(hideSeries);
- QLabel *calibrationLabel = new QLabel(tr("Calibration settings"));
- layout->addWidget(calibrationLabel);
- QHBoxLayout *calibrationLayout = new QHBoxLayout;
- QFormLayout *calibrationControlsLayout = new QFormLayout;
- QLineEdit *measuredLowerEdit = new QLineEdit;
- measuredLowerEdit->setText("0");
- QLineEdit *measuredUpperEdit = new QLineEdit;
- measuredUpperEdit->setText("10");
- QLineEdit *mappedLowerEdit = new QLineEdit;
- mappedLowerEdit->setText("0");
- QLineEdit *mappedUpperEdit = new QLineEdit;
- mappedUpperEdit->setText("10");
- calibrationControlsLayout->addRow(tr("Measured lower value"), measuredLowerEdit);
- calibrationControlsLayout->addRow(tr("Mapped lower value"), mappedLowerEdit);
- calibrationControlsLayout->addRow(tr("Measured upper value"), measuredUpperEdit);
- calibrationControlsLayout->addRow(tr("Mapped upper value"), mappedUpperEdit);
- QCheckBox *closedBox = new QCheckBox(tr("Closed range"));
- calibrationControlsLayout->addRow(closedBox);
- QLineEdit *sensitivityEdit = new QLineEdit;
- sensitivityEdit->setText("0");
- calibrationControlsLayout->addRow(tr("Discrete interval skip"), sensitivityEdit);
- QVBoxLayout *calibrationTestLayout = new QVBoxLayout;
- QHBoxLayout *deviceControlLayout = new QHBoxLayout;
- deviceControlLayout->addWidget(startButton);
- deviceControlLayout->addWidget(resetButton);
- deviceControlLayout->addWidget(stopButton);
- QFormLayout *indicatorLayout = new QFormLayout;
- currentMeasurement->setReadOnly(true);
- minimumMeasurement->setReadOnly(true);
- maximumMeasurement->setReadOnly(true);
- averageMeasurement->setReadOnly(true);
- currentMapped->setReadOnly(true);
- minimumMapped->setReadOnly(true);
- maximumMapped->setReadOnly(true);
- averageMapped->setReadOnly(true);
- indicatorLayout->addRow(tr("Measured Values"), new QWidget);
- indicatorLayout->addRow(tr("Current"), currentMeasurement);
- indicatorLayout->addRow(tr("Minimum"), minimumMeasurement);
- indicatorLayout->addRow(tr("Maximum"), maximumMeasurement);
- indicatorLayout->addRow(tr("Mean"), averageMeasurement);
- indicatorLayout->addRow(tr("Mapped Values"), new QWidget);
- indicatorLayout->addRow(tr("Current Mapped"), currentMapped);
- indicatorLayout->addRow(tr("Minimum Mapped"), minimumMapped);
- indicatorLayout->addRow(tr("Maximum Mapped"), maximumMapped);
- indicatorLayout->addRow(tr("Mean Mapped"), averageMapped);
- calibrationTestLayout->addLayout(deviceControlLayout);
- calibrationTestLayout->addLayout(indicatorLayout);
- calibrationLayout->addLayout(calibrationControlsLayout);
- calibrationLayout->addLayout(calibrationTestLayout);
- layout->addLayout(calibrationLayout);
- @<Get device configuration data for current node@>@;
- for(int i = 0; i < configData.size(); i++)
- {
- node = configData.at(i).toElement();
- if(node.attribute("name") == "column")
- {
- columnEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "type")
- {
- unitSelector->setCurrentIndex(unitSelector->findText(node.attribute("value")));
- }
- else if(node.attribute("name") == "smoothing")
- {
- smoothingBox->setChecked(node.attribute("value") == "true");
- }
- else if(node.attribute("name") == "calibrationMeasuredLower")
- {
- measuredLowerEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "calibrationMeasuredUpper")
- {
- measuredUpperEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "calibrationMappedLower")
- {
- mappedLowerEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "calibrationMappedUpper")
- {
- mappedUpperEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "calibrationClosedInterval")
- {
- closedBox->setChecked(node.attribute("value") == "true");
- }
- else if(node.attribute("name") == "calibrationSensitivity")
- {
- sensitivityEdit->setText(node.attribute("value"));
- }
- else if(node.attribute("name") == "hidden")
- {
- hideSeries->setChecked(node.attribute("value") == "true");
- }
- }
- updateColumnName(columnEdit->text());
- updateUnits(unitSelector->currentText());
- updateSmoothingEnabled(smoothingBox->isChecked());
- updateMeasuredLower(measuredLowerEdit->text());
- updateMeasuredUpper(measuredUpperEdit->text());
- updateMappedLower(mappedLowerEdit->text());
- updateMappedUpper(mappedUpperEdit->text());
- updateClosedInterval(closedBox->isChecked());
- updateSensitivity(sensitivityEdit->text());
- updateHidden(hideSeries->isChecked());
- connect(columnEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateColumnName(QString)));
- connect(unitSelector, SIGNAL(currentIndexChanged(QString)),
- this, SLOT(updateUnits(QString)));
- connect(smoothingBox, SIGNAL(toggled(bool)),
- this, SLOT(updateSmoothingEnabled(bool)));
- connect(measuredLowerEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateMeasuredLower(QString)));
- connect(mappedLowerEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateMappedLower(QString)));
- connect(measuredUpperEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateMeasuredUpper(QString)));
- connect(mappedUpperEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateMappedUpper(QString)));
- connect(closedBox, SIGNAL(toggled(bool)),
- this, SLOT(updateClosedInterval(bool)));
- connect(sensitivityEdit, SIGNAL(textChanged(QString)),
- this, SLOT(updateSensitivity(QString)));
- connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
- setLayout(layout);
- }
-
- @ We generate measurements with whatever unit will eventually be required to
- avoid the need for something that only exists to change one value of every
- measurement. At present we generate measurements either in Fahrenheit or as
- Unitless. It might not be a bad idea to have the calibration adjustment allow
- display of temperature measurements in Celsius.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::updateUnits(const QString &unit)
- {
- updateAttribute("type", unit);
- }
-
- @ Changing calibration settings requires both saving the settings and updating
- the |LinearCalibrator| used for calibration assistance.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::updateMeasuredLower(const QString &value)
- {
- updateAttribute("calibrationMeasuredLower", value);
- calibrator->setMeasuredLower(value.toDouble());
- }
-
- void DataqSdkChannelConfWidget::updateMeasuredUpper(const QString &value)
- {
- updateAttribute("calibrationMeasuredUpper", value);
- calibrator->setMeasuredUpper(value.toDouble());
- }
-
- void DataqSdkChannelConfWidget::updateMappedLower(const QString &value)
- {
- updateAttribute("calibrationMappedLower", value);
- calibrator->setMappedLower(value.toDouble());
- }
-
- void DataqSdkChannelConfWidget::updateMappedUpper(const QString &value)
- {
- updateAttribute("calibrationMappedUpper", value);
- calibrator->setMappedUpper(value.toDouble());
- }
-
- void DataqSdkChannelConfWidget::updateClosedInterval(bool closed)
- {
- updateAttribute("calibrationClosedInterval", closed ? "true" : "false");
- calibrator->setClosedRange(closed);
- }
-
- void DataqSdkChannelConfWidget::updateSmoothingEnabled(bool enabled)
- {
- updateAttribute("smoothing", enabled ? "true" : "false");
- }
-
- void DataqSdkChannelConfWidget::updateSensitivity(const QString &value)
- {
- updateAttribute("calibrationSensitivity", value);
- calibrator->setSensitivity(value.toDouble());
- }
-
- void DataqSdkChannelConfWidget::updateHidden(bool hidden)
- {
- updateAttribute("hidden", hidden ? "true" : "false");
- }
-
- @ When calibrating a device, we must know certain information to open a
- connection to the appropriate hardware and know which channel we are interested
- in.
-
- @<DATAQ SDK device settings@>=
- bool autoSelect;
- QString deviceID;
- unsigned int channelOfInterest;
-
- @ This information is accessed through the reference element associated with
- the parent node of the current configuration and from the row number of the
- current node.
-
- @<Find DATAQ SDK device settings from parent node@>=
- QDomElement parentReference = model->referenceElement(model->data(index.parent(), Qt::UserRole).toString());
- QDomNodeList deviceConfigData = parentReference.elementsByTagName("attribute");
- QDomElement deviceNode;
- QString configPort;
- QString configAuto;
- for(int i = 0; i < deviceConfigData.size(); i++)
- {
- deviceNode = deviceConfigData.at(i).toElement();
- if(deviceNode.attribute("name") == "autoSelect")
- {
- autoSelect = (deviceNode.attribute("value") == "true");
- }
- else if(deviceNode.attribute("name") == "deviceNumber")
- {
- configAuto = deviceNode.attribute("value");
- }
- else if(deviceNode.attribute("name") == "port")
- {
- configPort = deviceNode.attribute("value");
- }
- }
- deviceID = autoSelect ? configAuto : configPort;
- channelOfInterest = index.row();
-
- @ It must be possible to perform calibration operations with the hardware not
- connected. As such, the device should only be opened on request. Methods for
- opening and closing these connections to the hardware are provided.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::startCalibration()
- {
- startButton->setEnabled(false);
- stopButton->setEnabled(true);
- resetButton->setEnabled(true);
- calibrationDevice = new DataqSdkDevice(deviceID);
- Channel *channel;
- for(unsigned int i = 0; i <= channelOfInterest; i++)
- {
- channel = calibrationDevice->newChannel(Units::Unitless);
- }
- connect(channel, SIGNAL(newData(Measurement)), this, SLOT(updateInput(Measurement)));
- connect(channel, SIGNAL(newData(Measurement)), calibrator, SLOT(newMeasurement(Measurement)));
- connect(calibrator, SIGNAL(newData(Measurement)), this, SLOT(updateOutput(Measurement)));
- calibrationDevice->setClockRate(6.0 / (1.0 + channelOfInterest));
- calibrationDevice->start();
- }
-
- void DataqSdkChannelConfWidget::stopCalibration()
- {
- startButton->setEnabled(true);
- stopButton->setEnabled(false);
- resetButton->setEnabled(false);
- calibrationDevice->deleteLater();
- @<Reset DATAQ SDK channel calibration aggregates@>@;
- }
-
- @ When collecting calibration data it is useful to have a few types of
- information. The most recent reported measurement is fine, but the hardware
- supported here does not produce a constant value in response to a stable input,
- making this less useful than it would be if that were not the case. Aggregate
- data such as the minimum, maximum, and mean of measured values for a stable
- input are useful to have, but it must be possible to reset these statistics for
- convenient testing in multiple parts of the range.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::resetCalibration()
- {
- @<Reset DATAQ SDK channel calibration aggregates@>@;
- }
-
- @ When calibration is stopped or reset, aggregate statistics are set to
- their initial values;
-
- @<Reset DATAQ SDK channel calibration aggregates@>=
- rmCount = 0;
- cmCount = 0;
- rmin = std::numeric_limits<double>::max();
- rmax = std::numeric_limits<double>::min();
- rmean = 0;
- cmin = std::numeric_limits<double>::max();
- cmax = std::numeric_limits<double>::min();
- cmean = 0;
-
- @ Two methods are responsible for updating line edits with current and
- aggregate data when calibrating a channel. One handles raw measurements from
- the channel and the other handles output from the |LinearCalibrator|.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::updateInput(Measurement measure)
- {
- double nv = measure.temperature();
- currentMeasurement->setText(QString("%1").arg(nv));
- rmin = qMin(nv, rmin);
- minimumMeasurement->setText(QString("%1").arg(rmin));
- rmax = qMax(nv, rmax);
- maximumMeasurement->setText(QString("%1").arg(rmax));
- rmean = ((rmean * rmCount) + nv) / (rmCount + 1);
- rmCount++;
- averageMeasurement->setText(QString("%1").arg(rmean));
- }
-
- void DataqSdkChannelConfWidget::updateOutput(Measurement measure)
- {
- double nv = measure.temperature();
- currentMapped->setText(QString("%1").arg(nv));
- cmin = qMin(nv, cmin);
- minimumMapped->setText(QString("%1").arg(cmin));
- cmax = qMax(nv, cmax);
- maximumMapped->setText(QString("%1").arg(cmax));
- cmean = ((cmean * cmCount) + nv) / (cmCount + 1);
- cmCount++;
- averageMapped->setText(QString("%1").arg(cmean));
- }
-
- @ Column name is handled as usual.
-
- @<DataqSdkDeviceConfWidget implementation@>=
- void DataqSdkChannelConfWidget::updateColumnName(const QString &value)
- {
- updateAttribute("column", value);
- }
-
-
- @ These configuration widgets are registered with the configuration system.
-
- @<Register device configuration widgets@>=
- app.registerDeviceConfigurationWidget("dataqsdk", DataqSdkDeviceConfWidget::staticMetaObject);
- app.registerDeviceConfigurationWidget("dataqsdkchannel",
- DataqSdkChannelConfWidget::staticMetaObject);
-
- @ A |NodeInserter| is also added to provide access to
- |DataqSdkDeviceConfWidget|, but only on Windows.
-
- @<Register top level device configuration nodes@>=
- #ifdef Q_OS_WIN32
- inserter = new NodeInserter(tr("DATAQ SDK Device"), tr("DATAQ Device"),
- "dataqsdk", NULL);
- topLevelNodeInserters.append(inserter);
- #endif
|