Dokit
Internal development documentation
Loading...
Searching...
No Matches
devicecommand.cpp
1// SPDX-FileCopyrightText: 2022-2024 Paul Colby <git@colby.id.au>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "devicecommand.h"
5
10#include "qtpokit/pokitpro.h"
11
12/*!
13 * \class DeviceCommand
14 *
15 * The AbstractCommand class extends AbstractCommand to add a PokitDevice instance.
16 */
17
18/*!
19 * Construct a new DeviceCommand object with \a parent.
20 */
22{
23
24}
25
26/*!
27 * Begins scanning for the Pokit device.
28 */
30{
31 qCInfo(lc).noquote() << ((deviceToScanFor.isNull())
32 ? tr("Looking for first available Pokit device...")
33 : tr(R"(Looking for device "%1"...)").arg(deviceToScanFor));
35 return true;
36}
37
38/*!
39 * Disconnects the underlying Pokit device, and sets \a exitCode to be return to the OS once the
40 * disconnection has taken place.
41 */
42void DeviceCommand::disconnect(int exitCode)
43{
44 qCDebug(lc).noquote() << tr("Disconnecting Pokit device...");
45 Q_ASSERT(device);
46 Q_ASSERT(device->controller());
47 exitCodeOnDisconnect = exitCode;
49}
50
51/*!
52 * \fn virtual AbstractPokitService * DeviceCommand::getService() = 0
53 *
54 * Returns a Pokit service object for the derived command class. This will be called by
55 * deviceDiscovered() when the requested Pokit device has been found, after which
56 * deviceDiscovered() will connect the returned service's common signals, and kick off the
57 * device's connection process.
58 */
59
60#define DOKIT_CLI_IF_LESS_THAN_RETURN(value, ns, label) \
61if (value <= ns::maxValue(label).toUInt()) { \
62 return label; \
63}
64
65/**
66 * \fn template<typename T> static T DeviceCommand::minRange(const quint32 maxValue)
67 *
68 * Returns the lowest \a T range that can measure at least up to \a maxValue, or AutoRange if no such range is
69 * available.
70 *
71 * \tparam T Range enumerator to evaluate ranges for. Must be one of:
72 * * PokitMeter::CurrentRange
73 * * PokitMeter::ResistanceRange
74 * * PokitMeter::VoltageRange
75 * * PokitPro::CapacitanceRange
76 * * PokitPro::CurrentRange
77 * * PokitPro::ResistanceRange
78 * * PokitPro::VoltageRange
79 *
80 * \cond Doxygen has "only very limited support for member specialization at the moment", so hide these from Doxygen.
81 * Specifically, if we don't hide them, then Doxygen (at least the current version: v1.9.6) sees the following
82 * specialisations as new, public, non-static members.
83 */
84
85/*!
86 * Returns the lowest PokitMeter::CurrentRange value that can measure at least up to \a maxValue, or AutoRange if
87 * the Pokit Meter cannot measure as high as \a maxValue.
88 */
89template<> PokitMeter::CurrentRange DeviceCommand::minRange<>(const quint32 maxValue)
90{
91 if (maxValue == 0) return PokitMeter::CurrentRange::AutoRange;
92 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_10mA)
93 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_30mA)
94 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_150mA)
95 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_300mA)
96 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_2A)
98}
99
100/*!
101 * Returns the lowest PokitMeter::ResistanceRange value that can measure at least up to \a maxValue, or AutoRange if
102 * the Pokit Meter cannot measure as high as \a maxValue.
103 */
104template<> PokitMeter::ResistanceRange DeviceCommand::minRange(const quint32 maxValue)
105{
106 if (maxValue == 0) return PokitMeter::ResistanceRange::AutoRange;
107 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_160)
108 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_330)
109 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_890)
110 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_1K5)
111 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_10K)
112 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_100K)
113 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_470K)
114 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_1M)
116}
117
118/*!
119 * Returns the lowest PokitMeter::VoltageRange value that can measure at least up to \a maxValue, or AutoRange if
120 * the Pokit Meter cannot measure as high as \a maxValue.
121 */
122template<> PokitMeter::VoltageRange DeviceCommand::minRange(const quint32 maxValue)
123{
124 if (maxValue == 0) return PokitMeter::VoltageRange::AutoRange;
125 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_300mV)
126 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_2V)
127 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_6V)
128 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_12V)
129 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_30V)
130 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_60V)
132}
133
134/*!
135 * Returns the lowest PokitPro::CapacitanceRange value that can measure at least up to \a maxValue, or AutoRange if
136 * the Pokit Pro cannot measure as high as \a maxValue.
137 */
138template<> PokitPro::CapacitanceRange DeviceCommand::minRange(const quint32 maxValue)
139{
140 if (maxValue == 0) return PokitPro::CapacitanceRange::AutoRange;
141 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_100nF)
142 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_10uF)
143 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_1mF)
145}
146
147/*!
148 * Returns the lowest PokitPro::CurrentRange value that can measure at least up to \a maxValue, or AutoRange if
149 * the Pokit Pro cannot measure as high as \a maxValue.
150 */
151template<> PokitPro::CurrentRange DeviceCommand::minRange(const quint32 maxValue)
152{
153 if (maxValue == 0) return PokitPro::CurrentRange::AutoRange;
154 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_500uA)
155 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_2mA)
156 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_10mA)
157 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_125mA)
158 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_300mA)
159 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_3A)
160 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_10A)
162}
163
164/*!
165 * Returns the lowest PokitPro::ResistanceRange value that can measure at least up to \a maxValue, or AutoRange if
166 * the Pokit Pro cannot measure as high as \a maxValue.
167 */
168template<> PokitPro::ResistanceRange DeviceCommand::minRange(const quint32 maxValue)
169{
170 if (maxValue == 0) return PokitPro::ResistanceRange::AutoRange;
171 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_30)
172 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_75)
173 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_400)
174 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_5K)
175 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_10K)
176 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_15K)
177 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_40K)
178 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_500K)
179 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_700K)
180 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_1M)
181 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_3M)
183}
184
185/*!
186 * Returns the lowest PokitPro::VoltageRange value that can measure at least up to \a maxValue, or AutoRange if
187 * the Pokit Pro cannot measure as high as \a maxValue.
188 */
189template<> PokitPro::VoltageRange DeviceCommand::minRange(const quint32 maxValue)
190{
191 if (maxValue == 0) return PokitPro::VoltageRange::AutoRange;
192 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_250mV)
193 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_2V)
194 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_10V)
195 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_30V)
196 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_60V)
197 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_125V)
198 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_400V)
199 DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_600V)
201}
202
203/// \endcond
204
205/*!
206 * Returns the \a product's lowest capacitance range that can measure at least up to \a maxValue (nF), or AutoRange if
207 * no such range is available.
208 *
209 * \note Since Pokit Meters do not support capacitance measurement, \a product should not be PokitProduct::PokitMeter.
210 *
211 * \see minRange<PokitPro::CapacitanceRange>
212 */
213quint8 DeviceCommand::minCapacitanceRange(const PokitProduct product, const quint32 maxValue)
214{
215 switch (product) {
217 Q_ASSERT_X(false, "DeviceCommand::minCapacitanceRange", "Pokit Meter has no capacitance support.");
218 return 255;
220 return +minRange<PokitPro::CapacitanceRange>(maxValue);
221 }
222 Q_ASSERT_X(false, "DeviceCommand::minCapacitanceRange", "Unknown PokitProduct enum value");
223 return 255;
224}
225
226/*!
227 * Returns the \a product's lowest current range that can measure at least up to \a maxValue (µA), or AutoRange if no
228 * such range is available.
229 *
230 * \see DeviceCommand::minRange<PokitMeter::CurrentRange>(const quint32 maxValue)
231 * \see minRange<PokitPro::CurrentRange>(const quint32 maxValue)
232 */
233quint8 DeviceCommand::minCurrentRange(const PokitProduct product, const quint32 maxValue)
234{
235 switch (product) {
237 return +minRange<PokitMeter::CurrentRange>(maxValue);
239 return +minRange<PokitPro::CurrentRange>(maxValue);
240 }
241 Q_ASSERT_X(false, "DeviceCommand::minCurrentRange", "Unknown PokitProduct enum value");
242 return 255;
243}
244
245/*!
246 * Returns the \a product's lowest resistance range that can measure at least up to \a maxValue (Ω), or AutoRange if no
247 * such range is available.
248 *
249 * \see DeviceCommand::minRange<PokitMeter::ResistanceRange>(const quint32 maxValue)
250 * \see minRange<PokitPro::ResistanceRange>(const quint32 maxValue)
251 */
252quint8 DeviceCommand::minResistanceRange(const PokitProduct product, const quint32 maxValue)
253{
254 switch (product) {
258 return +minRange<PokitPro::ResistanceRange>(maxValue);
259 }
260 Q_ASSERT_X(false, "DeviceCommand::minResistanceRange", "Unknown PokitProduct enum value");
261 return 255;
262}
263
264/*!
265 t
266* Returns the \a product's lowest voltage range that can measure at least up to \a maxValue (mV), or AutoRange if no
267 * such range is available.
268 *
269 * \see DeviceCommand::minRange<PokitMeter::VoltageRange>(const quint32 maxValue)
270 * \see minRange<PokitPro::VoltageRange>(const quint32 maxValue)
271 */
272quint8 DeviceCommand::minVoltageRange(const PokitProduct product, const quint32 maxValue)
273{
274 switch (product) {
276 return +minRange<PokitMeter::VoltageRange>(maxValue);
278 return +minRange<PokitPro::VoltageRange>(maxValue);
279 }
280 Q_ASSERT_X(false, "DeviceCommand::minVoltageRange", "Unknown PokitProduct enum value");
281 return 255;
282}
283
284#undef DOKIT_CLI_IF_LESS_THAN_RETURN
285
286
287/*!
288 * Handles controller error events. This base implementation simply logs \a error and then exits
289 * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error
290 * handing if desired.
291 */
293{
294 qCWarning(lc).noquote() << tr("Bluetooth controller error:") << error;
295 QCoreApplication::exit(EXIT_FAILURE);
296}
297
298/*!
299 * Handles devics disconnection events. This base implementation simply logs and exits the
300 * application (via QCoreApplication::exit) with the current exitCodeOnDisconnect value, which is
301 * initialise to `EXIT_FAILURE` in the constructor, but should be set to `EXIT_SUCESS` if/when
302 * the derived command class has completed its actions and requested the disconnection (as opposed
303 * to a spontaneous disconnection on error).
304 */
306{
307 qCDebug(lc).noquote() << tr("Pokit device disconnected. Exiting with code %1.")
310}
311
312/*!
313 * Handles service error events. This base implementation simply logs \a error and then exits
314 * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error
315 * handing if desired.
316 *
317 * \note As this base class does not construct services (derived classed do), its up to the derived
318 * classed to connect this slot to the relevant service's error signal if desired.
319 */
321{
322 qCWarning(lc).noquote() << tr("Bluetooth service error:") << error;
323 QCoreApplication::exit(EXIT_FAILURE);
324}
325
326/*!
327 * Handles service detail discovery events. This base implementation simply logs the event, and
328 * nothing more. Derived classes may (usually do) override this slot to provide their own processing
329 * when a services details have been discovered.
330 */
332{
333 qCDebug(lc).noquote() << tr("Service details discovered.");
334}
335
336/*!
337 * Checks if \a info is the device (if any) we're looking for, and if so, create a contoller and
338 * service, and begins connecting to the device.
339 */
341{
342 Q_ASSERT(isPokitProduct(info));
343
344 if (device) {
345 qCDebug(lc).noquote() << tr(R"(Ignoring additional Pokit device "%1" (%2) at (%3).)")
346 .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
347 return;
348 }
349
350 if ((deviceToScanFor.isEmpty()) || (deviceToScanFor == info.name()) ||
351 ((!info.address().isNull()) && (info.address() == QBluetoothAddress(deviceToScanFor))) ||
352 ((!info.deviceUuid().isNull()) && (info.deviceUuid() == QBluetoothUuid(deviceToScanFor))))
353 {
354 qCDebug(lc).noquote() << tr(R"(Found Pokit device "%1" (%2) at (%3).)")
355 .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
357
358 device = new PokitDevice(info, this);
362 #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
363 QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
364 #else
365 &QLowEnergyController::errorOccurred,
366 #endif
368
369 AbstractPokitService * const service = getService();
370 service->setPokitProduct(pokitProduct(info));
371
372 Q_ASSERT(service);
377
378 qCDebug(lc).noquote() << tr(R"(Connecting to %1 device "%2" (%3) at (%4).)").arg(
379 toString(*service->pokitProduct()), info.name(), info.deviceUuid().toString(), info.address().toString());
381 return;
382 }
383
384 qCDebug(lc).noquote() << tr(R"(Ignoring non-matching Pokit device "%1" (%2) at (%3).)")
385 .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
386 return;
387}
388
389/*!
390 * Checks that the requested device was discovered, and if not, reports and error and exits.
391 */
393{
394 if (!device) {
395 qCWarning(lc).noquote() << ((deviceToScanFor.isNull())
396 ? tr("Failed to find any Pokit device.")
397 : tr(R"(Failed to find device "%1".)").arg(deviceToScanFor));
398 QCoreApplication::exit(EXIT_FAILURE);
399 }
400}
Declares the AbstractPokitService class.
The AbstractCommand class provides a consistent base for the classes that implement CLI commands.
QString deviceToScanFor
Device (if any) that were passed to processOptions().
PokitDiscoveryAgent * discoveryAgent
Agent for Pokit device descovery.
The AbstractPokitService class provides a common base for Pokit services classes.
std::optional< PokitProduct > pokitProduct() const
Returns the Pokit product this service is attached to.
void serviceErrorOccurred(QLowEnergyService::ServiceError newError)
This signal is emitted whenever an error occurs on the underlying QLowEnergyService.
void setPokitProduct(const PokitProduct product)
Sets the current Pokit product.
void serviceDetailsDiscovered()
This signal is emitted when the Pokit service details have been discovered.
static quint8 minResistanceRange(const PokitProduct product, const quint32 maxValue)
Returns the product's lowest resistance range that can measure at least up to maxValue (Ω),...
virtual void controllerError(const QLowEnergyController::Error error)
Handles controller error events.
PokitDevice * device
Pokit Bluetooth device (if any) this command interracts with.
virtual void deviceDisconnected()
Handles devics disconnection events.
DeviceCommand(QObject *const parent=nullptr)
Construct a new DeviceCommand object with parent.
bool start() override
Begins scanning for the Pokit device.
int exitCodeOnDisconnect
Exit code to return on device disconnection.
void deviceDiscoveryFinished() override
Checks that the requested device was discovered, and if not, reports and error and exits.
static quint8 minCapacitanceRange(const PokitProduct product, const quint32 maxValue)
Returns the product's lowest capacitance range that can measure at least up to maxValue (nF),...
virtual void serviceDetailsDiscovered()
Handles service detail discovery events.
static quint8 minVoltageRange(const PokitProduct product, const quint32 maxValue)
t Returns the product's lowest voltage range that can measure at least up to maxValue (mV),...
virtual void serviceError(const QLowEnergyService::ServiceError error)
Handles service error events.
static quint8 minCurrentRange(const PokitProduct product, const quint32 maxValue)
Returns the product's lowest current range that can measure at least up to maxValue (µA),...
static T minRange(const quint32 maxValue)
void deviceDiscovered(const QBluetoothDeviceInfo &info) override
Checks if info is the device (if any) we're looking for, and if so, create a contoller and service,...
void disconnect(int exitCode=EXIT_SUCCESS)
Disconnects the underlying Pokit device, and sets exitCode to be return to the OS once the disconnect...
virtual AbstractPokitService * getService()=0
Returns a Pokit service object for the derived command class.
The PokitDevice class simplifies Pokit device access.
Definition pokitdevice.h:31
QLowEnergyController * controller()
Returns a non-const pointer to the controller used to access the Pokit device.
void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
Starts Pokit device discovery.
Encapsulates details specific to Pokit Meter devices.
Definition pokitmeter.h:19
CurrentRange
Values supported by the Pokit Meter's Range attributes in *Current modes.
Definition pokitmeter.h:22
ResistanceRange
Values supported by the Pokit Meter's Range attributes in Resistance mode.
Definition pokitmeter.h:34
VoltageRange
Values supported by the Pokit Meter's Range attributes in *Voltage modes.
Definition pokitmeter.h:49
@ _60V
Up to 60V DC (42V AC).
Encapsulates details specific to Pokit Pro devices.
Definition pokitpro.h:19
VoltageRange
Values supported by the Pokit Pro's Range attributes in *Voltage modes.
Definition pokitpro.h:65
@ AutoRange
Auto-range.
@ _250mV
Up to 250mV.
CurrentRange
Values supported by the Pokit Pro's Range attributes in *Current modes.
Definition pokitpro.h:33
@ AutoRange
Auto-range.
@ _300mA
Up to 300mA.
@ _125mA
Up to 125mA.
ResistanceRange
Values supported by the Pokit Pro's Range attributes in Resistance mode.
Definition pokitpro.h:47
CapacitanceRange
Values supported by the Pokit Pro's Range attributes in Capacitance mode.
Definition pokitpro.h:22
Declares the PokitDevice class.
Declares the PokitDiscoveryAgent class.
Declares the PokitMeter namespace.
Declares the PokitPro namespace.
QTPOKIT_EXPORT PokitProduct pokitProduct(const QBluetoothDeviceInfo &info)
Returns the PokitProduct corresponding the Bluetotoh device info.
PokitProduct
Pokit products known to, and supported by, the QtPokit library.
@ PokitPro
Pokit Pro.
@ PokitMeter
Pokit Meter.
QTPOKIT_EXPORT QString toString(const PokitProduct product)
Returns product as user-friendly string.
QTPOKIT_EXPORT bool isPokitProduct(const QBluetoothDeviceInfo &info)
Returns true if info describes a Pokit device.
bool isNull() const const
QString toString() const const
QBluetoothAddress address() const const
QBluetoothUuid deviceUuid() const const
QString name() const const
void exit(int returnCode)
QLowEnergyController::Error error() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const
bool isNull() const const
QueuedConnection
bool isNull() const const
QString toString() const const