pez-globo / pufferfish-software

All software for the Pufferfish ventilator.
Apache License 2.0
0 stars 1 forks source link

Provide a way to set the timezone #430

Open ethanjli opened 3 years ago

ethanjli commented 3 years ago

The current UI/UX design specifies a way for the clinician/operator to adjust the local time from the frontend, via a tab in the Settings screen. However, if a ventilator moves across timezones, we really should preserve the correctness of UTC time (for complicated reasons summarized here) so that timestamps of the event log will be consistent and correct, which means that we need to provide a way to adjust the timezone setting. For regions which use Daylight Savings Time, we should ensure that the start or end of DST doesn't cause chaos for our system clock, e.g. if the user tries to adjust the local time in response. And we should make sure that anyone who wants to adjust the time will first ensure that the timezone is correct. I'm not sure if the frontend should expose an easy way for the clinician to change the timezone, if changing the timezone only needs to be easy enough for a technician to carry out, or if the timezone only needs to be set once in the factory. I guess if we're making it easy for the clinician to change the local time, then we have to require them to set the timezone correctly too...

Timezone picking options

I haven't been able to find any easy-to-use React component for changing the timezone, if we decide that we need to make it possible to set the timezone from the frontend. Almost all React components I've found rely either on typing or on making an unusably long menu to scroll through.

Picking timezone from a map

There are some jQuery plugins which provide a map interface where you tap on a map to select a timezone:

Picking timezone from menus by continent and country

An alternative (probably simpler) option would be to make our own component where we just have a three menus to list continents, then the countries in the selected continent (e.g. via countries-db), and finally the timezones in the selected country (e.g. via countries-and-timezones). We might also need to provide something to specify whether daylight savings time is in effect, for timezones which use it. This should be acceptable for a technician to use, and it would probably be good enough for a clinician to use. And this would probably be simpler to implement than wrapping around and customizing a jQuery map-based picker. But we will still need to deal with the problem of long menus for listing countries in a continent.

Picking country by map and menu

We could improve on the usability of a country-list approach by showing a map where the user roughly taps on where their country is, and then we list the countries near it in a smaller menu, ordered by distance or alphabetically; then they would select the country, and then they can select the timezone for the selected country. However, I'm not sure if it's a valid assumption to expect that any user would immediately know where on a world map their country roughly is (as opposed to tapping a few places to see if their country shows up), so this might not actually improve usability for everyone.

Picking country by first letter and menu

Perhaps a more universally accessible approach would be to make it easy to jump through the long menu of all countries in a continent by the first letter of the country (this works if the app is in English, I'm not sure if it will work for all languages), e.g. as in https://xcarpentier.github.io/react-native-country-picker-modal/ with the "alpha filter code" set to true (the repo is at react-native-country-picker-modal).

What can we do about long menus?

If we have a long list of countries, we'll need to either add scrolling or pagination to the list of countries; in our app it seems we always use pagination rather than scrolling. Maybe the "country picking" and/or timezone picking interactions would need to be in a pop-up modal? We could also consider using the rotary dial to allow the user to quickly scroll through the list of countries, but that might be a bit complicated to implement.

Who should be adjusting the system time and timezone?

I think I would prefer if the panel for adjusting system time is not immediately accessible to clinicians; it should only need to be adjusted if the hardware RTC loses its battery, in which case the ventilator should be sent for servicing rather than continuing to be used, or if the hardware RTC's clock has drifted far enough to warrant an adjustment, which could probably be solved during regular (e.g. quarterly or semi-yearly) maintenance checks by a technician. If the ventilator will be used in places where a technician cannot service the unit at least every few months, that probably adds a bunch of other safety concerns, so maybe we can rule out the use case of running a ventilator without a regular technician maintenance. Because of clock drift, I don't think it would be reasonable to remove time-adjustment functionality from the frontend (i.e. relying only on the factory setting of time).

So maybe the interfaces for adjusting system time and timezone should be in a different screen on aimed at technicians, e.g. a from "Maintenance" or "Technicians" tab in the Settings screen. An advanced clinician would be able to access it, but if it's buried in a "scarier" tab where the expectation is that a user on that tab is changing settings deep in the internals of the ventilator software, then that will be a signal to the user to read the user manual while doing anything on that tab (and we could provide explicit instructions about that on that tab, too). This way, if the time and timezone need to be adjusted in a crisis situation where a technician isn't available, someone can do so by following the user manual.

When should the system time be adjustable?

It should be fine to adjust the timezone at any time, because the backend and frontend fundamentally just use UTC time. However, if the user changes the UTC time, we might screw up the behavior of the backend and frontend (e.g. the state synchronizer and the connection loss detectors work using the UTC time, and then the UTC time jumps backwards or forwards in time when the user changes the system time). We may be able to solve this in the backend by using time.monotonic() instead of time.time() in Python; and in the frontend by using window.performance.now() instead of new Date(Date.now()) in the browser. If we can prevent bad behavior when the system time is changed, then it should be fine to let the system time be changed whenever the user wants. Note that in Python on the RPi we'll need to check time.get_clock_info('monotonic')['adjustable'] to see if time.monotonic() is adjustable (which I think means if it is affected by clock skew) - see PEP 418 for more details.

ethanjli commented 3 years ago

On the RPi, time.get_clock_info('monotonic') returns namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)', monotonic=True, resolution=1e-09). This means that Python's monotonic clock on the RPi can't be changed automatically or manually by the system administrator. So adjustments to the system time won't disrupt the monotonic clock, so if we use the monotonic clock then the backend should be robust against system time adjustments. This is is consistent with a small experiment I did where I queried the monotonic time, then adjusted the system clock one year into the future and then adjusted the system clock one year into the past; after each adjustment, the monotonic time advanced by the real-world duration without any impacts from the system clock.

ethanjli commented 3 years ago

I've confirmed that when I use time.time() as the global source of time in the backend and then adjust the system time to be one day forwards and then adjust it back, then the backend's state synchronizer stops sending messages on its schedule, and any active alarm mute is cancelled early. However, if I instead use time.monotonic() for those purposes, then the backend's state synchronizer and alarm mute services continue to work as they should.