ThymioBootloader.cpp
Go to the documentation of this file.
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                 // Create the gui ...
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                 // FIXME: is it correct, what to do if someone requsted a close
00078                 // of the main window while we are flashing?
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                 // Note that now nodeTab is not valid anymore
00096                 
00097                 // we must delete ourself, because the tab is not there any more to do bookkeeping for us
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                 // make target ready for flashing
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                 // Now we have a valid hex file ...
00133                 pageMap.clear();
00134 
00135                 for (HexFile::ChunkMap::iterator it = hex.data.begin(); it != hex.data.end(); it ++)
00136                 {
00137                         // get page number
00138                         unsigned chunkAddress = it->first;
00139                         // index inside data chunk
00140                         unsigned chunkDataIndex = 0;
00141                         // size of chunk in bytes
00142                         unsigned chunkSize = it->second.size();
00143                         // copy data from chunk to page
00144                         do
00145                         {
00146                                 // get page number
00147                                 unsigned pageIndex = (chunkAddress + chunkDataIndex) / 2048;
00148                                 // get address inside page
00149                                 unsigned byteIndex = (chunkAddress + chunkDataIndex) % 2048;
00150                                 // if page does not exists, create it
00151                                 if (pageMap.find(pageIndex) == pageMap.end())
00152                                         pageMap[pageIndex] = vector<uint8>(2048, (uint8)0);
00153                                 // copy data
00154                                 unsigned amountToCopy = min(2048 - byteIndex, chunkSize - chunkDataIndex);
00155                                 copy(it->second.begin() + chunkDataIndex, it->second.begin() + chunkDataIndex + amountToCopy, pageMap[pageIndex].begin() + byteIndex);
00156                                 // increment chunk data pointer
00157                                 chunkDataIndex += amountToCopy;
00158                         }
00159                         while(chunkDataIndex < chunkSize);
00160                 }
00161 
00162                 progressBar->setMaximum(pageMap.size());
00163                 progressBar->setValue(0);
00164                 
00165                 // Ask the pic to switch into bootloader
00166                 Reboot msg(nodeId);
00167                 try
00168                 {
00169                         msg.serialize(stream);
00170                         stream->flush();
00171                         // now, wait until the disconnect event is sent.
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                         // Fixme should not ignore errors...
00190                 }
00191                 progressBar->setValue(progressBar->value()+1);
00192                 if(currentPage->first == 0) {
00193                         // End of flash operation
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                                  // ARG FIXME HACK no 0 page, write null byte instead, bootloader will understand ...
00203                                 pageMap[0] = vector<uint8>(2048,(uint8)0);
00204                                 currentPage = pageMap.find(0);
00205                         }
00206                 }
00207                 writePage(currentPage->first, &currentPage->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, &currentPage->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                 // Send exit bootloader, send presence
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                 // make target behave normally again
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                 // send GetDescription 100 ms later
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                 // automatically close
00292                 done(0);
00293                 // Don't allow to immediatly reflash, we want studio to redisplay a new tab ! 
00294                 //quitButton->setEnabled(true);
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                         // oops... we are doomed
00311                         // non-modal message box
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 };


aseba
Author(s): Stéphane Magnenat
autogenerated on Sun Oct 5 2014 23:46:39