Skip to content

Small improvements in update example #552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 1, 2017
Merged

Small improvements in update example #552

merged 1 commit into from
Aug 1, 2017

Conversation

copercini
Copy link
Contributor

  • 100ms is too low to get server response

- 100ms is too low to get server response
@me-no-dev me-no-dev merged commit 5c1b10f into espressif:master Aug 1, 2017
Raienryu97 pushed a commit to Raienryu97/arduino-esp32 that referenced this pull request Aug 2, 2017
- 100ms is too low to get server response
@huyhl248
Copy link

huyhl248 commented Oct 6, 2017

I used this sample to update my ESP32 firmware, I put my bin file on my server, check contentLength, and content type and herder OK. However, it always fails after some packets, toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)); return 0 and the process terminated.
Do the problem belong to the stream? How to fix it?
Thanks a lot!
Best regards,
Huy.

@me-no-dev
Copy link
Member

@huyhl248 where is that data.readBytes code? how much is _bufferLen when it fails?

@huyhl248
Copy link

huyhl248 commented Oct 9, 2017

Dear @me-no-dev ,
I added some trace:

size_t UpdateClass::writeStream(Stream &data) {
    size_t written = 0;
    size_t toRead = 0;
    if(hasError() || !isRunning())
        return 0;

    if(!_verifyHeader(data.peek())) {
        _reset();
        return 0;
    }
    
    log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
    while(remaining()) {
        toRead = data.readBytes(_buffer + _bufferLen,  (SPI_FLASH_SEC_SIZE - _bufferLen));
        log_e("toRead %d",toRead);
        if(toRead == 0) { //Timeout
            delay(100);
            toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
            if(toRead == 0) { //Timeout
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
        }
        _bufferLen += toRead;
        if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
            return written;
        written += toRead;
        log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
        //delay(1000);
    }
    return written;
}

And here is my log:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 816048
Connection: close
Date: Mon, 09 Oct 2017 01:44:22 GMT
Server: Apache
X-Powered-By: PHP/5.6.31
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Transfer-Encoding: Binary
Content-Disposition: attachment; filename="firmware_01.01.01.bin"
Content-Description: File Transfer
Accept-Ranges: bytes

Got application/octet-stream payload.
Got 816048 bytes from server
contentLength : 816048, isValidContentType : 1

Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
[E][Updater.cpp:311] writeStream(): data.available() 5192 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 3900 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 4316 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 4120 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 4230 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 3508 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 3048 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 3932 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 3268 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 4356 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 260 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:327] writeStream(): data.available() 2196 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 2196
[E][Updater.cpp:327] writeStream(): data.available() 0 SPI_FLASH_SEC_SIZE 4096, _bufferLen 2196, _buffer 1073644440
[E][Updater.cpp:314] writeStream(): toRead 0
Written only : 47252/816048. Retry?

Please advise!
Thanks a lot!
Best regards,
Huy.

@me-no-dev
Copy link
Member

me-no-dev commented Oct 9, 2017

Looks like we need to do more waiting before timeout. Still strange why data slows down, but a loop to 20 that trys to read more data once Timeout is detected could do. Can you try to implement that and report?

        if(toRead == 0) { //Timeout
            uint8_t turn = 0;
            while(toRead == 0 && turn++ < 20){
                delay(100);
                toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
                log_e("toRead[%u] %d",turn,toRead);
            }
            if(toRead == 0) { //Timeout
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
        }

@huyhl248
Copy link

huyhl248 commented Oct 9, 2017

Dear @me-no-dev ,
Updated code:

size_t UpdateClass::writeStream(Stream &data) {
    size_t written = 0;
    size_t toRead = 0;
    if(hasError() || !isRunning())
        return 0;

    if(!_verifyHeader(data.peek())) {
        _reset();
        return 0;
    }
    
    log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
    while(remaining()) {
        toRead = data.readBytes(_buffer + _bufferLen,  (SPI_FLASH_SEC_SIZE - _bufferLen));
        log_e("toRead %d",toRead);
        if(toRead == 0) { //Timeout
            uint8_t turn = 0;
            while(toRead == 0 && turn++ < 20){
                delay(100);
                toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
            }
            if(toRead == 0) { //Timeout
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
        }
        _bufferLen += toRead;
        if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
            return written;
        written += toRead;
        log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
        //delay(1000);
    }
    return written;
}

Updated log:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 816048
Connection: close
Date: Mon, 09 Oct 2017 03:54:05 GMT
Server: Apache
X-Powered-By: PHP/5.6.31
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Transfer-Encoding: Binary
Content-Disposition: attachment; filename="firmware_01.01.01.bin"
Content-Description: File Transfer
Accept-Ranges: bytes

Got application/octet-stream payload.
Got 816048 bytes from server
contentLength : 816048, isValidContentType : 1

Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
[E][Updater.cpp:311] writeStream(): data.available() 5192 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073643144
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:330] writeStream(): data.available() 3900 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073643144
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:330] writeStream(): data.available() 4010 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073643144
[E][Updater.cpp:314] writeStream(): toRead 4010
[E][Updater.cpp:330] writeStream(): data.available() 0 SPI_FLASH_SEC_SIZE 4096, _bufferLen 4010, _buffer 1073643144
[E][Updater.cpp:314] writeStream(): toRead 0
Written only : 12202/816048. Retry?

Best regards,
Huy.

@me-no-dev
Copy link
Member

please add that debug line inside the loop :) check my updated comment/code above :) so we are sure that it waits for at least 2 seconds. or better yet, add this one:

        if(toRead == 0) { //Timeout
            uint8_t turn = 0;
            uint64_t tstarted = millis();
            while(toRead == 0 && turn++ < 20){
                delay(100);
                toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
                log_e("toRead[%u] %d",turn,toRead);
            }
            if(toRead == 0) { //Timeout
                log_e("Timeout after %lu ms without data!",millis() - tstarted);
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
        }

@huyhl248
Copy link

huyhl248 commented Oct 9, 2017

Updated Code:

size_t UpdateClass::writeStream(Stream &data) {
    size_t written = 0;
    size_t toRead = 0;
    if(hasError() || !isRunning())
        return 0;

    if(!_verifyHeader(data.peek())) {
        _reset();
        return 0;
    }
    
    log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
    while(remaining()) {
        toRead = data.readBytes(_buffer + _bufferLen,  (SPI_FLASH_SEC_SIZE - _bufferLen));
        log_e("toRead %d",toRead);
        if(toRead == 0) { //Timeout
            uint8_t turn = 0;
            uint64_t tstarted = millis();
            while(toRead == 0 && turn++ < 20){
                delay(100);
                toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
                log_e("toRead[%u] %d",turn,toRead);
            }
            if(toRead == 0) { //Timeout
                log_e("Timeout after %lu ms without data!",millis() - tstarted);
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
        }
        _bufferLen += toRead;
        if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
            return written;
        written += toRead;
        log_e("data.available() %d SPI_FLASH_SEC_SIZE %d, _bufferLen %d, _buffer %d",data.available(),SPI_FLASH_SEC_SIZE, _bufferLen, _buffer);
        //delay(1000);
    }
    return written;
}

Updated log:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 816048
Connection: close
Date: Mon, 09 Oct 2017 04:33:10 GMT
Server: Apache
X-Powered-By: PHP/5.6.31
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Transfer-Encoding: Binary
Content-Disposition: attachment; filename="firmware_01.01.01.bin"
Content-Description: File Transfer
Accept-Ranges: bytes

Got application/octet-stream payload.
Got 816048 bytes from server
contentLength : 816048, isValidContentType : 1

Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
[E][Updater.cpp:311] writeStream(): data.available() 5192 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3900 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 4316 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 1316 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 4230 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3508 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3048 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3932 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3268 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 4356 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3488 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 2196 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3556 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 2416 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3928 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 4192 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 2968 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 52 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3392 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:333] writeStream(): data.available() 3076 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 3076
[E][Updater.cpp:333] writeStream(): data.available() 0 SPI_FLASH_SEC_SIZE 4096, _bufferLen 3076, _buffer 1073648172
[E][Updater.cpp:314] writeStream(): toRead 0
[E][Updater.cpp:321] writeStream(): toRead[1] 0
[E][Updater.cpp:321] writeStream(): toRead[2] 0
[E][Updater.cpp:321] writeStream(): toRead[3] 0
[E][Updater.cpp:321] writeStream(): toRead[4] 0
[E][Updater.cpp:321] writeStream(): toRead[5] 0
[E][Updater.cpp:321] writeStream(): toRead[6] 0
[E][Updater.cpp:321] writeStream(): toRead[7] 0
[E][Updater.cpp:321] writeStream(): toRead[8] 0
[E][Updater.cpp:321] writeStream(): toRead[9] 0
[E][Updater.cpp:321] writeStream(): toRead[10] 0
[E][Updater.cpp:321] writeStream(): toRead[11] 0
[E][Updater.cpp:321] writeStream(): toRead[12] 0
[E][Updater.cpp:321] writeStream(): toRead[13] 0
[E][Updater.cpp:321] writeStream(): toRead[14] 0
[E][Updater.cpp:321] writeStream(): toRead[15] 0
[E][Updater.cpp:321] writeStream(): toRead[16] 0
[E][Updater.cpp:321] writeStream(): toRead[17] 0
[E][Updater.cpp:321] writeStream(): toRead[18] 0
[E][Updater.cpp:321] writeStream(): toRead[19] 0
[E][Updater.cpp:321] writeStream(): toRead[20] 0
[E][Updater.cpp:324] writeStream(): Timeout after 22000 ms without data!
Written only : 80900/816048. Retry?

@me-no-dev
Copy link
Member

there seems to be something wrong with your server/connection. 22 seconds without getting more data is plenty for timeout ;)

@huyhl248
Copy link

huyhl248 commented Oct 9, 2017

Thanks.
I set up a local server and no change! I also used a web browser and the bin file was downloaded immediately.
I don't think there is any problem with my connection/server.
Best regards,
Huy.

@tonywestonuk
Copy link

I was getting pedeoric timeouts. 100ms is too short, implementing code in this thread fixes.. Refactored the code to this

    size_t toRead = 0;
    uint8_t turn = 0;
    while((toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)))==0 ) {    
            if (turn++==50){
                _abort(UPDATE_ERROR_STREAM);
                return written;
            }
            delay(50);
    }

@huyhl248
Copy link

huyhl248 commented May 18, 2018

@tonywestonuk
Not resolved yet! :(

execOTA 71 HTTP/1.1 200 OK
execOTA 71 Content-Type: application/octet-stream
execOTA 71 Content-Length: 1255648
execOTA 71 Connection: close
execOTA 71 Date: Fri, 18 May 2018 04:25:31 GMT
execOTA 71 Server: Apache
execOTA 71 X-Powered-By: PHP/5.6.35
execOTA 71 Content-Disposition: attachment; filename=firmware_02.02.00.bin
execOTA 71 Accept-Ranges: bytes
execOTA 71 
execOTA 129 Got application/octet-stream payload.
execOTA 130 Got 1255648 bytes from server
execOTA 133 content_length : 1255648, isValidContentType : 1
execOTA 134 
execOTA 146 Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while ... Patience!
[E][Updater.cpp:311] writeStream(): data.available() 5339 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 2815 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 2755 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 2865 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 2015 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 3085 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 1793 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 4096
[E][Updater.cpp:341] writeStream(): data.available() 1903 SPI_FLASH_SEC_SIZE 4096, _bufferLen 0, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 1903
[E][Updater.cpp:341] writeStream(): data.available() 0 SPI_FLASH_SEC_SIZE 4096, _bufferLen 1903, _buffer 1073628020
[E][Updater.cpp:314] writeStream(): toRead 0
[E][Updater.cpp:325] writeStream(): Timeout after 53500 ms without data!
execOTA 158 Written only : 30575/1255648. Retry?
scanningLoop 544 >>>>>>>>>>> Restart!

@huyhl248
Copy link

After this error occurred, I could not restart by ESP.restart();. Why?

@tonywestonuk
Copy link

Have you tried downloading the update.bin, using a browser? it does appear to be stalling halfway through.

@tonywestonuk
Copy link

Whats the MTU setting on your wifi network'? - Maybe its transmitting something that the M5Stack cant cope with, but your PC can? Try reducing it! (I am clutching at straws here!)...

@huyhl248
Copy link

Using browser ok. I reduced MTU to 1360 (minimum of my router) but no change!

@TLS1000
Copy link

TLS1000 commented Dec 2, 2018

Hi, I just tried the S3 update example. the 'contentType' verification is wrong

now it's:
if (contentType == "application/octet-stream") { //and isValidContentType returns false

when I change it to this, it works:
if (contentType == "binary/octet-stream") { //and isValidContentType returns true and
//update executes correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants