code.py
  1  # SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
  2  # SPDX-License-Identifier: MIT
  3  
  4  import time
  5  import json
  6  import supervisor
  7  import simpleio
  8  import vectorio
  9  import board
 10  import terminalio
 11  import rtc
 12  import socketpool
 13  import wifi
 14  import displayio
 15  import adafruit_ntp
 16  from adafruit_display_text import bitmap_label,  wrap_text_to_lines
 17  from adafruit_bitmap_font import bitmap_font
 18  from adafruit_azureiot import IoTCentralDevice
 19  import adafruit_bme680
 20  from adafruit_lc709203f import LC709203F, PackSize
 21  
 22  
 23  # Get wifi details and more from a secrets.py file
 24  try:
 25      from secrets import secrets
 26  except ImportError:
 27      print("WiFi secrets are kept in secrets.py, please add them there!")
 28      raise
 29  
 30  print("Connecting to WiFi...")
 31  wifi.radio.connect(secrets["ssid"], secrets["password"])
 32  
 33  print("Connected to WiFi!")
 34  
 35  #  ntp clock - update tz_offset to your timezone
 36  pool = socketpool.SocketPool(wifi.radio)
 37  ntp = adafruit_ntp.NTP(pool, tz_offset=-4)
 38  rtc.RTC().datetime = ntp.datetime
 39  
 40  if time.localtime().tm_year < 2022:
 41      print("Setting System Time in UTC")
 42      rtc.RTC().datetime = ntp.datetime
 43  
 44  else:
 45      print("Year seems good, skipping set time.")
 46  
 47  cal = ntp.datetime
 48  year = cal[0]
 49  mon = cal[1]
 50  day = cal[2]
 51  hour = cal[3]
 52  minute = cal[4]
 53  
 54  # To use Azure IoT Central, you will need to create an IoT Central app.
 55  # You can either create a free tier app that will live for 7 days without an Azure subscription,
 56  # Or a standard tier app that will last for ever with an Azure subscription.
 57  # The standard tiers are free for up to 2 devices
 58  #
 59  # If you don't have an Azure subscription:
 60  #
 61  # If you are a student, head to https://aka.ms/FreeStudentAzure and sign up, validating with your
 62  #  student email address. This will give you $100 of Azure credit and free tiers of a load of
 63  #  service, renewable each year you are a student
 64  #
 65  # If you are not a student, head to https://aka.ms/FreeAz and sign up to get $200 of credit for 30
 66  #  days, as well as free tiers of a load of services
 67  #
 68  # Create an Azure IoT Central app by following these
 69  # instructions: https://aka.ms/CreateIoTCentralApp
 70  # Add a device template with telemetry, properties and commands, as well as a view to visualize the
 71  # telemetry and execute commands, and a form to set properties.
 72  #
 73  # Next create a device using the device template, and select Connect to get the
 74  # device connection details.
 75  # Add the connection details to your secrets.py file, using the following values:
 76  #
 77  # 'id_scope' - the devices ID scope
 78  # 'device_id' - the devices device id
 79  # 'device_sas_key' - the devices primary key
 80  #
 81  # The adafruit-circuitpython-azureiot library depends on the following libraries:
 82  #
 83  # From the Adafruit CircuitPython Bundle
 84  # (https://github.com/adafruit/Adafruit_CircuitPython_Bundle):
 85  # * adafruit-circuitpython-minimqtt
 86  # * adafruit-circuitpython-requests
 87  # Create sensor object, communicating over the board's default I2C bus
 88  i2c = board.I2C()  # uses board.SCL and board.SDA
 89  bme680 = adafruit_bme680.Adafruit_BME680_I2C(i2c, debug=False)
 90  
 91  # change this to match the location's pressure (hPa) at sea level
 92  bme680.sea_level_pressure = 1013.25
 93  
 94  # Create an IoT Hub device client and connect
 95  esp = None
 96  pool = socketpool.SocketPool(wifi.radio)
 97  device = IoTCentralDevice(
 98      pool, esp, secrets["id_scope"], secrets["device_id"], secrets["device_primary_key"]
 99  )
100  
101  print("Connecting to Azure IoT Central...")
102  device.connect()
103  
104  print("Connected to Azure IoT Central!")
105  
106  temperature_offset = -5
107  
108  # Create sensor object, using the board's default I2C bus.
109  battery_monitor = LC709203F(board.I2C())
110  
111  # Update to match the mAh of your battery for more accurate readings.
112  # Can be MAH100, MAH200, MAH400, MAH500, MAH1000, MAH2000, MAH3000.
113  # Choose the closest match. Include "PackSize." before it, as shown.
114  battery_monitor.pack_size = PackSize.MAH2000
115  
116  temp = int((bme680.temperature * 9/5) + (32 + temperature_offset))
117  humidity = int(bme680.relative_humidity)
118  pressure = int(bme680.pressure)
119  battery = battery_monitor.cell_percent
120  
121  #  display setup
122  display = board.DISPLAY
123  
124  palette0 = displayio.Palette(2)
125  palette0[0] = 0x00FF00
126  palette0[1] = 0xFF0000
127  
128  #  load bitmap
129  bitmap = displayio.OnDiskBitmap("/bmeTFT.bmp")
130  tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
131  group = displayio.Group()
132  group.append(tile_grid)
133  #  rectangle for battery life monitor
134  #  vectorio allows for resizing in the loop
135  rect = vectorio.Rectangle(pixel_shader=palette0, width=22, height=10, x=12, y=116, color_index = 0)
136  group.append(rect)
137  #  bitmap font
138  font_file = "/roundedHeavy-26.bdf"
139  font = bitmap_font.load_font(font_file)
140  #  text elements
141  temp_text = bitmap_label.Label(font, text="%0.1f° F" % temp, x=20, y=80, color=0xFFFFFF)
142  humid_text = bitmap_label.Label(font, text="%0.1f %%" % humidity, x=95, y=80, color=0xFFFFFF)
143  press_text = bitmap_label.Label(font, text="%0.2f" % pressure, x=170, y=80, color=0xFFFFFF)
144  time_text = bitmap_label.Label(terminalio.FONT,
145              text="\n".join(wrap_text_to_lines
146              ("Data sent on %s/%s/%s at %s:%s" % (mon,day,year,hour,minute), 20)),
147              x=125, y=105, color=0xFFFFFF)
148  group.append(temp_text)
149  group.append(humid_text)
150  group.append(press_text)
151  group.append(time_text)
152  display.show(group)
153  
154  #  clock to count down to sending data to Azure
155  azure_clock = 500
156  #  clock to count down to updating TFT
157  feather_clock = 30
158  
159  
160  while True:
161      try:
162  		#  read BME sensor
163          temp = int((bme680.temperature * 9/5) + (32 + temperature_offset))
164          humidity = int(bme680.relative_humidity)
165          pressure = int(bme680.pressure)
166  		#  log battery %
167          battery = battery_monitor.cell_percent
168  		#  map range of battery charge to rectangle size on screen
169          battery_display = round(simpleio.map_range(battery, 0, 100, 0, 22))
170  		#  update rectangle to reflect battery charge
171          rect.width = int(battery_display)
172  		#  if below 20%, change rectangle color to red
173          if battery_monitor.cell_percent < 20:
174              rect.color_index = 1
175  		#  when the azure clock runs out
176          if azure_clock > 500:
177              print("getting ntp date/time")
178              cal = ntp.datetime
179              year = cal[0]
180              mon = cal[1]
181              day = cal[2]
182              hour = cal[3]
183              minute = cal[4]
184              time.sleep(2)
185              print("getting msg")
186  			#  pack message
187              message = {"Temperature": temp,
188                         "Humidity": humidity,
189                         "Pressure": pressure,
190                         "BatteryPercent": battery}
191              print("sending json")
192              device.send_telemetry(json.dumps(message))
193              print("data sent")
194              clock_view = "%s:%s" % (hour, minute)
195              if minute < 10:
196                  clock_view = "%s:0%s" % (hour, minute)
197              print("updating time text")
198              time_text.text="\n".join(wrap_text_to_lines
199              ("Data sent on %s/%s/%s at %s" % (mon,day,year,clock_view), 20))
200  			#  reset azure clock
201              azure_clock = 0
202  		#  when the feather clock runs out
203          if feather_clock > 30:
204              print("updating screen")
205              temp_text.text = "%0.1f° F" % temp
206              humid_text.text = "%0.1f %%" % humidity
207              press_text.text = "%0.2f" % pressure
208  			#  reset feather clock
209              feather_clock = 0
210  		#  if no clocks are running out
211  		#  increase counts by 1
212          else:
213              feather_clock += 1
214              azure_clock += 1
215  		#  ping azure
216          device.loop()
217  	#  if something disrupts the loop, reconnect
218      # pylint: disable=broad-except
219      except (ValueError, RuntimeError, OSError, ConnectionError) as e:
220          print("Network error, reconnecting\n", str(e))
221          supervisor.reload()
222          continue
223  	#  delay
224      time.sleep(1)
225      print(azure_clock)