00001 #include <QFileDialog>
00002 #include <QCloseEvent>
00003 #include <QMessageBox>
00004 #include <QDebug>
00005 #include <QPushButton>
00006
00007 #include "../../utils/HexFile.h"
00008
00009 #include "ThymioBootloader.h"
00010
00011 #include "ThymioBootloader.moc"
00012
00013 using namespace std;
00014
00015 namespace Aseba
00016 {
00017 ThymioBootloaderDialog::ThymioBootloaderDialog(NodeTab* nodeTab):
00018 InvasivePlugin(nodeTab),
00019 nodeId(getNodeId()),
00020 target(getTarget()),
00021 stream(getDashelStream())
00022 {
00023
00024 setWindowTitle(tr("Thymio Firmware Updater"));
00025 resize(544,182);
00026
00027 verticalLayout = new QVBoxLayout(this);
00028 fileLayout = new QHBoxLayout();
00029 lineEdit = new QLineEdit(this);
00030 fileLayout->addWidget(lineEdit);
00031
00032 fileButton = new QPushButton(tr("File..."), this);
00033 fileLayout->addWidget(fileButton);
00034
00035 verticalLayout->addLayout(fileLayout);
00036
00037 progressBar = new QProgressBar(this);
00038 progressBar->setValue(0);
00039
00040 verticalLayout->addWidget(progressBar);
00041
00042 flashLayout = new QHBoxLayout();
00043
00044 spacer = new QSpacerItem(40,20,QSizePolicy::Expanding,
00045 QSizePolicy::Minimum);
00046
00047 flashLayout->addItem(spacer);
00048
00049 flashButton = new QPushButton(tr("Update"), this);
00050 flashLayout->addWidget(flashButton);
00051
00052 quitButton = new QPushButton(tr("Quit"), this);
00053 flashLayout->addWidget(quitButton);
00054
00055 verticalLayout->addItem(flashLayout);
00056
00057 connect(fileButton, SIGNAL(clicked()),this,SLOT(openFile()));
00058 connect(flashButton, SIGNAL(clicked()), this, SLOT(doFlash()));
00059 connect(quitButton, SIGNAL(clicked()), this, SLOT(doClose()));
00060
00061 deleteMyself = false;
00062 }
00063
00064 ThymioBootloaderDialog::~ThymioBootloaderDialog()
00065 {
00066 }
00067
00068 QWidget* ThymioBootloaderDialog::createMenuEntry()
00069 {
00070 QPushButton *flashButton = new QPushButton(tr("Update firmware"));
00071 connect(flashButton, SIGNAL(clicked()), SLOT(showFlashDialog()));
00072 return flashButton;
00073 }
00074
00075 void ThymioBootloaderDialog::closeAsSoonAsPossible()
00076 {
00077
00078
00079 close();
00080 }
00081
00082 bool ThymioBootloaderDialog::surviveTabDestruction() const
00083 {
00084 return deleteMyself;
00085 }
00086
00087 void ThymioBootloaderDialog::showFlashDialog()
00088 {
00089 progressBar->setValue(0);
00090 fileButton->setEnabled(true);
00091 flashButton->setEnabled(true);
00092 lineEdit->setEnabled(true);
00093
00094 exec();
00095
00096
00097
00098 if(deleteMyself)
00099 deleteLater();
00100 }
00101
00102 void ThymioBootloaderDialog::openFile(void)
00103 {
00104 QString name = QFileDialog::getOpenFileName(this, tr("Select hex file"), QString(), tr("Hex files (*.hex)"));
00105 lineEdit->setText(name);
00106 }
00107
00108 void ThymioBootloaderDialog::doFlash(void)
00109 {
00110 const int warnRet = QMessageBox::warning(this, tr("Pre-update warning"), tr("Your are about to write a new firmware to the Thymio II. Make sure that the robot is charged and that the USB cable is properly connected.<p><b>Do not unplug the robot during the update!</b></p>Are you sure you want to proceed?"), QMessageBox::No|QMessageBox::Yes, QMessageBox::No);
00111 if (warnRet != QMessageBox::Yes)
00112 return;
00113
00114 HexFile hex;
00115 try {
00116 hex.read(lineEdit->text().toStdString());
00117 }
00118 catch(HexFile::Error& e) {
00119 QMessageBox::critical(this, tr("Update Error"), tr("Unable to read Hex file"));
00120 return;
00121 }
00122 quitButton->setEnabled(false);
00123 flashButton->setEnabled(false);
00124 fileButton->setEnabled(false);
00125 lineEdit->setEnabled(false);
00126
00127
00128 target->blockWrite();
00129 connect(target, SIGNAL(bootloaderAck(uint,uint)), this, SLOT(ackReceived(uint,uint)));
00130 connect(target, SIGNAL(nodeDisconnected(uint)), this, SLOT(vmDisconnected(unsigned)));
00131
00132
00133 pageMap.clear();
00134
00135 for (HexFile::ChunkMap::iterator it = hex.data.begin(); it != hex.data.end(); it ++)
00136 {
00137
00138 unsigned chunkAddress = it->first;
00139
00140 unsigned chunkDataIndex = 0;
00141
00142 unsigned chunkSize = it->second.size();
00143
00144 do
00145 {
00146
00147 unsigned pageIndex = (chunkAddress + chunkDataIndex) / 2048;
00148
00149 unsigned byteIndex = (chunkAddress + chunkDataIndex) % 2048;
00150
00151 if (pageMap.find(pageIndex) == pageMap.end())
00152 pageMap[pageIndex] = vector<uint8>(2048, (uint8)0);
00153
00154 unsigned amountToCopy = min(2048 - byteIndex, chunkSize - chunkDataIndex);
00155 copy(it->second.begin() + chunkDataIndex, it->second.begin() + chunkDataIndex + amountToCopy, pageMap[pageIndex].begin() + byteIndex);
00156
00157 chunkDataIndex += amountToCopy;
00158 }
00159 while(chunkDataIndex < chunkSize);
00160 }
00161
00162 progressBar->setMaximum(pageMap.size());
00163 progressBar->setValue(0);
00164
00165
00166 Reboot msg(nodeId);
00167 try
00168 {
00169 msg.serialize(stream);
00170 stream->flush();
00171
00172 }
00173 catch(Dashel::DashelException e)
00174 {
00175 handleDashelException(e);
00176 }
00177 }
00178
00179 void ThymioBootloaderDialog::doClose(void)
00180 {
00181 done(0);
00182 }
00183
00184 void ThymioBootloaderDialog::ackReceived(unsigned error_code, unsigned address)
00185 {
00186 qDebug() << "Got ack: " << error_code;
00187 if(error_code != 0)
00188 {
00189
00190 }
00191 progressBar->setValue(progressBar->value()+1);
00192 if(currentPage->first == 0) {
00193
00194 flashDone();
00195 return;
00196 }
00197 currentPage++;
00198 if(currentPage == pageMap.end()) {
00199 currentPage = pageMap.find(0);
00200 if(currentPage == pageMap.end()) {
00201 qDebug() << "BUG: No page 0 !";
00202
00203 pageMap[0] = vector<uint8>(2048,(uint8)0);
00204 currentPage = pageMap.find(0);
00205 }
00206 }
00207 writePage(currentPage->first, ¤tPage->second[0]);
00208 }
00209
00210 void ThymioBootloaderDialog::vmDisconnected(unsigned node)
00211 {
00212 deleteMyself = true;
00213
00214 qDebug() << "Bootloader entered " << node;
00215 if(node != nodeId)
00216 abort();
00217
00218 currentPage = pageMap.begin();
00219 if(currentPage == pageMap.end()) {
00220 flashDone();
00221 return;
00222 }
00223 while(currentPage->first == 0 && currentPage != pageMap.end())
00224 currentPage++;
00225 if(currentPage == pageMap.end())
00226 currentPage = pageMap.find(0);
00227 writePage(currentPage->first, ¤tPage->second[0]);
00228 }
00229
00230 void ThymioBootloaderDialog::writePage(unsigned page, unsigned char * data)
00231 {
00232 qDebug() << "Write page: " << page;
00233 BootloaderWritePage writePage;
00234 writePage.dest = nodeId;
00235 writePage.pageNumber = page;
00236
00237 try
00238 {
00239 writePage.serialize(stream);
00240 stream->write(data,2048);
00241 stream->flush();
00242 }
00243 catch(Dashel::DashelException e)
00244 {
00245 handleDashelException(e);
00246 }
00247 }
00248
00249 void ThymioBootloaderDialog::flashDone(void)
00250 {
00251 pageMap.clear();
00252
00253
00254 qDebug() << "Flash done, switch back to VM";
00255
00256 BootloaderReset msg(nodeId);
00257 try
00258 {
00259 msg.serialize(stream);
00260 stream->flush();
00261 }
00262 catch(Dashel::DashelException e)
00263 {
00264 handleDashelException(e);
00265 }
00266
00267
00268 disconnect(target, SIGNAL(nodeDisconnected(uint)),this, SLOT(vmDisconnected(unsigned)));
00269 disconnect(target, SIGNAL(bootloaderAck(uint,uint)), this, SLOT(ackReceived(uint,uint)));
00270 target->unblockWrite();
00271
00272
00273 startTimer(100);
00274 }
00275
00276 void ThymioBootloaderDialog::timerEvent(QTimerEvent *event)
00277 {
00278 killTimer(event->timerId());
00279
00280 GetDescription message;
00281 try
00282 {
00283 message.serialize(stream);
00284 stream->flush();
00285 }
00286 catch(Dashel::DashelException e)
00287 {
00288 handleDashelException(e);
00289 }
00290
00291
00292 done(0);
00293
00294
00295 }
00296
00297 void ThymioBootloaderDialog::closeEvent(QCloseEvent *event)
00298 {
00299 if(quitButton->isEnabled())
00300 event->accept();
00301 else
00302 event->ignore();
00303 }
00304
00305 void ThymioBootloaderDialog::handleDashelException(Dashel::DashelException e)
00306 {
00307 switch(e.source)
00308 {
00309 default:
00310
00311
00312 QMessageBox* message = new QMessageBox(QMessageBox::Critical, tr("Dashel Unexpected Error"), tr("A communication error happened during the update process:") + " (" + QString::number(e.source) + ") " + e.what(), QMessageBox::NoButton, this);
00313 message->setWindowModality(Qt::NonModal);
00314 message->show();
00315 }
00316 }
00317 };