$search
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, ¤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 // 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 };