«Каких Марин?» или управляем контроллером через bluetooth с помощью мобильного приложения на Xamarin (Android)

    В прошлой статье я пообещал рассказать, о том как подключать CANNY 3 tiny с помощью UART к bluetooth. И поскольку на этих майских особо не разгуляешься, было принято решение провести время с пользой и таки сдержать обещание. Но просто подключить контроллер к Bluetooth адаптеру HC-06, было бы слишком просто для Хабра.

    Поэтому мы не просто всё подключим, но еще и напишем для нашей схемы примитивнейшее приложение для Android используя C# и Xamarin.

    Если вы любите мониторить «концевики» и герконы, так как это люблю я, милости прошу под кат.



    Вот о чем пойдет речь сегодня:

    Часть I: Введение
    Часть II: Подключение схемы и программа для CANNY 3 tiny
    Часть III: Пишем приложение на Xamarin для Android
    Часть IV: Заключение

    Часть I: Введение


    Начну с хорошего, если не считать вставок программного кода на C#, то в этот раз статья будет сравнительно небольшая, потому что основные приемы работы с контроллером мы разобрали раньше. Чтобы не повторяться лишний раз, вот список статей, в которых мы уже разбирали основные приёмы работы с контроллером CANNY:

    1. «Раз, два, три – ёлочка гори!» или мой первый взгляд на контроллер CANNY 3 tiny — в этой статье мы разбирали что из себя представляет контроллер, а также азы работы в среде разработки CannyLab.
    2. «У Предназначения масса обличий...» или автоматизируем управление автолампой с помощью CANNY 3 tiny и фоторезистора — в этой статье мы разбирали работу с USB Virtual COM-port, подключение датчиков к АЦП, а также высокочастотный ШИМ на выходах контроллера.
    3. «Как зеницу ока...» или делаем простенькую охранную систему на базе микроконтроллера (CANNY или Arduino) и Raspberry PI — в этой статье мы разбирали работу с UART, а также повторили ранее пройденное.

    При подготовке данной статьи я использовал следующее железо: контроллер CANNY 3 tiny, bluetooth адаптер HC-06, концевой выключатель (концевик Trema-модуль), геркон, старые проводные наушники, макетную плату, провода, «крокодилы».

    Мы будем собирать систему, которая с помощью мобильного приложения мониторит состояние двух датчиков и при необходимости может в ручном режиме подавать звуковой сигнал.

    Все что будет изложено в данной статье, выдумано чисто в учебно-демонстрационных целях. Я просто хотел показать, некоторые приемы работы с контроллером, а также, чтобы купленные в свое время железки хоть как-то отработали свою стоимость.

    Несмотря на далекий от реальности характер решаемой задачи мы представим, что делаем систему мониторинга за раздвижной дверью купе. Когда она начнет движение, сработает геркон, а в конце пути даст сигнал концевик. Если нам надо будет привлечь внимание, например, чтобы дверь обратно закрыли, мы подадим «писклявый» сигнал через динамик. Правда динамика у меня нет, но зато есть старые наушники.



    Ну и, как всегда, примечание. Настоятельно не рекомендую использовать, материалы данной статьи, как истину в последней инстанции. Многие вещи я сам делал в первый раз, их наверняка можно сделать лучше.


    Часть II: Подключение схемы и программа для CANNY 3 tiny


    Для начала, чтобы никого в авторских правах не обидеть уточню, что идею подключить контроллер к HC-06, по управлять им через приложение «Serial bluetooth terminal» и некоторые приемы при разработке диаграммы, я позаимствовал с форума, но само собой я их адаптировал под свою задачу.

    Схема подключения выглядит следующим образом:



    Концевик и геркон подключены к выводам контроллера №6 и №5, наушники к выводу №4 (у него есть ВЧ ШИМ), UART RX – это вывод № 1, UART TX – вывод № 2, вывод № 3 используется для подачи «+5В», вывод «-» — для связи с «землей».

    Вот как это выглядит в сборе:



    Диаграмму (программу) для СANNY 3 tiny я разрабатывал в CannyLab версии 1.42, возможно в других версиях среды разработки и с другими контроллерами, надо будет внести изменения в диаграмму.

    Вот, что получилось:



    Блоки связанные с настройкой контроллера и отправкой сообщения по UART, разбирали в прошлой статье.

    Разберём подробней два оставшихся.

    Блок «Получение сообщения по UART», отвечает за включение сирены(наушников). В принципе он нужен, чтобы разобрать пример, получения сообщения по UART.

    Вначале мы проверяем есть ли полученные данные в UART, если есть, то подаем на вход «Е» D-триггера единицу, в таком случае триггер копирует значение со входа “D” в который мы запишем первые два символа из сообщения пришедшего по UART. Я не хотел всё усложнять поэтому дальше мы используем простую схему. Предполагаем, что по UART к нам придет любое число от 00 до 99, переводим это число из символьной формы в числовую (рекомендую почитать как работает блок конвертера у меня с ним был небольшой «затык»). Дальше любое значение ">0" на входе детектора переднего фронта вызывает единичный сигнал, который включит на 5 секунд выход №4, работающий в режиме ВЧ ШИМ.

    Вы можете в настройках поиграться с периодом заполнения ВЧ ШИМ, от этого будет зависеть, звук в наушниках.

    Перейдем к блоку «Формирование сообщения». Его реализация на первый взгляд может показаться необычной. Объясняется это тем, что я толком не разобрался как работать с программой Serial bluetooth terminal и с аналогичным bluetooth протоколом в Xamarin.

    Забегу немножко вперёд и скажу, что я так и не научился гарантированно получать на смартфоне отправленное с контроллера сообщение. Если с проводным UART в прошлой статье все было очевидно, то с Bluetooth на практике вместо отправленного сообщения может прочитаться только его часть и смысл передаваемой команды нарушиться.

    Я решил, что самое простое решение — это передавать одно число, которое гарантированно дойдет до адресата без потерь.

    В нашем случае мы мониторим дискретное состояние геркона и концевика. То есть у нас всего 4 возможные комбинации: геркон и концевик выключены, включен только кто-то один, оба включены.

    Поскольку геркон и концевик дают дискретный сигнал (0/1) нужно как-то их различить. Для этого умножим значение сигнала геркона на 2. Теперь получается, что сумма сигналов даст нам значения от 0 до 3.

    Теперь разберем не очевидный вариант с прибавлением к этому значению пятидесяти. Дело в том, что CannyLab передает в UART пару символов, то есть вместо 3 допустим 03, но как я говорил есть риск потери части информации. Например, из значения 01, программа на смартфоне может прочитать только первый «0», а это уже будет ошибка.

    Можно было бы заморочиться и преобразовать данные, заменив, например символ «D1» регистра какой-нибудь буквой или пробелом, но я решил сделать проще. Я превратил значение 01 в 51 (02 в 52 и т.д.). Пятерка не несет сигнала и я ее вырезаю на уровне программы для смартфона. Таким образом, у нас всегда гарантированно остается полезная часть сообщения.

    Загружаем программу в контроллер, нажимаем «запустить», если все работает как задумано, то HC-06 начнет периодически мигать красным светодиодом.

    Далее сопрягаем смартфон с адаптером. Теперь можно проверить работоспособность в приложении «Serial Bluetooth terminal» или любом другом с похожим функционалом.

    Запишите адрес Bluetooth адаптера, он нам пригодится в следующей главе.



    Как видите данные приходят, в зависимости от состояния датчиков, а если отправить «11», то в наушниках послышится противный писк. Можно было бы на этом и остановиться, но давайте набросаем примитивное приложение.

    Программу для контроллера и исходный код программы для смартфона можно скачать с GitHub

    Хотелось бы отметить, что вам не обязательно реализовывать всё в железе именно на контроллерах CANNY, вы вполне можете написать программу для Arduino или другого любимого вами контроллера. Изначально я и сам планировал написать дополнительно еще и версию скетча для Arduino, но поскольку убил почти все майские праздники, на подключение CANNY и приложение для смартфона у меня уже просто не осталось сил.

    Часть III: Пишем приложение на Xamarin для Android


    Я знаю, что Xamarin скажем мягко – не самое популярное решение для мобильной разработки. И возможно у вас уже возник вопрос: «Почему я его выбрал?». Ответить на него мне бы хотелось словами из одноименной песни Псоя Короленко:



    Честно, нет никаких объективных причин. Просто пару лет назад я учил азы C# и все хотел посмотреть, что такое Xamarin. И теперь из-за «самоизоляции» наконец-то дошли руки.

    Ну и еще раз напомню. Я первый раз встречаюсь с Xamarin и это мое первое приложение для Android. Не стоит слепо копировать мой кривой код, если вдруг вы сможете найти более красивое решение, воспользуйтесь им.

    При разработке своей программы я опирался на этот материал. Статья не особо разжевана, да еще и на испанском, поэтому я всё-таки счел уместным поделиться с вами своей вариацией на эту тему.

    Я собирал программу в Visual studio 2019 community edition.

    Первым делом создадим новый пустой проект для Android (Xamarin), как на картинке.



    Я вносил изменения только в три файла, целиком их можно просмотреть на GitHub, а тут разберем только важные части:

    AndroidManifest.xml

    В стандартный шаблон добавлены 2 разрешения:

      <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
    // based on http://alejandroruizvarela.blogspot.com/2014/01/bluetooth-arduino-xamarinandroid.html
    // for this article https://habr.com/ru/post/500454/
    
    
    // based on http://alejandroruizvarela.blogspot.com/2014/01/bluetooth-arduino-xamarinandroid.html
    // for this article https://habr.com/ru/post/500454/
    
    
    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;
            // don't forget change addres to your device:
            private static string address = "98:D3:91:F9:6C:F6";
            // MY_UUID can be saved as is
            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);
                // Set our view from the "main" layout resource
                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);
                                    // transform string for deleate all symbols except 1-4(command from canny).
                                    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;
            // don't forget change addres to your device:
            private static string address = "98:D3:91:F9:6C:F6";
            // MY_UUID can be saved as is
            private static UUID MY_UUID = UUID.FromString("00001101-0000-1000-8000-00805F9B34FB");
            private Stream inStream = null;
    

    Нам здесь важно вбить адрес вашего модуля HC-06 в поле address.

    Поскольку это мой первый опыт разработки для смартфонов в том числе работы с Xamarin, я решил ничего не усложнять поэтому адрес устройства, как и в исходном примере, жестко зафиксирован. Если хотите вы можете посмотреть эту статью, там похоже реализован перебор доступных Bluetooth устройств.

    Идем дальше.

    
            protected override void OnCreate(Bundle savedInstanceState)
            {
    
                base.OnCreate(savedInstanceState);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
                // Set our view from the "main" layout resource
                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).

    Проверяем подключение по Bluetooth:

    
            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);
                        }
                    }
                }
            }
    

    При включении начинаем соединение с Bluetooth.

    Реализация непосредственно подключения:

       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);
                                    // transform string for deleate all symbols except 1-4(command from canny).
                                    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);
            }
        }
     }
    

    Как я понимаю в данном блоке кода реализованы отправка сообщения и обработчик события на зарос разрешения доступа к Bluetooth.

    Ну вот и все осталось настроить подключение смартфона для отладки приложения.

    Как ни странно, но все работает:



    Часть IV: Заключение


    Вот как работа программы выглядит в натуре:



    Вот таким был мой первый опыт разработки приложений для смартфона на Android.

    Хотелось бы отметить, что VS 2019 и Xamarin на моем стареньком компьютере работают очень медленно.

    При первой сборке проекта, я реально успел съесть еще этих мягких французских булок да выпить чаю. Также само приложение получилось откровенно убогое, переключатель «вкл/выкл» работает очень не удобно, но с другой стороны учитывая, что я лишь немного знаком с базовыми приёмами разработки для .NET, а также то, что я вообще не программист, я смог даже не проходя целиком ни одного туториала и ни одного урока, за день набросать свое первое приложение. Поэтому порог вхождения для создания элементарного приложения получается достаточно низкий.

    Я так понимаю, контроллеры CANNY можно использовать при тюнинге автомобилей, особенно отечественных так, что вполне можно сделать какую-нибудь «фичу» и написать к ней приложение для смартфона. Главное помнить, что при питании от бортовой сети автомобиля на выходах контроллера будет тоже напряжение что и на входе (например, 12 В вместо 5 В). Не забудьте защитить Bluetooth адаптер, чтобы он ненароком не вышел из строя.

    Статья оказалась для меня очень трудоёмкой, надеюсь, что все было не зря и она вам понравиться.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 15

      +2
      Так-с сразу вопросы:
      Первый Вопрос —
      Забегу немножко вперёд и скажу, что я так и не научился гарантированно получать на смартфоне отправленное с контроллера сообщение. Если с проводным UART в прошлой статье все было очевидно, то с Bluetooth на практике вместо отправленного сообщения может прочитаться только его часть и смысл передаваемой команды нарушиться.

      Почему так? Где проблема? Без надежной работы в таком режиме будет очень сложно сделать хоть какое-то рабочее приложение. Что в обратном направлении? Команда проходит гарантированно или не всегда? Какие задержки между срабатыванием датчиков и отображением на экране телефона?

      2. Насколько хорошо работает подключение — то есть, если сделали сопряжение, потом выключили вашу штуку или BT на телефоне, затем включили, зашли в приложение, нажали на Connect — соединение всегда устанавливается? Быстро? Что происходит, если вы выходите из зоны видимости, а затем возвращаетесь.

      3. Я так понимаю, что Serial over Bluetooth и приложения под него можно делать только под Андроид. С яблоками так не получится и требуется изобретать другой путь, если хочется сделать универсальный вариант?

        +2
        Добрый день, я прям всю статью пишу, про то, что это мой первый опыт и с тем и с тем, и что я не программист а вы сразу такие сложные вопросы.

        Жалко конечно, что Вы поздно написали, я уже все барахло разобрал и запихал обратно по коробкам, на час раньше я бы хоть часть вопросов перепроверил, а все заново собирать пока не хочу и так большую часть праздников на это «потерял».

        В принципе я глубоко приложение не тестировал. Только слегка его успел поковырять:
        но думаю, в части Android можно наладить прием больше чем одного символа, просто у меня сейчас не было цели сделать, полезное приложение. Тот пример, что я брал за «базу» обрабатывал символы как-то нестабильно, то один символ за раз, то два, то три (я посылал команду в 4 символа с учетом перевода строки).

        Причем я так понимаю, что если бы я сделал все на Arduino получил такой же эффект. Вот тут поднимался похожий вопрос, я так понимаю можно изловчиться, но надо лучше знать .NET чем я знаю сейчас. Я так стараюсь вспомнить, вроде бы при этом сами по себе данные не терялись (а может и терялись, я уже не помню), просто приходили в разное время, так что думаю можно было бы сделать буфер и проверять его наполнение, я даже что-то пытался такое вчера ночью сделать, но у меня не особо получилось и я принял более «топорное решение» устранив проблему на уровне сигнала отправляемого контроллером.

        Я в принципе не до конца понимаю, как работает bluetooth serial. Но проверил 3-4 приложения на андройд из магазина приложений и они все читали по одному — два символа, bluetooth serial terminal читал 4 символа… а вот если отправлять более длинное сообщение уже начинались проблемы.

        Честно с телефоном по квартире я не ходил, потому что при отладке в него был воткнут провод и мне как-то в голову не пришло, проверить зону покрытия. Но даже если есть проблемы с подключением и покрытием, то думаю это, точно решаемо за счет усложнения логики, проверки других событий системы и восстановления подключения если отвалиться. Просто как я уже писал в самой статье я не хотел усложнять, я даже адрес HC-06 забил в код, вместо реализации выбора из списка доступных устройств.

        Реакция на изменение датчиков примерно около секунды (а может и меньше когда всё в порядке), но иногда бывает, что в цикле вместо команды считывается текст: "\n 5", тогда реакции надо ждать до следующей итерации цикла, чтобы прочитать значимую цифру команды.

        Отправка звукового сигнала в первый раз всегда срабатывала идеально, включалась быстро, но вот два раза подряд не удавалось запустить, (нужно было выждать паузу) но это я подозреваю из-за того, как я написал диаграмму для canny. Возможно я не очень корректно использовал блок 5 секундной задержки, вместо него можно было вставить блок ШИМ с полным заполнением на 5 секунд или еще что-нибудь придумать.

        Про IOS ответить ничего не могу у меня никогда «Айфона» не было, я как-то даже изначально не ставил вопрос на проверку работоспособности для IOS, все равно проверить мне было бы не на чем.

        Сожалею, что не могу ответить на вопросы подробней, надеюсь кто-нибудь более опытный придет в эту ветку комментариев и подскажет. Ну или может я когда-нибудь еще повторю опыты и появится новая информация.
          +1
          3. Я так понимаю, что Serial over Bluetooth и приложения под него можно делать только под Андроид. С яблоками так не получится и требуется изобретать другой путь, если хочется сделать универсальный вариант?

          HC-05/06 старые модули которые работают на SPP поэтому с яблоками сложно. Уберите эти модули возьмите NRF52 и уже там спокойно можно что к яблокам что к андроидам, полноценный ble 4-5.
            0
            возьмите NRF52 и уже там спокойно можно что к яблокам что к андроидам, полноценный ble 4-5.

            Как вы думаете, это решило бы проблемы автора статьи тоже?
              0
              Не факт, там разница в цене больше чем в 10 раз. Если учесть, что HC-06 я покупал за копейки просто побаловаться, то NRF52 стоит уже как недорогой смартфон.
                0
                Я имел ввиду вопросы с приемом и посылкой сообщений
                  0
                  я так понимаю там и принцип передачи другой, и библиотеки для работы другие, так-то наверняка поможет если кто-то серьезную систему будет собирать, а для задачки из двух датчиков, наверное проще еще помучить HC-06 (учитывая разницу в цене), наверняка там можно и более стабильно данные получать, если будет когда-то скучно, попробую еще раз все собрать и поковыряться.
                    0
                    Для Вас как для разработчика софта логика по сути не меняется, отличия уже на аппаратной части, и протоколе передачи данных, пользователю отдается тот же самый уарт в самом простом случае.
                    P.S. и насчет цены 52810 самый дешевый стоит даже дешевле чем HC-6 смотрю по ценам али
                    0
                    тут вообще на самом деле все очень странно. Я не очень хорошо понимаю откуда вообще вылезла такая проблема, а может просто давно не игрался с этими модулями.
                    0
                    нрф52 стоит от 250р я не думаю что настолько критично, ну и не забывайте что нрф52 не требует никакой ардуины и чего то еще, он сам по себе
                      0
                      Это я так понимаю голый чип стоит сотни рублей, это как вместо Ардуино предложить просто купить голый AVR контроллер или я не прав?
                        +1
                        это готовый модуль на али
                          0
                          нашел за 250 рублей, надо будет когда-то попробовать
              +1
              сам по себе модуль НС-06 имеет на борту микроконтроллер с 8ю портами наружу, теоретически можно было бы подключать к нему датчики напрямую. я даже поставил блюлаб и проект на этот модуль, но особого прогресса не достиг.
                +1
                модуль сильно старый и ковырятся с ним смысла нет, лучше в сторону более новых смотрите

              Only users with full accounts can post comments. Log in, please.