code.py
  1  # SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import os
  6  import time
  7  import ipaddress
  8  import wifi
  9  import socketpool
 10  import busio
 11  import board
 12  import microcontroller
 13  import displayio
 14  import terminalio
 15  from adafruit_display_text import label
 16  import adafruit_displayio_ssd1306
 17  import adafruit_imageload
 18  from digitalio import DigitalInOut, Direction
 19  from adafruit_httpserver.server import HTTPServer
 20  from adafruit_httpserver.response import HTTPResponse
 21  from adafruit_onewire.bus import OneWireBus
 22  from adafruit_ds18x20 import DS18X20
 23  
 24  #  onboard LED setup
 25  led = DigitalInOut(board.LED)
 26  led.direction = Direction.OUTPUT
 27  led.value = False
 28  
 29  #  pin used for party parrot animation
 30  parrot_pin = DigitalInOut(board.GP10)
 31  parrot_pin.direction = Direction.OUTPUT
 32  parrot_pin.value = False
 33  
 34  # one-wire bus for DS18B20
 35  ow_bus = OneWireBus(board.GP6)
 36  
 37  # scan for temp sensor
 38  ds18 = DS18X20(ow_bus, ow_bus.scan()[0])
 39  
 40  #  function to convert celcius to fahrenheit
 41  def c_to_f(temp):
 42      temp_f = (temp * 9/5) + 32
 43      return temp_f
 44  
 45  #  i2c display setup
 46  displayio.release_displays()
 47  oled_reset = board.GP9
 48  
 49  # STEMMA I2C on picowbell
 50  i2c = busio.I2C(board.GP5, board.GP4)
 51  display_bus = displayio.I2CDisplay(i2c, device_address=0x3D, reset=oled_reset)
 52  
 53  WIDTH = 128
 54  HEIGHT = 64
 55  offset_y = 5
 56  
 57  display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)
 58  
 59  # default display group
 60  splash = displayio.Group()
 61  display.show(splash)
 62  
 63  #  connect to network
 64  print()
 65  print("Connecting to WiFi")
 66  connect_text = "Connecting..."
 67  connect_text_area = label.Label(
 68      terminalio.FONT, text=connect_text, color=0xFFFFFF, x=0, y=offset_y
 69  )
 70  splash.append(connect_text_area)
 71  
 72  #  set static IP address
 73  ipv4 =  ipaddress.IPv4Address("192.168.1.42")
 74  netmask =  ipaddress.IPv4Address("255.255.255.0")
 75  gateway =  ipaddress.IPv4Address("192.168.1.1")
 76  wifi.radio.set_ipv4_address(ipv4=ipv4,netmask=netmask,gateway=gateway)
 77  #  connect to your SSID
 78  wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
 79  
 80  print("Connected to WiFi")
 81  pool = socketpool.SocketPool(wifi.radio)
 82  server = HTTPServer(pool)
 83  
 84  #  variables for HTML
 85  #  comment/uncomment desired temp unit
 86  
 87  #  temp_test = str(ds18.temperature)
 88  #  unit = "C"
 89  temp_test = str(c_to_f(ds18.temperature))
 90  unit = "F"
 91  #  font for HTML
 92  font_family = "monospace"
 93  
 94  #  the HTML script
 95  #  setup as an f string
 96  #  this way, can insert string variables from code.py directly
 97  #  of note, use {{ and }} if something from html *actually* needs to be in brackets
 98  #  i.e. CSS style formatting
 99  def webpage():
100      html = f"""
101      <!DOCTYPE html>
102      <html>
103      <head>
104      <meta http-equiv="Content-type" content="text/html;charset=utf-8">
105      <meta name="viewport" content="width=device-width, initial-scale=1">
106      <style>
107      html{{font-family: {font_family}; background-color: lightgrey;
108      display:inline-block; margin: 0px auto; text-align: center;}}
109        h1{{color: deeppink; width: 200; word-wrap: break-word; padding: 2vh; font-size: 35px;}}
110        p{{font-size: 1.5rem; width: 200; word-wrap: break-word;}}
111        .button{{font-family: {font_family};display: inline-block;
112        background-color: black; border: none;
113        border-radius: 4px; color: white; padding: 16px 40px;
114        text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}}
115        p.dotted {{margin: auto;
116        width: 75%; font-size: 25px; text-align: center;}}
117      </style>
118      </head>
119      <body>
120      <title>Pico W HTTP Server</title>
121      <h1>Pico W HTTP Server</h1>
122      <br>
123      <p class="dotted">This is a Pico W running an HTTP server with CircuitPython.</p>
124      <br>
125      <p class="dotted">The current ambient temperature near the Pico W is
126      <span style="color: deeppink;">{temp_test}°{unit}</span></p><br>
127      <h1>Control the LED on the Pico W with these buttons:</h1><br>
128      <form accept-charset="utf-8" method="POST">
129      <button class="button" name="LED ON" value="ON" type="submit">LED ON</button></a></p></form>
130      <p><form accept-charset="utf-8" method="POST">
131      <button class="button" name="LED OFF" value="OFF" type="submit">LED OFF</button></a></p></form>
132      <h1>Party?</h>
133      <p><form accept-charset="utf-8" method="POST">
134      <button class="button" name="party" value="party" type="submit">PARTY!</button></a></p></form>
135      </body></html>
136      """
137      return html
138  
139  #  route default static IP
140  @server.route("/")
141  def base(request):  # pylint: disable=unused-argument
142      #  serve the HTML f string
143      #  with content type text/html
144      return HTTPResponse(content_type="text/html", body=webpage())
145  
146  #  if a button is pressed on the site
147  @server.route("/", "POST")
148  def buttonpress(request):
149      #  get the raw text
150      raw_text = request.raw_request.decode("utf8")
151      print(raw_text)
152      #  if the led on button was pressed
153      if "ON" in raw_text:
154          #  turn on the onboard LED
155          led.value = True
156      #  if the led off button was pressed
157      if "OFF" in raw_text:
158          #  turn the onboard LED off
159          led.value = False
160      #  if the party button was pressed
161      if "party" in raw_text:
162          #  toggle the parrot_pin value
163          parrot_pin.value = not parrot_pin.value
164      #  reload site
165      return HTTPResponse(content_type="text/html", body=webpage())
166  
167  print("starting server..")
168  # startup the server
169  try:
170      server.start(str(wifi.radio.ipv4_address))
171      print("Listening on http://%s:80" % wifi.radio.ipv4_address)
172  #  if the server fails to begin, restart the pico w
173  except OSError:
174      time.sleep(5)
175      print("restarting..")
176      microcontroller.reset()
177  ping_address = ipaddress.ip_address("8.8.4.4")
178  
179  #  text objects for screen
180  #  connected to SSID text
181  connect_text_area.text = "Connected to:"
182  ssid_text = "%s" % os.getenv('WIFI_SSID')
183  ssid_text_area = label.Label(
184      terminalio.FONT, text=ssid_text, color=0xFFFFFF, x=0, y=offset_y+15
185  )
186  splash.append(ssid_text_area)
187  #  display ip address
188  ip_text = "IP: %s" % wifi.radio.ipv4_address
189  ip_text_area = label.Label(
190      terminalio.FONT, text=ip_text, color=0xFFFFFF, x=0, y=offset_y+30
191  )
192  splash.append(ip_text_area)
193  #  display temp reading
194  temp_text = "Temperature: %.02f F" % float(temp_test)
195  temp_text_area = label.Label(
196      terminalio.FONT, text=temp_text, color=0xFFFFFF, x=0, y=offset_y+45
197  )
198  splash.append(temp_text_area)
199  
200  #  party parrot display group
201  parrot_group = displayio.Group()
202  #  load in party parrot bitmap
203  parrot_bit, parrot_pal = adafruit_imageload.load("/partyParrots64.bmp",
204                                                   bitmap=displayio.Bitmap,
205                                                   palette=displayio.Palette)
206  parrot_grid = displayio.TileGrid(parrot_bit, pixel_shader=parrot_pal,
207                                   width=1, height=1,
208                                   tile_height=64, tile_width=64,
209                                   default_tile=1,
210                                   x=32, y=0)
211  parrot_group.append(parrot_grid)
212  
213  clock = time.monotonic() #  time.monotonic() holder for server ping
214  parrot = False #  parrot state
215  party = 0 #  time.monotonic() holder for party parrot
216  p = 0 #  index for tilegrid
217  
218  while True:
219      try:
220          #  every 30 seconds, ping server & update temp reading
221          if (clock + 30) < time.monotonic():
222              if wifi.radio.ping(ping_address) is None:
223                  connect_text_area.text = "Disconnected!"
224                  ssid_text_area.text = None
225                  print("lost connection")
226              else:
227                  connect_text_area.text = "Connected to:"
228                  ssid_text_area.text = "%s" % os.getenv('WIFI_SSID')
229                  print("connected")
230              clock = time.monotonic()
231              #  comment/uncomment for desired units
232              #  temp_test = str(ds18.temperature)
233              temp_test = str(c_to_f(ds18.temperature))
234              temp_text_area.text = "Temperature: %d F" % temp_test
235  
236          #if parrot is True:
237          if parrot_pin.value is True:
238              #  switch to party parrot display group
239              display.show(parrot_group)
240              if (party + 0.1) < time.monotonic():
241                  #  the party parrot animation cycles
242                  parrot_grid[0] = p
243                  #  p is the tilegrid index location
244                  p = (p + 1) % 10
245                  party = time.monotonic()
246          #  if it isn't a party
247          else:
248              #  show default display with info
249              display.show(splash)
250          #  poll the server for incoming/outgoing requests
251          server.poll()
252      # pylint: disable=broad-except
253      except Exception as e:
254          print(e)
255          continue