Le blog d'UNITPROD Geek Inside

10Déc/150

Xcode : can’t add localization to project

Suite à une erreur de manipulation, j'ai supprimé la langue de mon projet Xcode (project -> info -> localizations).

Par conséquence, j'ai voulu en rajouter une nouvelle pour éviter le "This project has not been localized".

Problème, lors de l'ajout de la langue, la fenêtre "Choose files and reference language to create ... localization" est vide, je ne peux rien sélectionner...

Solution :
- en ligne de commande, se rendre dans le répertoire du projet, copier le dossier Base.lproj en en.lproj
- aller dans en.lproj, supprimer tout le contenu
- toujours au même endroit, créer un fichier vide (avec vim par exemple) : Localizable.strings
- ensuite sur Xcode, sur votre projet, "Add files to...", sélectionner en.lproj

Et voilà, la zone Localizations dans les informations de votre projet contient maintenant : "English - Development language" - "1 File Localized".

🙂

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
Remplis sous: Divers Aucun commentaire
10Juil/150

Ecran noir sur MacBook Pro mi-2010

Problème : le MacBook pro 15" de ma conjointe acheté mi 2010 s'est mit à avoir un comportement bizarre du jour au lendemain : l'écran devenait tout noir (comme si il était en veille) mais le Mac continuait de fonctionner et impossible de récupérer l'affichage si ce n'est en forçant l'extinction du brutalement en restant appuyé sur le bouton Power.

Et cela au bout de 4 ans d'utilisation, que je qualifierais de normale et saine : pas tous les jours à travailler dessus, jamais sur des draps, très souvent sur un bureau avec une tablette de ventilation en dessous, pourquoi ?, parce que son utilisation est le montage vidéo.

Je me renseigne donc sur internet pour voir si je suis le seul à avoir ce dysfonctionnement, et je ne suis pas le seul, mais Apple ne reconnait pas le problème.

J'essai donc de détecter ce qui déclenche cet écran noir, mais c'est très aléatoire, je peux avoir le problème 2 fois d'affilées comme pas du tout pendant plusieurs jours, mais je retrouve souvent les mêmes cas : visualisation de photos en mode aperçu, Mission Control, App exposé, ... en gros des effets calculés.

J'essai de lire en diagonale le rapport d'erreur et je vois bien que la carte graphique est concernée, je tente donc un petit tour dans un Apple Store (même hors garantie pour voir si quelque chose est possible). Résultat, la carte graphique Nvidia dédiée est défectueuse, il faut changer la carte mère, coût de l'opération : 600€ tout compris.

Je refuse le devis, insistant que je suis déçu qu'un matériel "professionnel" (c'est comme cela qu'ils le vendent non ?) ne supporte pas une utilisation dans de bonnes conditions au bout de 4 ans, mais en vain.

Je rentre donc chez moi et fait avec le problème (en évitant au mieux les cas cités précédemment) jusqu'à début 2015 où je tombe sur Google sur une prise en charge d'Apple concernant mon problème : affichage d’un écran noir de manière intermittente ou à la perte de vidéo du MacBook Pro (15 pouces, mi-2010)

Bien entendu au moment où je lis cette page, la prise en charge est finie, mais elle ne l'était pas quand j'ai été les voir en 2014...

Bref, trêve de détail sur ma vie, je résume vite fait la suite des évènements : je les re-contacte et explique le problème et me plains de ne pas avoir été pris en charge gratuitement. Ils me renvoient faire des tests pour savoir si je suis concerné, mais malheureusement, même si j'ai le bon problème, je n'ai pas les bons symptômes. Donc ils ne feront rien de plus pour moi, je ne suis pas concerné par cette prise en charge (le contact par mail que j'ai eu à Apple a eu son email supprimé du jour au lendemain, je ne peux même plus lui écrire [non il n'avait pas l'air d'un email temporaire]).

Mais durant le test matériel à l'Apple Store, je retombe sur le conseiller que j'avais eu en 2014 (un signe ? :D) et je lui dit que je ne sais plus quoi faire, que je n'ai pas 600€ à mettre dans un matériel de 2010 ni 1500€ dans un nouveau MacBook Pro...

C'est alors, et c'est maintenant que cela devient intéressant pour vous ^^, qu'il me parle d'un logiciel qui permet de forcer l'utilisation de la carte graphique intégrée plutôt que la dédiée (la Nvidia défectueuse). Son petit nom : GfxCardStatus !

Et je suis heureux de vous annoncer qu'après 1 mois d'utilisation, en cochant "Intégré uniquement" dans le menu de GfxCardStatus, je n'ai plus d'écran noir \o/

Voilà, désolé pour le pavé de texte 😀

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
Remplis sous: Divers Aucun commentaire
11Nov/140

Satellite C660D-120 Wifi / Wlan drivers

Si comme moi vous n'arrivez pas à trouver le driver wifi pour cet ordinateur portable (Satellite C660D-120), il se trouve ici (Realtek) : http://aps2.toshiba-tro.de/wlan/?page=downloads

Et plus particulièrement celui pour Windows 7 : http://support1.toshiba-tro.de/tools/updates/realtek-wlan/realtek_wlan_win7_2000016L_20110523.zip

Et si malheureusement le lien est mort au moment où vous me lisez, je l'ai également hébergé sur mon blog, on ne sait jamais ^^ : realtek_wlan_win7_2000016L_20110523.zip

Je ne sais pas pourquoi Toshiba ne l'a pas mis avec tous les autres drivers listés sur cette page...

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
22Mar/140

Motion : Unable to find a compatible palette format

Heureux possesseur d'un Raspberry Pi, j'ai souhaité utiliser motion dessus.

L'installation et la configuration se sont déroulés avec succès (voir ici), tout fonctionne parfaitement après un reboot de la machine.

Mais malheureusement, si je fais un stop puis un start de motion, j'ai le droit dans les logs (cat /var/log/syslog | grep motion) à cette erreur :

Unable to find a compatible palette format

Je tiens à préciser que je n'ai pas cette erreur au démarrage du Raspberry quand motion se lance tout seul grâce à son deamon on dans la conf.

Après des tests en tout genre, j'ai finalement contourné le problème, j'effectue une commande avant de faire un start de motion, cela doit initialiser quelque chose, mais le fait est que motion fonctionne très bien ensuite, sans avoir besoin de faire un reboot.

Voici la commande en question : fswebcam -r 320x240 -d /dev/video0 -v /dev/null

Si cela peut être utile 😉

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
19Oct/130

Hubic, application de synchronisation pour Linux

Suite à la sortie de leur nouvelle Api, Hubic met à disposition une version beta pour Linux.

Tout est expliqué ici : https://forums.hubic.com/showthread.php?230-hubic-Linux-sortie-de-la-version-b%EAta&p=507

A l'heure où j'écris ces lignes, l'application est en train de synchroniser mon compte Hubic avec un répertoire de mon NAS (monté sur un Raspberry Pi).

🙂

EDIT 12/2013 : Un tutorial très complet sur l'utilisation d'Hubic sur linux est disponible ici : http://doc.ubuntu-fr.org/hubic

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
6Sep/130

Synchroniser un NAS vers Hubic en passant par un Raspberry Pi

Depuis que j'ai un compte Hubic de 100go, mon seul souhait est de pouvoir envoyer des backups (cryptés) depuis mon NAS (Dlink DNS-323).

J'avais réussi à le faire depuis mon Mac en passant par l'ancien protocole webdav, mais cela ne m'avançait pas trop (cf mon ancien article).

Récemment je suis tombé sur un très bon article : Transformer un Raspberry Pi en passerelle vers Hubic d'OVH : Nginx, script Toorop & Cloudfuse.

Grâce à ce dernier (merci à son auteur), j'ai pu enfin mettre en place ce que je souhaitais 🙂

La suite vient donc en complément de l'article cité juste au dessus.

Voici comment j'ai procédé pour effectuer mon rsync :

- Installer les outils nécessaires sur le Raspberry Pi :

sudo apt-get update
sudo apt-get install nginx php5-fpm
cd /var/www
git clone https://github.com/Toorop/HubicSwiftGateway.git
cp -R /var/www/HubicSwiftGateway/src/www /var/www/HubicSwiftGateway
sudo touch /etc/nginx/sites-available/hubicgw
sudo vim /etc/nginx/sites-available/hubicgw

- Paramétrer nginx (j'effectue un changement de port), y coller :

server {
listen 82;
server_name localhost;
root /var/www/HubicSwiftGateway/src/www;
index index.html index.htm index.php;

location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
}

cd /etc/nginx/sites-enabled
sudo ln -s ../sites-available/hubicgw
sudo vim /etc/nginx/sites-available/default

- Décommenter "listen" et remplacer 80 par 81.

sudo service nginx restart

- Installer curl si besoin (erreur lors de l'affichage du php de Toorop) :

sudo apt-get install php5-curl

- Régler le problème du dossier cache :

sudo mkdir /var/www/HubicSwiftGateway/src/cache
sudo chmod 777 /var/www/HubicSwiftGateway/src/cache
sudo chown pi:root -R /var/www/HubicSwiftGateway
sudo chmod 777 -R /var/www/HubicSwiftGateway/src/www

- Tester avec le client Swift (depuis le Raspberry Pi)

sudo apt-get install swift
swift -A http://192.168.1.24:82/ -U email_hubic@provider.com -K motdepasse_hubic stat

- Installer cloudfuse (le télécharger ici) : mettre le dossier cloud-master dans /home/pi.

cd /home/pi/cloudfuse-master/
sudo apt-get install build-essential libcurl4-openssl-dev libxml2-dev libssl-dev libfuse-dev
sudo chmod a+x configure
sudo chown pi *
./configure
make
sudo make install
sudo vim /root/.cloudfuse

- Paramétrer cloudfuse, y coller :

username=email_hubic@provider.com
api_key=motdepasse_hubic
authurl=http://192.168.1.24:82/
cache_timeout=20

- Préparer le dossier qui accueillera le contenu d'Hubic :

sudo mkdir /mnt/hubic
sudo chown pi /mnt/hubic

- Monter le dossier Hubic :

sudo cloudfuse /mnt/hubic/

- Parcourir le dossier Hubic :

sudo su
cd /mnt/hubic/default/
ls

- Monter le NAS automatiquement au boot du Raspberry Pi :

sudo vim /etc/fstab

- Y coller :

//192.168.1.21/Volume_1 /mnt/NAS cifs guest,uid=pi 0 0

- Monter le NAS :

sudo mount -a

- Lancer un rsync du NAS vers Hubic dans un screen :

sudo apt-get install screen
screen -S synchro
sudo su
rsync -auit --progress --delete /mnt/NAS/BACKUP/duplicity /mnt/hubic/default/

Il suffira par la suite de mettre tout cela dans un cron qui se lancera par exemple toutes les nuits :).

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
30Août/130

Jarvis avec un Raspberry, PHP et un téléphone sous Android

Tout le monde connaît Jarvis (ou pas), l'IA de Tony Stark dans Iron Man.

Le but : avoir un ordinateur qui me répond à mes demandes vocales (un siri like).

Matériel : un Raspberry Pi et un téléphone sous Android (un Nexus 4 en l'occurrence).

Concept : une application Android vous écoute (speech to text) et envoie à un serveur php (le Raspberry) le dit texte, ce dernier analyse la demande et effectue un affichage que le téléphone récupère et vous dicte (text to speech).

Projet Jarvis

Etape 1, le Raspberry :

  • Avoir un Raspberry et sa distribution conseillée : http://elinux.org/RPi_Easy_SD_Card_Setup
  • La première fois que vous démarrez dessus, voici les choses que je vous conseille de faire et/ou d'activer :
    • update tool
    • expand filesystem
    • 128mo GPU
    • Enable SSH
  • On installe le serveur web :
    • sudo apt-get install apache2 php5 libapache2-mod-php5
    • sudo groupadd www-data
    • sudo usermod -g www-data www-data
    • sudo chown -R pi /var/www
  • Mettre une IP fixe au Raspberry :
    • sudo apt-get install vim (ou autre éditeur)
    • sudo vim /etc/network/interfaces
    • remplacer :
      iface eth0 inet dhcp
    • par :
      iface eth0 inet static
      address 192.168.xxx.xxx
      netmask 255.255.255.0
      gateway 192.168.1.254

Donc normalement, si vous taper l'IP de votre Raspberry dans un navigateur web, vous tomberez sur un joli "It works!" 🙂

Etape 2, Jarvis en PHP (établissement des commandes vocales) :

Exemple du contenu, je laisse place à votre imagination pour le remplir.

<?php
$args = isset($_POST) && count($_POST) > 0 ? $_POST : (isset($_GET) && count($_GET) > 0 ? $_GET : NULL);
 
if(!is_null($args))
{
  $text = $args['speechToText'];
 
  // allumer un ordinateur
  if(preg_match('#allume#', $text) && preg_match('#ordinateur#', $text))
  {
    //@note Pour éxécuter etherwake sans le root : "sudo visudo" puis rajouter en bas "www-data ALL=(ALL) NOPASSWD: /usr/sbin/etherwake" 
    exec('sudo etherwake -b xx:xx:xx:xx:xx:xx');
    echo 'Ordinateur allumé.';
  }
  elseif(preg_match('#comment#', $text) && preg_match('#tu#', $text) && preg_match('#appel#', $text))
  {
    echo 'Je m\'apelle Jarvis.';
  }
  // aucune correspondance
  else
    echo 'Je n\'ai pas compris votre demande.';
}
else
  echo 'Aucun paramètre envoyé !';
?>

Etape 3, l'application Android :

N'hésitez pas à l'améliorer 😉

Petite précision : il faut renseigner le nom de votre connexion wifi pour que quand vous êtes chez vous, que cela passe par le réseau local et non par internet (donc 2 IPs à fournir dans le code).

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unit.jarvis"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="16" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.unit.jarvis.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
</manifest>

MainActivity.java

package com.unit.jarvis;
 
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.UtteranceProgressListener;
import android.support.v4.app.NotificationCompat;
import android.view.Menu;
import android.widget.Toast;
 
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
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.apache.http.protocol.HTTP;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
 
public class MainActivity extends Activity implements OnInitListener
{
    protected static final int NOTIFICATION_ID = 1;
    protected static final int RESULT_SPEECH = 1;
    protected static final String INTENT_EXTRA_VAR = "from";
    protected static final String POST_VARNAME = "speechToText";
    protected static final String RESPONSE_ENCODING = "UTF-8";
 
    private TextToSpeech tts;
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // création TTS
        tts = new TextToSpeech(this, this);
 
        // on vient de la notification et on doit parler directement ?
        if(comesFromNotification())
           startTalking();
        else
        {
            // on ouvre la classe MainActivity sur le clic de la notification
            Intent intent = new Intent(this, MainActivity.class);
            // on envoie une variable à la classe MainActivity
            intent.putExtra(INTENT_EXTRA_VAR, "notification");
            // on lance l'activité MainActivity
            PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
 
            // notification
            NotificationManager notificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 
            notificationManager.cancel(NOTIFICATION_ID);
 
            NotificationCompat.Builder notificationCompatBuilder =
                    new NotificationCompat.Builder(this)
                            .setSmallIcon(R.drawable.ic_launcher)
                            .setContentTitle(getString(R.string.app_name))
                            .setContentText(getString(R.string.clic_here_to_talk))
                            .addAction(R.drawable.ic_launcher, getString(R.string.talk), pIntent)
                            .setContentIntent(pIntent);
 
            Notification notification = notificationCompatBuilder.build();
            notification.flags |= Notification.FLAG_NO_CLEAR;
            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
 
            notificationManager.notify(NOTIFICATION_ID, notification);
        }
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    protected void onResume()
    {
        // on vient de la notification et on doit parler directement ?
        if(comesFromNotification())
            startTalking();
 
        super.onResume();
    }
 
    @Override
    protected void onStop()
    {
        // TTS
        if(tts != null)
            tts.stop();
 
        super.onStop();
    }
 
    @Override
    protected void onDestroy()
    {
        // TTS
        if(tts != null)
            tts.shutdown();
 
        super.onDestroy();
    }
 
    @Override
    public void onInit(int status)
    {
        if(status == TextToSpeech.SUCCESS)
        {
            // langue TTS
            int setLanguage = tts.setLanguage(Locale.getDefault());
 
            if (setLanguage == TextToSpeech.LANG_MISSING_DATA
                    || setLanguage == TextToSpeech.LANG_NOT_SUPPORTED)
                Toast.makeText(getApplicationContext(), R.string.language_not_supported, Toast.LENGTH_SHORT).show();
        }
        else
            Toast.makeText(getApplicationContext(), R.string.tts_init_failed, Toast.LENGTH_SHORT).show();
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);
 
        switch(requestCode)
        {
            case RESULT_SPEECH:
            {
                if(resultCode == RESULT_OK && null != data)
                {
                    ArrayList<String> text = data
                            .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
 
                    // post
                    new AsyncHttpPost().execute(text.toString());
                }
                break;
            }
        }
    }
 
    public boolean comesFromNotification()
    {
        // on récupère la variable si envoyée (et on la supprime ensuite)
        Intent sender = getIntent();
        String from = sender.getStringExtra(INTENT_EXTRA_VAR);
        sender.removeExtra(INTENT_EXTRA_VAR);
 
        // on vient de la notification ?
        return (from != null);
    }
 
    public String getWifiName(Context context)
    {
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        String ssid = wifiInfo.getSSID();
 
        return ssid;
    }
 
    public void startTalking()
    {
        Intent intent = new Intent(
                RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
 
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, Locale.getDefault());
 
        try {
            startActivityForResult(intent, RESULT_SPEECH);
        } catch (ActivityNotFoundException a) {
            Toast.makeText(getApplicationContext(), R.string.tts_not_supported, Toast.LENGTH_SHORT).show();
        }
    }
 
    public void quitApplication()
    {
        finish();
        moveTaskToBack(true);
    }
 
    // classe asynchrone de requête http post
    private class AsyncHttpPost extends AsyncTask<String, Integer, Double>
    {
        @Override
        protected Double doInBackground(String... params)
        {
            // TODO Auto-generated method stub
            postData(params[0]);
            return null;
        }
 
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
        public void postData(String toPost)
        {
            String personalWifi = "<nom-de-votre-connexion-wifi-personnel>";
            String wifiName = getWifiName(getApplicationContext());
            String ip = "<ip-internet-de-votre-box>";
 
            // réseau personnel ?
            if(wifiName.replace("\"", "").toString().equals(personalWifi.toString()))
                ip = "<ip-local-de-votre-raspberry>";
 
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost("http://" + ip + "/jarvis.php");
 
            try {
                // ajout des données
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
                nameValuePairs.add(new BasicNameValuePair(POST_VARNAME, toPost));
                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8));
 
                // exécution du post
                HttpResponse response = httpclient.execute(httppost);
 
                BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), RESPONSE_ENCODING));
 
                for (String line = null; (line = reader.readLine()) != null;)
                {
                    if(tts.isSpeaking())
                    {
                        tts.stop();
                    }
                    else
                    {
                        // écouteur TTS
                        if(Build.VERSION.SDK_INT < 15)
                        {
                            tts.setOnUtteranceCompletedListener(new TextToSpeech.OnUtteranceCompletedListener() {
                                @Override
                                public void onUtteranceCompleted(String s) {
                                    quitApplication();
                                }
                            });
                        }
                        else
                        {
                            tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
                                @Override
                                public void onStart(String s) {
 
                                }
 
                                @Override
                                public void onDone(String s) {
                                    quitApplication();
                                }
 
                                @Override
                                public void onError(String s) {
 
                                }
                            });
                        }
 
                        HashMap<String, String> hashmap = new HashMap<String, String>();
                        hashmap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "jarvisTalking");
                        tts.speak(line, TextToSpeech.QUEUE_FLUSH, hashmap);
                    }
                }
            } catch (ClientProtocolException e) {
                Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_LONG).show();
            } catch (IOException e) {
                Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_LONG).show();
            }
        }
    }
}

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Jarvis</string>
    <string name="action_settings">Options</string>
    <string name="welcome">Bienvenue!</string>
    <string name="talk">Parler</string>
    <string name="tts_not_supported">Votre appareil ne suporte pas le Speech to Text</string>
    <string name="language_not_supported">Ce Language n\'est pas supporté</string>
    <string name="tts_init_failed">Initialisation du TTS a échoué</string>
    <string name="clic_here_to_talk">Cliquer ici pour parler.</string>
</resources>

Dernière précision, pour parler, il faut passer par la barre de notification du téléphone et appuyer sur "Jarvis" ou "Parler", une fois votre demande envoyée et la réponse du serveur lue, l'application se ferme automatiquement.

Voilà, c'est tout simple mais cela peut servir (fermer des volets roulants, allumer un ordinateur, ...).

Have fun 😉

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
16Août/130

PHP : exécuter etherwake pour allumer un ordinateur depuis un fichier php

Problématique du jour, bonjour ^^ : comment lancer la commande "etherwake" (wake on lan) depuis un exec() alors que cette dernière nécessite les droits root ?

Code PHP qui ne fonctionne pas : exec('sudo etherwake -b xx:xx:xx:xx:xx:xx');

Après quelques recherches, je me tourne vers les binary wrapper qui permettent d'exécuter du shell en restreignant les droits que sur le fichier à lancer, mais j'aimerais trouver une solution plus rapide...

Du coup, je me suis tourné vers les droits du sudo.

Solution : permettre à www-data (l'utilisateur Apache) d'avoir les droits root que sur la commande "etherwake".

Pratique :
- en ssh : sudo visudo (cette commande édite le fichier /etc/sudoers en vérifiant vos changements avant d'enregistrer)
- rajouter tout en bas du fichier : www-data ALL=(ALL) NOPASSWD: /usr/sbin/etherwake

Attention : cette solution ne se fait pas sur les commandes qui pourraient provoquer des problèmes de sécurité sur votre machine.

🙂

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
12Juin/1229

Synchroniser ses fichiers sur un compte Hubic d’OVH sur Mac OS X

EDIT Janvier 2013 : Le protocole Webdav utilisé dans la suite de cette article n'est plus accessible, il a laissé place à l'OpenStack.
EDIT Septembre 2013 : Un nouvel article explique comment synchroniser mon NAS avec Hubic en passant par un Raspberry.

Récemment acquéreur d'un espace de 100Go sur Hubic, j'ai vite eu le besoin et surtout l'envie de synchroniser mes données automatiquement avec ce dernier (sans devoir me rappeler ou chercher quels fichiers j'ai mis à jour depuis ma dernière sauvegarde).

Malheureusement, cet espace de stockage n'est accessible que par un logiciel fourni par OVH (windows, mac, linux, android, iphone).

Cependant l'accès à ce dernier passe par le protocole WebDAV.

Sachant cela, une petite recherche sur Google m'a permis de trouver cet article très intéressant : http://www.protocol-hacking.org/post/2012/01/29/Hubic%2C-maintenant-vraiment-ubiquitous

Pour les bidouilleurs, ce lien vous suffira pour faire votre propre installation, mais pour les autres, je vous propose une solution de synchronisation quasi-automatique.

Cette solution se composera de 4 fichiers que nous regrouperons dans un dossier synchro dans vos documents.

Tout d'abord nous aurons besoin du script Perl qui se connectera sur votre espace par protocole webdav : http://www.protocol-hacking.org/public/hubic.pl

Ensuite un petit script sh tout simple pour utiliser le script Perl, que nous appellerons mount-hubic.sh, et qui contiendra :

perl hubic.pl -l votre.email.de.compte.hubic@domain.tld

En lançant ce dernier en console, votre mot de passe du compte Hubic vous est demandé. Une fois rentré et appuyé sur "entrer", des informations dont nous avons besoin vont s'afficher :

URL: https://cloudnas1.ovh.com/b64d61c75395e009etype3a3a0b23211/
Login: cloudnas
Password: 7d3sdfh4h9dfh875fgh3485

Grâce à cela, montez le lecteur réseau en utilisant la commande Pomme + K de Finder ("Se connecter au serveur...").

Votre espace de stockage apparaît désormais dans votre Finder sous le nom de cloudnas1.ovh.com.

Cette étape sera à effectuer avant toute synchronisation de vos documents, c'est pour cela que je parle de solution quasi-automatique 😀

Maintenant que le lecteur est monté, passons à la synchronisation à l'aide de la commande rsync. Créons un fichier synchro-hubic.sh contenant :

#!/bin/bash
options="-aui --progress --delete --exclude-from=exclude-hubic.txt"
rsync $options /Chemin/Dossier/A/Sauvegarder/ /Volumes/b64d61c75395e009etype3a3a0b23211/

Comme vous pouvez le constater, on retrouve dans la commande rsync le nom du dossier que l'on a récupéré plus haut : b64d61c75395e009etype3a3a0b23211

Vous constatez également que je synchronise tout mon répertoire sauf les fichiers listés dans un fichier nommé exclude-hubic.txt, qui contient notamment :

.DS_Store
/.*
/Applications
/Desktop
/Downloads
/Documents/Virtual*Machines.localized

Pour résumer, nous avons un dossier synchro dans nos documents, auquel nous accédons depuis la console, pour y lancer tout d'abord mount-hubic.sh (qui nous permettra de monter le lecteur réseau) et ensuite pour y lancer le script de synchronisation : synchro-hubic.sh. Dans ce dossier se trouvent également hubic.pl et exclude-hubic.txt.

Si vous avez des questions, n'hésitez pas 😉

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook
8Mai/120

Créer un initram avec un mode rescue

Je ne vais pas copier ni citer le très bon article de Geekfault mais simplement y coller le lien de l'article.

Très bien structuré, il permet de comprendre pas à pas ce qu'il faut faire et pourquoi.

http://geekfault.org/2012/05/08/comment-faire-un-initram-minimal-avec-mode-rescue/

Bonne lecture 😉

Tweet about this on TwitterShare on Google+Email this to someoneShare on Facebook