.. include:: /index.rst
:start-after: start_hello_message
:end-before: end_hello_message
.. _py_smart_weather_station:
(Example) スマート気象ステーション
=============================================
**はじめに**
このプロジェクトでは、ローカル環境センサー、クラウドの気象データ、そして AI 分析を組み合わせた包括的な **スマート気象ステーション** を構築します。システムには次の機能が統合されています:
1. **ローカルセンサーデータ**:DHT11(温度/湿度)および LDR(光センサー)から取得
2. **世界の天気予報データ**:OpenWeather API から取得
3. **AI 音声分析**:OpenAI の GPT と TTS 機能を利用
4. **視覚表示**:128×64 OLED ディスプレイに表示
5. **インタラクティブボタン**:必要に応じて AI による天気分析を実行
.. raw:: html
この気象ステーションは、ローカル環境データと天気予報データを自動的に比較し、音声出力によってインテリジェントなアドバイスを提供します。これにより、総合的な環境モニタリングシステムを実現します。
また、他の LLM モジュールや TTS モジュールを利用して、自分だけのスマートデバイスを構築することもできます。
参照:
* :ref:`py_online_llm`
* :ref:`tts_espeak_pico2wave`
* :ref:`tts_piper_openai`
----------------------------------------------
**必要なもの**
このプロジェクトに必要な部品は以下の通りです:
.. list-table::
:widths: 30 20
:header-rows: 1
* - COMPONENT
- PURCHASE LINK
* - :ref:`cpn_humiture_sensor`
- |link_humiture_buy|
* - :ref:`cpn_photoresistor`
- |link_photoresistor_buy|
* - :ref:`cpn_button`
- |link_button_buy|
* - :ref:`cpn_oled`
- \-
* - :ref:`cpn_fusion_hat`
- \-
* - :ref:`cpn_wires`
- |link_wires_buy|
* - :ref:`cpn_resistor`
- |link_resistor_buy|
* - Raspberry Pi
- \-
----------------------------------------------
**配線図**
以下のようにコンポーネントを Fusion HAT+ に接続します:
.. image:: img/fzz/llm_weather_bb.png
:width: 80%
:align: center
----------------------------------------------
.. include:: python_online_llms.rst
:start-after: start_setup_openai
:end-before: end_setup_openai
---------------------------------------------
**OpenWeather API キーの取得**
|link_openweather| は OpenWeather Ltd が提供するオンラインサービスで、API を通じて世界中の天気データ(現在の天気、予報、ナウキャスト、過去の気象データなど)を取得できます。
#. |link_openweather| にアクセスしてログイン、またはアカウントを作成します。
.. image:: img/OWM-1.png
#. ナビゲーションバーから **API** ページを開きます。
.. image:: img/OWM-2.png
#. **Current Weather Data** を見つけて **Subscribe** をクリックします。
.. image:: img/OWM-3.png
#. **Current weather and forecasts collection** の中から適切なサービスを購読します。
このプロジェクトでは Free プランで十分です。
.. image:: img/OWM-4.png
#. **API keys** ページから Key をコピーします。
.. image:: img/OWM-5.png
#. 次のコマンドで ``secret.py`` ファイルを開きます:
.. raw:: html
.. code-block:: bash
cd ~/ai-lab-kit/llm
sudo nano secret.py
#. コピーした API Key を追加します:
.. code-block:: shell
:emphasize-lines: 1
OPENWEATHER_API_KEY = "732exxxxxxxxxxxxxxxxxxxxx919b"
#. ``Ctrl + X`` → ``Y`` → ``Enter`` の順に押して保存し、エディタを終了します。
---------------------------------------------
**サンプルの実行**
#. コードを実行する
.. raw:: html
.. code-block:: shell
cd ~/ai-lab-kit/llm
sudo python3 llm_openai_weather.py
#. スクリプト起動後の表示
* OLED が起動し、天気情報の表示を開始します。
* ターミナルには起動情報(対象都市やボタンの GPIO ピンなど)が表示されます。
* OLED は **10 秒ごと** にページが自動切り替えされます(全 3 ページ):
- **ページ1:ローカルセンサー** (DHT11 + LDR)
温度、湿度、光レベル(小さなライトバー付き)を表示します。
- **ページ2:天気予報** (OpenWeather)
現在の気温、天気の説明、最終更新時刻を表示します。
- **ページ3:AI インサイト**
ローカルセンサーと OpenWeather データの差分、および簡単な快適度(例:Comfortable / Warm / Cool / Humid / Dry)を表示します。
#. AI 音声分析の実行
**GPIO 27** のボタンを押すと、AI が短い「気象レポーター風」の分析を生成します。
* ターミナルには ``AI Analysis`` セクションが表示され、以下の情報が出力されます:
- ローカル測定値(温度 / 湿度 / 光)
- 外部天気情報(OpenWeather の気温と説明)
- AI が生成した簡単な要約
* OLED には一時的に **SPEAKING...** と表示されます
* 分析結果は OpenAI TTS を使用してスピーカーから音声で読み上げられます
#. データ更新の動作
* ローカルセンサーは **約2秒ごと** に更新されます。
* OpenWeather のデータは **約5分ごと** に更新されます。
* 光センサーの値はちらつきを抑えるため自動的に平滑化されます。
#. プログラムの停止
* ターミナルで ``Ctrl+C`` を押すと終了します。
* OLED がクリアされ、安全にプログラムが停止します。
----------------------------------------------
**コード**
以下はスマート気象ステーションの Python スクリプト全体です:
.. raw:: html
.. code-block:: python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Smart Weather Station with AI Assistant
- Reads local temperature & humidity from DHT11 on GPIO 17
- Reads light level from LDR on ADC A0 (0..4095)
- Fetches weather forecast from OpenWeather API
- Provides AI voice analysis using OpenAI (triggered by button)
- Displays all information on 128x64 SSD1306 OLED
"""
import time
import requests
from datetime import datetime
from statistics import mean
from fusion_hat.modules import DHT11
from fusion_hat.adc import ADC
from fusion_hat.pin import Pin, Mode, Pull
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import board
from sunfounder_voice_assistant.tts import OpenAI_TTS
from secret import OPENAI_API_KEY, OPENWEATHER_API_KEY
from signal import pause
# Configuration
DHT_PIN = 17 # DHT11 uses GPIO 17
LDR_CH = 0
I2C_ADDR = 0x3C
# OpenWeather API Configuration
CITY_NAME = "Shanghai"
COUNTRY_CODE = "CN"
LATITUDE = 31.2304
LONGITUDE = 121.4737
UNITS = "metric"
# Update intervals in seconds
WEATHER_UPDATE_INTERVAL = 300
SENSOR_UPDATE_INTERVAL = 2
# GPIO Pins
BUTTON_PIN = 27 # Button uses GPIO 27
# OLED Setup
WIDTH, HEIGHT = 128, 64
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=I2C_ADDR)
oled.fill(0)
oled.show()
# Load fonts
try:
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
font_medium = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
except:
print("Warning: Using default font")
font_small = ImageFont.load_default()
font_medium = ImageFont.load_default()
font_large = ImageFont.load_default()
image = Image.new("1", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(image)
# Sensors
dht = DHT11(pin=DHT_PIN)
ldr = ADC(LDR_CH)
# Button for triggering AI analysis
button = Pin(BUTTON_PIN, mode=Mode.IN, pull=Pull.DOWN)
# OpenWeather API Class
class WeatherAPI:
def __init__(self, api_key, city, country_code, lat=None, lon=None):
self.api_key = api_key
self.city = city
self.country_code = country_code
self.lat = lat
self.lon = lon
self.current_weather = None
self.forecast = None
self.last_update = 0
def get_weather_url(self):
if self.lat and self.lon:
return f"https://api.openweathermap.org/data/2.5/weather?lat={self.lat}&lon={self.lon}&appid={self.api_key}&units={UNITS}"
else:
return f"https://api.openweathermap.org/data/2.5/weather?q={self.city},{self.country_code}&appid={self.api_key}&units={UNITS}"
def get_forecast_url(self):
if self.lat and self.lon:
return f"https://api.openweathermap.org/data/2.5/forecast?lat={self.lat}&lon={self.lon}&appid={self.api_key}&units={UNITS}"
else:
return f"https://api.openweathermap.org/data/2.5/forecast?q={self.city},{self.country_code}&appid={self.api_key}&units={UNITS}"
def update_weather(self):
try:
# Current weather
response = requests.get(self.get_weather_url(), timeout=10)
if response.status_code == 200:
self.current_weather = response.json()
print(f"Weather API success: {self.current_weather['weather'][0]['description']}")
else:
print(f"Weather API error: {response.status_code}")
return False
# Forecast
response = requests.get(self.get_forecast_url(), timeout=10)
if response.status_code == 200:
self.forecast = response.json()
self.last_update = time.time()
return True
except Exception as e:
print(f"Weather API error: {e}")
return False
def get_temperature(self):
if self.current_weather:
return self.current_weather['main']['temp']
return None
def get_humidity(self):
if self.current_weather:
return self.current_weather['main']['humidity']
return None
def get_weather_description(self):
if self.current_weather:
return self.current_weather['weather'][0]['description'].title()
return None
def get_weather_condition(self):
if self.current_weather:
weather_id = self.current_weather['weather'][0]['id']
if weather_id < 300:
return "TSTORM"
elif weather_id < 600:
return "RAIN"
elif weather_id < 700:
return "SNOW"
elif weather_id == 800:
return "CLEAR"
elif weather_id < 900:
return "CLOUDS"
else:
return "OTHER"
return "N/A"
def get_forecast_summary(self):
if not self.forecast or 'list' not in self.forecast:
return "No forecast"
forecasts = self.forecast['list'][:8]
temps = [f['main']['temp'] for f in forecasts]
min_temp = min(temps)
max_temp = max(temps)
conditions = {}
for f in forecasts:
condition = f['weather'][0]['main']
conditions[condition] = conditions.get(condition, 0) + 1
most_common = max(conditions, key=conditions.get)
return f"{min_temp:.0f}-{max_temp:.0f}C {most_common}"
# AI Weather Analyst Class
class WeatherAI:
def __init__(self, api_key):
self.api_key = api_key
self.tts = OpenAI_TTS(api_key=api_key)
self.tts.set_voice(self.tts.Voice.ALLOY)
self.is_speaking = False
def analyze_weather(self, local_temp, local_hum, local_light, weather_data):
temp_diff = abs(local_temp - weather_data.get('current_temp', local_temp)) if weather_data.get('current_temp') else 0
if temp_diff > 3:
accuracy = "significantly different from"
elif temp_diff > 1:
accuracy = "slightly different from"
else:
accuracy = "very close to"
recommendations = []
if local_hum > 80:
recommendations.append("It's quite humid")
elif local_hum < 30:
recommendations.append("The air is dry")
if local_light > 80:
recommendations.append("It's bright here")
elif local_light < 20:
recommendations.append("It's quite dark")
weather_desc = weather_data.get('weather_desc', '').lower()
if 'rain' in weather_desc or 'drizzle' in weather_desc or 'thunderstorm' in weather_desc:
recommendations.append("Don't forget an umbrella")
elif 'clear' in weather_desc:
recommendations.append("Great day to go outside")
elif 'cloud' in weather_desc:
recommendations.append("Partly cloudy today")
rec_text = ". ".join(recommendations) + "." if recommendations else "Conditions are normal."
analysis = f"Local sensors show {local_temp:.1f}C, which is {accuracy} the forecast. {rec_text}"
return analysis
def speak_analysis(self, analysis_text):
if self.is_speaking:
print("Already speaking, please wait...")
return False
try:
self.is_speaking = True
print(f"Speaking analysis: {analysis_text}")
self.tts.say(analysis_text, instructions="speak clearly and professionally like a weather reporter")
self.is_speaking = False
return True
except Exception as e:
print(f"TTS error: {e}")
self.is_speaking = False
return False
# Light sensor helper
_light_window = []
def light_percent(raw, min_val=0, max_val=4095):
global _light_window
_light_window.append(raw)
if len(_light_window) > 5:
_light_window.pop(0)
smooth_raw = int(mean(_light_window))
pct = (smooth_raw - min_val) / (max_val - min_val) * 100 if max_val > min_val else 50
pct = max(0, min(100, pct))
return int(pct), smooth_raw
# Display Manager Class
class DisplayManager:
def __init__(self):
self.current_page = 0
self.num_pages = 3
self.last_page_change = 0
self.page_cycle_interval = 10
def next_page(self):
self.current_page = (self.current_page + 1) % self.num_pages
self.last_page_change = time.time()
def should_change_page(self):
return time.time() - self.last_page_change > self.page_cycle_interval
def draw_page(self, page_num, local_temp, local_hum, light_pct, weather_api, weather_ai):
draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
if page_num == 0:
self._draw_local_sensors(local_temp, local_hum, light_pct)
elif page_num == 1:
self._draw_weather_forecast(weather_api)
elif page_num == 2:
self._draw_ai_insights(local_temp, local_hum, light_pct, weather_api)
# Page indicator at bottom right
indicator = f"{page_num+1}/{self.num_pages}"
indicator_width = len(indicator) * 6
draw.text((WIDTH - indicator_width - 2, HEIGHT - 10), indicator, font=font_small, fill=255)
def _draw_local_sensors(self, temp, hum, light):
# Title at top
draw.text((2, 2), "LOCAL SENSORS", font=font_medium, fill=255)
# Temperature - larger font on first line
temp_text = f"Temp: {temp:.1f} C"
draw.text((10, 18), temp_text, font=font_large, fill=255)
# Humidity - second line
hum_text = f"Humidity: {hum:.1f}%"
draw.text((10, 38), hum_text, font=font_medium, fill=255)
# Light with bar on same line
light_text = f"Light: {light}%"
draw.text((10, 53), light_text, font=font_small, fill=255)
# Light bar positioned next to text, not overlapping
bar_x = 60 # Position after "Light: XX%"
bar_y = 55
bar_width = 50
bar_height = 4
# Draw background bar
draw.rectangle((bar_x, bar_y, bar_x + bar_width, bar_y + bar_height), outline=255, fill=0)
# Draw filled portion
fill_width = int(bar_width * light / 100)
if fill_width > 0:
draw.rectangle((bar_x, bar_y, bar_x + fill_width, bar_y + bar_height), outline=0, fill=255)
def _draw_weather_forecast(self, weather_api):
draw.text((2, 2), "WEATHER", font=font_medium, fill=255)
if not weather_api.current_weather:
draw.text((10, 25), "No weather data", font=font_medium, fill=255)
draw.text((10, 45), "Check connection", font=font_small, fill=255)
return
current_temp = weather_api.get_temperature()
weather_desc = weather_api.get_weather_description()
weather_cond = weather_api.get_weather_condition()
# Temperature - large font
if current_temp is not None:
draw.text((10, 18), f"{current_temp:.0f} C", font=font_large, fill=255)
# Weather description
if weather_desc:
desc_text = weather_desc[:15]
draw.text((10, 38), desc_text, font=font_medium, fill=255)
# Weather condition
if weather_cond:
draw.text((10, 53), weather_cond, font=font_small, fill=255)
# Update time at top right
if weather_api.last_update > 0:
update_time = datetime.fromtimestamp(weather_api.last_update).strftime("%H:%M")
update_text = f"Up: {update_time}"
update_width = len(update_text) * 6
draw.text((WIDTH - update_width - 2, 2), update_text, font=font_small, fill=255)
def _draw_ai_insights(self, local_temp, local_hum, light_pct, weather_api):
draw.text((2, 2), "AI INSIGHTS", font=font_medium, fill=255)
api_temp = weather_api.get_temperature() if weather_api.current_weather else None
api_hum = weather_api.get_humidity() if weather_api.current_weather else None
line_y = 18
# Temperature difference
if api_temp is not None:
temp_diff = local_temp - api_temp
temp_symbol = "+" if temp_diff > 0 else "" if temp_diff == 0 else ""
diff_text = f"Temp: {temp_symbol}{temp_diff:.1f}C"
draw.text((10, line_y), diff_text, font=font_medium, fill=255)
line_y += 15
# Humidity difference
if api_hum is not None:
hum_diff = local_hum - api_hum
hum_symbol = "+" if hum_diff > 0 else "" if hum_diff == 0 else ""
diff_text = f"Hum: {hum_symbol}{hum_diff:.1f}%"
draw.text((10, line_y), diff_text, font=font_medium, fill=255)
line_y += 15
# Comfort level
comfort = "Normal"
comfort_color = 255
if 20 <= local_temp <= 25 and 40 <= local_hum <= 60:
comfort = "Comfortable"
comfort_color = 255
elif local_temp > 28:
comfort = "Warm"
comfort_color = 255
elif local_temp < 16:
comfort = "Cool"
comfort_color = 255
elif local_hum > 70:
comfort = "Humid"
comfort_color = 255
elif local_hum < 30:
comfort = "Dry"
comfort_color = 255
draw.text((10, line_y), f"Feel: {comfort}", font=font_small, fill=comfort_color)
# Button hint at bottom left
draw.text((2, HEIGHT - 10), "Press BTN for AI", font=font_small, fill=255)
# Main Application Class
class SmartWeatherStation:
def __init__(self):
print("Initializing Smart Weather Station...")
self.weather_api = WeatherAPI(OPENWEATHER_API_KEY, CITY_NAME, COUNTRY_CODE, LATITUDE, LONGITUDE)
self.weather_ai = WeatherAI(OPENAI_API_KEY)
self.display = DisplayManager()
self.local_temp = 0.0
self.local_hum = 0.0
self.light_pct = 0
self.raw_adc = 0
self.last_weather_update = 0
self.last_sensor_update = 0
# Setup button callback
button.when_activated = self._button_pressed
# Initial readings
self._update_sensors()
self.weather_api.update_weather()
print("Smart Weather Station ready!")
print(f"City: {CITY_NAME}")
print(f"Temperature unit: {UNITS}")
print(f"Button on GPIO {BUTTON_PIN} for AI analysis")
print("="*50)
def _update_sensors(self):
try:
result = dht.read()
if result:
hum, temp = result
self.local_hum = float(hum)
self.local_temp = float(temp)
raw = ldr.read()
self.light_pct, self.raw_adc = light_percent(raw)
self.last_sensor_update = time.time()
return True
except Exception as e:
print(f"Sensor error: {e}")
return False
def _update_weather(self):
if time.time() - self.last_weather_update > WEATHER_UPDATE_INTERVAL:
print("Updating weather data...")
if self.weather_api.update_weather():
self.last_weather_update = time.time()
return True
return False
def _button_pressed(self):
"""Called when button is pressed"""
print("\n" + "="*50)
print("Button pressed! Triggering AI analysis...")
print("="*50)
# Update sensors first to get latest data
self._update_sensors()
# Get weather data
api_temp = self.weather_api.get_temperature()
if api_temp is None:
print("No weather data available. Please wait for update.")
return
# Prepare weather data for analysis
weather_data = {
'current_temp': api_temp,
'weather_desc': self.weather_api.get_weather_description(),
'forecast_summary': self.weather_api.get_forecast_summary()
}
# Generate analysis
analysis = self.weather_ai.analyze_weather(
self.local_temp,
self.local_hum,
self.light_pct,
weather_data
)
print(f"\nAI Analysis:")
print(f"Local: {self.local_temp:.1f}C, {self.local_hum:.1f}%, Light: {self.light_pct}%")
print(f"Remote: {api_temp}C, {self.weather_api.get_weather_description()}")
print(f"Analysis: {analysis}")
# Show "Speaking..." on display
self._show_speaking_message()
# Speak the analysis
success = self.weather_ai.speak_analysis(analysis)
if success:
print("Analysis completed successfully!")
else:
print("Analysis failed or interrupted.")
print("="*50)
def _show_speaking_message(self):
"""Display a temporary "Speaking..." message"""
draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
draw.text((WIDTH//2 - 40, HEIGHT//2 - 10), "SPEAKING...", font=font_medium, fill=255)
oled.image(image)
oled.show()
def run(self):
print("\n" + "="*50)
print("SMART WEATHER STATION")
print("="*50)
print("Display Pages:")
print("1. Local Sensors (DHT11 + LDR)")
print("2. Weather Forecast (OpenWeather)")
print("3. AI Insights (Comparisons)")
print("")
print(f"Press button on GPIO {BUTTON_PIN} for AI voice analysis")
print("Press Ctrl+C to exit")
print("="*50 + "\n")
try:
while True:
current_time = time.time()
# Update sensors periodically
if current_time - self.last_sensor_update > SENSOR_UPDATE_INTERVAL:
self._update_sensors()
# Update weather data periodically
self._update_weather()
# Auto-cycle display pages
if self.display.should_change_page():
self.display.next_page()
# Draw current page
self.display.draw_page(
self.display.current_page,
self.local_temp,
self.local_hum,
self.light_pct,
self.weather_api,
self.weather_ai
)
# Update OLED display
oled.image(image)
oled.show()
# Small delay to prevent CPU overload
time.sleep(0.1)
except KeyboardInterrupt:
print("\nShutting down...")
finally:
# Cleanup
oled.fill(0)
oled.show()
print("Smart Weather Station stopped.")
# Main Entry Point
if __name__ == "__main__":
if not OPENAI_API_KEY or OPENAI_API_KEY == "your-openai-api-key-here":
print("ERROR: Please set your OpenAI API key in secret.py")
exit(1)
if not OPENWEATHER_API_KEY or OPENWEATHER_API_KEY == "your-openweather-api-key-here":
print("ERROR: Please set your OpenWeather API key in secret.py")
print("Get one at: https://openweathermap.org/api")
exit(1)
station = SmartWeatherStation()
station.run()
----------------------------------------------
**コードの理解**
1. センサー統合
このシステムは 2 種類のローカルセンサーから値を読み取ります:
.. code-block:: python
# DHT11 for temperature and humidity
dht = DHT11(pin=DHT_PIN)
result = dht.read() # Returns (humidity, temperature)
# LDR (Light Dependent Resistor) through ADC
ldr = ADC(LDR_CH)
raw = ldr.read() # Returns 0-4095 value
2. OpenWeather API の統合
``WeatherAPI`` クラスは、現在の天気と予報を取得するための OpenWeather 接続を管理します:
.. code-block:: python
class WeatherAPI:
def update_weather(self):
# Current weather endpoint
response = requests.get(self.get_weather_url(), timeout=10)
self.current_weather = response.json()
# Forecast endpoint
response = requests.get(self.get_forecast_url(), timeout=10)
self.forecast = response.json()
3. AI 分析エンジン
``WeatherAI`` クラスは、インテリジェントな天気分析を生成し、それを音声に変換します:
.. code-block:: python
class WeatherAI:
def analyze_weather(self, local_temp, local_hum, local_light, weather_data):
# Calculate temperature difference
temp_diff = abs(local_temp - weather_data.get('current_temp', local_temp))
# Generate recommendations based on conditions
recommendations = []
if local_hum > 80:
recommendations.append("It's quite humid")
# Combine into analysis text
analysis = f"Local sensors show {local_temp:.1f}C..."
return analysis
def speak_analysis(self, analysis_text):
self.tts.say(analysis_text, instructions="speak clearly...")
4. マルチページ表示システム
``DisplayManager`` は、自動切り替えされる 3 つの情報ページを管理します:
.. code-block:: python
class DisplayManager:
def draw_page(self, page_num, ...):
if page_num == 0:
self._draw_local_sensors(...)
elif page_num == 1:
self._draw_weather_forecast(...)
elif page_num == 2:
self._draw_ai_insights(...)
def _draw_local_sensors(self, temp, hum, light):
# Draw temperature, humidity, and light level with progress bar
5. ボタンイベント処理
ボタンを押すと AI 音声分析が実行されます:
.. code-block:: python
button = Pin(BUTTON_PIN, mode=Mode.IN, pull=Pull.DOWN)
button.when_activated = self._button_pressed
def _button_pressed(self):
# Update sensors, generate analysis, and speak
analysis = self.weather_ai.analyze_weather(...)
self.weather_ai.speak_analysis(analysis)
6. 光センサーのデータ平滑化
光センサーの値には移動平均を用いて、安定した読み取り値を得ています:
.. code-block:: python
def light_percent(raw, min_val=0, max_val=4095):
_light_window.append(raw)
if len(_light_window) > 5:
_light_window.pop(0)
smooth_raw = int(mean(_light_window)) # Moving average
pct = (smooth_raw - min_val) / (max_val - min_val) * 100
7. メインアプリケーションループ
``SmartWeatherStation`` クラスは、適切なタイミングで全コンポーネントを連携させます:
.. code-block:: python
def run(self):
while True:
# Update sensors every 2 seconds
if time.time() - self.last_sensor_update > SENSOR_UPDATE_INTERVAL:
self._update_sensors()
# Update weather every 5 minutes
self._update_weather()
# Auto-cycle pages every 10 seconds
if self.display.should_change_page():
self.display.next_page()
# Draw current page
self.display.draw_page(...)
----------------------------------------------
**トラブルシューティング**
- "DHT11 read failed" エラー
- 配線を確認してください:VCC(3.3V)、DATA(GPIO 17)、GND
- DATA と VCC の間に 10kΩ のプルアップ抵抗を追加してください
- センサーを熱源から離してください(Raspberry Pi 本体も熱を持つことがあります)
- 読み取り間隔に少し待ち時間を入れてみてください: ``time.sleep(2)``
- OpenWeather API エラー
- API キーが正しく、有効期限切れでないことを確認してください
- インターネット接続を確認してください: ``ping 8.8.8.8``
- 都市名と国コードが正しいことを確認してください
- Free プランにはレート制限があります(60 回/分、1,000,000 回/月)
- OLED に何も表示されない
- I2C 接続を確認してください: ``sudo i2cdetect -y 1`` (0x3C が表示されるはずです)
- OLED に電源が供給されているか確認してください(モデルにより 3.3V または 5V)
- I2C アドレスが正しいことを確認してください(0x3C または 0x3D)
- TTS から音が出ない
- 音声出力設定を確認してください: ``sudo raspi-config`` → **System Options** → **Audio**
- 音声テスト: ``speaker-test -t sine -f 440``
- OpenAI TTS API キーに十分なクレジットがあるか確認してください
- API 呼び出しに必要なインターネット接続を確認してください
- ボタンが反応しない
- 配線を確認してください:ボタンは GPIO 27 と GND の間に接続
- コード内でプルダウン抵抗が設定されているか確認してください
- 簡単なテストスクリプトでボタン動作を確認してください
- 光センサーの値が不正確
- ``light_percent()`` 内の ``min_val`` と ``max_val`` を調整して LDR をキャリブレーションしてください
- LDR を完全に覆って最小値を確認してください
- 強い光を当てて最大値を確認してください
- LDR が他の部品の影になっていないか確認してください
- 天気データが古い
- ``WEATHER_UPDATE_INTERVAL`` を短くして更新頻度を上げてください
- API 呼び出しが成功しているか確認してください(エラーメッセージを確認)
- システム時刻が正しいか確認してください: ``date``
----------------------------------------------
このスマート気象ステーションは、ローカルセンサーデータ、クラウドサービス、そして AI を組み合わせることで、実用的な洞察と知的な提案を提供する高度な環境モニタリングシステムを構築できることを示しています。