Комплект контроллера бассейна — Raspberry Pi 2 + Arduino, релейные модули, датчики температуры и аксессуары
Компоненты и материалы
| Raspberry Pi 2 Model B | ×1 |
| PNY 16GB Turbo MicroSDXC CL10 | ×1 |
| SparkFun Arduino Pro Mini 328 — 5V/16MHz | ×1 |
| SainSmart 5V 4-канальная плата твердотельных реле | ×1 |
| Модуль реле 5V Tolako для Arduino | ×1 |
| Водонепроницаемые датчики температуры DS18B20 | ×1 |
| Резисторы 4,7 кОм 1/4 Вт 5% (10 шт.) | ×1 |
| USB Wi-Fi адаптер для Raspberry Pi | ×1 |
| USB-продление Male-Female 1 фут | ×1 |
| Хомуты American Valve CL40PK6 №40 (упаковка 6 шт.) | ×1 |
| Эпоксидный клей J-B Weld 8272 MarineWeld — 2 унции | ×1 |
| Прокладка сальника | ×2 |
| Блок питания Micro USB (сетевой адаптер) | ×1 |
Необходимые инструменты и оборудование
| 3D-принтер Printrbot Simple | Для печати корпуса и креплений датчиков |
| FTDI USB-TTL адаптер | Для загрузки скетча в Arduino Pro Mini |
Программное обеспечение и онлайн-сервисы
| Microsoft Windows 10 IoT Core |
| Microsoft Visual Studio 2015 |
| Microsoft IIS |
| Arduino IDE |
| Библиотека OneWire |
| Библиотека Dallas Temperature |
| openHAB — открытое ПО для домашней автоматизации |
О проекте
Автоматизированный контроллер бассейна
Дважды за три месяца выходил из строя таймер моего бассейна. Это вдохновило меня на создание данного проекта. Замена таймеров обошлась более чем в $120, а в итоге я получил прибор с минимальным функционалом и высокой частотой отказов. Также вышел из строя датчик температуры солнечного водонагревателя — ещё $30 ущерба.

Я понял, что могу создать экономичный автоматизированный контроллер бассейна, дающий гораздо больше возможностей управления насосом. Мне нужно было учитывать не только время и день недели, как в старом таймере, но и дополнительные параметры: температуру наружного воздуха, воды в солнечном коллекторе и в бассейне. Также важной целью стал удалённый доступ с любого устройства.
Проект получился очень экономичным: используется Raspberry Pi на Windows 10 IoT Core, реле, Arduino Pro Mini, датчики температуры, проводка и 3D-печатные детали. Вся система обошлась дешевле двух таймеров и солнечного датчика вместе взятых.
Управление насосом бассейна (компоненты переменного тока)


Начал я с управления твердотельными реле от Raspberry Pi на Windows 10 IoT Core. Эти реле позволяют управлять компонентами переменного тока — насосом бассейна. Твердотельные реле управляют существующими 30-амперными реле переменного тока, которые использовал старый таймер. После проектирования и тестирования схемы для насоса бассейна я добавил управление водопадом, подсветкой бассейна и двором. Теперь все эти элементы можно управлять удалённо — не нужно открывать щит управления для включения водопада, света или настройки таймера.

Корпус контроллера бассейна
Мой сын спроектировал корпус контроллера и напечатал его на 3D-принтере, обеспечив надёжное размещение Raspberry Pi и твердотельных реле внутри коробки.




Датчики температуры

Одной из целей было управление на основе дополнительных переменных, а не только времени. Я хотел учитывать температуру наружного воздуха, воды в солнечном коллекторе и в бассейне для решения о включении насоса. Критически важный сценарий — когда наружная температура близка к замерзающей, а вода в бассейне тоже остывает: нужно включить насосы бассейна и водопада, чтобы предотвратить замерзание труб. Этот проект позволяет делать это удалённо. Для реализации я добавил датчики температуры, считываемые Arduino Pro Mini, который передаёт данные на Raspberry Pi по интерфейсу I2C.
Датчик температуры наружного воздуха
Первым был установлен датчик наружного воздуха. Мой сын спроектировал и напечатал крепление. Он пробовал PLA и ABS — ABS показал себя лучше благодаря устойчивости к погодным условиям и более высокой температуре плавления. Обязательно печатайте с заполнением не менее 75%. Датчик подключён согласно схеме.


Датчики температуры воды
Затем я добавил датчики температуры воды бассейна и солнечного коллектора. Это позволяет не только отображать данные пользователю, но и использовать их для автоматического управления. Крепления спроектированы и напечатаны на 3D-принтере — снова лучше использовать ABS с заполнением не менее 75%.

Сборка и установка водяных датчиков температуры
После печати крепления я использовал сверло для фаски 45° вокруг отверстия датчика — это увеличивает площадь сцепления с эпоксидным клеем. Шероховатость после сверла улучшает адгезию JB Weld.

Датчик вставляется в крепление так, чтобы он выходил примерно на 2 см изнизу. Прокладка сальника фиксирует его.

Затем верхнюю часть крепления заливают JB Weld и дают высохнуть 24 часа.

Важное примечание: Перед установкой водяных датчиков обязательно отключите все насосы!
После отключения насосов рекомендуется открыть клапаны для сброса давления в зоне установки — это сильно облегчит монтаж и сохранит вас сухим.
Сверлите отверстие 8 мм (5/16 дюйма) в трубе бассейна. Установите датчик и закрепите двумя хомутами. Не затягивайте их слишком сильно — это раздавит крепление. Закройте клапаны, включите насосы и проверьте на течь.




Управление клапаном солнечного коллектора

После установки датчиков я спроектировал и установил управление клапаном солнечного водонагревателя. Солнечная система использует постоянный ток, в отличие от переменного тока остальных компонентов. Это потребовало применения реле постоянного тока. Принцип похож, но реле должны соответствовать типу управляемого напряжения.
Это управление позволяет направлять воду бассейна на солнечные панели на крыше. Вода подаётся на панели только когда температура наружного воздуха выше 15°C (60°F). После включения клапана система проверяет, что возвращающаяся вода как минимум на 1-2°C теплее воды в бассейне — иначе перекачка бесполезна.
Схема подключения приведена в разделе «Схемы» (DC Components).
Разработка приложения
После установки Windows 10 IoT Core на Raspberry Pi я обнаружил встроенный веб-сервер для управления. Подумал: не является ли это урезанной версией IIS? Если да, можно было бы написать REST-сервисы на IIS. Но после долгих поисков выяснилось — этот путь нереализуем на данный момент.
Выбрав другой подход, я изучил пример «Blinky Web Server» и статью на Druss Blog. Решил написать фоновое приложение Windows 10 IoT Core — простой HTTP-сервер, отвечающий на GET и POST запросы.
За несколько дней получился рабочий прототип, что дало уверенность в успехе. После тщательного тестирования в отладчике Visual Studio 2015 я думал, что развёртывание пройдёт легко.
Развёртывание приложения
Здесь возникли трудности, которыми делюсь, чтобы вы их избежали. Думал, что достаточно переключить режим с Debug на Release. Приложение развернулось и запустилось в режиме отладки, но после остановки отладки запуск через AppX Manager выдавал ошибку «Application failed to initialize».
Решение: удалить текущее развёртывание и установить приложение заново через AppX Manager. Это сэкономит вам много времени.
Даже работая безупречно в отладчике, приложение падало после первого HTTP-запроса. Причина так и не найдена.
Под давлением сроков я переключился на архитектуру «Blinky Web Server». В моём варианте не было нужды в графическом приложении, так как веб-сервер управлял GPIO и считывал I2C. Я заставил графическое приложение запускать веб-сервер, который отправляет сообщения обратно для отображения принятых HTTP-вызовов. Этот подход работает стабильно с тем же кодом.
Пользовательский интерфейс
Наконец, я создал HTML-панель управления, работающую на практически любом устройстве. Она позволяет не только управлять насосом бассейна, водопадом и светом, но и мониторить все датчики удалённо.

Позже я интегрировал openHAB и создал карту сайта для альтернативного интерфейса.

Надеюсь, вам было интересно читать о проекте так же, как мне его создавать. Спасибо!
Программный код
- Скетч Arduino для датчиков температуры по I2C
- PoolWebServer — BackgroundTask.cs
- PoolWebServer — Devices.cs
- PoolWebServer — Sensors.cs
- PoolWebService — MainPage.xaml.cs
- PoolWebService — App.xaml.cs
- Карта сайта openHAB
- Элементы openHAB
Скетч Arduino для датчиков температуры по I2C C++
Код считывает датчики DS18B20 и передаёт данные по I2C при запросе.
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#define SLAVE_ADDRESS 0x40
const int POOL_PIN = 3;
const int SOLAR_PIN = 5;
const int OUTSIDE_PIN = 7;
const int I2C_BUFFER_LEN = 24;
OneWire poolTemp(POOL_PIN);
OneWire solarTemp(SOLAR_PIN);
OneWire outsideTemp(OUTSIDE_PIN);
DallasTemperature poolSensor(&poolTemp);
DallasTemperature solarSensor(&solarTemp);
DallasTemperature outsideSensor(&outsideTemp);
char data[I2C_BUFFER_LEN];
String temperatureData;
long prevMillis = 0;
long interval = 1000;
void setup(void) {
poolSensor.begin();
solarSensor.begin();
outsideSensor.begin();
Wire.begin(SLAVE_ADDRESS);
Wire.onRequest(requestEvent);
}
void loop(void) {
unsigned long currMillis = millis();
if (currMillis - prevMillis > interval) {
prevMillis = currMillis;
readTemperatures();
}
}
void readTemperatures() {
poolSensor.requestTemperatures();
solarSensor.requestTemperatures();
outsideSensor.requestTemperatures();
temperatureData = padRight(String(poolSensor.getTempFByIndex(0)) + "|" +
String(solarSensor.getTempFByIndex(0)) + "|" +
String(outsideSensor.getTempFByIndex(0)), I2C_BUFFER_LEN);
}
String padRight(String inStr, int inLen) {
while (inStr.length() < inLen) inStr = inStr + " ";
return inStr;
}
void requestEvent() {
temperatureData.toCharArray(data, I2C_BUFFER_LEN);
Wire.write(data);
}
PoolWebServer — BackgroundTask.cs C#
Определяет HTTP-сервер, отвечающий на POST и GET запросы.
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Http;
using Windows.Foundation.Collections;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.AppService;
using Windows.System.Threading;
using Windows.Networking.Sockets;
using System.IO;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Devices.Gpio;
namespace WebServerTask {
public sealed class WebServerBGTask : IBackgroundTask {
public void Run(IBackgroundTaskInstance taskInstance) {
taskInstance.Canceled += OnCanceled;
serviceDeferral = taskInstance.GetDeferral();
var appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
if (appService != null && appService.Name == "App2AppComService") {
appServiceConnection = appService.AppServiceConnection;
appServiceConnection.RequestReceived += OnRequestReceived;
}
}
// ... (остальной код класса HttpServer и обработчиков запросов)
}
}
PoolWebServer — Devices.cs C#
Класс определяет все устройства и их GPIO-пины.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Gpio;
namespace WebServerTask {
public static class Devices {
private const int POOL_PUMP_PIN = 12;
private const int POOL_WATERFALL_PIN = 13;
private const int POOL_LIGHTS_PIN = 16;
private const int YARD_LIGHTS_PIN = 18;
private const int POOL_SOLAR_VALVE_PIN = 22;
private static GpioPin poolPumpPin;
private static GpioPin poolWaterfallPin;
private static GpioPin poolLightsPin;
private static GpioPin yardLightsPin;
private static GpioPin poolSolarValvePin;
public static GpioPinValue PoolPumpPinValue {
get { return poolPumpPin.Read(); }
set { if (poolPumpPin.Read() != value) poolPumpPin.Write(value); }
}
public static string PoolPumpState {
get { return GetState(PoolPumpPinValue, GpioPinValue.High); }
}
// ... (остальные свойства для Waterfall, Lights, YardLights, SolarValve)
public static void InitDevices() {
var gpio = GpioController.GetDefault();
if (gpio != null) {
poolPumpPin = gpio.OpenPin(POOL_PUMP_PIN);
poolPumpPin.Write(GpioPinValue.Low);
poolPumpPin.SetDriveMode(GpioPinDriveMode.Output);
// ... инициализация остальных пинов аналогично
}
}
private static string GetState(GpioPinValue value, GpioPinValue ActiveState) {
return value == ActiveState ? "ON" : "OFF";
}
}
}
PoolWebServer — Sensors.cs C#
Класс определяет датчики температуры и I2C-интерфейс для их опроса.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.I2c;
namespace WebServerTask {
public static class Sensors {
private static I2cDevice Device;
private static Timer periodicTimer;
private static int ReadInterval = 4000;
private static string poolTemperature = "--.--";
private static string solarTemperature = "--.--";
private static string outsideTemperature = "--.--";
public static string PoolTemperature { get { lock (poolTemperature) { return poolTemperature; } } set { lock (poolTemperature) { poolTemperature = value; } } }
public static string SolarTemperature { get { lock (solarTemperature) { return solarTemperature; } } set { lock (solarTemperature) { solarTemperature = value; } } }
public static string OutsideTemperature { get { lock (outsideTemperature) { return outsideTemperature; } } set { lock (outsideTemperature) { outsideTemperature = value; } } }
async public static void InitSensors() {
var settings = new I2cConnectionSettings(0x40);
settings.BusSpeed = I2cBusSpeed.StandardMode;
string aqs = I2cDevice.GetDeviceSelector("I2C1");
var dis = await DeviceInformation.FindAllAsync(aqs);
Device = await I2cDevice.FromIdAsync(dis[0].Id, settings);
periodicTimer = new Timer(Sensors.TimerCallback, null, 0, ReadInterval);
}
private static void TimerCallback(object state) {
byte[] RegAddrBuf = new byte[] { 0x40 };
byte[] ReadBuf = new byte[24];
try { Device.Read(ReadBuf); } catch (Exception) { }
char[] cArray = System.Text.Encoding.UTF8.GetString(ReadBuf, 0, 23).ToCharArray();
String c = new String(cArray).Trim();
string[] data = c.Split('|');
try {
if (data[0].Trim() != "") PoolTemperature = data[0];
if (data[1].Trim() != "") SolarTemperature = data[1];
if (data[2].Trim() != "") OutsideTemperature = data[2];
} catch (Exception) { }
}
}
}
PoolWebService — MainPage.xaml.cs C#
Главная страница приложения, запускающая веб-сервер.
// Copyright (c) Microsoft. All rights reserved.
using System;
using Windows.ApplicationModel.AppService;
using Windows.Devices.Gpio;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace PoolWebService {
public sealed partial class MainPage : Page {
AppServiceConnection appServiceConnection;
public MainPage() { InitializeComponent(); InitializeAppSvc(); }
private async void InitializeAppSvc() {
string WebServerStatus = "PoolWebServer failed to start...";
appServiceConnection = new AppServiceConnection();
appServiceConnection.PackageFamilyName = "PoolWebServer_hz258y3tkez3a";
appServiceConnection.AppServiceName = "App2AppComService";
var res = await appServiceConnection.OpenAsync();
if (res == AppServiceConnectionStatus.Success) {
var message = new ValueSet(); message.Add("Command", "Initialize");
var response = await appServiceConnection.SendMessageAsync(message);
if (response.Status != AppServiceResponseStatus.Success) {
WebServerStatus = "PoolWebServer failed to start.";
throw new Exception("Failed to send message");
}
appServiceConnection.RequestReceived += OnMessageReceived;
WebServerStatus = "PoolWebServer started.";
}
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { txtWebServerStatus.Text = WebServerStatus; });
}
private async void OnMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) {
var message = args.Request.Message;
string msgRequest = message["Request"] as string;
string msgResponse = message["Response"] as string;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
txtRequest.Text = msgRequest; txtResponse.Text = msgResponse;
});
}
}
}
OpenHAB Sitemap JavaScript
sitemap default label="Windows 10 IoT" {
Frame label="" {
Text label="Pool" icon="swimmingpool" {
Switch item=PoolPump mappings=[ON="ON", OFF="OFF"]
Switch item=WaterFall mappings=[ON="ON", OFF="OFF"]
Switch item=PoolLights mappings=[ON="ON", OFF="OFF"]
Text item=pooltemp
Text item=solartemp
Text item=outsidetemp
}
}
}
OpenHAB Items Plain text
Switch PoolPump "Pool Pump" (grp1) {http=">[ON:POST:https:///DEVICES/POOLPUMP/ON] >[OFF:POST:https:///DEVICES/POOLPUMP/OFF] <[https:///DEVICES/POOLPUMP/STATE:1500:REGEX((.*?))]", autoupdate="true"}
Switch WaterFall "Water Fall" (grp1) {http=">[ON:POST:https:///DEVICES/WATERFALLPUMP/ON] >[OFF:POST:https:///DEVICES/WATERFALLPUMP/OFF] <[https:///DEVICES/WATERFALLPUMP/STATE:1500:REGEX((.*?))]", autoupdate="true"}
Switch PoolLights "Pool Lights" (grp1) {http=">[ON:POST:https:///DEVICES/POOLLIGHTS/ON] >[OFF:POST:https:///DEVICES/POOLLIGHTS/OFF] <[https:///DEVICES/POOLLIGHTS/STATE:1500:REGEX((.*?))]", autoupdate="true"}
Number pooltemp "Pool Water Temp [%.2f F]" (grp1) {http="<[https:///SENSORS/POOLTEMP:30000:REGEX((.*?))]"}
Number solartemp "Solar Water Temp [%.2f F]" (grp1) {http="<[https:///SENSORS/SOLARTEMP:30000:REGEX((.*?))]"}
Number outsidetemp "Outside Air Temp [%.2f F]" (grp1) {http="<[https:///SENSORS/OUTSIDETEMP:30000:REGEX((.*?))]"}
Репозиторий GitHub: Полный проект Visual Studio 2015 — Windows-10-IoT-PoolController
3D-модели деталей и корпусов
- Крепление датчика DS18B20 для измерения температуры воздуха
- Крепление датчика DS18B20 на стандартную трубу бассейна
- Корпус для Raspberry Pi и реле (два варианта)
Схемы подключения
- Подключение Raspberry Pi к реле переменного тока — управление насосом бассейна, водопадом, подсветкой бассейна и двором
- Подключение Raspberry Pi к реле постоянного тока — управление клапаном солнечного коллектора
- Подключение Raspberry Pi к Arduino Pro Mini и датчикам температуры — мониторинг воды бассейна, солнечного коллектора и наружного воздуха
Производственный процесс
- Профессиональный мониторинг температуры на Raspberry Pi с датчиком DS18B20
- Сборка уличной метеостанции на Raspberry Pi 2 и ADS‑WS1
- Точный мониторинг температуры в серверной на Raspberry Pi
- Создание умного домашнего контроллера 433 МГц с Sensorflare и Raspberry Pi
- Продвинутое отслеживание теннисного мяча на Raspberry Pi с OpenCV
- Создание профессионального универсального пульта на Raspberry Pi с LIRC
- Управление светодиодом с PIR-датчиком движения на Raspberry Pi
- Преобразование RF в DC с Raspberry Pi: сборка и отладка детектора огибающей
- Cycle Chaser: превратите свой велосипед в ночной световый шоу с Raspberry Pi
- Построение низкобюджетного датчика влажности почвы на Raspberry Pi для умного полива