Ampelmännchen 은 동독의 인기 있는 상징으로 모든 길모퉁이의 보행자 신호등에 표시됩니다. 그들의 디자인에서 영감을 받은 베를린 기반의 소매 체인도 있습니다.
저는 벼룩시장에서 중고 Ampelmann 신호 세트를 발견했고 이를 휴대폰으로 제어하고 싶었습니다. 당신도 똑같이 하고 싶다면 계속 읽어보세요!
건강 경고: 이 프로젝트는 220V 주 전원을 사용합니다. 나는 전기 기술자가 아닙니다. 귀하의 책임 하에 다음 지침을 따르십시오.
나는 베를린의 미학을 담은 무언가를 만들고 싶었고, 내 집에 오는 방문객들이 즐겁게 소통할 수 있는 공간이 되고 싶었습니다. 안타깝게도 올해 방문객 수가 급격히 감소했지만 2021년에도 여전히 좋은 반응을 기대하고 있습니다...?
로컬 네트워크의 각 장치에는 자체 개인 IP 주소(예: 192.168.1.20
)가 있습니다. FritzBox와 같은 일부 라우터에서는 로컬 호스트 이름으로 탐색할 수도 있으므로 192.168.1.20
mydevice.fritz.box
에서도 액세스할 수 있습니다. 신호등의 경우 장치 호스트 이름은 traffic-light
이므로 http://traffic-light.fritz.box
에서 방문할 수 있습니다.
webapp은 매우 간단한 반응형 단일 페이지 애플리케이션입니다. 그것은 다음을 보여줍니다:
코드는 /webapp 디렉토리에 있습니다. CSS 변환, WebSocket 및 XHR과 같은 표준 브라우저 기능에만 의존하므로 외부 종속성이 필요하지 않습니다. 물론 내 LAN에 있지 않기 때문에 아무것도 제어할 수는 없지만 여기에서 실행 중인 애플리케이션을 미리 볼 수 있습니다. 로컬 네트워크(예: http://traffic-light.fritz.box
에서 방문했다면 완벽하게 작동할 것입니다.
페이지가 로드되면 애플리케이션은 /api/status
에서 현재 상태를 찾기 위해 GET 요청을 한 다음 포트 81
에서 서버에 대한 WebSocket 연결을 엽니다. 여러 클라이언트의 동기화를 유지하기 위해 후속 상태 업데이트는 항상 WebSocket을 통해 제공됩니다. websocket 이벤트가 도착할 때마다 단일 전역 state
객체에 변경 사항을 적용합니다. 잠시 후 updateScreen()
메서드는 이러한 변경 사항을 DOM에 적용합니다.
시작 시 터치 이벤트 또는 클릭 이벤트를 처리하기 위해 사용자가 모바일 또는 데스크톱 장치에 있는지도 감지합니다. 우리는 실제로 touchend
이벤트를 사용하여 서버에 명령을 보냅니다. 왜냐하면 이것이 iPhone X에서 더 안정적으로 수행되었기 때문입니다. Safari를 종료하기 위해 화면 하단에서 위로 스와이프하면 touchstart
이벤트가 발생하여 전원을 켜지 않고는 앱을 종료할 수 없게 되었습니다. 초록불!
마지막으로, 우리는 가능한 한 서버의 부하를 줄이고 싶습니다. ESP8266은 RAM이 약 50kB 에 불과한 80MHz 프로세서에서 실행된다는 점을 기억하세요. 그것은 강력한 장치가 아닙니다. 따라서 브라우저가 비활성 상태이면 웹소켓 연결을 끊습니다. 탭이나 브라우저가 다시 열리면 다시 상태를 확인하고 WebSocket을 다시 연결합니다.
ESP8266은 API 요청과 타이밍 코드를 처리하느라 바쁘기 때문에 웹앱 자체를 제공하는 데 필요한 리소스가 없습니다. 또한 업데이트를 적용할 때마다 하드웨어에 물리적으로 연결해야 한다면 웹앱의 외관을 변경하는 것이 어렵습니다.
웹앱의 index.html은 모든 것이 Javascript로 렌더링되어야 한다는 단일 페이지 애플리케이션 원칙을 따르므로 HTML 콘텐츠 자체가 매우 작아집니다. 550바이트 정도 작습니다. 다른 모든 것은 서버를 추가로 호출할 필요 없이 클라이언트 브라우저에 의해 로드됩니다. 따라서 웹앱은 실제로 무료 정적 사이트 호스팅 도구인 GitHub 페이지에서 전체적으로 호스팅됩니다. /index.html
누르면 실제로 GitHub 페이지에 프록시 요청이 이루어지고 결과가 클라이언트 브라우저에 반환됩니다.
이제 웹앱에서 무엇이든 변경할 수 있으며 서버는 영향을 받지 않습니다. 엄청난! 글쎄요, 거의...
이 웹앱의 코드 대부분은 index.html
자체가 아닌 CSS 및 JS 파일에 있습니다. 브라우저는 로드된 파일을 다시 요청하기 전에 불확실한 기간 동안 캐시합니다. index.html이 변경되지 않았지만 새 JS 버전을 배포한 경우 클라이언트가 새 JS 버전을 로드해야 한다는 것을 어떻게 알 수 있습니까?
새로운 버전의 코드를 git master
브랜치에 푸시하면 GitHub 작업이 실행되어 페이지가 실제로 대중에게 제공되는 GitHub 페이지에 배포가 실행됩니다. 여기서 중요한 점은 index.html
에서 자체 CSS 및 JS 파일 끝에 ?version=latest
접미사를 추가하는 것입니다. 콘텐츠를 gh-pages
브랜치에 복사하기 전에 작업은 sed
명령을 사용하여 해당 " latest
"을 실제로 master
브랜치의 마지막 커밋 ID인 $GITHUB_SHA
변수의 값으로 바꿉니다. (예: b43200422c4f5da6dd70676456737e5af46cb825
와 같은 값)
그런 다음 다음에 클라이언트가 웹앱을 방문하면 브라우저는 ?version=
뒤에 새로운 다른 값을 확인하고 아직 캐시되지 않은 업데이트된 새 JS 또는 CSS 파일을 요청합니다.
실제로 어떻게 작동하는지에 대해서는 traffic-light-controller.ino
의 setup(void)
메소드와 Arduino 코드 섹션을 참조하세요.
나는 REST와 WebSocket을 동시에 사용하기로 결정했습니다. REST는 주로 클라이언트가 서버를 제어하는 데 사용됩니다. WebSocket은 상태 정보를 클라이언트에 브로드캐스트하는 데 사용됩니다. REST API를 쉽게 실험할 수 있는 Postman과 같은 도구가 많기 때문에 이것이 더 편리하다는 것을 알았습니다.
HTTP API: 여기에서 Swagger 설명서를 참조하세요.
WebSocket API: websocket 연결은 webapp이 내부 상태를 업데이트하는 데 사용하는 JSON blob을 보냅니다. websocket 이벤트에는 업데이트할 필드가 하나 이상 포함될 수 있습니다. 환경 정보가 포함된 예는 다음과 같습니다.
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
현재 웹소켓을 통해 클라이언트에서 서버로 데이터가 전송되지 않습니다. 그러나 이것이 가능합니다.
Arduino 코드는 설명 주석이 포함된 단일 파일 내에 모두 들어 있습니다.
이는 핀 위치, 라이브러리 가져오기, HTTP 콘텐츠 유형 및 응답 코드 값과 같은 하드코딩된 값에 대한 정의 세트로 시작됩니다. 그 다음에는 런타임에 변경될 수 있는 변수 세트가 있으며, 모두 밑줄로 시작됩니다. 웹 서버, 웹 소켓 서버, WiFi 클라이언트 및 온도 센서를 포함하여 몇 가지 개체도 여기에서 초기화됩니다. "시스템 시계"는 _currentMillis
필드에 의해 유지됩니다.
부팅 후 setup(void)
메소드가 실행됩니다. 일부 핀 설정을 수행한 후 REST 엔드포인트에 필요한 매핑을 생성하고 클라이언트 요청을 수신하는 서버를 시작합니다. loop(void)
메소드는 다른 모든 것을 담당합니다. 각 주기마다 보류 중인 웹 요청을 처리하고 리듬 주기를 업데이트하며 필요한 경우 센서를 읽습니다. 파티 모드에 있으면 현재 플래시/펄스 상태가 설정됩니다.
파티 모드용 리듬은 RHYTHM_PATTERN
필드의 시퀀스를 재생하기 위해 하드코딩되어 있지만 이론적으로는 런타임에 다른 것으로 변경될 수 있습니다. rhythm()
메서드를 호출할 때마다 현재 _bpm
및 _currentMillis
값을 사용하여 패턴에서 어떤 위치에 있어야 하는지 계산합니다. 이는 _rhythmStep
필드에 저장됩니다.
리듬 패턴 중에는 두 릴레이가 실제로 꺼지는 기간이 있습니다. 하지만 조명은 백열전구이기 때문에 빛 방출이 즉시 시작되거나 중단되지는 않습니다. 전구가 완전히 켜지거나 꺼지는 데 약 1.7초가 걸리는 것 같습니다. 따라서 둘 다 꺼지는 패턴 내에 기간을 추가하면 전구가 예열되고 식을 때 부드러운 펄스 패턴이 생성됩니다.
partyFlash()
메소드에서 현재 표시되어야 하는(또는 둘 다 꺼져야 하는) 패턴 항목을 가져오고 적절한 매개변수를 사용하여 lightSwitch(...)
호출합니다. lightSwitch(...)
는 차례로 sendToWebSocketClients(...)
호출하여 연결된 모든 클라이언트가 새 상태로 업데이트됩니다.
사용자가 조명 중 하나를 클릭하여 켜거나 끄는 경우 프로세스는 유사하지만 REST 요청으로 처리됩니다. 요청의 유효성을 검사하는 handleX
메서드 중 하나가 호출되고 차례로 lightSwitch(...)
가 호출됩니다.
좀 더 빈번한 간격으로 두 엔클로저의 온도를 확인하고 이를 WebSocket을 통해 모든 클라이언트에 보냅니다. 이는 현재 정보 제공 목적으로만 사용되지만 온도가 일부 안전 한계를 초과하면 조명을 비활성화하는 데 사용될 수 있습니다.
이 기사를 검토하는 데 도움을 준 @mrcosta에게 감사드립니다.