산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

풀 컨트롤러

구성품 및 소모품

라즈베리 파이 2 모델 B
× 1
PNY 16GB Turbo MicroSDXC CL10
× 1
SparkFun Arduino Pro Mini 328 - 5V/16MHz
× 1
SainSmart 5V 4채널 솔리드 스테이트 릴레이 보드
× 1
Tolako 5v Arduino용 릴레이 모듈
× 1
DS18b20 방수 온도 센서
× 1
4.7k 옴 저항기 - 1/4와트 - 5% - 4K7(10개)
× 1
라즈베리 파이 USB WIFI 동글
× 1
남성에서 여성으로 확장되는 1피트 USB
× 1
아메리칸 밸브 CL40PK6 번호 40 클램프, 6팩
× 1
J-B Weld 8272 MarineWeld 해양 에폭시 - 2온스
× 1
시트 와셔
× 2
마이크로 USB 전원 공급 장치 벽면 충전기 AC 어댑터
× 1

필요한 도구 및 기계

Printrbot 단순
인클로저 및 센서 마운트를 만드는 데 사용
Ftdi USB-Ttl 직렬 어댑터 모듈(Arduino 미니 포트용)
Arduino Mini Pro에 스케치를 업로드하는 데 사용

앱 및 온라인 서비스

Microsoft Windows 10 IoT Core
Microsoft Visual Studio 2015
Microsoft IIS
Arduino IDE
OneWire 라이브러리
댈러스 온도 라이브러리
openHAB 오픈 소스 홈 자동화 소프트웨어

이 프로젝트 정보

자동화된 풀 컨트롤러

두 번, 3개월 동안 내 수영장 펌프 타이머가 고장났습니다. 이것이 제가 이 프로젝트를 만들도록 영감을 주었습니다. 이 타이머를 교체하는 데 드는 비용은 120달러가 넘었고 제어할 수 있는 권한이 거의 없고 실패율이 높은 타이머만 보여줘야 했습니다. 나는 또한 30달러의 추가 비용이 드는 태양열 온수기의 온도 센서 고장을 겪었습니다.

수영장 펌프가 작동하는 시기를 훨씬 더 많이 제어할 수 있는 비용 효율적인 자동화 수영장 컨트롤러를 만들 수 있다는 것을 알고 있었습니다. 기존 타이머의 단순한 시간과 요일 대신 펌프가 작동하는 시점에 대해 더 많은 변수를 갖고 싶었습니다. 나는 또한 내 수영장 펌프를 자동화할 수 있을 뿐만 아니라 내 수영장 환경의 다양한 측면의 상태를 모니터링할 수 있기를 원했습니다. 또 다른 목표는 모든 장치를 사용하여 어디서나 이러한 작업을 수행할 수 있도록 하는 것이었습니다.

내가 만든 프로젝트는 Windows 10 IoT Core, 릴레이, Arduino Mini Pro를 실행하는 Raspberry Pi와 온도 센서, 배선 및 3D 인쇄 구성 요소를 활용하기 때문에 매우 비용 효율적입니다. 나는 두 개의 이전 타이머와 태양열 온도 센서에 대해 지불했던 것보다 훨씬 적은 돈으로 이 프로젝트를 완료했습니다.

수영장 펌프 제어(AC 구성 요소)

Windows 10 IoT Core를 실행하는 Raspberry Pi에서 솔리드 스테이트 릴레이를 제어하여 프로젝트를 시작했습니다. 이 릴레이를 사용하면 수영장 펌프와 같은 AC(교류) 구성 요소를 제어할 수 있습니다. 솔리드 스테이트 릴레이는 이전 타이머가 사용했던 기존 30Amp AC 릴레이를 제어합니다. 수영장 펌프의 회로를 설계하고 테스트한 후 수영장 폭포, 수영장 및 마당 조명과 같은 다른 AC 구성 요소를 제어하는 ​​추가 기능을 만들었습니다. 프로젝트의 이 부분을 디자인하면 이 모든 요소를 ​​원격으로 제어할 수 있습니다. 더 이상 내 가족이나 내가 물리적으로 제어 상자를 열어 폭포를 켜거나 수영장이나 마당 조명을 켜거나 수영장 펌프의 타이머를 설정할 필요가 없습니다.

풀 컨트롤러 인클로저

내 아들이 Pool Controller 인클로저를 설계하고 우리의 3D 프린터를 사용하여 제작했으며 Raspberry Pi와 솔리드 스테이트 릴레이가 모두 컨트롤러 상자에 단단히 고정되도록 했습니다.

온도 센서

myproject의 설계 목표 중 하나는 요일 및 시간 외에 변수를 기반으로 제어할 수 있도록 하는 것이었습니다. 외부 공기 온도와 태양열 온수기 및 수영장 수온을 고려하여 펌프가 언제 가동되어야 하고 언제 정지해야 하는지를 결정할 수 있기를 원했습니다. 이러한 유형의 작동이 중요한 경우의 한 가지 예는 외부 공기 온도가 매우 차갑고 거의 동결될 때입니다. 수영장 수온도 동결에 가깝다면 파이프가 얼어 시스템이 손상되는 것을 방지하기 위해 수영장과 워터폴 펌프가 작동하는지 확인해야 합니다. 이 프로젝트를 사용하면 집에 없을 때도 이 작업을 수행할 수 있습니다. 이를 구현하기 위해 온도 센서를 myproject에 통합했습니다. I2Cinterface를 통해 풀과 워터폴 펌프를 제어하는 ​​동일한 Raspberry Pi로 데이터를 보내는 Arduino Mini Pro를 활용하여 해당 센서를 읽었습니다.

외기 온도 센서

외기 온도 센서는 내가 처음으로 통합한 센서였습니다. 다시 말하지만, 내 아들은 3D 프린터에서 센서 마운트를 설계하고 인쇄했습니다. 그는 PLA와 ABS를 모두 시도했습니다. ABS는 내후성이 더 높고 유리 전이 온도가 높아 내열성이 더 우수하기 때문에 실제로 더 잘 작동합니다. 최소 75% 채우기로 인쇄해야 합니다. 센서는 회로도에서 위에서 설명한 대로 연결되었습니다.

수온 센서

그런 다음 수영장 물과 태양열 히터 온도 센서를 통합했습니다. 이를 통해 프로젝트는 사용자에게 표시될 수온 데이터를 수집할 수 있을 뿐만 아니라 특정 구성 요소가 실행되거나 정지된 시간을 결정하기 위한 추가 변수를 제공할 수 있습니다. 먼저 센서 마운트를 설계하고 3D로 인쇄했습니다. 앞서 언급했듯이 ABS는 실제로 더 나은 날씨와 내열성으로 인해 더 잘 작동합니다. 또한 75% 이상의 충전재를 사용해야 합니다. .

수온 센서 구축 및 설치

수온 센서 마운트를 인쇄한 후 카운터싱크 드릴 비트를 사용하여 센서 구멍 주위에 45도 영역을 만들었습니다. 이렇게 하면 JB Weld가 더 많은 표면적을 접착할 수 있습니다. 드릴 비트의 러프 컷이 JB Weld에 더 나은 유지력을 제공하는 것처럼 보였기 때문에 3D 프린트 디자인을 변경하는 것보다 드릴 비트를 사용하여 이 작업을 수행하는 것을 선호했습니다.

다음 단계는 온도 센서가 마운트 바닥에서 약 3/4" 확장될 때까지 마운트에 삽입하는 것입니다. 시트 와셔를 추가하여 제자리에 고정합니다.

그런 다음 마운트 상단을 JB Weld로 채우고 24시간 동안 건조시킵니다.

JB Weld가 마를 때까지 최소 24시간을 기다린 후 수온 센서를 설치할 차례였습니다.

중요 참고사항: 수온 센서를 설치하기 전에 모든 펌프가 꺼져 있는지 확인하세요!

모든 물 펌프가 꺼져 있는지 확인한 후 수온 센서를 설치하려는 영역의 수압을 제거할 수 있는 밸브를 여는 것이 좋습니다. 이렇게 하면 설치가 훨씬 쉬워지고 건조함도 유지됩니다.

수영장 배관에 5/16" 구멍을 뚫습니다. 수온 센서를 설치하고 2개의 클램프를 사용하여 제자리에 단단히 고정합니다. 저와 같은 실수를 하지 말고 클램프를 너무 세게 조이면 센서 마운트가 부서집니다. 닫기 밸브를 잠그고 펌프를 켭니다. 누수 여부를 확인합니다.

태양열 히터 밸브 제어

온도 센서를 배치한 후 태양열 온수기 밸브 컨트롤을 설계하고 설치할 수 있습니다. 태양열 히터는 앞서 언급한 다른 풀 구성 요소와 함께 사용되는 AC 전압과 달리 DC 전압을 사용합니다. 이를 위해서는 AC 릴레이 대신 DC 릴레이를 제어해야 했습니다. 개념은 비슷하지만 필요한 릴레이가 다릅니다. 프로젝트에 사용하는 릴레이가 제어하는 ​​장치에서 사용하는 올바른 유형의 전압을 제어하는지 확인하십시오.

이 컨트롤을 사용하면 수영장 물을 지붕의 태양열 패널로 보낼 수 있습니다. 외부 공기 온도가 60도 이상일 때만 패널에 물을 보내고 싶습니다. 물이 패널로 우회되면 되돌아오는 물이 수영장 물보다 2도 이상 따뜻한지 확인하십시오. 그렇지 않으면 패널에 물을 펌핑하는 것이 에너지 낭비입니다.

이 컨트롤의 배선 및 연결은 Pool Controller DC Components 회로도에 제공됩니다.

애플리케이션 개발

내 Raspberry Pi에 Windows 10IoT Core를 설치한 후 이를 관리하는 데 사용되는 웹 서버가 내장되어 있다는 것을 깨달았습니다. 이것이 IIS의 제거 버전인지 궁금했습니다. 그렇다면 IIS에서 편안한 서비스를 작성하고 이 프로젝트를 위해 호출할 수 있습니다. 많은 웹 검색과 많은 연구 끝에 가능한 것으로 보이지 않았습니다. 그 접근 방식을 선호하지만 현 시점에서는 실현 가능하지 않은 것 같습니다.

다른 접근 방식으로 'Blinky Web Server' 예제와 'Druss Blog' 기사를 검토했습니다. HTTP GET 및 POST 요청에 응답하는 간단한 HTTP 웹 서버 역할을 하는 헤드리스 Windows 10 IoT Core 백그라운드 애플리케이션을 빌드하기로 결정했습니다. .

며칠 만에 작업 프로토타입이 생겼습니다. 이것은 제 프로젝트가 성공할 수 있다는 큰 확신을 주었습니다. 그래서 저는 이 아키텍처로 진행하기로 결정했습니다. Visual Studio 2015 디버거를 통해 내 코드를 철저히 테스트한 후 myapplication을 쉽게 배포할 수 있다는 인상을 받았습니다.

애플리케이션 배포

이것은 제가 고심했던 지점이므로 그러한 어려움을 피하는 방법을 보여 드리고자합니다. 내 애플리케이션은 Visual Studio 2015 디버거에서 철저히 테스트되었기 때문에 디버그에서 릴리스로 모드를 변경하여 내 애플리케이션을 배포할 수 있다는 인상을 받았습니다. 이 접근 방식을 시도했고 실제로 내 애플리케이션을 배포하고 디버그 모드에서 시작했습니다. 그런 다음 디버그를 중지하고 AppX Manager에서 애플리케이션을 실행하려고 했습니다. 이 작업을 시도했을 때 성공하지 못했습니다. "응용 프로그램을 초기화하지 못했습니다."라는 일반 오류가 발생했습니다.

이 문제에 대한 해결책은 현재 배포를 삭제한 다음 대신 AppX Manager에서 애플리케이션을 설치하는 것입니다. 이 작업에는 많은 시간이 소요되므로 이 문제를 방지하는 데 도움이 되었기를 바랍니다.

응용 프로그램이 Visual Studio 2015 디버그 모드에서 완벽하게 실행되더라도 첫 번째 HTTP 요청을 받은 후 종료됩니다. 이 문제를 해결하기 위해 많은 시간을 보냈지만 여전히 왜 이런 일이 발생하는지 모르겠습니다.

이 프로젝트를 완료해야 한다는 압박감에 저는 "Blinky Web Server" 예제처럼 프로젝트를 변경하기로 결정했습니다. 내 구현에서는 웹 서버가 GPIO 핀을 제어하고 I2C 인터페이스(화면 응용 프로그램이 아님)를 읽도록 계획했기 때문에 Windows 10 IoT Core 화면 응용 프로그램의 필요성을 보지 못했습니다. 내 프로젝트에서 한 것은 화면 응용 프로그램이 웹 서버를 시작하도록 하는 것이었습니다. 그런 다음 웹 서버는 화면 응용 프로그램에 메시지를 다시 보내어 내 서버에서 어떤 HTTP 호출을 수신했는지 확인할 수 있습니다. 이 접근 방식은 확고한 것으로 보이며 원래 시도에서 사용한 것과 정확히 동일한 코드입니다.

사용자 인터페이스

마지막으로 모든 장치에서 실행되는 HTML 제어 프로그램을 만들었습니다. 이를 통해 수영장 펌프, 폭포수 및 수영장 조명을 제어할 수 있을 뿐만 아니라 어디서나 추가 센서를 모니터링할 수 있습니다.

나중에 OpenHAB을 활용하여 이 추가 인터페이스를 제공하는 사이트 맵을 만들었습니다.

내가 만든 프로젝트만큼 당신이 내 프로젝트에 대해 읽는 것을 즐겼기를 바랍니다. 감사합니다.

YouTube, Vimeo 또는 Vine 링크를 누르고 Enter 키를 누릅니다.

<섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드">

코드

<울>
  • I2C를 사용하는 온도 센서용 Arduino Sketch
  • PoolWebServer - BackgroundTask.cs
  • PoolWebServer - Devices.cs
  • PoolWebServer - Sensors.cs
  • PoolWebService- MainPage.xaml.cs
  • PoolWebService - App.xaml.cs
  • OpenHAB 사이트맵
  • OpenHAB 항목
  • I2C Java를 사용하는 온도 센서용 Arduino Sketch
    I2C 인터페이스를 통해 요청 시 DS18b20 온도 센서를 읽고 데이터를 보내는 코드.
    #include #include #include #define SLAVE_ADDRESS 0x40 //Define GPIO 핀 constantsconst int POOL_PIN =3;const int SOLAR_PIN =5;const int OUTSIDE_PIN =7;//I2C 인터페이스에 대한 버퍼 길이 정의const int I2C_BUFFER_LEN =24; //IMPORTANT MAX is 32!!!//Load OneWire - 독점 댈러스 반도체 센서 프로토콜 - 라이센스 필요 없음OneWire poolTemp(POOL_PIN);OneWire solarTemp(SOLAR_PIN);OneWire outsideTemp(OUTSIDE_PIN);//Load Dallas - 독점 댈러스 센서 프로토콜 활용 onewire - 라이선스 필요 없음DallasTemperature poolSensor(&poolTemp);DallasTemperature solarSensor(/solarTemp);DallasTemperature outsideSensor(&outsideTemp);//I2C 버퍼 문자 데이터 정의[I2C_BUFFER_LEN];00 문자열 온도 데이터;//0 =타이머에 대한 사전 정의 변수 void setup(void) { //온도 센서 버스에 연결 poolSensor.begin(); SolarSensor.begin(); 외부센서.begin(); //I2C 인터페이스 시작 Wire.begin(SLAVE_ADDRESS); Wire.onRequest(requestEvent);}void loop(void) { //정의된 간격마다 한 번씩 온도 센서를 읽는 시간 모니터링 // 1초보다 빠르게 읽지 마십시오. 그들은 빠른 unsigned long currMillis =millis(); if (currMillis - prevMillis> 간격) { prevMillis =currMillis; 온도 읽기(); }}void readTemperatures() { //3개의 온도 센서 모두 읽기 poolSensor.requestTemperatures(); SolarSensor.requestTemperatures(); 외부센서.요청온도(); //문자열에 온도 데이터 저장 //오래된 데이터를 덮어쓰기 위해 버퍼의 전체 길이로 오른쪽으로 채웁니다. //데이터는 "88.99|78.12|100.00" 형식입니다. 여기서 "PoolTemp|SolarTemp|OutsideTemp" temperatureData =padRight(String(poolSensor.getTempFByIndex(0)) + "|" + String(solarSensor.getTempFByIndex(0)) + "|" + String(outsideSensor.getTempFByIndex(0)), I2C_BUFFER_LEN);}문자열 padRight(문자열 inStr , int inLen) { 동안 (inStr.length()  
    PoolWebServer - BackgroundTask.csC#
    HTTP POST 및 GET 요청에 응답하는 HTTP 서버를 정의합니다.
    // Copyright (c) Microsoft. 모든 권리 보유. 시스템 사용, System.Collections.Generic 사용, System.Linq 사용, System.Text 사용, System.Net.Http 사용, Windows.Foundation.Collections 사용, Windows.ApplicationModel.Background 사용, Windows.ApplicationModel 사용 AppService;Windows.System.Threading 사용;Windows.Networking.Sockets 사용;System.IO 사용;Windows.Storage.Streams 사용;System.Threading.Tasks 사용;System.Runtime.InteropServices.WindowsRuntime 사용;Windows.Foundation 사용;사용 Windows.Devices.Gpio;namespace WebServerTask{ public sealing class WebServerBGTask :IBackgroundTask { public void Run(IBackgroundTaskInstance taskInstance) { // 취소 처리기를 백그라운드 작업과 연결합니다. taskInstance.Canceled +=OnCanceled; // 태스크 인스턴스에서 지연 객체를 가져옵니다. serviceDeferral =taskInstance.GetDeferral(); var appService =taskInstance.TriggerDetails를 AppServiceTriggerDetails로; if (appService !=null &&appService.Name =="App2AppComService") { appServiceConnection =appService.AppServiceConnection; appServiceConnection.RequestReceived +=OnRequestReceived; } } //PoolWebService 앱에서 보낸 메시지 요청 처리 private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var message =args.Request.Message; 문자열 명령 =메시지["명령"] 문자열로; 스위치(명령) { case "초기화":{ Sensors.InitSensors(); 장치.InitDevices(); var 메시지 지연 =args.GetDeferral(); // 호출자에게 반환할 결과 설정 var returnMessage =new ValueSet(); //포트 8888에서 HTTPServer의 새 인스턴스를 정의합니다. HttpServer server =new HttpServer(8888, appServiceConnection); IAsyncAction asyncAction =Windows.System.Threading.ThreadPool.RunAsync( (workItem) => { //서버 시작 server.StartServer(); }); //성공 상태로 PoolWebService에 다시 응답 returnMessage.Add("Status", "Success"); var responseStatus =대기 args.Request.SendResponseAsync(returnMessage); messageDeferral.Complete(); 부서지다; } case "Quit":{ //서비스를 종료하도록 요청했습니다. 서비스 연기 제공 // 플랫폼이 백그라운드 작업을 종료할 수 있도록 serviceDeferral.Complete(); 부서지다; } } } private void OnCanceled(IBackgroundTaskInstance 발신자, BackgroundTaskCancellationReason 이유) { //정리하고 종료 준비 } BackgroundTaskDeferral serviceDeferral; 앱서비스연결 앱서비스연결; } //HTTP WebServer를 정의하는 클래스 public sealing class HttpServer :IDisposable { //HTTP 데이터를 읽을 버퍼 생성 private const uint BufferSize =8192; //개인용 포트에서 수신 대기할 포트 int port =8888; // 전용 읽기 전용 StreamSocketListener 리스너에 대한 리스너; //PoolControllerWebService에 상태 정보를 다시 보내기 위한 연결 private AppServiceConnection appServiceConnection; 공개 HttpServer(int serverPort, AppServiceConnection 연결) { 리스너 =new StreamSocketListener(); 포트 =서버포트; appServiceConnection =연결; //HTTP 연결을 위한 이벤트 핸들러 추가 listener.ConnectionReceived +=(s, e) => ProcessRequestAsync(e.Socket); } // 리스너 시작 호출 public void StartServer() {#pragma warning disable CS4014 listener.BindServiceNameAsync(port.ToString());#pragma warning restore CS4014 } public void Dispose() { listener.Dispose(); } 개인 비동기 무효 ProcessRequestAsync(StreamSocket 소켓) { 시도 { StringBuilder 요청 =new StringBuilder(); //(IInputStream input =socket.InputStream) { byte[] data =new byte[BufferSize]를 사용하여 들어오는 데이터를 가져옵니다. IBuffer 버퍼 =data.AsBuffer(); 단위 데이터 읽기 =버퍼 크기; // 들어오는 모든 데이터 읽기 while (dataRead ==BufferSize) { await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial); 요청.Append(Encoding.UTF8.GetString(데이터, 0, 데이터.길이)); dataRead =버퍼.길이; } } //(IOutputStream output =socket.OutputStream) { string requestMethod =request.ToString(); 문자열[] requestParts ={ "" }; if (requestMethod !=null) { //요청을 부분으로 비크업 requestMethod =requestMethod.Split('\n')[0]; requestParts =requestMethod.Split(' '); } //(requestParts[0] =="GET") await WriteGetResponseAsync(requestParts[1], output)인 경우에만 HTTP GETS 및 POST 메서드에 응답합니다. else if (requestParts[0] =="POST") WritePostResponseAsync(requestParts[1], output)를 기다립니다. 그렇지 않으면 WriteMethodNotSupportedResponseAsync(requestParts[1], 출력)를 기다립니다. } } catch (Exception) { } } // 모든 HTTP GET의 비공개 비동기 작업 처리 WriteGetResponseAsync(string request, IOutputStream os) { bool urlFound =false; byte[] bodyArray =null; 문자열 응답 메시지 =""; //요청이 유효한 요청 URL과 일치하는지 확인하고 응답 메시지 생성 switch (request.ToUpper()) { case "/SENSORS/POOLTEMP":responseMsg =Sensors.PoolTemperature; urlFound =사실; 부서지다; case "/SENSORS/SOLARTEMP":responseMsg =Sensors.SolarTemperature; urlFound =사실; 부서지다; 케이스 "/SENSORS/OUTSIDETEMP":responseMsg =Sensors.OutsideTemperature; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLPUMP/STATE":responseMsg =Devices.PoolPumpState; urlFound =사실; 부서지다; 사례 "/DEVICES/WATERFALLPUMP/STATE":responseMsg =Devices.PoolWaterfallState; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLLIGHTS/STATE":responseMsg =Devices.PoolLightsState; urlFound =사실; 부서지다; 사례 "/DEVICES/YARDLIGHTS/STATE":responseMsg =Devices.YardLightsState; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLSOLAR/STATE":responseMsg =Devices.PoolSolarValveState; urlFound =사실; 부서지다; 기본값:urlFound =false; 부서지다; } bodyArray =인코딩.UTF8.GetBytes(responseMsg); WriteResponseAsync(request.ToUpper(), responseMsg, urlFound, bodyArray, os)를 기다립니다. } // 모든 HTTP POST의 비공개 비동기 작업 처리 WritePostResponseAsync(string request, IOutputStream os) { bool urlFound =false; byte[] bodyArray =null; 문자열 응답 메시지 =""; //요청이 유효한 요청 URL과 일치하는지 확인하고 응답 메시지 생성 switch (request.ToUpper()) { case "/DEVICES/POOLPUMP/OFF":Devices.PoolPumpPinValue =GpioPinValue.Low; bodyArray =Encoding.UTF8.GetBytes("OFF"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLPUMP/ON":Devices.PoolPumpPinValue =GpioPinValue.High; bodyArray =Encoding.UTF8.GetBytes("ON"); 응답 메시지 ="켜짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/WATERFALLPUMP/OFF":Devices.PoolWaterfallPinValue =GpioPinValue.Low; bodyArray =Encoding.UTF8.GetBytes("OFF"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/WATERFALLPUMP/ON":Devices.PoolWaterfallPinValue =GpioPinValue.High; bodyArray =Encoding.UTF8.GetBytes("ON"); 응답 메시지 ="켜짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLLIGHTS/OFF":Devices.PoolLightsPinValue =GpioPinValue.Low; bodyArray =Encoding.UTF8.GetBytes("OFF"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLLIGHTS/ON":Devices.PoolLightsPinValue =GpioPinValue.High; bodyArray =Encoding.UTF8.GetBytes("ON"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/YARDLIGHTS/OFF":Devices.YardLightsPinValue =GpioPinValue.Low; bodyArray =Encoding.UTF8.GetBytes("OFF"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/YARDLIGHTS/ON":Devices.YardLightsPinValue =GpioPinValue.High; bodyArray =Encoding.UTF8.GetBytes("ON"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLSOLAR/OFF":Devices.PoolSolarValvePinValue =GpioPinValue.Low; bodyArray =Encoding.UTF8.GetBytes("OFF"); responseMsg ="꺼짐"; urlFound =사실; 부서지다; 케이스 "/DEVICES/POOLSOLAR/ON":Devices.PoolSolarValvePinValue =GpioPinValue.High; bodyArray =Encoding.UTF8.GetBytes("ON"); 응답 메시지 ="켜짐"; urlFound =사실; 부서지다; 기본값:bodyArray =Encoding.UTF8.GetBytes(""); urlFound =거짓; 부서지다; } WriteResponseAsync(request.ToUpper(), responseMsg, urlFound,bodyArray, os)를 기다립니다. } //지원되지 않는 HTTP 메서드에 대한 응답 쓰기 private async Task WriteMethodNotSupportedResponseAsync(string request, IOutputStream os) { bool urlFound =false; byte[] bodyArray =null; bodyArray =Encoding.UTF8.GetBytes(""); WriteResponseAsync(request.ToUpper(), "지원되지 않음", urlFound, bodyArray, os)를 기다립니다. } // HTTP GET 및 POST의 비공개 비동기에 대한 응답 쓰기 Task WriteResponseAsync(string RequestMsg, string ResponseMsg, bool urlFound, byte[] bodyArray, IOutputStream os) { try //appService는 하루 정도 지나면 종료됩니다. http 서버가 계속 응답할 수 있도록 별도로 잡아봅시다. { var updateMessage =new ValueSet(); updateMessage.Add("요청", RequestMsg); updateMessage.Add("응답", ResponseMsg); var responseStatus =대기 appServiceConnection.SendMessageAsync(updateMessage); } catch(예외) {} try { MemoryStream bodyStream =new MemoryStream(bodyArray); using (스트림 응답 =os.AsStreamForWrite()) { 문자열 헤더 =GetHeader(urlFound, bodyStream.Length.ToString()); byte[] headerArray =Encoding.UTF8.GetBytes(헤더); 응답을 기다립니다.WriteAsync(headerArray, 0, headerArray.Length); if (urlFound) bodyStream.CopyToAsync(response)를 기다립니다. 응답을 기다립니다.FlushAsync(); } } catch(Exception) {} } //찾은 URL과 찾을 수 없는 URL에 대한 HTTP 헤더 텍스트를 생성합니다. string GetHeader(bool urlFound, string bodyStreamLength) { string header; if (urlFound) { 헤더 ="HTTP/1.1 200 OK\r\n" + "Access-Control-Allow-Origin:*\r\n" + "Content-Type:text/plain\r\n" + " 콘텐츠 길이:" + bodyStreamLength + "\r\n" + "연결:닫기\r\n\r\n"; } else { header ="HTTP/1.1 404 Not Found\r\n" + "Access-Control-Allow-Origin:*\r\n" + "Content-Type:text/plain\r\n" + "Content -길이:0\r\n" + "연결 종료\r\n\r\n"; } 반환 헤더; } }}
    PoolWebServer - Devices.csC#
    클래스는 모든 장치와 연결되어 있는 GPIO 핀을 정의합니다. namespace WebServerTask{ //Class는 모든 장치와 장치가 연결된 GPIO 핀을 정의합니다. public static class Devices { //GPIO 핀 번호 정의 private const int POOL_PUMP_PIN =12; 개인 const 정수 POOL_WATERFALL_PIN =13; 개인 const int POOL_LIGHTS_PIN =16; 개인 const int YARD_LIGHTS_PIN =18; 개인 const 정수 POOL_SOLAR_VALVE_PIN =22; //GPIO 핀 정의 private static GpioPin poolPumpPin; 개인 정적 GpioPin poolWaterfallPin; 개인 정적 GpioPin poolLightsPin; 개인 정적 GpioPin yardLightsPin; 개인 정적 GpioPin poolSolarValvePin; //풀 펌프에 할당된 GPIO 핀에 대한 속성 public static GpioPinValue PoolPumpPinValue { get { return poolPumpPin.Read(); //핀이 High 또는 Low를 반환하는 읽기 } set { if (poolPumpPin.Read() !=value) //핀이 변경되는 경우에만 핀을 설정합니다. poolPumpPin.Write(value); } } //Property to read status of the Pool Pump ON or OFF public static string PoolPumpState { get { return GetState(PoolPumpPinValue, GpioPinValue.High); //Get the state } } //Property for GPIO Pin assigned to the Waterfall Pump public static GpioPinValue PoolWaterfallPinValue { get { return poolWaterfallPin.Read(); } set { if (poolWaterfallPin.Read() !=value) poolWaterfallPin.Write(value); } } //Property to read status of the Waterfall Pump ON or OFF public static string PoolWaterfallState { get { return GetState(PoolWaterfallPinValue, GpioPinValue.High); } } //Property for GPIO Pin assigned to the Pool Lights public static GpioPinValue PoolLightsPinValue { get { return poolLightsPin.Read(); } set { if (poolLightsPin.Read() !=value) poolLightsPin.Write(value); } } //Property to read status of the Pool Lights ON or OFF public static string PoolLightsState { get { return GetState(PoolLightsPinValue, GpioPinValue.High); } } //Property for GPIO Pin assigned to the valve to turn Solar on and off public static GpioPinValue PoolSolarValvePinValue { get { return poolSolarValvePin.Read(); } set { if (poolSolarValvePin.Read() !=value) poolSolarValvePin.Write(value); } } //Property to read status of the Solar valve ON or OFF public static string PoolSolarValveState { get { return GetState(PoolSolarValvePinValue, GpioPinValue.High); } } //Property for GPIO Pin assigned to the Yard Lights public static GpioPinValue YardLightsPinValue { get { return yardLightsPin.Read(); } set { if (yardLightsPin.Read() !=value) yardLightsPin.Write(value); } } //Property to read status of the Yard Lights ON or OFF public static string YardLightsState { get { return GetState(YardLightsPinValue, GpioPinValue.High); } } //Intialize all GPIO pin used public static void InitDevices() { var gpio =GpioController.GetDefault(); if (gpio !=null) { //These pins are on an active high relay. We set everything to OFF when we start poolPumpPin =gpio.OpenPin(POOL_PUMP_PIN); poolPumpPin.Write(GpioPinValue.Low); poolPumpPin.SetDriveMode(GpioPinDriveMode.Output); poolWaterfallPin =gpio.OpenPin(POOL_WATERFALL_PIN); poolWaterfallPin.Write(GpioPinValue.Low); poolWaterfallPin.SetDriveMode(GpioPinDriveMode.Output); poolLightsPin =gpio.OpenPin(POOL_LIGHTS_PIN); poolLightsPin.Write(GpioPinValue.Low); poolLightsPin.SetDriveMode(GpioPinDriveMode.Output); yardLightsPin =gpio.OpenPin(YARD_LIGHTS_PIN); yardLightsPin.Write(GpioPinValue.Low); yardLightsPin.SetDriveMode(GpioPinDriveMode.Output); poolSolarValvePin =gpio.OpenPin(POOL_SOLAR_VALVE_PIN); poolSolarValvePin.Write(GpioPinValue.Low); poolSolarValvePin.SetDriveMode(GpioPinDriveMode.Output); } } //Gets the state of a device based upon it ActiveState //ActiveState means what required to turn the device on High or Low on the GPIO pin private static string GetState(GpioPinValue value, GpioPinValue ActiveState) { string state ="OFF"; if (value ==ActiveState) state ="ON"; return state; } }}
    PoolWebServer - Sensors.csC#
    Class that defines all temperature sensors and the I2C interface used to read them
    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{ //Class that defines all temperature sensors and the I2C interface used to read them them public static class Sensors { private static I2cDevice Device; private static Timer periodicTimer; //How often to read temperature data from the Arduino Mini Pro private static int ReadInterval =4000; //4000 =4 seconds //Variables to hold temperature data private static string poolTemperature ="--.--"; private static string solarTemperature ="--.--"; private static string outsideTemperature ="--.--"; //Property to expose the Temperature Data public static string PoolTemperature { get { //Lock the variable incase the timer is tring to write to it lock (poolTemperature) { return poolTemperature; } } set { //Lock the variable incase the HTTP Server is tring to read from it lock (poolTemperature) { poolTemperature =value; } } } //Property to expose the Temperature Data public static string SolarTemperature { get { //Lock the variable incase the timer is tring to write to it lock (solarTemperature) { return solarTemperature; } } set { //Lock the variable incase the HTTP Server is tring to read from it lock (solarTemperature) { solarTemperature =value; } } } //Property to expose the Temperature Data public static string OutsideTemperature { get { //Lock the variable incase the timer is tring to write to it lock (outsideTemperature) { return outsideTemperature; } } set { //Lock the variable incase the HTTP Server is tring to read from it lock (outsideTemperature) { outsideTemperature =value; } } } //Initilizes the I2C connection and starts the timer to read I2C Data async public static void InitSensors() { //Set up the I2C connection the Arduino var settings =new I2cConnectionSettings(0x40); // Arduino address settings.BusSpeed =I2cBusSpeed.StandardMode; string aqs =I2cDevice.GetDeviceSelector("I2C1"); var dis =await DeviceInformation.FindAllAsync(aqs); Device =await I2cDevice.FromIdAsync(dis[0].Id, settings); //Create a timer to periodicly read the temps from the Arduino periodicTimer =new Timer(Sensors.TimerCallback, null, 0, ReadInterval); } //Handle the time call back private static void TimerCallback(object state) { byte[] RegAddrBuf =new byte[] { 0x40 }; byte[] ReadBuf =new byte[24]; //Read the I2C connection try { Device.Read(ReadBuf); // read the data } catch (Exception) { } //Parse the response //Data is in the format "88.99|78.12|100.00" where "PoolTemp|SolarTemp|OutsideTemp" char[] cArray =System.Text.Encoding.UTF8.GetString(ReadBuf, 0, 23).ToCharArray(); // Converte Byte to Char String c =new String(cArray).Trim(); string[] data =c.Split('|'); //Write the data to temperature variables 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.csC#
    Main page of app that starts the WebServer
    // 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. AppServiceConnectionStatus was not successful."; // Initialize the AppServiceConnection appServiceConnection =new AppServiceConnection(); appServiceConnection.PackageFamilyName ="PoolWebServer_hz258y3tkez3a"; appServiceConnection.AppServiceName ="App2AppComService"; // Send a initialize request 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; }); } }}
    PoolWebService - App.xaml.csC#
    // Copyright (c) Microsoft. All rights reserved.using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Runtime.InteropServices.WindowsRuntime;using Windows.ApplicationModel;using Windows.ApplicationModel.Activation;using Windows.Foundation;using Windows.Foundation.Collections;using Windows.UI.Xaml;using Windows.UI.Xaml.Controls;using Windows.UI.Xaml.Controls.Primitives;using Windows.UI.Xaml.Data;using Windows.UI.Xaml.Input;using Windows.UI.Xaml.Media;using Windows.UI.Xaml.Navigation;namespace PoolWebService{ ///  /// Provides application-specific behavior to supplement the default Application class. ///  sealed partial class App :Application { ///  /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). ///  public App() { InitializeComponent(); Suspending +=OnSuspending; } ///  /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. ///  /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs e) {#if DEBUG if (System.Diagnostics.Debugger.IsAttached) { DebugSettings.EnableFrameRateCounter =true; }#endif Frame rootFrame =Window.Current.Content as Frame; // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame ==null) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame =new Frame(); // Set the default language rootFrame.Language =Windows.Globalization.ApplicationLanguages.Languages[0]; rootFrame.NavigationFailed +=OnNavigationFailed; if (e.PreviousExecutionState ==ApplicationExecutionState.Terminated) { //TODO:Load state from previously suspended application } // Place the frame in the current Window Window.Current.Content =rootFrame; } if (rootFrame.Content ==null) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter rootFrame.Navigate(typeof(MainPage), e.Arguments); } // Ensure the current window is active Window.Current.Activate(); } ///  /// Invoked when Navigation to a certain page fails ///  /// The Frame which failed navigation /// Details about the navigation failure void OnNavigationFailed(object sender, NavigationFailedEventArgs e) { throw new Exception("Failed to load Page " + e.SourcePageType.FullName); } ///  /// Invoked when application execution is being suspended. Application state is saved /// without knowing whether the application will be terminated or resumed with the contents /// of memory still intact. ///  /// The source of the suspend request. /// Details about the suspend request. private void OnSuspending(object sender, SuspendingEventArgs e) { var deferral =e.SuspendingOperation.GetDeferral(); //TODO:Save application state and stop any background activity deferral.Complete(); } }}
    OpenHAB SitemapJavaScript
    Sample sitemap used in openHAB configuration
    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 ItemsPlain text
    Sample items openHAB configuration
    Switch PoolPump "Pool Pump"  (grp1) {http=">[ON:POST:http:///DEVICES/POOLPUMP/ON]>[OFF:POST:http:///DEVICES/POOLPUMP/OFF] <[http:///DEVICES/POOLPUMP/STATE:1500:REGEX((.*?))]", autoupdate="true"}Switch WaterFall "Water Fall"  (grp1) {http=">[ON:POST:http:///DEVICES/WATERFALLPUMP/ON]>[OFF:POST:http:///DEVICES/WATERFALLPUMP/OFF] <[http:///DEVICES/WATERFALLPUMP/STATE:1500:REGEX((.*?))]", autoupdate="true"}Switch PoolLights "Pool Lights" (grp1) {http=">[ON:POST:http:///DEVICES/POOLLIGHTS/ON]>[OFF:POST:http:///DEVICES/POOLLIGHTS/OFF] <[http:///DEVICES/POOLLIGHTS/STATE:1500:REGEX((.*?))]", autoupdate="true"}Number pooltemp "Pool Water Temp [%.2f F]"  (grp1) {http="<[http:///SENSORS/POOLTEMP:30000:REGEX((.*?))]"}Number solartemp "Solar Water Temp [%.2f F]"  (grp1) {http="<[http:///SENSORS/SOLARTEMP:30000:REGEX((.*?))]"}Number outsidetemp "Outside Air Temp [%.2f F]"  (grp1) {http="<[http:///SENSORS/OUTSIDETEMP:30000:REGEX((.*?))]"}
    GitHub project repository
    Full Visual Studio 2015 Pool Controller projecthttps://github.com/mmackes/Windows-10-IoT-PoolController

    맞춤형 부품 및 인클로저

    Mount to hold DS18B20 waterproof sensor to monitor air temperatureMount to hold DS18B20 waterproof sensor on to standard pool pipingEnclosure for Raspberry Pi and RelaysEnclosure for Raspberry Pi and Relays

    회로도

    Schematic showing how to connect Raspberry Pi to AC relays. Controls pool pump, waterfall, pool lights and AC yard lights Schematic showing how to connect Raspberry Pi to DC relay. Controls the solar water valve. Schematic showing how to connect Raspberry Pi to Arduino Mini Pro and temperature sensors. Monitors pool water, solar heater water and outside air temperatures.

    제조공정

    1. Raspberry Pi의 온도 모니터링
    2. Raspberry Pi 2 기상 관측소
    3. Raspberry Pi로 온도 모니터링
    4. Sensorflare 및 RaspberryPi가 포함된 433MHz 스마트 홈 컨트롤러
    5. 라즈베리 파이 볼 추적
    6. Raspberry Pi 범용 리모컨
    7. Raspberry Pi를 사용한 모션 센서
    8. 라즈베리 파이 한 조각
    9. 사이클 체이서
    10. 라즈베리 파이 토양 수분 센서