PiClock project

This commit is contained in:
2024-01-03 09:59:59 +01:00
parent edc186dbd4
commit ca21954980
9 changed files with 1737 additions and 0 deletions

3
.micropico Normal file
View File

@@ -0,0 +1,3 @@
{
"info": "This file is just used to identify a project folder."
}

119
ht16k33/ht16k33.py Normal file
View File

@@ -0,0 +1,119 @@
class HT16K33:
"""
A simple, generic driver for the I2C-connected Holtek HT16K33 controller chip.
This release supports MicroPython and CircuitPython
Bus: I2C
Author: Tony Smith (@smittytone)
License: MIT
Copyright: 2023
"""
# *********** CONSTANTS **********
HT16K33_GENERIC_DISPLAY_ON = 0x81
HT16K33_GENERIC_DISPLAY_OFF = 0x80
HT16K33_GENERIC_SYSTEM_ON = 0x21
HT16K33_GENERIC_SYSTEM_OFF = 0x20
HT16K33_GENERIC_DISPLAY_ADDRESS = 0x00
HT16K33_GENERIC_CMD_BRIGHTNESS = 0xE0
HT16K33_GENERIC_CMD_BLINK = 0x81
# *********** PRIVATE PROPERTIES **********
i2c = None
address = 0
brightness = 15
flash_rate = 0
# *********** CONSTRUCTOR **********
def __init__(self, i2c, i2c_address):
assert 0x00 <= i2c_address < 0x80, "ERROR - Invalid I2C address in HT16K33()"
self.i2c = i2c
self.address = i2c_address
self.power_on()
# *********** PUBLIC METHODS **********
def set_blink_rate(self, rate=0):
"""
Set the display's flash rate.
Only four values (in Hz) are permitted: 0, 2, 1, and 0,5.
Args:
rate (int): The chosen flash rate. Default: 0Hz (no flash).
"""
assert rate in (0, 0.5, 1, 2), "ERROR - Invalid blink rate set in set_blink_rate()"
self.blink_rate = rate & 0x03
self._write_cmd(self.HT16K33_GENERIC_CMD_BLINK | rate << 1)
def set_brightness(self, brightness=15):
"""
Set the display's brightness (ie. duty cycle).
Brightness values range from 0 (dim, but not off) to 15 (max. brightness).
Args:
brightness (int): The chosen flash rate. Default: 15 (100%).
"""
if brightness < 0 or brightness > 15: brightness = 15
self.brightness = brightness
self._write_cmd(self.HT16K33_GENERIC_CMD_BRIGHTNESS | brightness)
def draw(self):
"""
Writes the current display buffer to the display itself.
Call this method after updating the buffer to update
the LED itself.
"""
self._render()
def update(self):
"""
Alternative for draw() for backwards compatibility
"""
self._render()
def clear(self):
"""
Clear the buffer.
Returns:
The instance (self)
"""
for i in range(0, len(self.buffer)): self.buffer[i] = 0x00
return self
def power_on(self):
"""
Power on the controller and display.
"""
self._write_cmd(self.HT16K33_GENERIC_SYSTEM_ON)
self._write_cmd(self.HT16K33_GENERIC_DISPLAY_ON)
def power_off(self):
"""
Power on the controller and display.
"""
self._write_cmd(self.HT16K33_GENERIC_DISPLAY_OFF)
self._write_cmd(self.HT16K33_GENERIC_SYSTEM_OFF)
# ********** PRIVATE METHODS **********
def _render(self):
"""
Write the display buffer out to I2C
"""
buffer = bytearray(len(self.buffer) + 1)
buffer[1:] = self.buffer
buffer[0] = 0x00
self.i2c.writeto(self.address, bytes(buffer))
def _write_cmd(self, byte):
"""
Writes a single command to the HT16K33. A private method.
"""
self.i2c.writeto(self.address, bytes([byte]))

View File

@@ -0,0 +1,162 @@
# Import the base class
from ht16k33.ht16k33 import HT16K33
class HT16K33SegmentBig(HT16K33):
"""
Micro/Circuit Python class for the Adafruit 1.2-in 4-digit,
7-segment LED matrix backpack and equivalent Featherwing.
Bus: I2C
Author: Tony Smith (@smittytone)
License: MIT
Copyright: 2023
"""
# *********** CONSTANTS **********
HT16K33_SEGMENT_COLON_ROW = 0x04
HT16K33_SEGMENT_MINUS_CHAR = 0x10
HT16K33_SEGMENT_DEGREE_CHAR = 0x11
HT16K33_SEGMENT_SPACE_CHAR = 0x13
COLON_CENTRE = 0x02
COLON_LEFT_UPPER = 0x04
COLON_LEFT_LOWER = 0x08
COLON_LEFT = 0x0C
DECIMAL_POINT = 0x10
# The positions of the segments within the buffer
POS = (0, 2, 6, 8)
# Bytearray of the key alphanumeric characters we can show:
# 0-9, A-F, minus, degree
CHARSET = b'\x3F\x06\x5B\x4F\x66\x6D\x7D\x07\x7F\x6F\x5F\x7C\x58\x5E\x7B\x71\x40\x63\x00'
# *********** CONSTRUCTOR **********
def __init__(self, i2c, i2c_address=0x70):
self.buffer = bytearray(16)
self.point_pattern = 0x00
super(HT16K33SegmentBig, self).__init__(i2c, i2c_address)
# *********** PUBLIC METHODS **********
def set_colon(self, pattern=0x02):
"""
Set or unset the segment LED display's colon and decimal point lights.
This method updates the display buffer, but does not send the buffer to the display itself.
Call 'draw()' to render the buffer on the display.
Args:
patter (int) An integer indicating which elements to light (OR the values required). Default: 0x00
0x00: no colon
0x02: centre colon
0x04: left colon, lower dot
0x08: left colon, upper dot
0x10: decimal point (upper)
Returns:
The instance (self)
"""
# Bail on incorrect pattern values
assert 0 <= pattern < 0x1F, "ERROR - bad patter passed to set_colon()"
self.point_pattern = pattern
self.buffer[self.HT16K33_SEGMENT_COLON_ROW] = pattern
return self
def set_glyph(self, glyph, digit=0):
"""
Present a user-defined character glyph at the specified digit.
Glyph values are 8-bit integers representing a pattern of set LED segments.
The value is calculated by setting the bit(s) representing the segment(s) you want illuminated.
Bit-to-segment mapping runs clockwise from the top around the outside of the matrix; the inner segment is bit 6:
0
_
5 | | 1
| |
- <----- 6
4 | | 2
| _ |
3
This method updates the display buffer, but does not send the buffer to the display itself.
Call 'update()' to render the buffer on the display.
Args:
glyph (int): The glyph pattern.
digit (int): The digit to show the glyph. Default: 0 (leftmost digit).
Returns:
The instance (self)
"""
# Bail on incorrect row numbers or character values
assert 0 <= digit < 4, "ERROR - Invalid digit (0-3) set in set_glyph()"
assert 0 <= glyph < 0x80, "ERROR - Invalid glyph (0x00-0xFF) set in set_glyph()"
self.buffer[self.POS[digit]] = glyph & 0x7F
return self
def set_number(self, number, digit=0):
"""
Present single decimal value (0-9) at the specified digit.
This method updates the display buffer, but does not send the buffer to the display itself.
Call 'update()' to render the buffer on the display.
Args:
number (int): The number to show.
digit (int): The digit to show the number. Default: 0 (leftmost digit).
Returns:
The instance (self)
"""
# Bail on incorrect row numbers or character values
assert 0 <= digit < 4, "ERROR - Invalid digit (0-3) set in set_number()"
assert 0 <= number < 10, "ERROR - Invalid value (0-9) set in set_number()"
return self.set_character(str(number), digit)
def set_character(self, char, digit=0):
"""
Present single alphanumeric character at the specified digit.
Only characters from the class' character set are available:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d ,e, f, -.
Other characters can be defined and presented using 'set_glyph()'.
This method updates the display buffer, but does not send the buffer to the display itself.
Call 'draw()' to render the buffer on the display.
Args:
char (string): The character to show.
digit (int): The digit to show the number. Default: 0 (leftmost digit).
has_dot (bool): Whether the decimal point to the right of the digit should be lit. Default: False.
Returns:
The instance (self)
"""
# Bail on incorrect row numbers
assert 0 <= digit < 4, "ERROR - Invalid digit set in set_character()"
char = char.lower()
char_val = 0xFF
if char == "deg":
char_val = self.HT16K33_SEGMENT_DEGREE_CHAR
elif char == '-':
char_val = self.HT16K33_SEGMENT_MINUS_CHAR
elif char == ' ':
char_val = self.HT16K33_SEGMENT_SPACE_CHAR
elif char in 'abcdef':
char_val = ord(char) - 87
elif char in '0123456789':
char_val = ord(char) - 48
# Bail on incorrect character values
assert char_val != 0xFF, "ERROR - Invalid char string set in set_character()"
self.buffer[self.POS[digit]] = self.CHARSET[char_val]
return self

127
hybotics_ht16k33/ht16k33.py Normal file
View File

@@ -0,0 +1,127 @@
# SPDX-FileCopyrightText: Radomir Dopieralski 2016 for Adafruit Industries
# SPDX-FileCopyrightText: Tony DiCola 2016 for Adafruit Industries
# SPDX-FileCopyrightText: Dale Weber 2021
#
# SPDX-License-Identifier: MIT
"""
`hybotics_ht16k33.ht16k33`
===========================
* Authors: Radomir Dopieralski & Tony DiCola for Adafruit Industries
* Ported to Micropython by Dale Weber <hybotics.wy@gmail.com>
"""
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/hybotics/Hybotics_Micropython_HT16K33.git"
from micropython import const
from utime import sleep
_HT16K33_BLINK_CMD = const(0x80)
_HT16K33_BLINK_DISPLAYON = const(0x01)
_HT16K33_CMD_BRIGHTNESS = const(0xE0)
_HT16K33_OSCILATOR_ON = const(0x21)
RETRY_MAX = 10
RETRY_WAIT_SEC = 1.0
class HT16K33:
"""The base class for all HT16K33-based backpacks and wings."""
def __init__(self, i2c, address=0x70, auto_write=True, brightness=1.0):
self.i2c = i2c
self.address = address
self._temp = bytearray(1)
self._buffer = bytearray(17)
self._auto_write = auto_write
self.fill(0)
self._write_cmd(_HT16K33_OSCILATOR_ON)
self._blink_rate = None
self._brightness = None
self.blink_rate = 0
self.brightness = brightness
self.fill(0)
def _write_cmd(self, byte):
self._temp[0] = byte
self.i2c.writeto(self.address, self._temp)
@property
def blink_rate(self):
"""The blink rate. Range 0-3."""
return self._blink_rate
@blink_rate.setter
def blink_rate(self, rate=None):
if not 0 <= rate <= 3:
raise ValueError("Blink rate must be an integer in the range: 0-3")
rate = rate & 0x03
self._blink_rate = rate
self._write_cmd(_HT16K33_BLINK_CMD | _HT16K33_BLINK_DISPLAYON | rate << 1)
@property
def brightness(self):
"""The brightness. Range 0.0-1.0"""
return self._brightness
@brightness.setter
def brightness(self, brightness):
if not 0.0 <= brightness <= 1.0:
raise ValueError(
"Brightness must be a decimal number in the range: 0.0-1.0"
)
self._brightness = brightness
xbright = round(15 * brightness)
xbright = xbright & 0x0F
self._write_cmd(_HT16K33_CMD_BRIGHTNESS | xbright)
@property
def auto_write(self):
"""Auto write updates to the display."""
return self._auto_write
@auto_write.setter
def auto_write(self, auto_write):
if isinstance(auto_write, bool):
self._auto_write = auto_write
else:
raise ValueError("Must set to either True or False.")
def show(self):
"""Refresh the display and show the changes."""
# Byte 0 is 0x00, address of LED data register. The remaining 16
# bytes are the display register data to set.
print(self._buffer)
self.i2c.writeto(self.address, self._buffer)
def fill(self, color):
"""Fill the whole display with the given color."""
fill = 0xFF if color else 0x00
for i in range(16):
self._buffer[i + 1] = fill
if self._auto_write:
self.show()
def _pixel(self, x, y, color=None):
addr = 2 * y + x // 8
mask = 1 << x % 8
if color is None:
return bool(self._buffer[addr + 1] & mask)
if color:
# set the bit
self._buffer[addr + 1] |= mask
else:
# clear the bit
self._buffer[addr + 1] &= ~mask
if self._auto_write:
self.show()
return None
def _set_buffer(self, i, value):
self._buffer[i + 1] = value # Offset by 1 to move past register address.
def _get_buffer(self, i):
return self._buffer[i + 1] # Offset by 1 to move past register address.

View File

@@ -0,0 +1,557 @@
# SPDX-FileCopyrightText: Radomir Dopieralski 2016 for Adafruit Industries
# SPDX-FileCopyrightText: Tony DiCola 2016 for Adafruit Industries
# SPDX-FileCopyrightText: Dale Weber 2021
#
# SPDX-License-Identifier: MIT
"""
Segment Displays
=================
* Ported to Micropython by Dale Weber <hybotics.wy@gmail.com>
"""
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/hybotics/Hybotics_Micropython_HT16K33.git"
from hybotics_ht16k33.ht16k33 import HT16K33
from utime import sleep
# The number of seconds to delay between writing segments
DEFAULT_CHAR_DELAY_SEC = 0.2
# The number of cycles to go for each animation
DEFAULT_CYCLES = 5
# Brightness of the display (0 to 15)
DEFAULT_DISPLAY_BRIGHTNESS = 0.3
CHARS = (
0b00000000, 0b00000000, #
0b01000000, 0b00000110, # !
0b00000010, 0b00100000, # "
0b00010010, 0b11001110, # #
0b00010010, 0b11101101, # $
0b00001100, 0b00100100, # %
0b00100011, 0b01011101, # &
0b00000100, 0b00000000, # '
0b00100100, 0b00000000, # (
0b00001001, 0b00000000, # )
0b00111111, 0b11000000, # *
0b00010010, 0b11000000, # +
0b00001000, 0b00000000, # ,
0b00000000, 0b11000000, # -
0b00000000, 0b00000000, # .
0b00001100, 0b00000000, # /
0b00001100, 0b00111111, # 0
0b00000000, 0b00000110, # 1
0b00000000, 0b11011011, # 2
0b00000000, 0b10001111, # 3
0b00000000, 0b11100110, # 4
0b00100000, 0b01101001, # 5
0b00000000, 0b11111101, # 6
0b00000000, 0b00000111, # 7
0b00000000, 0b11111111, # 8
0b00000000, 0b11101111, # 9
0b00010010, 0b00000000, # :
0b00001010, 0b00000000, # ;
0b00100100, 0b01000000, # <
0b00000000, 0b11001000, # =
0b00001001, 0b10000000, # >
0b01100000, 0b10100011, # ?
0b00000010, 0b10111011, # @
0b00000000, 0b11110111, # A
0b00010010, 0b10001111, # B
0b00000000, 0b00111001, # C
0b00010010, 0b00001111, # D
0b00000000, 0b11111001, # E
0b00000000, 0b01110001, # F
0b00000000, 0b10111101, # G
0b00000000, 0b11110110, # H
0b00010010, 0b00000000, # I
0b00000000, 0b00011110, # J
0b00100100, 0b01110000, # K
0b00000000, 0b00111000, # L
0b00000101, 0b00110110, # M
0b00100001, 0b00110110, # N
0b00000000, 0b00111111, # O
0b00000000, 0b11110011, # P
0b00100000, 0b00111111, # Q
0b00100000, 0b11110011, # R
0b00000000, 0b11101101, # S
0b00010010, 0b00000001, # T
0b00000000, 0b00111110, # U
0b00001100, 0b00110000, # V
0b00101000, 0b00110110, # W
0b00101101, 0b00000000, # X
0b00010101, 0b00000000, # Y
0b00001100, 0b00001001, # Z
0b00000000, 0b00111001, # [
0b00100001, 0b00000000, # \
0b00000000, 0b00001111, # ]
0b00001100, 0b00000011, # ^
0b00000000, 0b00001000, # _
0b00000001, 0b00000000, # `
0b00010000, 0b01011000, # a
0b00100000, 0b01111000, # b
0b00000000, 0b11011000, # c
0b00001000, 0b10001110, # d
0b00001000, 0b01011000, # e
0b00000000, 0b01110001, # f
0b00000100, 0b10001110, # g
0b00010000, 0b01110000, # h
0b00010000, 0b00000000, # i
0b00000000, 0b00001110, # j
0b00110110, 0b00000000, # k
0b00000000, 0b00110000, # l
0b00010000, 0b11010100, # m
0b00010000, 0b01010000, # n
0b00000000, 0b11011100, # o
0b00000001, 0b01110000, # p
0b00000100, 0b10000110, # q
0b00000000, 0b01010000, # r
0b00100000, 0b10001000, # s
0b00000000, 0b01111000, # t
0b00000000, 0b00011100, # u
0b00100000, 0b00000100, # v
0b00101000, 0b00010100, # w
0b00101000, 0b11000000, # x
0b00100000, 0b00001100, # y
0b00001000, 0b01001000, # z
0b00001001, 0b01001001, # {
0b00010010, 0b00000000, # |
0b00100100, 0b10001001, # }
0b00000101, 0b00100000, # ~
0b00111111, 0b11111111,
)
NUMBERS = (
0x3F, # 0
0x06, # 1
0x5B, # 2
0x4F, # 3
0x66, # 4
0x6D, # 5
0x7D, # 6
0x07, # 7
0x7F, # 8
0x6F, # 9
0x77, # a
0x7C, # b
0x39, # C
0x5E, # d
0x79, # E
0x71, # F
0x40, # -
)
class Seg14x4(HT16K33):
"""Alpha-numeric, 14-segment display."""
POSITIONS = (0, 2, 6, 8) # The positions of characters.
def __init__(self, i2c, address=0x70, auto_write=True):
super().__init__(i2c, address, auto_write)
# Use colon for controling two-dots indicator at the center (index 0)
self._colon = Colon(self)
def print(self, value, decimal=0, auto_round=False):
"""Print the value to the display."""
if isinstance(value, (str)):
self._text(value)
elif isinstance(value, (int, float)):
self._number(value, decimal, auto_round)
else:
raise ValueError("Unsupported display value type: {}".format(type(value)))
if self._auto_write:
self.show()
def print_hex(self, value):
"""Print the value as a hexidecimal string to the display."""
if isinstance(value, int):
self.print("{0:X}".format(value))
else:
self.print(value)
def __setitem__(self, key, value):
self._put(value, key)
if self._auto_write:
self.show()
def scroll(self, count=1):
"""Scroll the display by specified number of places."""
if count >= 0:
offset = 0
else:
offset = 2
for i in range(6):
self._set_buffer(i + offset, self._get_buffer(i + 2 * count))
def _put(self, char, index=0):
"""Put a character at the specified place."""
if not 0 <= index <= 3:
return
if not 32 <= ord(char) <= 127:
return
if char == ".":
self._set_buffer(
index * 2 + 1, self._get_buffer(index * 2 + 1) | 0b01000000
)
return
character = ord(char) * 2 - 64
self._set_buffer(index * 2, CHARS[1 + character])
self._set_buffer(index * 2 + 1, CHARS[character])
def _push(self, char):
"""Scroll the display and add a character at the end."""
if char != "." or self._get_buffer(7) & 0b01000000:
self.scroll()
self._put(" ", 3)
self._put(char, 3)
def _text(self, text):
"""Display the specified text."""
for character in text:
self._push(character)
def _number(self, number, decimal=None, auto_round=False):
"""
Display a floating point or integer number on the Adafruit HT16K33 based displays
Param: number - The floating point or integer number to be displayed, which must be
in the range 0 (zero) to 9999 for integers and floating point or integer numbers
and between 0.0 and 999.0 or 99.00 or 9.000 for floating point numbers.
Param: decimal - The number of decimal places for a floating point number if decimal
is greater than zero, or the input number is an integer if decimal is zero.
Returns: The output text string to be displayed.
"""
if number < 0:
raise ValueError("Input underflow: {0} is negative - unable to display!".format(number))
# Initialize
auto_write = self._auto_write
self._auto_write = False
txt = ""
dec_places = 0
num = number
# Check for floating point numbers (dot > 0)
stnum = str(number)
stnum_len = len(stnum)
dot = stnum.find(".")
if dot > 0:
whole = len(stnum[:dot])
dec_places = len(stnum[dot + 1:])
else:
whole = stnum_len
dec_places = 0
if auto_round:
# Automatically round up to when there is no decimal part
while whole + dec_places > 4 and dec_places >= 0:
dec_places -= 1
num = round(num, dec_places) #dec_places)
stnum = str(num)
stnum_len = len(stnum)
elif decimal >= 1:
# Round according to the number of decimal places requested
num = round(number, decimal)
dec_places = decimal
stnum = str(num)
dot = stnum.find(".")
if dot > 0:
whole = len(stnum[:dot])
stnum = stnum[:dot + decimal + 1]
stnum_len = len(stnum)
if whole + dec_places > 5:
raise ValueError("Input overflow - '{0}' is too large for the display!".format(number))
# Set decimal places, if number of decimal places is specified (decimal > 0)
if dec_places > 0 and dot > 0 and stnum[0] == ".":
txt = "0" + stnum[dot:]
else:
txt = stnum
if len(txt) > 5:
raise ValueError("Output string '{0}' is too long!".format(txt))
self._text(txt)
self._auto_write = auto_write
return txt
def set_digit_raw(self, index, bitmask):
"""Set digit at position to raw bitmask value. Position should be a value
of 0 to 3 with 0 being the left most character on the display.
bitmask should be 2 bytes such as: 0xFFFF
If can be passed as an integer, list, or tuple
"""
if not isinstance(index, int) or not 0 <= index <= 3:
raise ValueError("Index value must be an integer in the range: 0-3")
if isinstance(bitmask, (tuple, list)):
bitmask = ((bitmask[0] & 0xFF) << 8) | (bitmask[1] & 0xFF)
# Use only the valid potion of bitmask
bitmask &= 0xFFFF
# Set the digit bitmask value at the appropriate position.
self._set_buffer(index * 2, bitmask & 0xFF)
self._set_buffer(index * 2 + 1, (bitmask >> 8) & 0xFF)
if self._auto_write:
self.show()
def marquee(self, text, delay=0.25, loop=True):
"""
Automatically scroll the text at the specified delay between characters
:param str text: The text to display
:param float delay: (optional) The delay in seconds to pause before scrolling
to the next character (default=0.25)
:param bool loop: (optional) Whether to endlessly loop the text (default=True)
"""
if isinstance(text, str):
self.fill(False)
if loop:
while True:
self._scroll_marquee(text, delay)
else:
self._scroll_marquee(text, delay)
def _scroll_marquee(self, text, delay):
"""Scroll through the text string once using the delay"""
char_is_dot = False
for character in text:
self.print(character)
# Add delay if character is not a dot or more than 2 in a row
if character != "." or char_is_dot:
sleep(delay)
char_is_dot = character == "."
self.show()
def animate(self, digits, bitmasks, delay=DEFAULT_CHAR_DELAY_SEC, auto_write=True):
"""
Main driver for all alphanumeric display animations.
Param: digits - a list of the digits to write to, in order, like [0, 1, 3]. The digits are
0 to 3 starting at the left most digit.
Param: bitmasks - a list of the bitmasks to write, in sequence, to the specified digits.
Param: delay - The delay, in seconds (or fractions of), between writing bitmasks to a digit.
Param: auto_write - Whether to actually write to the display immediately or not.
Returns: Nothing
"""
if not isinstance(digits, list):
raise ValueError("The first parameter MUST be a list!")
if not isinstance(bitmasks, list):
raise ValueError("The second parameter MUST be a list!")
if delay < 0:
raise ValueError("The delay between frames must be positive!")
for dig in digits:
if not 0 <= dig <= 3:
raise ValueError(
"Digit value must be an integer in the range: 0-3")
for bits in bitmasks:
if not 0 <= bits <= 0xFFFF:
raise ValueError(
"Bitmask value must be an integer in the range: 0-65535"
)
self.set_digit_raw(dig, bits)
if auto_write:
self.show()
sleep(delay)
@property
def colon(self):
"""Simplified colon accessor"""
return self._colon[0]
@colon.setter
def colon(self, turn_on):
self._colon[0] = turn_on
class Seg7x4(Seg14x4):
"""Numeric 7-segment display. It has the same methods as the alphanumeric display, but only
supports displaying a limited set of characters."""
POSITIONS = (0, 2, 6, 8) # The positions of characters.
def __init__(self, i2c, address=0x70, auto_write=True):
super().__init__(i2c, address, auto_write)
# Use colon for controling two-dots indicator at the center (index 0)
self._colon = Colon(self)
def scroll(self, count=1):
"""Scroll the display by specified number of places."""
if count >= 0:
offset = 0
else:
offset = 1
for i in range(3):
self._set_buffer(
self.POSITIONS[i + offset], self._get_buffer(self.POSITIONS[i + count])
)
def _push(self, char):
"""Scroll the display and add a character at the end."""
if char in ":;":
self._put(char)
else:
if char != "." or self._get_buffer(self.POSITIONS[3]) & 0b10000000:
self.scroll()
self._put(" ", 3)
self._put(char, 3)
def _put(self, char, index=0):
"""Put a character at the specified place."""
if not 0 <= index <= 3:
return
char = char.lower()
index = self.POSITIONS[index]
if char == ".":
self._set_buffer(index, self._get_buffer(index) | 0b10000000)
return
if char in "abcdef":
character = ord(char) - 97 + 10
elif char == "-":
character = 16
elif char in "0123456789":
character = ord(char) - 48
elif char == " ":
self._set_buffer(index, 0x00)
return
elif char == ":":
self._set_buffer(4, 0x02)
return
elif char == ";":
self._set_buffer(4, 0x00)
return
else:
return
self._set_buffer(index, NUMBERS[character])
def set_digit_raw(self, index, bitmask):
"""Set digit at position to raw bitmask value. Position should be a value
of 0 to 3 with 0 being the left most digit on the display.
"""
if not isinstance(index, int) or not 0 <= index <= 3:
raise ValueError("Index value must be an integer in the range: 0-3")
# Set the digit bitmask value at the appropriate position.
self._set_buffer(self.POSITIONS[index], bitmask & 0xFF)
if self._auto_write:
self.show()
@property
def colon(self):
"""Simplified colon accessor"""
return self._colon[0]
@colon.setter
def colon(self, turn_on):
self._colon[0] = turn_on
class BigSeg7x4(Seg7x4):
"""Numeric 7-segment display. It has the same methods as the alphanumeric display, but only
supports displaying a limited set of characters."""
def __init__(self, i2c, address=0x70, auto_write=True):
super().__init__(i2c, address, auto_write)
# Use colon for controling two-dots indicator at the center (index 0)
# or the two-dots indicators at the left (index 1)
self.colon = Colon(self, 2)
def _setindicator(self, index, value):
"""Set side LEDs (dots)
Index is as follow :
* 0 : two dots at the center
* 1 : top-left dot
* 2 : bottom-left dot
* 3 : right dot (also ampm indicator)
"""
bitmask = 1 << (index + 1)
current = self._get_buffer(0x04)
if value:
self._set_buffer(0x04, current | bitmask)
else:
self._set_buffer(0x04, current & ~bitmask)
if self._auto_write:
self.show()
def _getindicator(self, index):
"""Get side LEDs (dots)
See setindicator() for indexes
"""
bitmask = 1 << (index + 1)
return self._get_buffer(0x04) & bitmask
@property
def top_left_dot(self):
"""The top-left dot indicator."""
return bool(self._getindicator(1))
@top_left_dot.setter
def top_left_dot(self, value):
self._setindicator(1, value)
@property
def bottom_left_dot(self):
"""The bottom-left dot indicator."""
return bool(self._getindicator(2))
@bottom_left_dot.setter
def bottom_left_dot(self, value):
self._setindicator(2, value)
@property
def ampm(self):
"""The AM/PM indicator."""
return bool(self._getindicator(3))
@ampm.setter
def ampm(self, value):
self._setindicator(3, value)
class Colon:
"""Helper class for controlling the colons. Not intended for direct use."""
# pylint: disable=protected-access
MASKS = (0x02, 0x0C)
def __init__(self, disp, num_of_colons=1):
self._disp = disp
self._num_of_colons = num_of_colons
def __setitem__(self, key, value):
if key > self._num_of_colons - 1:
raise ValueError("Trying to set a non-existent colon.")
current = self._disp._get_buffer(0x04)
if value:
self._disp._set_buffer(0x04, current | self.MASKS[key])
else:
self._disp._set_buffer(0x04, current & ~self.MASKS[key])
if self._disp.auto_write:
self._disp.show()
def __getitem__(self, key):
if key > self._num_of_colons - 1:
raise ValueError("Trying to access a non-existent colon.")
return bool(self._disp._get_buffer(0x04) & self.MASKS[key])

406
main.py Normal file
View File

@@ -0,0 +1,406 @@
import _thread
import gc
import math
import network
import socket
import sys
import time
import ubinascii
from machine import I2C, Pin, ADC
from secrets import secrets
import sht4x.sht4x as sht4x
from web_tools import result_ok, result_notfound
from tools import connect_wifi, set_time, with_fallback, with_fallback_to_str
from ht16k33.ht16k33segmentbig import HT16K33SegmentBig
# GPIO devices
led = Pin('LED', Pin.OUT)
pir_sensor = Pin(2, Pin.IN, Pin.PULL_UP)
photoRes = ADC(Pin(26))
def blink_onboard_led(num_blinks):
for i in range(num_blinks):
led.value(1)
time.sleep_ms(200)
led.value(0)
time.sleep_ms(200)
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
devices = i2c.scan()
if len(devices) != 0:
print('Number of I2C devices found=',len(devices))
for device in devices:
print("Device Hexadecimel Address=",hex(device))
else:
print("No device found")
display = HT16K33SegmentBig(i2c)
display.clear()
display.set_glyph(0b01111100, 0)
display.set_glyph(0b01011100, 1)
display.set_glyph(0b01011100, 2)
display.set_glyph(0b01111000, 3)
display.set_brightness(1)
display.draw()
last_brightness = 1
wlan = network.WLAN(network.STA_IF)
ssid = secrets['ssid']
pw = secrets['pw']
connect_wifi(wlan, ssid, pw, blink_onboard_led)
display.set_colon(0x10)
display.draw()
wlan_mac = wlan.config('mac')
mac_readable = ubinascii.hexlify(wlan_mac).decode()
sht4x = sht4x.SHT4X(i2c)
update_time = False
try:
print('Updating time...')
set_time()
print('Time updated')
except:
print('Could not set time')
update_time = True
temp_sht4x = 0
humidity_sht4x = 0
enable_temp = True
enable_hum = True
dim_light = True
enable_auto_brightness = True
enable_motion_detection = False
motion_timeout_ms = 18000 # Default value, it will be recalculated
motion_started_ms = 0
motion_state_on = False
def web_thread():
global temp_sht4x
global humidity_sht4x
global enable_temp
global enable_hum
global dim_light
global enable_auto_brightness
global enable_motion_detection
GET_PATH_START = 4
POST_PATH_START = 5
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
print('Binding to ' + str(addr))
serverSocket = socket.socket()
serverSocket.bind(addr)
serverSocket.listen(1)
while True:
try:
#print('waiting for client')
cl, addr = serverSocket.accept()
#print('client connected from', addr)
request = cl.recv(1024)
request = str(request)
request = request[2:-1] # remove b' and ' from string
#print(request)
sensor_lock.acquire()
temperature = temp_sht4x
humidity = humidity_sht4x
sensor_lock.release()
if request.find('/homeassistant ') == GET_PATH_START:
result_ok(cl,
"{" +
"\"humidity\": \"" + with_fallback_to_str(humidity, "NaN") + "\"," +
"\"temp\": \"" + with_fallback_to_str(temperature, "NaN")+"\""+
"}",
'application/json')
elif request.find('/toggle_temp') == GET_PATH_START:
result_ok(cl, '{"is_active": "' + str(enable_temp).lower() + '"}', 'application/json')
elif request.find('/toggle_hum') == GET_PATH_START:
result_ok(cl, '{"is_active": "' + str(enable_hum).lower() + '"}', 'application/json')
elif request.find('/toggle_dim_light') == GET_PATH_START:
result_ok(cl, '{"is_active": "' + str(dim_light).lower() + '"}', 'application/json')
elif request.find('/toggle_auto_light') == GET_PATH_START:
result_ok(cl, '{"is_active": "' + str(enable_auto_brightness).lower() + '"}', 'application/json')
elif request.find('/toggle_motion_detection') == GET_PATH_START:
result_ok(cl, '{"is_active": "' + str(enable_motion_detection).lower() + '"}', 'application/json')
elif request.find('/toggle_temp') == POST_PATH_START:
content_start_pos = request.find('\\r\\n\\r\\n')
content = request[content_start_pos+8:]
# HomeAssistant sends request body in a second packet
if len(content) == 0:
content = str(cl.recv(1024))
is_active = content.find('true') != -1
enable_temp = is_active
result_ok(cl, 'ok')
elif request.find('/toggle_hum') == POST_PATH_START:
content_start_pos = request.find('\\r\\n\\r\\n')
content = request[content_start_pos+8:]
# HomeAssistant sends request body in a second packet
if len(content) == 0:
content = str(cl.recv(1024))
is_active = content.find('true') != -1
enable_hum = is_active
result_ok(cl, 'ok')
elif request.find('/toggle_dim_light') == POST_PATH_START:
content_start_pos = request.find('\\r\\n\\r\\n')
content = request[content_start_pos+8:]
# HomeAssistant sends request body in a second packet
if len(content) == 0:
content = str(cl.recv(1024))
is_active = content.find('true') != -1
dim_light = is_active
result_ok(cl, 'ok')
elif request.find('/toggle_auto_light') == POST_PATH_START:
content_start_pos = request.find('\\r\\n\\r\\n')
content = request[content_start_pos+8:]
# HomeAssistant sends request body in a second packet
if len(content) == 0:
content = str(cl.recv(1024))
is_active = content.find('true') != -1
enable_auto_brightness = is_active
result_ok(cl, 'ok')
elif request.find('/toggle_motion_detection') == POST_PATH_START:
content_start_pos = request.find('\\r\\n\\r\\n')
content = request[content_start_pos+8:]
# HomeAssistant sends request body in a second packet
if len(content) == 0:
content = str(cl.recv(1024))
is_active = content.find('true') != -1
enable_motion_detection = is_active
result_ok(cl, 'ok')
elif request.find('/metrics') == GET_PATH_START:
attrs = "{mac=\"""" + mac_readable + "\"} "
content = (
"""# HELP temperature Temperature in Celsius
# TYPE temperature gauge
temperature""" + attrs + with_fallback_to_str(temperature, "NaN") +
"""
# HELP humidity Relative humidity in %
# TYPE humidity gauge
humidity""" + attrs + with_fallback_to_str(humidity, "NaN"))
result_ok(cl, content)
else:
result_notfound(cl)
except KeyboardInterrupt:
serverSocket.close()
break
except Exception as e:
print("caught exception in main loop {} {}".format(type(e).__name__, e))
sys.print_exception(e)
time.sleep_ms(100)
try:
gc.collect()
except:
pass
def display_thread():
global temp_sht4x
global humidity_sht4x
global enable_temp
global enable_hum
global dim_light
global enable_auto_brightness
global enable_motion_detection
global motion_timeout_ms
global motion_started_ms
global motion_state_on
global last_brightness
global update_time
global display
global sensor_lock
current_time = time.time_ns()
last_display_mode_time = int(current_time // 1_000_000)
display_mode = 0
while True:
try:
try:
sensor_lock.acquire()
temp_sht4x, humidity_sht4x = sht4x.measurements
except KeyboardInterrupt:
break
except Exception as e:
print("caught exception in main loop {} {}".format(type(e).__name__, e))
sys.print_exception(e)
sensor_lock.release()
current_time = time.time_ns()
current_time_ms = int(current_time // 1_000_000)
display.clear()
ltime = time.localtime()
hour = ltime[3]
minute = ltime[4]
# Light on a 0 - 65535 scale
light = light_raw = photoRes.read_u16()
# We want to use the minimum brightness in darker environments
# So we scale the light value to a 0 - 15 scale but uses a 18 (0-17) scale with zeroes at the beggining
# Because of this the lower (18 [extended range] - 16 [real range] + 1 [0 is 0 on the real range]) * 100 / 18 = 16.6%
# the brightness will be 0
# We also chop of the top so instead of 18 we use a 20 scale but only subtract 3 from
light = light * 19 // 65535 - 3
if light < 0:
light = 0
if enable_auto_brightness:
#if hour <= 7 or hour >= 20:
# brighness = 1
#else:
# brighness = 15
brightness = light
if brightness != last_brightness:
last_brightness = brightness
display.set_brightness(brightness)
else:
if dim_light and last_brightness != 1:
display.set_brightness(1)
last_brightness = 1
elif not dim_light and last_brightness != 15:
display.set_brightness(15)
last_brightness = 15
if motion_state_on and current_time_ms >= motion_started_ms + motion_timeout_ms:
motion_state_on = False
if motion_state_on == False and enable_motion_detection and pir_sensor.value() == 1:
motion_state_on = True
motion_started_ms = current_time_ms
display_mode = 0
last_display_mode_time = current_time_ms
motion_timeout_ms = 6000
if enable_temp:
motion_timeout_ms = motion_timeout_ms + 6000
if enable_hum:
motion_timeout_ms = motion_timeout_ms + 6000
if display_mode == 0:
hour_0 = int(math.floor(hour/10))
hour_1 = hour - hour_0 * 10
display.set_number(hour_0, 0)
display.set_number(hour_1, 1)
minute_0 = int(math.floor(minute/10))
minute_1 = minute - minute_0*10
display.set_number(minute_0, 2)
display.set_number(minute_1, 3)
signs = 0
signs = signs | (0x02 if time.time() % 2 == 0 else 0) # (0x02 if current_time_ms % 1000 < 500 else 0)
display.set_colon(signs)
elif display_mode == 1:
temp_i0 = int(temp_sht4x // 10)
temp_i1 = int(temp_sht4x - (temp_i0 * 10))
temp_d0 = int(temp_sht4x * 10 - temp_i0 * 100 - temp_i1 * 10)
display.set_number(temp_i0, 0)
display.set_number(temp_i1, 1)
display.set_number(temp_d0, 2)
display.set_glyph(0x39, 3)
display.set_colon(0x02)
elif display_mode == 2:
hum_i0 = int(humidity_sht4x // 10)
hum_i1 = int(humidity_sht4x - (hum_i0 * 10))
hum_d0 = int(humidity_sht4x * 10 - hum_i0 * 100 - hum_i1 * 10)
display.set_number(hum_i0, 0)
display.set_number(hum_i1, 1)
display.set_number(hum_d0, 2)
display.set_colon(0x12)
try:
if enable_motion_detection and not motion_state_on:
display.clear()
display.draw()
except KeyboardInterrupt:
break
except Exception as e:
print("caught exception in main loop {} {}".format(type(e).__name__, e))
sys.print_exception(e)
if current_time_ms >= last_display_mode_time + 3000:
last_display_mode_time = current_time_ms
display_mode = display_mode + 1
if display_mode == 1 and not enable_temp:
display_mode = display_mode + 1
if display_mode == 2 and not enable_hum:
display_mode = display_mode + 1
if display_mode > 2:
display_mode = 0
gc.collect()
try:
if update_time:
try:
set_time()
update_time = False
except:
print('Could not set time')
update_time = True
except KeyboardInterrupt:
break
except Exception as e:
print("caught exception in main loop {} {}".format(type(e).__name__, e))
sys.print_exception(e)
time.sleep_ms(100)
except KeyboardInterrupt:
break
except Exception as e:
print("caught exception in main loop {} {}".format(type(e).__name__, e))
sys.print_exception(e)
time.sleep_ms(100)
sensor_lock = _thread.allocate_lock()
print('Starting Display thread')
second_thread = _thread.start_new_thread(display_thread, ())
print('Starting Main loop')
web_thread()

261
sht4x/sht4x.py Normal file
View File

@@ -0,0 +1,261 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`sht4x`
================================================================================
MicroPython Driver fot the Sensirion Temperature and Humidity SHT40, SHT41 and SHT45 Sensor
* Author: Jose D. Montoya
"""
import time
import struct
from micropython import const
try:
from typing import Tuple
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_SHT4X.git"
_RESET = const(0x94)
HIGH_PRECISION = const(0)
MEDIUM_PRECISION = const(1)
LOW_PRECISION = const(2)
temperature_precision_options = (HIGH_PRECISION, MEDIUM_PRECISION, LOW_PRECISION)
temperature_precision_values = {
HIGH_PRECISION: const(0xFD),
MEDIUM_PRECISION: const(0xF6),
LOW_PRECISION: const(0xE0),
}
HEATER200mW = const(0)
HEATER110mW = const(1)
HEATER20mW = const(2)
heater_power_values = (HEATER200mW, HEATER110mW, HEATER20mW)
TEMP_1 = const(0)
TEMP_0_1 = const(1)
heat_time_values = (TEMP_1, TEMP_0_1)
wat_config = {
HEATER200mW: (0x39, 0x32),
HEATER110mW: (0x2F, 0x24),
HEATER20mW: (0x1E, 0x15),
}
class SHT4X:
"""Driver for the SHT4X Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the SHT4X is connected to.
:param int address: The I2C device address. Defaults to :const:`0x44`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`SHT4X` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_sht4x import sht4x
Once this is done you can define your `machine.I2C` object and define your sensor object
.. code-block:: python
i2c = I2C(1, sda=Pin(2), scl=Pin(3))
sht = sht4x.SHT4X(i2c)
Now you have access to the attributes
.. code-block:: python
temp = sht.temperature
hum = sht.relative_humidity
"""
def __init__(self, i2c, address: int = 0x44) -> None:
self._i2c = i2c
self._address = address
self._data = bytearray(6)
self._command = 0xFD
self._temperature_precision = HIGH_PRECISION
self._heater_power = HEATER20mW
self._heat_time = TEMP_0_1
@property
def temperature_precision(self) -> str:
"""
Sensor temperature_precision
+------------------------------------+------------------+
| Mode | Value |
+====================================+==================+
| :py:const:`sht4x.HIGH_PRECISION` | :py:const:`0` |
+------------------------------------+------------------+
| :py:const:`sht4x.MEDIUM_PRECISION` | :py:const:`1` |
+------------------------------------+------------------+
| :py:const:`sht4x.LOW_PRECISION` | :py:const:`2` |
+------------------------------------+------------------+
"""
values = ("HIGH_PRECISION", "MEDIUM_PRECISION", "LOW_PRECISION")
return values[self._temperature_precision]
@temperature_precision.setter
def temperature_precision(self, value: int) -> None:
if value not in temperature_precision_values:
raise ValueError("Value must be a valid temperature_precision setting")
self._temperature_precision = value
self._command = temperature_precision_values[value]
@property
def relative_humidity(self) -> float:
"""
The current relative humidity in % rH
The RH conversion formula (1) allows values to be reported
which are outside the range of 0 %RH … 100 %RH. Relative
humidity values which are smaller than 0 %RH and larger than
100 %RH are non-physical, however these “uncropped” values might
be found beneficial in some cases (e.g. when the distribution of
the sensors at the measurement boundaries are of interest)
"""
return self.measurements[1]
@property
def temperature(self) -> float:
"""The current temperature in Celsius"""
return self.measurements[0]
@property
def measurements(self) -> Tuple[float, float]:
"""both `temperature` and `relative_humidity`, read simultaneously
If you use t the heater function, sensor will be not give a response
back. Waiting time is added to the logic to account for this situation
"""
self._i2c.writeto(self._address, bytes([self._command]), False)
if self._command in (0x39, 0x2F, 0x1E):
time.sleep(1.2)
elif self._command in (0x32, 0x24, 0x15):
time.sleep(0.2)
time.sleep(0.2)
self._i2c.readfrom_into(self._address, self._data)
temperature, temp_crc, humidity, humidity_crc = struct.unpack_from(
">HBHB", self._data
)
if temp_crc != self._crc(
memoryview(self._data[0:2])
) or humidity_crc != self._crc(memoryview(self._data[3:5])):
raise RuntimeError("Invalid CRC calculated")
temperature = -45.0 + 175.0 * temperature / 65535.0
humidity = -6.0 + 125.0 * humidity / 65535.0
humidity = max(min(humidity, 100), 0)
return temperature, humidity
@staticmethod
def _crc(buffer) -> int:
"""verify the crc8 checksum"""
crc = 0xFF
for byte in buffer:
crc ^= byte
for _ in range(8):
if crc & 0x80:
crc = (crc << 1) ^ 0x31
else:
crc = crc << 1
return crc & 0xFF
@property
def heater_power(self) -> str:
"""
Sensor heater power
The sensor has a heater. Three heating powers and two heating
durations are selectable.
The sensor executes the following procedure:
1. The heater is enabled, and the timer starts its count-down.
2. Measure is taken after time is up
3. After the measurement is finished the heater is turned off.
4. Temperature and humidity values are now available for readout.
The maximum on-time of the heater commands is one second in order
to prevent overheating
+-------------------------------+---------------+
| Mode | Value |
+===============================+===============+
| :py:const:`sht4x.HEATER200mW` | :py:const:`0` |
+-------------------------------+---------------+
| :py:const:`sht4x.HEATER110mW` | :py:const:`1` |
+-------------------------------+---------------+
| :py:const:`sht4x.HEATER20mW` | :py:const:`2` |
+-------------------------------+---------------+
"""
values = ("HEATER200mW", "HEATER110mW", "HEATER20mW")
return values[self._heater_power]
@heater_power.setter
def heater_power(self, value: int) -> None:
if value not in heater_power_values:
raise ValueError("Value must be a valid heater power setting")
self._heater_power = value
self._command = wat_config[value][self._heat_time]
@property
def heat_time(self) -> str:
"""
Sensor heat_time
The sensor has a heater. Three heating powers and two heating
durations are selectable.
The sensor executes the following procedure:
1. The heater is enabled, and the timer starts its count-down.
2. Measure is taken after time is up
3. After the measurement is finished the heater is turned off.
4. Temperature and humidity values are now available for readout.
The maximum on-time of the heater commands is one second in order
to prevent overheating
+----------------------------+---------------+
| Mode | Value |
+============================+===============+
| :py:const:`sht4x.TEMP_1` | :py:const:`0` |
+----------------------------+---------------+
| :py:const:`sht4x.TEMP_0_1` | :py:const:`1` |
+----------------------------+---------------+
"""
values = ("TEMP_1", "TEMP_0_1")
return values[self._heat_time]
@heat_time.setter
def heat_time(self, value: int) -> None:
if value not in heat_time_values:
raise ValueError("Value must be a valid heat_time setting")
self._heat_time = value
self._command = wat_config[self._heater_power][value]
def reset(self):
"""
Reset the sensor
"""
self._i2c.writeto(self._address, bytes([_RESET]), False)
time.sleep(0.1)

93
tools.py Normal file
View File

@@ -0,0 +1,93 @@
import ubinascii
import time
import socket
import struct
import machine
def with_fallback(value, fallback):
if value is None:
return fallback
else:
return value
def with_fallback_to_str(value, fallback: str) -> str:
if value is None:
return fallback
else:
return str(value)
def set_time():
NTP_DELTA = 2208988800
HU_CORRECTION = 3600
host = "pool.ntp.org"
NTP_QUERY = bytearray(48)
NTP_QUERY[0] = 0x1B
addr = socket.getaddrinfo(host, 123)[0][-1]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.settimeout(10)
res = s.sendto(NTP_QUERY, addr)
msg = s.recv(48)
finally:
s.close()
val = struct.unpack("!I", msg[40:44])[0]
t = val - NTP_DELTA + HU_CORRECTION
tm = time.gmtime(t)
machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))
def connect_wifi(wlan, ssid, pw, blink_onboard_led):
print('Connecting to WiFi ' + ssid + '...')
wlan.active(True)
# If you need to disable powersaving mode
# wlan.config(pm = 0xa11140)
# See the MAC address in the wireless chip OTP
mac = ubinascii.hexlify(wlan.config('mac'),':').decode()
print('mac = ' + mac)
# Other things to query
# print(wlan.config('channel'))
# print(wlan.config('essid'))
# print(wlan.config('txpower'))
wlan.connect(ssid, pw)
# Wait for connection with 10 second timeout
timeout = 10
while timeout > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
timeout -= 1
print('Waiting for connection... Status is ' + str(wlan.status()))
blink_onboard_led(2)
time.sleep(1)
# Handle connection error
# Error meanings
# 0 Link Down
# 1 Link Join
# 2 Link NoIp
# 3 Link Up
# -1 Link Fail
# -2 Link NoNet
# -3 Link BadAuth
wlan_status = wlan.status()
blink_onboard_led(wlan_status)
if wlan_status != 3:
blink_onboard_led(5)
print('Wi-Fi connection failed')
return False
else:
blink_onboard_led(1)
print('Connected')
status = wlan.ifconfig()
print('ip = ' + status[0])
return True
def disconnect_wifi(wlan):
wlan.disconnect()
wlan.active(False)
wlan.deinit()
print('Disconnected')

9
web_tools.py Normal file
View File

@@ -0,0 +1,9 @@
def result_ok(cl, response = None, content_type = "text/plain"):
cl.send('HTTP/1.0 200 OK\r\nContent-type: ' + content_type + '\r\n\r\n')
cl.send(response if response is not None else "Ok")
cl.close()
def result_notfound(cl, response = None):
cl.send('HTTP/1.0 404 NotFound\r\nContent-type: text/plain\r\n\r\n')
cl.send(response if response is not None else "Not Found")
cl.close()