{"id":6696,"date":"2025-12-07T23:13:03","date_gmt":"2025-12-07T22:13:03","guid":{"rendered":"https:\/\/arduino.net.pl\/?p=6696"},"modified":"2025-12-11T21:48:08","modified_gmt":"2025-12-11T20:48:08","slug":"pobieramy-z-serwera-api-dane-o-zanieczyszczeniu-powietrza-uzywajac-rpzw-i-wysylamy-do-serwera-mqtt-na-rpz2w","status":"publish","type":"post","link":"https:\/\/arduino.net.pl\/index.php\/pobieramy-z-serwera-api-dane-o-zanieczyszczeniu-powietrza-uzywajac-rpzw-i-wysylamy-do-serwera-mqtt-na-rpz2w\/","title":{"rendered":"Pobieramy z serwera API dane o zanieczyszczeniu powietrza u\u017cywaj\u0105c RPZW i wysy\u0142amy do lokalnego serwera MQTT na RPZ2W"},"content":{"rendered":"\n<p>Projekt <strong>air_mqtt_rpzw<\/strong> to lekki skrypt dzia\u0142aj\u0105cy na Raspberry Pi Zero W, kt\u00f3ry cyklicznie pobiera dane jako\u015bci powietrza z publicznego API GIO\u015a, wybiera najwa\u017cniejsze wska\u017aniki jako\u015bci powietrza i wysy\u0142a je do brokera MQTT w Twojej lokalnej sieci. Skrypt dzia\u0142a jako us\u0142uga systemowa (systemd), automatycznie uruchamia si\u0119 po starcie systemu i reaguje miganiem diody LED:<br>\u2013 pojedynczy b\u0142ysk sygnalizuje udany odczyt,<br>\u2013 pi\u0119\u0107 szybkich b\u0142ysk\u00f3w oznacza b\u0142\u0105d pobierania lub po\u0142\u0105czenia.<\/p>\n\n\n\n<p>Projekt jest przeznaczony do integracji z systemem IoT (np. Home Assistant, Node-RED, EPD, wy\u015bwietlacze MQTT itd.) i ma by\u0107 mo\u017cliwie prosty, niezawodny i energooszcz\u0119dny.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1024\" src=\"https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3.png\" alt=\"SMOG\" class=\"wp-image-6728\" srcset=\"https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3.png 1024w, https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3-300x300.png 300w, https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3-150x150.png 150w, https:\/\/arduino.net.pl\/wp-content\/uploads\/2025\/12\/Gemini_Generated_Image_onl3mhonl3mhonl3-768x768.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Obrazek AI Gemini, prompt: <em>Narysuj obrazek nowoczesnego, zapylonego miasta ze smogiem w formie ilustracji komiksowej.<\/em><\/figcaption><\/figure>\n\n\n\n<p><br>Oto kr\u00f3tki tutorial pokazuj\u0105cy <strong>tworzenie projektu <code>air_mqtt_rpzw<\/code><\/strong> na <strong>Raspberry Pi Zero W<\/strong>. Bardzo pom\u00f3g\u0142 mi w tym <strong>ChatGPT&nbsp;<\/strong>wersja szybka.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Utw\u00f3rz katalog projektu<\/h2>\n\n\n\n<p>Zaloguj si\u0119 na RPZW i przejd\u017a do katalogu domowego:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\nmkdir -p pi-project\ncd pi-project\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. Utw\u00f3rz struktur\u0119 katalog\u00f3w<\/h2>\n\n\n\n<p>Zorganizuj projekt w logiczne podkatalogi:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir config\nmkdir systemd\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>config<\/code> \u2013 pliki konfiguracyjne (np. <code>config.json<\/code>)<\/li>\n\n\n\n<li><code>systemd<\/code> \u2013 pliki us\u0142ug systemd<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">3. Przygotuj plik konfiguracyjny<\/h2>\n\n\n\n<p>W katalogu <code>config<\/code> utw\u00f3rz plik <code>config.json<\/code> z ustawieniami:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ID stacji pomiarowej<\/li>\n\n\n\n<li>Adres serwera MQTT<\/li>\n\n\n\n<li>Temat MQTT<\/li>\n\n\n\n<li>Interwa\u0142 pobierania danych<\/li>\n\n\n\n<li>Numer pinu LED<\/li>\n<\/ul>\n\n\n\n<p><strong>Przyk\u0142adowy plik konfiguracyjny:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; light: false; title: Kod:; toolbar: true; notranslate\" title=\"Kod:\">\n{\n    &quot;STATION_ID&quot;: 732,\n    &quot;MQTT_SERVER&quot;: &quot;192.168.1.199&quot;,\n    &quot;MQTT_TOPIC&quot;: &quot;city\/air&quot;,\n    &quot;INTERVAL&quot;: 600,        \/\/ 600 sekund = 10 minut\n    &quot;LED_PIN&quot;: 17\n}\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">4. Utw\u00f3rz g\u0142\u00f3wny skrypt<\/h2>\n\n\n\n<p>W katalogu g\u0142\u00f3wnym <code>pi-project<\/code> utw\u00f3rz skrypt <code>air_mqtt_rpzw.py<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>import bibliotek: <code>requests<\/code>, <code>paho-mqtt<\/code>, <code>RPi.GPIO<\/code>, <code>time<\/code>, <code>json<\/code>, <code>os<\/code><\/li>\n\n\n\n<li>wczytywanie konfiguracji z <code>config.json<\/code><\/li>\n\n\n\n<li>konfiguracja GPIO dla diody<\/li>\n\n\n\n<li>funkcje: pobranie danych z API, filtrowanie danych, wysy\u0142anie do MQTT, miganie diod\u0105<\/li>\n\n\n\n<li>g\u0142\u00f3wna p\u0119tla z obs\u0142ug\u0105 wyj\u0105tk\u00f3w, z interwa\u0142em zdefiniowanym w konfiguracji<\/li>\n\n\n\n<li>sprz\u0105tanie GPIO przy zako\u0144czeniu<\/li>\n<\/ul>\n\n\n\n<p><strong>Przyk\u0142adowy skrypt: <\/strong><em>(edytowany 11.12.2025 Dodane sprawdzanie po\u0142\u0105czenia z WiFi, w razie braku pr\u00f3ba ponownego \u0142\u0105czenia. Po wy\u0142\u0105czeniu zasilania z routera raspberry nie powsta\u0142 st\u0105d dodana funkcja)<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; light: false; title: Kod:; toolbar: true; notranslate\" title=\"Kod:\">\nimport requests\nimport json\nimport paho.mqtt.client as mqtt\nimport time\nimport RPi.GPIO as GPIO\nimport os\nimport subprocess\n\n# ===== Wczytanie konfiguracji =====\nCONFIG_FILE = os.path.join(os.path.dirname(__file__), &quot;config&quot;, &quot;config.json&quot;)\n\nwith open(CONFIG_FILE, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:\n    config = json.load(f)\n\nSTATION_ID = config.get(&quot;STATION_ID&quot;, 732)\nMQTT_SERVER = config.get(&quot;MQTT_SERVER&quot;, &quot;192.168.1.199&quot;)\nMQTT_TOPIC = config.get(&quot;MQTT_TOPIC&quot;, &quot;city\/air&quot;)\nINTERVAL = config.get(&quot;INTERVAL&quot;, 600)  # w sekundach\nLED_PIN = config.get(&quot;LED_PIN&quot;, 17)\n\n# ===== Konfiguracja GPIO =====\nGPIO.setmode(GPIO.BCM)\nGPIO.setup(LED_PIN, GPIO.OUT)\n\n# PWM \u2013 sta\u0142e \u015bwiecenie 30%\nled_pwm = GPIO.PWM(LED_PIN, 1000)   # 1 kHz\nled_pwm.start(0)  # zaczynamy od zgaszonej\n\n# ===== Funkcje =====\n\ndef wifi_ok():\n    &quot;&quot;&quot;Sprawdza, czy Raspberry jest po\u0142\u0105czone z Wi-Fi.&quot;&quot;&quot;\n    try:\n        out = subprocess.check_output(&#x5B;&quot;iwgetid&quot;], stderr=subprocess.STDOUT).decode()\n        return &quot;ESSID&quot; in out\n    except:\n        return False\n\ndef wifi_reconnect():\n    &quot;&quot;&quot;Restart interfejsu wlan0 w razie utraty Wi-Fi.&quot;&quot;&quot;\n    print(&quot;Restart WiFi...&quot;)\n    os.system(&quot;sudo ifconfig wlan0 down&quot;)\n    time.sleep(3)\n    os.system(&quot;sudo ifconfig wlan0 up&quot;)\n    time.sleep(5)\n\ndef fetch_air_quality():\n    url = f&quot;https:\/\/api.gios.gov.pl\/pjp-api\/v1\/rest\/aqindex\/getIndex\/{STATION_ID}&quot;\n    r = requests.get(url, timeout=8)\n    r.raise_for_status()\n    return r.json()\n\ndef send_to_mqtt(data):\n    client = mqtt.Client()\n    client.connect(MQTT_SERVER)\n    payload = json.dumps(data, ensure_ascii=False)\n    client.publish(MQTT_TOPIC, payload)\n    client.disconnect()\n    print(&quot;Dane wys\u0142ane do MQTT:&quot;, payload)\n\ndef get_filtered_data():\n    data = fetch_air_quality()\n    aq = data.get(&quot;AqIndex&quot;, {})\n\n    # klucze do pobrania\n    keys = &#x5B;\n        &quot;Data wykonania oblicze\u0144 indeksu&quot;,\n        &quot;Nazwa kategorii indeksu&quot;,\n        &quot;Nazwa kategorii indeksu dla wska\u017cnika PM2.5&quot;,\n        &quot;Nazwa kategorii indeksu dla wska\u017cnika PM10&quot;,\n        &quot;Nazwa kategorii indeksu dla wska\u017cnika O3&quot;\n    ]\n\n    # pobranie danych bez KeyError\n    filtered = {key: aq.get(key, &quot;Brak danych&quot;) for key in keys}\n    return filtered\n\n# ===== G\u0142\u00f3wna p\u0119tla =====\ntry:\n    while True:\n\n        # ---- Monitoring WiFi ----\n        if not wifi_ok():\n            print(&quot;WiFi roz\u0142\u0105czone \u2013 pr\u00f3buj\u0119 po\u0142\u0105czy\u0107 ponownie...&quot;)\n            led_pwm.ChangeDutyCycle(0)  # LED zgaszony = problem\n\n            wifi_reconnect()\n            time.sleep(10)\n            continue  # wr\u00f3\u0107 do p\u0119tli, nie pr\u00f3buj pobiera\u0107 danych\n\n        # ---- G\u0142\u00f3wna logika ----\n        try:\n            filtered_data = get_filtered_data()\n            print(&quot;Dane do wys\u0142ania:&quot;, filtered_data)\n\n            send_to_mqtt(filtered_data)\n\n            # LED \u015bwieci, bo jest OK  (pwm na 3, bo dioda zielona ja\u015bniej \u015bwieci)\n            led_pwm.ChangeDutyCycle(3)\n\n        except requests.RequestException as e:\n            print(&quot;B\u0142\u0105d pobierania danych:&quot;, e)\n            led_pwm.ChangeDutyCycle(0)  # problem = wy\u0142\u0105cz LED\n\n        except Exception as e:\n            print(&quot;Inny b\u0142\u0105d:&quot;, e)\n            led_pwm.ChangeDutyCycle(0)  # problem = wy\u0142\u0105cz LED\n\n        time.sleep(INTERVAL)\n\nfinally:\n    led_pwm.stop()\n    GPIO.cleanup()\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">5. Zainstaluj wymagane pakiety przez apt<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt install -y python3-requests python3-paho-mqtt python3-rpi.gpio\n<\/code><\/pre>\n\n\n\n<p>Nie u\u017cywamy pip3, aby nie by\u0142o problem\u00f3w z wersjami bibliotek.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Utw\u00f3rz plik systemd<\/h2>\n\n\n\n<p>W katalogu <code>systemd<\/code> utw\u00f3rz plik <code>air_mqtt.service<\/code> z konfiguracj\u0105:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>nazwa us\u0142ugi<\/li>\n\n\n\n<li>u\u017cytkownik, katalog roboczy<\/li>\n\n\n\n<li>\u015bcie\u017cka do skryptu<\/li>\n\n\n\n<li>restart po awarii<\/li>\n\n\n\n<li>logowanie do journal<\/li>\n<\/ul>\n\n\n\n<p><strong>Przyk\u0142adowy plik systemowy:<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; light: false; title: Kod:; toolbar: true; notranslate\" title=\"Kod:\">\n&#x5B;Unit]\nDescription=Air Quality MQTT Script for RPZW\nAfter=network.target\n\n&#x5B;Service]\nUser=minimj\nWorkingDirectory=\/home\/minimj\/pi-project\nExecStart=\/usr\/bin\/python3 \/home\/minimj\/pi-project\/air_mqtt_rpzw.py\nRestart=always\nRestartSec=10\nStandardOutput=journal\nStandardError=journal\n\n&#x5B;Install]\nWantedBy=multi-user.target\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">7. Skopiuj plik systemd do katalogu systemowego<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo cp ~\/pi-project\/systemd\/air_mqtt.service \/etc\/systemd\/system\/\nsudo systemctl daemon-reload\nsudo systemctl enable air_mqtt.service\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">8. Uruchom i sprawd\u017a dzia\u0142anie<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Start us\u0142ugi:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl start air_mqtt.service\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sprawdzenie statusu:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status air_mqtt.service\nsudo journalctl -u air_mqtt.service -f\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Dioda LED miga po pobraniu danych (1 b\u0142ysk = sukces, 5 b\u0142ysk\u00f3w = problem).<\/li>\n\n\n\n<li>Dane s\u0105 wysy\u0142ane do brokera MQTT.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">9. Testowanie interwa\u0142u<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Interwa\u0142 pobierania i wysy\u0142ki ustawiany jest w pliku <code>config.json<\/code><\/li>\n\n\n\n<li>Po zmianie interwa\u0142u zrestartuj us\u0142ug\u0119:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl restart air_mqtt.service\n<\/code><\/pre>\n\n\n","protected":false},"excerpt":{"rendered":"<p>Projekt air_mqtt_rpzw to lekki skrypt dzia\u0142aj\u0105cy na Raspberry Pi Zero W, kt\u00f3ry cyklicznie pobiera dane jako\u015bci powietrza z publicznego API GIO\u015a, wybiera najwa\u017cniejsze wska\u017aniki jako\u015bci powietrza i wysy\u0142a je do&#8230;<\/p>\n","protected":false},"author":3,"featured_media":6728,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[59,114],"tags":[160],"class_list":["post-6696","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","category-raspberry","tag-mqtt"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/posts\/6696","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/comments?post=6696"}],"version-history":[{"count":21,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/posts\/6696\/revisions"}],"predecessor-version":[{"id":6737,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/posts\/6696\/revisions\/6737"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/media\/6728"}],"wp:attachment":[{"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/media?parent=6696"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/categories?post=6696"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/arduino.net.pl\/index.php\/wp-json\/wp\/v2\/tags?post=6696"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}