«

»

Lis 24 2014

3. Vzdálené ovládání zásuvky na 220V přes ssh, android zařízením…

Zasuvka220Rele_1

Další možnost využití Raspberry Pi v domácnosti :). Před časem jsem si pořídil zařízení WD My Cloud – 3TB od western digital (byl za cenu HDD) a zjistil jsem, že není třeba, aby zařízení běželo 24/7. Spotřeba tohoto zařízení v klidovém stavu je cca 6-10 watů, a to o proti raspberry pi je téměř 5ti násobné. Jenže takovéto domácí cloudy se dají pouze vypnout přes webové rozhraní, ale už nejdou nijak vzdáleně spustit. Jak to tedy udělat, aby si každý v domácnosti toto zařízení sám vzdáleně spustil, popřípadě zkontroloval, zda již neběží. (Samozřejmostí je mít veřejnou IP adresu, aby se to dalo ovládat i přes internet. Pokud není, tak bude fungovat pouze na místní síti).

 

 

 

 

Základní součástky a zapojení

  • relé
  • prodlužovací kabel se zásuvkou na 220V o délce 1,5m
  • mikro spínač
  • odpor 10K
  • kabely a nepájivé pole

 

zapojení tlačítka

bude umístěné na stole v nepájivém poli pro případné zapnutí, vypnutí NAS serveru (pokud člověk nechce zapínat přes aplikaci přes telefon, popřípadě přes ssh), zapojeného do této zásuvky

Zasuvka220Rele_5Zasuvka220Rele_6

Zasuvka220Rele_4Zasuvka220Rele_3

prodlužovací kabel se zásuvkou + relé

Zasuvka220Rele_8  Zasuvka220Rele_10

 

kabel pro ovládání relé

nůžkami jsem odstřihnul 4 pin, tak aby to šlo zastrčit do relé

Zasuvka220Rele_11 Zasuvka220Rele_12 Zasuvka220Rele_13 Zasuvka220Rele_17

 

 

Ovládání pomocí mikrotlačítka

skript pro ovládání tlačítkem, musí nejdříve zkontrolovat stav (zapnuto/vypnuto) a poté udělat příslušnou operaci.

vytvoření skriptu button.py

Notice

sudo nano button.py

a vložit

Notice

import time
import RPi.GPIO as GPIO

def main():

    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)

    GPIO.setup(19,GPIO.IN)
    GPIO.setup(26,GPIO.OUT)

    while True:
        if GPIO.input(19):
             testButton = "false"
        else:
             testButton =  "true"

        #print testButton
        time.sleep(0.15)

        if GPIO.input(26):
           testZasuvka = "true"
        else:
           testZasuvka =  "false"

       # print testZasuvka
        time.sleep(0.15)

        if(testButton=="true" and testZasuvka=="true"):
                GPIO.output(26,False)
        if (testButton=="true" and testZasuvka=="false"):
                GPIO.output(26,True)

#GPIO.cleanup()

if __name__=="__main__":
    main()

uzavřít a uložit soubor (CTRL+x a pak „y“ a enter)

aby se tento skript automaticky vždy spustil po startu RPi (aby mohlo být tlačítko obslouženo, v případě stisknutí), tak je nutné dodat řádek do cronu s nastavením pro tento skript

Notice

sudo crontab -e

na konec přidáme tento řádek

Notice

@reboot sudo python /home/pi/button.py

uzavřít a uložit soubor (CTRL+x a pak „y“ a enter)

Ovládání a kontrola stavu pomocí konzole (přes SSH)

vytvoření skriptu status.py pro kontrolu stavu, zda je nyní zásuvka zapnutá, popřípadě vypnutá

Notice

sudo nano status.py

a vložit

Notice

import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(26,GPIO.OUT)

if GPIO.input(26):
 testZasuvka = "running"
else:
 testZasuvka =  "stopped"

print testZasuvka

 uzavřít a uložit soubor (CTRL+x a pak „y“ a enter)

test skriptu status.py

Zasuvka220Rele_18

vytvoření skriptu cloud.py pro zapnutí, popřípadě vypnutí (záleží na tom, v jakém je to aktuálním stavu)

Notice

sudo nano cloud.py

a vložit

Notice

import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(26,GPIO.OUT)
if GPIO.input(26):
        GPIO.output(26,False)
        testZasuvka =  "stopped"
else:
        GPIO.output(26,True)
        testZasuvka = "running"
print testZasuvka

uzavřít a uložit soubor (CTRL+x a pak „y“ a enter)

test skriptu cloud.py

Zasuvka220Rele_19

 

Ovládání a kontrola stavu přes Android zařízení 

pro ovládání přes internet je nutné mít veřejnou IP adresu. (Jinak funguje pouze po připojení v místní síti). Ve skriptu programu pro android, se při spuštění zkontroluje současný stav přes StatusCloud()  – zavolá dotaz na PHP skript (android_cloud.php) a tento skript zavolá skript pro zjištění stavu v PYTHONu (status.py viz. výše), pak se zjistí google účet (GetUserAccount()) a ID zařízení (GetAndroidID()) a to z toho důvodu, aby ovládání zásuvky ovládal jen oprávněný uživatel a nemohlo dojít k nějakému zneužití :). Poslední metodou tam je CloudControl(), který může ovládat zásuvku, který pošle dotaz s uživatelským jménem a ID zařízení na PHP skript (android_cloud_control.php), který nejdříve ověří, že se jedná o oprávněného uživatele a pokud ano, tak PHP skript zavolá skript v PYTHONu (cloud.py viz. výše), který provede změnu.

Zasuvka220Rele_22 Zasuvka220Rele_21

 

MainActivity.java

Notice

package cz.androidapk.teplota;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONObject;
import cz.androidapk.teplota.R.id;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.Typeface;
import android.provider.Settings;
import android.util.Log;
import android.util.Patterns;
import android.util.TypedValue;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
public class MainActivity extends Activity {
    String resultStatusCloud = "";
    ImageView statusImage;
    String UserAccountEmail;
    String AndroidID;
    ToggleButton toggle;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);
        statusImage = (ImageView) findViewById(id.imageView);
        StrictMode.enableDefaults();
        getData();
    }
    public void getData(){
        StatusCloud();
        UserAccountEmail = GetUserAccount();
        AndroidID = GetAndroidID();
        CloudControl();
    }
    public void StatusCloud(){
//status cloud
        InputStream isr = null;
        try{
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost("http://localhost/android_cloud.php");
            HttpResponse response = httpclient.execute(httppost);
            HttpEntity entity = response.getEntity();
            isr = entity.getContent();
        }catch(Exception e) {
            Log.e("log_tag","Error on HTTP connection " + e.toString());
            resultView.setText("Couldnt connect to HTTP");
        }
        try{
            BufferedReader reader = new BufferedReader(new InputStreamReader(isr,"iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null){
                sb.append(line + "\n");
            }
            isr.close();
            resultStatusCloud = sb.toString();
        }catch(Exception e){
            Log.e("log_tag","Error converting result "+ e.toString());
        }
//parse json data
        try{
            ;
            Log.e("log_tag", resultStatusCloud);
            if(resultStatusCloud.equals("stopped\n")){
                statusImage.setBackgroundResource(R.drawable.red_on_64);
            }else if(resultStatusCloud.equals("running\n")){
                statusImage.setBackgroundResource(R.drawable.green_on_64);
            }else{
                statusImage.setBackgroundResource(R.drawable.white_on_64);
            }
        }catch (Exception e){
            Log.e("log_tag", "Error Parsing data "+ e.toString());
        }
    }
    public String GetUserAccount() {
        Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
        Account[] accounts = AccountManager.get(this).getAccountsByType("com.google");
        String possibleEmail = null;
        for (Account account : accounts) {
            if (emailPattern.matcher(account.name).matches()) {
                possibleEmail = account.name;
//Log.e("log_tag",possibleEmail);
            }
        }
        return possibleEmail;
    }
    public String GetAndroidID(){
        String android_id = Settings.Secure.getString(this.getContentResolver(),
                Settings.Secure.ANDROID_ID);
// Log.e("log_tag",android_id);
        return android_id;
    }
    public void CloudControl(){
        toggle = (ToggleButton) findViewById(id.toggleButton);
        if (resultStatusCloud=="stopped"){
            toggle.setChecked(false);
        }else if (resultStatusCloud=="running"){
            toggle.setChecked(true);
        }
        toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    InputStream isr = null;
                    String resultControlCloud;
                    try {
                        HttpClient httpclient = new DefaultHttpClient();
                        HttpPost httppost = new HttpPost("http://localhost/android_cloud_control.php");
//add your data
                        ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
                        nameValuePairs.add(new BasicNameValuePair("username",UserAccountEmail));
                        nameValuePairs.add(new BasicNameValuePair("password",AndroidID));
                        httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
//Execute HTTP Post Request
                        HttpResponse response = httpclient.execute(httppost);
                        HttpEntity entity = response.getEntity();
                        isr = entity.getContent();
                    }catch(Exception e) {
                        Log.e("log_tag","Error on HTTP connection " + e.toString());
                        resultView.setText("Couldnt connect to HTTP");
                    }Log.e("log_tag","isChecked - true");
                    try{
                        BufferedReader reader = new BufferedReader(new InputStreamReader(isr,"iso-8859-1"), 8);
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        while ((line = reader.readLine()) != null){
                            sb.append(line + "\n");
                        }
                        isr.close();
                        resultControlCloud = sb.toString();
                        Log.e("log_tag",resultControlCloud);
                        if(resultControlCloud.equals("error\n")){
                            toggle.setChecked(false);
                            Context context = getApplicationContext();
                            CharSequence text = "Tak to ti neprojde 🙂 ";
                            int duration = Toast.LENGTH_LONG;
                            Toast toast = Toast.makeText(context, text, duration);
                            toast.show();
                        }
                    }catch(Exception e) {
                        Log.e("log_tag", "Error converting result " + e.toString());
                    }
                    getData();
                } else {
                    InputStream isr = null;
                    String resultControlCloud;
                    try {
                        HttpClient httpclient = new DefaultHttpClient();
                        HttpPost httppost = new HttpPost("http://localhost/android_cloud_control.php");
//add your data
                        ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
                        nameValuePairs.add(new BasicNameValuePair("username",UserAccountEmail));
                        nameValuePairs.add(new BasicNameValuePair("password",AndroidID));
                        httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
//Execute HTTP Post Request
                        HttpResponse response = httpclient.execute(httppost);
                        HttpEntity entity = response.getEntity();
                        isr = entity.getContent();
                    }catch(Exception e) {
                        Log.e("log_tag","Error on HTTP connection " + e.toString());
                        resultView.setText("Couldnt connect to HTTP");
                    }Log.e("log_tag","isChecked - else");
                    try{
                        BufferedReader reader = new BufferedReader(new InputStreamReader(isr,"iso-8859-1"), 8);
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        while ((line = reader.readLine()) != null){
                            sb.append(line + "\n");
                        }
                        isr.close();
                        resultControlCloud = sb.toString();
                        Log.e("log_tag",resultControlCloud);
                        if(resultControlCloud.equals("error\n")){
                            toggle.setChecked(true);
                            Context context = getApplicationContext();
                            CharSequence text = "Tak to ti neprojde 🙂 ";
                            int duration = Toast.LENGTH_LONG;
                            Toast toast = Toast.makeText(context, text, duration);
                            toast.show();
                        }
                    }catch(Exception e) {
                        Log.e("log_tag", "Error converting result " + e.toString());
                    }
                    getData();
                }
            }
        });
    }
}

je3t2 je nutné zde sudo nano /etc/sudoers přidat tento řádek, aby bylo možné spouštět python skripty z PHP kódu.

Notice

sudo nano /etc/sudoers

přidat na konec tento řádek:

www-data ALL=(ALL) NOPASSWD: ALL

uzavřít a uložit soubor (CTRL+x a pak „y“ a enter)

android_cloud.php

Notice

<?php
// Path to the python script - either FULL path or relative to PHP script
  $pythonScript = 'status.py';

  // Path to python executable  - either FULL path or relative to PHP script
  $pythonExec = '/var/www/';

  // Check the file exists and PHP has permission to execute it
  clearstatcache();
  if (!file_exists($pythonExec)) {
    exit("The python executable '$pythonExec' does not exist!");
  }
  if (!is_executable($pythonExec)) {
    exit(("The python executable '$pythonExec' is not executable!"));
  }
  if (!file_exists($pythonScript)) {
    exit("The python script file '$pythonScript' does not exist!");
  }

  // Execute it, and redirect STDERR to STDOUT so we can see error messages as well
  exec("echo 'heslo' | sudo -S python \"$pythonExec\"\"$pythonScript\" 2>&1", $output);
  // Show the output of the script
  print_r($output[0]);
?>

 

android_cloud_control.php

Notice

<?php
$username = $_POST['username'];
$password = $_POST['password'];

if($username == "xxxxx@gmail.com"  && $password == "xxxxxxxxxxxxx") {
 // Path to the python script - either FULL path or relative to PHP script
  $pythonScript = 'cloud.py';

  // Path to python executable  - either FULL path or relative to PHP script
  $pythonExec = '/var/www/';

  // Check the file exists and PHP has permission to execute it
  clearstatcache();
  if (!file_exists($pythonExec)) {
    exit("The python executable '$pythonExec' does not exist!");
  }
  if (!is_executable($pythonExec)) {
    exit(("The python executable '$pythonExec' is not executable!"));
  }
  if (!file_exists($pythonScript)) {
    exit("The python script file '$pythonScript' does not exist!");
  }

  // Execute it, and redirect STDERR to STDOUT so we can see error messages as $
   exec("echo 'heslo' | sudo -S python \"$pythonExec\"\"$pythonScript\" 2>&1", $output);
  // Show the output of the script
 print_r($output[0]);
 }
 else  {
     echo "error";
  //     echo $username;
}
?>

12 comments

Skip to comment form

  1. Bulanek Radim Bulík

    Bylo by dobré napsat všechny potřebné součástky a pokud možno i schéma zapojení desky s relé 🙂
    (tranzistor, …)

    1. Le-Ze

      Toto je jednoduché zapojení 1 GPA k relatku.
      Třeba takto.
      Jen ten 10 k odpor před GPA je třeba vyněnít podle odporu cviky.
      Postačí 2 – 3 k

      Spíše smekám nad znalostí programovávaní a hlavně pod androidem.

      Byl by jste ochotný upravit jeden jednoduchý widget?,
      Dodal bych již stažený a rozbalený.

      1. BeeCZ

        pokud ten widget ma zdrojove kody a nebude treba nekde neco shanet a davat dohromady, abych mel nejakou vyvojovou verzi, tak pokud byl byl cas, tak bych se na to mohl podivat. Muzete poslat na admin@raspberrypi.cz a pak bych na to kouknul. Jinak jsem jiz zprovoznil prvni forum verzi 🙂

    2. BeeCZ

      tranzistor tam zadny neni, jen rele, mikrotlacitko, odpor 10k a to je vse. 2 kabely napajeni a jeden ovladaci jak pro rele tak i pro tlacitko.

      1. Le-Ze

        A co je to za kouzelné relé, které sepne přes 10 k odpor?

          1. Le-Ze

            Tak to jo.
            Ten planek co jsem tu dal s relem RELEM3S05T je řešení za 25 kč
            http://www.gme.cz/relem3s05t-p634-263

          2. BeeCZ

            jj tak toto je tak za 60 kc

            Základní vlastnosti:

            1-kanálové relé
            Napájení / logika: 5V
            Parametry cívky: 5V/70 Ohm
            Zatížitelnost kontaktů: 10A/250VAC,
            10A/30VDC
            LED červená: indikace napájení
            LED zelená: indikace sepnutí
            Sepnutí:
            IN=>“HIGH“ (3.3V/5V)

            Paralelně k cívce relé je
            zapojena ochranná dioda

  2. Aleš

    Ahoj, můžu se zeptat, kde se dají koupit ty „female jumper cables“? Prolezl jsem celé stránky gme.cz a nemůžu je vůbec najít. Díky

  3. Ooondrej

    Prve bych nedoporučoval na řízení 230 využívat tyhle mini relátka,
    dost jiskřej a časem se kontakty zapečou a relátko už nerozepíná (záleží
    teda co tím ovládáte)… Doporučuji použít ssr relátka, která jsou sice
    o něco dražší, ale maj spoustu výhod.

    Dál k tomu pythonímu scriptu…
    true a false se nepíše do uvozovek, prostě napíšeš jen stav = True stav1 = False případně None…

    if nemusíš dávat do závorek, ale pro někoho asi čitelnější?

    Knihovna gpio podporuje jednu super funkcni
    wait_for_edge která nahrazuje čtení tlačítka v nekonečné smyčce…
    http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/

  4. Ján Polča

    Ahoj …. som uplny zaciatocnik s Malinou – preto mi prepacte moju blbu otazku 🙂
    Ako je to so stavom GPIO pri bootovani ? je tam 0 resp. 1 ? Snad som neurobil nieco zle, ale neviem si pomoct s tymto – potrebujem zopnut rele, urobim to cez jednoduchy python skript. Na GPIO nameram cca 3,3V …. Ale pri startovani je tam cca 0,7 – 0,8 V a toto napatie postacuje na zopnutie rele 🙁 A to nemoze byt …. Da sa to nejako osetrit, aby pri boote bola na GPIO nula a rele sa nezoplo ? Dakujem za odpoved…

  5. bob

    Dotaz:
    Kdyz jste admin tohoto webu nesly by obecne stranky s clanky prizpusobit pro tisk tak, aby nabidly cisty clanek popripade clanek+komentare?
    Ten balast co se tam tet primichava je rusivy!
    Bob

Napsat komentář