Как отправить данные из Raspberry pi в Google Cloud IoT Core с помощью Android Studio?

Я делаю проект Android Things.

Я хочу опубликовать строковое сообщение в Google Cloud IoT Core, но отображаются ошибки.

Я использую Raspberry Pi 3 с ОС Android Things и программирую его с помощью Android Studio.

Скрин ошибок:

введите здесь описание изображения

Это весь код:

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="cacaosd.com.sample1">

    <!-- PAHO Permissions -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- PAHO Permissions -->

    <application>
        <uses-library android:name="com.google.android.things"/>

        <!-- Mqtt Service -->
        <service android:name="org.eclipse.paho.android.service.MqttService" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.IOT_LAUNCHER"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Класс IotCoreCommunicator

  package cacaosd.com.sample1;

import android.content.Context;
import android.util.Log;

import java.util.concurrent.TimeUnit;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

public class IotCoreCommunicator {

    private static final String SERVER_URI = "ssl://mqtt.googleapis.com:8883";

    public static class Builder {

        private Context context;
        private String projectId;
        private String cloudRegion;
        private String registryId;
        private String deviceId;
        private int privateKeyRawFileId;

        public Builder withContext(Context context) {
            this.context = context;
            return this;
        }

        public Builder withProjectId(String projectId) {
            this.projectId = projectId;
            return this;
        }

        public Builder withCloudRegion(String cloudRegion) {
            this.cloudRegion = cloudRegion;
            return this;
        }

        public Builder withRegistryId(String registryId) {
            this.registryId = registryId;
            return this;
        }

        public Builder withDeviceId(String deviceId) {
            this.deviceId = deviceId;
            return this;
        }

        public Builder withPrivateKeyRawFileId(int privateKeyRawFileId) {
            this.privateKeyRawFileId = privateKeyRawFileId;
            return this;
        }

        public IotCoreCommunicator build() {
            if (context == null) {
                throw new IllegalStateException("context must not be null");
            }

            if (projectId == null) {
                throw new IllegalStateException("projectId must not be null");
            }
            if (cloudRegion == null) {
                throw new IllegalStateException("cloudRegion must not be null");
            }
            if (registryId == null) {
                throw new IllegalStateException("registryId must not be null");
            }
            if (deviceId == null) {
                throw new IllegalStateException("deviceId must not be null");
            }
            String clientId = "projects/" + projectId + "/locations/" + cloudRegion + "/registries/" + registryId + "/devices/" + deviceId;

            if (privateKeyRawFileId == 0) {
                throw new IllegalStateException("privateKeyRawFileId must not be 0");
            }
            MqttAndroidClient client = new MqttAndroidClient(context, SERVER_URI, clientId);
            IotCorePasswordGenerator passwordGenerator = new IotCorePasswordGenerator(projectId, context.getResources(), privateKeyRawFileId);
            return new IotCoreCommunicator(client, deviceId, passwordGenerator);
        }

    }

    private final MqttAndroidClient client;
    private final String deviceId;
    private final IotCorePasswordGenerator passwordGenerator;

    IotCoreCommunicator(MqttAndroidClient client, String deviceId, IotCorePasswordGenerator passwordGenerator) {
        this.client = client;
        this.deviceId = deviceId;
        this.passwordGenerator = passwordGenerator;
    }

    public void connect() {
        monitorConnection();
        clientConnect();
        subscribeToConfigChanges();
    }

    private void monitorConnection() {
        client.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable cause) {
                Log.e("TUT", "connection lost", cause);
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                Log.d("TUT", "message arrived " + topic + " MSG " + message);
                // You need to do something with messages when they arrive
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                Log.d("TUT", "delivery complete " + token);
            }
        });
    }

    private void clientConnect() {
        try {
            MqttConnectOptions connectOptions = new MqttConnectOptions();
            // Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we explicitly set this.
            // If you don't, the server will immediately close its connection to your device.
            connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION
    package cacaosd.com.sample1;

import android.content.res.Resources;
import android.util.Base64;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

class IotCorePasswordGenerator {

    private final String projectId;
    private final Resources resources;
    private final int privateKeyRawFileId;

    IotCorePasswordGenerator(String projectId, Resources resources, int privateKeyRawFileId) {
        this.projectId = projectId;
        this.resources = resources;
        this.privateKeyRawFileId = privateKeyRawFileId;
    }

    char[] createJwtRsaPassword() {
        try {
            byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
            return createJwtRsaPassword(projectId, privateKeyBytes).toCharArray();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Algorithm not supported. (developer error)", e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalStateException("Invalid Key spec. (developer error)", e);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot read private key file.", e);
        }
    }

    private static byte[] decodePrivateKey(Resources resources, int privateKeyRawFileId) throws IOException {
        try(InputStream inStream = resources.openRawResource(privateKeyRawFileId)) {
            return Base64.decode(inputToString(inStream), Base64.DEFAULT);
        }
    }

    private static String inputToString(InputStream is) {
        java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";
    }

    private static String createJwtRsaPassword(String projectId, byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return createPassword(projectId, privateKeyBytes, "RSA", SignatureAlgorithm.RS256);
    }

    private static String createPassword(String projectId, byte[] privateKeyBytes, String algorithmName, SignatureAlgorithm signatureAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Instant now = Instant.now();
        // Create a JWT to authenticate this device. The device will be disconnected after the token
        // expires, and will have to reconnect with a new token. The audience field should always be set
        // to the GCP project id.
        JwtBuilder jwtBuilder =
                Jwts.builder()
                        .setIssuedAt(Date.from(now))
                        .setExpiration(Date.from(now.plus(Duration.ofMinutes(20))))
                        .setAudience(projectId);

        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory kf = KeyFactory.getInstance(algorithmName);

        return jwtBuilder.signWith(signatureAlgorithm, kf.generatePrivate(spec)).compact();
    }

}
1_1); // With Google Cloud IoT Core, the username field is ignored, however it must be set for the // Paho client library to send the password field. The password field is used to transmit a JWT to authorize the device. connectOptions.setUserName("unused-but-necessary"); connectOptions.setPassword(passwordGenerator.createJwtRsaPassword()); IMqttToken iMqttToken = client.connect(connectOptions); iMqttToken.setActionCallback(new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { Log.d("TUT", "success, connected"); } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { Log.e("TUT", "failure, not connected", exception); } }); iMqttToken.waitForCompletion(TimeUnit.SECONDS.toMillis(30)); Log.d("TUT", "IoT Core connection established."); } catch (MqttException e) { throw new IllegalStateException(e); } } /** * Configuration is managed and sent from the IoT Core Platform */ private void subscribeToConfigChanges() { try { client.subscribe("/devices/" + deviceId + "/config", 1); } catch (MqttException e) { throw new IllegalStateException(e); } } public void publishMessage(String subtopic, String message) { String topic = "/devices/" + deviceId + "/" + subtopic; String payload = "{msg:\"" + message + "\"}"; MqttMessage mqttMessage = new MqttMessage(payload.getBytes()); mqttMessage.setQos(1); try { client.publish(topic, mqttMessage); Log.d("TUT", "IoT Core message published. To topic: " + topic); } catch (MqttException e) { throw new IllegalStateException(e); } } public void disconnect() { try { Log.d("TUT", "IoT Core connection disconnected."); client.disconnect(); } catch (MqttException e) { throw new IllegalStateException(e); } } }

Класс IotCorePasswordGenerator

    package cacaosd.com.sample1;

import android.content.res.Resources;
import android.util.Base64;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

class IotCorePasswordGenerator {

    private final String projectId;
    private final Resources resources;
    private final int privateKeyRawFileId;

    IotCorePasswordGenerator(String projectId, Resources resources, int privateKeyRawFileId) {
        this.projectId = projectId;
        this.resources = resources;
        this.privateKeyRawFileId = privateKeyRawFileId;
    }

    char[] createJwtRsaPassword() {
        try {
            byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
            return createJwtRsaPassword(projectId, privateKeyBytes).toCharArray();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Algorithm not supported. (developer error)", e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalStateException("Invalid Key spec. (developer error)", e);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot read private key file.", e);
        }
    }

    private static byte[] decodePrivateKey(Resources resources, int privateKeyRawFileId) throws IOException {
        try(InputStream inStream = resources.openRawResource(privateKeyRawFileId)) {
            return Base64.decode(inputToString(inStream), Base64.DEFAULT);
        }
    }

    private static String inputToString(InputStream is) {
        java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";
    }

    private static String createJwtRsaPassword(String projectId, byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return createPassword(projectId, privateKeyBytes, "RSA", SignatureAlgorithm.RS256);
    }

    private static String createPassword(String projectId, byte[] privateKeyBytes, String algorithmName, SignatureAlgorithm signatureAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Instant now = Instant.now();
        // Create a JWT to authenticate this device. The device will be disconnected after the token
        // expires, and will have to reconnect with a new token. The audience field should always be set
        // to the GCP project id.
        JwtBuilder jwtBuilder =
                Jwts.builder()
                        .setIssuedAt(Date.from(now))
                        .setExpiration(Date.from(now.plus(Duration.ofMinutes(20))))
                        .setAudience(projectId);

        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory kf = KeyFactory.getInstance(algorithmName);

        return jwtBuilder.signWith(signatureAlgorithm, kf.generatePrivate(spec)).compact();
    }

}

Класс основной активности:

    package cacaosd.com.sample1;

import android.app.Activity;
import android.hardware.SensorEvent;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Handler;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;



import cacaosd.com.sample1.R;
import cacaosd.com.sample1.IotCoreCommunicator;


import com.google.android.things.pio.Gpio;


import java.io.IOException;
import java.util.concurrent.TimeUnit;


public class MainActivity extends Activity {



    private IotCoreCommunicator communicator;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Setup the communication with your Google IoT Core details
        communicator = new IotCoreCommunicator.Builder()
                .withContext(this)
                .withCloudRegion("us-central1") // ex: europe-west1
                .withProjectId("my-first-project-198704")   // ex: supercoolproject23236
                .withRegistryId("vibration") // ex: my-devices
                .withDeviceId("my-device") // ex: my-test-raspberry-pi
                .withPrivateKeyRawFileId(R.raw.rsa_private)
                .build();

        HandlerThread thread = new HandlerThread("MyBackgroundThread");
        thread.start();
        handler = new Handler(thread.getLooper());
        handler.post(connectOffTheMainThread); // Use whatever threading mechanism you want
    }

    private final Runnable connectOffTheMainThread = new Runnable() {
        @Override
        public void run() {
            communicator.connect();

            handler.post(sendMqttMessage);
        }
    };



    private final Runnable sendMqttMessage = new Runnable() {
        private int i;

        /**
         * We post 100 messages as an example, 1 a second
         */
        @Override
        public void run() {
            if (i == 100) {
                return;
            }



            // events is the default topic for MQTT communication
            String subtopic = "events";
            // Your message you want to send
            String message = "Hello World " + i++;

            communicator.publishMessage(subtopic, message);



            handler.postDelayed(this, TimeUnit.SECONDS.toMillis(1));
        }
    };

    @Override
    protected void onDestroy() {
        communicator.disconnect();
        super.onDestroy();
    }
}

Обновление:

Я преобразовал закрытый ключ из формата "pem" в формат "pkcs8", следуя этому documentation and this demo, то ошибка "Недопустимая спецификация ключа" исчезла, но все еще есть "FATAL EXCEPTION" и "java.lang.IllegalArgumentException: bad base-64", как показано на изображении ниже:

[![введите описание изображения здесь][4]][4]

В нем говорится, что это связанные коды, которые вызвали ошибку (которая показана синим цветом на предыдущем изображении:

IotCorePasswordGenerator.java:47

return Base64.decode(inputToString(inStream), Base64.DEFAULT);

IotCorePasswordGenerator.java:34

 byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);

IotCoreCommunicator.java:135

connectOptions.setPassword(passwordGenerator.createJwtRsaPassword());

IotCoreCommunicator.java:101

clientConnect();

Основная активность.java:58

communicator.connect();

Обновление 2

Я удалил оператор "-----BEGIN PRIVATE KEY-----" и оператор "------END PRIVATE KEY-----" и ошибка "плохая база 64" исчезла, теперь есть еще одна ошибка, которая является «сломанной трубой», как показано на изображении ниже, когда я снова открываю Android Studio и перестраиваю проект, эта ошибка «сломанная труба» удаляется, и когда я снова запускаю проект, он возвращается снова.

Ошибка (первое изображение)

Закрытый ключ с операторами start и end (второе изображение)

Закрытый ключ без операторов start и end (третье изображение)

введите здесь описание изображениявведите здесь описание изображениявведите здесь описание изображения


person Alkhalil    schedule 02.05.2018    source источник
comment
Вы подтвердили, что сообщения о событиях публикуются?   -  person Gabe Weiss    schedule 02.05.2018
comment
Спасибо за ваш ответ, мистер ГейбВайс, я обнаружил много показанных ошибок, которых я не видел, прежде чем опубликовать вопрос, извините за это, теперь я редактирую свой вопрос, чтобы вы могли видеть ошибки.   -  person Alkhalil    schedule 05.05.2018


Ответы (2)


Судя по ошибке, вы зарегистрировали устройство с неправильным типом ключа SSL. Убедитесь, что вы создали ключ SSL, соответствующий формату, указанному в IoT Core. т.е. если вы создали ключ RSA с оболочкой сертификата x509, убедитесь, что ваше устройство зарегистрировано с этим типом, а не только с ключом RSA.

Также убедитесь, что закрытый ключ действительно находится на устройстве и не поврежден.

Изменить: возможно, проблема заключалась в том, что root.pem не был на устройстве для обработки рукопожатия TLS с IoT Core. Посмотрим... чтобы получить его, запустите: wget https://pki.google.com/roots.pem и поместите roots.pem в тот же каталог, что и закрытый ключ на устройстве.

person Gabe Weiss    schedule 07.05.2018
comment
Есть два ключа, один из которых является открытым ключом, и я ввел его в IoT Core для аутентификации устройства, и его формат: RS256_X509. Другой ключ — это закрытый ключ, который я предоставил в коде. Открытый ключ начинается с: -----BEGIN CERTIFICATE----- , а закрытый ключ начинается с: -----BEGIN PRIVATE KEY----- Наконец, как я могу узнать, что закрытый ключ на самом деле на устройстве и что оно не повреждено. - person Alkhalil; 07.05.2018
comment
Правильно, я просто хотел убедиться, что вы подтвердили, что при регистрации устройства в IoT Core вы установили переключатель для RSA256_x509. Я знаю, что не раз совершал ошибку, когда регистрировал устройство по умолчанию, то есть RSA256, без оболочки сертификата x509. Также убедитесь, что на вашем устройстве есть файл root.pem от Google. - person Gabe Weiss; 11.05.2018
comment
Что вы имеете в виду под переключателем?, где я могу его найти, потому что я его не видел. Кроме того, говоря roots.pem, вы имеете в виду закрытый ключ (rsa_private.pem) или что? наконец, я преобразовал формат закрытого ключа из pem в pkcs8, посмотрите, пожалуйста, обновление по моему вопросу. Спасибо - person Alkhalil; 12.05.2018
comment
Когда вы добавляете новое устройство в консоль (если вы используете консоль), есть переключатель, чтобы указать формат открытого ключа. Однако я предполагаю, что проблема действительно связана с файлом root.pem, поскольку он звучит для вас незнакомо! :D запустите это: wget https://pki.google.com/roots.pem И убедитесь, что root.pem находится в том же каталоге, что и ваш закрытый ключ на устройстве. Посмотрите, работает ли это. - person Gabe Weiss; 13.05.2018

Я настоятельно рекомендую вам ознакомиться с коннектором Android Things для ядра Cloud IoT. Этот проект значительно упрощает доступ к ядру Cloud IoT из Android Things и использует различные передовые методы, такие как обновление токена.

person class    schedule 14.08.2018