728x90

Notification 설정에서 저는 주로 아래와 같은 코드처럼 Notification bar를 눌렀을 때 액티비티를 불러오는 식으로 사용합니다.

detectPendingIntent 
    = PendingIntent.getActivity(getApplicationContext(),
                                0, new Intent(getApplicationContext(),
                                MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(detectPendingIntent);

하지만 이 코드를 사용하니 MainActivity에서 소켓통신 중이었는데 intent로 Activity를 불러오니 onCreate를 거치게 되어서 새로 불러온 Activity는 통신이 안되고 원래의 Activity에만 통신이 되었습니다.

 

그래서 구글링을 통해 알아낸 방법은 moveTaskToFront를 이용하는 것입니다.

우선 Notification 설정입니다. 

Intent intent = new Intent(getApplicationContext(), notificationBroadcast.class);
alarmPendingIntent 
     = PendingIntent.getBroadcast(getApplicationContext(),
                                  0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(alarmPendingIntent);

그리고 수신할 BroadcastReceiver 클래스를 하나 만들어줍니다.

public class notificationBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(Integer.MAX_VALUE);
        if (!tasks.isEmpty()) {
            int tasksSize = tasks.size();
            for (int i = 0; i < tasksSize; i++) {
                ActivityManager.RunningTaskInfo taskinfo = tasks.get(i);
                if (taskinfo.topActivity.getPackageName().equals(
                                          context.getApplicationContext().getPackageName())) {
                    am.moveTaskToFront(taskinfo.id, 0);
                }
            }
        }
    }
}

마지막으로 AndroidManifest에 아래 코드를 추가해줍니다.

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

<application
	....
    <receiver android:name=".notificationBroadcast"/>
	....
</application>

이제 어플을 실행시켜보면 Notification bar을 클릭했을때 최근 실행 목록에서 어플을 실행하는 것처럼 실행중인 어플을 불러오게 됩니다.

 

참고자료

- https://pppdwer.tistory.com/entry/Notify-%ED%84%B0%EC%B9%98%EC%8B%9C-%ED%98%84%EC%9E%AC-%EC%8B%A4%ED%96%89%EC%A4%91-%ED%9E%88%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0

- https://abyser.tistory.com/80

 

728x90
728x90

이 포스팅은 앞서 작성한 라즈베리파이 Qt가 서버이고 어플이 클라이언트로 구성되어 있습니다.

https://1d1cblog.tistory.com/39

 

라즈베리파이 Qt Tcp 소켓통신하기(서버)

#ifndef DIALOG_H #define DIALOG_H #include #include #include "processthread.h" namespace Ui { class Dialog; } class Dialog : public QDialog { Q_OBJECT public: explicit Dialog(Q..

1d1cblog.tistory.com

먼저 AndroidManifest.xml에 아래 코드를 추가해줍니다.

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

그 후 액티비티에 InputStream과 OutputStream, Socket을 선언해줍니다. 그리고 서버 ip, port를 설정합니다. 

여기서 Thread를 사용하는 이유는 반응하지 않는 UI가 생성되는 것을 방지하려면 UI 스레드에서 네트워크 작업을 수행해서는 안 됩니다. 기본적으로 Android 3.0(API 레벨 11) 이상에서는 기본 UI 스레드를 제외한 스레드에서 네트워크 작업을 수행해야 합니다. 그렇지 않으면 NetworkOnMainThreadException이 발생합니다. 

public class MainActivity extends AppCompatActivity {

    final String TAG = "TAG+MainActivity";

    public InputStream dataInputStream;
    public OutputStream dataOutputStream;
    private Socket socket;
    private String ip = "121.153.150.157";
    private int port = 9999;

    Thread thread;
    ImageButton lightButton;
    
 	...
}

수신하는 쓰레드입니다. 

thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    socket = new Socket(ip,port);
                    dataInputStream = socket.getInputStream();
                    dataOutputStream = socket.getOutputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                byte[] buffer = new byte[1024];
                int bytes;
                Log.d(TAG, "수신 시작");
                while(true) {
                    try {
                        Log.d(TAG, "수신 대기");
                        bytes = dataInputStream.read(buffer);
                        Log.d(TAG, "byte = " + bytes);
                        String tmp = new String(buffer, 0, bytes);
                        Log.d(TAG,tmp);
                        ...
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

다음으로 송신부분입니다. turnOn()이라는 함수 안에 write함수가 있습니다. 

public void turnOn() throws IOException {
        byte[] inst = "On".getBytes();
        dataOutputStream.write(inst);
}

turnOn()함수를 별도의 쓰레드를 생성해서 사용해줬습니다.

lightButton.setOnClickListener(new View.OnClickListener() {    
    @Override
    public void onClick(DialogInterface dialog, int which) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                        turnOn();
                } catch (IOException e) {
                    e.printStackTrace();
            	}
           }
    }).start();
 }
728x90
728x90

TimerPickerDialog를 자바 코드에서 사용하는 방법입니다.

 

public class MainActivity extends AppCompatActivity {
    ImageButton alarmButton;
    int alarmHour=0, alarmMinute=0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        alarmButton = (ImageButton) findViewById(R.id.alarmButton);
        
        alarmButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TimePickerDialog timePickerDialog = new TimePickerDialog
                (MainActivity.this, new TimePickerDialog.OnTimeSetListener() {
                    @Override
                    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
						
                    }
                },alarmHour, alarmMinute, false);
                timePickerDialog.show();
            }
        });
}

alarmButton이라는 버튼을 눌렀을 때 TimePickerDialog가 생성되도록 작성했습니다. 

public void onTimeSet의 매개변수인 hourOfDay, miute은 TimePickerDialog에서 선택한 시간을 저장하는 변수입니다.

 

TimePickerDialog를 생성할 때의 매개변수인 alarmHour, alarmMinute은 TimePickerDialog를 띄웠을 때 시간을 설정해 줍니다.

현재는 alarmHour와 alarmMinute이 0이니 아래처럼 나오게 됩니다.

여기서 alarmHour을 2로 세팅하면 TimePickerDialog를 띄웠을 때 아래처럼 나오게 됩니다.

 

false로 설정된 마지막 매개변수는 24시간 모드를 사용할 것인지에 대한 설정인데 false 값을 넣어줬다면 위처럼 AM, PM을 구분하게 나오고 true로 값을 넣어주면 아래처럼 나옵니다.

마지막으로 아래처럼 TimePickerDialog의 색과 종류를 바꿀 수 있습니다. 

alarmButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TimePickerDialog timePickerDialog 
                = new TimePickerDialog(MainActivity.this, 
                android.R.style.Theme_Holo_Light_Dialog,new TimePickerDialog.OnTimeSetListener() {
                    @Override
                    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {

                    }
                },alarmHour, alarmMinute, false);
                timePickerDialog.show();
                Log.d(TAG, String.valueOf(alarmHour));
            }

        });

android.R.style.Theme_Holo_Light_Dialog
android.R.style.Theme
TimePickerDialog.THEME_DEVICE_DEFAULT_DARK

728x90
728x90

먼저 헤더파일입니다. 헤더파일에서는 데이터를 수집하는 함수를 처리하기 위한 쓰레드, QTcpServer, QTcpSocket이 선언되어 있습니다. 

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QtNetwork>
#include "processthread.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);
    void tcpInit();		// tcp 소켓 통신 서버 오픈 및 초기화
    ~Dialog();
private slots:
    void newConnection();				// 서버에 소켓이 접속했을 때 실행
    void readData();					// 데이터 수신 함수
    void disConnected();				// 소켓 연결 해제 시 실행
    void sendValue(int temp, int hum, int dust, int human);	// 수집된 센서 데이터 어플리케이션에 송신
private:
    Ui::Dialog *ui;
    processThread* pthread;			// 센서 데이터 수집 프로세스 쓰레드
    
    QTcpServer* tcpServer;			// Tcp 서버
    QTcpSocket* client;				// Tcp socket

    int con; // server 접속한 소켓 수
};

#endif // DIALOG_H

다음으로 ui 생성자 코드입니다. tcpInit()이라는 소켓 서버를 오픈해주고 초기화주는 함수입니다. 아래 쓰레드는 데이터를 수집하는 함수는 쓰레드 처리하기 위해 만들었습니다.

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    tcpInit();	// 소켓 서버 오픈 및 초기화
    
    pthread = new processThread(this);	// 센서 데이터 수집 쓰레드 동적 할당
    pthread->start();	// 쓰레드 run
}

tcpInit() 함수는 listen(ip, port) 함수를 통해 tcp Server를 오픈합니다. 그 후 newConnection() 시그널과 newConnection SLOT을 연결하여 Server에 새로운 소켓이 접속하는것을 감지하면 SLOT의 newConnection()이 실행됩니다.

void Dialog::tcpInit()
{
    QHostAddress hostAddress;
    QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
    for (int i = 0 ; i < ipAddressesList.size() ; ++i)
    {
        if(ipAddressesList.at(i) != QHostAddress::LocalHost && ipAddressesList.at(i).toIPv4Address())
        {
            hostAddress = ipAddressesList.at(i);
            break;
        }
    }
    if(hostAddress.toString().isEmpty())    hostAddress = QHostAddress(QHostAddress::LocalHost);
    tcpServer = new QTcpServer(this);
    if(!tcpServer->listen(hostAddress, 9999))	// 서버 오픈 ip : hostaddress, port : 9999
    {
        std::cout << "connect Failed\n";
        close();
    }
    else std::cout << "Tcp Server Open\n";
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));	
    // 서버에 소켓이 접속했을 때 newConnection() 실행
} // tcp 소켓 통신 서버 오픈 및 초기화

newConnection() 함수는 새로운 클라이언트 접속 시 실행됩니다. tcpSocket에 클라이언트의 정보를 nextPendingConnection()을 이용해 넘겨줍니다. 다음으로 소켓에 readyRead() 시그널과 disconnected() 시그널을 연결해줍니다.

그리고 위 코드는 서버에 한 개의 클라이언트만 접속하기 위한 코드입니다. disconnect newConnection()을 하게 되면 서버에 클라이언트가 접속하게 되도 newConnection()함수가 실행되지 않습니다.

void Dialog::newConnection()
{
    std::cout << "connected" << con << std::endl;
    if(con == 0)
    {
        client = tcpServer->nextPendingConnection();
        con++;
        connect(pthread, SIGNAL(setValue(int, int, int, int)), this, SLOT(sendValue(int, int, int, int)));    
        // 수집된 센서 데이터 송신
        connect(client,SIGNAL(readyRead()),this,SLOT(readData()));
        connect(client,SIGNAL(disconnected()),this,SLOT(disConnected()));
        disconnect(tcpServer, SIGNAL(newConnection()),this,SLOT(newConnection()));
    }
} // 서버에 소켓이 접속했을 때 실행

클라이언트에서 데이터를 보내주게 되면 readyRead 시그널이 발생해 readData() 함수가 실행됩니다. 데이터는 QByteArray 형태로 수신하게 됩니다.

void Dialog::readData()
{
    std::cout << "readData\n";
    if(client->bytesAvailable()>=0)
    {
        QByteArray data = client->readAll();
    }
} // 데이터 수신 함수

클라이언트가 접속을 끊게 된다면 disconnected 시그널이 발생해 disConnected() 함수가 실행됩니다. disConnected()함수에서는 클라이언트 소켓을 닫아주게 됩니다.

void Dialog::disConnected()
{
    std::cout << "disconnected\n";
    client->close();
    --con;
    disconnect(pthread, SIGNAL(setValue(int, int, int, int)), this, SLOT(sendValue(int, int, int, int)));    
    // 수집된 센서 데이터 송신
    if(con==0)
        connect(tcpServer,SIGNAL(newConnection()),this,SLOT(newConnection()));
} // 소켓 연결 해제

마지막으로 데이터를 보내는 함수입니다. QByteArray 형태의 변수를 socket에 write해 보내는 방식입니다. 

void Dialog::sendValue(int temp, int hum, int dust,int human)
{
    std::cout << "sendData\n";
    QByteArray tempbyte = QByteArray::number(temp);
    QByteArray humbyte = QByteArray::number(hum);
    QByteArray dustbyte = QByteArray::number(dust);
    QByteArray humanbyte = QByteArray::number(human);
    client->write(tempbyte+","+humbyte+","+dustbyte+","+humanbyte);
} // 수집된 센서 데이터 어플리케이션에 송신

 

728x90
728x90

GUI 프로그래밍한 프로그램을 부팅 시에 자동 실행해보기 위해 /etc/rc.local 파일에도 등록해보고 /etc/profile.d/안에 넣어보기도 했지만 잘 되지 않았다. 부팅이 다 된 후에 자동실행하는 법을 찾아봤다.

 

먼저 프로그램을 실행할 shell 파일을 만들어주자. shell 파일 안에는 프로그램을 실행하는 명령어가 들어있으면 된다.

 

그 후 cd /etc/xdg/lxsession/LXDE-pi/로 들어가 autostart 파일을 수정한다.

autostart 파일 가장 아래에 본인이 실행하고자 하는 파일을 lxterminal -e 명령어와 함께 작성한다.

그 후 재부팅을 하면 부팅, 로그인이 끝나고 화면 로딩이 끝난 이후에 설정한 쉘 스크립트가 실행된다.

 

자료출처 : https://frogbam07.tistory.com/1

 

라즈베리파이 프로그램자동시작

라즈베리파이에서 재부팅시에 프로그램을 자동시작 하는 방법이다. 이 방법의 장점은 startx까지 모두 실행한후에 실행하므로 crontab으로 불가능한 GUI프로그램들도 모두 잘 작동한다는 점이다. 터미널에서 다음..

frogbam07.tistory.com

 

728x90
728x90

먼저 QPixmap 변수를 만들어 사진 경로를 지정해 줍니다. 저는 온도, 습도, 미세먼지 사진을 넣기 위해 3개의 QPixmap 변수를 생성했습니다. QPixmap을 사용하기 위해서는 QPixmap 헤더파일이 선언되어 있어야 합니다.

QPixmap tempIconPicture, humIconPicture, dustIconPicture;

tempIconPicture.load("/home/pi/Github/DJU_OSP/Raspberry/temperature.png");
humIconPicture.load("/home/pi/Github/DJU_OSP/Raspberry/hum.png");
dustIconPicture.load("/home/pi/Github/DJU_OSP/Raspberry/dust.png");

다음으로 경로를 지정한 QPixmap 변수를 setPixmap 함수를 이용해 원하는 QLabel에 넣어줍니다. 

ui->tempIcon->setPixmap(tempIconPicture.scaled(128,128,Qt::KeepAspectRatio));
ui->humIcon->setPixmap(humIconPicture.scaled(128,128,Qt::KeepAspectRatio));
ui->dustIcon->setPixmap(dustIconPicture.scaled(128,128,Qt::KeepAspectRatio));

scaled에 원하는 사이즈를 넣어주거나 생성한 라벨의 크기에 맞추려면 ui->label->width(), ui->label->height()를 이용해서 크기를 넣어줍니다.

 

Qt::KeepAspectRatio는 주어진 Label의 종횡비에 사진을 맞춰줍니다.

출처 : https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/core/Qt.AspectRatioMode.html

마지막으로 사진을 가운데 정렬하려면 아래코드를 사용하면 됩니다.

ui->tempIcon->setAlignment(Qt::AlignCenter);
ui->humIcon->setAlignment(Qt::AlignCenter);
ui->dustIcon->setAlignment(Qt::AlignCenter);

마지막으로 실행화면입니다.

텍스트 위에 아이콘 사진들이 추가 되었습니다. 아래 텍스트를 포함한 코드는 아래 링크를 들어가시면 보실 수 있습니다.

https://github.com/psy1064/DJU_OSP/blob/master/Raspberry/dialog.h

https://github.com/psy1064/DJU_OSP/blob/master/Raspberry/dialog.cpp

 

psy1064/DJU_OSP

대전대학교 오픈소스프로젝트. Contribute to psy1064/DJU_OSP development by creating an account on GitHub.

github.com

참고자료 

- https://m.blog.naver.com/PostView.nhn?blogId=hextrial&logNo=221109232458&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

[Qt] Label을 이용한 이미지 display

Display widgets에 있는 Label을 이용하여 GUI 상에 이미지를 보여줄 수 있다. 먼저 위와 같이 label...

blog.naver.com

 

728x90
728x90

https://1d1cblog.tistory.com/17

 

라즈베리파이 VNC 이용해서 원격 접속하기

1. VNC란? VNC(Virtual Network Computing, 가상 네트워크 컴퓨팅)는 컴퓨터 환경에서 RFB 프로토콜을 이용하여 원격으로 다른 컴퓨터를 제어하는 그래픽 데스크톱 공유 시스템이다. 라즈베리파이에 모니터를 연결..

1d1cblog.tistory.com

https://1d1cblog.tistory.com/13?category=806245

 

라즈베리파이 외부에서 접속하기

1. 포트포워딩의 필요성 라즈베리파이를 작업 공간이 아닌 다른 곳에 나가서 작업을 하려면 여러가지 제한사항도 많고 귀찮다. 그러다 보면 '파이는 작업 공간에 두고 노트북만 들고 나가 작업 공간에 있는 파이에..

1d1cblog.tistory.com

포트포워딩으로 외부에서 VNC로 접속할 포트를 열어줍니다. 포트포워딩 하는 방법은 위 링크를 보시고 하시면 됩니다.

 

내부 아이피에서 VNC로 접속을 해 준 다음 라즈베리파이의 우측 상단 VNC 아이콘을 클릭해 줍니다.

클릭하면 위와 같은 창이 뜨게 되는데 다시 우측 상단의 세 줄 아이콘을 클릭 후 Option을 선택합니다. 그 후 Connections 탭을 선택하면 VNC에 연결하는 포트가 나오게 됩니다. 

이제 이 포트 번호를 내부 포트로 넣고 외부 포트에는 본인이 원하는 포트 번호를 넣어줍니다.

적용 후 VNC에서 상단에 IP주소::포트번호를 입력하면 외부에서도 접속을 할 수 있습니다.

728x90
728x90

먼저 터미널에서 sudo apt-get install ibus ibus-hangul을 실행합니다.

설치가 완료되었다면 sudo raspi-config 명령어를 이용해 아래 화면으로 진입합니다.

위 화면에서 Localisation Options > Change keyboard Layout 을 눌러줍니다.

터미널 창에서 Reloading keymap. This may take a short while 메세지만 뜨고 다시 위 화면으로 돌아온다면 키보드 연결을 다시 한번 확인해 보셔야 합니다.

generic 105-key PC (intl.) > Korean > Korean - Korean (101/104 key compatible) > The default for the keyboard layout > No compose key 선택하시면 됩니다. 

마지막으로 위와 같은 창이 뜨게 되는데 ctrl + Alt + Backspace 를 눌러서 X server를 종료할 것인지 묻습니다. 

 

설정이 다 됐다면 Finish를 눌러 종료 후 재부팅을 해줍니다.

 

재부팅 후 파이의 화면에서 Preferences > IBus Preferences 를 선택해 줍니다.

IBus Preferences에서 Input method로 들어가 아래 화면에서 Add를 누르고 ... > Korean > Hangul 을 선택하고 Add를 눌러줍니다.

제대로 추가가 됐다면 아래와 같이 나오게 됩니다.

다음으로 General 탭에서 Next Input method를 설정해주어야 한다. 그 옆에 ... 을 눌러 아래와 같은 창이 뜨면 본인의 취향에 맞게 설정해줍니다. Modifiers + Key code로 설정이 되니 shift를 체크하고 key code를 스페이스 바로 설정하고 Apply 버튼을 누르게 되면 아래와 같이 설정이 됩니다.

이제 OK를 누르고 Close를 눌러 나옵니다. 이제 설정한 키를 누르게 되면 우측 상단 표시줄에 EN이 아래처럼 삼색 문양으로 나오게 됩니다. 이 문양이 됐다면 한/영키를 눌러서 영어와 한글을 혼용해서 사용할 수 있습니다.

728x90

+ Recent posts