|
| 1 | +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries |
| 2 | +# SPDX-FileCopyrightText: Copyright (c) 2026 ThingsBoard Inc. |
| 3 | +# |
| 4 | +# SPDX-License-Identifier: Unlicense |
| 5 | + |
| 6 | +import time |
| 7 | + |
| 8 | +import adafruit_sht31d |
| 9 | +import adafruit_ssd1306 |
| 10 | +import board |
| 11 | +import busio |
| 12 | +import requests |
| 13 | + |
| 14 | +from tb_device_mqtt import TBDeviceMqttClient |
| 15 | + |
| 16 | +THINGSBOARD_HOST = "YOUR_THINGSBOARD_HOST" |
| 17 | +THINGSBOARD_TOKEN = "YOUR_THINGSBOARD_DEVICE_TOKEN" |
| 18 | +THINGSBOARD_PORT = 1883 |
| 19 | + |
| 20 | +OPENWEATHER_API_KEY = "YOUR_WEATHER_API_KEY" |
| 21 | +DEFAULT_CITY = "Kyiv,UA" |
| 22 | + |
| 23 | +WEATHER_UPDATE_PERIOD = 600 |
| 24 | +DISPLAY_REFRESH_PERIOD = 2 |
| 25 | +TELEMETRY_SEND_PERIOD = 10 |
| 26 | + |
| 27 | +selected_city = DEFAULT_CITY |
| 28 | + |
| 29 | +i2c = busio.I2C(board.SCL, board.SDA) |
| 30 | +display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c) |
| 31 | +sensor = adafruit_sht31d.SHT31D(i2c) |
| 32 | + |
| 33 | + |
| 34 | +def draw_screen(indoor_temp, indoor_humidity, outdoor_temp, outdoor_humidity): |
| 35 | + display.fill(0) |
| 36 | + |
| 37 | + display.text(f"InT:{indoor_temp:5.1f}C", 0, 0, 1) |
| 38 | + display.text(f"InH:{indoor_humidity:5.1f}%", 64, 0, 1) |
| 39 | + |
| 40 | + if outdoor_temp is not None: |
| 41 | + display.text(f"OutT:{outdoor_temp:4.1f}C", 0, 16, 1) |
| 42 | + else: |
| 43 | + display.text("OutT: N/A", 0, 16, 1) |
| 44 | + |
| 45 | + if outdoor_humidity is not None: |
| 46 | + display.text(f"OutH:{outdoor_humidity:4.1f}%", 64, 16, 1) |
| 47 | + else: |
| 48 | + display.text("OutH: N/A", 64, 16, 1) |
| 49 | + |
| 50 | + display.show() |
| 51 | + |
| 52 | + |
| 53 | +def get_current_weather(city_name): |
| 54 | + url = "https://api.openweathermap.org/data/2.5/weather" |
| 55 | + params = {"q": city_name, "appid": OPENWEATHER_API_KEY, "units": "metric"} |
| 56 | + |
| 57 | + try: |
| 58 | + response = requests.get(url, params=params, timeout=10) |
| 59 | + response.raise_for_status() |
| 60 | + data = response.json() |
| 61 | + |
| 62 | + return { |
| 63 | + "temp": float(data["main"]["temp"]), |
| 64 | + "humidity": float(data["main"]["humidity"]), |
| 65 | + } |
| 66 | + except Exception as e: |
| 67 | + print("Weather request failed:", e) |
| 68 | + return None |
| 69 | + |
| 70 | + |
| 71 | +def main(): |
| 72 | + weather_data = {"temp": None, "humidity": None} |
| 73 | + |
| 74 | + last_weather_update = 0.0 |
| 75 | + last_display_refresh = 0.0 |
| 76 | + last_telemetry_send = 0.0 |
| 77 | + |
| 78 | + client = TBDeviceMqttClient( |
| 79 | + THINGSBOARD_HOST, port=THINGSBOARD_PORT, access_token=THINGSBOARD_TOKEN |
| 80 | + ) |
| 81 | + client.connect() |
| 82 | + |
| 83 | + initial_weather = get_current_weather(selected_city) |
| 84 | + if initial_weather is not None: |
| 85 | + weather_data["temp"] = initial_weather["temp"] |
| 86 | + weather_data["humidity"] = initial_weather["humidity"] |
| 87 | + |
| 88 | + while True: |
| 89 | + now = time.monotonic() |
| 90 | + |
| 91 | + indoor_temp = sensor.temperature |
| 92 | + indoor_humidity = sensor.relative_humidity |
| 93 | + |
| 94 | + if now - last_weather_update >= WEATHER_UPDATE_PERIOD: |
| 95 | + last_weather_update = now |
| 96 | + updated_weather = get_current_weather(selected_city) |
| 97 | + if updated_weather is not None: |
| 98 | + weather_data["temp"] = updated_weather["temp"] |
| 99 | + weather_data["humidity"] = updated_weather["humidity"] |
| 100 | + |
| 101 | + if now - last_display_refresh >= DISPLAY_REFRESH_PERIOD: |
| 102 | + last_display_refresh = now |
| 103 | + draw_screen( |
| 104 | + indoor_temp, indoor_humidity, weather_data["temp"], weather_data["humidity"] |
| 105 | + ) |
| 106 | + |
| 107 | + if now - last_telemetry_send >= TELEMETRY_SEND_PERIOD: |
| 108 | + last_telemetry_send = now |
| 109 | + |
| 110 | + telemetry = { |
| 111 | + "indoorTemp": round(indoor_temp, 1), |
| 112 | + "indoorHumidity": round(indoor_humidity, 1), |
| 113 | + "outdoorTemp": weather_data["temp"], |
| 114 | + "outdoorHumidity": weather_data["humidity"], |
| 115 | + "city": selected_city, |
| 116 | + } |
| 117 | + |
| 118 | + try: |
| 119 | + client.send_telemetry(telemetry) |
| 120 | + print("Telemetry sent:", telemetry) |
| 121 | + except Exception as e: |
| 122 | + print("Telemetry send failed:", e) |
| 123 | + |
| 124 | + time.sleep(0.1) |
| 125 | + |
| 126 | + |
| 127 | +if __name__ == "__main__": |
| 128 | + main() |
0 commit comments