Paul Trebilcox-Ruiz
Published © Apache-2.0

Augmented Reality Board Game

Teach children about the solar system with augmented reality and a board game.

AdvancedFull instructions provided24 hours2,740

Things used in this project

Hardware components

RC522 NFC Reader
×1
Xenon
Particle Xenon
×1
Argon
Particle Argon
×1
Li-Ion Battery 100mAh
Li-Ion Battery 100mAh
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Breadboard (generic)
Breadboard (generic)
×1

Software apps and online services

Firebase
Google Firebase
Google Cloud
Android Studio
Android Studio
Google AR Core

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Schematics

3D Models

Zip file containing the 3D models for the sun, planets, and Earth's moon.

Assets

3D model assets (sfb files)

Code

CelestialBody (Android - Kotlin)

Java
package ptrprograms.com.arboardgame

import android.content.Context
import android.view.MotionEvent
import android.widget.TextView
import com.google.ar.sceneform.FrameTime
import com.google.ar.sceneform.HitTestResult
import com.google.ar.sceneform.Node
import com.google.ar.sceneform.math.Quaternion
import com.google.ar.sceneform.math.Vector3
import com.google.ar.sceneform.rendering.ModelRenderable
import com.google.ar.sceneform.rendering.ViewRenderable

class CelestialBody (
    val context: Context,
    val celestialName: String,
    val planetScale: Float = 1.0f,
    val tilt : Float = 0.0f,
    val clockwise: Boolean = false,
    val rotationSpeedMultipler: Float = 1.0f,
    val rotationPerSecond: Float = 90.0f,
    val renderable: ModelRenderable,
    val infoCallback: InfoCallback?
) : Node(), Node.OnTapListener {

    private var infoCard: Node? = null
    private val INFO_CARD_Y_POS_COEFFICIENT = 0.55f
    private var planetNode : RotatingNode? = null

    init {
        setOnTapListener(this)
    }

    override fun onTap(p0: HitTestResult?, p1: MotionEvent?) {
        if( infoCallback != null ) {
            infoCallback.showInfo()
        }
    }

    override fun onActivate() {
        if( infoCard == null ) {
            infoCard = Node()
            infoCard!!.setParent(this)
            infoCard!!.isEnabled = false
            infoCard!!.localPosition = Vector3(0.0f, planetScale * INFO_CARD_Y_POS_COEFFICIENT, 0.0f)

            ViewRenderable.builder()
                .setView(context, R.layout.celestial_card_view)
                .build()
                .thenAccept({ renderable ->
                    infoCard!!.renderable = renderable
                    val textView = renderable.view as TextView
                    textView.text = celestialName
                    infoCard!!.isEnabled = !infoCard!!.isEnabled
                })

        }

        if( planetNode == null ) {
            planetNode = RotatingNode(
                clockwise = clockwise,
                axisTiltDeg = tilt,
                rotationSpeedMultipler = rotationSpeedMultipler,
                degreesPerSecond = rotationPerSecond)

            planetNode!!.setParent(this)
            planetNode!!.renderable = renderable
            planetNode!!.localScale = Vector3(planetScale, planetScale, planetScale)

        }


    }

    override fun onUpdate(p0: FrameTime?) {
        super.onUpdate(p0)
        if( scene == null || infoCard == null ) {
            return
        }

        val cameraPosition = scene!!.camera.worldPosition
        val cardPosition = infoCard!!.worldPosition
        val direction = Vector3.subtract(cameraPosition, cardPosition)
        val lookRotation = Quaternion.lookRotation(direction, Vector3.up())
        infoCard!!.worldRotation = lookRotation

    }
}

Rotating Node (Android - Kotlin)

Java
package ptrprograms.com.arboardgame

import android.animation.ObjectAnimator
import android.view.animation.LinearInterpolator
import com.google.ar.sceneform.FrameTime
import com.google.ar.sceneform.Node
import com.google.ar.sceneform.math.Quaternion
import com.google.ar.sceneform.math.QuaternionEvaluator
import com.google.ar.sceneform.math.Vector3

class RotatingNode(val clockwise: Boolean = false,
                   val axisTiltDeg : Float = 0.0f,
                   val rotationSpeedMultipler : Float = 1.0f,
                   val degreesPerSecond: Float = 90.0f) : Node() {

    private var animator : ObjectAnimator? = null

    override fun onUpdate(frameTime: FrameTime?) {
        super.onUpdate(frameTime)

        if (animator == null) {
            return
        }

        if( rotationSpeedMultipler == 0.0f ) {
            animator!!.pause()
        } else {

            animator!!.resume()

            val animatedFraction = animator!!.getAnimatedFraction()
            animator!!.setDuration(getAnimationDuration())
            animator!!.setCurrentFraction(animatedFraction)
        }
    }

    override fun onActivate() {
        if (animator != null) {
            return
        }

        animator = createAnimator()
        animator!!.setTarget(this)
        animator!!.setDuration(getAnimationDuration())
        animator!!.start()
    }

    override fun onDeactivate() {
        if (animator == null) {
            return
        }

        animator!!.cancel()
        animator = null
    }

    private fun getAnimationDuration(): Long {
        return (1000 * 360 / (degreesPerSecond * rotationSpeedMultipler)).toLong()
    }


    private fun createAnimator() : ObjectAnimator {
        val orientations = arrayOfNulls<Quaternion>(4)
        val baseOrientation = Quaternion.axisAngle(Vector3(1.0f, 0f, 0.0f), axisTiltDeg)

        for (i in orientations.indices) {
            var angle = (i * 360 / (orientations.size - 1)).toFloat()

            if (clockwise) {
                angle = 360 - angle
            }

            val orientation = Quaternion.axisAngle(Vector3(0.0f, 1.0f, 0.0f), angle)
            orientations[i] = Quaternion.multiply(baseOrientation, orientation)
        }

        val animator = ObjectAnimator()

        animator.setObjectValues(*orientations as Array<Any>)

        animator.propertyName = "localRotation"

        animator.setEvaluator(QuaternionEvaluator())

        animator.repeatCount = ObjectAnimator.INFINITE
        animator.repeatMode = ObjectAnimator.RESTART
        animator.interpolator = LinearInterpolator()
        animator.setAutoCancel(true)

        return animator
    }


}

Manifest

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

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />

    <uses-feature android:name="android.hardware.camera.ar" android:required="true"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:largeHeap="true">

        <meta-data android:name="com.google.ar.core" android:value="required" />

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

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

</manifest>

Module level build.gradle file

Groovy
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "ptrprograms.com.arboardgame"
        minSdkVersion 26
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation "com.google.ar.sceneform:core:1.7.0"

    implementation 'com.google.firebase:firebase-core:16.0.7'
    implementation 'com.google.firebase:firebase-database:16.1.0'

    implementation 'com.android.support:design:28.0.0'
}

apply plugin: 'com.google.ar.sceneform.plugin'
apply plugin: 'com.google.gms.google-services'

sceneform.asset('sampledata/models/Sol/Sol.gltf',
        'default',
        'sampledata/models/Sol/Sol.sfa',
        'src/main/assets/Sol')

sceneform.asset('sampledata/models/Mercury/Mercury.gltf',
        'default',
        'sampledata/models/Mercury/Mercury.sfa',
        'src/main/assets/Mercury')

sceneform.asset('sampledata/models/Venus/Venus.gltf',
        'default',
        'sampledata/models/Venus/Venus.sfa',
        'src/main/assets/Venus')

sceneform.asset('sampledata/models/Earth/Earth.gltf',
        'default',
        'sampledata/models/Earth/Earth.sfa',
        'src/main/assets/Earth')

sceneform.asset('sampledata/models/Luna/Luna.gltf',
        'default',
        'sampledata/models/Luna/Luna.sfa',
        'src/main/assets/Luna')

sceneform.asset('sampledata/models/Mars/Mars.gltf',
        'default',
        'sampledata/models/Mars/Mars.sfa',
        'src/main/assets/Mars')

sceneform.asset('sampledata/models/Jupiter/Jupiter.gltf',
        'default',
        'sampledata/models/Jupiter/Jupiter.sfa',
        'src/main/assets/Jupiter')

sceneform.asset('sampledata/models/Saturn/Saturn.gltf',
        'default',
        'sampledata/models/Saturn/Saturn.sfa',
        'src/main/assets/Saturn')

sceneform.asset('sampledata/models/Neptune/Neptune.gltf',
        'default',
        'sampledata/models/Neptune/Neptune.sfa',
        'src/main/assets/Neptune')

sceneform.asset('sampledata/models/Uranus/Uranus.gltf',
        'default',
        'sampledata/models/Uranus/Uranus.sfa',
        'src/main/assets/Uranus')

Project level build.gradle file

Groovy
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.3.21'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.ar.sceneform:plugin:1.7.0'
        classpath 'com.google.gms:google-services:4.0.1'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Game Piece Particle Code

C/C++
#include "RFID.h"

#define SS_PIN      A2
#define RST_PIN     D2
#define MOSI_PIN    D3
#define MISO_PIN    D4
#define SCK_PIN     D5

RFID RC522(SS_PIN, RST_PIN, MOSI_PIN, MISO_PIN, SCK_PIN);


void setup()
{ 
  Serial.begin(9600);
  
  RC522.init();
}

bool flag = false;

void loop()
{
  uint8_t i;

  if (RC522.isCard())
  {
    flag = false;
    String address = "";
    RC522.readCardSerial();

    Serial.println("Card detected:");

    for(i = 0; i <= 4; i++)
    {
      Serial.print(RC522.serNum[i],HEX);
      address += String(RC522.serNum[i],HEX);
      Serial.print(" ");
    }
    
    Serial.println();
    Serial.println(address);
    Mesh.publish("nfc-tag", address);
  }
  else {
      if (!RC522.isCard()) {
          Serial.println("Card NOT detected");
          if( !flag ) {
              flag = true;
              Mesh.publish("nfc-tag", "none");
          }
      }
  }
      
  delay(1000);
}

RFID.cpp

C/C++
/*
 * RFID.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT.
 * Based on code Dr.Leong   ( WWW.B2CQSHOP.COM )
 * Created by Miguel Balboa, Jan, 2012. 
 * Modified by Paul Kourany to run on Spark Core with added support for Software SPI, Mar, 2014.
 * Released into the public domain.
 */

/******************************************************************************
 * Includes
 ******************************************************************************/
#include "RFID.h"

/******************************************************************************
 * User API
 ******************************************************************************/

/**
 * Construct RFID
 * uint8_t chipSelectPin RFID /ENABLE pin
 */
RFID::RFID(uint8_t chipSelectPin, uint8_t NRSTPD)
{
	_chipSelectPin = chipSelectPin;

  pinMode(_chipSelectPin,OUTPUT);			// Set digital as OUTPUT to connect it to the RFID /ENABLE pin 
  digitalWrite(_chipSelectPin, LOW); 


  pinMode(NRSTPD,OUTPUT);					// Set digital pin, Not Reset and Power-down
  digitalWrite(NRSTPD, HIGH);
  _NRSTPD = NRSTPD;
}


RFID::RFID(uint8_t chipSelectPin, uint8_t NRSTPD, uint8_t mosiPin, uint8_t misoPin, uint8_t clockPin) {
  _mosiPin = mosiPin;
  _misoPin = misoPin;
  _clockPin = clockPin;
  _chipSelectPin = chipSelectPin;
  _NRSTPD = NRSTPD;  

  pinMode(NRSTPD,OUTPUT);					// Set digital pin, Not Reset and Power-down
  digitalWrite(NRSTPD, HIGH);
  
  pinMode(_clockPin, OUTPUT);
  pinMode(_mosiPin, OUTPUT);
  pinMode(_misoPin, INPUT);
  
  pinMode(_chipSelectPin, OUTPUT);
  digitalWrite(_chipSelectPin, LOW); 
}

/******************************************************************************
 * User API
 ******************************************************************************/

 bool RFID::isCard() 
 {
	uint8_t status;
	uint8_t str[MAX_LEN];

	status = MFRC522Request(PICC_REQIDL, str);	
    if (status == MI_OK) {
		return true;
	} else { 
		return false; 
	}
 }

 bool RFID::readCardSerial(){

	uint8_t status;
	uint8_t str[MAX_LEN];

	// Anti-colisión, devuelva el número de serie de tarjeta de 4 bytes
	status = anticoll(str);
	memcpy(serNum, str, 5);

	if (status == MI_OK) {
		return true;
	} else {
		return false;
	}

 }

/******************************************************************************
 * Dr.Leong   ( WWW.B2CQSHOP.COM )
 ******************************************************************************/

void RFID::init()
{
    digitalWrite(_NRSTPD,HIGH);

	reset();

	//Timer: TPrescaler*TreloadVal/6.78MHz = 24ms
    writeMFRC522(TModeReg, 0x8D);		//Tauto=1; f(Timer) = 6.78MHz/TPreScaler
    writeMFRC522(TPrescalerReg, 0x3E);	//TModeReg[3..0] + TPrescalerReg
    writeMFRC522(TReloadRegL, 30);           
    writeMFRC522(TReloadRegH, 0);

	writeMFRC522(TxAutoReg, 0x40);		//100%ASK
	writeMFRC522(ModeReg, 0x3D);		// CRC valor inicial de 0x6363

	//ClearBitMask(Status2Reg, 0x08);	//MFCrypto1On=0
	//writeMFRC522(RxSelReg, 0x86);		//RxWait = RxSelReg[5..0]
	//writeMFRC522(RFCfgReg, 0x7F);   	//RxGain = 48dB

	antennaOn();		//Abre  la antena


}
void RFID::reset()
{
	writeMFRC522(CommandReg, PCD_RESETPHASE);
}


void RFID::writeMFRC522(uint8_t addr, uint8_t val)
{
    digitalWrite(_chipSelectPin, LOW);

#if defined(_USE_SOFT_SPI_)
    softSPITranser((addr<<1)&0x7E);	
    softSPITranser(val);
#else
    //0XXXXXX0 formato de dirección
    SPI.transfer((addr<<1)&0x7E);	
    SPI.transfer(val);
#endif	

	digitalWrite(_chipSelectPin, HIGH);
}


/*
 *  Read_MFRC522 Nombre de la función: Read_MFRC522
 *  Descripción: Desde el MFRC522 leer un byte de un registro de datos
 *  Los parámetros de entrada: addr - la dirección de registro
 *  Valor de retorno: Devuelve un byte de datos de lectura
 */
uint8_t RFID::readMFRC522(uint8_t addr)
{
    uint8_t val;
    digitalWrite(_chipSelectPin, LOW);
	
#if defined(_USE_SOFT_SPI_)
    softSPITranser(((addr<<1)&0x7E) | 0x80);	
    val = softSPITranser(0x00);
#else	
    SPI.transfer(((addr<<1)&0x7E) | 0x80);	
    val = SPI.transfer(0x00);
#endif

    digitalWrite(_chipSelectPin, HIGH);
    return val;	
}


void RFID::antennaOn(void)
{
	uint8_t temp;

	temp = readMFRC522(TxControlReg);
	if (!(temp & 0x03))
	{
		setBitMask(TxControlReg, 0x03);
	}
}


void RFID::setBitMask(uint8_t reg, uint8_t mask)  
{ 
    uint8_t tmp;
    tmp = readMFRC522(reg);
    writeMFRC522(reg, tmp | mask);  // set bit mask
}

void RFID::clearBitMask(uint8_t reg, uint8_t mask)  
{
    uint8_t tmp;
    tmp = readMFRC522(reg);
    writeMFRC522(reg, tmp & (~mask));  // clear bit mask
} 

void RFID::calculateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
    uint8_t i, n;

    clearBitMask(DivIrqReg, 0x04);			//CRCIrq = 0
    setBitMask(FIFOLevelReg, 0x80);			//Claro puntero FIFO
    //Write_MFRC522(CommandReg, PCD_IDLE);

	//Escribir datos en el FIFO	
    for (i=0; i<len; i++)
    {   
		writeMFRC522(FIFODataReg, *(pIndata+i));   
	}
    writeMFRC522(CommandReg, PCD_CALCCRC);

	// Esperar a la finalización de cálculo del CRC
    i = 0xFF;
    do 
    {
        n = readMFRC522(DivIrqReg);
        i--;
    }
    while ((i!=0) && !(n&0x04));			//CRCIrq = 1

	//Lea el cálculo de CRC
    pOutData[0] = readMFRC522(CRCResultRegL);
    pOutData[1] = readMFRC522(CRCResultRegM);
}

uint8_t RFID::MFRC522ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen, uint8_t *backData, uint16_t *backLen)
{
    uint8_t status = MI_ERR;
    uint8_t irqEn = 0x00;
    uint8_t waitIRq = 0x00;
	uint8_t lastBits;
    uint8_t n;
    uint16_t i;

    switch (command)
    {
        case PCD_AUTHENT:		// Tarjetas de certificación cerca
		{
			irqEn = 0x12;
			waitIRq = 0x10;
			break;
		}
		case PCD_TRANSCEIVE:	//La transmisión de datos FIFO
		{
			irqEn = 0x77;
			waitIRq = 0x30;
			break;
		}
		default:
			break;
    }

    writeMFRC522(CommIEnReg, irqEn|0x80);	//De solicitud de interrupción
    clearBitMask(CommIrqReg, 0x80);			// Borrar todos los bits de petición de interrupción
    setBitMask(FIFOLevelReg, 0x80);			//FlushBuffer=1, FIFO de inicialización

	writeMFRC522(CommandReg, PCD_IDLE);	//NO action;Y cancelar el comando

	//Escribir datos en el FIFO
    for (i=0; i<sendLen; i++)
    {   
		writeMFRC522(FIFODataReg, sendData[i]);    
	}

	//???? ejecutar el comando
	writeMFRC522(CommandReg, command);
    if (command == PCD_TRANSCEIVE)
    {    
		setBitMask(BitFramingReg, 0x80);		//StartSend=1,transmission of data starts  
	}   

	// A la espera de recibir datos para completar
	i = 2000;	//i????????,??M1???????25ms	??? i De acuerdo con el ajuste de frecuencia de reloj, el tiempo máximo de espera operación M1 25ms tarjeta??
    do 
    {
		//CommIrqReg[7..0]
		//Set1 TxIRq RxIRq IdleIRq HiAlerIRq LoAlertIRq ErrIRq TimerIRq
        n = readMFRC522(CommIrqReg);
        i--;
    }
    while ((i!=0) && !(n&0x01) && !(n&waitIRq));

    clearBitMask(BitFramingReg, 0x80);			//StartSend=0

    if (i != 0)
    {    
        if(!(readMFRC522(ErrorReg) & 0x1B))	//BufferOvfl Collerr CRCErr ProtecolErr
        {
            status = MI_OK;
            if (n & irqEn & 0x01)
            {   
				status = MI_NOTAGERR;			//??   
			}

            if (command == PCD_TRANSCEIVE)
            {
               	n = readMFRC522(FIFOLevelReg);
              	lastBits = readMFRC522(ControlReg) & 0x07;
                if (lastBits)
                {   
					*backLen = (n-1)*8 + lastBits;   
				}
                else
                {   
					*backLen = n*8;   
				}

                if (n == 0)
                {   
					n = 1;    
				}
                if (n > MAX_LEN)
                {   
					n = MAX_LEN;   
				}

				//??FIFO??????? Lea los datos recibidos en el FIFO
                for (i=0; i<n; i++)
                {   
					backData[i] = readMFRC522(FIFODataReg);    
				}
            }
        }
        else
        {   
			status = MI_ERR;  
		}

    }

    //SetBitMask(ControlReg,0x80);           //timer stops
    //Write_MFRC522(CommandReg, PCD_IDLE); 

    return status;
}


/*
 *  Nombre de la función: MFRC522_Request
 *  Descripción: Buscar las cartas, leer el número de tipo de tarjeta
 *  Los parámetros de entrada: reqMode - encontrar el modo de tarjeta,
 *			   Tagtype - Devuelve el tipo de tarjeta
 *			 	0x4400 = Mifare_UltraLight
 *				0x0400 = Mifare_One(S50)
 *				0x0200 = Mifare_One(S70)
 *				0x0800 = Mifare_Pro(X)
 *				0x4403 = Mifare_DESFire
 *  Valor de retorno: el retorno exitoso MI_OK
 */
uint8_t  RFID::MFRC522Request(uint8_t reqMode, uint8_t *TagType)
{
	uint8_t status;  
	uint16_t backBits;			//   Recibió bits de datos

	writeMFRC522(BitFramingReg, 0x07);		//TxLastBists = BitFramingReg[2..0]	???

	TagType[0] = reqMode;
	status = MFRC522ToCard(PCD_TRANSCEIVE, TagType, 1, TagType, &backBits);

	if ((status != MI_OK) || (backBits != 0x10))
	{    
		status = MI_ERR;
	}

	return status;
}

/**
 *  MFRC522Anticoll -> anticoll
 *  Anti-detección de colisiones, la lectura del número de serie de la tarjeta de tarjeta
 *  @param serNum - devuelve el número de tarjeta 4 bytes de serie, los primeros 5 bytes de bytes de paridad
 *  @return retorno exitoso MI_OK
 */
uint8_t RFID::anticoll(uint8_t *serNum)
{
    uint8_t status;
    uint8_t i;
	uint8_t serNumCheck=0;
    uint16_t unLen;


    //ClearBitMask(Status2Reg, 0x08);		//TempSensclear
    //ClearBitMask(CollReg,0x80);			//ValuesAfterColl
	writeMFRC522(BitFramingReg, 0x00);		//TxLastBists = BitFramingReg[2..0]

    serNum[0] = PICC_ANTICOLL;
    serNum[1] = 0x20;
    status = MFRC522ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);

    if (status == MI_OK)
	{
		//?????? Compruebe el número de serie de la tarjeta
		for (i=0; i<4; i++)
		{   
		 	serNumCheck ^= serNum[i];
		}
		if (serNumCheck != serNum[i])
		{   
			status = MI_ERR;    
		}
    }

    //SetBitMask(CollReg, 0x80);		//ValuesAfterColl=1

    return status;
}

/* 
 * MFRC522Auth -> auth
 * Verificar la contraseña de la tarjeta
 * Los parámetros de entrada: AuthMode - Modo de autenticación de contraseña
                 0x60 = A 0x60 = validación KeyA
                 0x61 = B 0x61 = validación KeyB
             BlockAddr--  bloque de direcciones
             Sectorkey-- sector contraseña
             serNum--,4? Tarjeta de número de serie, 4 bytes
 * MI_OK Valor de retorno: el retorno exitoso MI_OK
 */
uint8_t RFID::auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *serNum)
{
    uint8_t status;
    uint16_t recvBits;
    uint8_t i;
	uint8_t buff[12]; 

	//????+???+????+???? Verifique la dirección de comandos de bloques del sector + + contraseña + número de la tarjeta de serie
    buff[0] = authMode;
    buff[1] = BlockAddr;
    for (i=0; i<6; i++)
    {    
		buff[i+2] = *(Sectorkey+i);   
	}
    for (i=0; i<4; i++)
    {    
		buff[i+8] = *(serNum+i);   
	}
    status = MFRC522ToCard(PCD_AUTHENT, buff, 12, buff, &recvBits);

    if ((status != MI_OK) || (!(readMFRC522(Status2Reg) & 0x08)))
    {   
		status = MI_ERR;   
	}

    return status;
}

/*
 * MFRC522Read -> read
 * Lectura de datos de bloque
 * Los parámetros de entrada: blockAddr - dirección del bloque; recvData - leer un bloque de datos
 * MI_OK Valor de retorno: el retorno exitoso MI_OK
 */
uint8_t RFID::read(uint8_t blockAddr, uint8_t *recvData)
{
    uint8_t status;
    uint16_t unLen;

    recvData[0] = PICC_READ;
    recvData[1] = blockAddr;
    calculateCRC(recvData,2, &recvData[2]);
    status = MFRC522ToCard(PCD_TRANSCEIVE, recvData, 4, recvData, &unLen);

    if ((status != MI_OK) || (unLen != 0x90))
    {
        status = MI_ERR;
    }

    return status;
}

/*
 * MFRC522Write -> write
 * La escritura de datos de bloque
 * blockAddr - dirección del bloque; WriteData - para escribir 16 bytes del bloque de datos
 * Valor de retorno: el retorno exitoso MI_OK
 */
uint8_t RFID::write(uint8_t blockAddr, uint8_t *writeData)
{
    uint8_t status;
    uint16_t recvBits;
    uint8_t i;
	uint8_t buff[18]; 

    buff[0] = PICC_WRITE;
    buff[1] = blockAddr;
    calculateCRC(buff, 2, &buff[2]);
    status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 4, buff, &recvBits);

    if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
    {   
		status = MI_ERR;   
	}

    if (status == MI_OK)
    {
        for (i=0; i<16; i++)		//?FIFO?16Byte?? Datos a la FIFO 16Byte escribir
        {    
        	buff[i] = *(writeData+i);   
        }
        calculateCRC(buff, 16, &buff[16]);
        status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 18, buff, &recvBits);

		if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
        {   
			status = MI_ERR;   
		}
    }

    return status;
}


/*
 * MFRC522Halt -> halt
 * Cartas de Mando para dormir
 * Los parámetros de entrada: Ninguno
 * Valor devuelto: Ninguno
 */
void RFID::halt()
{
    uint8_t status;
    uint16_t unLen;
    uint8_t buff[4];

    buff[0] = PICC_HALT;
    buff[1] = 0;
    calculateCRC(buff, 2, &buff[2]);

    clearBitMask(Status2Reg, 0x08); // turn off encryption

    status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 4, buff,&unLen);
}

inline __attribute__((always_inline))
uint8_t RFID::softSPITranser(uint8_t data) {

  uint8_t b=0;

  for (uint8_t bit = 0; bit < 8; bit++) {
    if (data & (1 << (7-bit)))		// walks down mask from bit 7 to bit 0
      pinSetFast(_mosiPin); // Data High
    else
      pinResetFast(_mosiPin); // Data Low
		
    pinSetFast(_clockPin); // Clock High

    b <<= 1;
    if (pinReadFast(_misoPin))
      b |= 1;

    pinResetFast(_clockPin); // Clock Low
  }
  return b;

}

RFID.h

C Header File
/* RFID.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT.
 * Based on code Dr.Leong   ( WWW.B2CQSHOP.COM )
 * Created by Miguel Balboa (circuitito.com), Jan, 2012.
 * Modified by Paul Kourany to run on Spark Core with added support for Software SPI, Mar, 2014.
 */
#ifndef RFID_h
#define RFID_h

#include "application.h"

/* !!! Uncomment the following line if SOFT SPI is to be used !!! */
#define _USE_SOFT_SPI_


/******************************************************************************
 * Definitions
 ******************************************************************************/
#define MAX_LEN 16   // Largo máximo de la matriz

//MF522 comando palabra
#define PCD_IDLE              0x00               // NO action; Y cancelar el comando
#define PCD_AUTHENT           0x0E               // autenticación de clave
#define PCD_RECEIVE           0x08               // recepción de datos
#define PCD_TRANSMIT          0x04               // Enviar datos
#define PCD_TRANSCEIVE        0x0C               // Enviar y recibir datos
#define PCD_RESETPHASE        0x0F               // reajustar
#define PCD_CALCCRC           0x03               // CRC calcular

//Mifare_One  Tarjeta Mifare_One comando palabra
#define PICC_REQIDL           0x26               // Area de la antena no esta tratando de entrar en el estado de reposo
#define PICC_REQALL           0x52               // Todas las cartas para encontrar el área de la antena
#define PICC_ANTICOLL         0x93               // anti-colisión
#define PICC_SElECTTAG        0x93               // elección de tarjeta
#define PICC_AUTHENT1A        0x60               // verificación key A
#define PICC_AUTHENT1B        0x61               // verificación Key B
#define PICC_READ             0x30               // leer bloque
#define PICC_WRITE            0xA0               // Escribir en el bloque 
#define PICC_DECREMENT        0xC0               // cargo
#define PICC_INCREMENT        0xC1               // recargar
#define PICC_RESTORE          0xC2               // Transferencia de datos de bloque de buffer
#define PICC_TRANSFER         0xB0               // Guardar los datos en el búfer
#define PICC_HALT             0x50               // inactividad

//MF522 Código de error de comunicación cuando regresó
#define MI_OK                 0
#define MI_NOTAGERR           1
#define MI_ERR                2

//------------------ MFRC522 registro---------------
//Page 0:Command and Status
#define     Reserved00            0x00    
#define     CommandReg            0x01    
#define     CommIEnReg            0x02    
#define     DivlEnReg             0x03    
#define     CommIrqReg            0x04    
#define     DivIrqReg             0x05
#define     ErrorReg              0x06    
#define     Status1Reg            0x07    
#define     Status2Reg            0x08    
#define     FIFODataReg           0x09
#define     FIFOLevelReg          0x0A
#define     WaterLevelReg         0x0B
#define     ControlReg            0x0C
#define     BitFramingReg         0x0D
#define     CollReg               0x0E
#define     Reserved01            0x0F
//Page 1:Command     
#define     Reserved10            0x10
#define     ModeReg               0x11
#define     TxModeReg             0x12
#define     RxModeReg             0x13
#define     TxControlReg          0x14
#define     TxAutoReg             0x15
#define     TxSelReg              0x16
#define     RxSelReg              0x17
#define     RxThresholdReg        0x18
#define     DemodReg              0x19
#define     Reserved11            0x1A
#define     Reserved12            0x1B
#define     MifareReg             0x1C
#define     Reserved13            0x1D
#define     Reserved14            0x1E
#define     SerialSpeedReg        0x1F
//Page 2:CFG    
#define     Reserved20            0x20  
#define     CRCResultRegM         0x21
#define     CRCResultRegL         0x22
#define     Reserved21            0x23
#define     ModWidthReg           0x24
#define     Reserved22            0x25
#define     RFCfgReg              0x26
#define     GsNReg                0x27
#define     CWGsPReg	          0x28
#define     ModGsPReg             0x29
#define     TModeReg              0x2A
#define     TPrescalerReg         0x2B
#define     TReloadRegH           0x2C
#define     TReloadRegL           0x2D
#define     TCounterValueRegH     0x2E
#define     TCounterValueRegL     0x2F
//Page 3:TestRegister     
#define     Reserved30            0x30
#define     TestSel1Reg           0x31
#define     TestSel2Reg           0x32
#define     TestPinEnReg          0x33
#define     TestPinValueReg       0x34
#define     TestBusReg            0x35
#define     AutoTestReg           0x36
#define     VersionReg            0x37
#define     AnalogTestReg         0x38
#define     TestDAC1Reg           0x39  
#define     TestDAC2Reg           0x3A   
#define     TestADCReg            0x3B   
#define     Reserved31            0x3C   
#define     Reserved32            0x3D   
#define     Reserved33            0x3E   
#define     Reserved34			  0x3F
//-----------------------------------------------

class RFID
{
  public:
    RFID(uint8_t chipSelectPin, uint8_t NRSTPD);
    
    RFID(uint8_t chipSelectPin, uint8_t NRSTPD, uint8_t mosiPin, uint8_t misoPin, uint8_t clockPin);

	bool isCard();
	bool readCardSerial();

    void init();
	void reset();
	void writeMFRC522(uint8_t addr, uint8_t val);
	void antennaOn(void);
	uint8_t readMFRC522(uint8_t addr);
	void setBitMask(uint8_t reg, uint8_t mask);
	void clearBitMask(uint8_t reg, uint8_t mask);
	void calculateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData);
	uint8_t MFRC522Request(uint8_t reqMode, uint8_t *TagType);
	uint8_t MFRC522ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen, uint8_t *backData, uint16_t *backLen);
	uint8_t anticoll(uint8_t *serNum);
	uint8_t auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *serNum);
	uint8_t read(uint8_t blockAddr, uint8_t *recvData);
	uint8_t write(uint8_t blockAddr, uint8_t *writeData);
	void halt();

	uint8_t serNum[5];       // Constante para guardar el numero de serie leido.
	uint8_t AserNum[5];      // Constante para guardar el numero d serie de la secion actual.

  private:
    uint8_t softSPITranser(uint8_t data);      // SOFT SPI
  
    uint8_t _chipSelectPin;
	uint8_t _NRSTPD;
    uint8_t _mosiPin;
    uint8_t _misoPin;
    uint8_t _clockPin;
};

#endif

Board Gateway - Particle Argon

C/C++
void myHandler(const char *event, const char *data)
{
  Serial.println(data);
  Particle.publish("gamesetting", data ? data : "none", PRIVATE);
}

void setup() {
    Serial.begin(9600);
    Mesh.subscribe("nfc-tag", myHandler);
}

void loop() {
}

MainActivity

Java
Android MainActivity (Kotlin)
package ptrprograms.com.arboardgame

import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.Toast
import com.google.ar.core.*
import com.google.ar.core.exceptions.CameraNotAvailableException
import com.google.ar.core.exceptions.UnavailableException
import com.google.ar.sceneform.AnchorNode
import com.google.ar.sceneform.ArSceneView
import com.google.ar.sceneform.Node
import com.google.ar.sceneform.math.Vector3
import com.google.ar.sceneform.rendering.ModelRenderable
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException

class MainActivity : AppCompatActivity(), InfoCallback {

    private val CAMERA_PERMISSION = 42

    //Maps what we get from Firebase with an easier to work with value
    companion object {
        val SUN = "Sun"
        val MERCURY = "Mercury"
        val VENUS = "Venus"
        val EARTH = "Earth"
        val MARS = "Mars"
        val JUPITER = "Jupiter"
        val SATURN = "Saturn"
        val NEPTUNE = "Neptune"
        val URANUS = "Uranus"
    }

    //Item that we get back from Firebase
    private lateinit var celestial: String

    //Prevents stacking multiple instances of the same object
    private var hasPlacedObject = false

    private lateinit var arSceneView: ArSceneView

    //Used to intercept tap events on the screen
    private lateinit var gestureDetector: GestureDetector

    //Used to validate if the user has the AR Core app installed on their device
    private var installRequested: Boolean = false

    //Used to clear the base when a new value is available
    private var objectBase : Node? = null

    //Renderable objects for our 3D models
    private var sunRenderable: ModelRenderable? = null
    private var mercuryRenderable: ModelRenderable? = null
    private var venusRenderable: ModelRenderable? = null
    private var earthRenderable: ModelRenderable? = null
    private var marsRenderable: ModelRenderable? = null
    private var jupiterRenderable: ModelRenderable? = null
    private var saturnRenderable: ModelRenderable? = null
    private var neptuneRenderable: ModelRenderable? = null
    private var uranusRenderable: ModelRenderable? = null

    //Flag for when the 3D models have finished loading
    private var hasFinishedLoading = false

    private val infoFragment = InfoFragment()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        arSceneView = findViewById(R.id.ar_scene_view)

        initData()
        initRenderables()
        initGestures()
        initSceneView()

        requestCameraPermission()
    }

    private fun initGestures() {
        gestureDetector = GestureDetector(this,
            object: GestureDetector.SimpleOnGestureListener() {
                override fun onSingleTapUp(e: MotionEvent?): Boolean {
                    onSingleTap(e, celestial)
                    return super.onSingleTapUp(e)
                }

                override fun onDown(e: MotionEvent?): Boolean {
                    return true
                }
            })

        arSceneView.scene.setOnTouchListener { hitTestResult, motionEvent ->
            if( !hasPlacedObject ) {
                return@setOnTouchListener gestureDetector.onTouchEvent(motionEvent)
            }

            return@setOnTouchListener false
        }
    }

    private fun onSingleTap(tap: MotionEvent?, celestialKey: String) {
        if( !hasFinishedLoading ) {
            return
        }

        val frame = arSceneView.arFrame
        if( frame != null ) {
            if( !hasPlacedObject && tryPlacingObject(tap, frame, celestialKey))
                hasPlacedObject = true
        }
    }

    private fun tryPlacingObject(tap: MotionEvent?, frame: Frame, celestialKey: String) : Boolean {
        if( frame.camera.trackingState == TrackingState.TRACKING) {
            for (hit in frame.hitTest(tap)) {
                val trackable = hit.trackable
                if (trackable is Plane && trackable.isPoseInPolygon(hit.hitPose)) {
                    val anchor = hit.createAnchor()
                    val anchorNode = AnchorNode(anchor)
                    anchorNode.setParent(arSceneView.scene)
                    objectBase = createCelestial(celestialKey)
                    anchorNode.addChild(objectBase)
                    return true
                }
            }
        }

        return false
    }

    private fun createCelestial(celestialKey: String) : Node {
        val base = Node()


        val renderable = getRenderable(celestialKey)

        if( renderable != null ) {
            val celestialObject =
                CelestialBody(context = this, celestialName = celestial, renderable = renderable, infoCallback = this)
            celestialObject.setParent(base)
            celestialObject.localPosition = Vector3(0.0f, 0.5f, 0.0f)
            celestialObject.localScale = Vector3(0.5f, 0.5f, 0.5f)
        }

        return base
    }

    private fun getRenderable(celestialKey: String) : ModelRenderable? {
        return when( celestialKey ) {
            SUN -> sunRenderable
            MERCURY -> mercuryRenderable
            VENUS -> venusRenderable
            EARTH -> earthRenderable
            MARS -> marsRenderable
            JUPITER -> jupiterRenderable
            SATURN -> saturnRenderable
            NEPTUNE -> neptuneRenderable
            URANUS -> uranusRenderable
            else -> null
        }
    }

    private fun initSceneView() {
        arSceneView
            .scene
            .addOnUpdateListener { frameTime ->
                val frame = arSceneView.arFrame

                if( frame == null )
                    return@addOnUpdateListener

                if( frame.camera.trackingState != TrackingState.TRACKING)
                    return@addOnUpdateListener

            }
    }

    private fun initData() {
        val database = FirebaseDatabase.getInstance()
        val myRef = database.getReference("piece/1/tag/planet")

        myRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                celestial = dataSnapshot.getValue(String::class.java) ?: MERCURY
                hasPlacedObject = false
                if( infoFragment.isAdded ) {
                    infoFragment.dismiss()
                }
                this@MainActivity.runOnUiThread { objectBase?.setParent(null) }

            }

            override fun onCancelled(error: DatabaseError) {
                Log.w("TEST", "Failed to read value.", error.toException())
            }
        })
    }

    private fun initRenderables() {
        val sunStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Sol.sfb")).build()

        val mercuryStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Mercury.sfb")).build()

        val venusStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Venus.sfb")).build()

        val earthStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Earth.sfb")).build()

        val marsStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Mars.sfb")).build()

        val jupiterStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Jupiter.sfb")).build()

        val saturnStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Saturn.sfb")).build()

        val neptuneStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Neptune.sfb")).build()

        val uranusStage =
            ModelRenderable.builder().setSource(this,
                Uri.parse("Uranus.sfb")).build()

        CompletableFuture.allOf(
            sunStage,
            mercuryStage,
            venusStage,
            earthStage,
            marsStage,
            jupiterStage,
            saturnStage,
            neptuneStage,
            uranusStage).handle { t, u ->
            try {
                sunRenderable = sunStage.get()
                mercuryRenderable = mercuryStage.get()
                venusRenderable = venusStage.get()
                earthRenderable = earthStage.get()
                marsRenderable = marsStage.get()
                jupiterRenderable = jupiterStage.get()
                saturnRenderable = saturnStage.get()
                neptuneRenderable = neptuneStage.get()
                uranusRenderable = uranusStage.get()

                hasFinishedLoading = true
            } catch(e: InterruptedException) {

            } catch(e: ExecutionException) {

            }
        }
    }

    private fun requestCameraPermission() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if( !hasCameraPermission() ) {
            Toast.makeText(this,
                "You must grant camera permissions to use this app.",
                Toast.LENGTH_SHORT).show()

            finish()
        }
    }

    private fun hasCameraPermission() : Boolean {
        return ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
    }

    override fun onResume() {
        super.onResume()

        if (arSceneView.session == null) {
            try {
                val session = createArSession(installRequested)
                if (session == null) {
                    installRequested = hasCameraPermission()
                    return
                } else {
                    arSceneView.setupSession(session)
                }
            } catch (e: UnavailableException) {
                Toast.makeText(this, "Exception occurred.", Toast.LENGTH_LONG).show()
                finish()
            }

        }

        try {
            arSceneView.resume()
        } catch (ex: CameraNotAvailableException) {
            finish()
            return
        }
    }

    private fun createArSession(installRequested: Boolean) : Session? {
        var session: Session? = null
        if( hasCameraPermission()) {
            when( ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
                ArCoreApk.InstallStatus.INSTALL_REQUESTED -> return null
                ArCoreApk.InstallStatus.INSTALLED -> {}
            }

            session = Session(this)
            val config = Config(session)
            config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
            session.configure(config)
        }

        return session
    }

    override fun onPause() {
        super.onPause()
        arSceneView.pause()
    }

    override fun onDestroy() {
        super.onDestroy()
        arSceneView.destroy()
    }

    override fun showInfo() {
        infoFragment.planet = celestial
        infoFragment.show(supportFragmentManager, "infoFragment")
    }
}

interface InfoCallback {
    fun showInfo()
}

InfoFragment

Java
Info fragment (Android - Kotlin)
package ptrprograms.com.arboardgame

import android.os.Bundle
import android.support.design.widget.BottomSheetDialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.TextView

class InfoFragment : BottomSheetDialogFragment() {

    var planet = ""
    lateinit var textView: TextView
    val info : Map<String, String> = mapOf(
        MainActivity.MERCURY to "Mercury is the closest planet to the Sun and due to its proximity it is not easily seen except during twilight. For every two orbits of the Sun, Mercury completes three rotations about its axis and up until 1965 it was thought that the same side of Mercury constantly faced the Sun. Thirteen times a century Mercury can be observed from the Earth passing across the face of the Sun in an event called a transit, the next will occur on the 9th May 2016.",
        MainActivity.VENUS to "Venus is the second planet from the Sun and is the second brightest object in the night sky after the Moon. Named after the Roman goddess of love and beauty, Venus is the second largest terrestrial planet and is sometimes referred to as the Earth’s sister planet due the their similar size and mass. The surface of the planet is obscured by an opaque layer of clouds made up of sulphuric acid.",
        MainActivity.EARTH to "Earth is the third planet from the Sun and is the largest of the terrestrial planets. The Earth is the only planet in our solar system not to be named after a Greek or Roman deity. The Earth was formed approximately 4.54 billion years ago and is the only known planet to support life.",
        MainActivity.MARS to "Mars is the fourth planet from the Sun and is the second smallest planet in the solar system. Named after the Roman god of war, Mars is also often described as the “Red Planet” due to its reddish appearance. Mars is a terrestrial planet with a thin atmosphere composed primarily of carbon dioxide.",
        MainActivity.JUPITER to "The planet Jupiter is the fifth planet out from the Sun, and is two and a half times more massive than all the other planets in the solar system combined. It is made primarily of gases and is therefore known as a “gas giant”.",
        MainActivity.SATURN to "Saturn is the sixth planet from the Sun and the most distant that can be seen with the naked eye. Saturn is the second largest planet and is best known for its fabulous ring system that was first observed in 1610 by the astronomer Galileo Galilei. Like Jupiter, Saturn is a gas giant and is composed of similar gasses including hydrogen, helium and methane.",
        MainActivity.NEPTUNE to "Neptune is the eighth planet from the Sun making it the most distant in the solar system. This gas giant planet may have formed much closer to the Sun in early solar system history before migrating to its present position.",
        MainActivity.URANUS to "Uranus is the seventh planet from the Sun. While being visible to the naked eye, it was not recognised as a planet due to its dimness and slow orbit. Uranus became the first planet discovered with the use of a telescope. Uranus is tipped over on its side with an axial tilt of 98 degrees. It is often described as “rolling around the Sun on its side.”",
        MainActivity.SUN to "The Sun (or Sol), is the star at the centre of our solar system and is responsible for the Earth’s climate and weather. The Sun is an almost perfect sphere with a difference of just 10km in diameter between the poles and the equator. The average radius of the Sun is 695,508 km (109.2 x that of the Earth) of which 20–25% is the core.")

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.info_layout, container, false)
        textView = view.findViewById(R.id.infoText) as TextView
        return view
    }

    override fun onStart() {
        super.onStart()
        textView.text = info.get(planet) ?: ""

        val attributes = dialog.window.attributes
        attributes.dimAmount = 0.0f
        attributes.flags = attributes.flags.or(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
        dialog.window.attributes = attributes
    }

}

Credits

Paul Trebilcox-Ruiz

Paul Trebilcox-Ruiz

17 projects • 53 followers
Google Developer Expert (GDE) for IoT & Android. Former zookeeper, current 501st and Rebel Legion member, have built real robots

Comments