|
| 1 | +import {BehaviorSubject} from 'rxjs'; |
| 2 | + |
| 3 | +import { |
| 4 | + takeUntil, |
| 5 | + filter, |
| 6 | + first |
| 7 | +} from 'rxjs/operators'; |
| 8 | + |
| 9 | +const versionsMap = { |
| 10 | + wifi101: { |
| 11 | + latestVersion: 'WINC1500 19.5.4', |
| 12 | + loaderPath: null, |
| 13 | + latestVersionPath: null |
| 14 | + }, |
| 15 | + wifiNina: { |
| 16 | + latestVersion: 'NINA 1.2.2', |
| 17 | + loaderPath: null, |
| 18 | + latestVersionPath: null |
| 19 | + } |
| 20 | +}; |
| 21 | + |
| 22 | +/* The status of the Firmware Updater Tool */ |
| 23 | +const FWUToolStatusEnum = Object.freeze({ |
| 24 | + NOPE: 'NOPE', |
| 25 | + OK: 'OK', |
| 26 | + CHECKING: 'CHECKING', |
| 27 | + ERROR: 'ERROR DOWNLOADING TOOL' |
| 28 | +}); |
| 29 | + |
| 30 | +/* The signatures needed to run the commands to use the Firmware Updater Tool */ |
| 31 | +const signaturesEnum = Object.freeze({ |
| 32 | + GET_FIRMWARE_INFO: 'aceffd98d331df0daa5bb3308bb49a95767d77e7a1557c07a0ec544d2f41c3ec67269f01ce9a63e01f3b43e087ab8eb22b7f1d34135b6686e8ce27d4b5dc083ec8e6149df11880d32486448a71280ef3128efccbd45a84dbd7990a9420a65ee86b3822edba3554fa8e6ca11aec12d4dd99ad072285b98bfdf7b2b64f677da50feb8bddef25a36f52d7605078487d8a5d7cbdc84bfa65d510cee97b46baefea149139a9a6ed4b545346040536e33d850e6ad84c83fe605f677e2ca77439de3fa42350ce504ad9a49cf62c6751d4c2a284500d2c628cd52cd73b4c3e7ef08ae823eb8941383f9c6ff0686da532369d3b266ded8fdd33cca1a128068a4795920f25', |
| 33 | + UPLOAD_FIRMWARE_BOSSAC: 'b2137435f74601a0b1b090ca1592b14199c1c79c87cecac4bb168456b570f17b635f438f5cd80649a101ab27085394e4a0e92f41a2b1b65932789eee8c3ebf39633a503a8a53d3291944afe10ff88f6ea06b0fd7fd7de6d70b302df3a92091a2abe2691ad1aeee051e4bb69f6afa3bd41e643855769347dca018804ae97bcb1331df796fd8921a11b333b45bebe430d323ddd151a907e8fb0e875a45d093cdb4332ec3dbd72a50341b538e058b3f25d4a528bea514b96b8e0701032d64232b7141c62f2231352f4197a1011292a4c3e900c133824e148f3703ea8374873d9097578146819e62685f1a8cbc442dd435ea603136c86ecf028df39fd10a8b3499c6', |
| 34 | + UPLOAD_FIRMWARE_AVRDUDE: '68ed3d9abdc7c77d01223d09c7ae55b08b8ff94f2a42d21a672effb4bdeeb12b10177e831805b3037d9f8d38e8eeeb8327b6c4691731a2a146cfd12398e5a12596e097160ae8d84fc488650ee57439f7fcd83f27d01e9834de555ceec7dc6951b3be5e5a3507752dce8e4ba1f6f9e6494162d537009db899882b9c4fca0868ad446b82fdeabf93bc30c5a39f8fe9c25e799842a10f4171e2896a0e19667b34258f06494663a4a102bcf9fd61d1ff8ebec18204bbd1a66de3e0b53e257ce521b41999dc82428539086ae9a4a5ea7112d87c05a2782cdace0e6576b162294f9ba47c658cc40999ba31be8a129689a703f6c4182055a600b6e41d450fad2896180d' |
| 35 | +}); |
| 36 | + |
| 37 | + |
| 38 | +export default class FirmwareUpdater { |
| 39 | + constructor(Daemon) { |
| 40 | + this.updateStatusEnum = Object.freeze({ |
| 41 | + NOPE: 'NOPE', |
| 42 | + STARTED: 'STARTED', |
| 43 | + GETTING_INFO: 'GETTING_INFO', |
| 44 | + GOT_INFO: 'GOT_INFO', |
| 45 | + UPLOADING: 'UPLOADING', |
| 46 | + DONE: 'DONE', |
| 47 | + ERROR: 'ERROR' |
| 48 | + }); |
| 49 | + |
| 50 | + this.Daemon = Daemon; |
| 51 | + this.Daemon.downloadingDone.subscribe(() => { |
| 52 | + this.FWUToolStatus = FWUToolStatusEnum.OK; |
| 53 | + }); |
| 54 | + |
| 55 | + this.FWUToolStatus = FWUToolStatusEnum.NOPE; |
| 56 | + this.updating = new BehaviorSubject({ status: this.updateStatusEnum.NOPE }); |
| 57 | + |
| 58 | + this.updatingDone = this.updating.pipe(filter(update => update.status === this.updateStatusEnum.DONE)) |
| 59 | + .pipe(first()) |
| 60 | + .pipe(takeUntil(this.updating.pipe(filter(update => update.status === this.updateStatusEnum.ERROR)))); |
| 61 | + |
| 62 | + this.updatingError = this.updating.pipe(filter(update => update.status === this.updateStatusEnum.ERROR)) |
| 63 | + .pipe(first()) |
| 64 | + .pipe(takeUntil(this.updatingDone)); |
| 65 | + |
| 66 | + this.gotFWInfo = this.updating.pipe(filter(update => update.status === this.updateStatusEnum.GOT_INFO)) |
| 67 | + .pipe(first()) |
| 68 | + .pipe(takeUntil(this.updatingDone)) |
| 69 | + .pipe(takeUntil(this.updatingError)); |
| 70 | + } |
| 71 | + |
| 72 | + getFirmwareInfo(boardId, port, wifiModule) { |
| 73 | + this.updating.next({ status: this.updateStatusEnum.GETTING_INFO }); |
| 74 | + let versionsList = []; |
| 75 | + let firmwareInfoMessagesSubscription; |
| 76 | + |
| 77 | + const handleFirmwareInfoMessage = message => { |
| 78 | + switch (message.ProgrammerStatus) { |
| 79 | + case 'Starting': |
| 80 | + break; |
| 81 | + case 'Busy': |
| 82 | + if (message.Msg.indexOf('Flashing with command:') >= 0) { |
| 83 | + return; |
| 84 | + } |
| 85 | + const versions = JSON.parse(message.Msg); |
| 86 | + Object.keys(versions).forEach(v => { |
| 87 | + if (versions[v][0].IsLoader) { |
| 88 | + versionsMap[wifiModule].loaderPath = versions[v][0].Path; |
| 89 | + } |
| 90 | + else { |
| 91 | + versionsList = [...versionsList, ...versions[v]]; |
| 92 | + } |
| 93 | + }); |
| 94 | + const latestVersion = versionsList.find(version => version.Name === versionsMap[wifiModule].latestVersion) |
| 95 | + versionsMap[wifiModule].latestVersionPath = latestVersion.Path; |
| 96 | + break; |
| 97 | + case 'Error': |
| 98 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Couldn't get firmware info: ${message}` }) |
| 99 | + firmwareInfoMessagesSubscription.unsubscribe(); |
| 100 | + break; |
| 101 | + case 'Done': |
| 102 | + firmwareInfoMessagesSubscription.unsubscribe(); |
| 103 | + this.updating.next({ status: this.updateStatusEnum.GOT_INFO, versionsList }) |
| 104 | + break; |
| 105 | + default: |
| 106 | + break; |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + if (this.FWUToolStatus !== FWUToolStatusEnum.OK) { |
| 111 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't get firmware info: couldn't find firmware updater tool` }); |
| 112 | + return; |
| 113 | + } |
| 114 | + |
| 115 | + firmwareInfoMessagesSubscription = this.Daemon.appMessages.subscribe(message => { |
| 116 | + if (message.ProgrammerStatus) { |
| 117 | + handleFirmwareInfoMessage(message); |
| 118 | + } |
| 119 | + }); |
| 120 | + const data = { |
| 121 | + board: boardId, |
| 122 | + port, |
| 123 | + commandline: `"{runtime.tools.fwupdater.path}/updater" -get_available_for {network.password}`, |
| 124 | + signature: signaturesEnum.GET_FIRMWARE_INFO, |
| 125 | + extra: { |
| 126 | + auth: { |
| 127 | + password: boardId |
| 128 | + } |
| 129 | + }, |
| 130 | + filename: 'ListFirmwareVersionsInfo.bin' |
| 131 | + }; |
| 132 | + |
| 133 | + return fetch(`${this.Daemon.pluginURL}/upload`, { |
| 134 | + method: 'POST', |
| 135 | + headers: { |
| 136 | + 'Content-Type': 'text/plain; charset=utf-8' |
| 137 | + }, |
| 138 | + body: JSON.stringify(data) |
| 139 | + }).then(response => { |
| 140 | + if (!response.ok) { |
| 141 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Error fetching ${this.Daemon.pluginURL}/upload` }); |
| 142 | + return; |
| 143 | + } |
| 144 | + }).catch(reason => { |
| 145 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Coudln't list firmware versions info.` }); |
| 146 | + return ; |
| 147 | + }); |
| 148 | + } |
| 149 | + |
| 150 | + /* |
| 151 | + wifiModule can be either 'wifiNina' or 'wifi101' |
| 152 | + */ |
| 153 | + updateFirmware(boardId, port, wifiModule) { |
| 154 | + this.updating.next({ status: this.updateStatusEnum.STARTED }); |
| 155 | + if (!port) { |
| 156 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: no port selected.` }); |
| 157 | + return; |
| 158 | + } |
| 159 | + this.gotFWInfo.subscribe(state => { |
| 160 | + if (!versionsMap[wifiModule] && !versionsMap[wifiModule].latestVersion) { |
| 161 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: couldn't find module '${wifiModule}'` }); |
| 162 | + return; |
| 163 | + } |
| 164 | + const latestVersion = state.versionsList.find(version => version.Name === versionsMap[wifiModule].latestVersion); |
| 165 | + if (!latestVersion) { |
| 166 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: couldn't find version '${versionsMap[wifiModule].latestVersion}' for module '${versionsMap[wifiModule]}'` }); |
| 167 | + return; |
| 168 | + } |
| 169 | + |
| 170 | + let updateFirmwareMessagesSubscription; |
| 171 | + |
| 172 | + const handleFirmwareUpdateMessage = message => { |
| 173 | + switch (message.ProgrammerStatus) { |
| 174 | + case 'Error': |
| 175 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: ${message.Msg}`}); |
| 176 | + updateFirmwareMessagesSubscription.unsubscribe(); |
| 177 | + break; |
| 178 | + case 'Done': |
| 179 | + this.updating.next({ status: this.updateStatusEnum.DONE}); |
| 180 | + updateFirmwareMessagesSubscription.unsubscribe(); |
| 181 | + break; |
| 182 | + default: |
| 183 | + break; |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + updateFirmwareMessagesSubscription = this.Daemon.appMessages.subscribe(message => { |
| 188 | + if (message.ProgrammerStatus) { |
| 189 | + handleFirmwareUpdateMessage(message); |
| 190 | + } |
| 191 | + }); |
| 192 | + |
| 193 | + let addresses = ''; |
| 194 | + const rootCertificates = [{ |
| 195 | + domain: 'arduino.cc', |
| 196 | + port: 443 |
| 197 | + }]; |
| 198 | + |
| 199 | + rootCertificates.forEach(address => { |
| 200 | + if (address.domain && address.port) { |
| 201 | + addresses += `-address ${address.domain}:${address.port} `; |
| 202 | + } |
| 203 | + }); |
| 204 | + |
| 205 | + const isUsingAvrdude = boardId === 'uno2018'; |
| 206 | + const programmer = isUsingAvrdude ? '{runtime.tools.avrdude}/bin/avrdude' : '{runtime.tools.bossac}/bossac'; |
| 207 | + |
| 208 | + const loaderPath = versionsMap[wifiModule].loaderPath; |
| 209 | + if (!loaderPath) { |
| 210 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: invalid loaderPath` }); |
| 211 | + return; |
| 212 | + } |
| 213 | + |
| 214 | + const data = { |
| 215 | + board: boardId, |
| 216 | + port, |
| 217 | + commandline: `"{runtime.tools.fwupdater.path}/updater" -flasher {network.password} -firmware {upload.verbose} -port {serial.port} -restore_binary "{build.path}/{build.project_name}.bin" -programmer ${programmer}`, |
| 218 | + hex: '', |
| 219 | + extra: { |
| 220 | + auth: { |
| 221 | + password: loaderPath |
| 222 | + }, |
| 223 | + verbose: true, |
| 224 | + params_verbose: `${versionsMap[wifiModule].latestVersionPath} ${addresses}` // eslint-disable-line camelcase |
| 225 | + }, |
| 226 | + signature: isUsingAvrdude ? signaturesEnum.UPLOAD_FIRMWARE_AVRDUDE : signaturesEnum.UPLOAD_FIRMWARE_BOSSAC, |
| 227 | + filename: 'CheckFirmwareVersion.bin' |
| 228 | + }; |
| 229 | + |
| 230 | + fetch(`${this.Daemon.pluginURL}/upload`, { |
| 231 | + method: 'POST', |
| 232 | + headers: { |
| 233 | + 'Content-Type': 'text/plain; charset=utf-8' |
| 234 | + }, |
| 235 | + body: JSON.stringify(data) |
| 236 | + }).then(response => { |
| 237 | + if (!response.ok) { |
| 238 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: error fetching ${this.Daemon.pluginURL}/upload` }); |
| 239 | + return; |
| 240 | + } |
| 241 | + }).catch(reason => { |
| 242 | + this.updating.next({ status: this.updateStatusEnum.ERROR, err: `Can't update Firmware: ${reason}` }); |
| 243 | + return; |
| 244 | + }); |
| 245 | + }); |
| 246 | + this.getFirmwareInfo(boardId, port, wifiModule); |
| 247 | + } |
| 248 | +} |
0 commit comments