/ adafruit_register / i2c_bcd_alarm.py
i2c_bcd_alarm.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2016 Scott Shawcroft for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  # pylint: disable=too-few-public-methods
 23  
 24  """
 25  `adafruit_register.i2c_bcd_alarm`
 26  ====================================================
 27  
 28  Binary Coded Decimal alarm register
 29  
 30  * Author(s): Scott Shawcroft
 31  """
 32  
 33  __version__ = "0.0.0-auto.0"
 34  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
 35  
 36  import time
 37  
 38  
 39  def _bcd2bin(value):
 40      """Convert binary coded decimal to Binary
 41  
 42      :param value: the BCD value to convert to binary (required, no default)
 43      """
 44      return value - 6 * (value >> 4)
 45  
 46  
 47  def _bin2bcd(value):
 48      """Convert a binary value to binary coded decimal.
 49  
 50      :param value: the binary value to convert to BCD. (required, no default)
 51      """
 52      return value + 6 * (value // 10)
 53  
 54  
 55  ALARM_COMPONENT_DISABLED = 0x80
 56  FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"]
 57  
 58  
 59  class BCDAlarmTimeRegister:
 60      """
 61      Alarm date and time register using binary coded decimal structure.
 62  
 63      The byte order of the registers must* be: [second], minute, hour, day,
 64      weekday. Each byte must also have a high enable bit where 1 is disabled and
 65      0 is enabled.
 66  
 67      * If weekday_shared is True, then weekday and day share a register.
 68      * If has_seconds is True, then there is a seconds register.
 69  
 70      Values are a tuple of (`time.struct_time`, `str`) where the struct represents
 71      a date and time that would alarm. The string is the frequency:
 72  
 73      * "secondly", once a second (only if alarm has_seconds)
 74      * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0)
 75      * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match
 76      * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match
 77      * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
 78      * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
 79  
 80      :param int register_address: The register address to start the read
 81      :param bool has_seconds: True if the alarm can happen minutely.
 82      :param bool weekday_shared: True if weekday and day share the same register
 83      :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
 84        week (Monday)
 85      """
 86  
 87      # Defaults are based on alarm1 of the DS3231.
 88      def __init__(
 89          self, register_address, has_seconds=True, weekday_shared=True, weekday_start=1
 90      ):
 91          buffer_size = 5
 92          if weekday_shared:
 93              buffer_size -= 1
 94          if has_seconds:
 95              buffer_size += 1
 96          self.has_seconds = has_seconds
 97          self.buffer = bytearray(buffer_size)
 98          self.buffer[0] = register_address
 99          self.weekday_shared = weekday_shared
100          self.weekday_start = weekday_start
101  
102      def __get__(self, obj, objtype=None):
103          # Read the alarm register.
104          with obj.i2c_device as i2c:
105              i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
106  
107          frequency = None
108          i = 1
109          seconds = 0
110          if self.has_seconds:
111              if (self.buffer[1] & 0x80) != 0:
112                  frequency = "secondly"
113              else:
114                  frequency = "minutely"
115                  seconds = _bcd2bin(self.buffer[1] & 0x7F)
116              i = 2
117          minute = 0
118          if (self.buffer[i] & 0x80) == 0:
119              frequency = "hourly"
120              minute = _bcd2bin(self.buffer[i] & 0x7F)
121  
122          hour = 0
123          if (self.buffer[i + 1] & 0x80) == 0:
124              frequency = "daily"
125              hour = _bcd2bin(self.buffer[i + 1] & 0x7F)
126  
127          mday = None
128          wday = None
129          if (self.buffer[i + 2] & 0x80) == 0:
130              # day of the month
131              if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0:
132                  frequency = "monthly"
133                  mday = _bcd2bin(self.buffer[i + 2] & 0x3F)
134              else:  # weekday
135                  frequency = "weekly"
136                  wday = _bcd2bin(self.buffer[i + 2] & 0x3F) - self.weekday_start
137  
138          # weekday
139          if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0:
140              frequency = "monthly"
141              mday = _bcd2bin(self.buffer[i + 3] & 0x7F)
142  
143          if mday is not None:
144              wday = (mday - 2) % 7
145          elif wday is not None:
146              mday = wday + 2
147          else:
148              # Jan 1, 2017 was a Sunday (6)
149              wday = 6
150              mday = 1
151  
152          return (
153              time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)),
154              frequency,
155          )
156  
157      def __set__(self, obj, value):
158          if len(value) != 2:
159              raise ValueError("Value must be sequence of length two")
160          # Turn all components off by default.
161          for i in range(len(self.buffer) - 1):
162              self.buffer[i + 1] = ALARM_COMPONENT_DISABLED
163          frequency_name = value[1]
164          error_message = "%s is not a supported frequency" % frequency_name
165          if frequency_name not in FREQUENCY:
166              raise ValueError(error_message)
167  
168          frequency = FREQUENCY.index(frequency_name)
169          if frequency <= 1 and not self.has_seconds:
170              raise ValueError(error_message)
171  
172          # i is the index of the minute byte
173          i = 2 if self.has_seconds else 1
174  
175          if frequency > 0 and self.has_seconds:  # minutely at least
176              self.buffer[1] = _bin2bcd(value[0].tm_sec)
177  
178          if frequency > 1:  # hourly at least
179              self.buffer[i] = _bin2bcd(value[0].tm_min)
180  
181          if frequency > 2:  # daily at least
182              self.buffer[i + 1] = _bin2bcd(value[0].tm_hour)
183  
184          if value[1] == "weekly":
185              if self.weekday_shared:
186                  self.buffer[i + 2] = (
187                      _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40
188                  )
189              else:
190                  self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start)
191          elif value[1] == "monthly":
192              self.buffer[i + 2] = _bin2bcd(value[0].tm_mday)
193  
194          with obj.i2c_device:
195              obj.i2c_device.write(self.buffer)