kopia lustrzana https://github.com/peterhinch/micropython-samples
Porównaj commity
2 Commity
4691354a40
...
6c478601a6
Autor | SHA1 | Data |
---|---|---|
peterhinch | 6c478601a6 | |
peterhinch | 14ebf88797 |
189
DS3231/README.md
189
DS3231/README.md
|
@ -5,23 +5,190 @@ 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
|
||||
accurate time to platforms lacking a good RTC (notably the ESP8266).
|
||||
|
||||
Two drivers are provided:
|
||||
1. `ds3231_port.py` A multi-platform driver.
|
||||
2. `ds3231_pb.py` A Pyboard-specific driver with RTC calibration facility. For
|
||||
Three drivers are provided:
|
||||
1. `ds3231_gen.py` General purpose portable driver supporting alarms.
|
||||
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.
|
||||
|
||||
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
|
||||
not supplied on the breakout board.
|
||||
|
||||
Both divers use edge detection to achieve millisecond-level precision from the
|
||||
DS3231. This enables relatively rapid accuracy testing of the platform's RTC,
|
||||
and fast calibration of the Pyboard's RTC. To quantify this, a sufficiently
|
||||
precise value of calibration may be acquired in 5-10 minutes.
|
||||
Drivers 2 and 3 use edge detection to achieve millisecond-level precision from
|
||||
the DS3231. This enables relatively rapid accuracy testing of the platform's
|
||||
RTC, and fast calibration of the Pyboard's RTC. To quantify this, a
|
||||
sufficiently precise value of calibration may be acquired in 5-10 minutes.
|
||||
|
||||
# Datetime tuples
|
||||
|
||||
MicroPython currently enjoys three formats. The first is that used by the
|
||||
`time` module in `localtime` and `gmtime`. This is
|
||||
`(year, month, mday, hour, minute, second, weekday, yearday)`
|
||||
with the meaning of each field as described in the
|
||||
[MP official docs](http://docs.micropython.org/en/latest/library/time.html#functions) and
|
||||
[CPython docs](https://docs.python.org/3/library/time.html#time.struct_time).
|
||||
**These drivers use this format as it is the CPython standard.**
|
||||
|
||||
The micropython RTC class uses [this format](http://docs.micropython.org/en/latest/library/machine.RTC.html#machine.RTC.init)
|
||||
in its `__init__` method:
|
||||
`(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])`
|
||||
and this format in its `datetime` method:
|
||||
`(year, month, mday, weekday, hours, minutes, seconds, subseconds)`
|
||||
It is to be hoped that some standardisation will occur. Please check official
|
||||
docs for any changes.
|
||||
|
||||
###### [Main README](../README.md)
|
||||
|
||||
# 1. The multi-platform driver
|
||||
# 1. General purpose driver ds3231_gen
|
||||
|
||||
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()`. Returns the DS3231 time as a datetime tuple with
|
||||
`yearday=0`.
|
||||
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, in which case alarms continue
|
||||
to operate.
|
||||
|
||||
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) # Alarm on the half minute
|
||||
```
|
||||
If a power outage occurs here the following code demonstrates that 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.
|
||||
|
||||
#### Setting system RTC
|
||||
|
||||
Note that the DS3231 driver uses the CPython standard datetime tuple:
|
||||
`(year, month, mday, hour, minute, second, weekday, yearday)`
|
||||
Currently the RTC `datetime` method uses a different format.
|
||||
|
||||
The system RTC may be set from the DS3231 as follows:
|
||||
```python
|
||||
from machine import SoftI2C, Pin, RTC
|
||||
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)
|
||||
rtc = RTC()
|
||||
YY, MM, DD, hh, mm, ss, wday, _ = d.get_time()
|
||||
rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
|
||||
```
|
||||
The following can be used to set the RTC to better than +-1s accuracy. Though
|
||||
the DS3231 has only 1s resolution, setting the RTC on a transition of the
|
||||
seconds value minimises error.
|
||||
```python
|
||||
t = d.get_time() # As per above, d is the DS3231 instance
|
||||
while t == d.get_time()[5]: # Wait for change in seconds
|
||||
pass
|
||||
YY, MM, DD, hh, mm, ss, wday, _ = t # Set time now
|
||||
rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
|
||||
```
|
||||
|
||||
#### Application: micropower systems
|
||||
|
||||
The DS3231 alarm pin may be used to control the application of power to the
|
||||
MicroPython host and peripheral devices. This may be done with a PNP transistor
|
||||
driven (via suitable resistors) from the alarm output. When in an alarm state,
|
||||
power is applied to the system. The DS3231 is set to alarm at the required
|
||||
interval - say at 00:03:00 every Sunday. On startup the system takes readings
|
||||
and logs them or reports them via MQTT. Its final act is to issue
|
||||
```python
|
||||
ds3231.alarm1.clear()
|
||||
```
|
||||
which turns off the MOSFET and the transistor and powers down the system.
|
||||
Current in the OFF state will be very much less than 1μA.
|
||||
|
||||
# 2. Portable driver ds3231_port
|
||||
|
||||
This can use soft I2C so any pins may be used.
|
||||
|
||||
|
@ -47,7 +214,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
|
||||
will vary.
|
||||
|
||||
## 1.1 The DS3231 class
|
||||
## 2.1 The DS3231 class
|
||||
|
||||
Constructor:
|
||||
This takes one mandatory argument, an initialised I2C bus.
|
||||
|
@ -69,7 +236,7 @@ Public methods:
|
|||
devices with poor RTC's (e.g. ESP8266).
|
||||
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
|
||||
supports the Pyboard 1.x and Pyboard D. Note that the RTC on the Pyboard D is
|
||||
|
@ -102,7 +269,7 @@ ds3231.calibrate()
|
|||
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.
|
||||
|
||||
## 2.1 The DS3231 class
|
||||
## 3.1 The DS3231 class
|
||||
|
||||
Constructor:
|
||||
This takes one mandatory argument, an I2C bus instantiated using the `machine`
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# 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, data=bytearray(7)):
|
||||
def bcd2dec(bcd): # Strip MSB
|
||||
return ((bcd & 0x70) >> 4) * 10 + (bcd & 0x0F)
|
||||
|
||||
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
|
||||
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