Popularny post H1M4W4R1 Napisano 17 czerwca Popularny post Udostępnij Napisano 17 czerwca Czasami najciekawsze projekty DIY nie muszą być skomplikowane. Wystarczy kilka gotowych modułów, prosta obudowa z drukarki 3D i krótki program, aby zbudować urządzenie, które od razu zachęca do zabawy. Tym razem przygotujemy prostą zabawkę działającą jak elektroniczna wersja gry „zielone światło, czerwone światło”, znanej między innymi z serialu Squid Game. Rysunek inspirowany serialem Urządzenie losowo przełącza kolor świecenia między zielonym i czerwonym. Gdy świeci się zielone światło, można się poruszać. Gdy zapali się czerwone, czujnik ruchu zaczyna obserwować otoczenie. Wykrycie ruchu powoduje trzykrotne mignięcie diod na żółto, po czym urządzenie wraca do zielonego światła na co najmniej kilka sekund. Artykuł powstał we współpracy z firmą Botland. Co zbudujemy? Projekt składa się z niewielkiego modułu M5Stack Matrix v1.1 z matrycą LED oraz czujnika ruchu (M5Stack Unit PIR lub M5Stack Unit TMOS PIR) umieszczonych w prostej, drukowanej obudowie. Całość zasilana jest przez USB i po uruchomieniu działa w pełni autonomicznie. Nie trzeba podłączać komputera, telefonu ani żadnej aplikacji sterującej. Urządzenie nie posiada wbudowanego akumulatora, więc do działania wymaga podłączenia do zasilacza USB, powerbanku lub portu USB komputera. Takie rozwiązanie upraszcza konstrukcję i eliminuje konieczność ładowania akumulatora. Moduł z matrycą został wybrany jako pokazanie jednej z możliwości, równie dobrze można byłoby użyć modułu z matrycą LCD. Czujniki PIR zostały dobrane do kompletu z modułem, aczkolwiek inne, które posiadają wyjścia kompatybilne z ekosystemem Grove również powinny działać prawidłowo, aczkolwiek nie będą pasowały do obudowy z tego wpisu. Teoretycznie można było użyć np. płytek Xiao, które pozwalają na łatwe dodanie akumulatora, aczkolwiek projekt miał w założeniu być prosty i zachęcać do nauki elektroniki, a lutowanie zwykle odstrasza nowe osoby, stąd wybór padł na ekosystem M5Stack. Zielone światło - można się poruszać Obudowa widoczna na zdjęciach została wydrukowana z filamentu PETG. W tym projekcie obudowa nie jest narażona na duże obciążenia mechaniczne, więc najważniejsze jest poprawne spasowanie elementów i estetyka wydruku. Czujnik PIR można zamocować dwoma śrubami M4 z podkładkami, ale na zdjęciu został on po prostu włożony do obudowy, co wystarczyło, by się solidnie w niej trzymał. Jak działa urządzenie? Zasada działania jest bardzo prosta. Po podłączeniu zasilania program uruchamia się automatycznie i zaczyna losowo przełączać stan urządzenia. W programie zapisane są na sztywno parametry określające, jak długo może trwać zielone i czerwone światło. Dzięki temu urządzenie nie przełącza się zawsze po takim samym czasie, a zabawa jest mniej przewidywalna. Gdy świeci się zielone światło, czujnik ruchu nie powoduje żadnej reakcji. Jest to moment, w którym gracz może się poruszać. Gdy zapali się czerwone światło, urządzenie zaczyna sprawdzać wskazania czujnika PIR. Wykrycie ruchu oznacza przegraną rundę. W takiej sytuacji matryca LED miga trzy razy na żółto, a następnie urządzenie automatycznie przełącza się na zielone światło. Zielone światło świeci wtedy przez minimum 3 sekundy, aby gracz miał chwilę na wznowienie zabawy. Czerwone światło - lepiej unikać ruchu Czujnik PIR i przewód Grove Do wykrywania ruchu używany jest moduł M5Stack Unit PIR albo M5Stack Unit TMOS PIR. Oba moduły można połączyć z M5Stack Matrix v1.1 za pomocą krótkiego przewodu Grove, który znajduje się w pakiecie z modułami. Dzięki temu montaż jest prosty i nie wymaga lutowania. W praktyce najwygodniej jest umieścić czujnik z przodu obudowy, tak aby „patrzył” w stronę gracza. Matryca LED powinna być dobrze widoczna z tej samej strony. Warto zwrócić uwagę, aby obudowa nie zasłaniała pola widzenia czujnika. Stąd też obudowa została stworzona w formie "kamerki internetowej". Montaż w obudowie Obudowa została zaprojektowana tak, aby pomieścić oba moduły. Przewód jest umieszczony pod obudową, gdyż lubię taką estetykę. Model 3D udostępniony jest w formacie STEP (który można go łatwo zmodyfikować w większości programów CAD) oraz formacie STL (do druku 3D). Obudowa widziana od tyłu Najpierw należy wydrukować elementy obudowy. Następnie w obudowie montujemy moduł M5Stack Matrix v1.1 oraz czujnik PIR. Po połączeniu ich przewodem Grove warto sprawdzić, czy przewód nie jest zagięty pod zbyt dużym kątem i czy nie "wyciąga" elementów elektronicznych. Poniżej możesz znaleźć pliki obudowy do samodzielnego wydruku lub modyfikacji. Przed wydrukiem warto sprawdzić orientację elementów, gdyż mogą one nie być optymalnie ustawione. Moje ustawienia wydruku dla drukarki BambuLab X1 Carbon Modele.zip Program urządzenia Program wgrany do urządzenia realizuje kilka prostych zadań: losuje czas trwania zielonego i czerwonego światła, wyświetla odpowiedni kolor na matrycy LED, sprawdza czujnik ruchu tylko podczas czerwonego światła, po wykryciu ruchu miga trzy razy na żółto, po sygnale błędu przełącza urządzenie na zielone światło na minimum 3 sekundy. Wszystkie parametry są zapisane bezpośrednio w kodzie. Dzięki temu projekt jest prosty i nie wymaga dodatkowego panelu konfiguracyjnego. Osoby, które chcą zmienić zachowanie urządzenia, mogą samodzielnie edytować program, na przykład zmieniając minimalny i maksymalny czas świecenia poszczególnych kolorów. Cały projekt mona pobrać tutaj: Program.zip #include <Arduino.h> #include <FastLED.h> #include <M5_STHS34PF80.h> #include <Wire.h> #include <assert.h> /** * @brief Domyślna konfiguracja czasu działania świateł. * * Makra RG_* mogą zostać nadpisane z poziomu platformio.ini przez build_flags, * np. -DRG_GREEN_MIN_MS=3000UL. Pozwala to zmieniać zachowanie programu bez * modyfikowania kodu automatu stanów. */ #ifndef RG_GREEN_MIN_MS #define RG_GREEN_MIN_MS 4000UL #endif #ifndef RG_GREEN_MAX_MS #define RG_GREEN_MAX_MS 9000UL #endif #ifndef RG_RED_MIN_MS #define RG_RED_MIN_MS 4000UL #endif #ifndef RG_RED_MAX_MS #define RG_RED_MAX_MS 9000UL #endif #ifndef RG_TRANSITION_MS #define RG_TRANSITION_MS 500UL #endif #ifndef RG_PENALTY_GREEN_MS #define RG_PENALTY_GREEN_MS 3000UL #endif #ifndef RG_TMOS_MOTION_THRESHOLD #define RG_TMOS_MOTION_THRESHOLD 60 #endif static const uint8_t kLedPin = 27U; static const uint8_t kLedCount = 25U; static const uint8_t kPirSignalPin = 32U; static const uint8_t kGroveSdaPin = 26U; static const uint8_t kGroveSclPin = 32U; /** * @brief Stałe sprzętowe oraz przepisana konfiguracja czasów pracy programu. * * Sekcja zawiera mapowanie pinów ATOM Matrix/Grove, zakresy losowania czasów, * parametry migania ostrzegawczego oraz próg detekcji ruchu dla czujnika TMOS. */ static const uint32_t kGreenMinMs = RG_GREEN_MIN_MS; static const uint32_t kGreenMaxMs = RG_GREEN_MAX_MS; static const uint32_t kRedMinMs = RG_RED_MIN_MS; static const uint32_t kRedMaxMs = RG_RED_MAX_MS; static const uint32_t kTransitionMs = RG_TRANSITION_MS; static const uint32_t kPenaltyGreenMs = RG_PENALTY_GREEN_MS; static const uint32_t kYellowBlinkTotalMs = 2000UL; static const uint8_t kYellowBlinkCount = 3U; static const uint8_t kBrightness = 20U; static const int16_t kTmosMotionThreshold = RG_TMOS_MOTION_THRESHOLD; enum LightState { LIGHT_GREEN, LIGHT_RED, LIGHT_TRANSITION }; /** * @brief Typ aktywnego czujnika ruchu wybrany podczas inicjalizacji. * * Program najpierw sprawdza obecność TMOS na magistrali I2C. Jeśli czujnik * odpowiada i przejdzie inicjalizację, wykorzystywany jest TMOS. W przeciwnym * razie program konfiguruje klasyczny PIR z wyjściem cyfrowym. */ enum MotionSensor { SENSOR_DIGITAL_PIR, SENSOR_TMOS_PIR }; static CRGB g_leds[kLedCount]; static LightState g_state = LIGHT_GREEN; static LightState g_next_state = LIGHT_RED; static MotionSensor g_motion_sensor = SENSOR_DIGITAL_PIR; static uint32_t g_state_started_ms = 0UL; static uint32_t g_state_duration_ms = 0UL; static uint32_t g_after_transition_duration_ms = 0UL; static CRGB g_from_color = CRGB::Black; static CRGB g_to_color = CRGB::Green; static M5_STHS34PF80 g_tmos; static bool g_tmos_ready = false; /** * @brief Losuje czas trwania stanu w podanym zakresie. * * Metoda wykonuje losowanie czasu w milisekundach w celu nadania zmiennej, * nieprzewidywalnej długości fazie zielonej lub czerwonej. * * @param min_ms Minimalny czas trwania stanu w milisekundach. * @param max_ms Maksymalny czas trwania stanu w milisekundach. * @return Wylosowany czas z przedziału domkniętego [min_ms, max_ms]. */ static uint32_t randomDuration(uint32_t min_ms, uint32_t max_ms) { assert(min_ms > 0UL); assert(max_ms >= min_ms); return random(min_ms, max_ms + 1UL); } /** * @brief Interpoluje kolor pomiędzy dwoma wartościami RGB. * * Metoda wykonuje liniowe mieszanie kolorów w celu płynnego przejścia pomiędzy * światłem zielonym i czerwonym w czasie zdefiniowanym przez kTransitionMs. * * @param from_color Kolor początkowy przejścia. * @param to_color Kolor docelowy przejścia. * @param amount Pozycja interpolacji w zakresie 0..255. * @return Kolor pośredni wyliczony przez bibliotekę FastLED. */ static CRGB lerpColor(CRGB from_color, CRGB to_color, uint8_t amount) { assert(kLedCount == 25U); assert(kBrightness <= 100U); return blend(from_color, to_color, amount); } /** * @brief Aktualizuje całą matrycę LED jednym kolorem. * * Metoda ustawia ten sam kolor na wszystkich diodach WS2812 w celu zachowania * jednolitego wyglądu sygnalizacji świetlnej. * * @param color Kolor, który ma zostać wyświetlony na wszystkich diodach. */ static void showAll(CRGB color) { assert(kLedCount > 0U); assert(kLedCount <= 25U); fill_solid(g_leds, kLedCount, color); FastLED.show(); } /** * @brief Ustawia bieżący stan automatu świateł. * * Metoda zapisuje nowy stan, czas jego rozpoczęcia oraz planowany czas trwania * w celu późniejszego sterowania przejściami w funkcji loop(). * * @param state Stan, który ma zostać aktywowany. * @param duration_ms Czas obowiązywania stanu w milisekundach. */ static void setState(LightState state, uint32_t duration_ms) { assert(duration_ms > 0UL); assert(state == LIGHT_GREEN || state == LIGHT_RED || state == LIGHT_TRANSITION); g_state = state; g_state_started_ms = millis(); g_state_duration_ms = duration_ms; } /** * @brief Rozpoczyna płynne przejście do wskazanego światła. * * Metoda zapisuje kolor początkowy, kolor docelowy i czas docelowego stanu * w celu obsłużenia przejścia przez handleTransition(). * * @param target_state Docelowy stan światła po zakończeniu przejścia. * @param target_duration_ms Czas trwania docelowego stanu po przejściu. */ static void startTransition(LightState target_state, uint32_t target_duration_ms) { assert(target_duration_ms > 0UL); assert(target_state == LIGHT_GREEN || target_state == LIGHT_RED); g_from_color = (g_state == LIGHT_GREEN) ? CRGB::Green : CRGB::Red; g_to_color = (target_state == LIGHT_GREEN) ? CRGB::Green : CRGB::Red; g_next_state = target_state; g_after_transition_duration_ms = target_duration_ms; setState(LIGHT_TRANSITION, kTransitionMs); } /** * @brief Uruchamia fazę zielonego światła. * * Metoda losuje czas zielonego światła i wymusza dolny limit czasu w celu * zapewnienia minimum kPenaltyGreenMs po wykryciu ruchu na czerwonym. * * @param min_duration_ms Minimalny dopuszczalny czas zielonego światła. */ static void startGreen(uint32_t min_duration_ms) { const uint32_t random_ms = randomDuration(kGreenMinMs, kGreenMaxMs); assert(min_duration_ms > 0UL); assert(random_ms >= kGreenMinMs); const uint32_t duration_ms = (random_ms < min_duration_ms) ? min_duration_ms : random_ms; startTransition(LIGHT_GREEN, duration_ms); } /** * @brief Uruchamia fazę czerwonego światła. * * Metoda losuje czas czerwonego światła z konfiguracji w celu utrzymania * podstawowego cyklu pracy urządzenia. */ static void startRed(void) { const uint32_t duration_ms = randomDuration(kRedMinMs, kRedMaxMs); assert(duration_ms >= kRedMinMs); assert(duration_ms <= kRedMaxMs); startTransition(LIGHT_RED, duration_ms); } /** * @brief Sprawdza stan klasycznego czujnika PIR. * * Metoda odczytuje cyfrowy pin modułu PIR w celu wykrycia sygnału ruchu * zgłaszanego przez czujnik podłączony do portu Grove. * * @return true, jeśli pin PIR ma stan wysoki; false w przeciwnym razie. */ static bool digitalPirDetected(void) { const int value = digitalRead(kPirSignalPin); assert(value == LOW || value == HIGH); assert(kPirSignalPin < 40U); return value == HIGH; } /** * @brief Sprawdza stan czujnika TMOS STHS34PF80. * * Metoda odczytuje wartość ruchu z czujnika TMOS w celu wykrycia obecności lub * ruchu bez korzystania z cyfrowego wejścia PIR. * * @return true, jeśli TMOS jest gotowy, odczyt się udał i wartość ruchu * przekracza skonfigurowany próg; false w przeciwnym razie. */ static bool tmosPirDetected(void) { int16_t motion = 0; const int32_t result = g_tmos.getMotionValue(&motion); assert(kTmosMotionThreshold > 0); assert(kGroveSdaPin != kGroveSclPin); return g_tmos_ready && result == 0 && motion >= kTmosMotionThreshold; } /** * @brief Wykonuje detekcję ruchu przez aktywny czujnik. * * Metoda wybiera właściwy backend pomiarowy w celu ukrycia różnic pomiędzy * TMOS po I2C oraz klasycznym PIR z wyjściem cyfrowym. * * @return true, jeśli aktywny czujnik zgłasza ruch; false w przeciwnym razie. */ static bool motionDetected(void) { assert(g_motion_sensor == SENSOR_DIGITAL_PIR || g_motion_sensor == SENSOR_TMOS_PIR); assert(kPirSignalPin < 40U); return (g_motion_sensor == SENSOR_TMOS_PIR) ? tmosPirDetected() : digitalPirDetected(); } /** * @brief Sygnalizuje naruszenie czerwonego światła. * * Metoda wykonuje trzy żółte mignięcia w czasie kYellowBlinkTotalMs w celu * pokazania, że ruch został wykryty podczas fazy czerwonej. */ static void blinkPenalty(void) { const uint32_t segment_ms = kYellowBlinkTotalMs / (kYellowBlinkCount * 2UL); assert(segment_ms > 0UL); assert(kYellowBlinkCount == 3U); for (uint8_t index = 0U; index < kYellowBlinkCount; ++index) { showAll(CRGB::Yellow); delay(segment_ms); showAll(CRGB::Black); delay(segment_ms); } } /** * @brief Obsługuje stan płynnego przejścia między kolorami. * * Metoda wylicza postęp przejścia na podstawie czasu od startu stanu w celu * płynnego mieszania koloru początkowego z docelowym. Po zakończeniu przejścia * aktywuje stan zapisany w g_next_state. * * @param elapsed_ms Czas, który upłynął od rozpoczęcia stanu przejściowego. */ static void handleTransition(uint32_t elapsed_ms) { assert(g_state == LIGHT_TRANSITION); assert(kTransitionMs > 0UL); if (elapsed_ms >= kTransitionMs) { setState(g_next_state, g_after_transition_duration_ms); showAll(g_to_color); return; } const uint8_t amount = static_cast<uint8_t>((elapsed_ms * 255UL) / kTransitionMs); showAll(lerpColor(g_from_color, g_to_color, amount)); } /** * @brief Obsługuje fazę zielonego światła. * * Metoda utrzymuje matrycę w kolorze zielonym w celu realizacji bezpiecznej * fazy ruchu. Po upływie zaplanowanego czasu rozpoczyna przejście do czerwieni. * * @param elapsed_ms Czas, który upłynął od rozpoczęcia zielonego światła. */ static void handleGreen(uint32_t elapsed_ms) { assert(g_state == LIGHT_GREEN); assert(g_state_duration_ms > 0UL); showAll(CRGB::Green); if (elapsed_ms >= g_state_duration_ms) { startRed(); } } /** * @brief Obsługuje fazę czerwonego światła. * * Metoda utrzymuje matrycę w kolorze czerwonym i sprawdza czujnik ruchu w celu * wykrycia naruszenia. Wykryty ruch uruchamia sygnał żółty i wymusza powrót do * zielonego na co najmniej kPenaltyGreenMs. * * @param elapsed_ms Czas, który upłynął od rozpoczęcia czerwonego światła. */ static void handleRed(uint32_t elapsed_ms) { assert(g_state == LIGHT_RED); assert(g_state_duration_ms > 0UL); showAll(CRGB::Red); if (motionDetected()) { blinkPenalty(); startGreen(kPenaltyGreenMs); } else if (elapsed_ms >= g_state_duration_ms) { startGreen(kGreenMinMs); } } /** * @brief Sprawdza odpowiedź urządzenia na magistrali I2C. * * Metoda wykonuje krótką transmisję adresową w celu ustalenia, czy na porcie * Grove znajduje się czujnik TMOS o oczekiwanym adresie. * * @param address Siedmiobitowy adres I2C sprawdzanego urządzenia. * @return true, jeśli urządzenie potwierdziło adres; false w przeciwnym razie. */ static bool i2cDeviceExists(uint8_t address) { assert(address > 0U); assert(address < 128U); Wire.beginTransmission(address); return Wire.endTransmission() == 0U; } /** * @brief Inicjalizuje i wybiera aktywny czujnik ruchu. * * Metoda uruchamia magistralę I2C, wykrywa TMOS i próbuje go zainicjalizować * w celu użycia dokładniejszego czujnika, jeśli jest podłączony. Gdy TMOS nie * odpowiada albo inicjalizacja się nie powiedzie, metoda konfiguruje wejście * cyfrowe dla klasycznego PIR. */ static void setupMotionSensor(void) { assert(kGroveSdaPin == 26U); assert(kGroveSclPin == 32U); Wire.begin(kGroveSdaPin, kGroveSclPin); if (i2cDeviceExists(STHS34PF80_I2C_ADDRESS)) { g_tmos_ready = g_tmos.begin(&Wire, STHS34PF80_I2C_ADDRESS, kGroveSdaPin, kGroveSclPin); if (g_tmos_ready) { const int32_t init_result = g_tmos.init(); g_tmos_ready = init_result == 0; } } if (g_tmos_ready) { g_motion_sensor = SENSOR_TMOS_PIR; } else { g_motion_sensor = SENSOR_DIGITAL_PIR; pinMode(kPirSignalPin, INPUT); } } /** * @brief Inicjalizuje urządzenie po starcie mikrokontrolera. * * Metoda przygotowuje port szeregowy, generator losowy, sterownik WS2812, * aktywny czujnik ruchu oraz początkowy stan zielonego światła. */ void setup() { Serial.begin(115200); randomSeed(esp_random()); FastLED.addLeds<WS2812, kLedPin, GRB>(g_leds, kLedCount); FastLED.setBrightness(kBrightness); setupMotionSensor(); setState(LIGHT_GREEN, randomDuration(kGreenMinMs, kGreenMaxMs)); showAll(CRGB::Green); } /** * @brief Wykonuje główną pętlę automatu świateł. * * Metoda oblicza czas trwania aktualnego stanu i deleguje obsługę do funkcji * właściwej dla zielonego, czerwonego albo przejściowego stanu matrycy. */ void loop() { const uint32_t now_ms = millis(); const uint32_t elapsed_ms = now_ms - g_state_started_ms; if (g_state == LIGHT_TRANSITION) { handleTransition(elapsed_ms); } else if (g_state == LIGHT_GREEN) { handleGreen(elapsed_ms); } else { handleRed(elapsed_ms); } delay(20U); } Tworzenia programu nie będę opisywał, gdyż jest on względnie krótki i posiada dokumentację w języku polskim. Jeżeli ktoś będzie miał pytania śmiało służę pomocą. Możliwe rozszerzenia projektu Podstawowa wersja urządzenia działa w pełni lokalnie i nie wymaga żadnej komunikacji z innymi urządzeniami. To wystarcza do prostej zabawy lub demonstracji działania czujnika ruchu. Bardziej zaawansowane osoby mogą rozbudować projekt o serwer WebSocket. W takiej wersji urządzenie mogłoby wysyłać informację o wykryciu ruchu do wskazanego punktu docelowego. Pozwoliłoby to zintegrować zabawkę z interaktywną grą komputerową, aplikacją webową albo innym systemem reagującym na zdarzenia z prawdziwego świata. Można też dodać konfigurację czasów przez Wi-Fi, efekty dźwiękowe, licznik punktów albo tryb wieloosobowy. Warto jednak zacząć od najprostszej wersji, ponieważ już ona dobrze pokazuje, jak połączyć gotowe moduły M5Stack, czujnik ruchu i wydrukowaną obudowę w kompletny projekt DIY. Podsumowanie Ten projekt jest dobrym przykładem niewielkiego urządzenia, które można wykonać bez skomplikowanego montażu elektronicznego. Gotowe moduły M5Stack ograniczają liczbę połączeń do minimum, a drukowana obudowa sprawia, że całość wygląda jak samodzielne urządzenie, a nie tylko testowy układ na biurku. Po podłączeniu zasilania zabawka działa autonomicznie: losowo zmienia światło, wykrywa ruch tylko w odpowiednim momencie i sygnalizuje przegraną rundę żółtym miganiem. To prosty, ale efektowny projekt dla osób, które chcą połączyć elektronikę, programowanie i druk 3D w jednej praktycznej konstrukcji. Post Scriptum Przerobiłem również urządzenie na wersję BLE. Działa poprawnie, ale niestety kod jest nieudokumentowany, więc jest on dedykowany głównie dla osób, które są w stanie go przeczytać i przeanalizować we własnym zakresie. 4
ethanak 20 czerwca Udostępnij 20 czerwca Pomysł świetny, ale... Po pierwsze, czujnik ma pewną bezwładność, i przez chwilę po zapaleniu czerwonego będzie wykrywał ruch. Po drugie: nie ma szans, aby nagle stanąć w bezruchu (fizyka). Przewidziałeś to? Ja tworząc taką zabawkę dodałbym żółte między zielonym i czerwonym - ale to tylko drobna sugestia.
H1M4W4R1 21 czerwca Autor tematu Udostępnij 21 czerwca (edytowany) 18 godzin temu, ethanak napisał: Po pierwsze, czujnik ma pewną bezwładność, i przez chwilę po zapaleniu czerwonego będzie wykrywał ruch. Po drugie: nie ma szans, aby nagle stanąć w bezruchu (fizyka). Przewidziałeś to? Jest 0.5s przerwy (da się podmienić w konfiguracji makra, albo podmieniając definicję w konfiguracji projektu poprzez platformio.ini) #define RG_TRANSITION_MS 500UL To służy za korekcję bezwładności. 0.5s to wprwadzie bardzo mały czas, ale da się zareagować. Ta przerwa jest akurat sygnalizowana poprzez LERPa z zielonego do czerwonego (i vice versa, podczas przerw też nie wykrywa ruchu). Edytowano 21 czerwca przez H1M4W4R1
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »