PiClock project
This commit is contained in:
3
.micropico
Normal file
3
.micropico
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"info": "This file is just used to identify a project folder."
|
||||||
|
}
|
||||||
119
ht16k33/ht16k33.py
Normal file
119
ht16k33/ht16k33.py
Normal 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]))
|
||||||
162
ht16k33/ht16k33segmentbig.py
Normal file
162
ht16k33/ht16k33segmentbig.py
Normal 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
127
hybotics_ht16k33/ht16k33.py
Normal 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.
|
||||||
557
hybotics_ht16k33/segments.py
Normal file
557
hybotics_ht16k33/segments.py
Normal 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
406
main.py
Normal 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
261
sht4x/sht4x.py
Normal 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
93
tools.py
Normal 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
9
web_tools.py
Normal 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()
|
||||||
Reference in New Issue
Block a user