/ Jar_Minder_v2 / code.py
code.py
  1  # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import digitalio
  6  import board
  7  
  8  done = digitalio.DigitalInOut(board.A4)
  9  done.direction = digitalio.Direction.OUTPUT
 10  done.value = False
 11  
 12  #pylint: disable=wrong-import-position,wrong-import-order
 13  import time
 14  import pwmio
 15  import busio
 16  from adafruit_epd.epd import Adafruit_EPD
 17  from adafruit_epd.il0373 import Adafruit_IL0373
 18  import adafruit_si7021
 19  import font
 20  
 21  #--------------------------------------------------
 22  # Setup
 23  
 24  spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
 25  ecs = digitalio.DigitalInOut(board.D11)
 26  dc = digitalio.DigitalInOut(board.D10)
 27  srcs = digitalio.DigitalInOut(board.D9)
 28  rst = digitalio.DigitalInOut(board.D6)
 29  busy = digitalio.DigitalInOut(board.D12)
 30  display = Adafruit_IL0373(152, 152, rst, dc, busy, srcs, ecs, spi)
 31  
 32  i2c = busio.I2C(board.SCL, board.SDA)
 33  sensor = adafruit_si7021.SI7021(i2c)
 34  
 35  ON = 2**15
 36  OFF = 0
 37  
 38  buzzer = pwmio.PWMOut(board.D5, variable_frequency=True)
 39  buzzer.duty_cycle = OFF
 40  
 41  silence_button = digitalio.DigitalInOut(board.A5)
 42  silence_button.direction = digitalio.Direction.INPUT
 43  silence_button.pull = digitalio.Pull.UP
 44  
 45  #--------------------------------------------------
 46  # Default parameter values
 47  
 48  settings = {}
 49  settings['temperature_range'] = (15, 30)
 50  settings['humidity_range'] = (60, 70)
 51  settings['title'] = 'Weed Minder'
 52  settings['alarm_frequency'] = 4000
 53  settings['alarm_number_of_beeps'] = 3
 54  settings['alarm_seconds_beep_on'] = 0.5
 55  settings['alarm_seconds_between_beeps'] = 0.5
 56  settings['alarm_seconds_between_alarms'] = 5.0
 57  settings['alarm_timeout'] = 60.0
 58  
 59  #--------------------------------------------------
 60  # Support functions
 61  
 62  def render_character(x, y, ch, color=Adafruit_EPD.BLACK):
 63      """Render a character.
 64      :param int x: horizontal position of the left edge of the character
 65      :param int y: vertical position of the top edge of the character
 66      :param str ch: a single character string to be displayed
 67      :param Adafruit_EPD.* color: BLACK or RED, background is always white
 68      """
 69  
 70      if x < 144 and y < 144:
 71          bitmap = font.bitmaps[ord(ch)]
 72          for row_num in range(8):
 73              row = bitmap[row_num]
 74              for column_num in range(8):
 75                  if (row & 1) == 0:
 76                      display.draw_pixel(x + column_num, y + row_num, Adafruit_EPD.WHITE)
 77                  else:
 78                      display.draw_pixel(x + column_num, y + row_num, color)
 79                  row >>= 1
 80  
 81  
 82  def render_string(x, y, s, color=Adafruit_EPD.BLACK):
 83      """Render a string.
 84      :param int x: horizontal position of the left edge of the string
 85      :param int y: vertical position of the top edge of the string
 86      :param str ch: a string to be displayed
 87      :param Adafruit_EPD.* color: BLACK or RED, background is always white
 88      """
 89  
 90      x_pos = x
 91      for ch in s:
 92          render_character(x_pos, y, ch, color)
 93          x_pos += 8
 94  
 95  
 96  def centered(s):
 97      """Computer the X position to center a string.
 98      :param str s: the string to center
 99      """
100  
101      return 75 - (4 * len(s))
102  
103  
104  def to_int_tuple(a):
105      """Convert an array of strings to a tuple of ints.
106      :param [int] a: array of strings to convert
107      """
108  
109      return tuple([int(x.strip()) for x in a])
110  
111  
112  def check_for_push(button, duration):
113      """Wait for a time, regularly checking for a button push.
114      :param DigitalInOut button: the button input to check
115      :param float duration: seconds to wait
116      Return True if the button is pushed, False if the time passes
117      """
118  
119      stop_at = time.monotonic() + duration
120      while time.monotonic() < stop_at:
121          if not button.value:
122              return True
123          time.sleep(0.1)
124      return False
125  
126  
127  def sound_alarm():
128      """Sound the alarm based on the settings."""
129  
130      buzzer.frequency = settings['alarm_frequency']
131      for _ in range(settings['alarm_number_of_beeps']):
132          buzzer.duty_cycle = ON
133          if check_for_push(silence_button, settings['alarm_seconds_beep_on']):
134              buzzer.duty_cycle = OFF
135              return True
136          buzzer.duty_cycle = OFF
137          if check_for_push(silence_button, settings['alarm_seconds_between_beeps']):
138              return True
139      return False
140  
141  
142  
143  def out_of_range(t, h):
144      """Check if either temperature and humidity is out of range.
145      :param float t: temperature reading
146      :param float h: humidity reading
147      """
148  
149      if t < settings['temperature_range'][0]:
150          return True
151      if t > settings['temperature_range'][1]:
152          return True
153      if h < settings['humidity_range'][0]:
154          return True
155      if h > settings['humidity_range'][1]:
156          return True
157      return False
158  
159  
160  #--------------------------------------------------
161  # Handle edit mode: allow the user to edit description and settings
162  # This is done by waking the device while holding the silence button pressed
163  # A low beep indicated entry and the display will indicate it as well
164  
165  if not silence_button.value:
166      buzzer.frequency = 440
167      buzzer.duty_cycle = ON
168      time.sleep(0.5)
169      buzzer.duty_cycle = OFF
170      display.clear_buffer()
171      render_string(39, 64, 'EDIT MODE')
172      display.display()
173      while not silence_button.value:       # wait for button to be released
174          pass
175      while silence_button.value:           # wait for button to be pressed
176          pass
177      buzzer.duty_cycle = ON
178      time.sleep(0.5)
179      buzzer.duty_cycle = OFF
180  
181  # Pressing the silence button again reverts to monitor mode
182  # A low beep indicates this
183  
184  #--------------------------------------------------
185  # Main script
186  
187  # Read settings file into setting dictionary
188  with open('settings.txt', 'r') as f:
189      for line in f:
190          key, value = [x.strip() for x in line.strip().split(':')]
191          values = value.split('-')
192          if key == 'temperature_range':
193              setting = to_int_tuple(values)
194          elif key == 'humidity_range':
195              setting = to_int_tuple(values)
196          elif key == 'title':
197              setting = value
198          elif key == 'alarm_frequency':
199              setting = int(value)
200          elif key == 'alarm_number_of_beeps':
201              setting = int(value)
202          elif key == 'alarm_seconds_beep_on':
203              setting = float(value)
204          elif key == 'alarm_seconds_between_beeps':
205              setting = float(value)
206          elif key == 'alarm_timeout':
207              setting = float(value)
208          settings[key] = setting
209  
210          # Get text
211  with open('description.txt', 'r') as f:
212      text = [line.strip() for line in f]
213  
214  display.clear_buffer()
215  render_string(centered(settings['title']), 12, settings['title'])
216  
217  # Display text
218  row_index = 64
219  for line in text:
220      if row_index > 112:
221          break
222      render_string(centered(line), row_index, line)
223      row_index += 10
224  
225  temperature = int(sensor.temperature)
226  humidity = int(sensor.relative_humidity)
227  render_string(8, 32, '{0:2d} C'.format(temperature))
228  render_string(112, 32, '{0:2d} %'.format(humidity))
229  
230  if temperature < settings['temperature_range'][0]:
231      temperature_message = 'LOW TEMPERATURE'
232  elif temperature > settings['temperature_range'][1]:
233      temperature_message = 'HIGH TEMPERATURE'
234  else:
235      temperature_message = ''
236  
237  if humidity < settings['humidity_range'][0]:
238      humidity_message = 'LOW HUMIDITY'
239  elif humidity > settings['humidity_range'][1]:
240      humidity_message = 'HIGH HUMIDITY'
241  else:
242      humidity_message = ''
243  
244  if temperature_message:
245      render_string(centered(temperature_message), 122, temperature_message, Adafruit_EPD.RED)
246  if humidity_message:
247      render_string(centered(humidity_message), 132, humidity_message, Adafruit_EPD.RED)
248  
249  if temperature_message or humidity_message:
250      display.fill_rect(0, 0, 152, 10, Adafruit_EPD.RED)
251      display.fill_rect(0, 142, 152, 10, Adafruit_EPD.RED)
252  
253  display.display()
254  
255  timeout = time.monotonic() + settings['alarm_timeout']
256  
257  while out_of_range(temperature, humidity) and time.monotonic() < timeout:
258      if sound_alarm():
259          break
260      if check_for_push(silence_button, settings['alarm_seconds_between_alarms']):
261          break
262  
263  done.value = True