kopia lustrzana https://github.com/peterhinch/micropython-samples
Add general purpose DS3231 driver.
rodzic
4691354a40
commit
14ebf88797
133
DS3231/README.md
133
DS3231/README.md
|
@ -5,23 +5,134 @@ is an ideal way rapidly to calibrate the Pyboard's RTC which can then achieve
|
||||||
similar levels of accuracy (+- ~2 mins/year). The chip can also provide
|
similar levels of accuracy (+- ~2 mins/year). The chip can also provide
|
||||||
accurate time to platforms lacking a good RTC (notably the ESP8266).
|
accurate time to platforms lacking a good RTC (notably the ESP8266).
|
||||||
|
|
||||||
Two drivers are provided:
|
Three drivers are provided:
|
||||||
1. `ds3231_port.py` A multi-platform driver.
|
1. `ds3231_gen.py` General purpose portable driver supporting alarms.
|
||||||
2. `ds3231_pb.py` A Pyboard-specific driver with RTC calibration facility. For
|
2. `ds3231_port.py` Portable driver: main purpose is to test accuracy of a
|
||||||
|
platform's RTC.
|
||||||
|
3. `ds3231_pb.py` A Pyboard-specific driver with RTC calibration facility. For
|
||||||
Pyboard 1.x and Pyboard D.
|
Pyboard 1.x and Pyboard D.
|
||||||
|
|
||||||
Breakout boards are widely available. The interface is I2C. Pullups to 3.3V
|
Breakout boards are widely available. The interface is I2C. Pullups to 3.3V
|
||||||
(typically 10KΩ) should be provided on the `SCL` and `SDA` lines if these are
|
(typically 10KΩ) should be provided on the `SCL` and `SDA` lines if these are
|
||||||
not supplied on the breakout board.
|
not supplied on the breakout board.
|
||||||
|
|
||||||
Both divers use edge detection to achieve millisecond-level precision from the
|
Drivers 2 and 3 use edge detection to achieve millisecond-level precision from
|
||||||
DS3231. This enables relatively rapid accuracy testing of the platform's RTC,
|
the DS3231. This enables relatively rapid accuracy testing of the platform's
|
||||||
and fast calibration of the Pyboard's RTC. To quantify this, a sufficiently
|
RTC, and fast calibration of the Pyboard's RTC. To quantify this, a
|
||||||
precise value of calibration may be acquired in 5-10 minutes.
|
sufficiently precise value of calibration may be acquired in 5-10 minutes.
|
||||||
|
|
||||||
###### [Main README](../README.md)
|
###### [Main README](../README.md)
|
||||||
|
|
||||||
# 1. The multi-platform driver
|
# 1. General purpose driver ds3231_gen.py
|
||||||
|
|
||||||
|
This uses datetime tuples to set and read time values. These are of form
|
||||||
|
(year, month, day, hour, minute, second, weekday, yearday)
|
||||||
|
as used by [time.localtime](http://docs.micropython.org/en/latest/library/time.html#time.localtime).
|
||||||
|
|
||||||
|
## 1.1 The DS3231 class
|
||||||
|
|
||||||
|
#### Constructor:
|
||||||
|
This takes one mandatory argument, an initialised I2C bus.
|
||||||
|
|
||||||
|
#### Public methods:
|
||||||
|
1. `get_time(set_rtc=False)`. If `set_rtc` is `True` it sets the platform's
|
||||||
|
RTC from the DS3231. Returns the DS3231 time as a datetime tuple with
|
||||||
|
`yearday=0`.
|
||||||
|
On ports/platforms which don't support an RTC, if `set_rtc` is `True`, the
|
||||||
|
system time will be set from the DS3231. If setting the RTC, for accuracy the
|
||||||
|
method will pause until a seconds transition occurs on the DS3231.
|
||||||
|
2. `set_time(tt=None)`. Sets the DS3231 time. By default it uses the
|
||||||
|
platform's syatem time, otherwise the passed `datetime` tuple. If passing a
|
||||||
|
tuple, see the note below.
|
||||||
|
3. `__str__()` Returns a dump of the device's registers for debug in a "pretty
|
||||||
|
print" format.
|
||||||
|
4. `temperature()` A float, temperature in °C. Datasheet specifies +-3°C
|
||||||
|
accuracy. It really is that bad.
|
||||||
|
|
||||||
|
#### Public bound variables:
|
||||||
|
|
||||||
|
1. `alarm1` `Alarm` instances (see below). Can be set to 1s precision.
|
||||||
|
2. `alarm2` Can be set to 1min precision.
|
||||||
|
|
||||||
|
#### Alarm Public methods
|
||||||
|
|
||||||
|
1. `set(when, day=0, hr=0, min=0, sec=0)` Arg `when` is one of the module
|
||||||
|
constants listed below. Alarm operation is started.
|
||||||
|
2. `clear()` Clears the alarm status and releases the alarm pin. The alarm
|
||||||
|
will occur again the next time the parameters match.
|
||||||
|
3. `__call__()` No args. Return `True` if alarm has occurred.
|
||||||
|
4. `enable(run)` If `run` is `False` the alarm is cleared and will enter a
|
||||||
|
stopped state; in that state the alarm will not occur again. If `True` a
|
||||||
|
stopped alarm is restarted and will occur on the next match.
|
||||||
|
|
||||||
|
#### Alarm bound variables
|
||||||
|
|
||||||
|
1. `alno` Alarm no. (1 or 2).
|
||||||
|
|
||||||
|
#### Module constants
|
||||||
|
|
||||||
|
These are the allowable options for the alarm's `when` arg, along with the
|
||||||
|
relevant `Alarm.set()` args:
|
||||||
|
`EVERY_SECOND` Only supported by alarm1.
|
||||||
|
`EVERY_MINUTE` `sec`
|
||||||
|
`EVERY_HOUR` `min`, `sec`
|
||||||
|
`EVERY_DAY` `hr`, `min`, `sec`
|
||||||
|
`EVERY_WEEK` `day` (weekday 0..6), `hr`, `min`, `sec`
|
||||||
|
`EVERY_MONTH` `day` (month day 1..month end), `hr`, `min`, `sec`
|
||||||
|
|
||||||
|
In all cases `sec` values are ignored by alarm2: alarms occur on minute
|
||||||
|
boundaries. This is a hardware restriction.
|
||||||
|
|
||||||
|
#### Setting DS3231 time
|
||||||
|
|
||||||
|
Where this is to be set using a datetime tuple rather than from system time, it
|
||||||
|
is necessary to pass the correct value of weekday. This can be acquired with
|
||||||
|
this function. It can be passed a tuple with `dt[6] == 0` and will return a
|
||||||
|
corrected tuple:
|
||||||
|
```python
|
||||||
|
import time
|
||||||
|
def dt_tuple(dt):
|
||||||
|
return time.localtime(time.mktime(dt)) # Populate weekday field
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Alarms
|
||||||
|
|
||||||
|
Comments assume that a backup battery is in use.
|
||||||
|
|
||||||
|
The battery ensures that alarm settings are stored through a power outage. If
|
||||||
|
an alarm occurs during an outage the pin will be driven low and will stay low
|
||||||
|
until power is restored and `clear` or `disable` are issued.
|
||||||
|
|
||||||
|
If an alarm is set and a power outage occurs, when power is restored the alarm
|
||||||
|
will continue to operate at the specified frequency. Setting an alarm:
|
||||||
|
```python
|
||||||
|
from machine import SoftI2C, Pin
|
||||||
|
from ds3231_gen import *
|
||||||
|
i2c = SoftI2C(scl=Pin(16, Pin.OPEN_DRAIN, value=1), sda=Pin(17, Pin.OPEN_DRAIN, value=1))
|
||||||
|
d = DS3231(i2c)
|
||||||
|
dt.alarm1.set(EVERY_MINUTE, sec=30)
|
||||||
|
```
|
||||||
|
If a power outage occurs here the following code will ensure alarms continue to
|
||||||
|
occur at one minute intervals:
|
||||||
|
```python
|
||||||
|
from machine import SoftI2C, Pin
|
||||||
|
from ds3231_gen import *
|
||||||
|
i2c = SoftI2C(scl=Pin(16, Pin.OPEN_DRAIN, value=1), sda=Pin(17, Pin.OPEN_DRAIN, value=1))
|
||||||
|
d = DS3231(i2c)
|
||||||
|
while True:
|
||||||
|
d.alarm1.clear() # Clear pending alarm
|
||||||
|
while not d.alarm1(): # Wait for alarm
|
||||||
|
pass
|
||||||
|
time.sleep(0.3) # Pin stays low for 300ms
|
||||||
|
```
|
||||||
|
Note that the DS3231 alarm2 does not have a seconds register: `sec` values will
|
||||||
|
be ignored and `EVERY_SECOND` is unsupported.
|
||||||
|
|
||||||
|
Re the `INT\` (alarm) pin the datasheet (P9) states "The pullup voltage can be
|
||||||
|
up to 5.5V, regardless of the voltage on Vcc". Note that some breakout boards
|
||||||
|
have a pullup resistor between this pin and Vcc.
|
||||||
|
|
||||||
|
# 2. Portable driver ds3231_port
|
||||||
|
|
||||||
This can use soft I2C so any pins may be used.
|
This can use soft I2C so any pins may be used.
|
||||||
|
|
||||||
|
@ -47,7 +158,7 @@ In my testing the ESP8266 RTC was out by 5%. The ESP32 was out by 6.7ppm or
|
||||||
about 12 minutes/yr. A PiPico was out by 1.7ppm, 3.2mins/yr. Hardware samples
|
about 12 minutes/yr. A PiPico was out by 1.7ppm, 3.2mins/yr. Hardware samples
|
||||||
will vary.
|
will vary.
|
||||||
|
|
||||||
## 1.1 The DS3231 class
|
## 2.1 The DS3231 class
|
||||||
|
|
||||||
Constructor:
|
Constructor:
|
||||||
This takes one mandatory argument, an initialised I2C bus.
|
This takes one mandatory argument, an initialised I2C bus.
|
||||||
|
@ -69,7 +180,7 @@ Public methods:
|
||||||
devices with poor RTC's (e.g. ESP8266).
|
devices with poor RTC's (e.g. ESP8266).
|
||||||
If `machine.RTC` is unsupported a `RuntimeError` will be thrown.
|
If `machine.RTC` is unsupported a `RuntimeError` will be thrown.
|
||||||
|
|
||||||
# 2. The Pyboard driver
|
# 3. The Pyboard driver
|
||||||
|
|
||||||
The principal reason to use this driver is to calibrate the Pyboard's RTC. This
|
The principal reason to use this driver is to calibrate the Pyboard's RTC. This
|
||||||
supports the Pyboard 1.x and Pyboard D. Note that the RTC on the Pyboard D is
|
supports the Pyboard 1.x and Pyboard D. Note that the RTC on the Pyboard D is
|
||||||
|
@ -102,7 +213,7 @@ ds3231.calibrate()
|
||||||
Calibration data is stored in battery-backed memory. So if a backup cell is
|
Calibration data is stored in battery-backed memory. So if a backup cell is
|
||||||
used the RTC will run accurately in the event of a power outage.
|
used the RTC will run accurately in the event of a power outage.
|
||||||
|
|
||||||
## 2.1 The DS3231 class
|
## 3.1 The DS3231 class
|
||||||
|
|
||||||
Constructor:
|
Constructor:
|
||||||
This takes one mandatory argument, an I2C bus instantiated using the `machine`
|
This takes one mandatory argument, an I2C bus instantiated using the `machine`
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
# ds3231_gen.py General purpose driver for DS3231 precison real time clock.
|
||||||
|
|
||||||
|
# Author: Peter Hinch
|
||||||
|
# Copyright Peter Hinch 2023 Released under the MIT license.
|
||||||
|
|
||||||
|
# Rewritten from datasheet to support alarms. Sources studied:
|
||||||
|
# WiPy driver at https://github.com/scudderfish/uDS3231
|
||||||
|
# https://github.com/notUnique/DS3231micro
|
||||||
|
|
||||||
|
# Assumes date > Y2K and 24 hour clock.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import machine
|
||||||
|
|
||||||
|
|
||||||
|
_ADDR = const(104)
|
||||||
|
|
||||||
|
EVERY_SECOND = 0x0F # Exported flags
|
||||||
|
EVERY_MINUTE = 0x0E
|
||||||
|
EVERY_HOUR = 0x0C
|
||||||
|
EVERY_DAY = 0x80
|
||||||
|
EVERY_WEEK = 0x40
|
||||||
|
EVERY_MONTH = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
rtc = machine.RTC()
|
||||||
|
except:
|
||||||
|
print("Warning: machine module does not support the RTC.")
|
||||||
|
rtc = None
|
||||||
|
|
||||||
|
|
||||||
|
class Alarm:
|
||||||
|
def __init__(self, device, n):
|
||||||
|
self._device = device
|
||||||
|
self._i2c = device.ds3231
|
||||||
|
self.alno = n # Alarm no.
|
||||||
|
self.offs = 7 if self.alno == 1 else 0x0B # Offset into address map
|
||||||
|
self.mask = 0
|
||||||
|
|
||||||
|
def _reg(self, offs : int, buf = bytearray(1)) -> int: # Read a register
|
||||||
|
self._i2c.readfrom_mem_into(_ADDR, offs, buf)
|
||||||
|
return buf[0]
|
||||||
|
|
||||||
|
def enable(self, run):
|
||||||
|
flags = self._reg(0x0E) | 4 # Disable square wave
|
||||||
|
flags = (flags | self.alno) if run else (flags & ~self.alno & 0xFF)
|
||||||
|
self._i2c.writeto_mem(_ADDR, 0x0E, flags.to_bytes(1, "little"))
|
||||||
|
|
||||||
|
def __call__(self): # Return True if alarm is set
|
||||||
|
return bool(self._reg(0x0F) & self.alno)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
flags = (self._reg(0x0F) & ~self.alno) & 0xFF
|
||||||
|
self._i2c.writeto_mem(_ADDR, 0x0F, flags.to_bytes(1, "little"))
|
||||||
|
|
||||||
|
def set(self, when, day=0, hr=0, min=0, sec=0):
|
||||||
|
if when not in (0x0F, 0x0E, 0x0C, 0x80, 0x40, 0):
|
||||||
|
raise ValueError("Invalid alarm specifier.")
|
||||||
|
self.mask = when
|
||||||
|
if when == EVERY_WEEK:
|
||||||
|
day += 1 # Setting a day of week
|
||||||
|
self._device.set_time((0, 0, day, hr, min, sec, 0, 0), self)
|
||||||
|
self.enable(True)
|
||||||
|
|
||||||
|
|
||||||
|
class DS3231:
|
||||||
|
def __init__(self, i2c):
|
||||||
|
self.ds3231 = i2c
|
||||||
|
self.alarm1 = Alarm(self, 1)
|
||||||
|
self.alarm2 = Alarm(self, 2)
|
||||||
|
if _ADDR not in self.ds3231.scan():
|
||||||
|
raise RuntimeError(f"DS3231 not found on I2C bus at {_ADDR}")
|
||||||
|
|
||||||
|
def get_time(self, set_rtc=False, data=bytearray(7)):
|
||||||
|
def bcd2dec(bcd): # Strip MSB
|
||||||
|
return ((bcd & 0x70) >> 4) * 10 + (bcd & 0x0F)
|
||||||
|
|
||||||
|
self.ds3231.readfrom_mem_into(_ADDR, 0, data)
|
||||||
|
if set_rtc: # For accuracy set RTC immediately after a seconds transition
|
||||||
|
ss = data[0]
|
||||||
|
while ss == data[0]:
|
||||||
|
self.ds3231.readfrom_mem_into(_ADDR, 0, data)
|
||||||
|
ss, mm, hh, wday, DD, MM, YY = [bcd2dec(x) for x in data]
|
||||||
|
MM &= 0x1F # Strip century
|
||||||
|
YY += 2000
|
||||||
|
# Time from DS3231 in time.localtime() format (less yday)
|
||||||
|
result = YY, MM, DD, hh, mm, ss, wday - 1, 0
|
||||||
|
if set_rtc:
|
||||||
|
if rtc is None: # Best we can do is to set local time
|
||||||
|
secs = time.mktime(result)
|
||||||
|
time.localtime(secs)
|
||||||
|
else:
|
||||||
|
rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Output time or alarm data to device
|
||||||
|
# args: tt A datetime tuple. If absent uses localtime.
|
||||||
|
# alarm: An Alarm instance or None if setting time
|
||||||
|
def set_time(self, tt=None, alarm=None):
|
||||||
|
# Given BCD value return a binary byte. Modifier:
|
||||||
|
# Set MSB if any of bit(1..4) or bit 7 set, set b6 if mod[6]
|
||||||
|
def gbyte(dec, mod=0):
|
||||||
|
tens, units = divmod(dec, 10)
|
||||||
|
n = (tens << 4) + units
|
||||||
|
n |= 0x80 if mod & 0x0F else mod & 0xC0
|
||||||
|
return n.to_bytes(1, "little")
|
||||||
|
|
||||||
|
YY, MM, mday, hh, mm, ss, wday, yday = time.localtime() if tt is None else tt
|
||||||
|
mask = 0 if alarm is None else alarm.mask
|
||||||
|
offs = 0 if alarm is None else alarm.offs
|
||||||
|
if alarm is None or alarm.alno == 1: # Has a seconds register
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(ss, mask & 1))
|
||||||
|
offs += 1
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mm, mask & 2))
|
||||||
|
offs += 1
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(hh, mask & 4)) # Sets to 24hr mode
|
||||||
|
offs += 1
|
||||||
|
if alarm is not None: # Setting an alarm - mask holds MS 2 bits
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mday, mask))
|
||||||
|
else: # Setting time
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(wday + 1)) # 1 == Monday, 7 == Sunday
|
||||||
|
offs += 1
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(mday)) # Day of month
|
||||||
|
offs += 1
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(MM, 0x80)) # Century bit (>Y2K)
|
||||||
|
offs += 1
|
||||||
|
self.ds3231.writeto_mem(_ADDR, offs, gbyte(YY - 2000))
|
||||||
|
|
||||||
|
def temperature(self):
|
||||||
|
def twos_complement(input_value: int, num_bits: int) -> int:
|
||||||
|
mask = 2 ** (num_bits - 1)
|
||||||
|
return -(input_value & mask) + (input_value & ~mask)
|
||||||
|
|
||||||
|
t = self.ds3231.readfrom_mem(_ADDR, 0x11, 2)
|
||||||
|
i = t[0] << 8 | t[1]
|
||||||
|
return twos_complement(i >> 6, 10) * 0.25
|
||||||
|
|
||||||
|
def __str__(self, buf=bytearray(0x13)): # Debug dump of device registers
|
||||||
|
self.ds3231.readfrom_mem_into(_ADDR, 0, buf)
|
||||||
|
s = ""
|
||||||
|
for n, v in enumerate(buf):
|
||||||
|
s = f"{s}0x{n:02x} 0x{v:02x} {v >> 4:04b} {v & 0xF :04b}\n"
|
||||||
|
if not (n + 1) % 4:
|
||||||
|
s = f"{s}\n"
|
||||||
|
return s
|
|
@ -0,0 +1,174 @@
|
||||||
|
# ds3231_gen_test.py Test script for ds3231_gen.oy.
|
||||||
|
|
||||||
|
# Author: Peter Hinch
|
||||||
|
# Copyright Peter Hinch 2023 Released under the MIT license.
|
||||||
|
|
||||||
|
from machine import SoftI2C, Pin
|
||||||
|
from ds3231_gen import *
|
||||||
|
import time
|
||||||
|
import uasyncio as asyncio
|
||||||
|
|
||||||
|
def dt_tuple(dt):
|
||||||
|
return time.localtime(time.mktime(dt)) # Populate weekday field
|
||||||
|
|
||||||
|
i2c = SoftI2C(scl=Pin(16, Pin.OPEN_DRAIN, value=1), sda=Pin(17, Pin.OPEN_DRAIN, value=1))
|
||||||
|
d = DS3231(i2c)
|
||||||
|
|
||||||
|
async def wait_for_alarm(alarm, t, target): # Wait for n seconds for an alarm, check time of occurrence
|
||||||
|
print(f"Wait {t} secs for alarm...")
|
||||||
|
if alarm.alno == 2:
|
||||||
|
target = 0 # Alarm 2 does not support secs
|
||||||
|
while t:
|
||||||
|
if alarm():
|
||||||
|
return target - 1 <= d.get_time()[5] <= target + 1
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
t -= 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def test_alarm(alarm):
|
||||||
|
print("Test weekly alarm")
|
||||||
|
result = True
|
||||||
|
dt = dt_tuple((2023, 2, 28, 23, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # day is 1
|
||||||
|
alarm.set(EVERY_WEEK, day=2, sec=5) # Weekday
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should alarm on rollover from day 1 to 2
|
||||||
|
print("\x1b[32mWeek test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mWeek test 1 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
|
||||||
|
dt = dt_tuple((2023, 2, 27, 23, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # day is 0
|
||||||
|
alarm.set(EVERY_WEEK, day=2, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should not alarm on rollover from day 0 to 1
|
||||||
|
print("\x1b[91mWeek test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
else:
|
||||||
|
print("\x1b[32mWeek test 2 pass\x1b[39m")
|
||||||
|
|
||||||
|
print("Test monthly alarm")
|
||||||
|
dt = dt_tuple((2023, 2, 28, 23, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # day is 1
|
||||||
|
alarm.set(EVERY_MONTH, day=1, sec=5) # Day of month
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should alarm on rollover from 28th to 1st
|
||||||
|
print("\x1b[32mMonth test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mMonth test 1 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
|
||||||
|
dt = dt_tuple((2023, 2, 27, 23, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # day is 0
|
||||||
|
alarm.set(EVERY_MONTH, day=1, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should not alarm on rollover from day 27 to 28
|
||||||
|
print("\x1b[91mMonth test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
else:
|
||||||
|
print("\x1b[32mMonth test 2 pass\x1b[39m")
|
||||||
|
|
||||||
|
print("Test daily alarm")
|
||||||
|
dt = dt_tuple((2023, 2, 1, 23, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 23:59:50
|
||||||
|
alarm.set(EVERY_DAY, hr=0, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should alarm at 00:00:05
|
||||||
|
print("\x1b[32mDaily test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mDaily test 1 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
|
||||||
|
dt = dt_tuple((2023, 2, 1, 22, 59, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 22:59:50
|
||||||
|
alarm.set(EVERY_DAY, hr=0, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should not alarm at 22:00:05
|
||||||
|
print("\x1b[91mDaily test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
else:
|
||||||
|
print("\x1b[32mDaily test 2 pass\x1b[39m")
|
||||||
|
|
||||||
|
print("Test hourly alarm")
|
||||||
|
dt = dt_tuple((2023, 2, 1, 20, 9, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 20:09:50
|
||||||
|
alarm.set(EVERY_HOUR, min=10, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should alarm at xx:10:05
|
||||||
|
print("\x1b[32mDaily test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mDaily test 1 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
|
||||||
|
dt = dt_tuple((2023, 2, 1, 20, 29, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 20:29:50
|
||||||
|
alarm.set(EVERY_HOUR, min=10, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should not alarm at xx:30:05
|
||||||
|
print("\x1b[91mDaily test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
else:
|
||||||
|
print("\x1b[32mDaily test 2 pass\x1b[39m")
|
||||||
|
|
||||||
|
print("Test minute alarm")
|
||||||
|
dt = dt_tuple((2023, 2, 1, 20, 9, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 20:09:50
|
||||||
|
alarm.set(EVERY_MINUTE, sec=5)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should alarm at xx:xx:05
|
||||||
|
print("\x1b[32mMinute test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mMinute test 1 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if alarm.alno == 2:
|
||||||
|
print("Skipping minute test 2: requires seconds resolution unsupported by alarm2.")
|
||||||
|
else:
|
||||||
|
dt = dt_tuple((2023, 2, 1, 20, 29, 50, 0, 0))
|
||||||
|
d.set_time(dt) # 20:29:50
|
||||||
|
alarm.set(EVERY_MINUTE, sec=30)
|
||||||
|
alarm.clear()
|
||||||
|
if await wait_for_alarm(alarm, 20, 5): # Should not alarm at xx:xx:05
|
||||||
|
print("\x1b[91mMinute test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
else:
|
||||||
|
print("\x1b[32mMinute test 2 pass\x1b[39m")
|
||||||
|
|
||||||
|
if alarm.alno == 2:
|
||||||
|
print("Skipping seconds test: unsupported by alarm2.")
|
||||||
|
else:
|
||||||
|
print("Test seconds alarm (test takes 1 minute)")
|
||||||
|
dt = dt_tuple((2023, 2, 1, 20, 9, 20, 0, 0))
|
||||||
|
d.set_time(dt) # 20:09:20
|
||||||
|
alarm.set(EVERY_SECOND)
|
||||||
|
alarm_count = 0
|
||||||
|
t = time.ticks_ms()
|
||||||
|
while time.ticks_diff(time.ticks_ms(), t) < 60_000:
|
||||||
|
alarm.clear()
|
||||||
|
while not d.alarm1():
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
alarm_count += 1
|
||||||
|
if 59 <= alarm_count <= 61:
|
||||||
|
print("\x1b[32mSeconds test 1 pass\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mSeconds test 2 fail\x1b[39m")
|
||||||
|
result = False
|
||||||
|
alarm.enable(False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print("Testing alarm 1")
|
||||||
|
result = await test_alarm(d.alarm1)
|
||||||
|
print("Teting alarm 2")
|
||||||
|
result |= await test_alarm(d.alarm2)
|
||||||
|
if result:
|
||||||
|
print("\x1b[32mAll tests passed\x1b[39m")
|
||||||
|
else:
|
||||||
|
print("\x1b[91mSome tests failed\x1b[39m")
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue