├── .gitignore ├── .vscode └── extensions.json ├── ESP-131GPIO ├── ESP-131GPIO.ino └── data │ ├── favicon.png │ └── index.html ├── README.md ├── _config.yml ├── images ├── Config.png ├── GPIORelay-Wiring.jpg ├── Relay.jpg ├── Status.png ├── Test.png └── Wemos.jpg └── platformio.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ESP-131GPIO/ESP-131GPIO.ino: -------------------------------------------------------------------------------- 1 | //https://github.com/garymueller/ESP-131GPIO 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | // ***** USER SETUP STUFF ***** 16 | String ssid = "SSID"; // replace with your SSID. 17 | String password = "Password"; // replace with your password. 18 | 19 | String CONFIG_FILE = "Config.json"; 20 | DynamicJsonDocument Config(1024); 21 | AsyncWebServer server(80); 22 | AsyncDNSServer dnsServer; 23 | ESPAsyncE131 e131; 24 | 25 | //for Wemos D1 R1 pins are 16,5,4,14,12,13,0,2 26 | //vector GpioVector{16,5,4,14,12,13,0,2}; 27 | //for Wemos D1 R2 pins are 16,5,4,0,2,14,12,13 28 | vector GpioVector{16,5,4,0,2,14,12,13}; 29 | int DigitalOn = HIGH; 30 | int DigitalOff = LOW; 31 | 32 | //Forward Declarations 33 | String WebReplace(const String& var); 34 | 35 | bool LoadConfig(); 36 | void SaveConfig(AsyncWebServerRequest* request); 37 | 38 | void InitGpio(); 39 | void InitWifi(); 40 | void Init131(); 41 | void InitWeb(); 42 | 43 | /* 44 | * Sets up the initial state and starts sockets 45 | */ 46 | void setup() { 47 | Serial.begin(115200); 48 | delay(500); 49 | Serial.println("=============================="); 50 | Serial.println("Start Setup"); 51 | 52 | LoadConfig(); 53 | 54 | InitGpio(); 55 | 56 | InitWifi(); 57 | 58 | Init131(); 59 | 60 | InitWeb(); 61 | 62 | Serial.println("Completed setup. Starting loop"); 63 | Serial.println("=============================="); 64 | } 65 | 66 | /* 67 | * Main Event Loop 68 | */ 69 | void loop() { 70 | e131_packet_t packet; 71 | bool Digital = Config["GPIO"]["digital"].as(); 72 | int DigitalThreshold = Config["GPIO"]["digital_threshold"].as(); 73 | int ChannelOffset = Config["E131"]["channel_offset"].as(); 74 | 75 | while(!e131.isEmpty()) { 76 | e131.pull(&packet); // Pull packet from ring buffer 77 | uint16_t num_channels = htons(packet.property_value_count) - 1; 78 | 79 | for(int GpioIndex = 0, ChannelIndex = ChannelOffset; 80 | GpioIndex < GpioVector.size() && ChannelIndex < num_channels; 81 | ++GpioIndex, ++ChannelIndex) { 82 | 83 | uint16_t data = packet.property_values[ChannelIndex+1]; 84 | if(Digital) { 85 | digitalWrite(GpioVector[GpioIndex], (data >= DigitalThreshold) ? DigitalOn : DigitalOff); 86 | } else { 87 | analogWrite(GpioVector[GpioIndex], data); 88 | } 89 | //Serial.printf("%d:%d:%d; ",GpioIndex,data,(data > DigitalThreshold) ? DigitalOn : DigitalOff); 90 | }//for loop 91 | //Serial.println(""); 92 | }//! empty 93 | }// end void loop 94 | 95 | 96 | /* 97 | * InitGpio 98 | */ 99 | void InitGpio() 100 | { 101 | //initialize GPIO pins 102 | for(int i = 0; i < GpioVector.size(); ++i) { 103 | pinMode(GpioVector[i], OUTPUT); 104 | digitalWrite(GpioVector[i], DigitalOff); 105 | } 106 | } 107 | 108 | /* 109 | * Loads the config from SPPS into the Config variable 110 | */ 111 | bool LoadConfig() 112 | { 113 | // Initialize LittleFS 114 | if(!LittleFS.begin()){ 115 | Serial.println("An Error has occurred while mounting LittleFS"); 116 | return false; 117 | } 118 | 119 | File file = LittleFS.open(CONFIG_FILE, "r"); 120 | if (!file) { 121 | //First Run, setup defaults 122 | Config["network"]["hostname"] = "esps-" + String(ESP.getChipId(), HEX); 123 | Config["network"]["ssid"] = ssid; 124 | Config["network"]["password"] = password; 125 | Config["network"]["static"] = false; 126 | Config["network"]["static_ip"] = "192.168.1.100"; 127 | Config["network"]["static_netmask"] = "255.255.255.0"; 128 | Config["network"]["static_gateway"] = "192.168.1.1"; 129 | Config["network"]["access_point"] = true; 130 | 131 | Config["E131"]["multicast"] = "true"; 132 | Config["E131"]["universe"] = 1; 133 | Config["E131"]["channel_offset"] = 0; 134 | 135 | Config["GPIO"]["digital"] = "true"; 136 | Config["GPIO"]["digital_threshold"] = 127; 137 | Config["GPIO"]["digital_lowlevel"] = false; 138 | 139 | } else { 140 | Serial.println("Loading Configuration File"); 141 | deserializeJson(Config, file); 142 | } 143 | 144 | if(Config["GPIO"]["digital_lowlevel"].as()) { 145 | DigitalOn = LOW; 146 | DigitalOff = HIGH; 147 | } else { 148 | DigitalOn = HIGH; 149 | DigitalOff = LOW; 150 | } 151 | 152 | serializeJson(Config, Serial);Serial.println(); 153 | return true; 154 | } 155 | 156 | void SaveConfig(AsyncWebServerRequest* request) 157 | { 158 | Serial.println("Saving Configuration File"); 159 | 160 | //debug 161 | int params = request->params(); 162 | for(int i=0;igetParam(i); 164 | Serial.printf("Param: %s, %s\n", p->name().c_str(), p->value().c_str()); 165 | } 166 | 167 | //Validate Config 168 | 169 | Config["network"]["hostname"] = request->getParam("hostname",true)->value(); 170 | Config["network"]["ssid"] = request->getParam("ssid",true)->value(); 171 | Config["network"]["password"] = request->getParam("password",true)->value(); 172 | //checkbox status isnt always included if toggled off 173 | Config["network"]["static"] = 174 | (request->hasParam("static",true) && (request->getParam("static",true)->value() == "on")); 175 | Config["network"]["static_ip"] = request->getParam("static_ip",true)->value(); 176 | Config["network"]["static_netmask"] = request->getParam("static_netmask",true)->value(); 177 | Config["network"]["static_gateway"] = request->getParam("static_gateway",true)->value(); 178 | Config["network"]["access_point"] = 179 | (request->hasParam("access_point",true) && (request->getParam("access_point",true)->value() == "on")); 180 | 181 | //checkbox status isnt always included if toggled off 182 | Config["E131"]["multicast"] = 183 | (request->hasParam("multicast",true) && (request->getParam("multicast",true)->value() == "on")); 184 | Config["E131"]["universe"] = request->getParam("universe",true)->value(); 185 | Config["E131"]["channel_offset"] = request->getParam("channel_offset",true)->value(); 186 | 187 | //checkbox status isnt always included if toggled off 188 | Config["GPIO"]["digital"] = 189 | (request->hasParam("digital",true) && (request->getParam("digital",true)->value() == "on")); 190 | Config["GPIO"]["digital_threshold"] = request->getParam("digital_threshold",true)->value(); 191 | Config["GPIO"]["digital_lowlevel"] = 192 | (request->hasParam("digital_lowlevel",true) && (request->getParam("digital_lowlevel",true)->value() == "on")); 193 | 194 | File file = LittleFS.open(CONFIG_FILE, "w"); 195 | if(file) { 196 | serializeJson(Config, file); 197 | serializeJson(Config, Serial); 198 | } 199 | 200 | request->send(200); 201 | ESP.restart(); 202 | } 203 | 204 | /* 205 | * Initialiaze Wifi (DHCP/STATIC and Access Point) 206 | */ 207 | void InitWifi() 208 | { 209 | // Switch to station mode and disconnect just in case 210 | WiFi.mode(WIFI_STA); 211 | WiFi.disconnect(); 212 | 213 | WiFi.hostname(Config["network"]["hostname"].as()); 214 | 215 | //configure Static/DHCP 216 | if(Config["network"]["static"].as()) { 217 | IPAddress IP; IP.fromString(Config["network"]["static_ip"].as()); 218 | IPAddress Netmask; Netmask.fromString(Config["network"]["static_netmask"].as()); 219 | IPAddress Gateway; Gateway.fromString(Config["network"]["static_gateway"].as()); 220 | 221 | if(WiFi.config(IP, Netmask, Gateway)) { 222 | Serial.println("Successfully configured static IP"); 223 | } else { 224 | Serial.println("Failed to configure static IP"); 225 | } 226 | } else { 227 | Serial.println("Connecting with DHCP"); 228 | } 229 | 230 | //Connect 231 | int Timeout = 15000; 232 | WiFi.begin(Config["network"]["ssid"].as(), Config["network"]["password"].as()); 233 | if(WiFi.waitForConnectResult(Timeout) != WL_CONNECTED) { 234 | if(Config["network"]["access_point"].as()) { 235 | Serial.println("*** FAILED TO ASSOCIATE WITH AP, GOING SOFTAP ***"); 236 | WiFi.mode(WIFI_AP); 237 | WiFi.softAP(Config["network"]["hostname"].as()); 238 | dnsServer.start(53, "*", WiFi.softAPIP()); 239 | } else { 240 | Serial.println(F("*** FAILED TO ASSOCIATE WITH AP, REBOOTING ***")); 241 | ESP.restart(); 242 | } 243 | } else { 244 | Serial.printf("Connected as %s\n",WiFi.localIP().toString().c_str()); 245 | } 246 | 247 | WiFi.printDiag(Serial); 248 | } 249 | 250 | void Init131() 251 | { 252 | if(Config["E131"]["multicast"].as()) { 253 | Serial.printf("Initializing Multicast with universe <%d>\n",Config["E131"]["universe"].as()); 254 | e131.begin(E131_MULTICAST, Config["E131"]["universe"]); 255 | } else { 256 | Serial.println("Initializing Unicast"); 257 | e131.begin(E131_UNICAST); 258 | } 259 | } 260 | 261 | 262 | /* 263 | * Initializes the webserver 264 | */ 265 | void InitWeb() 266 | { 267 | //enables redirect to /index.html on AP connection 268 | server.onNotFound([](AsyncWebServerRequest *request){ 269 | request->send(LittleFS, "/index.html", String(), false, WebReplace); 270 | }); 271 | 272 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 273 | request->send(LittleFS, "/index.html", String(), false, WebReplace); 274 | }); 275 | server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){ 276 | request->send(LittleFS, "/favicon.png", "image/png"); 277 | }); 278 | server.on("/SaveConfig", HTTP_POST, [](AsyncWebServerRequest *request){ 279 | SaveConfig(request); 280 | }); 281 | server.on("/SetRelay", HTTP_GET, [](AsyncWebServerRequest *request){ 282 | int relay = request->getParam("relay")->value().toInt(); 283 | if(relay<0 || relay >= GpioVector.size()) { 284 | Serial.println("SetRelay - Index out of range"); 285 | return; 286 | } 287 | digitalWrite(GpioVector[relay], (request->getParam("checked")->value() == "true") ? DigitalOn : DigitalOff); 288 | request->send(200); 289 | }); 290 | 291 | server.begin(); 292 | } 293 | 294 | /* 295 | * WebReplace: 296 | * Substitutes variables inside of the html 297 | */ 298 | String WebReplace(const String& var) 299 | { 300 | //Status Page 301 | if (var == "SSID") { 302 | return (String)WiFi.SSID(); 303 | } else if (var == "HOSTNAME") { 304 | return (String)WiFi.hostname(); 305 | } else if (var == "IP") { 306 | if(WiFi.getMode() == WIFI_AP) { 307 | return WiFi.softAPIP().toString(); 308 | } else { 309 | return WiFi.localIP().toString(); 310 | } 311 | } else if (var == "MAC") { 312 | return (String)WiFi.macAddress(); 313 | } else if (var == "RSSI") { 314 | return (String)WiFi.RSSI(); 315 | } else if (var == "HEAP") { 316 | return (String)ESP.getFreeHeap(); 317 | } else if (var == "UPTIME") { 318 | return String(millis()); 319 | } else if (var == "UNIVERSE") { 320 | return Config["E131"]["universe"]; 321 | } else if (var == "PACKETS") { 322 | return (String)e131.stats.num_packets; 323 | } else if (var == "PACKET_ERR") { 324 | return (String)e131.stats.packet_errors; 325 | } else if (var == "LAST_IP") { 326 | return e131.stats.last_clientIP.toString(); 327 | 328 | //Configuration Page 329 | } else if (var == "CONFIG_HOSTNAME") { 330 | return Config["network"]["hostname"]; 331 | } else if (var == "CONFIG_SSID") { 332 | return Config["network"]["ssid"]; 333 | } else if (var == "CONFIG_PASSWORD") { 334 | return Config["network"]["password"]; 335 | } else if (var == "CONFIG_AP") { 336 | if(Config["network"]["access_point"].as()) 337 | return "checked"; 338 | else 339 | return ""; 340 | } else if (var == "CONFIG_STATIC") { 341 | if(Config["network"]["static"].as()) 342 | return "checked"; 343 | else 344 | return ""; 345 | } else if (var == "CONFIG_STATIC_IP") { 346 | return Config["network"]["static_ip"]; 347 | } else if (var == "CONFIG_STATIC_NETMASK") { 348 | return Config["network"]["static_netmask"]; 349 | } else if (var == "CONFIG_STATIC_GATEWAY") { 350 | return Config["network"]["static_gateway"]; 351 | } else if (var == "CONFIG_MULTICAST") { 352 | if(Config["E131"]["multicast"].as()) 353 | return "checked"; 354 | else 355 | return ""; 356 | } else if (var == "CONFIG_UNIVERSE") { 357 | return Config["E131"]["universe"]; 358 | } else if (var == "CONFIG_CHANNEL_OFFSET") { 359 | return Config["E131"]["channel_offset"]; 360 | } else if (var == "CONFIG_DIGITAL") { 361 | if(Config["GPIO"]["digital"].as()) 362 | return "checked"; 363 | else 364 | return ""; 365 | } else if (var == "CONFIG_THRESHOLD") { 366 | return Config["GPIO"]["digital_threshold"]; 367 | } else if (var == "CONFIG_LOWLEVEL") { 368 | if(Config["GPIO"]["digital_lowlevel"].as()) 369 | return "checked"; 370 | else 371 | return ""; 372 | 373 | //Relay Page 374 | } else if (var == "RELAYS") { 375 | String Relays = ""; 376 | for(int i = 0; i < GpioVector.size(); ++i) { 377 | Relays += ""; 378 | Relays += "