1 2 3 |
Wszystkie pliku dostępne tutaj: https://github.com/GHRik/Parking-Sensor-DIY |
Hej,
dostałem się na praktyki w firmie, która zajmuje się sektorem automotive. Dowiedziałem się, że na praktykach będziemy używać Raspberry Pi oraz jakiegoś czujniczka prakowania. Uznałem, że idealnie będzie zrobić coś co prawdopodobnie będziemy robić na praktykach. W taki właśnie sposób wpadł mi do głowy pomysł na stworzenie czujnika parkowania.
Sprzęt:
Raspberry Pi 2 Model B VI.1 :
gdzie podłączyłem:
a) Wyświetlacz 4-Digit LED Display z układem TM1637
b) Buzzer z generatoremNodeMCU v2 z modułem wifi ESP8266
gdzie podłączyłem:
a) Ultradźwiękowy czujnik odległości HC-SR04Arduino MEGA 2560
zasilił on:
a) NodeMCU
b) HC – SR04- Smartphone oraz router :)
Układ:
Jak to działa?
Czujnik ma zasięg do 1,6M , kiedy wyjdziemy po za zakres ukaże nam się takie coś:
Jak zostało to zrobione?
Teraz może krok po kroku napisze wam co zrobiłem oraz jakie miałem problemy.
Założenia
Założyłem sobie wykonanie tego czujnika tak, aby symulowało to trochę realia samochodowe.
Nie miałem przewodu 2metrowego, żeby połączyć sensor parkowania , który powinien być z tyłu samochodu z wyświetlaczem, który powinien być w kokpicie tak, aby kierowca dokładnie go widział. Postanowiłem zrobić to przez sieć. Wykorzystałem protokół mqtt. Uczyłem się o nim na studiach i pamiętałem, że był dość prosty w wykonaniu.
MQTT
Protokół nadający się do wysyłania krótkich wiadomości, czyli idealny jak na ten projekt. Z
Zasada działania
W mqtt mamy 3 role: Broker , Publisher i Subscriber
Broker
To on trzyma wszystkie wiadomości. Możemy zasadę działania oprzeć np. na zasadzie działania forum. Broker to serwer , gdzie są publikowane wszystkie tematy.
Publisher
Wracając do forum publisher to osoba, która publikuje coś w danym temacie
Subscriber
Na forach jedną z podstawowych funkcjonalności jest możliwość subskrybowania tematów, czyli jeżeli zasubskrybujemy jakiś temat i ktoś w tym temacie coś napiszę, dostaniemy o tym wiadomość , nie rzadko jest to wiadomość z treścią publikacji. Nie inaczej jest tutaj.
Wracając do założeń:
Podzieliłem sobie prace na trzy urządzenia końcowe:
NodeMCU – który łączy się z moją siecią domową za pomocą modułu Wifi i publikuje na broker dane otrzymane z sensora.
Raspberry PI – odpowiednio obrabia dane otrzymane z zasubskrybowanego tematu , odpowiednio je obrabia , odpowiednie dopasowuje częstostliwość dla buzzera i odpowiednia wyświetla dane na wyświetlacz 7-segmentowy
Smartphone – niestety nie miałem skrzyni biegów , więc uznałem, że za kierowce, który wbija bieg wsteczny będzie robić mój telefon.
NodeMCU
Jak każdy wie to urządzenie jest zasilane 3.3v , tutaj pojawił się mój pierwszy problem. Czujnik zbliżeniowy potrzebował napięcia 5V. Tak oto w moim projekcie pojawiło się Arduino, które zasiliło NodeMCU oraz czujnik.
HC-SR04
4 wyprowadzenia VCC, GND, ECHO oraz TRIG. Zaczynamy pomiar, na pin TRIG podajemy stan wysoki przez 10 μS, następnie czujnik wyśle grupę impulsów o częstotliwości 40 kHz, gdy fale obiją się od przeszkody i trafią z powrotem do czujnika. Określi on czas pomiędzy ich wysłaniem i odebraniem i na pinie ECHO wygeneruje stan wysoki. Obliczamy odległość od przeszkody korzystając ze wzoru:
1 |
distance= duration*0.034/2; |
Mqtt
Tutaj ktoś za mnie odwalił całą robotę. Do NodeMCU została napisana biblioteka do obsługi mqtt:
1 |
https://github.com/knolleary/pubsubclient |
Wystarczyło napisać swój callback. Wymyśliłem sobie, że bieg wsteczny będę włączać i wyłączać telefonem. NodeMCU subskrybuje temat “Command” , kiedy na ten temat zostanie opublikowane 0 to NodeMCU publikuje na temat “Distance” wiadomość “-100” , a gdy na kanał “Command” przyjdzie 1 to NodeMCU zaczyna publikować na temat “Distance” dane z sensora:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) { message += ((char)payload[i]); } if(message == "0"){ flag = -1; } else if(message == "1"){ flag = 1; } } |
A w loopie są publikowane wiadomości zależnie od flagi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void loop() { client.loop(); if (!client.connected()) { reconnect(); } distance = getDistanceFromSensor(); //Distance to charArray, its not working in function. String value(distance); value += "\0"; char publishValue[20]; value.toCharArray(publishValue,20); //Distance to charArray if(flag == 1){ client.publish("Distance", publishValue); } else if(flag == -1) client.publish("Distance", "-100"); delay(1000); } |
Gdzie mój program?
NodeMCU po każdym zresetowaniu zapominało swojego programu i musiałem wgrywać go na nowo. Było to bardzo upierdliwe. Tutaj znowu udało się znaleźć rozwiązanie:
1 |
Jeśli podłączymy D3 do Vcc to NodeMCU nie gubi naszego programu :) |
Raspberry PI
Tutaj miałem największe problemy. Ze względu na to, że do raspberry podpiąłem kalawiature bezprzewodową , która kilka razy mi spadła… i poprzestawiały się klawisze… Dlatego takie rzeczy jak przecinki, dwukropki musiałem kopiować. Także proszę nie oceniajcie mnie za jakość kodu.
Wyświetlacz i TM1637
Znalazłem tylko jedną konkretną bibliotekę , reszta mi po prostu nie działała. Walczyłem bardzo długo z innymi aż znalazłem jedną , która robiła prawie to co chciałem
1 |
https://raspberrytips.nl/tm1637-4-digit-led-display-raspberry-pi/ |
Super, wszystko działa, wyświetlacz wyświetla godzinę i mruga kropkami w każdym segmencie. Jednak ja potrzebuje , żeby jedna kropka się świeciła, tylko jedna, na środku. Sensor podaje mi dane w centymetrach a ja będę wyświetlać w metrach, ułatwia to bardzo zadanie.
Metoda z biblioteki:
1 |
ShowDoublepoint |
Umożliwiała wyświetlanie kropek albo nie. Działała zero jedynkowo. Także metody zostały zmienione prze zemnie:
Nowe rzeczy jakie pojawiły się w klasie TM1637:
1 |
__whereSetPoint = 0 |
Zmienna definiująca w którym segmencie ma palić się kropka
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def coding(self, data, <strong>sequenceData</strong>): if( self.__doublePoint ): pointData = 0x80 else: pointData = 0; if(data == 0x7F): data = 0 else: data = HexDigits[data] <strong>if sequenceData == self.__whereSetPoint: data = data + pointData</strong> return data |
Dodałem jeden argument do funkcji, która zostaje wywołana w metodzie “Show” dokładnie w tym miejscu:
1 2 |
for i in range(0,4): self.writeByte(self.coding( data[i],<strong>i</strong>)) |
Okej, więc wygląda to tak, że funkcje ShowDoublepoint wywołujemy teraz z dwoma argumentami:
1 |
def ShowDoublepoint(self, on, where): |
Argument where określa segment zapelania kropki. whereSetPoint zostaje przyrównane do where i tym oto sposobem udało mi się zapalić kropkę na środku :D!
Buzzer
Buzzer musiał działać w oddzielnym wątku, początkowo nie wiedziałem jak zrobić, aby przekazywać częstotliwość z jaką ma piszczeć. Pomocne okazały się zmienne globalne. Okazuje się, że jeżeli w pliku gdzie odpalamy wątek są zmienne globalne to są one przekazywane do tego wątku, co najlepsze , jeżeli w funkcji z która odpalamy wątek zadeklarujemy taką zmienną globalną jako: global to możemy ją bez problemu edytować. Tyczy się to też innych funkcji :).
1 2 3 4 5 6 7 8 9 10 11 |
def buzzer(): buzzerCondition = True; while True: if buzzerFrequency > 0: GPIO.output(vccBuzzer,buzzerCondition) time.sleep(buzzerFrequency) buzzerCondition = not buzzerCondition elif buzzerFrequency < 0: GPIO.output(vccBuzzer,False) else: GPIO.output(vccBuzzer,True) |
i odpalanie wątku:
1 2 |
buzzerThread = Thread(target = buzzer, args=()) buzzerThread.start() |
jeszcze dodatkowo z każdym odpaleniem programu zanim cokolwiek zaczeło się dziać buzzer raz jednostajnie zapiszczy, żeby oznaczyć, że żyje
1 2 3 4 |
def setBuzzerReady(): GPIO.output(signalBuzzer,True) time.sleep(0.25) GPIO.output(signalBuzzer,False) |
MQTT
Okej , mamy wyświetlacz, mamy buzzer, trzeba zacząć odbierać wiadomości z MQTT! Znowu ktoś był taki miły i załatwił cała sprawę z mqtt , mamy blibliotek od paho
1 |
import paho.mqtt.client as mqtt |
Znowu wystarczy zadbać tylko o callback:
W tym callbacku musimy już uwzględnić jakieś częstotliwości buzzera typu: Jeśli przyjdzie wiadomość np. “123.6” to jak powinien piszczeć buzzer. Druga sprawa co ma się wyświetlić na wyświetlaczu?
1 2 3 4 5 6 7 8 9 10 11 12 |
def on_message(client, obj, msg): msa = str(msg.payload) msa1 = msa.replace("b","") msa2 = msa1.replace("'","") msa3 = float(msa2) msa4 = int(msa3) if msa4 < 0 or msa4 > sensorDataOutOfRange: sensorDateIsOutOfRangeOrOFF(msa4) else: Display.Show(sensorDateChooseRange(msa4)) Display.ShowDoublepoint(1,1) |
Okej , tutaj może być trochę zagmatwane. Co to za msa? Okazało się, że mqtt dodawało sobie jakieś dziwne znaczki do każdej wiadomości. Czyli wiadomość, która wychodziła od NodeMCU np. “123.6” na Raspberry Pi wyglądała tak:
1 |
b"`126.6'" |
W sumie powinien zamkną to w jakiejś funkcji, ale przypominam, o tej mojej przeklętej klawiaturze…
Zauważyłem, że czujnik czasami się gubi i daje wartości np. “25689.9” , to jest śmieć a nie odległość, więc napisałem funkcje
1 2 3 4 5 6 7 8 9 10 |
def sensorDateIsOutOfRangeOrOFF(msg): global buzzerFrequency if msg < 0: Display.Clear() buzzerFrequency = -1 elif msg > sensorDataOutOfRange: digits = [127,0,127,127] Display.Show(digits) Display.ShowDoublepoint(1,1) buzzerFrequency = 0.6 |
Pamiętacie , że NodeMCU wysyła wiadomość “-100” , kiedy go o to poprosimy, jest to sygnał dla Raspberry, aby wyłączyć buzzer oraz wyświetlacz. Natomiast kiedy przyjdzie wiadomość z liczbą ponad 2000 to zapali się tylko środkowa kropka oraz zero po jej lewej stronie.
Czym jest magiczna liczba 127 otóż określa nam, że ten segment po prostu cały gaśnie :).
AutoStart
Na koniec , aby nie korzystać już z tej felernej klawiatury postanowiłem, aby mój program włączał się na auto starcie. Jednak pojawił się następny problem, ale to w historyjce na koniec.
Smartphone
Na koniec zainstalowałem aplikacje MQTT Dash, gdzie ktoś wykonał kawał dobrej roboty, konfiguracja jest bajecznie prosta i z tą aplikacją aż chce się robić jakieś rzeczy IoT.
MQTT Cloud
Jako Brokera użyłem:
1 |
https://www.cloudmqtt.com/ |
Do takich zabaw bardzo polecam. W łatwy sposób tworzymy użytkowników i publikujemy w dowolnym temacie.
Historyjka kończąca
Każdy słyszał teksty typu: “U mnie działa” , “Jeszcze wczoraj wszystko było okej”. Nie jestem typem chwalipiętki. Mam takiego znajomego, który bardzo lubi bawić się różnego typu sensorami, ale ma dużo pracy na co dzień. Także nie było to chwalipieństwo jak mu to pokazałem.
Do sedna: Zrobiłem wszystko, czujniczek działał jak należy 3-razy resetowałem raspberry , aby sprawdzić czy działa wszystko bez najmniejszego problemu. Wszystko działa. ale… dla pewności nagram filmik.
Prawo Murphiego tym razem mnie nie zawiodło. Przyszedł znajomy i mu pokazuje co udało mi się zrobić i zgodnie z prawem, nic nie działało, na wyświetlaczu jakaś czarna magia, buzzer nawet nie piszczał tylko chrząkał… tragedia. Oczywiście za drugim odpaleniem wszystko działało, no ale efekt pierwszego razu runął.
Podsumowanie
Dziękuję , że aż tutaj dotarłeś. Mam nadzieję, że się podobało. Nie chciałem opisywać całego kodu , przedstawiłem tutaj bardziej koncepcje oraz jak pokonałem różnej maści problemy.
Jeśli masz jakieś pytania to zapraszam do komentowania :)
Zasubskrybować piszemy razem.
Poprawione :)
Fajnie że się pobawiłeś i się tym chwalisz. Jednak, to nie jest projekt tylko reportaż z prototypowania. Czujnik parkowania musi być maksymalnie prosty, bo musi być maksymalnie niezawodny, np czujnik + arduino + wyświetlacz + buzzer. Zainstalowałbyś taką pajęczynę komponentów w swoim samochodzie i ufał byś temu ?
Uruchamiasz samochód, potrzebujesz wycofać, ale musisz poczekać aż się malinka zabootuje… :-)
“chwalipięta” to jeden wyraz, nie dwa. Sam nie jestem mistrzem mowy polskiej, ale to bije po oczach.
Oczywiście błędy poprawione :)
Odnośnie Twojego komentarza,
1) Jest to przekombinowane , bo nie miałem innego pomysłu na komunikacje. W dzisiejszych czasach wszystko wpiętke jest w magistrale CAN, a że standard CAN nie jest publiczy to musiałem ratować się właśnie w ten sposób.
2) Niezawodność gwarantuje się w troszeczkę inne sposób, w sposób obsługi błędów, faktycznie nie uwzględniłem niczego takiego tutaj bo jak sam stwierdziłeś jest to tylko prototyp.
3) W swoim samochodzie zamontował bym to podobnie. Też na malinie. Zauważ , że jeżeli odpalasz samochód to nic nie dzieje się odrazu tylko system musi się “podnieść”. Nie ma tak, że silnik się odpala i nagle radio zaczyna grać.
4) Tak naprawdę na arduino byś tego nie zaimplementował , bo potrzebujesz wielowoątkowości. Najlepiej jeżeli każdy komponent działa na oddzielnym wątku. Jak dobrze pamiętam to arduino jest jedno wątkowe :)
Podsumowując jest to tylko prototyp, malina musi być lub jakiś inny komponent, który może obsłużyć wiele wątków. Gdybym realizował projekt to napewno zorganizowane było by to bardziej lokalnie , a nie przez internet. Zorganizowałbym jakieś normalnie zasilanie, żeby pozbyć się arduino , które defakto służy mi tlyko jako wlaśnie zasilanie. Zostały by tylko dwa urządzenia Raspbbery, które miało by swój moduł WiFi oraz ESP8266. Dołożył bym większa obsługe logowanie oraz sprawdzania błędów.
Dzięki za Twoją opinię! :)