本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

基于Particle Photon的车库"指挥官"

发布时间:2022-05-15
分享到:

基于Particle Photon的车库"指挥官"

发布时间:2022-05-15
分享到:

这个项目的主旨是:无论您身在何处,都可以控制您的车库门。

创新点
如今,网络上有很多车库开门器,每一个都有自己的特别之处。

那么这款有什么特别之处呢?主要集中在以下几点:

  • NFC 标签:扫描 NFC 标签以打开车库门
  • 语音:使用 Google Now 语音命令来控制您的车库门
  • 推送通知:在车库门发生变化时立即获取其状态(或随时请求其状态)

背景

自从我们家里的车库需要一个新的遥控器后,我就抵制住了购买“经典”遥控器的诱惑。所以这次我想要一个物联网遥控器,用我的手机打开和关闭我的车库门,我想通过自己制作一个。

这个项目的成功也多亏了物联网!它使您能够将大量设备连接到互联网,以记录其数据并从任何您想要的地方控制它们。所以最后“想要”连接到互联网的东西变成了车库门。

原型
由于我是这个 Particle Photon 的新手(当时是 Spark Core),我想给自己一个简单的挑战来获得动力,所以我用我手边的 maker kit 快速破解了一些东西。在面包板上,我安装了 Photon、继电器和 LED。我在墙上按钮上的微型按钮后面焊接了两根电线,以打开车库并在那里连接继电器。

之后我使用这个 Android 应用程序向粒子云发送休息请求 以激活继电器。我用代码加载粒子以关闭继电器一秒钟然后释放它,以这种方式模拟一个人按下墙上的按钮。 

车库门可以随时打开和关闭!

局限性

第一个原型很好地向我展示了我可以构建我想要的东西,但它几乎没有限制。

首先,如果我不在并且错误地点击了应用程序并发送了休息请求,我不知道门现在是打开还是关闭。当朋友不在我的地方时,我吹牛的权利非常有限!我也不知道请求是否有效,在我按下手机中的发送请求后门是否开始移动,除非我在房子前面。

换句话说,它缺少用户反馈。

用户反馈

通过在门完全关闭或打开时激活的开关,人们可以远程了解车库门的状态。

所以这就是我的项目所需要的,我选择并安装了两个簧片开关并将它们连接到我的项目板上。您还可以选择安装其他类型的开关,甚至是微动开关。

现在,我只需要手机上的这些信息,所以有什么比使用 Pushbullet更好的了。

安卓应用

iOS 用户注意:您可以使用 iPhone 或 Ipad 控制此项目,方法是使用 DO 按钮或 blynk 应用程序。

因为我还想学习 Android 编程,所以我开始创建我的第一个 Android 应用程序。该应用程序尚未完成或完善,但它已经让我学习了基础知识并在这个意义上让我很开心。

您也可以使用 DO 按钮应用程序来控制车库门,您有这个项目和这个其他项目作为参考。当然,您也可以使用Blynk 应用程序。

NFC 标签
我想在我的项目中拥有类似于一些车库门侧面的小键盘的东西。

所以我在车库门的一侧粘上了一个 NFC 标签,在那里我会用手机轻拍它,然后门就会打开。

在 Android 世界中,这可以通过Tasker和Trigger来实现。 

如果您知道 iOS 世界中有类似的应用程序,请在下面发表评论。

我想在我的项目中拥有的另一个功能就是可以通过我的声音打开和关闭车库门。

这将允许我开车回家,在一个街区外只需说“Ok Google”和“Open Garage”,无需我触摸手机就可以有效地打开车库门。

在 Android 世界中,可以通过Google Now 、  Tasker和Autovoice来实现。

同样,如果您知道 iOS 世界中类似的应用程序,请在下面发表评论。

推送通知

我们将使用与本项目中描述的相同的推送通知机制。请完全按照此处所示配置 pushbullet 和 webhook。 

我们可以重复使用完全相同的 webhook 来从不同的项目向我们的设备推送不同的通知。这很棒,因为我们可以在粒子云中创建最多 20 个 webhook。

 Android 应用程序的 java 代码:

package com.example.gusgonnet.myfirstapp;

//TODO: now
// - app has to have a get and a post function
// - app has to refresh status at beginning
// - have a settings page
// - store Access Token and Device ID
// - make the spark return open/close status in one call

//TODO: later
// - store email, password, Device ID
// - get an access token from email and password


import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

public class MyActivity extends Activity {

    public enum AppStatus { REFRESHING, OPENING, CLOSING, OPEN, CLOSED, UNKNOWN };

    RelativeLayout DefaultLayout;
    RelativeLayout WaitingLayout;

    Button actionButton;
    TextView waitingTextView;

    public static AppStatus appStatus = AppStatus.CLOSED;

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.my, menu);  //this was main_activity_actions -> my
        return super.onCreateOptionsMenu(menu);
    }

    public void actionButtonClickHandler(View view) {

        //replace 121345678901234567890 with your particle photon id
        String url = "https://api.spark.io/v1/devices/121345678901234567890/garage_open";

        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isConnected()) {
            appStatus = updateAppStatus(appStatus);
            updateLayout(appStatus);
            new sendCommand().execute(url);
        } else {
            Toast toast = Toast.makeText(getApplicationContext(), "Something wrong\r\nno internet connection?", Toast.LENGTH_LONG);
            toast.show();
        }
    }

    // Uses AsyncTask to create a task away from the main UI thread. This task takes a
    // URL string and uses it to create an HttpUrlConnection. Once the connection
    // has been established, the AsyncTask downloads the contents of the webpage as
    // an InputStream. Finally, the InputStream is converted into a string, which is
    // displayed in the UI by the AsyncTask's onPostExecute method.
    private class sendCommand extends AsyncTask<String, Void, String> {

        private static final String DEBUG_TAG = "HomeCommander";

        protected void onPostExecute (String result) {
                //check that the response indicates success
                if (result.toLowerCase().contains("\"return_value\": 0") ) {
                    Toast toast = Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG);
                    toast.show();
                    appStatus = updateAppStatus(appStatus);
                } else {
                    Toast toast = Toast.makeText(getApplicationContext(), "Something went wrong! \r\n" + result, Toast.LENGTH_LONG);
                    toast.show();
                    appStatus = rollbackAppStatus(appStatus);
                }
            updateLayout(appStatus);
        }


        @Override
        protected String doInBackground(String... urls) {

            // params comes from the execute() call: params[0] is the url.
            try {
                return sendCommandToSparkCloud(urls[0]);
            } catch (IOException e) {
                return "problems with the url?";
            }
        }


        // establishes an HttpUrlConnection and sends a command to the Spark cloud
        // it returns the response as a string
        private String sendCommandToSparkCloud(String myurl) throws IOException {
            InputStream is = null;

            try {
                URL url = new URL(myurl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000 /* milliseconds */);
                conn.setConnectTimeout(15000 /* milliseconds */);
                conn.setRequestMethod("POST");
                //replace 121345678901234567890 here with your particle auth token
                conn.setRequestProperty ("Authorization", "Bearer 121345678901234567890");
                conn.setDoOutput(true);

                // sends the request
                conn.connect();

                //get the response code
                int response = conn.getResponseCode();
                Log.d(DEBUG_TAG, "The response code is: " + response);

                // get the response content
                is = conn.getInputStream();
                // we are interested in only the first 500 chars
                String responseAsString = stream2string(is, 500);
                Log.d(DEBUG_TAG, "The response content is: " + responseAsString);

                return responseAsString;

                // This makes sure that the InputStream is closed after the app is
                // finished using it.
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        }

        // Reads an InputStream and converts it to a String.
        public String stream2string(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
            Reader reader = new InputStreamReader(stream, "UTF-8");
            char[] buffer = new char[len];
            reader.read(buffer);
            return new String(buffer);
        }

    }

        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

            DefaultLayout=(RelativeLayout)findViewById(R.id.DefaultLayout);
            WaitingLayout=(RelativeLayout)findViewById(R.id.WaitingLayout);

            actionButton = (Button)findViewById(R.id.actionButton);
            waitingTextView = (TextView)findViewById(R.id.waitingTextView);

            updateLayout(appStatus);

    }

    public void updateLayout(AppStatus appStatus) {
        switch (appStatus) {
            case OPENING:
                waitingTextView.setText(getString(R.string.opening_garage));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case CLOSING:
                waitingTextView.setText(getString(R.string.closing_garage));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case REFRESHING:
                waitingTextView.setText(getString(R.string.refreshing));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case OPEN:
                actionButton.setText(getString(R.string.close_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;

            case CLOSED:
                actionButton.setText(getString(R.string.open_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;

            case UNKNOWN:
                actionButton.setText(getString(R.string.open_close_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;
        }

    }

    public AppStatus updateAppStatus(AppStatus appStatus) {
        switch (appStatus) {
            case OPEN:
                return AppStatus.CLOSING;

            case CLOSING:
                return AppStatus.CLOSED;

            case CLOSED:
                return AppStatus.OPENING;

            case OPENING:
                return AppStatus.OPEN;

            case REFRESHING:
            case UNKNOWN:
            //TODO: not sure what to do here, so for now we don't change the appStatus
        }
        return appStatus;
    }

    public AppStatus rollbackAppStatus(AppStatus appStatus) {
        switch (appStatus) {
            case CLOSING:
                return AppStatus.OPEN;

            case OPENING:
                return AppStatus.CLOSED;

            case OPEN:
            case CLOSED:
            case REFRESHING:
            case UNKNOWN:
                //TODO: not sure what to do here, so for now we don't change the appStatus
        }
        return appStatus;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        switch (item.getItemId()) {
            case R.id.action_settings:
//                openSettings();
                Toast toast = Toast.makeText(getApplicationContext(), "Settings are coming soon...", Toast.LENGTH_SHORT);
                toast.show();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

如果您对此项目有任何想法、意见或问题,请在下方留言。

以上内容翻译自网络,原作者:Gustavo,如涉及侵权,可联系删除。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论