Hej,
chciałbym przedstawić Wam dzisiaj mój projekt, który powstał ponad 2,5 roku temu. Tzn. wtedy powstała jego pierwsza wersja, która od tamtej pory przeszła kilka modyfikacji ;)
Pokażę Wam dzisiaj, jak zrobić… analogowy wskaźnik liczby osób przebywających aktualnie na stronie www.
Pierwsza wersja prototypu mojego wskaźnika powstała w styczniu 2014 roku i została zbudowana na bazie Arduino UNO i modułu ethernet ENC28j60. Całość prezentowała się wtedy tak:
Jest to jeden z tych projektów, który powstał jako “szybki prototyp” i w takiej formie działał ponad dwa lata. Dosłownie w takiej formie, jaką widać na powyższym obrazku :D
Dopiero kilka miesięcy temu, gdy poznałem cudo, którym jest ESP8266, postanowiłem projekt nieco przerobić i wreszcie złożyć w całość i zamknąć w jakiejś obudowie.
Dlaczego ESP8266, a nie Arduino z modułem ENC28j60?
Powodów jest kilka:
- Cena. ESP8266 kosztuje ~8 zł (u majfriendów jeszcze taniej) i w standardzie ma zbudowany moduł WiFi.
- Rozmiar. Moduły ESP8266 są dużo mniejsze nawet od samego modułu ENC28j60, więc łatwiej je upakować w niedużej obudowie.
- WiFi. Możliwość wykorzystania łączności WiFi sprawia, że urządzenie jest dużo poręczniejsze i bardziej mobilne, bo wystarczy je tylko wpiąć do zasilania bez potrzeby ciągnięcia kabla sieciowego.
W modułach ESP jestem generalnie zakochany po uszy i robię na nich dosłownie wszystko :D
Ok – bierzmy się do roboty.
Skrypt PHP zliczający osoby na stronie
Aby licznik mógł działać, musi skądś pobierać informacje o tym, ile osób przebywa aktualnie na stronie. Początkowo próbowałem wydobywać te dane z Google Analytics, ale była to operacja bardzo czasochłonna i karkołomna. Dodatkowo Google narzuca dziennie limity wysyłanych zapytań, więc licznik mógłby się odświeżać najczęściej co 30 sekund, a to było dla mnie stanowczo za rzadko.
W docelowej wersji postawiłem na prosty skrypt PHP, którego jedynym zadaniem jest zliczanie i wyświetlanie liczby osób online.
Cały skrypt wygląda tak:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<?php $host = "localhost"; $user = "..."; $pass = "..."; $db = "..."; $conn = mysql_connect("$host", "$user", "$pass") or die ("Błąd połączenia z bazą."); mysql_select_db("$db", $conn); class usersOnline { var $timeout = 300; var $count = 0; var $error; var $i = 0; function usersOnline () { if($_GET['esp'] != 1) { $this->timestamp = time(); $this->ip = $this->ipCheck(); $this->new_user(); $this->delete_user(); } $this->count_users(); } function ipCheck() { foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } } function new_user() { $insert = mysql_query ("INSERT INTO majsterkowo_online(timestamp, ip) VALUES ('$this->timestamp', '$this->ip')"); if (!$insert) { $this->error[$this->i] = "Unable to record new visitor\r\n"; $this->i ++; } } function delete_user() { $delete = mysql_query ("DELETE FROM majsterkowo_online WHERE timestamp < ($this->timestamp - $this->timeout)"); if (!$delete) { $this->error[$this->i] = "Unable to delete visitors"; $this->i ++; } } function count_users() { if (count($this->error) == 0) { $count = mysql_num_rows ( mysql_query("SELECT DISTINCT ip FROM majsterkowo_online")); return $count; } } } $visitors_online = new usersOnline(); if($_GET['esp'] == 1) { if (count($visitors_online->error) == 0) { echo $visitors_online->count_users(); echo "\r\n\r\n"; } } ?> |
Nie będę ukrywał, że skrypt nie jest mojego autorstwa. W dużej części bazuje na gotowcach znalezionych w sieci, które dopiero później dostosowywałem do swoich potrzeb.
Adam Ziaja podesłał mi poprawkę bezpieczeństwa, którą już wdrożyłem w powyższym skrypcie. Dzięki!
W linii 11 znajduje się zmienna $timeout = 300; – jest to czas (w sekundach), przez jaki użytkownik jest zapamiętywany przez skrypt. Jeżeli użytkownik będzie nieaktywny przez ten czas, przestanie być zliczany przez skrypt. Wartość “300” (5 minut) dobrałem eksperymentalnie w taki sposób, żeby wartości zwracane przez skrypt jak najbardziej pokrywały się z tym, co pokazuje Google Analytics.
Do poprawnego działania skrypt wymaga utworzenia prostej bazy danych, w której są przechowywane informacje o użytkownikach aktualnie przebywających na stronie.
Bazę danych możecie utworzyć wykonując następującą kwerendę:
1 2 3 4 5 6 7 |
CREATE TABLE `majsterkowo_online` ( `ip` VARCHAR(15) NOT NULL, `timestamp` VARCHAR(10) NOT NULL ) COLLATE='utf8_general_ci' ENGINE=InnoDB ; |
Pamiętajcie tylko, żeby po utworzeniu bazy dodać na początku skryptu dane, które pozwolą się z nią połączyć :)
Gotowy plik umieściłem na serwerze, a następnie w kodzie strony dodałem ramkę iframe o rozmiarach 1×1 px, w której skrypt jest ładowany:
1 |
<iframe src="http://majsterkowo.pl/adres/do/licznik.php" style="width: 1px; height: 1px; display: hidden;"></iframe> |
I od strony strony (jakkolwiek by to nie zabrzmiało;) to już wszystko. Możemy przejść do tej bardziej namacalnej części projektu.
Kod programu do ESP8266
Program do ESP przedstawia się następująco:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
#include <ESP8266WiFi.h> #include <Timers.h> // Stałe #define czestotliwosc 2 #define dioda_x10 14 #define licznik 15 // Zmienne String ile = "0"; int ile_osob_pwm; // Timery Timer uaktualnij; // Konfiguracja WiFi const char *ssid = "..."; const char *pass = "..."; const char *host = "majsterkowo.pl"; void setup() { Serial.begin(115200); // Piny pinMode(licznik, OUTPUT); analogWrite(licznik, 0); pinMode(dioda_x10, OUTPUT); digitalWrite(dioda_x10, LOW); delay(1000); Serial.println("START"); // WiFi WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass); int retries = 0; while (WiFi.status() != WL_CONNECTED) {delay(500); Serial.print(".");} if (WiFi.status() == WL_CONNECTED) { Serial.println(F("IP address: ")); Serial.println(WiFi.localIP()); } uaktualnij.begin(SECS(czestotliwosc)); } void loop() { if(uaktualnij.available()) { Serial.print("Ile: "); WiFiClient client; const int httpPort = 80; if(!client.connect(host, httpPort)) { Serial.println(F("Connection failed")); return; } String url = "/adres/do/licznik.php?esp=1"; client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); int timeout = millis() + 5000; while(client.available() == 0) { if(timeout - millis() < 0) { Serial.println(F(">>> Client Timeout!")); client.stop(); return; } } int lines_received = 0; while(client.available() && lines_received <= 9) { String line = client.readStringUntil('\r'); if(lines_received == 8) { ile = line.substring(1,line.length()); int ile_osob = atoi(ile.c_str()); if(ile_osob <= 100) { int ile_osob_pwm = map(ile_osob, 0, 100, 0, 460); digitalWrite(dioda_x10, LOW); } else { int ile_osob_pwm = map(ile_osob, 0, 1000, 0, 460); digitalWrite(dioda_x10, HIGH); } analogWrite(licznik, ile_osob_pwm); Serial.println(ile_osob); } lines_received++; } uaktualnij.restart(); } delay(0); } |
Do poprawnego działania kodu potrzebna jest biblioteka Timers (ja wykorzystałem najnowszą wersję nr 16.4.0).
Jak widzicie kod stosunkowo prosty i jego działanie sprowadza się do kilku kroków:
- Pobierz adres http://majsterkowo.pl/adres/do/licznik.php?esp=1 (adres jest składany z hosta podanego w linii 19, oraz ścieżki do pliku w linii 57)
- Z dziewiątej linii odczytaj liczbę osób na stronie (w poprzednich liniach znajdują się nagłówki HTTP) (linia 72),
- Przekształć liczbę pobraną jako String na Int (linia 75),
- Sprawdź, czy liczba jest większa od 100:
- Jeżeli jest mniejsza lub równa 100, zmapuj wartości odczytane w zakresie od 0 – 100 na wartości od 0 – 255 (linia 76),
- Jeżeli jest większa od 100, zmapuj odczytane wartości w zakresie od 0 – 1000 na wartości od 0 – 255 (linia 80). Dodatkowo zapal diodę sygnalizującą przełączenie na drugi zakres,
- Ustaw na wyjściu PWM o wypełnieniu uzyskanym w poprzednim punkcie (linia 85),
- Zresetuj timer (linia 91),
- I od nowa :)
I tyle. Wyjaśnienia wymagają tylko dwie rzeczy.
Jak pewnie zauważyliście, do pobieranego adresu dodałem zmienną esp=1. Została ona dodana po to, aby skrypt na serwerze nie zliczał zapytał wysyłanych przez nasze ESP jako kolejnej wizyty na stronie.
W drugim punkcie pisałem z kolei o odczytywaniu liczby osób na stronie z 9 linii. W praktyce może się zdarzyć tak, że u Was tych linii z nagłówkami HTTP będzie mniej lub więcej. Wszystko zależy od konfiguracji serwera. Listę nagłówków, które zwraca Wasz serwer, możecie sprawdzić na tej stronie. U mnie wygląda to tak:
1 2 3 4 5 6 7 8 9 |
HTTP/1.0 200 OK Cache-Control: public, max-age=1 Expires: Thu, 18 Aug 2016 23:59:02 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 6 Date: Thu, 18 Aug 2016 23:59:01 GMT Accept-Ranges: bytes Server: LiteSpeed Connection: close |
Zwróćcie też uwagę na to, że w programie linie są numerowane od 0, więc dziewiąta linia występuje w programie jako 8.
Mam nadzieję, że nikt nie będzie miał problemów z tą częścią projektu. Jak coś, to pytajcie śmiało w komentarzach.
Hardware, czyli składamy licznik
Raster modułów ESP nie pozwala na wlutowanie ich bezpośrednio w uniwersalną płytkę drukowaną, ani wpięcie w płytkę stykową, dlatego dla wygody zawsze wlutowuję moduł w adapter PCB tego typu:
Do modułów ESP są takie dostępne inne adaptery wykonane na białej płytce PCB, jednak odradzam ich zakup. Po pierwsze – mają pomylone oznaczenia pinów, a po drugie są szersze, więc po wpięciu ich w płytkę stykową nie mamy już możliwości wpięcia obok jakiegokolwiek przewodu.
Na płytce adaptera mamy od razu wlutowany stabilizator napięcia, więc możemy zasilać płytkę bezpośrednio z 5V, oraz wszystkie rezystory potrzebne przy programowaniu modułu.
Samo podłączenie całego układu jest banalnie proste i sprowadza się w sumie do dosłownie kilku przewodów:
Do tego oczywiście zasilanie – albo 3,3V bezpośrednio do ESP, albo 5V do adaptera (co jest wygodniejsze, bo można zasilać całość z USB).
W roli miernika wykorzystałem stary analogowy miernik… cholera wie czego ;)
Wypatrzyłem go w lokalnym sklepie z elektronicznym outletem i nawet sprzedawczyni nie wiedziała, co on mógł mierzyć. Intuicja podpowiedziała mi, że mógł to być miernik współczynnika wypełnienia. I chyba trafiłem, bo podając na niego sygnał PWM o różnym wypełnieniu elegancko można nim sterować ;)
Wyskalowanie licznika
Aby licznik wskazywał prawidłowe wartości musimy najpierw ustalić, przy jakiej wartości PWM będzie wskazywał 100%. Można to prosto ustalić eksperymentalnie wgrywając taki oto prosty program:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <ESP8266WiFi.h> #include <Timers.h> // Stałe #define licznik 15 void setup() { pinMode(licznik, OUTPUT); analogWrite(licznik, 500); } void loop() { } |
Jeżeli wskazówka wychyla się za słabo, to zwiększamy wartość analogWrite na wyższą i wgrywamy poprawiony program. Jeżeli wskazówka wychyla się za mocno, to wartość zmniejszamy. I tak do skutku, aż znajdziemy wartość odpowiadającą maksymalnemu wychyleniu wskazówki (u mnie jest to 460), którą następnie podstawiamy w programie docelowym w funkcji map() (w liniach 76 i 80).
Obudowa
Początkowo chciałem zaadoptować jakąś gotową obudowę, ale skoro w naszym makerspace Fabryka mam do dyspozycji drukarkę 3D, odpaliłem Solidworksa i wysmarowałem na szybko prosty projekt obudowy:
Projekt obudowy możecie pobrać tutaj: Obudowa licznika (majsterkowo.pl).zip
Początkowo próbowałem drukować obudowę z ABS, ale po kilku próbach poległem. Albo model odklejał się od raftu, albo pękał w losowych miejscach. ABS ma niestety dosyć duży skurcz podczas stygnięcia, przez co jest on dosyć problematyczny przy drukowaniu dużych przedmiotów. Trudno – musiałem odżałować trochę kasy i zainwestowałem w szpulę tworzywa HIPS, które idealnie nadaje się do drukowania obudów :)
Po około 19 godzinach obudowa była gotowa, więc mogłem złożyć całość do kupy.
Gotowe!
Złożony i działający licznik prezentuje się następująco:
W prawym-górnym rogu (zaraz za skalą) umieściłem żółtą diodę sygnalizującą przełączenie zakresu na x10:
Mam nadzieję, że projekt się Wam spodobał. Ja sam cieszę się z tego gadżetu jak małe dziecko – tym bardziej, że był to chyba projekt, który przez najdłuższy czas (prawie 3 lata) działał u mnie w formie prototypu zmontowanego w formie pająka i prawdę mówiąc sam nie wierzyłem, że kiedyś się zbiorę, żeby go skończyć. Teraz jednak więcej czasu spędzam w domu, więc mam więcej czasu na podokańczanie starych projektów, więc bądźcie czujni, bo niebawem pojawią się nowe artykuły :D
PS – ciekawe do ilu podskoczy wskazówka po publikacji tego materiału ;)
Pozdrawiam!
Łukasz
Zacytuję klasyka: “its kurwa bjutiful!”.
Zdziwię się jeśli nikt tego nie zaadoptuje na nagrody branżowe. Świetne.
marcin Michno: Dzięki! :D
marcin Michno: Lepiej tego nazwać nie można. Prosty , skuteczny, czytelny, użyteczny – jeden z ciekawszych projektów tutaj. Na dodatek uwielbiam analogowe przyrządy pomiarowe :)
Jeśli masz ochote zmienic metode zbierania danych to mogę pomóc z api analytics.
@Paweł: Dzięki Pawle, ale już to przerabiałem. Była z tym wielka masa kombinowania (jakieś aplikacje na google apps i inne cuda), a dodatkowo ograniczona dobowa ilość zapytań, dlatego sobie to odpuściłem. Jeszcze o ile to ograniczenie ilości zapytań (wychodziło jedno zapytanie na chyba 10 sekund) można przeboleć, o tyle cała procedura zdobywania dostępu do ilości osób na stronie była istną mordęgą ;)
Solidworks jest drogi. Poza zasięgiem majsterkowicza :( ale obudowa wyszła Ci zgrabna
adam: Jest drogi, ale i możliwości ma olbrzymie. A do tego w wielu fablabach w PL można dostać darmową licencję (np. u nas w w makerspace Fabryka w Zielonej Górze mamy darmowe licencje dla naszych członków:)
Fajne, wystarczy jedno spojrzenie i już wiadomo że w “majsterkowie” coś się dzieje :)
engobos: Właśnie dlatego lubię mieć zawsze na oku ruch w Majsterkowie, żeby od razu wylapywac jakieś nagłe wzrosty :)
Świetny pomysł, mega wykonanie – chapeaux bas Łukasz!
@Ludwik C. Siadlak: Dzięki wielkie za uznanie :)
Łukaszu jesteś genialny, hahaha ogromny + za pomysłowość – zajebista robota
@Maria: Piękne dzięki!
To jest mega [pozytywne. Japończyk by powiedział – “Oto człowiek, który połączył stare z nowym” :) Gratki!
@pepe: Jak to mówią – potrzeba matką wynalazków ;)
Po prostu prosisz się o Sqlinjection
if (!filter_var($ip, FILTER_VALIDATE_IP) === false) {
echo(“$ip is a valid IP address”);
} else {
echo(“$ip is not a valid IP address”);
}
Albo: http://www.php.net/manual/en/function.long2ip.php
@safdasdsa: Już dodałem poprawkę bezpieczeństwa :)
“Liczby osób”, a nie ilości. Osoby są policzalne, to nie jest mąka, czy cukier. Proszę o poprawienie, bo potem takie babole idą w świat (wykop peel) i się szerzą w narodzie.
@Zbigniew: Poprawione. Dzięki za zwrócenie uwagi.
// code
if ($visitors_online->count_users() == 1) {
echo $visitors_online->count_users();
}
else {
echo $visitors_online->count_users();
}
// code
Czytam i czytam, i nie mogę dojść do tego po co ta powyższa instrukcja warunkowa, skoro wewnątrz wykonuje się ten sam kod? Nie mniej jednak: nice job :)
@m1chu: Tam w piertownej wersji działo się coś innego. Później coś tam poprawniałem, “na chwilę” zmieniałem i tak jakoś zostało ;) Zaraz to poprawię.
Do skryptu PHP dodałem łatkę bezpieczeństwa. Podziękowania dla Adama za podesłanie poprawki :)
@Łukasz Więcek: Świetnie rozwiązanie ale do jednej rzeczy muszę się przyczepić.
Wiem że kod PHP to tutaj tylko dodatek do całości i ma działać a nie wyglądać, ale ten snippet jest napisany w standardzie PHP w wersji 4, której rozwiązania są od dawna oznaczone jako “deprecated” a część rzeczy po prostu nie zadziała z najnowszą wersją silnika PHP 7 który wkrótce stanie się standardem.
Dlatego moim zdaniem kod powinien zostać nieco unowocześniony bo inaczej jest ryzyko że wkrótce ten skrypt na coraz większej ilości hostingów nie będzie działał :p
Jeśli chcesz to mogę Ci przygotować wersje skryptu która będzie kompatybilna wstecznie z PHP5 (który jeszcze jest standardem u większości providerów) ale jednocześnie będzie działać z nowym silnikiem :)
dominik G: Jeżeli masz wolną chwilę i chciałbyś napisać ten skrypt, to chętnie skorzystam. Zadowolisz się wyrazem wdzięczności w postaci wzmianki (z linkiem) w materiale? :)
Hej, jak szukać takiej płytki adaptera do ESP? Jest na Aliexpress? ;)
@Haxor: Ja brałem na Allegro: http://goo.gl/46HELR. Kosztują zaledwie kilka złotych, więc trochę nie ma sensu ściągać z chin i czekać miesiąc na przesyłkę ;)
Drobny drobiazg:
“Biała” płytka do ESP nie ma błędu, błąd był swego czasu w bibliotece Eagle. Mam te płytki zastosowane już w trzech układach i opis pinów zgadza się z ESP-12E. Myląca może być kolejność pinów po lewej (14, 12, 13) ale ten model naprawdę tak ma.
Płytka ma wlutowane rezystory do GPIO-15 i CS oraz miejsce na stabilizator.
Co do wielkości – fakt, największa wada.
Poza tym piona za pomysł!
ethanak: Hm… może wyszła jakaś poprawiona wersja białych płytek, bo w tych, które ja miałem, był jakiś ewidentny błąd (bodajże któryś pin był błędnie opisany jako GPIO15, przez co na płytce były dwa piny oznaczone jako 15, a brakowało jakiegoś innego).
Jescze tylko zrobić livestream na YouTube z kamerą na licznik :D.
Stefan: W domu nie pozwala mi na to łącze, ale może za kilka dni zaniosę licznik do naszego zielonogórskiego fablabu i tam puszczę stream :)
MEGA! Brawo!
Piotr Lebek: Dzięki!
Rozumiem, że przewidziana skala jest do 100 użytkowników jednocześnie ? A co się dzieje jak jest 110 ? Wskazówka wychyla się maksymalnie w prawo ?
@Mac-World.PL: Wszystko jest opisane w tekście ;) Po przekroczeniu 100 licznik przełącza się na drugi zakres i wtedy skala jest liczona x10, czyli jest od 0 do 1000 (dioda LED sygnalizuje zmianę zakresu).
Kurde jakoś mi to umknęło, dzięki za wyjaśnienie :)
Fajny gadżet, tylko niestety ale nie na moje umiejętności techniczne :(
Hej, super pomysł! Gratuluję wykonania.
A jaki moduł poleciłbyś w przypadku, gdy chciałbym stworzyć 4 lub więcej takich liczników? Jestem zielony w elektronice, ale za to biegły w programowaniu. Rozumiem, że wtedy układ musiałby mieć 4 lub więcej wyjść analogowych. Bo nie ma możliwości (aby w łatwy sposób, bez dodatkowych układów) zamienić wyjścia cyfrowe na analogowe?
marcin: Spokojnie zrobisz to na tym samym ESP8266. Ten licznik nie jest sterowany sygnałem analogowym, ale cyfrowym PWM – czyli sygnale o różnym współczynniku wypełnienia. A to bez problemu ogarniesz :)
Bardzo fajne ciekawostki. Chętnie rozejrzę się w temacie :)
Łukasz, możesz podpiąć webcam na licznik? Chciałem małą symulację zapuścić ;-)
@MJeczalik: Podepnę na dniach. Do tej pory miałem za słabe łącze w chacie, żeby robić jakikolwiek streaming, ale właśnie czekam na kuriera, kóry przywiezie mi antenę do LTE, więc będzie można poszaleć ;) Dam znać, jak ruszy streaming.
takie liczniki były wykorzystywane w zakładach chemicznych w procesie technologicznym pokazywały pojemność cieczy w zbiornikach …
O widzisz. Dzięki za info!
Genialny projekt. Gratulacje!
Bardzo chętnie bym to kupił! Mój blog rośnie i marzyłbym o takim wyświetlaniu sobie aktualnej liczby na www :) daj daj daj daj!
Cześć, dzięki za fajny projekt :).
Chciałem zrobić coś podobnego, ale pojawił się problem, ponieważ chcę przekazać do ESP więcej niż jedną daną. Czy wiesz jak można by przerzucić elementy do następnej linii tak aby dało się to odczytać tak jak u Ciebie? Znacznik to robi, ale w źródle strony już wszystko jest po kolei wraz ze znacznikiem.
Hmm, zawsze tak mam że myślę nad czymś chwilę postanowię się zapytać a potem w sumie wymyślam sposób :P.
Właściwie jeśli odbieram tę jedną linie jako string to wystarczą mi spacje pomiędzy danymi i będę mógł to już obrobić programowo w samym ESP :).