Skocz do zawartości

Pompa ciśnienia/podciśnienia sterowana radiowo


Pomocna odpowiedź

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

35 minut temu, kamolix321 napisał:

W jaki sposób kontroluje się urządzenie przez BLE? Napisał Pan dedykowaną aplikację na telefon czy może wykorzystuje gotową jak serial bluetooth?

Aktualnie steruję nią komendami przez zwykły interfejs GATT (BLE Hero), w wolnej chwili (czyli jak będzie mi potrzebny) myślę napisać program na komputer (Windowsa) na bazie mojego projektu - IRIS, w sumie przydałoby się też zaktualizować nieco wpis na Forum dotyczący tego projektu...

  • Pomogłeś! 1
(edytowany)

Bardzo ciekawe urządzenie, ale w opisie znajdujemy rzeczy banalne ( np. typ zastosowanego gniazdka sieciowego), a naprawdę ciekawe rzeczy - np.

Dnia 31.12.2025 o 18:07, H1M4W4R1 napisał:

kupiłem tani pilot RF z dwoma przyciskami (A/B) i sparowałem go przy pomocy nadajnika SYN115

pozostają nieopisane.

Jak się robi takie parowanie?

Edytowano przez jand
  • Lubię! 1
4 minuty temu, jand napisał:

Bardzo ciekawe urządzenie, ale w opisie znajdujemy rzeczy banalne ( np. typ zastosowanego gniazdka sieciowego)

Kolega zapomina, że czytają to także początkujący 😉 

5 minut temu, jand napisał:

Jak się robi takie parowanie?

To temat na osobny artykuł. W dużym uproszczeniu nadaje się cały czas określoną sekwencję (w tym przypadku były to specyficzne wartości i pierwszy protokół rc-switch). A potem wciska na pilocie klawisze zgodnie z instrukcją producenta:

Do wyczyszczenia danych: przytrzymać oba przyciski aż dioda mignie kilka razy, puścić górny, nacisnąć go 3-5 razy. Dioda powinna znów mignąć, a po naciśnięciu się nie zapalać.

Do ustawienia kodu: przytrzymać wyczyszczony przycisk przez 3-5 sekwencji identycznego sygnału 433MHz.

Inne piloty zazwyczaj mają nieco inną procedurę.

10 minut temu, H1M4W4R1 napisał:

czytają to także początkujący

I dlatego mi się bardzo podoba, że podajesz linki do wszystkich najważniejszych elementów - łatwiej sobie wyobrazić jak ta konstrukcja wygląda.

13 minut temu, H1M4W4R1 napisał:

To temat na osobny artykuł.

Miejmy nadzieję, że znajdziesz trochę czasu by to opisać. 😀 👍

  • Lubię! 1
using System.Diagnostics.CodeAnalysis;
using IRIS.Bluetooth.Common.Abstract;
using IRIS.Bluetooth.Common.Data;
using IRIS.Bluetooth.Devices;
using IRIS.Operations;
using IRIS.Operations.Abstract;
using IRIS.Operations.Configuration;
using IRIS.Operations.Data;
using IRIS.Utility;

namespace Pump_Configurator
{
    /// <summary>
    ///     Remote Pump built for Carvera Air by H1M4W4R1
    /// </summary>
    public sealed class RemotePumpBLEDevice()
        : BluetoothLowEnergyDeviceBase(PUMP_SERVICE_UUID, RegexType.Service)
    {
        private const string PUMP_SERVICE_UUID = "ae615e5d-b4be-428e-8ff9-9348c929a36e";
        private const string EXPECTED_SESSION_TIME_UUID = "ae615c93-0000-4b7e-95ea-38792724bf8f";
        private const string IS_PUMP_WORKING_UUID = "ae615056-0001-4c20-a036-408083930b06";
        private const string IS_VALVE_CLOSED_UUID = "ae615d1c-0002-440c-8e7a-a976b1e560bc";
        private const string CURRENT_SESSION_TIME_UUID = "ae61502e-0003-4bd1-8440-408169e0323a";
        private const string CAN_VALVE_BE_REMOTELY_CONTROLLED_UUID = "ae615096-0004-49ff-9ab0-fa194359595a";
        private const string EXPECTED_PUMP_WORKING_TIME_UUID = "ae6152b7-0005-49c3-975e-42c7fa2e14c9";
        private const string CURRENT_PUMP_WORKING_TIME_UUID = "ae6153c8-0006-4dd3-b764-80715dde7cab";

        private IBluetoothLECharacteristic? _expectedSessionTimeCharacteristic;
        private IBluetoothLECharacteristic? _isPumpWorkingCharacteristic;
        private IBluetoothLECharacteristic? _isValveClosedCharacteristic;
        private IBluetoothLECharacteristic? _currentSessionTimeCharacteristic;
        private IBluetoothLECharacteristic? _canValveBeRemotelyControlledCharacteristic;
        private IBluetoothLECharacteristic? _expectedPumpWorkingTimeCharacteristic;
        private IBluetoothLECharacteristic? _currentPumpWorkingTimeCharacteristic;

        [MemberNotNull(nameof(_expectedSessionTimeCharacteristic), nameof(_isPumpWorkingCharacteristic),
            nameof(_isValveClosedCharacteristic), nameof(_currentSessionTimeCharacteristic),
            nameof(_canValveBeRemotelyControlledCharacteristic), nameof(_expectedPumpWorkingTimeCharacteristic),
            nameof(_currentPumpWorkingTimeCharacteristic))]
        public override async ValueTask<IDeviceOperationResult> Configure()
        {
            _expectedSessionTimeCharacteristic = await Require(PUMP_SERVICE_UUID, EXPECTED_SESSION_TIME_UUID,
                CharacteristicFlags.Read | CharacteristicFlags.Write);
            _canValveBeRemotelyControlledCharacteristic = await Require(PUMP_SERVICE_UUID,
                CAN_VALVE_BE_REMOTELY_CONTROLLED_UUID,
                CharacteristicFlags.Read | CharacteristicFlags.Write);
            _expectedPumpWorkingTimeCharacteristic = await Require(PUMP_SERVICE_UUID,
                EXPECTED_PUMP_WORKING_TIME_UUID,
                CharacteristicFlags.Read | CharacteristicFlags.Write);
            _currentSessionTimeCharacteristic = await Require(PUMP_SERVICE_UUID, CURRENT_SESSION_TIME_UUID,
                CharacteristicFlags.Read);
            _currentPumpWorkingTimeCharacteristic = await Require(PUMP_SERVICE_UUID,
                CURRENT_PUMP_WORKING_TIME_UUID,
                CharacteristicFlags.Read);
            
            _isPumpWorkingCharacteristic = await Require(PUMP_SERVICE_UUID, IS_PUMP_WORKING_UUID,
                CharacteristicFlags.Read | CharacteristicFlags.Write);
            _isValveClosedCharacteristic = await Require(PUMP_SERVICE_UUID, IS_VALVE_CLOSED_UUID,
                CharacteristicFlags.Read | CharacteristicFlags.Write);

            return new DeviceConfiguredSuccessfullyResult();
        }

        public async ValueTask<int> GetCurrentSessionTime()
        {
            // Ensure characteristic is provided
            if (_currentSessionTimeCharacteristic == null) return -1;

            // Read data and ensure validity
            IDeviceOperationResult dataResult =
                await _currentSessionTimeCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (!DeviceOperation.IsSuccess(dataResult) || !dataResult.TryGetData(out byte[] bytes)) return -1;
            
            // Convert data to integer
            int value = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
            return value;
        }
        
        public async ValueTask<int> GetCurrentPumpingTime()
        {
            // Ensure characteristic is provided
            if (_currentPumpWorkingTimeCharacteristic == null) return -1;

            // Read data and ensure validity
            IDeviceOperationResult dataResult =
                await _currentPumpWorkingTimeCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (!DeviceOperation.IsSuccess(dataResult) || !dataResult.TryGetData(out byte[] bytes)) return -1;
            
            // Convert data to integer
            int value = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
            return value;
        }
        
        public async ValueTask<IDeviceOperationResult> ChangeValveStatus(bool isValveClosed)
        {
            // Convert to data
            byte[] value = new byte[1];
            value[0] = (byte) (isValveClosed ? 1 : 0);

            // Ensure characteristic is provided
            if (_isValveClosedCharacteristic == null) return new DeviceConfigurationInvalidResult();

            // Write data and validate
            IDeviceOperationResult wasWritten =
                await _isValveClosedCharacteristic.WriteAsync(value);
            if (DeviceOperation.IsFailure(wasWritten)) return wasWritten;

            // Read data to ensure validity
            IDeviceOperationResult dataResult =
                await _isValveClosedCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (DeviceOperation.IsFailure(dataResult) ||
                !dataResult.TryGetData(out byte[] bytes) ||
                bytes[0] != value[0])
                return new DeviceWriteFailedResult();

            // Data valid, written correctly
            return new DeviceWriteSuccessfulResult();
        }
        
        public async ValueTask<IDeviceOperationResult> ChangePumpStatus(bool isPumpEnabled)
        {
            // Convert to data
            byte[] value = new byte[1];
            value[0] = (byte) (isPumpEnabled ? 1 : 0);

            // Ensure characteristic is provided
            if (_isPumpWorkingCharacteristic == null) return new DeviceConfigurationInvalidResult();

            // Write data and validate
            IDeviceOperationResult wasWritten =
                await _isPumpWorkingCharacteristic.WriteAsync(value);
            if (DeviceOperation.IsFailure(wasWritten)) return wasWritten;

            // Read data to ensure validity
            IDeviceOperationResult dataResult =
                await _isPumpWorkingCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (DeviceOperation.IsFailure(dataResult) ||
                !dataResult.TryGetData(out byte[] bytes) ||
                bytes[0] != value[0])
                return new DeviceWriteFailedResult();

            // Data valid, written correctly
            return new DeviceWriteSuccessfulResult();
        }
        
        public async ValueTask<IDeviceOperationResult> ChangeValveRemoteControl(bool isRemoteControlEnabled)
        {
            // Convert to data
            byte[] value = new byte[1];
            value[0] = (byte) (isRemoteControlEnabled ? 0 : 1);

            // Ensure characteristic is provided
            if (_canValveBeRemotelyControlledCharacteristic == null) return new DeviceConfigurationInvalidResult();

            // Write data and validate
            IDeviceOperationResult wasWritten =
                await _canValveBeRemotelyControlledCharacteristic.WriteAsync(value);
            if (DeviceOperation.IsFailure(wasWritten)) return wasWritten;

            // Read data to ensure validity
            IDeviceOperationResult dataResult =
                await _canValveBeRemotelyControlledCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (DeviceOperation.IsFailure(dataResult) ||
                !dataResult.TryGetData(out byte[] bytes) ||
                bytes[0] != value[0])
                return new DeviceWriteFailedResult();

            // Data valid, written correctly
            return new DeviceWriteSuccessfulResult();
        }

        public async ValueTask<IDeviceOperationResult> SetExpectedPumpWorkingTime(int timeSeconds)
        {
            // Convert to bytes
            byte[] timeBytes = new byte[4];
            timeBytes[0] = (byte) (timeSeconds);
            timeBytes[1] = (byte) (timeSeconds >> 8);
            timeBytes[2] = (byte) (timeSeconds >> 16);
            timeBytes[3] = (byte) (timeSeconds >> 24);

            // Ensure characteristic is provided
            if (_expectedPumpWorkingTimeCharacteristic == null) return new DeviceConfigurationInvalidResult();

            // Write data and validate
            IDeviceOperationResult wasWritten = await _expectedPumpWorkingTimeCharacteristic.WriteAsync(timeBytes);
            if (DeviceOperation.IsFailure(wasWritten)) return wasWritten;

            // Read data to ensure validity
            IDeviceOperationResult dataResult =
                await _expectedPumpWorkingTimeCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (DeviceOperation.IsFailure(dataResult) ||
                !dataResult.TryGetData(out byte[] bytes) ||
                bytes[0] != timeBytes[0] ||
                bytes[1] != timeBytes[1] ||
                bytes[2] != timeBytes[2] ||
                bytes[3] != timeBytes[3])
                return new DeviceWriteFailedResult();

            // Data valid, written correctly
            return new DeviceWriteSuccessfulResult();
        }

        public async ValueTask<IDeviceOperationResult> SetExpectedSessionTime(int timeSeconds)
        {
            // Convert to bytes
            byte[] timeBytes = new byte[4];
            timeBytes[0] = (byte) (timeSeconds);
            timeBytes[1] = (byte) (timeSeconds >> 8);
            timeBytes[2] = (byte) (timeSeconds >> 16);
            timeBytes[3] = (byte) (timeSeconds >> 24);

            // Ensure characteristic is provided
            if (_expectedSessionTimeCharacteristic == null) return new DeviceConfigurationInvalidResult();

            // Write data and validate
            IDeviceOperationResult wasWritten = await _expectedSessionTimeCharacteristic.WriteAsync(timeBytes);
            if (DeviceOperation.IsFailure(wasWritten)) return wasWritten;

            // Read data to ensure validity
            IDeviceOperationResult dataResult =
                await _expectedSessionTimeCharacteristic.ReadAsync(new RequestTimeout(1000));
            if (DeviceOperation.IsFailure(dataResult) ||
                !dataResult.TryGetData(out byte[] bytes) ||
                bytes[0] != timeBytes[0] ||
                bytes[1] != timeBytes[1] ||
                bytes[2] != timeBytes[2] ||
                bytes[3] != timeBytes[3])
                return new DeviceWriteFailedResult();

            // Data valid, written correctly
            return new DeviceWriteSuccessfulResult();
        }
    }
}

Tak wygląda sterowanie pompy napisane w IRIS (a raczej interfejs urządzenia, który można teraz wykorzystać w dowolnej aplikacji). Poniżej przykład użycia:

 static async void Test()
 {
   RemotePumpBLEDevice _device = new RemotePumpBLEDevice();
   Console.WriteLine("Connecting to device...");

   // Connect to the device
   if (DeviceOperation.IsFailure(await _device.Connect((RequestTimeout) 15_000)))
   {
     Console.WriteLine("Failed to connect to the device.");
     return;
   }

   Console.WriteLine("Connected to the device.");
   await Task.Delay(500);

   if (DeviceOperation.IsSuccess(await _device.ChangeValveRemoteControl(false)))
   {
     Console.ForegroundColor = ConsoleColor.Green;
     Console.WriteLine("Disabled remote valve control");
     Console.ForegroundColor = ConsoleColor.White;
   }
   else
   {
     Console.ForegroundColor = ConsoleColor.Red;
     Console.WriteLine("Valve remote control change failed");
     Console.ForegroundColor = ConsoleColor.White;
   }

   if (DeviceOperation.IsSuccess(await _device.SetExpectedPumpWorkingTime(30)))
   {
     Console.ForegroundColor = ConsoleColor.Green;
     Console.WriteLine("Expected pump time set to 30 seconds");
     Console.ForegroundColor = ConsoleColor.White;
   }
   else
   {
     Console.ForegroundColor = ConsoleColor.Red;
     Console.WriteLine("Expected pump time write failed");
     Console.ForegroundColor = ConsoleColor.White;
   }

   if (DeviceOperation.IsSuccess(await _device.SetExpectedSessionTime(60)))
   {
     Console.ForegroundColor = ConsoleColor.Green;
     Console.WriteLine("Expected session time set to 60 seconds");
     Console.ForegroundColor = ConsoleColor.White;
   }
   else
   {
     Console.ForegroundColor = ConsoleColor.Red;
     Console.WriteLine("Expected session time write failed");
     Console.ForegroundColor = ConsoleColor.White;
   }

   await Task.Delay(10_000);

   int sessionTime = await _device.GetCurrentSessionTime();
   Console.WriteLine($"Current session time is: {sessionTime}");
   await Task.Delay(500);

   await _device.Disconnect();
   Console.WriteLine("Disconnected from pump");
 }
  • 2 miesiące później...

No i kiedy zacząłem używać mojej pompy z Pythonem (bleak) wyszedł kwiatek... WinRT bardzo nie lubi się z notyfikacjami, gdy usługi nie mają deskryptorów... Tak więc trzeba było je dodać.

    // Pump activity enable/disable
    pumpActiveCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_PUMP_ACTIVITY_STATUS,
        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
    pumpActiveCharacteristic->setCallbacks(new PumpActivityStatusCharacteristicCallbacks());
    pumpActiveCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
    pumpActiveCharacteristic->addDescriptor(new BLE2902());
    pumpActiveCharacteristic->setValue(&is_pump_active, 1);

Przy okazji doszły uprawnienia do charakterystyk (bo deskryptory mają je ustawiane automatycznie), by wszystko działało jak należy. Technicznie warto by jeszcze dodać 0x2901 z czytelnym opisem, ale nie chce mi się kolejny raz rozbierać tej obudowy, więc na ten moment sobie odpuszczę 😉 

  • Lubię! 1

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...