Advertisement

Sri Lanka's First and Only Platform for Luxury Houses and Apartment for Sale, Rent

Tuesday, March 26, 2013

My New RC Car

This may sound really childish but yes I bought myself a Brand New Ferrari Model RC Car. It is a Red color car which you can see in the below image.


The RC Car uses Radio Frequency 27 Mhz to receive control from the Remote. This is how the under the hood of the car looks like.


Some more closer looks


The IC in RC Car is RX - 2 which is the common IC in Chinese RC Cars.


The under the hood of the Remote Control looks like this.


The IC of the Remote Control is TX - 2. This is the datasheet for TX - 2 and RX - 2 

Friday, March 22, 2013

Android 4.1.2 Jelly Bean and Arduino Mega 2560 with USB Host Shield Communication

Few  weeks ago I wrote my first Android Jelly Bean App named ColorChanger. Since then I wanted to integrate it with Arduino and Control a physical RGB LED Light from the ColorChanger Android App. I finally managed to get it done using the following Items.
  1. Arduino Mega 2560 Rev 3
  2. DFRobot USB Host Shield 
  3. Arduino IDE 0023
  4. ADK Library
  5. 1 RGB LED
  6. 1 1k Resistor
  7. Few Jumper Cables
  8. One USB to Micro USB Cable
Few things changed from the original ColorChanger App starting from the AndroidManifest.xml where I had to allow my MainActivity to listen for USB_ACCESSORY_ATTACHED intent action and when my Accessory connects open the ColorChanger App.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shazin.colorchanger"
    android:versionCode="1"
    android:versionName="1.0" >
    
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.shazin.colorchanger.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
                
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
            <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" /> 
        </activity>
    </application>

</manifest>

The filtering criteria is placed in res/xml/accessory_filter.xml file which is as follows

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory model="ColorChangerApp" manufacturer="Shazin Sadakath" version="1.0"/>
</resources>
Then my MainActivity class changed as follows
package com.shazin.colorchanger;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.view.Menu;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends Activity implements OnSeekBarChangeListener, Runnable {

    private TextView textViewColor;
    
    private SeekBar seekBarRed;
    
    private SeekBar seekBarGreen;
    
    private SeekBar seekBarBlue;
    
    private byte red, green, blue;
    
    private LinearLayout linearLayout;
    
    private UsbAccessory mAccessory;
    
    private ParcelFileDescriptor mFileDescriptor;
    
    private FileInputStream mInputStream;
    
    private FileOutputStream mOutputStream;
    
    private UsbManager mUsbManager;
    
    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
        private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
         
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (ACTION_USB_PERMISSION.equals(action)) {
                    synchronized (this) {
                        UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            if(accessory != null){
                                //call method to set up accessory communication
                                openAccessory();
                            }
                        }
                        else {
                            //Log.d(TAG, "permission denied for accessory " + accessory);
                            Toast.makeText(context, "permission denied for accessory "+accessory, 2).show();
                        }
                    }
                    unregisterReceiver(mUsbReceiver);
                }    
                
            }
        };
    
        public final BroadcastReceiver mUsbDetachReciever = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                    UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                    if (accessory != null) {
                        cleanUp();                
                    }
                    unregisterReceiver(mUsbDetachReciever);
                }
                
            }
            
        };
        


        private boolean mPermissionRequestPending;        
        
        private void cleanUp() {
            try {
                if(mFileDescriptor != null)
                    mFileDescriptor.close();
                
                if(mInputStream != null) 
                    mInputStream.close();
                
                if(mOutputStream != null)
                    mOutputStream.close();
                mAccessory = null;
                mUsbManager = null;
                Toast.makeText(this, "Closed all resources", 2).show();
            } catch (IOException e) {
                Toast.makeText(this, "Error occured "+e, 2).show();
            }
        }
        
            
    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        linearLayout = (LinearLayout) findViewById(R.id.linearLayout);
        
        textViewColor = (TextView) findViewById(R.id.textViewColor);
                
        seekBarRed = (SeekBar) findViewById(R.id.seekBarRed);
        seekBarGreen = (SeekBar) findViewById(R.id.seekBarGreen);
        seekBarBlue = (SeekBar) findViewById(R.id.seekBarBlue);
        
        seekBarBlue.setOnSeekBarChangeListener(this);
        seekBarGreen.setOnSeekBarChangeListener(this);
        seekBarRed.setOnSeekBarChangeListener(this);
        
        textViewColor.setText(String.format("(%d, %d, %d)", red, green, blue));
        linearLayout.setBackgroundColor(Color.rgb(red, green, blue));
        
        
        mAccessory = (UsbAccessory) getIntent().getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
        Toast.makeText(this, "USB Accessory = "+mAccessory, 2).show();
        
        mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
        Toast.makeText(this, "USB Manager = "+mUsbManager, 2).show();
        
        PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        registerReceiver(mUsbReceiver, filter);
        Toast.makeText(this, "Intent Registered", 2).show();
        
        if(mUsbManager.hasPermission(mAccessory)) {
            Toast.makeText(this, "Accessory has permission", 2).show();
            unregisterReceiver(mUsbReceiver);
            openAccessory();
        } else {            
            Toast.makeText(this, "Accessory has no permission", 2).show();
            synchronized (mUsbReceiver) {                
                if (!mPermissionRequestPending) {
                    mUsbManager.requestPermission(mAccessory, mPermissionIntent);
                    Toast.makeText(this, "Permission Request Sent", 2).show();
                    mPermissionRequestPending = true;
                }
            }
        }
        
        IntentFilter filter2 = new IntentFilter(ACTION_USB_PERMISSION);
        filter2.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
        registerReceiver(mUsbDetachReciever, filter2);        
        
    }
    
    public void onDestroy() {
        super.onDestroy();
        cleanUp();
    }
    
    @SuppressLint("NewApi")
    private void openAccessory() {
        //Log.d(TAG, "openAccessory: " + accessory);
        mFileDescriptor = mUsbManager.openAccessory(mAccessory);
        Toast.makeText(this, "Accessory Opened", 2).show();
        if (mFileDescriptor != null) {
            FileDescriptor fd = mFileDescriptor.getFileDescriptor();
            Toast.makeText(this, "File Desc "+fd, 2).show();
            mInputStream = new FileInputStream(fd);
            mOutputStream = new FileOutputStream(fd);
            
        }
    }

    @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
    public void onProgressChanged(SeekBar seekBar, int value, boolean flag) {
        if(seekBar == seekBarRed) {
            red = (byte) value;
        } else if(seekBar == seekBarGreen) {
            green = (byte) value;
        } else if(seekBar == seekBarBlue) {
            blue = (byte) value;
        }
        linearLayout.setBackgroundColor(Color.rgb(red & 0xFF, green & 0xFF, blue & 0xFF));
        textViewColor.setText(String.format("(%d, %d, %d)", red & 0xFF, green & 0xFF, blue & 0xFF));
        if(mOutputStream != null) {
            try {
                byte[] data = new byte[4];
                data[0] = 0x2;
                data[1] = red;
                data[2] = green;
                data[3] = blue;
                mOutputStream.write(data);
                mOutputStream.flush();
            } catch (IOException e) {
                Toast.makeText(this, "Error "+e, 2).show();
            }
        }    
    }

    @Override
    public void onStartTrackingTouch(SeekBar arg0) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void onStopTrackingTouch(SeekBar arg0) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void run() {
    
    }

}


And the Arduino Sketch is as follows

#include <avrpins.h>
#include <max3421e.h>
#include <usbhost.h>
#include <usb_ch9.h>
#include <Usb.h>
#include <usbhub.h>
#include <avr/pgmspace.h>
#include <address.h>

#include <adk.h>

#include <printhex.h>
#include <message.h>
#include <hexdump.h>
#include <parsetools.h>
// For Arduino IDE 1.0.x Only following two is needed
#include <adk.h>
#include &ltusbhub.h>

USB Usb;
USBHub hub0(&Usb);
USBHub hub1(&Usb);
ADK adk(&Usb,"Shazin Sadakath",
            "ColorChangerApp", // This name Should match with xml/res/accessory_filter.xml name
            "Color Changer App",
            "1.0",
            "http://shazsterblog.blogspot.com",
            "0000000012345678");

#define  LED1_RED       5
#define  LED1_GREEN       4
#define  LED1_BLUE       3

void setup();
void loop();

void init_leds()
{
    digitalWrite(LED1_RED, 0);
    digitalWrite(LED1_GREEN, 0);
    digitalWrite(LED1_BLUE, 0);

    pinMode(LED1_RED, OUTPUT);
    pinMode(LED1_GREEN, OUTPUT);
    pinMode(LED1_BLUE, OUTPUT);
}

void setup()
{
    Serial.begin(115200);
    Serial.println("\r\nADK demo start");
        
        if (Usb.Init() == -1) {
          Serial.println("OSCOKIRQ failed to assert");
        while(1); //halt
        }//if (Usb.Init() == -1...


    init_leds();
}

void loop()
{
  uint8_t rcode;
  uint8_t msg[4] = { 0x00 };
   Usb.Task();
   
   if( adk.isReady() == false ) {
     analogWrite(LED1_RED, 255);
     return;
   }
   uint16_t len = sizeof(msg);
   
   rcode = adk.RcvData(&len, msg);
   if( rcode ) {
     USBTRACE2("Data rcv. :", rcode );
   } 
   if(len > 0) {
     if(msg[0] == 0x2) {
         USBTRACE("\r\nData Packet.");
         uint8_t r = (msg[1]) & 0xFF;
         uint8_t g = (msg[2]) & 0xFF;
         uint8_t b = (msg[3]) & 0xFF;
         analogWrite(LED1_RED, 255 - r);
         analogWrite(LED1_GREEN, 255 - g);
         analogWrite(LED1_BLUE, 255 - b);
     }
     

   }
   
   msg[0] = 0x1;
   
      delay( 10 );       
}

The video of the whole thing working together is available below.

 

References 

Friday, March 15, 2013

How to use jqgrid with Struts2 JSON result

jqgrid plugin is one of the most famous jquery plugins to display tables in web pages which enables searching, sorting and filtering etc.

I wanted to integrate jqgrid with Struts2 but there was little to no proper documentation on how to do it. I wanted to use JSON format and no website has clearly mentioned (or atleast I didn't find any) the format of the JSON and how it needs to be created using Struts2. So after some trial and error I managed to get this to work.



struts.xml will look like the following

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
 
<struts>    

     <constant name="struts.devMode" value="true" />     
    <package name="user" namespace="/User" extends="struts-default">
        <result-types>
            <result-type name="json" class="com.googlecode.jsonplugin.JSONResult"/>
        </result-types>    
        <action name="getUsersList" class="com.shazin.struts2hello.action.GetUsersListAction">
               <result type="json" />
          </action>        
          <action name="ListUsers" class="com.shazin.struts2hello.action.ListUsersAction">
            <result name="SUCCESS">../pages/list_users.jsp</result>            
        </action>
    <package>

There are two Actions getUserList which will return the JSON result and ListUsers which will render the JSON into the jqgrid. Following is the source code for GetUsersListAction and since this object is directly used to serialize the result into JSON it needs to have the instance variable names as it is with getters and setters.

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.Action;
import com.shazin.struts2hello.model.User;
import com.shazin.struts2hello.service.UserService;
import com.shazin.struts2hello.service.impl.UserServiceImpl;

public class GetUsersListAction {
    
    private String page;  
    private Double total;  
    private String records;  
    private List rows; 
    
    public String execute() {
        page = "1";
        total = 1.0;        
        UserService userService = new UserServiceImpl(); 
        List<User> usersList = userService.getAllUsers(); // Retrieves users from database
        records = usersList.size()+"";
        rows = new ArrayList();
        int id = 1;
        for(User u:usersList) {
            rows.add(new Row(String.valueOf(id), 
                    new String[] { String.valueOf(id), u.getFirstName() , u.getLastName(), u.getUsername(), u.getEmail() }));
            id++;
        }
        
        return Action.SUCCESS;
    }

    public String getPage() {
        return page;
    }

    public void setPage(String page) {
        this.page = page;
    }

    public Double getTotal() {
        return total;
    }

    public void setTotal(Double total) {
        this.total = total;
    }

    public String getRecords() {
        return records;
    }

    public void setRecords(String records) {
        this.records = records;
    }

    public List getRows() {
        return rows;
    }

    public void setRows(List rows) {
        this.rows = rows;
    }

    public class Row implements Serializable {
        private String id;
        
        private String[] cell;
        
        public Row() {
            
        }
        
        public Row(String id, String[] cell) {
            setId(id);
            setCell(cell);
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String[] getCell() {
            return cell;
        }

        public void setCell(String[] cell) {
            this.cell = cell;
        }        
        
    }    
}

The JSON output will be like this and it should be in this format to work with jqgrid.

{"page":"1","records":"1","rows":[{"cell":["1","Shazin","Sadakath","ssadakath","shazin.sadakath@gmail.com"],"id":"1"}],"total":1.0}

ListUsers action source code is really simple

public class ListUsersAction {
    public String execute() {
        return "SUCCESS";
    }
}


Finally the source code for list_users.jsp page

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link rel="stylesheet" type="text/css" media="screen" href="../css/jquery-ui-1.10.2.custom.css" />
<link rel="stylesheet" type="text/css" media="screen" href="../css/ui.jqgrid.css" />
<script src="../js/jquery-1.7.2.min.js" type="text/javascript"></script>
<script src="../js/jquery-ui-1.10.2.custom.min.js" type="text/javascript"></script>
<script src="../js/i18n/grid.locale-en.js" type="text/javascript"></script>
<script src="../js/jquery.jqGrid.min.js" type="text/javascript"></script>
<title>List Users</title>
<script type="text/javascript"> 
jQuery().ready(function (){
    //alert('Hi');
    jQuery("#list1").jqGrid({
           url:'/Struts2Hello/User/getUsersList.action',
        datatype: "json",
           colNames:['User Id','First Name', 'Last Name', 'Username','Email'],
           colModel:[
               {name:'id',index:'id', width:75},
               {name:'firstName',index:'firstName', width:90},
               {name:'lastName',index:'lastName', width:100},
               {name:'username',index:'username', width:80},
               {name:'email',index:'username', width:80}                   
           ],
           rowNum:10,
           autowidth: true,
           rowList:[10,20,30],
           pager: jQuery('#pager1'),
           sortname: 'id',
        viewrecords: true,
        sortorder: "desc",
        caption:"Users"
    }).navGrid('#pager1',{edit:false,add:false,del:false});    
});                
</script>
</head>
<body>
    <table id="list1"></table>
    <div id="pager1"></div>
</body>
</html>

Thursday, March 14, 2013

Fixing "name" is not translated in locales in Android

If you develop Android applications which has multiple locales, you might face an error similar to the below one

"<Value Name>" is not translated in <Locales> ...

This is because some names which you don't want to be translated are also taken as translatable names. The easiest fix for this is to mark the error cause name tag as

<string name="myname" translatable="false">

<string-array name="myname" translatable="false">

in string, string-array element. Or you can define all your non-translatable strings in a resource file called donottranslate.xml. Or, you can ignore the issue with a tools:ignore="MissingTranslation" attribute.

Monday, March 4, 2013

My First Android Jelly Bean App

I have been wanting to learn Android App development for sometime now and I wanted to start from the basics and progress. So I read the documentations and some tutorials and wrote my first app using Eclipse Helios with ADT Plugin.

I must tell Eclipse ADT Plugin 20.x has come a long way from its predecessors and now has the capability to perform many tasks directly from the plugin from development to signed package deployment.

The App I wrote is named Color Changer and as the name suggests it just changes color of the App based on some user input. This is really basic but enabled me to understand a lot about Android App structure.



There is a main AndroidManifest.xml where the Application is defined.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shazin.colorchanger"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.shazin.colorchanger.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

And one Activity named MainActivity

package com.shazin.colorchanger;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.view.Menu;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity implements OnSeekBarChangeListener {

    private TextView textViewColor;
    
    private SeekBar seekBarRed;
    
    private SeekBar seekBarGreen;
    
    private SeekBar seekBarBlue;
    
    private int red, green, blue;
    
    private LinearLayout linearLayout;
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        linearLayout = (LinearLayout) findViewById(R.id.linearLayout1);
        
        textViewColor = (TextView) findViewById(R.id.textViewColor);
                
        seekBarRed = (SeekBar) findViewById(R.id.seekBarRed);
        seekBarGreen = (SeekBar) findViewById(R.id.seekBarGreen);
        seekBarBlue = (SeekBar) findViewById(R.id.seekBarBlue);
        
        seekBarBlue.setOnSeekBarChangeListener(this);
        seekBarGreen.setOnSeekBarChangeListener(this);
        seekBarRed.setOnSeekBarChangeListener(this);
        
        textViewColor.setText(String.format("(%d, %d, %d)", red, green, blue));
    }

    @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
    public void onProgressChanged(SeekBar seekBar, int value, boolean flag) {
        if(seekBar == seekBarRed) {
            red = value;
        } else if(seekBar == seekBarGreen) {
            green = value;
        } else if(seekBar == seekBarBlue) {
            blue = value;
        }
        linearLayout.setBackgroundColor(Color.rgb(red, green, blue));
        textViewColor.setText(String.format("(%d, %d, %d)", red, green, blue));
    }

    @Override
    public void onStartTrackingTouch(SeekBar arg0) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void onStopTrackingTouch(SeekBar arg0) {
        // TODO Auto-generated method stub
        
    }

}


And an Activity can have a Layout which defines the User Interface components

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="0dp"
        android:layout_marginTop="0dp"
        android:orientation="vertical" >

        <SeekBar
            android:id="@+id/seekBarRed"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            android:max="255"/>

        <SeekBar
            android:id="@+id/seekBarGreen"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            android:max="255"/>

        <SeekBar
            android:id="@+id/seekBarBlue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            android:max="255"/>
            
        <TextView
            android:id="@+id/textViewColor"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"            
            android:textAppearance="?android:attr/textAppearanceLarge" />

    </LinearLayout>

</RelativeLayout>

And all the Strings used in the Application can be defined in strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Color Changer</string>
    <string name="action_settings">Settings</string>

</resources>


Source Code can be obtained from here.