diff --git a/SHinterface.pro b/SHinterface.pro index 6f0a2cb..4b3fcd1 100644 --- a/SHinterface.pro +++ b/SHinterface.pro @@ -4,9 +4,7 @@ # #------------------------------------------------- -QT += core gui widgets network serialport - -greaterThan(QT_MAJOR_VERSION, 4): QT += +QT += core gui widgets network serialport multimedia TARGET = SHinterface TEMPLATE = app @@ -21,14 +19,22 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += main.cpp mainwindow.cpp ampmanager.cpp alarmtime.cpp \ microcontroller.cpp \ - relaydialog.cpp + relaydialog.cpp \ + alarmsettingsdialog.cpp \ + power.cpp HEADERS += mainwindow.h \ ampmanager.h \ relay.h \ alarmtime.h \ microcontroller.h \ - relaydialog.h + relaydialog.h \ + alarmsettingsdialog.h \ + power.h FORMS += mainwindow.ui \ - relaydialog.ui + relaydialog.ui \ + alarmsettingsdialog.ui + +RESOURCES += \ + resources.qrc diff --git a/alarmsettingsdialog.ui b/alarmsettingsdialog.ui index f721017..55297a9 100644 --- a/alarmsettingsdialog.ui +++ b/alarmsettingsdialog.ui @@ -1,38 +1,109 @@ + - - - AlarmSettingsDialog - + 0 0 - 400 - 300 + 423 + 281 Dialog - - - - 30 - 240 - 341 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + + + + Sound File: + + + + + + + 0 + + + 0 + + + + + false + + + + + + + Change + + + + + + + + + 0 + + + + + Sunrise + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Enable + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + - diff --git a/ampmanager.cpp b/ampmanager.cpp index 777623c..735ad02 100644 --- a/ampmanager.cpp +++ b/ampmanager.cpp @@ -1,19 +1,28 @@ #include "ampmanager.h" -AmpManager::AmpManager(Microcontroller *micro, int relayNumber, QObject *parent) : QObject(parent), _micro(micro), _relayNumber(relayNumber) +AmpManager::AmpManager(Microcontroller *micro, int relayNumber, QAudioDeviceInfo device, QObject *parent) : QObject(parent), _micro(micro), _relayNumber(relayNumber) { - silenceCount = 0; + QAudioFormat format; + format.setSampleRate(8000); + format.setChannelCount(1); + format.setSampleSize(8); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::UnSignedInt); + + _audioDevice = new QAudioInput(device/*, format*/); } AmpManager::~AmpManager() { abort(); + delete _audioDevice; } void AmpManager::run() { abort(); - arecord.start( "arecord -D front -" ); + _ioDevice = _audioDevice->start(); loop.reset(new QEventLoop); QTimer timer; connect(&timer, SIGNAL(timeout()), this, SLOT(doTick())); @@ -29,18 +38,17 @@ void AmpManager::run() void AmpManager::abort() { - if (!loop.isNull()){ - loop->quit(); - } - if(arecord.state() == QProcess::Running)arecord.close(); + if (!loop.isNull())loop->quit(); + if(_audioDevice->error() == 0)_audioDevice->stop(); + if(_ioDevice != nullptr)delete _ioDevice; qDebug()<<"Stop Auto Amp Manager\n"; } void AmpManager::doTick() { - if(arecord.state() == QProcess::Running) + if(_audioDevice->error() == 0 && _ioDevice != nullptr) { - QByteArray buffer = arecord.readAllStandardOutput(); + QByteArray buffer = _ioDevice->readAll(); for(long i = 0; i < buffer.size(); i++) { if((uint8_t) buffer.at(i) != 128) @@ -60,6 +68,6 @@ void AmpManager::doTick() _micro->relayOn(_relayNumber); relayState = true; } - silenceCount ++; + silenceCount++; } } diff --git a/ampmanager.h b/ampmanager.h index 4d50ef2..5536c81 100644 --- a/ampmanager.h +++ b/ampmanager.h @@ -8,9 +8,11 @@ #include #include #include -#include #include #include +#include +#include +#include #include "microcontroller.h" @@ -19,7 +21,7 @@ class AmpManager : public QObject, public QRunnable { Q_OBJECT public: - explicit AmpManager(Microcontroller *micro, int relayNumber, QObject *parent = 0); + explicit AmpManager(Microcontroller *micro, int relayNumber, QAudioDeviceInfo device, QObject *parent = 0); ~AmpManager(); @@ -36,8 +38,8 @@ private: Microcontroller *_micro; int _relayNumber; - - QProcess arecord; + QAudioInput* _audioDevice; + QIODevice* _ioDevice = nullptr; bool relayState = false; diff --git a/main.cpp b/main.cpp index 803faf7..44bc000 100644 --- a/main.cpp +++ b/main.cpp @@ -11,11 +11,13 @@ #include + #include "alarmtime.h" #include "microcontroller.h" #include "mainwindow.h" #include "relaydialog.h" #include "ampmanager.h" +#include "power.h" #define BAUD QSerialPort::Baud38400 @@ -46,12 +48,14 @@ int main(int argc, char *argv[]) parser.addOption(serialOption); QCommandLineOption baudOption(QStringList() << "b" << "baud", QCoreApplication::translate("main", "Set Baud Rate")); parser.addOption(baudOption); - QCommandLineOption secondaryOption(QStringList() << "s" << "secondary", QCoreApplication::translate("main", "Set if instance is not main instance")); + QCommandLineOption secondaryOption(QStringList() << "e" << "secondary", QCoreApplication::translate("main", "Set if instance is not main instance")); parser.addOption(secondaryOption); parser.process(a); QSettings settings; + + //connect to microcontoler Microcontroller micro; if(parser.isSet(tcpOption)) @@ -85,23 +89,35 @@ int main(int argc, char *argv[]) else micro.setIODevice(microPort); } - AmpManager amp(µ, 0); + Power power(&settings, µ); + + + + + AmpManager amp(µ, 0, QAudioDeviceInfo::defaultInputDevice()); MainWindow w(&settings, µ, parser.isSet(secondaryOption)); + QObject::connect(µ, SIGNAL(textRecived(QString)), &w, SLOT(changeHeaderLableText(QString))); + QObject::connect(µ, SIGNAL(relayStateChanged(std::vector)), &w, SLOT(relayStateChanged(std::vector))); RelayDialog relayDialog(µ); + QObject::connect(µ, SIGNAL(relayStateChanged(std::vector)), &relayDialog, SLOT(relayStateChanged(std::vector))); + + //Alarms + AlarmTime almNight(settings.value("nightTime").toTime()); + + AlarmTime *almAlarm = new AlarmTime(settings.value("alarmTime").toTime()); + QSignalMapper signalMapper; if(!parser.isSet(secondaryOption)) { - //Alarms - AlarmTime almNight(settings.value("nightTime").toTime()); - QObject::connect(&almNight, SIGNAL(trigger()), &w, SLOT(slotSyncoff())); + + QObject::connect(&almNight, SIGNAL(trigger()), &power, SLOT(syncoff())); QObject::connect(&w, SIGNAL(signalAlmNightChanged(QTime)), &almNight, SLOT(changeTime(QTime))); QObject::connect(&w, SIGNAL(signalAlmNightStateChanged(int)), &almNight, SLOT(runOrAbort(int))); //QMetaObject::invokeMethod(&almNight, "run", Qt::QueuedConnection ); - AlarmTime *almAlarm = new AlarmTime(settings.value("alarmTime").toTime()); - QSignalMapper signalMapper; + QObject::connect(almAlarm, SIGNAL(trigger()), &signalMapper, SLOT(map())); signalMapper.setMapping(almAlarm, 4); QObject::connect(&signalMapper, SIGNAL(mapped(int)), µ, SLOT(setPattern(int))); @@ -115,13 +131,11 @@ int main(int argc, char *argv[]) QMetaObject::invokeMethod(&, "run", Qt::QueuedConnection ); } - //serial display - QObject::connect(µ, SIGNAL(textRecived(QString)), &w, SLOT(changeHeaderLableText(QString))); - QMetaObject::invokeMethod(µ, "run", Qt::QueuedConnection ); - //Advanced Relays QObject::connect(&w, SIGNAL(showAdvRelayDialog()), &relayDialog, SLOT(show())); + QMetaObject::invokeMethod(µ, "run", Qt::QueuedConnection ); + QMetaObject::invokeMethod(&w, "postActivate", Qt::QueuedConnection ); w.show(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 4a9d4a4..1f8fc12 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -19,7 +19,7 @@ MainWindow::MainWindow(QSettings *settings, Microcontroller *micro , bool isRemo //RGB Leds connect(ui->presettApply, SIGNAL(clicked()), this, SLOT(slotApplyPreset())); - connect(&colorChooser, SIGNAL(currentColorChanged(const QColor)), this, SLOT(slotChangedRgb(const QColor))); + connect(&colorChooser, SIGNAL(colorSelected(const QColor)), this, SLOT(slotChangedRgb(const QColor))); connect(ui->button_lightsOn, SIGNAL(clicked()), _micro, SLOT(rgbOn())); connect(ui->button_lightsOff, SIGNAL(clicked()), _micro, SLOT(rgbOff())); connect(ui->button_quit, SIGNAL(clicked()), this, SLOT(close())); @@ -32,24 +32,25 @@ MainWindow::MainWindow(QSettings *settings, Microcontroller *micro , bool isRemo new QListWidgetItem(tr("Pattern 4 Sunrise"), ui->listWidget_patern); + //Relays + _relayCheckBoxes.push_back(ui->checkBox_amp); + _relayCheckBoxes.push_back(ui->checkBox_bspeaker); + _relayCheckBoxes.push_back(ui->checkBox_inf); + _relayCheckBoxes.push_back(ui->checkBox_aircon); + + for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) connect(_relayCheckBoxes[i], SIGNAL(stateChanged(int)), this, SLOT(relayCheckBoxToggeled(int))); + //Amp - if(!isRemoteMode)connect(ui->checkBox_ampAuto, SIGNAL(stateChanged(int)), this, SLOT(slotAmpChkbtn(int))); - connect(ui->checkBox_amp, SIGNAL(stateChanged(int)), this, SLOT(slotAmpToggle(int))); + if(!isRemoteMode)connect(ui->checkBox_ampAuto, SIGNAL(stateChanged(int)), this, SLOT(slotAmpAutoToggle(int))); //Bedroom Speakers - connect(ui->checkBox_bspeaker, SIGNAL(stateChanged(int)), this, SLOT(slotBSpeakerToggle(int))); if(!isRemoteMode)connect(ui->checkBox_bspeakerAuto, SIGNAL(stateChanged(int)), this, SLOT(slotBSpeakerAutoToggle(int))); //Infinity Mirror - connect(ui->checkBox_inf, SIGNAL(stateChanged(int)), this, SLOT(slotInfMirrorToggle(int))); if(!isRemoteMode)connect(ui->checkBox_infAuto, SIGNAL(stateChanged(int)), this, SLOT(slotInfMirrorAutoToggle(int))); - - //Airconditioner - connect(ui->checkBox_aircon, SIGNAL(stateChanged(int)), this, SLOT(slotAirconToggle(int))); - if(!isRemoteMode) { //Alarm @@ -72,18 +73,21 @@ void MainWindow::remoteMode() { ui->alarmTime->setEnabled(false); ui->checkBox_alarm->setEnabled(false); - ui->label_alarm->setEnabled(false); + ui->pushButton_alarm->setEnabled(false); ui->nightTime->setEnabled(false); ui->checkBox_nightTime->setEnabled(false); ui->label_nightTime->setEnabled(false); ui->checkBox_ampAuto->setEnabled(false); + ui->checkBox_ampAuto->setChecked(false); ui->checkBox_bspeakerAuto->setEnabled(false); ui->checkBox_amp->setEnabled(true); ui->checkBox_bspeaker->setEnabled(true); ui->checkBox_inf->setEnabled(true); + ui->checkBox_inf->setChecked(false); ui->checkBox_infAuto->setEnabled(false); + ui->checkBox_infAuto->setChecked(false); } void MainWindow::postActivate() @@ -106,9 +110,9 @@ void MainWindow::slotChangedRgb(const QColor color) if( color.redF() < 0.2 && color.greenF() < 0.2 && color.blueF() > 0.8 ) { qDebug()<<"Auto turn on inf mirror\n"; - slotInfMirrorToggle(true); + _micro->relayOn(2); } - else slotInfMirrorToggle(false); + else _micro->relayOff(2); } } @@ -118,14 +122,20 @@ void MainWindow::slotApplyPreset() if(ui->listWidget_patern->selectedItems().count() == 1) _micro->setPattern(ui->listWidget_patern->currentRow()); } -void MainWindow::slotAmpToggle(int state) +void MainWindow::relayCheckBoxToggeled(int state) { - _micro->relayToggle(state, 0); + for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) + if(_relayCheckBoxes[i] == sender()) _micro->relayToggle(state, i); } -void MainWindow::slotBSpeakerToggle(int state) +void MainWindow::relayStateChanged(std::vector relayStates) { - _micro->relayToggle(state, 1); + if(relayStates.size() >= 4) for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) + { + _relayCheckBoxes[i]->blockSignals(true); + _relayCheckBoxes[i]->setChecked(relayStates[i]); + _relayCheckBoxes[i]->blockSignals(false); + } } void MainWindow::slotBSpeakerAutoToggle(int state) @@ -133,26 +143,15 @@ void MainWindow::slotBSpeakerAutoToggle(int state) ui->checkBox_bspeaker->setEnabled(!state); } -void MainWindow::slotInfMirrorToggle(int state) -{ - _micro->relayToggle(state, 2); -} - void MainWindow::slotInfMirrorAutoToggle(int state) { ui->checkBox_inf->setEnabled(!state); if(!state) { - slotInfMirrorToggle(ui->checkBox_inf->isChecked()); + _micro->relayToggle(ui->checkBox_inf->isChecked(), 2); } } - -void MainWindow::slotAirconToggle(int state) -{ - _micro->relayToggle(state, 3); -} - void MainWindow::slotChangedAlarmTime(const QTime time) { _settings->setValue("alarmTime", time); @@ -170,7 +169,7 @@ void MainWindow::slotChangedNightTime(const QTime time) signalAlmNightChanged(time); } -void MainWindow::slotAmpChkbtn(int state) +void MainWindow::slotAmpAutoToggle(int state) { ui->checkBox_amp->setEnabled(!state); if(state) @@ -180,7 +179,7 @@ void MainWindow::slotAmpChkbtn(int state) else { signalAmpOff(); - slotAmpToggle(ui->checkBox_amp->checkState()); + _micro->relayToggle(ui->checkBox_amp->checkState(), 0); } } @@ -188,15 +187,3 @@ void MainWindow::changeHeaderLableText(const QString string) { ui->label_serialRecive->setText(string); } - -void MainWindow::slotSyncoff() -{ - qDebug()<<"Power Off on alarm\n"; - _settings->sync(); - slotAmpToggle(false); - slotBSpeakerToggle(false); - slotInfMirrorToggle(false); - slotAirconToggle(false); - - QProcess::execute ( "syncoff" ); -} diff --git a/mainwindow.h b/mainwindow.h index c1506aa..64d242f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,15 +3,10 @@ #include #include -#include -#include -#include -#include #include #include #include -#include -#include +#include #include "alarmtime.h" #include "microcontroller.h" @@ -40,6 +35,8 @@ private: Microcontroller *_micro; + std::vector _relayCheckBoxes; + void remoteMode(); signals: @@ -62,17 +59,16 @@ private slots: //RGB void slotChangedRgb(const QColor color); void slotApplyPreset(); - void slotAmpChkbtn(int state); void changeHeaderLableText(const QString string); - //Relays - void slotAmpToggle(int state); - void slotBSpeakerToggle(int state); + //relays + void relayCheckBoxToggeled(int state); + + //Automation + void slotAmpAutoToggle(int state); void slotBSpeakerAutoToggle(int state); - void slotInfMirrorToggle(int state); void slotInfMirrorAutoToggle(int state); - void slotAirconToggle(int state); //Alarm void slotChangedAlarmTime(const QTime time); @@ -81,9 +77,10 @@ private slots: //Night void slotChangedNightTime(const QTime time); - //syncoff - void slotSyncoff(); +public slots: + + void relayStateChanged(std::vector relayStates); }; diff --git a/mainwindow.ui b/mainwindow.ui index 4c9f48b..ae9e9cd 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -23,11 +23,11 @@ - SHinterface + Smart Home Interface - - UVOSicon.bmpUVOSicon.bmp + + :/images/UVOSicon.bmp:/images/UVOSicon.bmp @@ -421,6 +421,12 @@ 0 + + + 16777215 + 80 + + 0 @@ -499,12 +505,9 @@ - - - - 0 - 0 - + + + true Alarm @@ -580,6 +583,8 @@ - + + + diff --git a/microcontroller.cpp b/microcontroller.cpp index c5494fb..39e5bdd 100644 --- a/microcontroller.cpp +++ b/microcontroller.cpp @@ -1,24 +1,6 @@ #include "microcontroller.h" -Microcontroller::Microcontroller(QIODevice* port): _port(port) -{ - getState(); -} - -Microcontroller::Microcontroller() -{ -} - -Microcontroller::~Microcontroller() -{ - if(_port != nullptr) delete _port; -} - -void Microcontroller::setIODevice(QIODevice *port) -{ - _port = port; - getState(); -} +//relays void Microcontroller::relayToggle(int state, int relay) { @@ -30,6 +12,18 @@ void Microcontroller::relayToggle(int state, int relay) if(_port != nullptr) _port->write(buffer, length); } +void Microcontroller::relayOn(int relay) +{ + relayToggle(true, relay); +} + +void Microcontroller::relayOff(int relay) +{ + relayToggle(false, relay); +} + +//rgb lights + void Microcontroller::rgbOn() { if(_port != nullptr) _port->write("rgb on\n", sizeof("rgb on\n")-1); @@ -57,16 +51,6 @@ void Microcontroller::setPattern(int pattern) _port->write(buffer, length); } -void Microcontroller::relayOn(int relay) -{ - relayToggle(true, relay); -} - -void Microcontroller::relayOff(int relay) -{ - relayToggle(false, relay); -} - bool Microcontroller::connected() { if(_port != nullptr) return _port->isOpen(); @@ -86,7 +70,7 @@ void Microcontroller::run() loop->exec(); } -void Microcontroller::getState() +void Microcontroller::requestState() { if(_port != nullptr) _port->write("relay state\n", sizeof("relay state\n")); } @@ -100,6 +84,34 @@ void Microcontroller::abort() } } +std::vector Microcontroller::getLastState() +{ + return _relayStates; +} + +//housekeeping + +Microcontroller::Microcontroller(QIODevice* port): _port(port) +{ + requestState(); +} + +Microcontroller::Microcontroller() +{ +} + +Microcontroller::~Microcontroller() +{ + if(_port != nullptr) delete _port; +} + +void Microcontroller::setIODevice(QIODevice *port) +{ + _port = port; + requestState(); +} + + void Microcontroller::processMicroReturn() { QString workbuff = _buffer; diff --git a/microcontroller.h b/microcontroller.h index 13fdc78..cd3273e 100644 --- a/microcontroller.h +++ b/microcontroller.h @@ -11,6 +11,7 @@ #include #include #include +#include #include class Microcontroller: public QObject @@ -25,7 +26,7 @@ private: QString _buffer; void processMicroReturn(); - void getState(); + void requestState(); public: Microcontroller(QIODevice* port); @@ -33,6 +34,7 @@ public: ~Microcontroller(); bool connected(); void setIODevice(QIODevice* port); + std::vector getLastState(); public slots: void rgbOn(); diff --git a/power.cpp b/power.cpp index 43bd7d3..ba0802f 100644 --- a/power.cpp +++ b/power.cpp @@ -1,6 +1,14 @@ #include "power.h" +#include -Power::Power(QObject *parent) : QObject(parent) +Power::Power(QSettings *settings, Microcontroller* micro, QObject *parent) : QObject(parent), _micro(micro), _settings(settings) { } + +void Power::syncoff() +{ + _settings->sync(); + for(unsigned int i = 0; i < _micro->getLastState().size(); i++) _micro->relayOff(i); + QProcess::execute ( "syncoff" ); +} diff --git a/power.h b/power.h index 3e20c8b..a283c52 100644 --- a/power.h +++ b/power.h @@ -2,16 +2,25 @@ #define POWER_H #include +#include +#include "microcontroller.h" class Power : public QObject { +private: Q_OBJECT + Microcontroller* _micro; + QSettings* _settings; + public: - explicit Power(QObject *parent = nullptr); + explicit Power(QSettings* settings, Microcontroller* micro, QObject *parent = nullptr); signals: public slots: + + void syncoff(); + }; -#endif // POWER_H \ No newline at end of file +#endif // POWER_H diff --git a/relaydialog.cpp b/relaydialog.cpp index 77197d9..6e20ed7 100644 --- a/relaydialog.cpp +++ b/relaydialog.cpp @@ -13,7 +13,7 @@ RelayDialog::RelayDialog(Microcontroller *micro, QWidget *parent) : _relayCheckBoxes.push_back(ui->checkBox_R2); _relayCheckBoxes.push_back(ui->checkBox_R3); - for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) connect(_relayCheckBoxes[i], SIGNAL(toggled(bool)), this, SLOT(relayCheckBoxToggeled(bool))); + for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) connect(_relayCheckBoxes[i], SIGNAL(stateChanged(int)), this, SLOT(relayCheckBoxToggeled(int))); _micro->relayOn(STARTING_RELAY); _micro->relayOn(STARTING_RELAY+1); @@ -26,18 +26,18 @@ RelayDialog::~RelayDialog() } -void RelayDialog::relayCheckBoxToggeled(bool checked) +void RelayDialog::relayCheckBoxToggeled(int state) { for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) - { - if(_relayCheckBoxes[i] == sender()) - { - checked ? _micro->relayOn(i+4) : _micro->relayOff(i+4); - } - } + if(_relayCheckBoxes[i] == sender())_micro->relayToggle(state, i+STARTING_RELAY); } void RelayDialog::relayStateChanged(std::vector relayStates) { - if(relayStates.size() >= 8) for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) _relayCheckBoxes[i]->setChecked(relayStates[i+4]); + if(relayStates.size() >= STARTING_RELAY+4) for(unsigned int i = 0; i < _relayCheckBoxes.size(); i++) + { + _relayCheckBoxes[i]->blockSignals(true); + _relayCheckBoxes[i]->setChecked(relayStates[i+STARTING_RELAY]); + _relayCheckBoxes[i]->blockSignals(false); + } } diff --git a/relaydialog.h b/relaydialog.h index ee27419..299c8d7 100644 --- a/relaydialog.h +++ b/relaydialog.h @@ -30,7 +30,7 @@ private: Microcontroller *_micro; private slots: - void relayCheckBoxToggeled(bool checked); + void relayCheckBoxToggeled(int state); public slots: void relayStateChanged(std::vector relayStates); diff --git a/relaydialog.ui b/relaydialog.ui index e64964e..26a3806 100644 --- a/relaydialog.ui +++ b/relaydialog.ui @@ -152,7 +152,7 @@ - Aux 1 + 3040 @@ -178,6 +178,44 @@ + + + + Qt::Horizontal + + + + + + + + + Aux + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + On + + + + + diff --git a/resources.qrc b/resources.qrc index 90f4a83..877c740 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,2 +1,5 @@ - - + + + UVOSicon.bmp + +