The issue was that the read loop was started in an asyncio task and exceptions from those tasks don't get automatically propagated to the calling code. Instead, the calling code needs to await the task and then check if an exception has happened. Once I implemented that in the Young32400WeatherStationDataClient, the timeout mechanism started working and the ESS CSC started going to FAULT state in case of an error.
In a video chat with Russell this was discussed and we decided to hoist the read loop plus exception monitoring into a new BaseReadLoopDataClient subclass of BaseDataClient and convert as many existing DataClients as possible to subclasses of that new subclass. The one possible execption may be the ts_ess_labjack LabJackAccelerometerDataClient, which gets data pushed via a stream. It may be possible to convert that one as well but that is now under investigation.