bluetooth(HID方式)で接続したバーコードリーダから入力データを読み取りたい

4,447 views
Skip to first unread message

patience

unread,
Jan 22, 2012, 9:35:43 PM1/22/12
to 日本Androidの会
お世話になります。Android初心者です。現在、bluetooth(HID方式)で接続したバーコードリーダからのデータを読み取るための試験的
なクラスを作成しています。android-sdk に付属している「BluetoothChat」を元に、必要なソースコードだけを拾い上げてのテス
トです。参考にしたのは、下記の2つのサイトになります。
http://www.bright-sys.co.jp/blog/android-using-bluetooth-spp/
http://d.hatena.ne.jp/hdk_embedded/20110206/1297012049
デバックをかけるとリッスンまでは進むようなのですが、コネクトできません。テストコード中では、UUIDに、バーコードリーダの.infファイル中の
ClassGuidを指定しており、この部分でひっかかっているような気もします(UUIDを他のものに変えた場合にも、デバック結果は、同一になりま
す)。
ちなみに、EditTextにフォーカシングさえされていれば、アプリケーション上はコネクトされていないにもかかわらず、Android OSレベル
ではコネクトしているためか、そのままバーコードリーダで読み込んだ数値がEditTextに表示されてしまいます。ただし、EditTextのイベン
トに頼った場合、フォーカシング有無の問題や、カーソルの位置によって入力値がぶれるなどの問題があるため、できれば、バーコードリーダで読み込んだ数
値をEditTextに直接読み込ませるようなことはせず、バーコードリーダから受け取ったデータを1度バッファして、数値(バーコードリーダから送ら
れている最後の改行コード部分)を加工し、TextViewに表示させたいと思っております。
どうも相当に見当違いのことをしているような気もするため、どなたかお教えいただけないでしょうか。bluetooth接続や、バーコードリーダの利用
が初めてということもあり、敢えて、ソースを全て掲載いたします。よろしくお願い申し上げます。

【バーコードリーダの.infファイルより】
[Version]
Signature="$Windows NT$"
Class=Ports
ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
Provider=%KoamTac%
LayoutFile=layout.inf
DriverVer=10/15/1999,5.0.2153.1

【BluetoothServiceクラス】
package test.com;

import java.io.IOException;
// 省略
import android.util.Log;

public class BluetoothService {
// Constants that indicate the current connection state
public static final int STATE_NONE = 0; // 何もしていない
public static final int STATE_LISTEN = 1; // コネクションのリッスン
public static final int STATE_CONNECTING = 2; // outgoingコネクションの初期化
public static final int STATE_CONNECTED = 3; // コネクション成立

// Name for the SDP record when creating server sockt
private static final String NAME_SECURE = "BluetoothSecure";
// Unique UUID for this application たぶん、この辺りに問題がありそう(どのUUIDを利用しても結果は同
じ)
private static final UUID UUID_FOR_HID = UUID.fromString("4D36E978-
E325-11CE-BFC1-08002BE10318");
private static final UUID UUID_FOR_HID2 = UUID.fromString("fa87c0d0-
afac-11de-8a39-0800200c9a66");
private static final UUID UUID_FOR_HID3 =
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

// Member fields
private final BluetoothAdapter bluetoothAdapter;
private final Handler handler;
private AcceptThread acceptThread;
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private int state;

// constructor
public BluetoothService(Context context, Handler handler) {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
state = STATE_NONE;
this.handler = handler;
}

/*
* Set the current state of the connection
* @param state An integer defining the current connection state
*/
private synchronized void setState(int state) {
this.state = state;

// Give the new state to the Handler so the UI Activity can update

handler.obtainMessage(BarcodeTestForGettingDataCorrectlyActivity.MESSAGE_STATE_CHANGE,
state, -1).sendToTarget();
}

/*
* Return the current connection state.
*/
public synchronized int getState() {
return state;
}

/*
* Start the service. Specifically start AcceptThread to begin a
* session in listening (server) mode. Called by the Activity
onResume()
*/
public synchronized void start() {
// Cancel any thread attempting to make a connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}

// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}

// Start the thread to listen on a BluetoothServerSocket
if (acceptThread == null) {
acceptThread = new AcceptThread();
acceptThread.start();
}
setState(STATE_LISTEN);

}

/*
* Start the ConnectThread to initiate a connection to a remote
device.
* @param bluetoothDevice The BluetootheDevice to connect
*/
public synchronized void connect(BluetoothDevice bluetoothDevice) {
// Cancel any thread attempting to make a connection
if (state == STATE_CONNECTING) {
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
}

// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}

// Start the thread to connect with the given device
connectThread = new ConnectThread(bluetoothDevice);
connectThread.start();
setState(STATE_CONNECTING);
}

/*
* Start the ConnectedThread to begin managing a Bluetooth connection
* @param bluetoothSocket The BluetoothSocket on which the connection
was made
* @param bluetoothDevice The BluetoothDevice that has been connected
*/
public synchronized void connected(BluetoothSocket bluetoothSocket,
BluetoothDevice bluetoothDevice) {
// Cancel the thread that completed the connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}

// Cancel any thread currently running a connection
if (connectThread != null) {
connectedThread.cancel();
connectedThread = null;
}

// Cancel the accept thread because we only want to connect to one
device
if (acceptThread != null) {
acceptThread = null;
}

// Start the thread to manage the connection and perform
transmissions
connectedThread = new ConnectedThread(bluetoothSocket);
connectedThread.start();

// Send the name of the connected device back to the UI Activity
Message message =
handler.obtainMessage(BarcodeTestForGettingDataCorrectlyActivity.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();

bundle.putString(BarcodeTestForGettingDataCorrectlyActivity.DEVICE_NAME,
bluetoothDevice.getName());
message.setData(bundle);
handler.sendMessage(message);

setState(STATE_CONNECTED);
}

/*
* Stop all threads
*/
public synchronized void stop() {
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
if (acceptThread != null) {
acceptThread.cancel();
acceptThread = null;
}
setState(STATE_NONE);
}

/*
* Indicate that the connection attempt failed and notify the UI
Activity
*/
private void connectionFailed() {
setState(STATE_LISTEN);

// Send a failure message back to the Activity
Message message =
handler.obtainMessage(BarcodeTestForGettingDataCorrectlyActivity.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BarcodeTestForGettingDataCorrectlyActivity.TOAST,
"Unable to connect device");
message.setData(bundle);
handler.sendMessage(message);
}

/*
* indicate that the connection was lost and notify the UI Activity.
*/
private void connectionLost() {
setState(STATE_LISTEN);

// Send a failure message back to the Activity
Message message =
handler.obtainMessage(BarcodeTestForGettingDataCorrectlyActivity.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BarcodeTestForGettingDataCorrectlyActivity.TOAST,
"Device connection was lost");
message.setData(bundle);
handler.sendMessage(message);
}

/*
* This thread runs while listening for incoming connections.
* It behaves like a server-side client.
* It runs until a connection is accepted (or until cancelled).
*/
private class AcceptThread extends Thread {
// The local server socket
private final BluetoothServerSocket bluetoothServerSocket;

// constructor
public AcceptThread() {
BluetoothServerSocket bluetoothServerSocketTmp = null;

// Create a new listening server socket
try {
// ここでUUID_FOR_HID2やUUID_FOR_HID3をしても、デ
バックの結果は同じ
bluetoothServerSocketTmp =
bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,
UUID_FOR_HID);
} catch (IOException ioException) {
}
bluetoothServerSocket = bluetoothServerSocketTmp;
}

public void run() {
setName("AcceptThread");
BluetoothSocket bluetoothSocket = null;

// Listen to the server socket if we're not connected
while (state != STATE_CONNECTED) {
try {
// This is a blocking call
// and will only return on a successful connection or an
exception
bluetoothSocket = bluetoothServerSocket.accept();
} catch (IOException ioException) {
break;
}

// If a connection was accepted
if (bluetoothSocket != null) {
synchronized (BluetoothService.this) {
switch (state) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(bluetoothSocket, bluetoothSocket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
bluetoothSocket.close();
} catch (IOException ioException) {
}
break;
}
}
}
}
}

public void cancel() {
try {
bluetoothServerSocket.close();
} catch (IOException e) {
}
}
}

/**
* This thread runs while attempting to make an outgoitng connection
with a device.
* It runs straight through; the connection either succeeds or fails.
*/
private class ConnectThread extends Thread {
private final BluetoothSocket bluetoothSocket;
private final BluetoothDevice bluetoothDevice;

public ConnectThread(BluetoothDevice bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
BluetoothSocket bluetoothSocketTmp = null;

// Get a BluetoothSockt for a connection with the given
BluetoothDevice
try {
// デバックモードでは、ここのUUIDには到達しない
bluetoothSocketTmp =
bluetoothDevice.createRfcommSocketToServiceRecord(UUID_FOR_HID);
} catch (IOException ioException) {
}
bluetoothSocket = bluetoothSocketTmp;
}

public void run() {
setName("ConnectThread");

// Always cancel discovery because it will slow down a connection
bluetoothAdapter.cancelDiscovery();

// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a successful
connection or an exception
bluetoothSocket.connect();
} catch (IOException ioException) {
connectionFailed();
// Close the socket
try {
bluetoothSocket.close();
} catch (IOException ioException2) {
}
// Start the service over to restart listening mode
BluetoothService.this.start();
return;
}

// Reset the ConnectThread because we're done
synchronized (BluetoothService.this) {
connectThread = null;
}

// Start the connected thread
connected(bluetoothSocket, bluetoothDevice);
}

public void cancel() {
try {
bluetoothSocket.close();
} catch (IOException ioException) {
}
}
}

/*
* This thread runs during a connection with a remote device.
* It handles all incoming transmissions.
*/
private class ConnectedThread extends Thread {
private final BluetoothSocket bluetoothSocket;
private final InputStream inputStream;

public ConnectedThread(BluetoothSocket bluetoothSocket) {
this.bluetoothSocket = bluetoothSocket;
InputStream inputStreamTmp = null;

// Get the BluetoothSocket input stream
try {
inputStreamTmp = bluetoothSocket.getInputStream();
} catch (IOException ioException) {
}

this.inputStream = inputStreamTmp;
}

public void run() {
byte[] bufferByte = new byte[1024];
int bytes;

// Keep listening to the InputStream while connected
while (true) {
try {
// Read from the InputStream
bytes = inputStream.read(bufferByte);

// Send the obtained bytes to the UI Activity

handler.obtainMessage(BarcodeTestForGettingDataCorrectlyActivity.MESSAGE_READ,
bytes, -1, bufferByte)
.sendToTarget();
} catch (IOException ioException) {
connectionLost();
break;
}
}
}

public void cancel() {
try {
bluetoothSocket.close();
} catch (IOException ioException) {
}
}
}
}

【TestForGettingDataCorrectlyActivityクラス】
package test.com;

import android.app.Activity;
// インポート宣言省略
import android.widget.Toast;

public class BarcodeTestForGettingDataCorrectlyActivity extends
Activity {
private TextView textView1;
private TextView textView2;
private Button button1;
// フォーカスが当たっていれば、このEditTextにそのままバーコードリーダの値が読み込まれてしまう。UUIDは無関係
private EditText editText1;

// BluetoothService Handlerから送られてきたメッセージタイプ
public static final int MESSAGE_STATE_CHANGE = 1;
public static final int MESSAGE_READ = 2;
public static final int MESSAGE_WRITE = 3;
public static final int MESSAGE_DEVICE_NAME = 4;
public static final int MESSAGE_TOAST = 5;

// BluetoothService Handlerから受け取ったキー・ネーム
public static final String DEVICE_NAME = "device_name";
public static final String TOAST = "toast";

private static final int REQUEST_ENABLE_BT = 2;

// 接続された機器名
private String connectedDeviceName = null;
// Local Bluetooth adapter
private BluetoothAdapter bluetoothAdapter = null;
// Member object for the service
private BluetoothService bluetoothService = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setViews();

// BluetoothAdapterの取得と有効化
// Get local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// bluetoothAdapterがnullであれば、bluetoothはサポートされていない
if (bluetoothAdapter == null) {
Toast.makeText(this, "Bluetooth is not available",
Toast.LENGTH_LONG).show();
finish();
return;
}
}

@Override
public void onStart() {
super.onStart();
// Bluetoothがonでない場合、使用可能なようにリクエストする
// setupReceive() will then be called during onActivityResult
if (bluetoothAdapter.isEnabled() == false) {
Toast.makeText(this, "Bluetooth is not available. reason:ブルートゥース
無効", Toast.LENGTH_LONG).show();
Intent enableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
// Otherwise, setup the chat session
} else {
if (bluetoothService == null) {
setupReceive();
}
}
}

@Override
public synchronized void onResume() {
super.onResume();
// Performing this check in onResume() covers the case in which
Bluetooth was
// not enabled during onStart(), so we were paused to enable
it...
// onResume() will be called when ACTION_REQUEST_ENABLE activity
returns.
if (bluetoothService != null) {
// Only if the state is STATE_NONE, do we know that we haven't
started already
if (bluetoothService.getState() == BluetoothService.STATE_NONE)
{
// Start the Bluetooth services
bluetoothService.start();
}
}
}

private void setupReceive() {
// Initialize the BluetoothService to perform bluetooth
connections
bluetoothService = new BluetoothService(this, this.handler);
}

@Override
public synchronized void onPause() {
super.onPause();
}

@Override
public void onStop() {
super.onStop();
}

@Override
public void onDestroy() {
super.onDestroy();
// Stop the Bluetooth services
if (bluetoothService != null) {
bluetoothService.stop();
}
}

// The Handler that gets information back from the BluetoothService
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_STATE_CHANGE:
switch (message.arg1) {
case BluetoothService.STATE_CONNECTED:
textView1.setText("接続されているデバイス: ");
textView1.append(connectedDeviceName);
textView2.setText("");
break;
case BluetoothService.STATE_CONNECTING:
textView1.setText("接続中");
break;
case BluetoothService.STATE_LISTEN:
case BluetoothService.STATE_NONE:
// デバックモードでは、ここで、agr1が1を取得し、終了す

textView1.setText("接続されていません。arg1:" + message.arg1);
break;
}
break;
case MESSAGE_READ:
byte[] bufferByte = (byte[]) message.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(bufferByte, 0, message.arg1);
textView2.setText(readMessage);
break;
case MESSAGE_DEVICE_NAME:
// save the connected device's name
connectedDeviceName =
message.getData().getString(DEVICE_NAME);
Toast.makeText(getApplicationContext(), "Connected to " +
connectedDeviceName, Toast.LENGTH_LONG).show();
break;
case MESSAGE_TOAST:
Toast.makeText(getApplicationContext(),
message.getData().getString(TOAST), Toast.LENGTH_LONG).show();
break;
}
}
};

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}

public void onActivityResult(int requestCode, int resultCode, Intent
data) {
switch (requestCode) {
// When the request to enable Bluetooth retuerns
case REQUEST_ENABLE_BT:
if (resultCode == Activity.RESULT_OK) {
// Bluetooth is now enabled, so set up a chat session
setupReceive();
} else {
// User did not enable Bluetooth or an error occured
Log.d(TAG, "Bluetooth not enabled");
Toast.makeText(this, "Bluetooth not enabled, leaving.",
Toast.LENGTH_LONG).show();
finish();
}
}
}

private void setViews() {
textView1 = (TextView) findViewById(R.id.textView1);
textView2 = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
editText1 = (EditText) findViewById(R.id.editText1);
}
}

Satoshi Sakamoto

unread,
Jan 23, 2012, 5:11:28 AM1/23/12
to android-g...@googlegroups.com
初めまして、坂本と申します。

「BluetoothChat」は「Bluetooth SPP」用のサンプルコードなので
デバイス検索やペアリングの部分は大枠では使えても
接続・データ通信部分は「Bluetooth SPP」用のソケット通信となっているので
そのままでは使えないのではないでしょうか。

esmasuiさんのブログにある
「BT接続できるSimeji」の記事の
http://d.hatena.ne.jp/esmasui/20100112/1263247514
■BimejiHID
を参考にされてはいかがでしょうか?
※apkの他にプログラム・ソース・コードも公開されていました。
 プロジェクト「BluetoothHIDKeyboard2」がそれに当たると思います。

動作確認やソースの中身などを詳細に見ておらず
推測の域を出ない助言・内容となりますが
何かのお役に立てば。

2012年1月23日11:35 patience <patienc...@gmail.com>:

> --
> このメールは Google グループのグループ「日本Androidの会」の登録者に送られています。
> このグループに投稿するには、android-g...@googlegroups.com にメールを送信してください。
> このグループから退会するには、android-group-j...@googlegroups.com にメールを送信してください。
> 詳細については、http://groups.google.com/group/android-group-japan?hl=ja からこのグループにアクセスしてください。
>

androkun

unread,
Jan 23, 2012, 8:56:36 AM1/23/12
to 日本Androidの会
bluetooth(HID方式)で接続したバーコードリーダの場合、OSが処理するのは正しい動作です
その場合、ユーザレベルでは、bluetoothへのアクセスはできなくなります。

一方、HID接続の場合、キーボードと等価になりますので、バーコードリーダーが読み取った文字を
activityはキーイベントとして1文字づつ受信できるので、加工も自由に扱えるかと思います。

以上

On 1月23日, 午前11:35, patience <patience.1w...@gmail.com> wrote:
> お世話になります。Android初心者です。現在、bluetooth(HID方式)で接続したバーコードリーダからのデータを読み取るための試験的
> なクラスを作成しています。android-sdk に付属している「BluetoothChat」を元に、必要なソースコードだけを拾い上げてのテス
> トです。参考にしたのは、下記の2つのサイトになります。http://www.bright-sys.co.jp/blog/android-using-bluetooth-spp/http://d.hatena.ne.jp/hdk_embedded/20110206/1297012049
> ...
>
> もっと読む ≫

ohisamallc

unread,
Jan 23, 2012, 5:46:27 PM1/23/12
to android-g...@googlegroups.com
山形のohisamaです。
山形は、日が照ってます。
がんばろう、東北。

ハードが使えるか、以下の3点確認願います。
①bluetoothプロファイルの確認
  hid--無理(セキュリティの理由から開放できないはず)
  sppがあること。
②登録されていること。
  ペアリングです。
  なんとかして、ペアリングして下さい。
③bluetooth rfcomm terminalで接続を確認する。
  ペアリングのリストでます。タップする。
  自分のアプリです。

patience

unread,
Jan 24, 2012, 2:47:49 AM1/24/12
to 日本Androidの会
お世話になります。
皆様、ご回答ありがとうございます。

1.
実は、androkun様が提示なさっている方法については、最初に試していて、
ただその場合、バーコードリーダ側から送られてくるデータ末尾のターミネータ(改行コード)にOSが反応して、
直下にある無関係なボタンを押してしまいます。

さりとて、バーコードリーダ側で、ターミネータは付けない設定にすると、アプリケーション側で終了のタイミングを取得できません。
やや強引に、TextWatcherの継承クラスでEditTextのイベントを取得し、
そこから別のThread継承クラスを呼び出して終了タイミングを監視しようとしても、
TextWatcherとの関係かと思いますが、実行時エラーが発生してしまいます。

EditTextに入力される直前に、入力データ(特に改行コード)を取得することができれば、万事解決なわけですが、
EditTextのイベントとして、そういう方法はありますでしょうか?

2.
BimejiHIDのソースはまだ読みきれておりませんが、SPPについては、
バーコードリーダ側をSPPの設定にして、先に掲載したコードを試してみました。
事前に、OS側でペアリングまでは実施した上で、実行いたしましたが、
コネクトされないようです。


On Jan 23, 11:35 am, patience <patience.1w...@gmail.com> wrote:
> お世話になります。Android初心者です。現在、bluetooth(HID方式)で接続したバーコードリーダからのデータを読み取るための試験的
> なクラスを作成しています。android-sdk に付属している「BluetoothChat」を元に、必要なソースコードだけを拾い上げてのテス
> トです。参考にしたのは、下記の2つのサイトになります。http://www.bright-sys.co.jp/blog/android-using-bluetooth-spp/http://d.hatena.ne.jp/hdk_embedded/20110206/1297012049
> ...
>
> read more ≫

androkun

unread,
Jan 24, 2012, 6:34:11 AM1/24/12
to 日本Androidの会

dispatchKeyEventでキー入力をフックできます。
これで対応できるかと思います。


On 1月24日, 午後4:47, patience <patience.1w...@gmail.com> wrote:
> お世話になります。
> 皆様、ご回答ありがとうございます。
>
> 1.
> 実は、androkun様が提示なさっている方法については、最初に試していて、
> ただその場合、バーコードリーダ側から送られてくるデータ末尾のターミネータ(改行コード)にOSが反応して、
> 直下にある無関係なボタンを押してしまいます。
>
> さりとて、バーコードリーダ側で、ターミネータは付けない設定にすると、アプリケーション側で終了のタイミングを取得できません。
> やや強引に、TextWatcherの継承クラスでEditTextのイベントを取得し、
> そこから別のThread継承クラスを呼び出して終了タイミングを監視しようとしても、
> TextWatcherとの関係かと思いますが、実行時エラーが発生してしまいます。
>
> EditTextに入力される直前に、入力データ(特に改行コード)を取得することができれば、万事解決なわけですが、
> EditTextのイベントとして、そういう方法はありますでしょうか?
>
> 2.
> BimejiHIDのソースはまだ読みきれておりませんが、SPPについては、
> バーコードリーダ側をSPPの設定にして、先に掲載したコードを試してみました。
> 事前に、OS側でペアリングまでは実施した上で、実行いたしましたが、
> コネクトされないようです。
>
> On Jan 23, 11:35 am, patience <patience.1w...@gmail.com> wrote:
>
>
>
>
>
>
>
> > お世話になります。Android初心者です。現在、bluetooth(HID方式)で接続したバーコードリーダからのデータを読み取るための試験的
> > なクラスを作成しています。android-sdk に付属している「BluetoothChat」を元に、必要なソースコードだけを拾い上げてのテス
> > トです。参考にしたのは、下記の2つのサイトになります。http://www.bright-sys.co.jp/blog/android-using-bluetooth-spp/http://d...
> ...
>
> もっと読む ≫

patience

unread,
Jan 24, 2012, 8:59:48 PM1/24/12
to 日本Androidの会
お世話になります。
androkun様、ご回答ありがとうございました。

dispatchKeyEventで、完全に思うように動かすことができました。
こんなに簡単に実装できるものとは思ってもいませんでした。

ありがとうございました。
> ...
>
> もっと読む ≫
Reply all
Reply to author
Forward
0 new messages