/ 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