728x90

MDB(Meterial Design for Bootstrap) 에서 sidenav 에 표시할 메뉴를 PHP 와 연동하여 자동으로 표시하는 방법을 구현했다.


먼저, 구글에서 multi level sidenav 를 검색하면 https://mdbootstrap.com/support/jquery/multi-level-in-sidenav-items/ 가 검색될 것이다.


검색된 Tree 구조는 아래와 같다.

 <li>
    <ul class="collapsible collapsible-accordion">
    <li><a class="collapsible-header waves-effect arrow-r"><i class="fa fa-hand-pointer-o"></i>메뉴1-1<i class="fa fa-angle-down rotate-icon"></i></a>
    <div class="collapsible-body">
        <ul>
            <li>
            <ul class="collapsible collapsible-accordion">
            <li><a class="collapsible-header waves-effect arrow-r"><i class="fa fa-hand-pointer-o"></i>메뉴2-1</a>
            <div class="collapsible-body">
                <ul>
                <li><a href="#" class="waves-effect">메뉴3-1</a></li>
                <li><a href="#" class="waves-effect">메뉴3-2</a></li>
                <li><a href="#" class="waves-effect">메뉴3-3</a></li>
                </ul>
            </div>
            </li>
        </ul>

        <ul>
        <li><a href="#" class="collapsible-header waves-effect">메뉴2-2</a></li>
        </ul>

    </div>
    </li>
    </ul>
</li>

<li>
    <a href="#" class="collapsible-header waves-effect">메뉴1-2</a>
</li>


이걸 가지고 PHP 와 연동된 자동 메뉴를 만들어 보자.

MDB 는 bootstrap 4 기반으로 연동된 템플릿이다.

bootstrap 4 에서는 순환구조가 매끄럽지 못한 거 같다. (내가 제대로 이해를 못했겠지만....)


테이블 구조

CREATE TABLE IF NOT EXISTS `menu_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `depth` int(3) NOT NULL DEFAULT '1',
  `name` varchar(50) NOT NULL,
  `text` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; 


Treeview 메뉴 코드

아래 코드는 https://link2me.tistory.com/1194 게시글과 비교하면서 살펴보면 도움될 것이다.

파일명 : muti_treeview.php

 <?php
if(!isset($_SESSION)) {
    session_start();
}
require_once 'path.php'; // root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass();
$res = $d->getDbresult("menu_item", "", "id,parent_id,name");
while($row = mysqli_fetch_assoc($res)){
    $sidemenu[] = $row;
}

$side_menu = bootstrap4_sideMenu($sidemenu);
echo $side_menu;

function bootstrap4_sideMenu($array,$parent_id = 0,$parents = array())
{
    global $d,$key;
    if($parent_id==0){ // sub가 있는 parent_id 값 추출
        foreach ($array as $element) {
            if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents)) {
                $parents[] = $element['parent_id'];
            }
        }
    }

    $menu_html = '';
    foreach($array as $element){
        if($element['parent_id']==$parent_id){
            $url = $element['id'].";".$element['parent_id'];            

            if(in_array($element['id'],$parents)){
                $menu_html .= '<li>';
                $menu_html .= '<ul class="collapsible collapsible-accordion">';
                $menu_html .= '<li><a class="collapsible-header waves-effect arrow-r" href="'.$url.'"><i class="fa-hand-pointer-o">';
                $menu_html .= '</i>'.$element['name'].'<i class="fas fa-angle-down rotate-icon"></i></a>';
                $menu_html .= '<div class="collapsible-body">';

            } else {
                $menu_html .= '<li>';
                $menu_html .= '<a class="collapsible-header waves-effect" href="'.$url.'">'.$element['name'].'</a>';
                $menu_html .= '</li>';
            }

            if(in_array($element['id'],$parents)){
                $menu_html .= '<ul>';
                $menu_html .= bootstrap4_sideMenu($array, $element['id'], $parents);
                $menu_html .= '</ul>';
                $menu_html .= '</div>';
                $menu_html .= '</ul>';
                $menu_html .= '</li>';
            }
           
        }
    }
    return $menu_html;
}
?>


이제 MDB 템플릿 구조에 포함된 HTML 파일구조다. jQuery 코드는 모두 생략했다.

<!DOCTYPE html>
<head>
<?php require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기 ?>
<?php require_once $g['path_config'].'config.php';?>
<?php require_once $g['path_layout'].'default/_import.head.php';?>
<?php require_once $g['path_root'].'deviceChk.php';?>
</head>

<body class="fixed-sn mdb-skin">
<header>
    <!-- Sidebar navigation -->
    <div id="slide-out" class="side-nav sn-bg-4 fixed">
        <ul class="custom-scrollbar">
            <!-- Side navigation links -->
            <?php require_once $g['path_menu'].'item/multi_treeview.php' ?>
        </ul>
        <div class="sidenav-bg mask-strong"></div>
    </div><!--/. Sidebar navigation -->
    <!-- Navbar -->
    <nav class="navbar fixed-top navbar-toggleable-md navbar-expand-lg scrolling-navbar double-nav">
        <!-- SideNav slide-out button -->
        <div class="float-left">
            <a href="#" data-activates="slide-out" class="button-collapse"><i class="fas fa-bars"></i></a>
        </div>
        <!-- Breadcrumb-->
        <div class="breadcrumb-dn mr-auto">
            <p><?=$hostName;?></p>
        </div>
        <ul class="nav navbar-nav nav-flex-icons ml-auto">
            <?php require_once $g['path_menu'].'item/item_login.php' ?>
        </ul>
    </nav>
    <!-- /.Navbar -->
</header>

<!--Main layout-->
<main>
    <div class="container-fluid text-center">
        <div class="row">
            <div class="col-md-12">
                <div class="content" id="panel_content">
                </div>
                <div id="ajaxPath" menu-path="<?php echo $g['path_help'];?>"  ></div>
            </div>
        </div>
    </div>
</main>
<!--/Main layout-->
<!-- SCRIPTS -->
<?php require_once $g['path_layout'].'default/_import.tail.php';?>
</body>
</html>


블로그 이미지

Link2Me

,
728x90

ScrollView 에 자동으로 가져온 데이터를 화면 출력하고 스크롤이 되도록 하는 샘플 코드다.

TextView 옵션을 못찾아 구글링으로 관련 코드를 찾으라 한참 삽질하고 동작 확인하고 세부 구현을 위한 기초 데이터로서는 되겠다 싶어 기초사항을 적는다.


 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_service_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="서비스 시작" />

        <Button
            android:id="@+id/btn_service_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="서비스 종료" />

        <Button
            android:id="@+id/btn_service_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="데이터 Get" />

        <Button
            android:id="@+id/btn_clear_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="화면 Clear" />

    </LinearLayout>

    <ScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:descendantFocusability="beforeDescendants"
        android:gravity="center"
        android:background="#000000">

        <TextView
            android:id="@+id/consoleText"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_margin="5dp"
            android:textIsSelectable="false"
            android:gravity="left"
            android:textColor="#FFFFFF"/>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="3">

        <EditText
            android:id="@+id/EtInput"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="3"
            android:inputType="textMultiLine"
            android:lines="2"
            android:hint="명령어를 입력하세요" />

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#00000000"
            android:text="send" />
    </LinearLayout>
</LinearLayout>


import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import static android.content.ContentValues.TAG;

public class getDataCallService extends Service {
    IBinder mBinder = new DataCallBinder();

    public getDataCallService() {
    }

    class DataCallBinder extends Binder {
        getDataCallService getService() { // 서비스 객체를 리턴
            return getDataCallService.this;
        }
    }

    public String getBroastData(){
        return "getDataCallService data\n Line Feed.";
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "서비스 onBind 에서 시작");
        return mBinder;
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "서비스 시작");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "서비스 종료");
        super.onDestroy();
    }
}

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.text.InputType;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    Context context;
    getDataCallService ms; // 서비스
    boolean isService = false; // 서비스 실행 확인
    TextView mDumpTextView;
    ScrollView mScrollView;
    private Runnable mScrollingRunnable;

    public EditText editText;
    public Button send;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 서비스와 연결되었을 때 호출되는 메서드
            getDataCallService.DataCallBinder mb = (getDataCallService.DataCallBinder) service;
            ms = mb.getService();
            isService = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isService = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        Button btnStart = (Button) findViewById(R.id.btn_service_start);
        Button btnEnd = (Button) findViewById(R.id.btn_service_end);
        Button btnCallData = (Button) findViewById(R.id.btn_service_data);
        Button btnClearData = (Button) findViewById(R.id.btn_clear_data);
        mScrollView = findViewById(R.id.scrollview);
        mDumpTextView = (TextView) findViewById(R.id.consoleText);
        editText = (EditText) findViewById(R.id.EtInput);
        send = (Button) findViewById(R.id.send);

        final SoftKeyboardDectectorView softKeyboardDecector = new SoftKeyboardDectectorView(this);
        addContentView(softKeyboardDecector, new FrameLayout.LayoutParams(-1, -1));

        softKeyboardDecector.setOnShownKeyboard(new SoftKeyboardDectectorView.OnShownKeyboardListener() {
            @Override
            public void onShowSoftKeyboard() {
                //키보드 등장할 때 채팅창 마지막 입력 내용을 바로 보이도록 처리
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 서비스 시작하기
                Intent intent = new Intent(getApplicationContext(), getDataCallService.class);
                bindService(intent,connection, Context.BIND_AUTO_CREATE);
            }
        });

        btnEnd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 서비스 종료
                unbindService(connection);
            }
        });

        btnClearData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDumpTextView.setText("");
                mScrollView.invalidate();
            }
        });

        // message send action
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!editText.getText().toString().isEmpty()) {
                    updateReceivedData(editText.getText().toString());
                    editText.setText(" ");
                }
            }
        });

        mScrollView.setVerticalScrollBarEnabled(true); // 수직방향 스크롤바 사용 가능하도록 설정
        mScrollView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false; // 이건 기능 테스트 목적으로 아직 구현이 안된 상태
            }
        });

        btnCallData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isService){
                    Toast.makeText(getApplicationContext(), "서비스가 실행중이 아닙니다.", Toast.LENGTH_SHORT).show();
                } else {
                    String data = ms.getBroastData(); // 서비스에서 가져온 데이터
                    updateReceivedData(data);
                }
            }
        });
    }

    private void updateReceivedData(String data) { // 콘솔 결과 데이터라 가정하고 테스트 함
        final String message = data + "\n";  // 실제 USB 콘솔 연결시에는 \n 은 삭제처리하는게 좋음
        mDumpTextView.append(message);

        mDumpTextView.setTextColor(Color.WHITE);
        mDumpTextView.setTextIsSelectable(false);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mDumpTextView.setElegantTextHeight(true);
        }
        mDumpTextView.setImeOptions(EditorInfo.IME_FLAG_NO_ENTER_ACTION);
        mDumpTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        mDumpTextView.setSingleLine(false);
        refreshView();
    }

    private void refreshView(){
        mScrollView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        },100);
        //mScrollView.setScrollY(View.FOCUS_DOWN);
        mScrollView.smoothScrollTo(0, mDumpTextView.getBottom());
    }
}

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

public class SoftKeyboardDectectorView extends View {
    // 출처 : http://givenjazz.tistory.com/54
    private boolean mShownKeyboard;
    private OnShownKeyboardListener mOnShownSoftKeyboard;
    private OnHiddenKeyboardListener onHiddenSoftKeyboard;

    public SoftKeyboardDectectorView(Context context) {
        this(context, null);
    }

    public SoftKeyboardDectectorView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diffHeight = (screenHeight - statusBarHeight) - h;
        if (diffHeight > 100 && !mShownKeyboard) { // 모든 키보드는 100px보다 크다고 가정
            mShownKeyboard = true;
            onShownSoftKeyboard();
        } else if (diffHeight < 100 && mShownKeyboard) {
            mShownKeyboard = false;
            onHiddenSoftKeyboard();
        }
        super.onSizeChanged(w, h, oldw, oldh);
    }

    public void onHiddenSoftKeyboard() {
        if (onHiddenSoftKeyboard != null)
            onHiddenSoftKeyboard.onHiddenSoftKeyboard();
    }

    public void onShownSoftKeyboard() {
        if (mOnShownSoftKeyboard != null)
            mOnShownSoftKeyboard.onShowSoftKeyboard();
    }

    public void setOnShownKeyboard(OnShownKeyboardListener listener) {
        mOnShownSoftKeyboard = listener;
    }

    public void setOnHiddenKeyboard(OnHiddenKeyboardListener listener) {
        onHiddenSoftKeyboard = listener;
    }

    public interface OnShownKeyboardListener {
        public void onShowSoftKeyboard();
    }

    public interface OnHiddenKeyboardListener {
        public void onHiddenSoftKeyboard();
    }

}


ScrollView 에서 TextView 값이 넘어오면 자동으로 화면이 스크롤 되도록 처리하는 것이 필요하다고 판단되어 관련 코드를 구글링하여 테스트하고 적었다.

아직 세부적으로 기능 설명은 적지 못했다.

블로그 이미지

Link2Me

,
728x90

안드로이드 Serial 통신 관련 게시글은 https://developer.android.com/guide/topics/connectivity/usb/host 에 나와 있다.


When your Android-powered device is in USB host mode, it acts as the USB host, powers the bus, and enumerates connected USB devices. USB host mode is supported in Android 3.1 and higher.

안드로이드 전력 장치가 USB host 모드일 때, USB host로서 역할을 수행하고, 버스에 전력을 공급하고, 연결된 USB 장치를 열거한다. USB host 모드는 안드로이드 3.1 이상에서 지원된다.


"android.hardware.usb" 패키지안에 USB host API들이 있다.

Class

 설 명

 UsbManager

 연결 USB 장치들을 열거하고 통신하도록 허용한다.

 UsbDevice

 연결된 USB 장치를 표시하고

 식별 정보, 인터페이스, endpoint에 접속하기 위한 메소드들을 포함한다.

 UsbInterface

 USB 장치의 인터페이스를 표시하고, 장치의 기능 셋이 정의되어 있다.

 장치는 하나 또는 이상의 통신 인터페이스를 갖고 있다.

 UsbEndpoint

 인터페이스 endpoint를 표시하며, 이 인터페이스에 대한 통신 채널이다.

 하나의 인터페이스는 하나 이상의 endpoint를 갖고 있으며,

 통상적으로 장치와 양방향통신을 위한 input, output endpint를 갖고 있다.

 UsbDeviceConnection

 장치에 연결을 표시하며, endpoint에 데이터를 전송한다.

 이 클래스는 데이터를 순방향, 역방향 동기/비동기 전송을 허용한다.

 UsbRequest

 UsbDeviceConnection을 통한 장치와의 통신을 위한 비동기 요청을 나타낸다.

 UsbConstants

 리눅스 커널의 linux/usb/ch9.h 에 정의되어져 있는 연관된 상수들이 정의되어져 있다.



How to Monitor USB Events on Android

https://github.com/yushulx/android-usb-monitor 를 받으면 Eclipse 기반으로 된 코드를 받게 된다.

이걸 Android Studio 에서 복사 및 붙여넣기 하면 된다.


Android Manifest.xml 파일 내용 수정

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

    <uses-feature android:name="android.hardware.usb.host" />

    <!-- android.hardware.usb.host 를 쓴다고 알려 줘야 된다. -->

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />

                <!-- USB Device 가 연결될때의 Action이라고 알려준다 -->
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />

                <!-- USB Device 정보가 res\xml\device_filter.xml 에 있다고  알려 준다 -->
        </activity>
    </application>

</manifest> 


device_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Application이 원하는 장치에 대해서만 통보를 받을 수 있다. -->
    <!-- 0x0403 FTDI -->
    <usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
    <usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
    <usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
    <usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
    <usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT231X -->

    <!-- 0x2341 / Arduino -->
    <usb-device vendor-id="9025" />

    <!-- 0x16C0 / 0x0483: Teensyduino  -->
    <usb-device vendor-id="5824" product-id="1155" />

    <!-- 0x10C4 / 0xEAxx: Silabs CP210x -->
    <usb-device vendor-id="4292" product-id="60000" />
    <usb-device vendor-id="4292" product-id="60016" />
    <usb-device vendor-id="4292" product-id="60032" />
    <usb-device vendor-id="4292" product-id="60033" />
    <usb-device vendor-id="4292" product-id="60048" />

    <!-- 0x1a86 / 0x7523: Qinheng CH340 -->
    <usb-device vendor-id="6790" product-id="29987" />

    <!-- 0x067B / 0x2303: Prolific PL2303 -->
    <usb-device vendor-id="1659" product-id="8963" />
    <usb-device vendor-id="1659" product-id="8964" />
    <usb-device vendor-id="1659" product-id="41216" />

    <!-- 0x0d28 / 0x0204: ARM mbed -->
    <usb-device vendor-id="3368" product-id="516" />
</resources>


까지 하고 컴파일하면 USB 케이블 장치를 자동 인식한다.

여기까지 하면 USB 장치가 인식되는 걸 확인할 수 있다.

블로그 이미지

Link2Me

,
728x90

Error: Program type already present: android.support.v4.app.BackStackRecord$Op


컴파일 하니까 에러가 발생하면서 컴파일이 안된다.


implementation fileTree(dir: 'libs', include: ['*.jar'])

하고 중복이 발생하는 부분이 있나 싶어서


dependencies {
    //implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation files('libs/d2xx.jar')
}

로 하고 컴파일을 하니까 에러가 발생하지 않고 잘 된다.





블로그 이미지

Link2Me

,
728x90

맥북에서 기본 부팅 순서를 변경하는 방법이다.


시스템 환경설정에서 시동디스크를 선택한다.


자물쇠를 눌러서 비밀번호를 입력하고 나면 선택을 변경할 수 있다.


블로그 이미지

Link2Me

,
728x90

해외 구매한 메모리를 16G로 증설을 시도했으나, 실패했다.


그래서 국내에서 4G 메모리 2개를 구입했다.

맥북이 지원하는 속도는 PC3-8500 인데 한단계 더 높은 메모리를 장착해도 문제가 없다는 얘기를 들어서 한단계 높은 메모리를 구입했다.

메모리를 꼽으니까 정상적으로 잘 동작된다.



지침에는 최대 8G로 나와있다.


최대 메모리를 초과하는 건 이 맥북 프로 2010에서는 안되나 보다.

블로그 이미지

Link2Me

,
728x90

Serial 통신 구현 검토을 위해 관련 지식을 찾아서 정리차 적어둔다.


UART(Universal Asynchronous Receiver/Transmitter) : 비동기통신
- UART는 디지털 데이터 0과 1을 어떻게 통신할 것인지에 대한 통신 규격을 정해 놓은 것

- UART에서 나오는 신호는 보통 TTL 신호레벨을 갖기 때문에 노이즈에 약하고 통신거리에 제약이 있다.

- 하드웨어가 간단해서 거의 모든 장치에서 UART를 지원하기 때문에 (9개의 핀이 연결되거나, USB가 있는 장치라면) 편리하다.

- 1:1 통신만 지원한다. 동일한 속도를 맞추고 시작해야 한다. 그렇지 않으면 데이터가 깨질 것이다. Voltage level 이 동일해야 한다.

- 시리얼 통신의 기본 통신 규격은 1바이트 단위로 반복 전송되는 형식이며 바이트들 사이의 데이터 속성 구분 정보는 UART 기본 규격에서는 정의되어 있지 않다.

- UART로 연결된 두 디바이스 간 공유 클럭이 없으므로, 데이터를 올바르게 디코딩하기 위한 동일한 Baud Rate를 구성하고 일치시켜야 한다.

- 프로토콜에 9600 Baud Rate라고 나오는 경우 클럭이 9600 Hz로 작동한다는 뜻이다.

  (Serial 포트는 9600 Hz로 데이터 라인을 샘플링, 초 당 9600 Bit 전송)

  데이터 프레임의 Over Head (Start Bit, Stop 및 Parity Bit)가 포함되므로 유효 데이터 전송 속도는 약간 낮아지고, 구성한 프레임 Bit 수에 따라 달라진다.



USRT(Universal Synchronous Receiver/Transmitter) : 동기통신


대표적인 직렬통신 방식으로는 LAN , RS232 , X.25 등이 있다.


RS-232는 UART라는 통신 방식으로 데이터를 주고 받을 수 있게 정한 통신 장비(하드웨어)에 대한 규격이다.
- RS-232는 1969년 미국의 EIA(Electric Industries Association)에 의해 정해진 표준 인터페이스
- RS : Recommended Standard
- UART 통신을 할 수 있게 PC의 통신 하드웨어 규격을 표준으로 정해 놓은 것
- RS-232C는 한 번에 한 비트씩 전송되는 방식이고 통신 거리는 일반적으로 15 m(50ft)

- 최고 통신속도 20kb/s

- 지원 전송방식 : Full Duplex

- 최대 출력전압 : ± 25V

- 3V 이상의 신호 논리적으로 0을 나타내며 –3V이하의 전기적 신호가 들어오면 논리적으로 1을 나타내게 된다.



DCD(Data Carrier Detect) :

모뎀이 상대편 모뎀과 전화선 등을 통해서 접속이 완료되었을 때 상대편 모뎀이 캐리어 신호를 보내오며 이 신호를 검출하였음을 컴퓨터 또는 터미널에 알려주는 신호선이다.


RXD(Receive Data)
외부 장치에서 들어오는 직렬통신 데이터를 입력 받는 신호선이다.


TXD(Transmit Data)
비동기식 직렬통신 장치가 외부 장치로 정보를 보낼 때 직렬통신 데이터가 나오는 신호선이다.


DTR(Data Terminal Ready)
컴퓨터 또는 터미널이 모뎀에게 자신이 송수신 가능한 상태임을 알리는 신호선이며 일반적으로 컴퓨터 등이 전원 인가 후 통신 포트를 초기화한 후 이 신호를 출력시킨다.


DSR(Data Set Ready)
모뎀이 컴퓨터 또는 터미널에게 자신이 송수신 가능한 상태임을 알려주는 신호선이며 일반적으로 모뎀에 전원 인가 후 모뎀이 자신의 상태를 파악 한 후 이상이 없을 때 이 신호를 출력시킨다. 


RTS(Ready To Send)
컴퓨터와 같은 DTE장치가 모뎀 또는 프린터와 같은 DCE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.


CTS(Clear To Send)
모뎀 또는 프린터와 같은 DCE장치가 컴퓨터와 같은 DTE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.


RI(Ring Indicator)
상대편 모뎀이 통신을 하기위해서 먼저 전화를 걸어오면 전화 벨이 울리게 된다. 이때 이 신호를 모뎀이 인식하여 컴퓨터 또는 터미널에 알려주는 신호선이며 일반적으로 컴퓨터가 이 신호를 받게 되면 전화벨 신호에 응답하는 프로그램을 인터럽터 등을 통해서 호출하게 된다.


참고하면 도움되는 게시글

http://www.hardcopyworld.com/ngine/aduino/index.php/archives/2080



PC에 꼽기만 하면 자동으로 Com Port를 할당해주는 케이블이다.


FTDI USB to UART COM Port Utility
This utility is for use with FTDI USB to UART devices. The utility provides a terminal emulation function for use on Android devices. The Android system must use Android OS version 3.2 or later and provide a USB host port.
Features
* The application will open automatically when you plug in supported FTDI device controller like FT232R, FT245R, FT232H, FT2232D, FT2232H, FT4232H and FT230X, FT231X...
* It supports FTDI USB TTL Serial, USB RS232 and USB Hi-Speed cables.
* Suitable for use on any Android platform with Android v3.2 and later versions.
* Provide general terminal UART utility; easily adaptable to a console function.
* Support CTS/RTS, DTR/DSR and XOFF/XON Flow controls.
* Support Baud from 300 to 921600.
* Save file and Send file functions support XModem, YModem and ZModem file transfer protocols.
* USB Plug and Play.
* USB 2.0 Full Speed compatible.


USB Host 기능을 이용한 시리얼 통신 방법
USB 장치가 OTG 케이블을 통해 연결되면 vendor-id와 product-id에 따라서 이 장치와 연결될 앱을 설정하는 부분이 있다.

SerialConnector 클래스에 initialize() 함수가 있다.
여기서 USB 장치가 연결되면 FTDI 모듈에 맞는 드라이버를 통해 통신할 준비를 한다.

블로그 이미지

Link2Me

,
728x90

Android Data 를 가져오기를 하는데 파일 복호화 시 IllegalBlockSizeException 발생한다.


원인은 Copy & Paste 를 하면서 파일 암호화를 하지 않았는데, 암호화한줄 알고 복호화를 시도해서 발생했다.

암호화, 복호화가 정확하게 맞는 것인지 체크를 하지 못한 문제였다.


암호화, 복호화 함수는 잘 만들어져 있는데도 불구하고 다른 암호화 모듈로 변경도 시도해보고 했다.

그런데, 암호화/복호화가 문제없는 코드도 있었으므로 암호화/복호화 코드 문제는 아니라고 판단을 했어야 하는데, 문제는 PHP 코드에서 암호화를 하는 부분에서 암호화하지 않는 부분을 Android 에서 복호화를 시도하고 있었던 것이었다.

아무튼 기본기가 약하면 손발이 고생하는 수 밖에 없다.



블로그 이미지

Link2Me

,
728x90

맥북프로 2012년형 모델 이후 레티나부터는 사용자가 별도로 구매 이후 메모리를 별도 증설하기가 불가능하단다.

오로직 SSD 만 교체 증설이 가능하다.


구글링을 하다보니 누군가가 애플에서 권장하는 8G 가 아닌 16G로 증설이 가능하다고 해서 기본 스펙과 딱 맞아떨어지는 속도(MHz)의 메모리 모듈을 구매하면 문제가 없겠다는 확신에 차서, 맥북 프로 2010에 메모리를 4G에서 16G로 증설할 목적으로 해외 구매를 했다.

국내에서 메모리를 검색하고 찾아도 제대로 구하기가 쉽지 않아서였다.

급한 성격 탓에 설 연휴 직전에 특급배송으로 구입을 했다.


https://eshop.macsales.com/shop/memory/Apple_MacBook_MacBook_Pro/Upgrade/DDR3_White


두근거리는 마음으로 맥북 2010 프로에 교체를 했는데 메모리 인식이 안되고 비정상 동작을 한다.

그래서 다시 원복하고 잘못 샀다는 후회로 심한 자괴감에 빠졌다.

국내에서 메모리를 구입하면 맥북에 맞지 않으면 5일 이내 반품하면 택배비 변제하고 반품을 받아주더라.

혹시나 하는 마음으로 맥북 2010에 장착을 시도했다. 맥북 2010에서는 아무 이상없이 인식이 잘 된다.








메모리 16G, SSD 240G로 교체하고 나서는 체감속도는 상당히 빨라서 사용 만족도는 매우 높아졌다.

다만, 윈도우를 별도 부팅하지 않고 사용해볼 목적으로 Virtual Box 를 설치해서 시도했으나 윈도우 10은 설치도중에 에러 발생했고, 윈도우 8.1은 처음부터 설치가 안되더라.

아무래도 윈도우 7 을 설치 시도해봐야 하는 생각이 들기는 하지만 그거라도 설치되면 다행이겠다 싶다.

다음에 시간이 허락하면 시도해보련다.


딸애 맥북 프로 2012 13인치 i7 2.9GHz 16G에서는 윈도우10이 잘 설치된다.

맥북 프로 2010 15인치 i5 2.4GZHz 4G에서는 윈도우 8.1을 설치되지만 윈도우 10은 설치되지 않는다.


이번에 맥북 메모리 업그레이드와 SSD 교체를 하면서 느낀 것은

SSD는 최근 가격이 엄청 하락해서 1TB SSD Western Digital 을 구매하더라도 20만원이 안가므로 이걸 구매해서 교체해보고 싶다.

250G SSD 는 이것저것 설치하면 금방 용량이 부족해진다.

Western Digital SSD 가 TLC 방식이긴 해도 컨트롤러가 매우 안정적인 걸 사용한다고 되더 있더라. 그래서 컴퓨터 A/S하는 곳에도서 웬디 SSD를 많이 추천하더라. 고장나지 않는다고.

중요한 데이터는 아이클라우드 또는 구글 드라이브에 백업해두면 좋겠다는 생각이 든다.

윈도우에서 다운로드 받거나 필요한 파일을 구글 드라이브에 업로드해두면 맥북 클린 설치이후 필요한 파일을 구글 드라이브에서 받아서 설치하면 속도도 빠르고 원하는 걸 바로 바로 설치할 수가 있더라.


맥북프로 2010 15인치 i5 2.4GZHz는 삼성 메모리 4G X 2개를 장착하고 Western Digital SSD 1TB를 윈도우8.1 400GB 할당하여 잘 사용하고 있다.

메모리가 적다보니 패러럴즈 14 속도가 느려서 패러럴즈는 사용하지 않고 있다.

LG 27인치 FHD 모니터와 연결하여 사용하고 있는데, 만족스럽게 잘 사용하고 있는 중이다.

블로그 이미지

Link2Me

,
728x90

내 맥북은 2010 년도 버전인데 문제는 설치 DVD가 10.6(Snow Leopard : 2009년 8월) 버전이라 초기 설치를 하고 나면 파일을 설치해도 OS Update가 너무 힘들다.

어찌어찌 Sierra 로 업데이트를 하긴 했는데 SSD로 변경을 하면서 또 시행착오를 겪는다.

맥북을 사용한 지가 얼마 안되어서 사용법이 서투른데다가 ....


한가지 느낀 점은 부팅용으로 사용할 맥북 USB를 만들면 그 USB는 다른 용도로 사용하지 말고 보관하고 있어야 겠다는 생각이 들었다.

(문득 든 생각이 macOS 버전이 높아진 다른 맥북에서 설치용 USB를 만들어서 이용하는 것도 방법일 거 같다)



https://blog.naver.com/imkkj0317/221165947089 에 설명된 방법대로 부팅용 USB를 만들었다.

https://kimsungjin.tistory.com/46 에 더 잘된 설명이 나온다.


1. 맥북 크롬 브라우저에서 macOS Sierra 로 업그레이드하는 방법을 검색한다.

2. App Store 에서 다운로드 한다.

    맥북에 macOS Sierra 설치 파일이 다운로드 된 상태에서 가능하다.

    macOS 버전이 너무 낮은 경우 AppStore 에서 파일 다운로드하는 것이 막히는 거 같다. 

    하도 삽질을 해서 이제 기억도 안난다.



Carbon Copy Cloner 를 이용하여 SATA HDD 에 있는 걸 그대로 복사하는 방법으로 파일을 옮겼다.

프로그램 설치정보 중에 원하지 않는 것이 동작되는 것이 있어서 클린 설치를 하고 싶었는데 너무 힘들고 시간낭비인 거 같아서 다시 시도하고 싶지는 않다.


macOS Sierra 다운로드된 파일이 있으면 아래 그림처럼 성공된 결과가 나온다.


sudo /Applications/Install\ macOS\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/macOS --applicationpath /Applications/Install\ macOS\ Sierra.app


설치된 유틸

uTorrent, Toad, TeamViewer, Pine Player, Xcode, Mounty, MS Office, Dropbox,

Carbon Copy Cloner, Chrome, Cleaner-App, Commander-One, Bandizip X,



블로그 이미지

Link2Me

,
728x90

네이버 지식인에 문의된 대구와 부산의 날씨 정보 파싱 예제다.


<?php
include_once 'simple_html_dom.php';

$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024"; // 용인
$url1 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT007007"; // 대구
$url2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008"; // 부산

$R = weather_parse($url1);
$S = weather_parse($url2);
echo json_encode(array("Time1"=>$R[0], "Weather1"=>$R[1],"Finedust1"=>$R[2],"Time2"=>$S[0], "Weather2"=>$S[1],"Finedust2"=>$S[2])); // 대구와 부산 날씨 정보

function weather_parse($url){
    $str = file_get_contents_curl($url);
    $enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
    if ($enc != 'UTF-8') {
        $str = iconv($enc, 'UTF-8', $str);
    }

    $html = new simple_html_dom(); // Create a DOM object
    $html->load($str); // Load HTML from a string

    $R = array();
    $item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext;
    array_push($R,$item);
    $item = $html->find('div[class=w_now2]',0)->find('em',0)->plaintext;
    array_push($R,$item);
    $item = $html->find('div[class=w_now2]',0)->find('em',1)->plaintext;
    array_push($R,$item);
    return $R;
}

function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}
?>


파일에 대한 설명은 https://link2me.tistory.com/1603 참조하라.


블로그 이미지

Link2Me

,
728x90

PHP Simple HTML DOM Parser 를 활용하여 네이버 날씨정보를 파싱하는 방법이다.


준비물

http://simplehtmldom.sourceforge.net/ 에서 파일을 다운로드하고 simple_html_dom.php 를 include 한다.

다운로드 받은 파일에 포함된 예제는 읽어보거나, 구글링해서 사용법에 대한 예제를 찾아보면 된다.


<?php
include_once 'simple_html_dom.php';
function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    //Set curl to return the data instead of printing it to the browser.
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

?> 


그럼 2단계로 파싱할 곳을 선택한다.

내가 사는 지역인 용인지역을 파싱할 곳으로 선택하고, 네이버 날씨정보 URL을 알아낸다.


$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024";


$str = file_get_contents_curl($url);
$enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
if ($enc != 'UTF-8') {
    $str = iconv($enc, 'UTF-8', $str);
}

print_r($str);


여기까지 하면 날씨정보에 대한 소스보기를 할 수 있다.

마우스 우클릭하여 소스보기를 하면 소스 정보가 나온다.

소스에서 파싱할 곳을 찾아내야 한다.


소스보기에 아래 그림을 찾아낼 수 있다.

<em>태그안에 있는 정보가 파싱할 정보라는 걸 확인할 수 있다.


그럼 이제 simple_html_dom 파싱 처리를 위한 단계로 코드를 구현하자.

// Create a DOM object
$html = new simple_html_dom();
// Load HTML from a string
$html->load($str);


<em> 태그를 파싱하는 방법은 아래와 같이 하면 모든 em 태그를 찾아준다.

foreach($html->find('em') as $element){
    echo $element->innertext . '<br>';
}


시간정보만 파싱해보자.

foreach($html->find('div[class=c_tit2]',0)->find('em') as $element){
    echo $element->innertext . '<br>';
}
와 같이하면 시간정보만 파싱되는 걸 확인된다.

$html->find('div[class=c_tit2]',0) 와 같이 0을 넣어주어야 하는 거 잊지말자. 소스와 비교하여 확인하면 된다.

find는 선택한 태크를 찾고 그 자식들을 모두 찾는데 다시 find 로 em 태크를 선택한 것이다.

육안으로 확인하면 하나 밖에 없다는 것을 알 수 있으므로

$time = $html->find('div[class=c_tit2]',0)->find('em');

를 하면 Trying to get property of non-object라고 하면서 에러가 발생한다.

$item = $html->find('div[class=c_tit2]',0)->find('em',0); 라고 하면 에러가 발생하지 않는다.

echo $item->plaintext; 를 하면 시간 정보가 출력되는 걸 확인할 수 있다.

한번에 하면 $item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext; 로 하면 된다.


원하는 결과만 뽑아내면

$R = array();
foreach($html->find('em') as $element){
    array_push($R,$element->innertext);
}
echo '시간 : '.$R[1].'<br />';
echo '날씨 : '.$R[2].'<br />';
echo '미세먼지 : '.$R[3].'<br />';


하나 하나 분리해서 코드를 작성해보면....

$item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext;
echo $item.'<br />';

$item = $html->find('div[class=w_now2]',0)->find('em',0)->plaintext;
echo $item.'<br />';

$item = $html->find('div[class=w_now2]',0)->find('em',1)->plaintext;
echo $item.'<br />';


코드를 Android 에서 JSON 으로 파싱처리하도록 수정했다.

<?php
include_once 'simple_html_dom.php';
function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    //Set curl to return the data instead of printing it to the browser.
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024";

$str = file_get_contents_curl($url);
$enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
if ($enc != 'UTF-8') {
    $str = iconv($enc, 'UTF-8', $str);
}

//print_r($str);

$html = new simple_html_dom(); // Create a DOM object
$html->load($str); // Load HTML from a string

$R = array();
foreach($html->find('em') as $element){
    array_push($R,$element->innertext);
}
$R[2] = preg_replace("(\<(/?[^\>]+)\>)", "", $R[2]); // html 태그를 없애는 정규식
echo json_encode(array("Time"=>$R[1], "Temperature"=>$R[2],"Fine dust"=>$R[3]));
?>


이렇게 하면 네이버 날씨정보 소스코드가 달라져도 이 PHP 파일만 수정해주면 되므로 Android 앱에서는 잘못된 정보로 업데이트할 일이 없을 것이다.


블로그 이미지

Link2Me

,
728x90

네이버 지식인 질문에 있는 예제를 거의 그대로 테스트한 걸 적어둔다.

volley 라이브러리를 사용해서 하려고 했더니 Jsoup 자체가 네트웍 통신을 하는 기능이 있는거 같다.

단지 AsyncTask 를 이용하여 백그라운드 처리만 하도록 해주면 OK다.

더 간단하게는 PHP 에서 파싱처리한 결과를 Android 에서 JSON으로 가져오는 것도 방법일 거 같다.

 

테스트한 소스코드 파일

weather_src.zip
다운로드
weather_src_new.zip
다운로드

 

수정 일자 : 2020.2.29

 

AndroidManifest.xml

파싱과는 무관한 권한도 추가되어 있는데

위험권한 설정하는 라이브러리를 이용하는 법 알려주려고 추가한 것이다.

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

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"  <!-- 안드로이드 9에서는 https 통신이 기본이라 http 통신 가능하게-->
        android:theme="@style/AppTheme">
        <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>

 

 

앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.link2me.android.jsoup"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }

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

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0' // ListView 개선 버전
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'org.jsoup:jsoup:1.11.3'
    implementation 'gun0912.ted:tedpermission:2.0.0'
}

recyclerview 는 실제 사용을 하지는 않았지만, androidx 에서 버전 표기를 찾기 쉽게 적어둔다.

 

Layout : activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/weather_rs1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.057" />

    <TextView
        android:id="@+id/weather_rs2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.127" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.java

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    Context context;
    TextView textView1, textView2;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = MainActivity.this;

        // 네트워크 연결상태 체크
        if (NetworkConnection() == false) NotConnected_showAlert();
        checkPermissions();
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
                            android.Manifest.permission.READ_EXTERNAL_STORAGE,
                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        textView1 = findViewById(R.id.weather_rs1);
        textView2 = findViewById(R.id.weather_rs2);

        String path1 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT007007";
        String path2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008";
        new getData1().execute(path1);
        new getData2().execute(path2);
    }

    private class getData1 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get(); // Web에서 내용을 가져온다.
                Elements elements = document.select("em"); // 내용중에서 원하는 부분을 가져온다.
                Element targetElement1 = elements.get(1);//1.현시간 2.온도 3.미세먼지
                Element targetElement2 = elements.get(2);//1.현시간 2.온도 3.미세먼지
                Element targetElement3 = elements.get(3);//1.현시간 2.온도 3.미세먼지
                String text1 = targetElement1.text();
                String text2 = targetElement2.text();
                String text3 = targetElement3.text();
                String text = "시간 : "+text1 + " ,날씨 : " + text2 + " ,미세먼지 : " + text3;
                System.out.println("대구날씨 : "+text);
                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView1.setText("대구 : "+result);
        }
    }

    private class getData2 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get();
                Elements elements = document.select("em");
                Element targetElement1 = elements.get(1);//1.현시간 2.온도 3.미세먼지
                Element targetElement2 = elements.get(2);//1.현시간 2.온도 3.미세먼지
                Element targetElement3 = elements.get(3);//1.현시간 2.온도 3.미세먼지
                String text1 = targetElement1.text();
                String text2 = targetElement2.text();
                String text3 = targetElement3.text();
                String text = "시간 : "+text1 + " ,날씨 : " + text2 + " ,미세먼지 : " + text3;
                System.out.println("부산날씨 : "+text);
                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView2.setText("부산 : "+result);
        }
    }

    private void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();

    }

    private boolean NetworkConnection() {
        int[] networkTypes = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if (activeNetwork != null && activeNetwork.getType() == networkType) {
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }
}

 

https://partnerjun.tistory.com/42 에 Jsoup 설명이 잘 되어 있는 편이다.

 

PHP 를 활용한 파싱 코드는 https://link2me.tistory.com/1604 참조하라.

 

 

 

블로그 이미지

Link2Me

,
728x90

현재의 영국은 잉글랜드(England), 스코틀랜드, 웨일스, 북아일랜드로 이루어져 있다.
고대 영국땅에는 이베이라 반도에서 건너간 민족과 프랑스 지역에서 건너간 켈트(Celt)족이 주로 모여 살고 있었다.
로마의 카이사르는 BC55년과 BC54년에 두차례에 걸쳐 영국을 점령하여 300년 이상 로마군이 주둔했다.
5세기에 들어 게르만족이 로마를 침입해오자 방어선 축소와 병력 확보를 위해 410년에 로마군을 영국에서 철수시켰다.
5세기경에 영국땅은 유럽 대륙 본토와 북유럽 여러 민족들의 각축장이 되었고, 색슨족과 앵글로족이 영국땅으로 건너오게 되었다.
8세기 후반에 스칸디나비아 지역으로부터 바이킹족이 대규모 침입이 시작되자 부족들은 통일된 왕국을 세웠고,
871년 알프레드 대왕이 왕위에 오르게 되었다.
크누트(재위 1016~1035)는 북부 스코틀랜드와 노르웨이, 덴마크까지 포함시켜 스칸디나비아 제국을 완성했다.
윌리엄 1세(재위 1066~1087)는 프랑스 노르망디 땅의 소유주로 영국을 점령하고, 주변의 프랑스 사람들을 데리고 오면서,
영국의 왕실과 귀족들은 프랑스어를, 일반 평민들은 영어를 사용하는 상황이 초래되었다.
헨리2세(재위 1154~1189)는 영토확장을 이루었고 영국 역사상 처음으로 영국땅에 통일국가를 세웠다.
영국과 프랑스는 14세기에 들어 백년전쟁(1337~1453)에 돌입한다.
헨리8세(재위 1509~1547)는 교황의 교권을 거부하고 별도의 기독교 조직(성공회)를 공표한다.
메리1세(재위 1553~1558)는 영국내 성공회를 금지시키고 카톨릭으로 돌아갈 것을 선언하고 수많은 사람들을 희생시켰다.
엘리자베스1세(재위 1558~1603)는 다시 성공회를 국교로 돌리고 스페인 함대를 물리쳐 해상권을 장악한다.
빅토리아여왕(재위 1837~1901)은 "해가 지지 않는 나라"라고 일컬어지는 영국의 세계제국 시대를 열었다.

학자들은 앵글로색슨족이 영국으로 건너온 이후부터 지금과 비슷한 형태의 영어가 사용됐을 것으로 추정하고 있다.
England 는 Anglo족이 사는 땅(land)란 의미다.
London 은 태양신 Lugh(루)를 섬기던 켈트족이 모시던 신전 Lughdun이 발달해 생겨난 도시다. 요새(dun:둔)

블로그 이미지

Link2Me

,
728x90

GitHub 에 소스를 보면

dependencies {
    compile project(':usbSerialForAndroid')
}

라고 되어 있는 경우가 있다.


이런 경우에는 어떻게 해야 프로젝트에 포함시켜 컴파일 할 수 있을까?


먼저, setting.gradle 파일을 열어서 프로젝트명을 본다.

include ':usbserialport'

라고 되어 있는 것이 보일 것이다.

여기에 추가를 한다.


include ':usbserialport'
include ':usbserialport:usbSerialForAndroid'


이제 실제 파일을 어떤 폴더에 넣는지 보자.



폴더를 여기에 넣었다.


build.gradle 에서는

 dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    compile project('usbSerialForAndroid')
}

로 적어주면 된다.


만약 libs 밑에 추가하고자 한다면 setting.gradle 에서는


include ':usbserialport'
include ':usbserialport:libs:usbSerialForAndroid'


라고 적어주고,


build.gradle 에서는

compile project('libs:usbSerialForAndroid')

라고 적어주면 된다.


이렇게 하고 나면 소소하게 발생하는 에러는 수정해주면 된다.

블로그 이미지

Link2Me

,