在上一篇文章中,我答应过谈如何使用UART将CANNY 3 tiny连接到蓝牙。而且由于您在这些五月的假期中不会真正散散步,因此决定度过一段有益的时光,并且仍然信守诺言。但是仅仅将控制器连接到蓝牙适配器HC-06,对于Habr来说太容易了。因此,我们不仅会连接所有内容,还将使用C#和Xamarin为电路编写一个原始的Android应用程序。如果您喜欢我喜欢的“限位开关”和簧片开关的监控,欢迎您的到来。
这就是我们今天要谈论的内容:第一部分:简介第二部分:连接CANNY 3 tiny的电路和程序第三部分:我们在Xamarin上为Android编写了一个应用程序第四部分:结论第一部分:简介
我先从好的代码开始,除了在C#中插入程序代码外,这一次,本文将相对较小,因为我们已较早地研究了使用控制器的基本技术。为了不再重复,这里列出了一些文章,在这些文章中,我们已经研究了使用CANNY控制器的基本方法:- “一,二,三-烧圣诞树!” 或我第一次看到CANNY 3微型控制器 -在本文中,我们分析了控制器的含义,以及在CannyLab开发环境中工作的基础知识。
- “目的地有很多伪装……”,或者我们使用CANNY 3 tiny和光敏电阻自动控制灯 -在本文中,我们研究了使用USB虚拟COM端口,将传感器连接到ADC以及在控制器输出端使用高频PWM的方法。
- « ...» (CANNY Arduino) Raspberry PI — UART, .
在准备本文时,我使用了以下硬件:CANNY 3微型控制器,HC-06蓝牙适配器,限位开关(Trema模块),簧片开关,旧有线耳机,面包板,电线,鳄鱼。我们将构建一个系统,该系统使用移动应用程序监视两个传感器的状态,并在必要时可以手动发出警报。本文将介绍的所有内容都是出于教育和示范目的而发明的。我只是想展示一些与控制器一起使用的技巧,以及一次购买的铁片,以某种方式计算出它们的价值。尽管要解决的问题的性质与现实相去甚远,但我们可以想象,我们正在隔室的滑动门后面制造一个监视系统。当它开始移动时,簧片开关将起作用,并且在路径的尽头它将向拖车发出信号。例如,如果我们需要引起注意,以使门关闭,我们将通过扬声器发出“吱吱”的信号。是的,我没有扬声器,但是有旧耳机。
好吧,和往常一样,一个音符。我强烈不建议将本文中的材料用作最终的真理。我本人第一次做很多事情,当然可以做得更好。
第二部分:连接CANNY 3 tiny的电路和程序
首先,为了不冒犯他人的版权,我从论坛上借来了东西,但我自己通过将它们连接到HC-06 的想法来适应他们的想法,方法是通过``串行蓝牙终端''应用程序控制它并在开发图表时使用一些技巧你的任务。接线图如下:
限位开关和簧片开关连接到6号和5号控制器的端子,耳机连接到4号端子(具有RF PWM),UART RX是1号端子,UART TX是2号端子,3号端子。它用于提供“ + 5V”,输出“-”-与“地”进行通信。这是组装时的样子:
我在CannyLab 1.42版中为CANNY 3开发了图表(程序),也许在其他版本的开发环境中以及与其他控制器一起使用时,有必要对图表进行更改。这是发生的事情:
与控制器设置和通过UART发送消息相关的模块在上一篇文章中已分解。让我们更详细地研究剩下的两个。“ 接收UART消息 ”块负责打开警笛(耳机)。原则上,需要解析通过UART接收消息的示例。首先,我们检查接收到的数据是否在UART中,如果存在,则将其发送到D触发器输入“ E”,在这种情况下,触发器将复制“ D”输入中的值,然后将其写入通过UART接收的消息中的前两个字符。我不想使所有事情复杂化,因此我们将进一步使用一个简单的方案。我们假设UART会收到从00到99的任何数字,我们会将其从符号形式转换为数字形式(我建议阅读转换器模块的工作方式;我有一个小的“插头”)。此外,前沿检测器的输入上的任何值> 0“都将导致一个信号,该信号在RF PWM模式下运行5秒钟将输出5导通。您可以使用RF PWM的填充时间在设置中播放,耳机中的声音将取决于此。让我们继续前进“消息的形成。”乍一看它的实现似乎很不寻常。这是因为我并没有真正弄清楚如何使用串行蓝牙终端程序以及Xamarin中的相同蓝牙协议。我会向前走一点,说我还没有学会如何在智能手机上接收从控制器发送的消息。如果上一篇文章中的有线UART一切都显而易见,那么实际上是使用蓝牙,而不是发送的消息,只能读取其一部分,并且违反了传输命令的含义。我认为最简单的解决方案是传输单个号码,这样可以保证到达接收方而不会造成损失。在我们的案例中,我们监视磁簧开关和限位开关的离散状态。也就是说,我们只有4种可能的组合:簧片开关和限位开关关闭,仅一个打开,两个都打开。由于簧片开关和限位开关会给出离散信号(0/1),因此您需要以某种方式区分它们。为此,我们将簧片信号的值乘以2.现在事实证明,信号的总和将为我们提供从0到3的值。现在我们将分析一个非显而易见的选项,将其加上50事实是CannyLab向UART发送了两个字符,即03,而不是3,但是正如我所说,存在丢失某些信息的风险。例如,从值01开始,智能手机上的程序只能读取第一个“ 0”,这已经是一个错误。可能会感到困惑并转换数据,例如用一些字母或空格替换寄存器的“ D1”字符,但我决定简化一下。我将值01转换为51(02转换为52,依此类推)。这五个不带信号,我在智能手机的程序级别将其剪切掉了。因此,我们始终保证保留消息的有用部分。我们将程序加载到控制器中,单击“运行”,如果一切按计划进行,则HC-06将定期闪烁红色LED。接下来,我们将智能手机与适配器配对。现在,您可以在“串行蓝牙终端”应用程序或任何其他具有类似功能的应用程序中检查性能。写下蓝牙适配器的地址,这将在下一章中对我们很有用。
如您所见,根据传感器的状态,数据来了,如果您发送“ 11”,则在耳机中会听到讨厌的尖叫声。我们可以在这里停止,但让我们来概述一个原始应用程序。可以从GitHub 下载用于控制器的程序和用于智能手机的程序的源代码。我想指出的是,您不必在CANNY控制器上专门在硬件中实现所有功能,您可以为Arduino或其他喜欢的控制器编写程序。最初,我本人还计划为Arduino写草图的另一个版本,但是由于我几乎杀死了所有的五月假期,所以我再也没有能力连接CANNY和智能手机应用程序。第三部分:编写Xamarin Android应用程序
坦率地说,我知道Xamarin并不是移动开发最受欢迎的解决方案。也许您已经有一个问题:“我为什么选择它?”。我想用Psoya Korolenko同名歌曲中的话回答他:
老实说,没有客观原因。就在几年前,我学习了C#的基础知识,每个人都想看看Xamarin是什么。而现在,由于“自我孤立”,人们终于可以动手了。好吧,让我再次提醒您。这是我第一次见到Xamarin,这是我的第一个Android应用程序。不要盲目复制我的曲线代码,如果突然之间您可以找到更漂亮的解决方案,请使用它。在开发程序时,我依赖于此材料。这篇文章并没有特别的嚼劲,甚至是西班牙语,所以我仍然觉得可以与您分享我在该主题上的变化。我在Visual Studio 2019社区版中构建了该程序。首先,如图所示,创建一个新的空Android项目(Xamarin)。
我仅对三个文件进行了更改,它们可以在GitHub上完全查看,在这里我们将仅分析重要的部分:AndroidManifest.xml2权限已添加到标准模板中: <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
activity_main.xml默认容器(RelativeLayout)已被删除。相反,添加LinearLayout容器仅仅是因为它更简单。在此容器中,所有元素都垂直对齐,并在屏幕的整个宽度上延伸。<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout1">
<TextView
android:text="Reed switch status - undefined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/rSwitch"
android:textSize="12pt" />
<TextView
android:text="End sensor status - undefined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/EndSensor"
android:textSize="12pt" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/startSiren"
android:text="Send signal to siren" />
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bltSwitch"
android:checked="false"
android:showText="true"
android:text="Connect bluetooth" />
<TextView
android:text="status"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/status" />
</LinearLayout>
任何稍微熟悉HTML布局或XML的人都可以轻松理解用户界面的结构。我们有三个只读文本字段(TextView),一个开关(Switch),基本上像一个复选框一样工作,还有一个普通按钮(Button)。可以通过从构造函数中拖放元素来将元素放置在窗体上,也可以在属性窗口或代码中将元素设置为更方便的ID,文本存根和其他参数。剩下要描述程序的逻辑。MainActivity.cs下面,在扰流板下方,整个代码是为了方便起见完整的MainActivity.cs代码
using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Android.Runtime;
using Android.Widget;
using System.Linq;
using System;
using System.IO;
using Java.Util;
using Android.Bluetooth;
using System.Threading.Tasks;
namespace _6.Canny_Xanarin_Bluetooth_Android
{
[Activity(Label = "Control Canny 3 tiny via bluetooth", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
Button startSiren;
TextView rSwitch;
TextView EndSensor;
Switch bltSwitch;
TextView status;
private Java.Lang.String dataToSend;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothSocket btSocket = null;
private Stream outStream = null;
private static string address = "98:D3:91:F9:6C:F6";
private static UUID MY_UUID = UUID.FromString("00001101-0000-1000-8000-00805F9B34FB");
private Stream inStream = null;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
startSiren = FindViewById<Button>(Resource.Id.startSiren);
rSwitch = FindViewById<TextView>(Resource.Id.rSwitch);
EndSensor = FindViewById<TextView>(Resource.Id.EndSensor);
status = FindViewById<TextView>(Resource.Id.status);
bltSwitch = FindViewById<Switch>(Resource.Id.bltSwitch);
startSiren.Click += startSiren_ClickOnButtonClicked;
bltSwitch.CheckedChange += bltSwitch_HandleCheckedChange;
CheckBt();
}
private void CheckBt()
{
mBluetoothAdapter = BluetoothAdapter.DefaultAdapter;
if (!mBluetoothAdapter.Enable())
{
Toast.MakeText(this, "Bluetooth Off",
ToastLength.Short).Show();
}
if (mBluetoothAdapter == null)
{
Toast.MakeText(this,
"Bluetooth does not exist or is busy", ToastLength.Short)
.Show();
}
}
void startSiren_ClickOnButtonClicked(object sender, EventArgs e)
{
if (bltSwitch.Checked)
{
try
{
dataToSend = new Java.Lang.String("11");
writeData(dataToSend);
System.Console.WriteLine("Send signal to siren");
}
catch (System.Exception execept)
{
System.Console.WriteLine("Error when send data" + execept.Message);
}
}
else status.Text = "bluetooth not connected";
}
void bltSwitch_HandleCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (e.IsChecked)
{
Connect();
}
else
{
status.Text = "bluetooth not connected";
if (btSocket.IsConnected)
{
try
{
btSocket.Close();
System.Console.WriteLine("Connection closed");
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
public void Connect()
{
BluetoothDevice device = mBluetoothAdapter.GetRemoteDevice(address);
System.Console.WriteLine("Connection in progress" + device);
mBluetoothAdapter.CancelDiscovery();
try
{
btSocket = device.CreateRfcommSocketToServiceRecord(MY_UUID);
btSocket.Connect();
System.Console.WriteLine("Correct Connection");
status.Text = "Correct Connection to bluetooth";
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
try
{
btSocket.Close();
System.Console.WriteLine("Connection closed");
}
catch (System.Exception)
{
System.Console.WriteLine("Impossible to connect");
status.Text = "Impossible to connect";
}
System.Console.WriteLine("Socket Created");
}
beginListenForData();
}
public void beginListenForData()
{
try
{
inStream = btSocket.InputStream;
}
catch (System.IO.IOException ex)
{
Console.WriteLine(ex.Message);
}
Task.Factory.StartNew(() => {
byte[] buffer = new byte[1024];
int bytes;
while (true)
{
try
{
bytes = inStream.Read(buffer, 0, 1024);
System.Console.WriteLine("bytes " + bytes.ToString());
if (bytes > 0)
{
RunOnUiThread(() => {
string valor = System.Text.Encoding.ASCII.GetString(buffer).Replace("5",String.Empty);
string command = new string(valor.Where(char.IsDigit).ToArray());
if (command.Length > 0)
{
status.Text="data successfully readed";
System.Console.WriteLine("command " + command);
switch (Int32.Parse(command))
{
case 0:
rSwitch.Text = "reed switch - disconnected ";
EndSensor.Text = "end sensor - not pressed ";
break;
case 1:
rSwitch.Text = "reed switch - disconnected ";
EndSensor.Text = "end sensor - pressed ";
break;
case 2:
rSwitch.Text = "reed switch - connected ";
EndSensor.Text = "end sensor - not pressed ";
break;
case 3:
rSwitch.Text = "reed switch - connected ";
EndSensor.Text = "end sensor - pressed ";
break;
}
}
});
}
}
catch (Java.IO.IOException)
{
RunOnUiThread(() => {
EndSensor.Text = "End sensor status - undefined";
rSwitch.Text = "Reed switch status - undefined ";
});
break;
}
}
});
}
private void writeData(Java.Lang.String data)
{
try
{
outStream = btSocket.OutputStream;
}
catch (System.Exception e)
{
System.Console.WriteLine("Error with OutputStream when write to Serial port" + e.Message);
}
Java.Lang.String message = data;
byte[] msgBuffer = message.GetBytes();
try
{
outStream.Write(msgBuffer, 0, msgBuffer.Length);
System.Console.WriteLine("Message sent");
}
catch (System.Exception e)
{
System.Console.WriteLine("Error with when write message to Serial port" + e.Message);
status.Text = "Error with when write message to Serial port";
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
现在分为几部分。具有名称空间,类声明等连接的块 我会想念。 我们创建变量,稍后将使用它们绑定用户界面元素: Button startSiren;
TextView rSwitch;
TextView EndSensor;
Switch bltSwitch;
TextView status;
接下来是我所依赖的示例中的代码。某些方法的操作所必需的变量(字段)。 private Java.Lang.String dataToSend;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothSocket btSocket = null;
private Stream outStream = null;
private static string address = "98:D3:91:F9:6C:F6";
private static UUID MY_UUID = UUID.FromString("00001101-0000-1000-8000-00805F9B34FB");
private Stream inStream = null;
对于我们来说,在此处将HC-06模块的地址驱动到地址字段中非常重要。由于这是我第一次使用智能手机进行开发,包括使用Xamarin,因此我决定不对其进行复杂化处理,因此设备地址(如原始示例中所示)是固定的。如果需要,您可以看一下本文,似乎已经实现了对可用蓝牙设备的搜索。继续。
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
startSiren = FindViewById<Button>(Resource.Id.startSiren);
rSwitch = FindViewById<TextView>(Resource.Id.rSwitch);
EndSensor = FindViewById<TextView>(Resource.Id.EndSensor);
status = FindViewById<TextView>(Resource.Id.status);
bltSwitch = FindViewById<Switch>(Resource.Id.bltSwitch);
startSiren.Click += startSiren_ClickOnButtonClicked;
bltSwitch.CheckedChange += bltSwitch_HandleCheckedChange;
CheckBt();
}
此方法是自动创建的,我们的任务是将其中的UI对象与该类的字段相关联,并附加处理程序以响应事件(startSiren.Click bltSwitch.CheckedChange
)。通过蓝牙检查连接:
private void CheckBt()
{
mBluetoothAdapter = BluetoothAdapter.DefaultAdapter;
if (!mBluetoothAdapter.Enable())
{
Toast.MakeText(this, "Bluetooth Off",
ToastLength.Short).Show();
}
if (mBluetoothAdapter == null)
{
Toast.MakeText(this,
"Bluetooth does not exist or is busy", ToastLength.Short)
.Show();
}
}
打开警笛。本质上只是将字符“ 11”发送给控制器:
void startSiren_ClickOnButtonClicked(object sender, EventArgs e)
{
if (bltSwitch.Checked)
{
try
{
dataToSend = new Java.Lang.String("11");
writeData(dataToSend);
System.Console.WriteLine("Send signal to siren");
}
catch (System.Exception execept)
{
System.Console.WriteLine("Error when send data" + execept.Message);
}
}
else status.Text = "bluetooth not connected";
}
检查开关的状态(如果向右移动,则表明它已打开): void bltSwitch_HandleCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (e.IsChecked)
{
Connect();
}
else
{
if (btSocket.IsConnected)
{
try
{
btSocket.Close();
System.Console.WriteLine("Connection closed");
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
当您打开电源时,我们将开始与蓝牙的连接。连接本身的实现: public void Connect()
{
BluetoothDevice device = mBluetoothAdapter.GetRemoteDevice(address);
System.Console.WriteLine("Connection in progress" + device);
mBluetoothAdapter.CancelDiscovery();
try
{
btSocket = device.CreateRfcommSocketToServiceRecord(MY_UUID);
btSocket.Connect();
System.Console.WriteLine("Correct Connection");
status.Text = "Correct Connection to bluetooth";
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
try
{
btSocket.Close();
System.Console.WriteLine("Connection closed");
}
catch (System.Exception)
{
System.Console.WriteLine("Impossible to connect");
status.Text = "Impossible to connect";
}
System.Console.WriteLine("Socket Created");
}
beginListenForData();
}
这是最重要的方法之一-直接读取数据:
public void beginListenForData()
{
try
{
inStream = btSocket.InputStream;
}
catch (System.IO.IOException ex)
{
Console.WriteLine(ex.Message);
}
Task.Factory.StartNew(() => {
byte[] buffer = new byte[1024];
int bytes;
while (true)
{
try
{
bytes = inStream.Read(buffer, 0, 1024);
System.Console.WriteLine("bytes " + bytes.ToString());
if (bytes > 0)
{
RunOnUiThread(() => {
string valor = System.Text.Encoding.ASCII.GetString(buffer).Replace("5",String.Empty);
string command = new string(valor.Where(char.IsDigit).ToArray());
if (command.Length > 0)
{
status.Text="data successfully readed";
System.Console.WriteLine("command " + command);
switch (Int32.Parse(command))
{
case 0:
rSwitch.Text = "reed switch - disconnected ";
EndSensor.Text = "end sensor - not pressed ";
break;
case 1:
rSwitch.Text = "reed switch - disconnected ";
EndSensor.Text = "end sensor - pressed ";
break;
case 2:
rSwitch.Text = "reed switch - connected ";
EndSensor.Text = "end sensor - not pressed ";
break;
case 3:
rSwitch.Text = "reed switch - connected ";
EndSensor.Text = "end sensor - pressed ";
break;
}
}
});
}
}
catch (Java.IO.IOException)
{
RunOnUiThread(() => {
EndSensor.Text = "End sensor status - undefined";
rSwitch.Text = "Reed switch status - undefined ";
});
break;
}
}
});
}
据我所知,我在示例中留下了该方法的许多元素,首先,它与数据流建立了连接,如果缓冲区中有一些内容与读取的数据一起被读取,则将其读取到变量中valor
。而且,正如我所承诺的,我们只需删除数字“ 5”。接下来,我们从读取的消息中删除除数字以外的所有字符string command = new string(valor.Where(char.IsDigit).ToArray());
,然后,一切变得简单,这取决于所收到的数字,从而在UI中显示此状态。我没有从根本上更改这两种方法: private void writeData(Java.Lang.String data)
{
try
{
outStream = btSocket.OutputStream;
}
catch (System.Exception e)
{
System.Console.WriteLine("Error with OutputStream when write to Serial port" + e.Message);
}
Java.Lang.String message = data;
byte[] msgBuffer = message.GetBytes();
try
{
outStream.Write(msgBuffer, 0, msgBuffer.Length);
System.Console.WriteLine("Message sent");
}
catch (System.Exception e)
{
System.Console.WriteLine("Error with when write message to Serial port" + e.Message);
status.Text = "Error with when write message to Serial port";
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
据我了解,此代码块实现了消息的发送和事件处理程序,以扩大访问蓝牙的权限。好了,剩下的就是建立用于调试应用程序的智能手机连接。
奇怪的是,但是它可以工作:
第四部分:结论
该程序的工作方式如下:
这是我在Android上为智能手机开发应用程序的第一次经验。我想指出的是,旧计算机上的VS 2019和Xamarin的运行速度非常慢。在项目的第一次组装中,我真的设法吃了这些柔软的法式面包卷和喝茶。而且,坦白地说,应用程序本身很痛苦,打开/关闭开关不能很方便地工作,但是另一方面,鉴于我对.NET的基本开发技术只有一点点熟悉,而且我根本不是程序员,所以我什至可以无需完成单个教程或单个课程,就可以在一天内概述您的第一个应用程序。因此,用于创建基本应用程序的进入阈值非常低。据我了解,CANNY控制器可用于汽车(尤其是家用汽车)的调试,因此很有可能具有某种“功能”并为其编写智能手机应用程序。要记住的主要事情是,当通过车辆的车载网络供电时,控制器的输出也将具有与输入相同的电压(例如12V而不是5V)。不要忘记保护蓝牙适配器,以免其意外失效。这篇文章对我来说非常费力,我希望一切都没有白费,您会喜欢的。