|
1 | 1 | """Climate station example using WSEN-PADS, HTS221 and SSD1327 OLED. |
2 | 2 |
|
3 | 3 | Reads temperature and pressure from the WSEN-PADS sensor and humidity |
4 | | -from the HTS221 sensor. Includes a startup calibration phase to compensate |
5 | | -sensor offsets. Displays live temperature, humidity and pressure readings |
6 | | -with horizontal progress bars and a comfort index on a round 128x128 OLED. |
| 4 | +from the HTS221 sensor. Displays live readings with a comfort index |
| 5 | +on the round 128x128 OLED using steami_screen widgets. |
| 6 | +
|
| 7 | +Hardware: |
| 8 | + - WSEN-PADS (temperature + pressure) |
| 9 | + - HTS221 (humidity) |
| 10 | + - SSD1327 128x128 OLED display (round) |
7 | 11 | """ |
8 | 12 |
|
9 | 13 | from time import sleep_ms |
10 | 14 |
|
11 | 15 | import ssd1327 |
12 | 16 | from hts221 import HTS221 |
13 | 17 | from machine import I2C, SPI, Pin |
| 18 | +from steami_screen import GRAY, GREEN, LIGHT, RED, WHITE, Screen, SSD1327Display |
14 | 19 | from wsen_pads import WSEN_PADS |
15 | 20 |
|
16 | | -# === Ecran === |
| 21 | +# --- Display --- |
17 | 22 | spi = SPI(1) |
18 | 23 | dc = Pin("DATA_COMMAND_DISPLAY") |
19 | 24 | res = Pin("RST_DISPLAY") |
20 | 25 | cs = Pin("CS_DISPLAY") |
21 | | -display = ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs) |
| 26 | +display = SSD1327Display(ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs)) |
| 27 | +screen = Screen(display) |
22 | 28 |
|
23 | | -# === Capteurs === |
| 29 | +# --- Sensors --- |
24 | 30 | i2c = I2C(1) |
25 | 31 | pads = WSEN_PADS(i2c) |
26 | 32 | hts = HTS221(i2c) |
27 | 33 |
|
28 | | -# === Calibration === |
29 | | -print("Calibration en cours...") |
30 | | -temps = [] |
31 | | -hums = [] |
32 | | -press = [] |
33 | | -for _ in range(10): |
34 | | - temps.append(pads.temperature()) |
35 | | - hums.append(hts.humidity()) |
36 | | - press.append(pads.pressure_hpa()) |
37 | | - sleep_ms(200) |
38 | | - |
39 | | -OFFSET_TEMP = 20.0 - (sum(temps) / len(temps)) |
40 | | -OFFSET_HUM = 50.0 - (sum(hums) / len(hums)) |
41 | | -OFFSET_PRES = 1013.0 - (sum(press) / len(press)) |
42 | | -print(f"Offset T:{OFFSET_TEMP:.2f} H:{OFFSET_HUM:.2f} P:{OFFSET_PRES:.2f}") |
43 | | -print("Calibration OK") |
44 | | - |
45 | | - |
46 | | -# === Dessin === |
47 | | -def draw_hline(x, y, w, color=15): |
48 | | - for i in range(w): |
49 | | - display.pixel(x + i, y, color) |
50 | | - |
51 | | - |
52 | | -def draw_vline(x, y, h, color=15): |
53 | | - for i in range(h): |
54 | | - display.pixel(x, y + i, color) |
55 | | - |
56 | | - |
57 | | -def draw_rect(x, y, w, h, color=15): |
58 | | - draw_hline(x, y, w, color) |
59 | | - draw_hline(x, y + h - 1, w, color) |
60 | | - draw_vline(x, y, h, color) |
61 | | - draw_vline(x + w - 1, y, h, color) |
62 | | - |
63 | | - |
64 | | -def draw_fill_rect(x, y, w, h, color=15): |
65 | | - for i in range(h): |
66 | | - draw_hline(x, y + i, w, color) |
67 | | - |
68 | | - |
69 | | -def draw_circle(cx, cy, r, color=15): |
70 | | - x = r |
71 | | - y = 0 |
72 | | - err = 0 |
73 | | - while x >= y: |
74 | | - display.pixel(cx + x, cy + y, color) |
75 | | - display.pixel(cx + y, cy + x, color) |
76 | | - display.pixel(cx - y, cy + x, color) |
77 | | - display.pixel(cx - x, cy + y, color) |
78 | | - display.pixel(cx - x, cy - y, color) |
79 | | - display.pixel(cx - y, cy - x, color) |
80 | | - display.pixel(cx + y, cy - x, color) |
81 | | - display.pixel(cx + x, cy - y, color) |
82 | | - y += 1 |
83 | | - err += 1 + 2 * y |
84 | | - if 2 * (err - x) + 1 > 0: |
85 | | - x -= 1 |
86 | | - err += 1 - 2 * x |
87 | | - |
88 | | - |
89 | | -def draw_bar_h(x, y, w, h, value, min_val, max_val, color=13): |
90 | | - draw_rect(x, y, w, h, 5) |
91 | | - ratio = (value - min_val) / (max_val - min_val) |
92 | | - ratio = max(0.0, min(1.0, ratio)) |
93 | | - filled = int((w - 2) * ratio) |
94 | | - if filled > 0: |
95 | | - draw_fill_rect(x + 1, y + 1, filled, h - 2, color) |
| 34 | +# --- Comfort thresholds --- |
| 35 | +TEMP_MIN, TEMP_MAX = 18.0, 26.0 |
| 36 | +HUM_MIN, HUM_MAX = 40.0, 60.0 |
| 37 | +PRES_MIN, PRES_MAX = 1000.0, 1025.0 |
| 38 | + |
| 39 | +POLL_MS = 1000 |
96 | 40 |
|
97 | 41 |
|
98 | 42 | def comfort_label(temp, hum, pres): |
99 | | - if 18 <= temp <= 26 and 40 <= hum <= 60 and 1000 <= pres <= 1025: |
100 | | - return "IDEAL", 15 |
101 | | - elif temp > 30 or hum > 75: |
102 | | - return "CHAUD", 11 |
103 | | - elif temp < 15 or hum < 25: |
104 | | - return "FROID", 9 |
105 | | - elif pres < 1000: |
106 | | - return "BASSE P", 9 |
107 | | - elif pres > 1025: |
108 | | - return "HAUTE P", 13 |
109 | | - else: |
110 | | - return "CORRECT", 12 |
| 43 | + """Return a (label, color) tuple describing indoor comfort.""" |
| 44 | + if TEMP_MIN <= temp <= TEMP_MAX and HUM_MIN <= hum <= HUM_MAX and PRES_MIN <= pres <= PRES_MAX: |
| 45 | + return "IDEAL", GREEN |
| 46 | + if temp > 30 or hum > 75: |
| 47 | + return "HOT", RED |
| 48 | + if temp < 15 or hum < 25: |
| 49 | + return "COLD", GRAY |
| 50 | + if pres < PRES_MIN: |
| 51 | + return "LOW P", GRAY |
| 52 | + if pres > PRES_MAX: |
| 53 | + return "HIGH P", LIGHT |
| 54 | + return "OK", WHITE |
111 | 55 |
|
112 | 56 |
|
113 | 57 | def draw_screen(temp, hum, pres): |
114 | | - display.fill(0) |
115 | | - |
116 | | - # Bordure circulaire |
117 | | - draw_circle(64, 64, 62, 4) |
118 | | - draw_circle(64, 64, 60, 2) |
119 | | - |
120 | | - # === TITRE === |
121 | | - display.text("CLIMAT", 35, 20, 15) |
122 | | - draw_hline(19, 29, 90, 6) |
123 | | - |
124 | | - # === TEMPERATURE === |
125 | | - display.text("T", 19, 35, 7) |
126 | | - temp_str = f"{temp:.1f}C" |
127 | | - display.text(temp_str, 29, 35, 15) |
128 | | - draw_bar_h(19, 44, 90, 5, temp, 0, 50, 13) |
129 | | - |
130 | | - # === HUMIDITE === |
131 | | - display.text("H", 19, 52, 7) |
132 | | - hum_str = f"{hum:.1f}%" |
133 | | - display.text(hum_str, 29, 52, 15) |
134 | | - draw_bar_h(19, 61, 90, 5, hum, 0, 100, 11) |
135 | | - |
136 | | - # === PRESSION === |
137 | | - display.text("P", 19, 69, 7) |
138 | | - pres_str = f"{pres:.0f}hPa" |
139 | | - display.text(pres_str, 29, 69, 15) |
140 | | - draw_bar_h(19, 78, 90, 5, pres, 950, 1050, 10) |
141 | | - |
142 | | - # === SEPARATEUR === |
143 | | - draw_hline(19, 86, 90, 4) |
144 | | - |
145 | | - # === CONFORT === |
146 | | - label, c_color = comfort_label(temp, hum, pres) |
147 | | - cx = 64 - len(label) * 4 |
148 | | - display.text(label, cx, 92, c_color) |
149 | | - |
150 | | - # === CAPTEURS === |
151 | | - draw_hline(19, 102, 90, 3) |
152 | | - display.text("PADS+HTS221", 22, 106, 4) |
153 | | - |
154 | | - display.show() |
155 | | - |
156 | | - |
157 | | -# === Boucle principale === |
158 | | -print("Station climatique demarree") |
159 | | -while True: |
160 | | - try: |
161 | | - temp = pads.temperature() + OFFSET_TEMP |
162 | | - hum = hts.humidity() + OFFSET_HUM |
163 | | - pres = pads.pressure_hpa() + OFFSET_PRES |
| 58 | + """Render one frame with all readings and comfort index.""" |
| 59 | + label, color = comfort_label(temp, hum, pres) |
| 60 | + |
| 61 | + screen.clear() |
| 62 | + screen.gauge( |
| 63 | + int(temp), min_val=0, max_val=50, color=color, |
| 64 | + ) |
| 65 | + screen.title("CLIMATE") |
| 66 | + screen.value("{:.1f}".format(temp), unit="C") |
| 67 | + screen.subtitle( |
| 68 | + "H:{:.0f}% P:{:.0f}hPa".format(hum, pres), |
| 69 | + label, |
| 70 | + ) |
| 71 | + screen.show() |
| 72 | + |
| 73 | + |
| 74 | +# --- Main loop --- |
| 75 | +print("Climate station started") |
| 76 | +print("Press Ctrl+C to exit.") |
| 77 | + |
| 78 | +try: |
| 79 | + while True: |
| 80 | + temp = pads.temperature() |
| 81 | + hum = hts.humidity() |
| 82 | + pres = pads.pressure_hpa() |
164 | 83 | hum = max(0.0, min(100.0, hum)) |
165 | | - print(f"T:{temp:.2f}C H:{hum:.2f}% P:{pres:.1f}hPa") |
| 84 | + print("T:{:.1f}C H:{:.1f}% P:{:.0f}hPa".format(temp, hum, pres)) |
166 | 85 | draw_screen(temp, hum, pres) |
167 | | - except Exception as e: |
168 | | - print("Erreur:", e) |
169 | | - display.fill(0) |
170 | | - display.text("ERREUR", 35, 55, 15) |
171 | | - display.text(str(e)[:16], 0, 70, 9) |
172 | | - display.show() |
173 | | - sleep_ms(1000) |
| 86 | + sleep_ms(POLL_MS) |
| 87 | +except KeyboardInterrupt: |
| 88 | + print("\nClimate station stopped.") |
| 89 | +finally: |
| 90 | + screen.clear() |
| 91 | + screen.show() |
| 92 | + pads.power_off() |
| 93 | + hts.power_off() |
0 commit comments