var net_addr = "192.168.123.255"; //net_addr = "10.0.50.255"; const files_path = "./"; const dgram = require('dgram'), clui = require('clui'), // clc = require('cli-color'), // Line = clui.Line, Spinner = clui.Spinner, Progress = clui.Progress, inquirer = require('inquirer'), fs = require('fs'); const shortSearch = 2, longSearch = 8; const countdown = new Spinner('Поиск устройств. Найдено [0] Осталось 10 секунд... ', ['|', '/', '-', ' ']);// ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷']); const m2 = [ "Загрузка прошивки" , { name: "Загрузка firmware" }, "Перезагрузка", new inquirer.Separator(), "Выход", ]; // const confirmMenu = [ // "Да", "Нет" // ] const client = dgram.createSocket('udp4'); var number = shortSearch; var mode = 1; // work mode - 1: search devices, 2 - uploading var devices = []; // device list var progressbar; var step = 1; //upload step var stopTimer; var mac=""; // current device mac address var fdata =[]; // uploading file content var pageNumber = 0; // page of uploading file var session = (Math.random() * 0xFFFFFFFF).toString(16); // work session var pr; var firmvare = 0; // ** // Get work network interface // used for create disable stoofing filter command // ** function getInterface(){ var net = require('os').networkInterfaces(); for (var ifs in net) { var res = net[ifs].find(ip => { return ip.address.includes(net_addr.substring(0,net_addr.length-3)) }); if (res) return ifs; } return "unknown"; } // function confirm(message){ // var deferred = Q.defer(); // showMenu(confirmMenu, message, (choice)=>{ // console.log(choice) // if (choice == "Да") // deferred.resolve(true); // else // deferred.reject(false); // }) // return deferred.promise; // } function toBytesInt16(num){ return [(num & 0xff00) >> 8,(num & 0x00ff)]; } // ** // Send command to device // ** function send(cmd,addr,msg,result){ var mess = "_"+addr+cmd+ (msg? "|"+msg :""); client.send(mess,0,mess.length,65535,net_addr,result); } // ** // process error result // ** function result(err,bytes) { if (err) throw err; }; function getFilesInDirectory (path,ext) { let dir = fs.readdirSync( path ); return dir.filter( elm => elm.match(new RegExp(`.*\.(${ext})$`, 'ig'))); } function getFile(path){ return fs.readFileSync(path); } console.log("") console.log("Программа обновления прошивки для устройств Tibbo"); console.log("") // attach events client.on('listening', () =>{ // udp net binding client.setBroadcast(1); startSearch(); }); var tm; client.on('message',(msg,rinfo) => { // udp message recived var m = msg.toString(); // message string from Buffer var addr = msg.toString().substring(0,25); // device mac address switch (mode){ case 1:{ // поиск устройств var d = devices.find(obj => { return obj.mac === addr }); if (!d){ devices.push({mac: addr, ip: rinfo.address}); // get device information client.send("_"+addr+ "X",65535,net_addr); }else { if (m.indexOf('/') >0){ if (m.indexOf('/')==m.lastIndexOf('/')){ d.platform = m.substr(26,m.length-27); d.app_version = "" }else{ d.platform = m.substr(27,m.indexOf('/')-28); d.app_version = m.substr(m.lastIndexOf('/')+1); } }else if ( !d.platform || d.app_version){ // requery if information not full client.send("_"+addr+ "X",65535,net_addr); } } countdown.message ("Поиск устройств. Найдено [" + devices.length + "] Осталось "+number+" секунд... "); break; } case 2:{ // Программирование switch (step){ case 1: // switch device to update mode if (m.indexOf(session)<=0) return; if (addr == mac){ step=2; // send first data blocks var cmdd="Q"; if (firmware==1) cmdd="QF"; send(cmdd,mac,session); tm = setInterval(function(){ var cmdd="Q"; if (firmware==1) cmdd="QF"; send(cmdd,mac,session); },500); // resend every 0.5 sec }else{ console.log("ERROR - wrong reply",) } break; case 2: // sending first block if (pageNumber == 0 && addr == mac && m.indexOf(session>0)) if (tm){ clearInterval(tm); } progressbar = new Progress(20); process.stdout.write("\u001b[1000D"+progressbar.update(pageNumber,fdata.length/128)) stopTimer = setTimeout(()=>{ // clearInterval(pr); console.log(); console.log("ОШИБКА: Ответ от устройства не получен!"); console.log("Возможно включена защита от Spoofing'а"); console.log("Для отключения выполните комманду : "); console.log("\tsudo sysctl net.ipv4.conf.all.rp_filter=0 && sudo sysctl net.ipv4.conf." +getInterface()+ ".rp_filter=0"); console.log(); process.exit(); },15000) sendFilePage(); break; case 3: // wait confirm block recieved switch to this mode in sendFilePage() var b = toBytesInt16(pageNumber); if (pageNumber >= 0 && addr == mac) { if (stopTimer) { // stop wrong net config check timer clearTimeout(stopTimer); stopTimer=null; } var b11 = Buffer.from([0x41,b[0],b[1]]) // number of confirmed block if (msg.compare(b11,0,3,25)==0){ pageNumber++; // change current block number process.stdout.write("\u001b[1000D"+progressbar.update(pageNumber,fdata.length/128)) if (((pageNumber)*128) >= fdata.length-1){ // if last block confirmed step = 4; // switch to final step if (firmware==1) send("N",mac,session); // switch device to normal mode else send("T",mac,session); // switch device to normal mode } else sendFilePage(); } } break; case 4: // get last success message and switch to search mode if (addr == mac && msg[25]==0x41){ clearTimeout(stopTimer); send("E",mac,session); // send reboot command to device (if it id not auto reboot) console.log(""); console.log("Загрузка завершена успешно"); console.log(""); mode = 1; step = 1; number = longSearch; // long time search for wait to device reboot and network ready fdata = []; // clear file content buffer startSearch(); } } } } }); client.on('close', function() { console.log('Соединение неожиданно было закрыто. Проверьте сетевое подключение.'); console.log(); process.exit(); }); client.bind(44515); // bind socket //** // Get file names and show in menu //** function select_file(ext){ console.log(""); var filelist = getFilesInDirectory(files_path,ext); filelist.push(new inquirer.Separator()); filelist.push("Отмена"); showMenu(filelist,'Выберите файл для загрузки в '+mac,item =>{ if (item.result == "Отмена"){ process.exit(); } console.log("OK"); upload_application(mac,item.result); }); } function upload_application(mac, filename){ console.log("upload_application("+mac+","+filename+")" ); var d = devices.find(obj => { return obj.mac === mac }); var platform = d.platform.substring(1,d.platform.lastIndexOf('.')); console.log("Начинается загрузка"); fdata = getFile(files_path+filename); var fst = fdata.indexOf(String.fromCharCode(0)+"")+5; var fen = fdata.indexOf(String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(0),fst); var line = fdata.toString('ascii',fst+1,fen-1); var finfo = line.split(String.fromCharCode(0)); if (firmware==0 && fdata.indexOf(platform)<0){ console.log("Выбрана неподходящая платформа :"); console.log("Устройство: "+platform+", файл:" +finfo[2]); select_file(firmware==0?"tpc":"bin"); return; } mode = 2; //switch to uploading mode step=1; // init device step pageNumber = 0; // init sending send("X",mac,session); // switch device to program mode } function sendFilePage(){ step = 3; clearTimeout(stopTimer); // clear resend timer var bmac = Buffer.from("_"+mac); var n = toBytesInt16(pageNumber); var cmd = Buffer.from([0x44,n[0],n[1]]); var f = Buffer.from(fdata.slice(128*pageNumber,128*(pageNumber+1))); var mess = Buffer.concat([bmac,cmd,f]); client.send(mess,0,mess.length,65535,net_addr,result); if (fdata.length/128 >pageNumber) stopTimer = setTimeout(sendFilePage,500); // resend every 0.5 sec } function startSearch(){ if (number<=0) number = shortSearch; devices = []; session = (Math.random() * 0xFFFFFFFF).toString(16); // change session countdown.start(); // get device list; pr = setInterval(()=>{ send("","?",""); countdown.message( "Поиск устройств. Найдено [" + devices.length + "] Осталось "+number+" секунд... "); if (number == 0){ clearInterval(pr); countdown.stop(); proc(); } number--; },1000); send("","?",""); } function proc(){ if (devices.length == 0){ console.log("ОШИБКА: Устройства не найдены!"); console.log("Возможно включена защита от Spoofing'а"); console.log("Для отключения выполните комманды : "); console.log("\tsudo sysctl net.ipv4.conf.all.rp_filter=0 && sudo sysctl net.ipv4.conf." +getInterface()+ ".rp_filter=0"); console.log(); process.exit(); } var m = []; var mlip = 0; var mlpl = 0; for (var i = 0; i < devices.length ; i++){ if (mlip < devices[i].ip.length) mlip = devices[i].ip.length; if (mlpl < devices[i].platform.length) mlpl = devices[i].platform.length; } for (var i = 0; i < devices.length ; i++) { m.push({ value: devices[i].mac, name: devices[i].mac.replace("[",'').replace("]",'') + " | " + devices[i].ip.padStart(mlip," ") + " | " + devices[i].platform.padEnd(mlpl," ") + " | " + devices[i].app_version }); } m.push(new inquirer.Separator()); m.push({ key:'0', value: "Выход" }); showMenu(m,"Выберите устройство",function(item) { if (item){ if (item.result == "Выход") process.exit(); else mac = item.result; console.log(""); showMenu(m2,"Выберите действие",(select)=>{ //console.log(select); if (select.result=="Выход"){ process.exit(); } if (select.result == 'Загрузка прошивки'){ //console.log("select file"); firmware=0; select_file("tpc"); } if (select.result == 'Загрузка firmware'){ firmware=1; select_file("bin"); } if (select.result == "Перезагрузка"){ send("E",mac,session); number = longSearch; startSearch(); } }) }else { console.log("cancel") } //process.exit(); }); }; function showMenu(m,title,select){ var q = { type: 'list', name: 'result', message: title, choices: m }; inquirer.prompt(q) .then(select); }