Skip to content

Instantly share code, notes, and snippets.

@tsohr
Last active May 2, 2026 08:48
Show Gist options
  • Select an option

  • Save tsohr/88600163787137d7996166d50d7df89c to your computer and use it in GitHub Desktop.

Select an option

Save tsohr/88600163787137d7996166d50d7df89c to your computer and use it in GitHub Desktop.
Tiny ESP8266 button controller for a Home Assistant fan entity. It uses the WebSocket API to track state and toggle half, full, and off modes while keeping credentials in a private token.h file.

ESP8266 Home Assistant 팬 버튼

Home Assistant 팬 엔티티를 제어하는 작은 ESP8266 버튼 컨트롤러입니다. Home Assistant WebSocket API로 팬 상태를 추적하고, 약풍, 강풍, 꺼짐 모드를 전환합니다.

개요

이 스케치는 ESP8266 보드를 fan.fan_switch_module 같은 Home Assistant 팬 엔티티용 물리 버튼 컨트롤러로 사용합니다.

장치는 Wi-Fi에 연결한 뒤 Home Assistant WebSocket API로 인증하고, 팬 상태 업데이트를 구독하며, 버튼이 눌리면 call_service 명령을 보냅니다.

Home Assistant에서 직접 팬을 조작한 경우도 처리합니다. Home Assistant가 새 percentage 속성 없이 on 상태만 보내면, 스케치는 WebSocket 스트림에서 마지막으로 본 percentage 값을 사용합니다.

동작

  • 버튼을 누르면 약풍, 강풍, 꺼짐 순서로 전환합니다.
  • 약풍은 fan.turn_onpercentage: 52를 보냅니다.
  • 강풍은 fan.turn_onpercentage: 100을 보냅니다.
  • 꺼짐은 fan.turn_off를 보냅니다.
  • LED 출력은 현재 팬 상태를 표시합니다.
  • 약풍은 30분 뒤 자동으로 꺼집니다.
  • 강풍은 10분 뒤 자동으로 꺼집니다.

상태 매핑

Home Assistant에서 들어오는 팬 상태는 다음과 같이 해석합니다.

  • off: 꺼짐.
  • on + percentage >= 75: 강풍.
  • on + percentage >= 50 그리고 < 75: 약풍.
  • on + percentage < 50: 꺼짐.

압축된 subscribe_entities 이벤트에서는 Home Assistant가 a.percentage를 반복하지 않고 s: "on"만 보낼 수 있습니다. 이 경우 스케치는 WebSocket 이벤트에서 마지막으로 관찰한 percentage 값을 사용합니다. 이렇게 하면 Home Assistant에서 수동으로 팬을 켰을 때 unknown 상태로 빠지는 것을 방지할 수 있습니다.

파일

  • sketch_dec1a.ino: 메인 Arduino 스케치.
  • token.h: 로컬 비공개 설정 파일. 커밋하지 마세요.

비공개 설정

로컬에 본인 환경에 맞는 token.h 파일을 만듭니다.

#ifndef __TOKEN__
#define __TOKEN

const char* TOKEN = "Bearer YOUR_HOME_ASSISTANT_LONG_LIVED_ACCESS_TOKEN";
const char* TARGET = "fan.fan_switch_module";
const char* HA_URL = "https://your-home-assistant.example/";

#ifndef STASSID
#define STASSID "YOUR_WIFI_SSID"
#define STAPSK "YOUR_WIFI_PASSWORD"
#endif

#endif

의존성

  • ESP8266 Arduino core
  • Arduino_JSON
  • ArduinoWebsockets

빌드 메모

이 스케치는 ESP8266 Arduino core의 esp8266:esp8266:nodemcuv2 보드 프로파일로 컴파일 검증했습니다.

보안 메모

token.h 또는 .bin, .elf, .map 같은 컴파일 산출물을 공개하지 마세요. 컴파일된 펌웨어에는 Home Assistant 토큰과 Wi-Fi 정보가 포함될 수 있습니다.

토큰을 한 번이라도 공개 저장소에 올린 적이 있다면, Home Assistant에서 해당 토큰을 폐기하고 새 토큰을 발급하세요.

ESP8266 Home Assistant Fan Button

Tiny ESP8266 button controller for a Home Assistant fan entity. It uses the Home Assistant WebSocket API to track fan state and toggle half, full, and off modes.

Overview

This sketch turns an ESP8266 board into a physical button controller for a Home Assistant fan entity such as fan.fan_switch_module.

The device connects to Wi-Fi, authenticates to Home Assistant over the WebSocket API, subscribes to fan state updates, and sends call_service commands when the button is pressed.

It also handles fan changes made directly from Home Assistant. When Home Assistant sends an on state without a fresh percentage attribute, the sketch falls back to the last percentage value seen on the WebSocket stream.

Behavior

  • Button press cycles the fan through half power, full power, and off.
  • Half power sends fan.turn_on with percentage: 52.
  • Full power sends fan.turn_on with percentage: 100.
  • Off sends fan.turn_off.
  • The LED output reflects the current fan state.
  • Half power automatically turns off after 30 minutes.
  • Full power automatically turns off after 10 minutes.

State Mapping

Incoming Home Assistant fan state is mapped as follows:

  • off: off.
  • on with percentage >= 75: full power.
  • on with percentage >= 50 and < 75: half power.
  • on with percentage < 50: off.

For compact subscribe_entities events, Home Assistant may send only s: "on" without repeating a.percentage. In that case, the sketch uses the last percentage value observed from WebSocket events. This keeps manual Home Assistant fan toggles from falling into an unknown state.

Files

  • sketch_dec1a.ino: Main Arduino sketch.
  • token.h: Local private configuration. Do not commit this file.

Private Configuration

Create a local token.h file with your own values:

#ifndef __TOKEN__
#define __TOKEN

const char* TOKEN = "Bearer YOUR_HOME_ASSISTANT_LONG_LIVED_ACCESS_TOKEN";
const char* TARGET = "fan.fan_switch_module";
const char* HA_URL = "https://your-home-assistant.example/";

#ifndef STASSID
#define STASSID "YOUR_WIFI_SSID"
#define STAPSK "YOUR_WIFI_PASSWORD"
#endif

#endif

Dependencies

  • ESP8266 Arduino core
  • Arduino_JSON
  • ArduinoWebsockets

Build Notes

The sketch has been verified with the ESP8266 Arduino core using the esp8266:esp8266:nodemcuv2 board profile.

Security Notes

Do not publish token.h or compiled firmware artifacts such as .bin, .elf, or .map files. The compiled firmware may contain your Home Assistant token and Wi-Fi credentials.

If a token was ever pushed publicly, revoke it in Home Assistant and create a new one.

#include <Arduino_JSON.h>
#include <Arduino.h>
#include "token.h"
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ArduinoWebsockets.h>
#define STATE_HALF 1
#define STATE_FULL 2
#define STATE_OFF 0
#define STATE_UNKNOWN -1
int last_state = STATE_UNKNOWN;
long last_getStateStamp = 0;
const long debounce_getState = 30 * 1000;
long autooff_schedule = 0;
int conseq_error_count = 0;
// -----------------------------------------------------------
#define ledPin 13
#define buttonPin 14
int buttonState; // the current reading from the input pin
int lastButtonState = LOW; // the previous reading from the input pin
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
void ledOn() {
digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level
}
void ledOff() {
digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH
}
void ledToggle() {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Change the state of the LED
}
// -----------------------------------------------------------
ESP8266WiFiMulti WiFiMulti;
websockets::WebsocketsClient webSocket;
String haWsHost;
String haWsPath = "/api/websocket";
uint16_t haWsPort = 443;
bool haWsSecure = true;
bool wsStarted = false;
bool wsAuthenticated = false;
int wsNextId = 1;
int wsEntitySubscriptionId = 0;
int wsEventSubscriptionId = 0;
int pendingRequestId = 0;
bool pendingRequestDone = false;
bool pendingRequestSuccess = false;
int last_percentage = -1;
int last_ws_percentage = -1;
bool wsConnectAttempted = false;
unsigned long lastWsConnectAttempt = 0;
const unsigned long wsAuthTimeout = 5000;
const unsigned long wsCommandTimeout = 5000;
const unsigned long wsStateTimeout = 3000;
const unsigned long wsReconnectInterval = 5000;
void setupWifi() {
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(STASSID, STAPSK);
Serial.println("setup() done connecting to ssid '" STASSID "'");
}
bool clock_done = false;
void clock_sync() {
if (clock_done) {
return;
}
// Set time via NTP, as required for x.509 validation
configTime(3 * 3600, 0, "kr.pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
clock_done = true;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT);
ledOn();
analogWrite(ledPin, 128);
ledOn();
Serial.begin(9600);
setupWifi();
long now = millis();
lastDebounceTime = now;
last_getStateStamp = now - debounce_getState;
}
String homeAssistantAccessToken() {
String token = TOKEN;
token.trim();
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}
return token;
}
void parseHomeAssistantUrl() {
String url = HA_URL;
url.trim();
String lower = url;
lower.toLowerCase();
int schemeEnd = lower.indexOf("://");
int restStart = 0;
if (schemeEnd >= 0) {
String scheme = lower.substring(0, schemeEnd);
haWsSecure = scheme == "https" || scheme == "wss";
restStart = schemeEnd + 3;
}
String rest = url.substring(restStart);
int slash = rest.indexOf('/');
String authority = slash >= 0 ? rest.substring(0, slash) : rest;
String basePath = slash >= 0 ? rest.substring(slash) : "/";
int colon = authority.lastIndexOf(':');
if (colon > 0) {
haWsHost = authority.substring(0, colon);
haWsPort = (uint16_t)authority.substring(colon + 1).toInt();
} else {
haWsHost = authority;
haWsPort = haWsSecure ? 443 : 80;
}
if (haWsPort == 0) {
haWsPort = haWsSecure ? 443 : 80;
}
if (basePath.length() == 0) {
basePath = "/";
}
if (!basePath.endsWith("/")) {
basePath += "/";
}
haWsPath = basePath + "api/websocket";
}
bool readPercentage(JSONVar attributes, int &percentage) {
if (JSON.typeof(attributes) != "object") {
return false;
}
if (!attributes.hasOwnProperty("percentage")) {
return false;
}
percentage = (int)attributes["percentage"];
return true;
}
int stateFromPercentage(int percentage) {
if (percentage >= 75) {
return STATE_FULL;
}
if (percentage >= 50) {
return STATE_HALF;
}
autooff_schedule = 0; // no job required.
return STATE_OFF;
}
int stateFromHaValues(const char *state, int percentage, bool hasPercentage) {
if (state == NULL) {
return STATE_UNKNOWN;
}
if (hasPercentage) {
last_percentage = percentage;
last_ws_percentage = percentage;
}
if (strncasecmp(state, "off", 3) == 0) {
autooff_schedule = 0; // no job required.
return STATE_OFF;
}
if (strncasecmp(state, "on", 2) == 0) {
if (!hasPercentage && last_ws_percentage >= 0) {
percentage = last_ws_percentage;
hasPercentage = true;
} else if (!hasPercentage && last_percentage >= 0) {
percentage = last_percentage;
hasPercentage = true;
}
if (!hasPercentage) {
return STATE_UNKNOWN;
}
Serial.printf("percentage: %d\n", percentage);
return stateFromPercentage(percentage);
}
return STATE_UNKNOWN;
}
bool applyHaStateValues(const char *state, int percentage, bool hasPercentage) {
int parsed = stateFromHaValues(state, percentage, hasPercentage);
if (parsed == STATE_UNKNOWN) {
Serial.println("[WS] state is unknown.");
last_state = STATE_UNKNOWN;
last_getStateStamp = millis();
return false;
}
last_state = parsed;
last_getStateStamp = millis();
conseq_error_count = 0;
Serial.printf("[WS] %s -> %d\n", TARGET, last_state);
return true;
}
bool updateStateFromStateObject(JSONVar entity) {
if (JSON.typeof(entity) != "object") {
return false;
}
if (!entity.hasOwnProperty("state")) {
return false;
}
const char *state = (const char *)entity["state"];
int percentage = -1;
bool hasPercentage = false;
if (entity.hasOwnProperty("attributes")) {
JSONVar attributes = entity["attributes"];
hasPercentage = readPercentage(attributes, percentage);
}
return applyHaStateValues(state, percentage, hasPercentage);
}
bool updateStateFromCompactObject(JSONVar compact) {
if (JSON.typeof(compact) != "object") {
return false;
}
const char *state = NULL;
bool hasState = false;
if (compact.hasOwnProperty("s")) {
state = (const char *)compact["s"];
hasState = true;
}
int percentage = -1;
bool hasPercentage = false;
if (compact.hasOwnProperty("a")) {
JSONVar attributes = compact["a"];
hasPercentage = readPercentage(attributes, percentage);
}
if (hasState) {
return applyHaStateValues(state, percentage, hasPercentage);
}
if (hasPercentage) {
return applyHaStateValues("on", percentage, true);
}
return false;
}
bool sendJson(JSONVar payload, bool logPayload) {
String payload_str = JSON.stringify(payload);
if (logPayload) {
Serial.printf("[WS] send: %s\n", payload_str.c_str());
}
return webSocket.send(payload_str);
}
bool sendJson(JSONVar payload) {
return sendJson(payload, true);
}
void sendAuth() {
String token = homeAssistantAccessToken();
JSONVar payload;
payload["type"] = "auth";
payload["access_token"] = token.c_str();
Serial.println("[WS] send auth.");
sendJson(payload, false);
}
void subscribeStateEvents() {
wsEventSubscriptionId = wsNextId++;
JSONVar payload;
payload["id"] = wsEventSubscriptionId;
payload["type"] = "subscribe_events";
payload["event_type"] = "state_changed";
sendJson(payload);
}
void subscribeTargetEntity() {
wsEntitySubscriptionId = wsNextId++;
JSONVar entityIds;
entityIds[0] = TARGET;
JSONVar payload;
payload["id"] = wsEntitySubscriptionId;
payload["type"] = "subscribe_entities";
payload["entity_ids"] = entityIds;
sendJson(payload);
}
void handleResultMessage(JSONVar message) {
if (!message.hasOwnProperty("id")) {
return;
}
int id = (int)message["id"];
bool success = true;
if (message.hasOwnProperty("success")) {
success = (bool)message["success"];
}
if (id == pendingRequestId) {
pendingRequestSuccess = success;
pendingRequestDone = true;
}
if (id == wsEntitySubscriptionId && !success && wsEventSubscriptionId == 0) {
Serial.println("[WS] subscribe_entities failed; falling back to state_changed events.");
subscribeStateEvents();
}
}
void handleCompactEntityMap(JSONVar entities) {
if (JSON.typeof(entities) != "object") {
return;
}
if (!entities.hasOwnProperty(TARGET)) {
return;
}
JSONVar targetEntity = entities[TARGET];
updateStateFromCompactObject(targetEntity);
}
void handleCompactEntityChange(JSONVar changes) {
if (JSON.typeof(changes) != "object") {
return;
}
if (!changes.hasOwnProperty(TARGET)) {
return;
}
JSONVar targetChange = changes[TARGET];
if (targetChange.hasOwnProperty("+")) {
JSONVar added = targetChange["+"];
updateStateFromCompactObject(added);
}
}
void handleStateChangedEvent(JSONVar event) {
if (JSON.typeof(event) != "object") {
return;
}
if (!event.hasOwnProperty("data")) {
return;
}
JSONVar data = event["data"];
if (!data.hasOwnProperty("entity_id")) {
return;
}
const char *entityId = (const char *)data["entity_id"];
if (entityId == NULL || strcmp(entityId, TARGET) != 0) {
return;
}
if (data.hasOwnProperty("new_state")) {
JSONVar newState = data["new_state"];
updateStateFromStateObject(newState);
}
}
void handleEventMessage(JSONVar message) {
if (!message.hasOwnProperty("event")) {
return;
}
JSONVar event = message["event"];
if (event.hasOwnProperty("a")) {
handleCompactEntityMap(event["a"]);
return;
}
if (event.hasOwnProperty("c")) {
handleCompactEntityChange(event["c"]);
return;
}
handleStateChangedEvent(event);
}
void onWsMessage(websockets::WebsocketsMessage message) {
String payload = message.data();
Serial.printf("[WS] recv: %s\n", payload.c_str());
JSONVar root = JSON.parse(payload);
if (JSON.typeof(root) == "undefined") {
Serial.println("[WS] JSON parse failed.");
return;
}
if (!root.hasOwnProperty("type")) {
return;
}
const char *messageType = (const char *)root["type"];
if (messageType == NULL) {
return;
}
if (strcmp(messageType, "auth_required") == 0) {
sendAuth();
} else if (strcmp(messageType, "auth_ok") == 0) {
Serial.println("[WS] authenticated.");
wsAuthenticated = true;
conseq_error_count = 0;
subscribeTargetEntity();
} else if (strcmp(messageType, "auth_invalid") == 0) {
Serial.println("[WS] authentication failed.");
wsAuthenticated = false;
conseq_error_count++;
} else if (strcmp(messageType, "result") == 0) {
handleResultMessage(root);
} else if (strcmp(messageType, "event") == 0) {
handleEventMessage(root);
}
}
void onWsEvent(websockets::WebsocketsEvent event, String data) {
if (event == websockets::WebsocketsEvent::ConnectionOpened) {
Serial.println("[WS] connected.");
wsAuthenticated = false;
} else if (event == websockets::WebsocketsEvent::ConnectionClosed) {
Serial.println("[WS] disconnected.");
wsAuthenticated = false;
pendingRequestDone = pendingRequestId != 0;
pendingRequestSuccess = false;
wsEntitySubscriptionId = 0;
wsEventSubscriptionId = 0;
} else if (event == websockets::WebsocketsEvent::GotPing) {
Serial.println("[WS] got ping.");
} else if (event == websockets::WebsocketsEvent::GotPong) {
Serial.println("[WS] got pong.");
}
}
bool startHaWebSocket() {
if (!wsStarted) {
parseHomeAssistantUrl();
webSocket.onMessage(onWsMessage);
webSocket.onEvent(onWsEvent);
if (haWsSecure) {
webSocket.setInsecure();
}
wsStarted = true;
}
if (webSocket.available()) {
return true;
}
unsigned long now = millis();
if (wsConnectAttempted && now - lastWsConnectAttempt < wsReconnectInterval) {
return false;
}
wsConnectAttempted = true;
lastWsConnectAttempt = now;
wsAuthenticated = false;
pendingRequestDone = pendingRequestId != 0;
pendingRequestSuccess = false;
wsEntitySubscriptionId = 0;
wsEventSubscriptionId = 0;
Serial.printf("[WS] begin... %s://%s:%u%s\n",
haWsSecure ? "wss" : "ws",
haWsHost.c_str(),
haWsPort,
haWsPath.c_str());
bool connected = false;
if (haWsSecure) {
connected = webSocket.connectSecure(haWsHost, haWsPort, haWsPath);
} else {
connected = webSocket.connect(haWsHost, haWsPort, haWsPath);
}
if (!connected) {
Serial.println("[WS] connect failed.");
}
return connected;
}
void pollHaWebSocket() {
startHaWebSocket();
if (webSocket.available()) {
webSocket.poll();
}
}
bool ensureHaReady(unsigned long timeoutMs) {
unsigned long start = millis();
while (!wsAuthenticated && millis() - start < timeoutMs) {
pollHaWebSocket();
delay(1);
}
return wsAuthenticated;
}
bool waitForState(unsigned long timeoutMs) {
unsigned long start = millis();
while (last_state == STATE_UNKNOWN && millis() - start < timeoutMs) {
pollHaWebSocket();
delay(1);
}
return last_state != STATE_UNKNOWN;
}
int getState(bool waitForFresh) {
if (last_state != STATE_UNKNOWN) {
return last_state;
}
if (!waitForFresh) {
return last_state;
}
if (!ensureHaReady(wsAuthTimeout)) {
conseq_error_count++;
return STATE_UNKNOWN;
}
waitForState(wsStateTimeout);
return last_state;
}
int getState() {
return getState(false);
}
bool waitForServiceResult(unsigned long timeoutMs) {
unsigned long start = millis();
while (!pendingRequestDone && millis() - start < timeoutMs) {
pollHaWebSocket();
delay(1);
}
bool ok = pendingRequestDone && pendingRequestSuccess;
pendingRequestId = 0;
pendingRequestDone = false;
pendingRequestSuccess = false;
return ok;
}
void updateLocalStateAfterCommand(int state) {
long now = millis();
last_getStateStamp = now;
last_state = state;
if (state == STATE_HALF) {
last_percentage = 52;
autooff_schedule = now + 30 * 60 * 1000; // 30min
} else if (state == STATE_FULL) {
last_percentage = 100;
autooff_schedule = now + 10 * 60 * 1000; // 10min
} else {
last_percentage = -1;
autooff_schedule = 0;
}
}
bool setState(int state) {
if (!ensureHaReady(wsAuthTimeout)) {
Serial.println("[WS] not ready.");
conseq_error_count++;
return false;
}
int requestId = wsNextId++;
JSONVar serviceData;
serviceData["entity_id"] = TARGET;
JSONVar payload;
payload["id"] = requestId;
payload["type"] = "call_service";
payload["domain"] = "fan";
if (state == STATE_HALF || state == STATE_FULL) {
payload["service"] = "turn_on";
serviceData["percentage"] = state == STATE_HALF ? 52 : 100;
} else {
payload["service"] = "turn_off";
}
payload["service_data"] = serviceData;
pendingRequestId = requestId;
pendingRequestDone = false;
pendingRequestSuccess = false;
if (!sendJson(payload)) {
pendingRequestId = 0;
conseq_error_count++;
return false;
}
if (!waitForServiceResult(wsCommandTimeout)) {
Serial.println("[WS] service call failed or timed out.");
conseq_error_count++;
return false;
}
updateLocalStateAfterCommand(state);
conseq_error_count = 0;
return true; // control OK.
}
void onButton(long now) {
Serial.println("button pressed.");
// last_getStateStamp = now - debounce_getState - 1;
int state = getState(true);
if (state == STATE_HALF) {
setState(STATE_FULL);
} else if (state == STATE_FULL) {
setState(STATE_OFF);
} else {
setState(STATE_HALF);
}
}
void readButton() {
long now = millis();
int reading = digitalRead(buttonPin);
if (reading != lastButtonState) {
lastDebounceTime = now;
}
if ((now - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) {
onButton(now);
}
}
}
// save the reading. Next time through the loop, it'll be the lastButtonState:
lastButtonState = reading;
}
int last_wifi_state = -1;
void loop() {
long now = millis();
if (conseq_error_count > 10) {
ESP.reset();
return;
}
int wifi_state = WiFiMulti.run();
if (last_wifi_state != wifi_state) {
Serial.printf("wifi state: %d -> %d\n", last_wifi_state, wifi_state);
last_wifi_state = wifi_state;
}
if (wifi_state != WL_CONNECTED) {
wsAuthenticated = false;
ledToggle();
return;
}
int fade_value = (int)(abs((now % 8000) - 4000) * 80 / 4000 + (255-80));
analogWrite(LED_BUILTIN, fade_value);
clock_sync();
pollHaWebSocket();
readButton();
if (autooff_schedule > 0 && now > autooff_schedule) {
if (setState(STATE_OFF)) {
digitalWrite(ledPin, 0);
} else {
analogWrite(ledPin, 128);
}
autooff_schedule = 0;
}
int state = getState();
if (state == STATE_OFF) {
digitalWrite(ledPin, 0);
} else if (state == STATE_HALF) {
if (now % 1000 < 500) {
analogWrite(ledPin, 50);
} else {
analogWrite(ledPin, 20);
}
} else if (state == STATE_FULL) {
analogWrite(ledPin, 128);
} else {
if (now % 2000 < 1400) {
digitalWrite(ledPin, 0);
} else {
digitalWrite(ledPin, 1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment