macOS “Optimized Battery Charging” in Windows 10 / Boot Camp causes MacBook Pro battery to not charge

On a MacBook Pro 16-inch 2019 (Intel), under macOS Ventura 13.1, if

  • the “Optimized Battery Charging” option is turned on in System Settings -> Battery
  • the power adapter is plugged in
  • the battery has charged to 80% or more under macOS
  • the machine is then rebooted into Windows 10, using Boot Camp
  • then: in Windows, the battery will not charge at all. Windows will report the battery as “Plugged in” but the battery will not charge. The battery will slowly drain to 0% as the machine is used.

Solution

Disable Optimized Battery Charging temporarily or permanently before rebooting into Windows 10 under Boot Camp, in System Settings -> Battery -> Battery Health -> Info disclosure

Discussion

Optimized Battery Charging is a new feature introduced in recent macOS versions to preserve the health of Intel MacBook Pro batteries. It is known that if a laptop is not being used on battery, then it is best to charge to 80% instead of full. macOS purports to learn its user’s laptop battery usage patterns, and will charge the battery to 80% under normal adapter usage, and only begins charging to full when it expects the user to go to battery power soon.

However, it seems to do this by instructing some SMC/firmware level controller to stop charging the battery once current capacity hits 80% or more. When rebooted into Windows 10, which does not understand this optimized charging feature, there is no corresponding instruction to begin charging the battery again when the capacity falls below 80%. The consequence is that the laptop battery will steadily drain to 0%, and nothing will make it charge again until the machine is rebooted back into macOS.

This is an understandable edge case that Apple engineers didn’t test for. However, it seems to have started only recently (I don’t recall macOS Monterey having this behavior). Until it is fixed, if regular Windows 10 / Boot Camp usage is expected, it is best to leave the Optimized Battery Charging feature turned off on the macOS side — temporarily or permanently.

Worth noting that there are multiple other possible reasons that Windows 10 / Boot Camp is causing battery drain on a MacBook Pro. For example, the 16-inch 2019 MBP’s white 96W USB-C charger looks identical to the 87W USB-C charger from previous-gen MacBook Pro 15. If mistakenly or deliberately used to power the 16-inch MBP, then under full load the 87W adapter is insufficient to run the laptop. In this scenario the OS will tap the battery in complement with the adapter, causing a steady drain. Windows also runs more inefficiently than macOS on MacBook Pro, so under full CPU/GPU usage, it seems to take more power sometimes than even the 96W adapter can provide.

However, in either of those cases, the drain is very slow. In the case of the Optimized Battery Charging bug described above, under full CPU/GPU usage, the battery drains extremely quickly, with 30 minutes of usage causing 50% or more battery drain sometimes. This is because in this case, the laptop is not drawing power from the adapter at all.

macOS Safari displays blank page when accessing flask development server on localhost:5000

There is a very strange behavior in macOS Ventura 13.1 (and possibly earlier macOS as well), where the macOS Safari browser and the Google Chrome browser cannot access a Python Flask web development server on port 5000. The URL http://localhost:5000 either shows as completely blank, or responds with a 403 Forbidden.

Context

In short, Flask is a lightweight web application framework for Python. A typical Flask webapp is run in development mode as

flask --debug run

By default, this will start a Flask development server on port 5000:

* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit

From the command line, subsequently, curl can be used to reach the server

curl "http://localhost:5000/"

In my case, it responds with

Hello world

Note that localhost and 127.0.0.1 are, in typical cases, equivalent to each other. Both of them are pointing to the local machine.

Problem

If using the Safari browser on the same machine, though, and hitting http://localhost:5000, this will display a completely blank page. The development server itself will not show that a GET request was made, which suggests that the request never made it to the flask application at all.

If using the Chrome browser to hit http://localhost:5000, it will display a 403 Forbidden error instead of a blank page:

Access to localhost was denied
You don't have authorization to view this page.
HTTP ERROR 403

And again, the Flask dev server itself does not show a successful GET request being made.

As a final symptom, if instead http://127.0.0.1:5000 was used, a GET will be successfully received by the Flask dev server, and the server will respond to it normally.

Solution

  1. On macOS Ventura, go to Settings -> General -> AirDrop & Handoff and turn off AirPlay Receiver.
  2. Alternatively, instruct Flask to use a different port for its dev server other than 5000 and access that port instead:
    flask --debug run -p 8000
  3. Alternatively, always interact with Flask on the IP literal 127.0.0.1:5000 rather than the localhost DNS name. This is what the Flask instructions say to do anyway.

Discussion

It turns out there is a port conflict, of sorts. On macOS Ventura (and possibly Monterey as well), the OS’s built-in AirPlay Receiver is also listening on TCP port 5000 for some purpose. Running

lsof -i -P

...
ControlCe  423 yliu    7u  IPv4       0t0  TCP *:5000 (LISTEN)
ControlCe  423 yliu    8u  IPv6       0t0  TCP *:5000 (LISTEN)
...
Python    1374 yliu    5u  IPv4       0t0  TCP localhost:5000 (LISTEN)

we can see that ControlCenter (really AirPlay Receiver) is listening on TCP *:5000 and the Python Flask app is listening on localhost:5000.

In fact, if the Flask server is turned off, and a curl -i command (to show response headers) is issued to localhost:5000, this is the response:
curl -i "http://localhost:5000/"

HTTP/1.1 404 Not Found
Content-Length: 0
Server: AirTunes/670.6.2

I’m not sure why this situation isn’t causing a port conflict, throwing a socket error for “address already in use”, which would block the Python app from starting at all. Similarly, I don’t understand why it doesn’t affect curl when run on the command line, which still successfully reaches the Flask server.

Nevertheless, changing Flask’s socket to an unoccupied port such as 8000, or turning off the offending macOS component AirPlay Receiver that is using the port, addresses the conflict. Now both Safari and Chrome will be able send requests to the actual Flask server, rather than the AirTunes server also residing on port 5000.